bigchaindb源码分析(四)——创建创世区块

根据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.collectionconn.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'], {})]
  • 之后再调用count函数,此时将首先调用__getattr__来查找名为name的函数,然后调用__call__来调用name函数,因此,Lazy实例中的stack应该为
['__getitem__', ([self.dbname], {}), '__getitem__', (['bigchain'], {}), 'count', ((), {})]
  • 最后再来调用run函数,run函数已经被定义了,因此不会再调用__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_assetswrite_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时再来分析

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值