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
开始,其payload
为HelloMessage
对象:
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
是节点的密码学IDaddress
是节点的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_BLOCKS
、SYNC_STATE_GET_SNAPSHOT
或者SYNC_STATE_GET_DELTAS
,相应地接收SYNC_BLOCKS
、SYNC_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}
的集合。