1. 引言
NEAR采用PoS共识机制,使用名为NighShade的分片设计,采用名为DoomSlug的区块生成机制。
2. 相关定义及符号
为了维护共识,交易会打包进区块。存在一个preconfigured block G G G,称为genesis block。除 G G G之外,每个block都有a link pointing to the previous block p r e v ( B ) prev(B) prev(B),其中 B B B为block,通过该link最终可连接到 G G G(不存在cycles)。
- A < B A<B A<B:即意味着 A ≠ B A\neq B A=B,且 B B B可通过previous block link连接到 A A A。
- A ≤ B A\leq B A≤B:即意味着 A < B A<B A<B 或 A = B A=B A=B。
- A ∼ B A\sim B A∼B:即意味着 A < B A<B A<B 或 A = B A=B A=B 或 A > B A>B A>B。
- A ≁ B A\nsim B A≁B:即为非 A ∼ B A\sim B A∼B。
chain c h a i n ( T ) chain(T) chain(T):是指a set of blocks reachable from block T T T,称为tips,即 c h a i n ( T ) = { B ∣ B ≤ T } chain(T)=\{B|B\leq T\} chain(T)={B∣B≤T}。对于任意的区块 A , B A,B A,B,当且仅当 A ∼ B A\sim B A∼B时, A , B A,B A,B之间存在a chain。此时,可成 A , B A,B A,B在同一链上。
每个区块都有一个整数height h ( B ) h(B) h(B)。区块高度是单调递增的,即对于任意的区块 B ≠ G B\neq G B=G,有 h ( B ) > h ( p r e v ( B ) ) h(B)>h(prev(B)) h(B)>h(prev(B))。但不要求区块高度是连续的,如 h ( G ) h(G) h(G)可不为0。每个节点keep track of a valid block with the largest height it knows about, which is called its head。
区块会打包进epoch。在链上,属于同一epoch的一组区块形成连续的范围:若区块 A < B A<B A<B都属于同一epoch,则每个满足 A < X < B A<X<B A<X<B的区块 X X X都属于该epoch。epoch可 以sequential indices来标记: G G G属于index为0的epoch;对于每个block B B B,其所属epoch index要么等于 p r e v ( B ) prev(B) prev(B)的epoch index,要么大于。
每个epoch关联 a set of block producers that are validating blocks in that epoch, as well as an assignment of block heights to block producers that are responsible for producing a block at that height。
负责生产高度为 h h h区块的block producer称为block proposer at h h h。
epoch index i ≥ 2 i\geq 2 i≥2 的validator set和block height assignment信息会在epoch index为 i − 2 i-2 i−2的最后一个block确定。对于epoch index为0和1的情况,其validator set和block height assignment为preconfigured的。因此,若2链共享某epoch的最后一个区块,则其后续2个epoch具有相同的validator set和相同的block height assignment,但是对于再更后的epoch则无法保证。
固化:若block B B B固化了,则任何未来的固化区块都是仅能基于 B B B构建的。因此,在 B B B及之前区块的交易将永远不会被reverse。 f i n a l ( B , T ) final(B,T) final(B,T),其中 B ≤ T B\leq T B≤T,意味着 B B B在 c h a i n ( T ) chain(T) chain(T)上固化了。若 f i n a l ( B , T ) final(B,T) final(B,T)为真,则对于所有的 T ′ ≥ T T'\geq T T′≥T, f i n a l ( B , T ′ ) final(B,T') final(B,T′)均为真。
2. Data structures
NEAR区块头中与共识相关的字段有:
struct BlockHeader {
...
prev_hash: BlockHash,
height: BlockHeight,
epoch_id: EpochId,
last_final_block_hash: BlockHash,
approvals: Vec<Option<Signature>>
...
}
在特定epoch的block producers会交换多种类型的消息,与共识相关的消息有2种:
- Blocks
- Approvals
具体包含了以下字段:
enum ApprovalInner {
Endorsement(BlockHash),//为the hash of the approved block
Skip(BlockHeight),//为the height of the approved block
}
struct Approval {
inner: ApprovalInner,
target_height: BlockHeight,//为the specific height at which the approval can be used (an approval with a particular `target_height` can be only included in the `approvals` of a block that has `height = target_height`)
signature: Signature,//为对`(inner, target_height)`的签名
account_id: AccountId //为the account of the block producer who created the approval
}
3. Approvals Requirements
除genesis block之外的每个区块 B B B中,都必须逻辑上包含来自于block producers的approvals,这些block producers的累计stake必须超过该epoch中总stake的2/3。在epoch切换时,要求block producers的累计stake需超过下一epoch总stake的2/3。
approval有2种类型:
- 当且仅当
h
(
B
)
=
h
(
p
r
e
v
(
B
)
)
+
1
h(B)=h(prev(B))+1
h(B)=h(prev(B))+1时,为
Endorsement
。 - 否则为
Skip
with the height of p r e v ( B ) prev(B) prev(B)。
存储在区块中的每个approval逻辑上对于每个block producer都是相同的(除了account_id
和signature
不同),因此, 存储所有approvals是冗余的。事实物理上值存储了signatures of the approvals,具体的存储方式为:
- 获取当前epoch的block producers ordered set。
若某block在epoch边界,也需要包含approvals form the next epoch,因此需要add new accounts from the new epoch:
def get_accounts_for_block_ordered(h, prev_block):
cur_epoch = get_next_block_epoch(prev_block)
next_epoch = get_next_block_next_epoch(prev_block)
account_ids = get_epoch_block_producers_ordered(cur_epoch)
if next_block_needs_approvals_from_next_epoch(prev_block):
for account_id in get_epoch_block_producers_ordered(next_epoch):
if account_id not in account_ids:
account_ids.append(account_id)
return account_ids
因此,该边界block中包含了a vector of optional signatures of the same or smaller size than the resulting set of account_ids
, with each element being None
if the approval for such account is absent, or the signature on the approval message if it is present。因此,很容易根据区块中的信息重构出签名approvals的block producers,并验证相应的签名。若the vector of signature短于account_ids
的长度,则剩余的signatures均假设为None
。
4. Messages
当收到approval消息,参与者仅将其存入the collection of approval messages中:
def on_approval(self, approval):
self.approvals.append(approval)
当参与者收到一个block,与共识相关的操作有:
- 更新
head
- 初始化a timer来开始发送该block的approvals给the block producers at the consecutive
target_height
s。计时器超时时间取决于俄最后固化区块的高度,该信息也会persisted。
def on_block(self, block):
header = block.header
if header.height <= self.head_height:
return
last_final_block = store.get_block(header.last_final_block_hash)
self.head_height = header.height
self.head_hash = block.hash()
self.largest_final_height = last_final_block.height
self.timer_height = self.head_height + 1
self.timer_started = time.time()
self.endorsement_pending = True
定时器会定期检查如下逻辑:
def get_delay(n):
min(MAX_DELAY, MIN_DELAY + DELAY_STEP * (n-2))
def process_timer(self):
now = time.time()
skip_delay = get_delay(self.timer_height - self.largest_final_height)
if self.endorsement_pending and now > self.timer_started + ENDORSEMENT_DELAY:
if self.head_height >= self.largest_target_height:
self.largest_target_height = self.head_height + 1
self.send_approval(head_height + 1)
self.endorsement_pending = False
if now > self.timer_started + skip_delay:
assert not self.endorsement_pending
self.largest_target_height = max(self.largest_target_height, self.timer_height + 1)
self.send_approval(self.timer_height + 1)
self.timer_started = now
self.timer_height += 1
def send_approval(self, target_height):
if target_height == self.head_height + 1:
inner = Endorsement(self.head_hash)
else:
inner = Skip(self.head_height)
approval = Approval(inner, target_height)
send(approval, to_whom = get_block_proposer(self.head_hash, target_height))
其中get_block_proposer
返回的下一block proposer。
同时要求ENDORSEMENT_DELAY < MIN_DELAY
,尽管不影响正确性,NEAR中要求ENDORSEMENT_DELAY * 2 <= MIN_DELAY
。
5. Block Production
定义了获取特定高度区块中approvals的函数:
def get_approvals(self, target_height):
return [approval for approval
in self.approvals
if approval.target_height == target_height and
(isinstance(approval.inner, Skip) and approval.prev_height == self.head_height or
isinstance(approval.inner, Endorsement) and approval.prev_hash == self.head_hash)]
只要get_approvals
返回的approvals中对应的block producers的累计质押量超过了总质押量的2/3,被指定该高度的产块者即可产块。
6. 固化条件
block B B B固化在 c h a i n ( T ) chain(T) chain(T),其中 T ≥ B T\geq B T≥B,要么 B = G B=G B=G,要么block X ≤ T X\leq T X≤T 使得 B = p r e v ( p r e v ( X ) ) 且 h ( X ) = h ( p r e v ( X ) ) + 1 = h ( B ) + 2 B=prev(prev(X)) 且 h(X)=h(prev(X))+1=h(B)+2 B=prev(prev(X))且h(X)=h(prev(X))+1=h(B)+2。即,要么 B B Bwei genesis block,要么 c h a i n ( T ) chain(T) chain(T)包含至少2个blocks on top of B B B,且这3个区块(包含区块 B B B本身及其后续2个区块)具有连续的区块高度。
7. epoch切换
e p o c h l e n g t h ≥ 3 epoch_{length}\geq 3 epochlength≥3定义了一个epoch的最小长度。假设某epoch e c u r e_{cur} ecur开始于高度 h h h,下一个epoch为 e n e x t e_{next} enext。
B
P
(
e
)
BP(e)
BP(e)为epoch
e
e
e中的一系列产块者。
l
a
s
t
f
i
n
a
l
(
T
)
last_{final(T)}
lastfinal(T)为the highest final block in
c
h
a
i
n
(
T
)
chain(T)
chain(T)。
则产块者产块中包含的approvals规则为:
- 1)对于高度为 h ( p r e v ( B ) ) < h + e p o c h l e n g t h − 3 h(prev(B))< h+epoch_{length}-3 h(prev(B))<h+epochlength−3的区块 B B B属于epoch e c u r e_{cur} ecur,必须有超过2/3的approvals of B P ( e c u r ) BP(e_{cur}) BP(ecur)(stake-weighted)。
- 2)对于高度为 h ( p r e v ( B ) ) ≥ h + e p o c h l e n g t h − 3 h(prev(B))\geq h+epoch_{length}-3 h(prev(B))≥h+epochlength−3且 h ( l a s t f i n a l ( p r e v ( B ) ) ) < h + e p o c h l e n g t h − 3 h(last_{final(prev(B))})<h+epoch_{length}-3 h(lastfinal(prev(B)))<h+epochlength−3的区块 B B B属于epoch e c u r e_{cur} ecur,必须同时拥有超过2/3的approvals of B P ( e c u r ) BP(e_{cur}) BP(ecur)(stake-weighted) 以及 超过2/3的approvals of B P ( e n e x t ) BP(e_{next}) BP(enext)(stake-weighted)。
- 3)对于第一个高度为 h ( l a s t f i n a l ( p r e v ( B ) ) ) ≥ h + e p o c h l e n g t h − 3 h(last_{final(prev(B))})\geq h+epoch_{length}-3 h(lastfinal(prev(B)))≥h+epochlength−3 区块 B B B属于epoch e n e x t e_{next} enext,必须有超过2/3的approvals of B P ( e n e x t ) BP(e_{next}) BP(enext)(stake-weighted)。
8. Safety
honest block producer:
- 永远无法为同一
prev_height
生成2个endorsement(即冲突的endorsements) - 永远无法生成a skip message
s
和 an endorsement e e e,使得s.prev_height < e.prev_height and s.target_height >= e.target_height
(即冲突的skip和endorsement)
有定理:
有推论:
9. Liveness
具体proof of liveness见 NEAR团队2020年论文 《Doomslug: block confirmation with single round of communication, and a finality gadget with guaranteed liveness》。此处的共识不同之处在于要求2个连续具有endorsements的区块。而在论文中的proof进行了扩展,之处,一旦the delay is sufficiently long for a honest block producer to collect enough endorsements, the next block producer ought to have enough time to collect all the endorsements too。
10. Approval condition
approval condition为:
- 任何有效的区块内,必须包含approvals from block producers,其累计质押量超过该epoch中总质押量的2/3。对于区块
B
B
B及其前一区块
B
′
B'
B′,当且仅当
B.height == B'.height + 1
时, B B B中的每个approval必须为anEndorsement
with the hash of B ′ B' B′,否则必须为aSkip
with the height of B ′ B' B′。
这2个条件无法合一。对于approval中的endorsement,其prev_hash
必须等于the hash of the previous block,否则上面第7节的safety proof将无法通过。
对于skip messages来说,不要求approval中的hash匹配the hash of the previous block,因为若有malicious actor可在同一高度创建2个区块,并将其分别分发给一半的block producers。每一半的block producers将给同一prev_height
发送具有不同prev_hash
的skip messages 给未来的产块者。若此时要求skip中的prev_hash
必须严格匹配区块的prev_hash
,则将没有产块者可创建新的区块。
参考资料
[1] NEAR 共识机制
[2] What is NEAR protocol?
[3] Doomslug vs PBFT, Tendermint, and Hotstuff
[4] NEAR团队2020年论文 《Doomslug: block confirmation with single round of communication, and a finality gadget with guaranteed liveness》