根据bigchaindb源码分析(三)所述,在初始化后端存储后,命令bigchaindb start
所触发执行的_run_init
函数将创建创世区块
# commands/bigchaindb.py
def _run_init():
# Try to access the keypair, throws an exception if it does not exist
b = bigchaindb.Bigchain()
schema.init_database(connection=b.connection)
b.create_genesis_block()
logger.info('Genesis block created.')
创建创世区块的具体实现在core.py
,其首先查找目前后端存储中已有的区块数目,如果存在区块,则说明创世区块已经创建好,抛出异常返回,否则则新建创世区块,并写入后端存储中
# core.py
def create_genesis_block(self):
blocks_count = backend.query.count_blocks(self.connection)
if blocks_count:
raise exceptions.GenesisBlockAlreadyExistsError('Cannot create the Genesis block')
block = self.prepare_genesis_block()
self.write_block(block)
return block
查询已有区块数目代码如下。调用的count_blocks
的原型在backend/query.py
。该函数有装饰器singledispatch,在源码分析(三)中,我们对该装饰器进行了详细的分析,其作用在于实现根据参数列表的不同类型来调用该函数原型,相当于实现函数多态。
# core.py
blocks_count = backend.query.count_blocks(self.connection)
# backend/query.py
@singledispatch
def count_blocks(connection):
raise NotImplementedError
由于形参self.connection
实际上为位于bigchaindb.backend.mongodb.connection
模块中的MongoDBConnection
类,根据源码分析(三),我们可以分析得到真正执行的函数为bigchaindb.backend.mongodb.query
中的count_blocks
函数,并且实参为MongoDBConnection
类的实例
@register_query(MongoDBConnection)
def count_blocks(conn):
return conn.run(
conn.collection('bigchain')
.count())
conn.collection('bigchain').count()
故将调用MongoDBConnection.collection
,conn.run
将调用MongoDBConnection.run
# bigchaindb.backend.mongodb.connection.MongoDBConnection
def query(self):
return Lazy()
def collection(self, name):
return self.query()[self.dbname][name]
def run(self, query):
try:
try:
return query.run(self.conn)
except ...
except ...
将这些函数组合起来,count_blocks
相当于调用了Lazy()[self.dbname]['bigchain'].count().run(self.conn)
。其中self.dbname
按照我们的配置文件,值为bigchain
,而self.conn
是一个已经通过了验证的pymongo.MongoClient
实例(源码分析(三)中有说明)
这里涉及到了Lazy
类,我们就先来看这个类吧
Lazy类位于bigchaindb/utils.py
,其代码如下。Lazy类维护了名为stack的list,挡在触发__getattr__
、__call__
、__getitem__
时,会将参数等加入到这个list中,从而形成了一条记录链。而run函数则是对于输入的实参instance,依次执行这个记录链。估计是因为可以暂缓执行记录链(直到调用run
),所以才取名为Lazy吧。。
class Lazy:
def __init__(self):
"""Instantiate a new Lazy object."""
self.stack = []
def __getattr__(self, name):
self.stack.append(name)
return self
def __call__(self, *args, **kwargs):
self.stack.append((args, kwargs))
return self
def __getitem__(self, key):
self.stack.append('__getitem__')
self.stack.append(([key], {}))
return self
def run(self, instance):
"""Run the recorded chain of methods on `instance`. Args: instance: an object. """
last = instance
for item in self.stack:
if isinstance(item, str):
last = getattr(last, item)
else:
last = last(*item[0], **item[1])
self.stack = []
return last
现在再来仔细分析count_blocks
所执行的代码,即Lazy()[self.dbname]['bigchain'].count().run(self.conn)
[]
运算符会调用__getitem__
,因此,Lazy()[self.dbname]['bigchain']
执行完成后,Lazy实例中的stack应该为['__getitem__', ([self.dbname], {}), '__getitem__', (['bigchain'], {})]
__getattr__
来查找名为name的函数,然后调用__call__
来调用name函数,因此,Lazy实例中的stack应该为['__getitem__', ([self.dbname], {}), '__getitem__', (['bigchain'], {}), 'count', ((), {})]
__getattr__
或者__call__
,而是直接执行run函数。run函数首先执行两次__getitem__
,然后再执行一次count,故而实际上是把stack中记录的操作重新记录一遍,因此执行的也就为# ::代表对某个对象进行操作
pymongo.MongoClient::[self.dbname]['bigchain'].count()
所以获取区块的数目其实就是查看表bigchain中的记录数目。。那么,为什么要使用Lazy类呢?估计是为了防止在连续调用时数据可能会发生变化而导致某些问题?并不清楚。。以后再来思考这个问题
若未查找到已有区块,则说明应当要创建创世区块,对应的最外层代码为
# core.py create_genesis_block
block = self.prepare_genesis_block()
self.write_block(block)
prepare_genesis_block
首先创建一个Transaction类,并指定签名者为本节点(self.me
为本节点的公钥),接受者也为本节点,云数据为{'message': 'Hello World from the BigchainDB'}
,之后将该事务的operation设置为GENESIS
,代表创世区块。bigchaindb中的事务有三类,其中create
表示新建资产,transfer
表示转移资源,而genesis
则特指创世区块。接着再利用sign函数来对事务进行签名
def prepare_genesis_block(self):
"""Prepare a genesis block."""
metadata = {'message': 'Hello World from the BigchainDB'}
transaction = Transaction.create([self.me], [([self.me], 1)],
metadata=metadata)
transaction.operation = 'GENESIS'
transaction = transaction.sign([self.me_private])
# create the block
return self.create_block([transaction])
在创建事务完成后,再使用Block类来根据事务创建区块,创建时指明了生成区块的节点(self.me
)、时间戳以及投票者。投票者为配置文件中keyring所指定的节点(self.nodes_except_me
)以及本节点。区块创建成功后,再使用本节点的私钥进行签名
federation = property(lambda self: set(self.nodes_except_me + [self.me]))
def create_block(self, validated_transactions):
# Prevent the creation of empty blocks
if not validated_transactions:
raise exceptions.OperationError('Empty block creation is not '
'allowed')
voters = list(self.federation)
block = Block(validated_transactions, self.me, gen_timestamp(), voters)
block = block.sign(self.me_private)
return block
在区块成功创建并被签名后,bigchaindb首先调用decouple_assets
将区块里的事务中的资产asset去重,并设置资产的id为创建该资产的事务的id,之后调用后端存储的write_assets
与write_block
方法来将资产与事务写入到后端存储中
def write_block(self, block):
# Decouple assets from block
assets, block_dict = block.decouple_assets()
# write the assets
if assets:
self.write_assets(assets)
# write the block
return backend.query.write_block(self.connection, block_dict)
def write_assets(self, assets):
return backend.query.write_assets(self.connection, assets)
写资产与写区块的实现代码均位于backend/mongodb/query.py
中。根据之前对Lazy类的分析,这两个函数实际上就是分别将资产插入到assets表中、将区块写入到bigchain
表中
@register_query(MongoDBConnection)
def write_assets(conn, assets):
try:
return conn.run(
conn.collection('assets')
.insert_many(assets, ordered=False))
except OperationError:
return
@register_query(MongoDBConnection)
def write_block(conn, block_dict):
return conn.run(
conn.collection('bigchain')
.insert_one(block_dict))
至此,创建创世区块步骤完成,基本流程也就为:创建事务->对事务签名->根据事务创建区块->对区块进行签名->获取区块里的资产,设置资产的id为事务id->写资产->写事务。关于事务、区块、资产还有目前没有提到的投票的细节,我们之后在调用API写bigchaindb时再来分析