Hyperledger详解(3)协议

Fabric的点对点通讯是基于gRPC构建的,实现了基于流的双向消息通讯。gRPC使用了谷歌公司的Protocol Buffers对数据结构进行串行化来实现节点之间的数据传输。Protocol buffers是一种语言中立、平台中立、并且可扩展的数据结构串行化技术。数据结构、消息和服务都是用proto3语言描述。

3.1 消息

节点之间传输的消息是由Message这个proto结构封装的,有四种不同的类型:发现(Discovery)、交易(Transaction)、同步(Synchronization)和共识(Consensus)。每种类型都可以在其内嵌的payload字段中定义更多的子类型。

message Message {
    enum Type {
        UNDEFINED = 0;

        DISC_HELLO = 1;
        DISC_DISCONNECT = 2;
        DISC_GET_PEERS = 3;
        DISC_PEERS = 4;
        DISC_NEWMSG = 5;

        CHAIN_STATUS = 6;
        CHAIN_TRANSACTION = 7;
        CHAIN_GET_TRANSACTIONS = 8;
        CHAIN_QUERY = 9;

        SYNC_GET_BLOCKS = 11;
        SYNC_BLOCKS = 12;
        SYNC_BLOCK_ADDED = 13;

        SYNC_STATE_GET_SNAPSHOT = 14;
        SYNC_STATE_SNAPSHOT = 15;
        SYNC_STATE_GET_DELTAS = 16;
        SYNC_STATE_DELTAS = 17;

        RESPONSE = 20;
        CONSENSUS = 21;
    }
    Type type = 1;
    bytes payload = 2;
    google.protobuf.Timestamp timestamp = 3;
}

payload字段是一个包含其他对象(Transaction或者Response)的不透明字节数组,例如,当type是CHAIN_TRANSACTION时,payload是Transaction对象。

3.1.1 发现消息

在节点启动之后,如果CORE_PEER_DISCOVERY_ROOTNODE字段存在则运行发现协议。CORE_PEER_DISCOVERY_ROOTNODE是另外一个网路上节点(任意节点)的IP地址,这个节点可以作为发现网络上其他所有节点的起点。协议序列从DISC_HELLO开始,其payloadHelloMessage对象:

message HelloMessage {
    PeerEndpoint peerEndpoint = 1;
    uint64 blockNumber = 2;
}
message PeerEndpoint {
    PeerID ID = 1;
    string address = 2;
    enum Type {
        UNDEFINED = 0;
        VALIDATOR = 1;
        NON_VALIDATOR = 2;
    }
    Type type = 3;
    bytes pkiID = 4;
}

message PeerID {
    string name = 1;
}
  • PeerId是节点启动的时候被赋予的名字,也可以在配置文件中定义。
  • PeerEndpoint用来描述验证节点或者非验证节点
  • pkiID是节点的密码学ID
  • address是节点的IP地址和端口(ip:port)
  • blockNumber是节点上区块链的当前高度

如果通过DISC_HELLO获取的区块高度超过了当前节点的区块链高度,则节点立即启动同步协议将节点状态更新到网络当前最新状态。

DISC_HELLO之后,节点周期性地发送DISC_GET_PEERS以发现新加入到网络的其他节点。作为对DISC_GET_PEERS消息的响应,收到消息的节点发送'DISC_PEERS'消息,该消息中的payload字段包含了一组PeerEndpoint。其他发现消息目前未使用。

3.1.2 交易消息

存在3种交易类型:部署(Deploy)、调用(Invoke)和查询(Query)。部署交易将指定的链码安装到区块链网络,而调用和查询交易则是调用已经部署到区块链上的链码函数。还有一种创建型(Create)交易用于将已经部署到区块链上的链码实例化并载入内存,但目前该交易类型未实现。

3.1.2.1 交易数据结构

message Transaction {
    enum Type {
        UNDEFINED = 0;
        CHAINCODE_DEPLOY = 1;
        CHAINCODE_INVOKE = 2;
        CHAINCODE_QUERY = 3;
        CHAINCODE_TERMINATE = 4;
    }
    Type type = 1;
    string uuid = 5;
    bytes chaincodeID = 2;
    bytes payloadHash = 3;

    ConfidentialityLevel confidentialityLevel = 7;
    bytes nonce = 8;
    bytes cert = 9;
    bytes signature = 10;

    bytes metadata = 4;
    google.protobuf.Timestamp timestamp = 6;
}

message TransactionPayload {
    bytes payload = 1;
}

enum ConfidentialityLevel {
    PUBLIC = 0;
    CONFIDENTIAL = 1;
}

3.1.3 同步消息

当节点通过发现协议发现自己比区块链网络落后或者不一致,则马上开启同步协议。节点广播SYNC_GET_BLOCKSSYNC_STATE_GET_SNAPSHOT或者SYNC_STATE_GET_DELTAS,相应地接收SYNC_BLOCKSSYNC_STATE_SNAPSHOT或者SYNC_STATE_DELTAS

已安装的共识机制插件决定同步协议如何执行,每种消息针对不同的情况:

SYNC_GET_BLOCKS在消息的payload字段中请求一组连续范围的区块,这种范围使用SyncBlockRange表示:

message SyncBlockRange {
    uint64 start = 1;
    uint64 end = 2;
}

接收到SYNC_GET_BLOCKS的节点响应SYNC_BLOCKS消息,在其payload字段中包含了SyncBlocks:

message SyncBlocks {
    SyncBlockRange range = 1;
    repeated Block blocks = 2;
}

3.1.4 共识消息

共识机制用于处理交易,因此CONSENSUS消息由内部的共识框架在接收到CHAIN_TRANSACTION消息后发送。共识框架将CHAIN_TRANSACTION转化为CONSENSUS,然后向所有验证节点进行广播。共识插件接收到消息后,根据自身算法对消息进行处理。

3.2 账簿

账簿(ledger)由区块链(blockchain)和世界状态(world state)这两个部分组成。区块链是一组链接起来的区块,用来在账簿中记录交易。世界状态是键值数据库,在链码执行过程中用来存储状态。

3.2.1 区块链

3.2.1.1 区块

区块链被定义为一组连接在一起的区块,因为每个区块都包含链中前一个区块的Hash。区块中另外2个重要信息是交易列表,以及区块中所有交易执行完成后的世界状态的Hash值。

message Block {
  version = 1;
  google.protobuf.Timestamp timestamp = 2;
  bytes transactionsHash = 3;
  bytes stateHash = 4;
  bytes previousBlockHash = 5;
  bytes consensusMetadata = 6;
  NonHashData nonHashData = 7;
}

message BlockTransactions {
  repeated Transaction transactions = 1;
}
  • transactionsHash是区块内交易列表merkle根的hash值
  • stateHash是世界状态merkle根的hash值
  • previousBlockHash是前一个区块的hash值
  • BlockTransactions.transactions是交易消息的数组,交易列表由于大小的关系并不直接包含在区块内部。

3.2.2 世界状态

节点中的世界状态是所有已部署的链码的状态集合,实际上是键值对{chaincodeID, ckey}的集合。