Michael.W谈hyperledger Fabric第26期-详细带读Fabric的源码11-世界状态在代码级别的行为实现

1 世界状态的底层依赖

关于世界状态的定义可以去我之前写的一篇帖子《Michael.W谈hyperledger Fabric第3期-关于Fabric你所需要知道的基本知识二》中看一下。
世界状态存储在状态数据库中,我在这里只谈默认的levelDB做状态数据库的情况,其源代码文件文职:core/ledger/kvledger/txmgmt/statedb/stateleveldb/stateleveldb.go

2 上层智能合约的键值对与底层存储的键值对是如何相关联的?

这涉及到数据隔离问题。

levelDB没有多数据库特性,所有的KV键值对都是存储在同样的地方。这就为不同通道的数据存储带来了问题,比如两个通道中都有kv对:michael=10,那到底要如何进行数据隔离呢?
看一下源代码:

	// 组合件的分割符[1]
	var compositeKeySep = []byte{0x00} 
	var lastKeyIndicator = byte(0x01)
	var savePointKey = []byte{0x00}
  • [1] Fabric将智能合约键智能合约所在的通道名智能合约的名字拼接在一起,形成一个组合键。然后用这个组合键作为底层数据库存储的唯一键。看一下组合键的过程:
    	func constructCompositeKey(ns string, key string) []byte {
    	  // 参数ns是前面讲过的namespace,由账本ID(对应通道)和链码名组合而成。
    	  // 将namespace和key值拼接在一起,中间插入分隔符compositeKeySep
    		return append(append([]byte(ns), compositeKeySep...), []byte(key)...)
    	}
    
    这样就保证底层数据库中的键值唯一性。再来看看反解组合键的过程:
    	func splitCompositeKey(compositeKey []byte) (string, string) {
      	// 以compositeKeySep为基准将组合件分成两个部分
    	split := bytes.SplitN(compositeKey, compositeKeySep, 2)
    	return string(split[0]), string(split[1])
    }
    

3 如何持久化区块的状态信息

不多bb,直接看源码:

	// 该方法实现了VersionedDB接口
	func (vdb *versionedDB) ApplyUpdates(batch *statedb.UpdateBatch, height *version.Height) error {
		// 关于参数:
		// batch:前面在交易验证中返回的待更新的状态集
		// height:当前的区块高度
		  
		// 实例化了一个levelDB数据库的事务,可以理解为一个新的状态容器
		dbBatch := leveldbhelper.NewUpdateBatch()
	  	// 获取到待更新的状态集中包含的所有namespace
		namespaces := batch.GetUpdatedNamespaces()
	  	// 遍历这些待更新的namespace
		for _, ns := range namespaces {
	    // 根据namespace得到这个namespace对应的需要更新的具体状态
	    	// updates类型为:map[string]*VersionedValue
			updates := batch.GetUpdates(ns)
			for k, vv := range updates {
	      	// 形成组合组合键值。可见key值就包含在namespace对应的需要更新的具体状态中
				compositeKey := constructCompositeKey(ns, k)
				logger.Debugf("Channel [%s]: Applying key=[%#v]", vdb.dbName, compositeKey)
				// 如果对应的更新状态为空,表示这是一个删除操作。
				if vv.Value == nil {
					dbBatch.Delete(compositeKey)
				} else {
	        	// 将这个组合键和对应的状态更新到新的状态容器dbBatch中
					dbBatch.Put(compositeKey, statedb.EncodeValue(vv.Value, vv.Version))
				}
			}
		}
		// 当整个更新状态集都被处理完成之后,将0x00作为键值,当前区块高度作为值,也添加到状态容器dbBatch中。用来标识这个区块结束
		dbBatch.Put(savePointKey, height.ToBytes())
	  	// 对以上事务进行提交
		if err := vdb.db.WriteBatch(dbBatch, true); err != nil {
			return err
		}
		return nil
	}

4 如何记录最新存储的区块状态

	func (vdb *versionedDB) GetLatestSavePoint() (*version.Height, error) {
	  	// 返回数据库中savePointKey对应的值(即区块高度),这个是被序列化过的
		versionBytes, err := vdb.db.Get(savePointKey)
		if err != nil {
			return nil, err
		}
		if versionBytes == nil {
			return nil, nil
		}
	  	// 反序列化的区块高度
		version, _ := version.NewHeightFromBytes(versionBytes)
	  	// 返回区块高度对象
		return version, nil
	}

参数中的区块高度类的定义如下:

	type Height struct {
	  	// 区块号
		BlockNum uint64	
	  	// 区块中的交易编号
		TxNum    uint64
	}

剩下还有一些辅助功能的方法:

	// 处理富文本查询的方法。由于levelDB无法做这类查询,所以直接返回错误。如果想进行富文本查询,请换couchDB来做数据库
	func (vdb *versionedDB) ExecuteQuery(namespace, query string) (statedb.ResultsIterator, error) {
		return nil, errors.New("ExecuteQuery not supported for leveldb")
	}
	// 查询单个键对应的值
	func (vdb *versionedDB) GetState(namespace string, key string) (*statedb.VersionedValue, error) {
		logger.Debugf("GetState(). ns=%s, key=%s", namespace, key)
		compositeKey := constructCompositeKey(namespace, key)
		dbVal, err := vdb.db.Get(compositeKey)
		if err != nil {
			return nil, err
		}
		if dbVal == nil {
			return nil, nil
		}
		val, ver := statedb.DecodeValue(dbVal)
		return &statedb.VersionedValue{Value: val, Version: ver}, nil
	}
	// 查询多个键对应值。逻辑很简单
	func (vdb *versionedDB) GetStateMultipleKeys(namespace string, keys []string) ([]*statedb.VersionedValue, error) {
		vals := make([]*statedb.VersionedValue, len(keys))
		for i, key := range keys {
			val, err := vdb.GetState(namespace, key)
			if err != nil {
				return nil, err
			}
			vals[i] = val
		}
		return vals, nil
	}

关于操作levelDB的源代码逻辑不是很难,基本仔细看一遍就能明白!

ps:
本人热爱图灵,热爱中本聪,热爱V神,热爱一切被梨花照过的姑娘。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!
后现代泼痞浪漫主义奠基人
公众号名称:后现代泼痞浪漫主义奠基人

发布了49 篇原创文章 · 获赞 23 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/michael_wgy_/article/details/88697619