Skip to content

Commit 576cfa1

Browse files
committed
opt proposal
1 parent 7ff4f18 commit 576cfa1

16 files changed

+776
-68
lines changed

config/src/config/consensus_config.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ impl Default for ConsensusConfig {
324324
max_pending_rounds_in_commit_vote_cache: 100,
325325
optimistic_sig_verification: true,
326326
enable_round_timeout_msg: true,
327-
enable_pipeline: false,
327+
enable_pipeline: true,
328328
}
329329
}
330330
}

consensus/consensus-types/src/block.rs

+26-4
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
// SPDX-License-Identifier: Apache-2.0
44

55
use crate::{
6-
block_data::{BlockData, BlockType},
7-
common::{Author, Payload, Round},
8-
quorum_cert::QuorumCert,
6+
block_data::{BlockData, BlockType}, common::{Author, Payload, Round}, opt_block_data::OptBlockData, quorum_cert::QuorumCert
97
};
10-
use anyhow::{bail, ensure, format_err};
8+
use anyhow::{bail, ensure, format_err, Result};
119
use aptos_bitvec::BitVec;
1210
use aptos_crypto::{bls12381, hash::CryptoHash, HashValue};
1311
use aptos_infallible::duration_since_epoch;
@@ -168,6 +166,10 @@ impl Block {
168166
self.block_data.is_nil_block()
169167
}
170168

169+
pub fn is_opt_block(&self) -> bool {
170+
self.block_data.is_opt_block()
171+
}
172+
171173
#[cfg(any(test, feature = "fuzzing"))]
172174
pub fn make_genesis_block() -> Self {
173175
Self::make_genesis_block_from_ledger_info(&LedgerInfo::mock_genesis(None))
@@ -308,6 +310,19 @@ impl Block {
308310
}
309311
}
310312

313+
pub fn new_from_opt(
314+
block_data: OptBlockData,
315+
quorum_cert: QuorumCert,
316+
failed_authors: Vec<(Round, Author)>,
317+
) -> Result<Self> {
318+
let block_data = BlockData::new_from_opt(block_data, quorum_cert, failed_authors)?;
319+
Ok(Block {
320+
id: block_data.hash(),
321+
block_data,
322+
signature: None,
323+
})
324+
}
325+
311326
pub fn validator_txns(&self) -> Option<&Vec<ValidatorTransaction>> {
312327
self.block_data.validator_txns()
313328
}
@@ -334,6 +349,11 @@ impl Block {
334349
validator.verify(*proposal_ext.author(), &self.block_data, signature)?;
335350
self.quorum_cert().verify(validator)
336351
},
352+
BlockType::OptProposal { .. } => {
353+
// Optimistic proposal is not signed by proposer
354+
self.block_data().grandparent_qc().map_or(Ok(()), |qc| qc.verify(validator))?;
355+
self.quorum_cert().verify(validator)
356+
},
337357
BlockType::DAGBlock { .. } => bail!("We should not accept DAG block from others"),
338358
}
339359
}
@@ -442,6 +462,8 @@ impl Block {
442462
fn previous_bitvec(&self) -> BitVec {
443463
if let BlockType::DAGBlock { parents_bitvec, .. } = self.block_data.block_type() {
444464
parents_bitvec.clone()
465+
} else if let BlockType::OptProposal { grandparent_qc, .. } = self.block_data.block_type() {
466+
grandparent_qc.ledger_info().get_voters_bitvec().clone()
445467
} else {
446468
self.quorum_cert().ledger_info().get_voters_bitvec().clone()
447469
}

consensus/consensus-types/src/block_data.rs

+83-10
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
// SPDX-License-Identifier: Apache-2.0
44

55
use crate::{
6-
common::{Author, Payload, Round},
7-
proposal_ext::ProposalExt,
8-
quorum_cert::QuorumCert,
9-
vote_data::VoteData,
6+
common::{Author, Payload, Round}, opt_block_data::OptBlockData, proposal_ext::ProposalExt, quorum_cert::QuorumCert, vote_data::VoteData
107
};
8+
use anyhow::{bail, Result};
119
use aptos_bitvec::BitVec;
12-
use aptos_crypto::hash::HashValue;
13-
use aptos_crypto_derive::{BCSCryptoHash, CryptoHasher};
10+
use aptos_crypto::{hash::{CryptoHash, CryptoHasher}, HashValue};
11+
use aptos_crypto_derive::CryptoHasher;
1412
use aptos_types::{
1513
aggregate_signature::AggregateSignature,
1614
block_info::BlockInfo,
@@ -49,6 +47,21 @@ pub enum BlockType {
4947
/// Proposal with extensions (e.g. system transactions).
5048
ProposalExt(ProposalExt),
5149

50+
/// Optimistic proposal
51+
OptProposal {
52+
validator_txns: Vec<ValidatorTransaction>,
53+
/// T of the block (e.g. one or more transaction(s)
54+
payload: Payload,
55+
/// Author of the block that can be validated by the author's public key and the signature
56+
author: Author,
57+
/// Failed authors from the parent's block to this block.
58+
/// I.e. the list of consecutive proposers from the
59+
/// immediately preceeding rounds that didn't produce a successful block.
60+
failed_authors: Vec<(Round, Author)>,
61+
/// Grandparent QC, used to generate previous_bitvec
62+
grandparent_qc: QuorumCert,
63+
},
64+
5265
/// A virtual block that's constructed by nodes from DAG, this is purely a local thing so
5366
/// we hide it from serde
5467
#[serde(skip_deserializing)]
@@ -63,7 +76,7 @@ pub enum BlockType {
6376
},
6477
}
6578

66-
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, CryptoHasher, BCSCryptoHash)]
79+
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, CryptoHasher)]
6780
/// Block has the core data of a consensus block that should be persistent when necessary.
6881
/// Each block must know the id of its parent and keep the QuorurmCertificate to that parent.
6982
pub struct BlockData {
@@ -96,10 +109,28 @@ pub struct BlockData {
96109
block_type: BlockType,
97110
}
98111

112+
impl CryptoHash for BlockData {
113+
type Hasher = BlockDataHasher;
114+
115+
fn hash(&self) -> HashValue {
116+
let mut state = Self::Hasher::default();
117+
if self.is_opt_block() {
118+
state.update(&self.epoch.to_be_bytes());
119+
state.update(&self.round.to_be_bytes());
120+
state.update(&self.timestamp_usecs.to_be_bytes());
121+
state.update(&bcs::to_bytes(self.quorum_cert().vote_data()).expect("Unable to serialize quorum cert vote data"));
122+
state.update(&bcs::to_bytes(self.block_type()).expect("Unable to serialize block type"));
123+
} else {
124+
state.update(&bcs::to_bytes(self).expect("Failed to serialize BlockData"));
125+
}
126+
state.finish()
127+
}
128+
}
129+
99130
impl BlockData {
100131
pub fn author(&self) -> Option<Author> {
101132
match &self.block_type {
102-
BlockType::Proposal { author, .. } | BlockType::DAGBlock { author, .. } => {
133+
BlockType::Proposal { author, .. } | BlockType::OptProposal { author, .. } | BlockType::DAGBlock { author, .. } => {
103134
Some(*author)
104135
},
105136
BlockType::ProposalExt(p) => Some(*p.author()),
@@ -126,6 +157,13 @@ impl BlockData {
126157
}
127158
}
128159

160+
pub fn grandparent_qc(&self) -> Result<QuorumCert> {
161+
match &self.block_type {
162+
BlockType::OptProposal { grandparent_qc, .. } => Ok(grandparent_qc.clone()),
163+
_ => bail!("Invalid block type"),
164+
}
165+
}
166+
129167
pub fn payload(&self) -> Option<&Payload> {
130168
match &self.block_type {
131169
BlockType::Proposal { payload, .. } | BlockType::DAGBlock { payload, .. } => {
@@ -139,8 +177,9 @@ impl BlockData {
139177
pub fn validator_txns(&self) -> Option<&Vec<ValidatorTransaction>> {
140178
match &self.block_type {
141179
BlockType::ProposalExt(proposal_ext) => proposal_ext.validator_txns(),
142-
BlockType::Proposal { .. } | BlockType::NilBlock { .. } | BlockType::Genesis => None,
180+
BlockType::Proposal { .. } | BlockType::NilBlock { .. } | BlockType::Genesis => None,
143181
BlockType::DAGBlock { validator_txns, .. } => Some(validator_txns),
182+
BlockType::OptProposal { validator_txns, .. } => (!validator_txns.is_empty()).then_some(validator_txns),
144183
}
145184
}
146185

@@ -176,13 +215,18 @@ impl BlockData {
176215
matches!(self.block_type, BlockType::NilBlock { .. })
177216
}
178217

218+
pub fn is_opt_block(&self) -> bool {
219+
matches!(self.block_type, BlockType::OptProposal { .. })
220+
}
221+
179222
/// the list of consecutive proposers from the immediately preceeding
180223
/// rounds that didn't produce a successful block
181224
pub fn failed_authors(&self) -> Option<&Vec<(Round, Author)>> {
182225
match &self.block_type {
183226
BlockType::Proposal { failed_authors, .. }
184227
| BlockType::NilBlock { failed_authors, .. }
185-
| BlockType::DAGBlock { failed_authors, .. } => Some(failed_authors),
228+
| BlockType::DAGBlock { failed_authors, .. }
229+
| BlockType::OptProposal { failed_authors, .. } => Some(failed_authors),
186230
BlockType::ProposalExt(p) => Some(p.failed_authors()),
187231
BlockType::Genesis => None,
188232
}
@@ -353,6 +397,35 @@ impl BlockData {
353397
}
354398
}
355399

400+
// Converting OptBlockData to BlockData
401+
// by adding QC and failed_authors
402+
pub fn new_from_opt(
403+
opt_block_data: OptBlockData,
404+
quorum_cert: QuorumCert,
405+
new_failed_authors: Vec<(Round, Author)>,
406+
) -> Result<Self> {
407+
let OptBlockData {
408+
epoch,
409+
round,
410+
timestamp_usecs,
411+
parent_id: _,
412+
block_type,
413+
} = opt_block_data;
414+
let block_type = match block_type {
415+
BlockType::OptProposal { validator_txns, payload, author, failed_authors: _, grandparent_qc } => {
416+
BlockType::OptProposal { validator_txns, payload, author, failed_authors: new_failed_authors, grandparent_qc }
417+
}
418+
_ => bail!("Invalid block type"),
419+
};
420+
Ok(Self {
421+
epoch,
422+
round,
423+
timestamp_usecs,
424+
quorum_cert,
425+
block_type,
426+
})
427+
}
428+
356429
/// It's a reconfiguration suffix block if the parent block's executed state indicates next epoch.
357430
pub fn is_reconfiguration_suffix(&self) -> bool {
358431
self.quorum_cert.certified_block().has_reconfiguration()

consensus/consensus-types/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub mod block_data;
99
pub mod block_retrieval;
1010
pub mod common;
1111
pub mod epoch_retrieval;
12+
pub mod opt_block_data;
13+
pub mod opt_proposal_msg;
1214
pub mod order_vote;
1315
pub mod order_vote_msg;
1416
pub mod order_vote_proposal;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright © Aptos Foundation
2+
// Parts of the project are originally copyright © Meta Platforms, Inc.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
use anyhow::{ensure, Result, bail};
6+
use aptos_crypto::HashValue;
7+
use aptos_crypto_derive::CryptoHasher;
8+
use aptos_infallible::duration_since_epoch;
9+
use aptos_types::validator_txn::ValidatorTransaction;
10+
use serde::{Deserialize, Serialize};
11+
12+
use crate::{block_data::BlockType, common::{Author, Payload, Round}};
13+
use crate::quorum_cert::QuorumCert;
14+
15+
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, CryptoHasher)]
16+
/// Same as BlockData, without QC and with parent id
17+
pub struct OptBlockData {
18+
pub epoch: u64,
19+
pub round: Round,
20+
pub timestamp_usecs: u64,
21+
pub parent_id: HashValue,
22+
pub block_type: BlockType,
23+
}
24+
25+
impl OptBlockData {
26+
pub fn author(&self) -> &Author {
27+
match &self.block_type {
28+
BlockType::OptProposal { author, .. } => author,
29+
_ => panic!("Invalid block type"),
30+
}
31+
}
32+
33+
pub fn block_type(&self) -> &BlockType {
34+
&self.block_type
35+
}
36+
37+
pub fn epoch(&self) -> u64 {
38+
self.epoch
39+
}
40+
41+
pub fn parent_id(&self) -> HashValue {
42+
self.parent_id
43+
}
44+
45+
pub fn grandparent_qc(&self) -> Result<QuorumCert> {
46+
match &self.block_type {
47+
BlockType::OptProposal { grandparent_qc, .. } => Ok(grandparent_qc.clone()),
48+
_ => bail!("Invalid block type"),
49+
}
50+
}
51+
52+
pub fn payload(&self) -> Option<&Payload> {
53+
match &self.block_type {
54+
BlockType::OptProposal { payload, .. } => Some(payload),
55+
_ => panic!("Invalid block type"),
56+
}
57+
}
58+
59+
pub fn validator_txns(&self) -> Option<&Vec<ValidatorTransaction>> {
60+
match &self.block_type {
61+
BlockType::OptProposal { validator_txns, .. } => (!validator_txns.is_empty()).then_some(validator_txns),
62+
_ => panic!("Invalid block type"),
63+
}
64+
}
65+
66+
pub fn dag_nodes(&self) -> Option<&Vec<HashValue>> {
67+
if let BlockType::DAGBlock {
68+
node_digests: nodes_digests,
69+
..
70+
} = &self.block_type
71+
{
72+
Some(nodes_digests)
73+
} else {
74+
None
75+
}
76+
}
77+
78+
pub fn round(&self) -> Round {
79+
self.round
80+
}
81+
82+
pub fn timestamp_usecs(&self) -> u64 {
83+
self.timestamp_usecs
84+
}
85+
86+
pub fn is_opt_proposal(&self) -> bool {
87+
matches!(self.block_type, BlockType::OptProposal { .. })
88+
}
89+
90+
pub fn new_proposal(
91+
validator_txns: Vec<ValidatorTransaction>,
92+
payload: Payload,
93+
author: Author,
94+
failed_authors: Vec<(Round, Author)>,
95+
epoch: u64,
96+
round: Round,
97+
timestamp_usecs: u64,
98+
parent_id: HashValue,
99+
grandparent_qc: QuorumCert,
100+
) -> Self {
101+
Self {
102+
epoch,
103+
round,
104+
timestamp_usecs,
105+
parent_id,
106+
block_type: BlockType::OptProposal {
107+
validator_txns,
108+
payload,
109+
author,
110+
failed_authors,
111+
grandparent_qc,
112+
},
113+
}
114+
}
115+
116+
pub fn verify(&self) -> Result<()> {
117+
// Verifies that the OptBlockData is well-formed.
118+
ensure!(
119+
self.is_opt_proposal(),
120+
"Only optimistic proposal is supported"
121+
);
122+
123+
if let Some(payload) = self.payload() {
124+
payload.verify_epoch(self.epoch())?;
125+
}
126+
127+
let current_ts = duration_since_epoch();
128+
129+
// we can say that too far is 5 minutes in the future
130+
const TIMEBOUND: u64 = 300_000_000;
131+
ensure!(
132+
self.timestamp_usecs() <= (current_ts.as_micros() as u64).saturating_add(TIMEBOUND),
133+
"Blocks must not be too far in the future"
134+
);
135+
136+
Ok(())
137+
}
138+
}

0 commit comments

Comments
 (0)