整个功能架构如下图所示。
包括三大组件:区块链服务(Blockchain)、链码服务(Chaincode)、成员权限管理(Membership)。
区块链服务提供一个分布式账本平台。一般地,多个交易被打包进区块中,多个区块构成一条区块链。区块链代表的是账本状态机发生变更的历史过程。
交易意味着围绕着某个链码进行操作。
交易可以改变世界状态。
交易中包括的内容主要有:
交易的数据结构(Protobuf 格式)定义为
message Transaction {
enum Type {
UNDEFINED = 0;
// deploy a chaincode to the network and call `Init` function
CHAINCODE_DEPLOY = 1;
// call a chaincode `Invoke` function as a transaction
CHAINCODE_INVOKE = 2;
// call a chaincode `query` function
CHAINCODE_QUERY = 3;
// terminate a chaincode; not implemented yet
CHAINCODE_TERMINATE = 4;
}
Type type = 1;
//store ChaincodeID as bytes so its encrypted value can be stored
bytes chaincodeID = 2;
bytes payload = 3;
bytes metadata = 4;
string uuid = 5;
google.protobuf.Timestamp timestamp = 6;
ConfidentialityLevel confidentialityLevel = 7;
string confidentialityProtocolVersion = 8;
bytes nonce = 9;
bytes toValidators = 10;
bytes cert = 11;
bytes signature = 12;
}
在 1.0 架构中,一个 transaction 包括如下信息:
[ledger] [channel], proposal:[chaincode, <function name, arguments>] endorsement:[proposal hash, simulation result, signature]
区块打包交易,确认交易后的世界状态。
一个区块中包括的内容主要有:
注意具体的交易信息并不存放在区块中。
区块的数据结构(Protobuf 格式)定义为
message Block {
uint32 version = 1;
google.protobuf.Timestamp timestamp = 2;
repeated Transaction transactions = 3;
bytes stateHash = 4;
bytes previousBlockHash = 5;
bytes consensusMetadata = 6;
NonHashData nonHashData = 7;
}
一个真实的区块内容示例:
{
"nonHashData": {
"localLedgerCommitTimestamp": {
"nanos": 975295157,
"seconds": 1466057539
},
"transactionResults": [
{
"uuid": "7be1529ee16969baf9f3156247a0ee8e7eee99a6a0a816776acff65e6e1def71249f4cb1cad5e0f0b60b25dd2a6975efb282741c0e1ecc53fa8c10a9aaa31137"
}
]
},
"previousBlockHash": "RrndKwuojRMjOz/rdD7rJD/NUupiuBuCtQwnZG7Vdi/XXcTd2MDyAMsFAZ1ntZL2/IIcSUeatIZAKS6ss7fEvg==",
"stateHash": "TiIwROg48Z4xXFFIPEunNpavMxnvmZKg+yFxKK3VBY0zqiK3L0QQ5ILIV85iy7U+EiVhwEbkBb1Kb7w1ddqU5g==",
"transactions": [
{
"chaincodeID": "CkdnaXRodWIuY29tL2h5cGVybGVkZ2VyL2ZhYnJpYy9leGFtcGxlcy9jaGFpbmNvZGUvZ28vY2hhaW5jb2RlX2V4YW1wbGUwMhKAATdiZTE1MjllZTE2OTY5YmFmOWYzMTU2MjQ3YTBlZThlN2VlZTk5YTZhMGE4MTY3NzZhY2ZmNjVlNmUxZGVmNzEyNDlmNGNiMWNhZDVlMGYwYjYwYjI1ZGQyYTY5NzVlZmIyODI3NDFjMGUxZWNjNTNmYThjMTBhOWFhYTMxMTM3",
"payload": "Cu0BCAESzAEKR2dpdGh1Yi5jb20vaHlwZXJsZWRnZXIvZmFicmljL2V4YW1wbGVzL2NoYWluY29kZS9nby9jaGFpbmNvZGVfZXhhbXBsZTAyEoABN2JlMTUyOWVlMTY5NjliYWY5ZjMxNTYyNDdhMGVlOGU3ZWVlOTlhNmEwYTgxNjc3NmFjZmY2NWU2ZTFkZWY3MTI0OWY0Y2IxY2FkNWUwZjBiNjBiMjVkZDJhNjk3NWVmYjI4Mjc0MWMwZTFlY2M1M2ZhOGMxMGE5YWFhMzExMzcaGgoEaW5pdBIBYRIFMTAwMDASAWISBTIwMDAw",
"timestamp": {
"nanos": 298275779,
"seconds": 1466057529
},
"type": 1,
"uuid": "7be1529ee16969baf9f3156247a0ee8e7eee99a6a0a816776acff65e6e1def71249f4cb1cad5e0f0b60b25dd2a6975efb282741c0e1ecc53fa8c10a9aaa31137"
}
]
}
世界观用于存放链码执行过程中涉及到的状态变量,是一个键值数据库。典型的元素为 [chaincodeID, ckey]: value
结构。
为了方便计算变更后的 hash 值,一般采用默克尔树数据结构进行存储。树的结构由两个参数(numBuckets
和 maxGroupingAtEachLevel
)来进行初始配置,并由 hashFunction
配置决定存放键值到叶子节点的方式。显然,各个节点必须保持相同的配置,并且启动后一般不建议变动。
numBuckets
:叶子节点的个数,每个叶子节点是一个桶(bucket),所有的键值被 hashFunction
散列分散到各个桶,决定树的宽度;maxGroupingAtEachLevel
:决定每个节点由多少个子节点的 hash 值构成,决定树的深度。其中,桶的内容由它所保存到键值先按照 chaincodeID 聚合,再按照升序方式组成。
一般地,假设某桶中包括 $$ M $$ 个 chaincodeID,对于 $$ chaincodeID_i $$,假设其包括 $$ N $$ 个键值对,则聚合 $$G_i$$ 内容可以计算为:
$$ G_i = Len(chaincodeID_i) + chaincodeIDi + N + \sum{1}^{N} {len(key_j) + key_j + len(value_j) + value_j} $$
该桶的内容则为
$$ bucket = \sum_{1}^{M} G_i $$
注:这里的 +
代表字符串拼接,并非数学运算。
链码包含所有的处理逻辑,并对外提供接口,外部通过调用链码接口来改变世界观。
链码需要实现 Chaincode 接口,以被 VP 节点调用。
type Chaincode interface { Init(stub *ChaincodeStub, function string, args []string) ([]byte, error) Invoke(stub *ChaincodeStub, function string, args []string) ([]byte, error) Query(stub *ChaincodeStub, function string, args []string) ([]byte, error)}
链码目前支持的交易类型包括:部署(Deploy)、调用(Invoke)和查询(Query)。
不同链码之间可能互相调用和查询。
在实现上,链码需要运行在隔离的容器中,超级账本采用了 Docker 作为默认容器。
对容器的操作支持三种方法:build、start、stop,对应的接口为 VM。
type VM interface {
build(ctxt context.Context, id string, args []string, env []string, attachstdin bool, attachstdout bool, reader io.Reader) error
start(ctxt context.Context, id string, args []string, env []string, attachstdin bool, attachstdout bool) error
stop(ctxt context.Context, id string, timeout uint, dontkill bool, dontremove bool) error
}
链码部署成功后,会创建连接到部署它的 VP 节点的 gRPC 通道,以接受后续 Invoke 或 Query 指令。
VP 节点和容器之间通过 gRPC 消息来交互。消息基本结构为
message ChaincodeMessage {
enum Type { UNDEFINED = 0; REGISTER = 1; REGISTERED = 2; INIT = 3; READY = 4; TRANSACTION = 5; COMPLETED = 6; ERROR = 7; GET_STATE = 8; PUT_STATE = 9; DEL_STATE = 10; INVOKE_CHAINCODE = 11; INVOKE_QUERY = 12; RESPONSE = 13; QUERY = 14; QUERY_COMPLETED = 15; QUERY_ERROR = 16; RANGE_QUERY_STATE = 17; }
Type type = 1; google.protobuf.Timestamp timestamp = 2; bytes payload = 3; string uuid = 4;}
当发生链码部署时,容器启动后发送 REGISTER
消息到 VP 节点。如果成功,VP 节点返回 REGISTERED
消息,并发送 INIT
消息到容器,调用链码中的 Init 方法。
当发生链码调用时,VP 节点发送 TRANSACTION
消息到容器,调用其 Invoke 方法。如果成功,容器会返回 RESPONSE
消息。
类似的,当发生链码查询时,VP 节点发送 QUERY
消息到容器,调用其 Query 方法。如果成功,容器会返回 RESPONSE
消息。
通过基于 PKI 的成员权限管理,平台可以对接入的节点和客户端的能力进行限制。
证书有三种,Enrollment,Transaction,以及确保安全通信的 TLS 证书。
目前,VP 节点执行了所有的操作,包括接收交易,进行交易验证,进行一致性达成,进行账本维护等。这些功能的耦合导致节点性能很难进行扩展。
新的思路就是对这些功能进行解耦,让每个功能都相对单一,容易进行扩展。社区内已经有了一些讨论。
Fabric 1.0 的设计采用了适当的解耦,根据功能将节点角色解耦开,让不同节点处理不同类型的工作负载。