layout | title | permalink | group | language |
---|---|---|---|---|
default |
CSL 应用层消息 |
/technical/protocols/csl-application-level/cn/ |
cn-technical-protocols |
cn |
在本章中,我们探讨卡尔达诺结算层的消息传递。本章的目的是如何将所有的部分(如 Time-Warp, Network-Transport, 和 Kademlia DHT)组合在一起,实现完整的卡尔达诺结算层节点。
阅读源代码时,你经常会遇到这样的东西:
-- | 'GetHeaders' message (see protocol specification).
data MsgGetHeaders = MsgGetHeaders
{ -- not guaranteed to be in any particular order
mghFrom :: ![HeaderHash]
, mghTo :: !(Maybe HeaderHash)
} deriving (Generic, Show, Eq)
instance Message MsgGetHeaders where
messageName _ = varIntMName 4
formatMessage _ = "GetHeaders"
怎么阅读这些?首先,我们来看看这个 instance
部分。这个特定的片段是说,有类型定义的数据结构 MsgGetHeaders
被当做消息有效载荷。这样的消息的名字是 "GetHeaders"
。
在这个特定的案例中,数据结构有两个字段:mghFrom
和 mghTo
。使用 mgh
这样的前缀,是因为 Haskell 把记录字段的符号放在全局名字空间中,所有程序员有责任避免冲突。
应该指出的是,有时你会看到使用类型变量 ssc
进行参数化的消息。这是为了使与我们进行共享种子计算的方式在代码上是多态的。这里是一个消息的例子,首先发送最新的头部,记作 ssc
。
消息序列化的方式可以在 Pos.Binary.Communication
模块看到。
每个消息类型都应该有一个 Message
类型类的实例。请参阅 Time-Warp-NT 指南了解更多信息。
卡尔达诺结算层的大部分消息都是 Inv/Req/Data
标准化的(参见 Pos.Communication.Relay
模块)。在这个框架内,我们定义了三种数据类型:
-- | Inventory message. Can be used to announce the fact that you have
-- some data.
data InvMsg key = InvMsg
{ imKey :: !key
}
deriving (Show, Eq)
-- | Request message. Can be used to request data (ideally data which
-- was previously announced by inventory message).
data ReqMsg key = ReqMsg
{ rmKey :: !key
}
deriving (Show, Eq)
-- | Data message. Can be used to send actual data.
data DataMsg contents = DataMsg
{ dmContents :: !contents
}
deriving (Show, Eq)
这里:
key
表示节点标示符的类型。contents
表示实际消息有效载荷的类型。
为了通过 Inv/Req/Data
引入新消息,应该创建两种类型:这个消息的 key
和 contents
,然后为它们都实现 MessagePart
。
class MessagePart a where
pMessageName :: Proxy a -> MessageName
这里,pMessageName
是一个特定消息类型的标识符。
InvMsg key
的 Message
的类型类,ReqMsg key
和 DataMsg contents
自动从 MessagePart
类型类派生出特定的键和内容。
请参阅 Pos.Communication.Message
模块了解使用 Inv/Req/Data
的消息例子。
该表格解释了 Pos.Block.Network.Types
模块。
Message type | Payload | Comments |
---|---|---|
MsgGetHeaders |
Header hash checkpoints (optional newest hash we're interested in) | Expect newest header first |
MsgGetBlocks |
Oldest header hash (newest hash) | Both hashes have to be present |
MsgHeaders |
Non-empty collection of block headers, newest first | Polymorphic in ssc |
MsgBlock |
A single block | Polymorphic in ssc |
有关详细信息,请参阅二进制协议。
所有消息都有给定的名字,因为使用完整的类型名称超过了限度。每个名称是一个或两个 UnsignedVarInt
编码的串联。
该表包含所有使用的消息部分的名称。这些名字也可以在 Pos.Communication.Message
模块中找到。为了区分整数加法,连接在这里表示为 (++)
Message type | Message name |
---|---|
MsgGetHeaders | 4 |
MsgHeaders | 5 |
MsgGetBlocks | 6 |
MsgBlock | 7 |
ReqMsg | 9 ++ pMessageName key |
MempoolMsg | 10 ++ pMessageName tag |
DataMsg | 11 ++ pMessageName contents |
InvMsg | 12 ++ pMessageName key |
Message part type | Name |
---|---|
TxMsgContents |
0 |
(UpdateProposal , [UpdateVote ]) |
1 |
UpdateVote |
2 |
因为 cardano-sl
随附系统更新协议,消息格式也可以改变。所以节点应该知道其他节点正在运行的协议。了解这些信息,节点可以选择一个消息名称发送给对等体。该消息表名 PeerData
会在握手期间的每个对话动作之前被发送。二进制协议章节中 PeerData
描述了确切的二进制格式。
消息具有最大长度限制。不同类型的消息具有不同的限制,在 Pos.Communication.Limits
模块中有定义。
委派是这样一个功能,它允许一个叫做 issuer 的权益所有人让另一个权益所有人(称为委托人)来代表它来生成块。
为此,issuer 应该创建代理签名密钥,允许委托人签署代替 issuer 的区块。任何权益所有人都可以验证代理签名密钥实际上是由特定权益所有人通过特定代理发布的,并且该密钥在某个时间段是有效的。
委派可以有两种类型:per-epoch 委派和可撤销的长期证书的授权。每个 Per-epoch 委派称为『轻量级』,而长期的委派称为『重量级』。
请阅读『卡尔达诺结算层的权益委派』获取更多信息。
警告:目前,轻量级委派已被禁用,并将在 Shelley 版本进行重新整理,因此下面的信息可能已过时。
轻量级委派允许委派人替代 issuer 在一定范围内的 epochs 生成区块(这个范围是签名密钥指定的)
为此,issuer 应该通过网络发送包含时间范围的消息,issuer 密钥,委派公钥和证书。来自网络的每个节点都会收到这条消息,并可以稍后检查生成该块的人是否合适。轻量级委派数据存储在内存中,在一段时间后会被删除(在配置文件中定义)。
这种授权类型可以用于在发行人知道某个时间范围内不存在的情况下将生成区块的权利委托给某个可信任的节点。
重量级委派有两个目的:
每一个特定的权益所有人最多能与一个代表分享权益。为了撤销证书,节点应该创建一个新的证书,并将其自身同时作为颁发者和委托者。
在 Pos.Delegation.Types
模块中有与委托相关的消息。授权消息的格式在二进制协议章节有描述。
你可以在 Pos.Communication.Methods
模块的 WorkMode
看到消息系统是怎么实现的。
Message type | Comments |
---|---|
UpdateProposal |
Serialized update proposal, sent to a DHT peers |
UpdateVote |
Message, payload of which contains the actual vote |
请参阅 sendUpdateProposal
和 sendVote
功能的更多细节。
你可以把它们视为消息的『操作人员』
Workers 发起消息交换,因此 worker 是卡尔达诺结算层的积极通信部分。Listeners 可以从 workers 接收信息,且可能会发送一些消息作为回复。因此 listener 是卡尔达诺结算层的被动通信部分,收到信息后,listener 使用一种叫做 handler 的函数来实际执行相应的作业。根据收到的信息的类型使用特定的处理程序(如上所述,消息具有不同的类型)。
为了能够执行必要的操作,所有的 workers 和 handlers 在 WorkMode
进行工作(见下文)。
上面描述了区块交换信息。
获取块在 Pos.Block.Network.Retrieval
模块中进行处理。
这个 retrievalWorker
非常重要:它是一个在区块检索队列上验证头文件的服务器,这些区块形成一个合适的链。它发送一个 MsgGetBlocks
类型的信息给 listener,此时它从这个 listener 接收一个类型为 MsgBlock
信息的回答。
这是另一个例子 - requestHeaders
功能。这个函数处理预期的区块头,并在本地跟踪它们。在这个地方,它向 listener 发送一种类型为 MsgGetHeaders
的信息,而在这,它从这个 listener 接收一个类型为 MsgHeaders
的回答。
Pos.Block.Worker
模块中定义了用于区块处理的其他 worker。我们重用了上述的 retrievalWorker
(TODO:and define a
well-documented),并记载了一个记录良好的 blkOnNewSlot
worker。它代表了一个新 slot 开始时应该完成的操作,这个操作包括以下步骤:
处理区块的方式在 Pos.Block.Logic
模块中定义。请阅读卡尔达诺结算层中的区块获取关于区块的更多信息。
区块处理的 Listeners 在 Pos.Block.Network.Listeners
模块中定义。
处理程序 handleGetHeaders
发送区块头部:在这,它从 worker 收到一个 MsgGetHeaders
类型的信息,获取头部,然后在这,它向 worker 发送 MsgHeaders
类型的回复信息。
handleGetBlocks
处理程序发送区块。这个处理程序对应 retrievalWorker
的 retrieveBlocks
,因此,它从 worker 这里接收 MsgGetBlocks
类型的信息,获得对应的头部 ,然后在这里向这个 worker 发送 MsgBlock
类型的响应信息。
处理程序 handleBlockHeaders
以类似的方式发送未经请求的用例的区块头部:它接收来自 worker 的 MsgHeaders
类型,并处理它。
另一个例子是使用上述的委派信息
委派信息的 Worker 在 Pos.Delegation.Worker
模块中定义。
所有这些 workers 不会发送信息到一个特定的节点。他们发送信息给所有的邻节点。
委派消息的 Listeners 在 Pos.Delegation.Listeners
模块中定义。
进行安全操作的 workers 在 Pos.Security.Workers
模块中定义。
以下是与更新系统相关的工作人员和听众列表。
更新系统的 worker 在 Pos.Update.Worker
模块中定义。更新系统所做的唯一事情是在每个 slot 上检查新的已批准更新。
更新系统的 Listeners 在 Pos.Update.Network.Listeners
模块中定义。
UpdateProposal
中继器:
Req
— 本地节点回答关于更新提案的请求,并针对此提案进行一组投票。Inv
— 检查我们是否需要提供的提案,并记录数据是否与此库存消息相关。Data
— 将提案信息与投票一起进行验证和记录。
UpdateVote
listeners:
Req
— 把我们的投票发给任何人。Inv
— 检查我们是否需要提供的投票,并记录相关的。Data
— 进行一次投票,核实和记录。
有一个特殊的类型称为 WorkMode
,MinWorkMode
表示一系列执行真实世界的分布式系统的工作的约束条件。你可以把约束看做运行时保证,它可以在特定的上下文执行特定的操作。例如,如果我们根据 logging 约束定义一些函数 f
的类型,我们肯定知道我们在这个函数 f
里面记录不同的信息。
上面描述的所有 workers 和 handlers 都受 WorkMode
的限制。