From b56c88468e50a8d5b21ffc6e546717e4941bfa91 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 2 Oct 2022 23:23:58 -0400 Subject: [PATCH 001/186] Machine without timeouts --- substrate/consensus/src/lib.rs | 1 + substrate/consensus/src/tendermint/mod.rs | 352 ++++++++++++++++++++ substrate/consensus/src/tendermint/paper.md | 40 +++ 3 files changed, 393 insertions(+) create mode 100644 substrate/consensus/src/tendermint/mod.rs create mode 100644 substrate/consensus/src/tendermint/paper.md diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index 763e6ff49..c6c902d9c 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -9,6 +9,7 @@ use sc_service::TaskManager; use serai_runtime::{self, opaque::Block, RuntimeApi}; mod algorithm; +mod tendermint; pub struct ExecutorDispatch; impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { diff --git a/substrate/consensus/src/tendermint/mod.rs b/substrate/consensus/src/tendermint/mod.rs new file mode 100644 index 000000000..3057c01dc --- /dev/null +++ b/substrate/consensus/src/tendermint/mod.rs @@ -0,0 +1,352 @@ +use std::collections::HashMap; + +type ValidatorId = u16; +const VALIDATORS: ValidatorId = 5; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +struct Hash; +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +struct Block { + hash: Hash +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum BlockError { + // Invalid behavior entirely + Fatal, + // Potentially valid behavior dependent on unsynchronized state + Temporal, +} + +fn valid(block: &Block) -> Result<(), BlockError> { + Ok(()) +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +enum Step { + Propose, + Prevote, + Precommit, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +enum Data { + Proposal(Option, Block), + Prevote(Option), + Precommit(Option), +} + +impl Data { + fn step(&self) -> Step { + match self { + Data::Proposal(..) => Step::Propose, + Data::Prevote(..) => Step::Prevote, + Data::Precommit(..) => Step::Precommit, + } + } +} + +#[derive(Clone, PartialEq, Eq, Debug)] +struct Message { + sender: ValidatorId, + + height: u32, + round: u32, + + data: Data, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum TendermintError { + MaliciousOrTemporal(u16), // TODO: Remove when we figure this out + Malicious(u16), + Offline(u16), + Temporal, +} + +fn proposer(height: u32, round: u32) -> ValidatorId { + ValidatorId::try_from((height + round) % u32::try_from(VALIDATORS).unwrap()).unwrap() +} + +fn broadcast(msg: Message) { + todo!(); +} + +#[derive(Clone, PartialEq, Eq, Debug)] +struct TendermintMachine { + proposer: ValidatorId, + personal_proposal: Option, + + height: u32, + + log_map: HashMap>>, + precommitted: HashMap, + + round: u32, + step: Step, + locked: Option<(u32, Block)>, + valid: Option<(u32, Block)>, +} + +impl TendermintMachine { + fn broadcast(&self, data: Data) -> Option { + let msg = Message { sender: self.proposer, height: self.height, round: self.round, data }; + let res = self.message(msg).unwrap(); + broadcast(msg); + res + } + + // 14-21 + fn round_propose(&mut self) { + // This will happen if it's a new height and propose hasn't been called yet + if self.personal_proposal.is_none() { + // Ensure it's actually a new height. Else, the caller failed to provide necessary data yet + // is still executing the machine + debug_assert_eq!(self.round, 0); + return; + } + + if proposer(self.height, self.round) == self.proposer { + let (round, block) = if let Some((round, block)) = self.valid { + (Some(round), block) + } else { + (None, self.personal_proposal.unwrap()) + }; + debug_assert!(self.broadcast(Data::Proposal(round, block)).is_none()); + } else { + // TODO schedule timeout propose + } + } + + // 11-13 + fn round(&mut self, round: u32) { + self.round = round; + self.step = Step::Propose; + self.round_propose(); + } + + /// Called whenever a new height occurs + pub fn propose(&mut self, block: Block) { + self.personal_proposal = Some(block); + self.round_propose(); + } + + // 1-9 + fn reset(&mut self) { + self.personal_proposal = None; + + self.height += 1; + + self.log_map = HashMap::new(); + self.precommitted = HashMap::new(); + + self.locked = None; + self.valid = None; + + self.round(0); + } + + // 10 + pub fn new(proposer: ValidatorId, height: u32) -> TendermintMachine { + TendermintMachine { + proposer, + personal_proposal: None, + + height, + + log_map: HashMap::new(), + precommitted: HashMap::new(), + + locked: None, + valid: None, + + round: 0, + step: Step::Propose + } + } + + // Returns true if it's a new message + fn log(&mut self, msg: Message) -> Result { + if matches!(msg.data, Data::Proposal(..)) && (msg.sender != proposer(msg.height, msg.round)) { + Err(TendermintError::Malicious(msg.sender))?; + }; + + if !self.log_map.contains_key(&msg.round) { + self.log_map.insert(msg.round, HashMap::new()); + } + let log = self.log_map.get_mut(&msg.round).unwrap(); + if !log.contains_key(&msg.sender) { + log.insert(msg.sender, HashMap::new()); + } + let log = log.get_mut(&msg.sender).unwrap(); + + // Handle message replays without issue. It's only multiple messages which is malicious + let step = msg.data.step(); + if let Some(existing) = log.get(&step) { + if existing != &msg.data { + Err(TendermintError::Malicious(msg.sender))?; + } + return Ok(false); + } + + // If they already precommitted to a distinct hash, error + if let Data::Precommit(Some(hash)) = msg.data { + if let Some(prev) = self.precommitted.get(&msg.sender) { + if hash != *prev { + Err(TendermintError::Malicious(msg.sender))?; + } + } + self.precommitted.insert(msg.sender, hash); + } + + log.insert(step, msg.data); + Ok(true) + } + + fn message_instances(&self, round: u32, data: Data) -> (usize, usize) { + let participating = 0; + let weight = 0; + for participant in self.log_map[&round].values() { + if let Some(msg) = participant.get(&data.step()) { + let validator_weight = 1; // TODO + participating += validator_weight; + if &data == msg { + weight += validator_weight; + } + + // If the msg exists, yet has a distinct hash, this validator is faulty + // (at least for precommit) + // TODO + } + } + (participating, weight) + } + + fn participation(&self, round: u32, step: Step) -> usize { + let (participating, _) = self.message_instances(round, match step { + Step::Propose => panic!("Checking for participation on Propose"), + Step::Prevote => Data::Prevote(None), + Step::Precommit => Data::Precommit(None), + }); + participating + } + + fn has_participation(&self, round: u32, step: Step) -> bool { + self.participation(round, step) >= ((VALIDATORS / 3 * 2) + 1).into() + } + + fn has_consensus(&self, round: u32, data: Data) -> bool { + let (_, weight) = self.message_instances(round, data); + weight >= ((VALIDATORS / 3 * 2) + 1).into() + } + + // 49-54 + fn check_committed(&mut self, round_num: u32) -> Option { + let proposer = proposer(self.height, round_num); + // Safe as we only check for rounds which we received a message for + let round = self.log_map[&round_num]; + + // Get the proposal + if let Some(proposal) = round.get(&proposer).map(|p| p.get(&Step::Propose)).flatten() { + // Destructure + debug_assert!(matches!(proposal, Data::Proposal(..))); + if let Data::Proposal(_, block) = proposal { + // Check if it has gotten a sufficient amount of precommits + let (participants, weight) = self.message_instances(round_num, Data::Precommit(Some(block.hash))); + + let threshold = ((VALIDATORS / 3) * 2) + 1; + if weight >= threshold.into() { + self.reset(); + return Some(*block); + } + + if (participants >= threshold.into()) && first { + schedule timeoutPrecommit(self.height, round); + } + } + } + + None + } + + pub fn message(&mut self, msg: Message) -> Result, TendermintError> { + if msg.height != self.height { + Err(TendermintError::Temporal)?; + } + + if !self.log(msg)? { + return Ok(None); + } + + // All functions, except for the finalizer and the jump, are locked to the current round + // Run the finalizer to see if it applies + if matches!(msg.data, Data::Proposal(..)) || matches!(msg.data, Data::Precommit(_)) { + let block = self.check_committed(msg.round); + if block.is_some() { + return Ok(block); + } + } + + // Else, check if we need to jump ahead + let round = self.log_map[&self.round]; + if msg.round < self.round { + return Ok(None); + } else if msg.round > self.round { + // 55-56 + // TODO: Move to weight + if round.len() > ((VALIDATORS / 3) + 1).into() { + self.round(msg.round); + } else { + return Ok(None); + } + } + + if self.step == Step::Propose { + if let Some(proposal) = round.get(&proposer(self.height, self.round)).map(|p| p.get(&Step::Propose)).flatten() { + debug_assert!(matches!(proposal, Data::Proposal(..))); + if let Data::Proposal(vr, block) = proposal { + if let Some(vr) = vr { + // 28-33 + let vr = *vr; + if (vr < self.round) && self.has_consensus(vr, Data::Prevote(Some(block.hash))) { + debug_assert!(self.broadcast( + Data::Prevote( + Some(block.hash).filter(|_| self.locked.map(|(round, value)| (round <= vr) || (block == &value)).unwrap_or(true)) + ) + ).is_none()); + self.step = Step::Prevote; + } else { + Err(TendermintError::Malicious(msg.sender))?; + } + } else { + // 22-27 + valid(&block).map_err(|_| TendermintError::Malicious(msg.sender))?; + debug_assert!(self.broadcast(Data::Prevote(Some(block.hash).filter(|_| self.locked.is_none() || self.locked.map(|locked| &locked.1) == Some(block)))).is_none()); + self.step = Step::Prevote; + } + } + } + } + + if self.step == Step::Prevote { + let (participation, weight) = self.message_instances(self.round, Data::Prevote(None)); + // 34-35 + if (participation > (((VALIDATORS / 3) * 2) + 1).into()) && first { + // TODO: Schedule timeout prevote + } + + // 44-46 + if (weight > (((VALIDATORS / 3) * 2) + 1).into()) && first { + debug_assert!(self.broadcast(Data::Precommit(None)).is_none()); + self.step = Step::Precommit; + } + } + + // 47-48 + if self.has_participation(self.round, Step::Precommit) && first { + // TODO: Schedule timeout precommit + } + + Ok(None) + } +} diff --git a/substrate/consensus/src/tendermint/paper.md b/substrate/consensus/src/tendermint/paper.md new file mode 100644 index 000000000..bf8d10310 --- /dev/null +++ b/substrate/consensus/src/tendermint/paper.md @@ -0,0 +1,40 @@ +The [paper](https://arxiv.org/abs/1807.04938) describes the algorithm with +pseudocode on page 6. This pseudocode is written as a series of conditions for +advancement. This is extremely archaic, as its a fraction of the actually +required code. This is due to its hand-waving away of data tracking, lack of +comments (beyond the entire rest of the paper, of course), and lack of +specification regarding faulty nodes. + +While the "hand-waving" is both legitimate and expected, as it's not the paper's +job to describe a full message processing loop nor efficient variable handling, +it does leave behind ambiguities and annoyances, not to mention an overall +structure which cannot be directly translated. This document is meant to be a +description of it enabling translation. + +The described pseudocode segments can be minimally described as follows: + +``` +01-09 Init +10-10 StartRound(0) +11-21 StartRound +22-27 Fresh proposal +28-33 Proposal building off a valid round with prevotes +34-35 2f+1 prevote -> schedule timeout prevote +36-43 First proposal with prevotes -> precommit Some +44-46 2f+1 nil prevote -> precommit nil +47-48 2f+1 precommit -> schedule timeout precommit +49-54 First proposal with precommits -> finalize +55-56 f+1 round > local round, jump +57-60 on timeout propose +61-64 on timeout prevote +65-67 on timeout precommit +``` + +Remaining: + +``` +36-43 First proposal with prevotes -> precommit Some +57-60 on timeout propose +61-64 on timeout prevote +65-67 on timeout precommit +``` From d081934725352d37f7e969d399710316fe2c38a5 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 12 Oct 2022 21:36:40 -0400 Subject: [PATCH 002/186] Time code --- substrate/consensus/src/tendermint/mod.rs | 96 +++++++++++++++++------ 1 file changed, 73 insertions(+), 23 deletions(-) diff --git a/substrate/consensus/src/tendermint/mod.rs b/substrate/consensus/src/tendermint/mod.rs index 3057c01dc..54c83c08a 100644 --- a/substrate/consensus/src/tendermint/mod.rs +++ b/substrate/consensus/src/tendermint/mod.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use tokio::{task::{JoinHandle, spawn}, sync::mpsc}; + type ValidatorId = u16; const VALIDATORS: ValidatorId = 5; @@ -68,11 +70,7 @@ fn proposer(height: u32, round: u32) -> ValidatorId { ValidatorId::try_from((height + round) % u32::try_from(VALIDATORS).unwrap()).unwrap() } -fn broadcast(msg: Message) { - todo!(); -} - -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Debug)] struct TendermintMachine { proposer: ValidatorId, personal_proposal: Option, @@ -86,13 +84,23 @@ struct TendermintMachine { step: Step, locked: Option<(u32, Block)>, valid: Option<(u32, Block)>, + + timeouts: Arc>>, // TODO: Remove Arc RwLock +} + +#[derive(Debug)] +struct TendermintHandle { + block: Arc>>, + messages: mpsc::Sender, + broadcast: mpsc::Receiver, + handle: JoinHandle<()>, } impl TendermintMachine { fn broadcast(&self, data: Data) -> Option { let msg = Message { sender: self.proposer, height: self.height, round: self.round, data }; let res = self.message(msg).unwrap(); - broadcast(msg); + self.broadcast.send(msg).unwrap(); res } @@ -114,7 +122,7 @@ impl TendermintMachine { }; debug_assert!(self.broadcast(Data::Proposal(round, block)).is_none()); } else { - // TODO schedule timeout propose + self.timeouts.write().unwrap().insert(Step::Precommit, self.timeout(Step::Precommit)); } } @@ -126,7 +134,7 @@ impl TendermintMachine { } /// Called whenever a new height occurs - pub fn propose(&mut self, block: Block) { + fn propose(&mut self, block: Block) { self.personal_proposal = Some(block); self.round_propose(); } @@ -147,21 +155,63 @@ impl TendermintMachine { } // 10 - pub fn new(proposer: ValidatorId, height: u32) -> TendermintMachine { - TendermintMachine { - proposer, - personal_proposal: None, - - height, + pub fn new(proposer: ValidatorId, height: u32) -> TendermintHandle { + let block = Arc::new(RwLock::new(None)); + let (msg_send, mut msg_recv) = mpsc::channel(100); // Backlog to accept. Currently arbitrary + let (broadcast_send, broadcast_recv) = mpsc::channel(5); + TendermintHandle { + block: block.clone(), + messages: msg_send, + broadcast: broadcast_recv, + handle: tokio::spawn(async { + let machine = TendermintMachine { + proposer, + personal_proposal: None, + + height, + + log_map: HashMap::new(), + precommitted: HashMap::new(), + + locked: None, + valid: None, + + round: 0, + step: Step::Propose + }; + + loop { + if self.personal_proposal.is_none() { + let block = block.lock().unwrap(); + if block.is_some() { + self.personal_proposal = Some(block.take()); + } else { + tokio::yield_now().await; + continue; + } + } - log_map: HashMap::new(), - precommitted: HashMap::new(), + let now = Instant::now(); + let (t1, t2, t3) = { +let timeouts = self.timeouts.read().unwrap(); +let ready = |step| timeouts.get(step).unwrap_or(now) < now; +(ready(Step::Propose), ready(Step::Prevote), ready(Step::Precommit)) +}; - locked: None, - valid: None, +if t1 { // Propose timeout +} +if t2 { // Prevote timeout +} +if t3 { // Precommit timeout +} - round: 0, - step: Step::Propose + match recv.try_recv() { + Ok(msg) => machine.message(msg), + Err(TryRecvError::Empty) => tokio::yield_now().await, + Err(TryRecvError::Disconnected) => break + } + } + }) } } @@ -269,7 +319,7 @@ impl TendermintMachine { None } - pub fn message(&mut self, msg: Message) -> Result, TendermintError> { + fn message(&mut self, msg: Message) -> Result, TendermintError> { if msg.height != self.height { Err(TendermintError::Temporal)?; } @@ -332,7 +382,7 @@ impl TendermintMachine { let (participation, weight) = self.message_instances(self.round, Data::Prevote(None)); // 34-35 if (participation > (((VALIDATORS / 3) * 2) + 1).into()) && first { - // TODO: Schedule timeout prevote + self.timeouts.write().unwrap().insert(Step::Prevote, self.timeout(Step::Prevote)); } // 44-46 @@ -344,7 +394,7 @@ impl TendermintMachine { // 47-48 if self.has_participation(self.round, Step::Precommit) && first { - // TODO: Schedule timeout precommit + self.timeouts.write().unwrap().insert(Step::Precommit, self.timeout(Step::Precommit)); } Ok(None) From ccd4ef193cd1b9de2948388729e2159dd2e3b780 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 01:32:54 -0400 Subject: [PATCH 003/186] Move substrate/consensus/tendermint to substrate/tendermint --- Cargo.toml | 1 + substrate/tendermint/Cargo.toml | 11 ++++ substrate/tendermint/LICENSE | 21 ++++++++ substrate/tendermint/README.md | 52 +++++++++++++++++++ .../{consensus/src => }/tendermint/paper.md | 0 .../mod.rs => tendermint/src/lib.rs} | 0 6 files changed, 85 insertions(+) create mode 100644 substrate/tendermint/Cargo.toml create mode 100644 substrate/tendermint/LICENSE create mode 100644 substrate/tendermint/README.md rename substrate/{consensus/src => }/tendermint/paper.md (100%) rename substrate/{consensus/src/tendermint/mod.rs => tendermint/src/lib.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index 2b0c67adb..0f4e16f11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "processor", "substrate/runtime", + "substrate/tendermint", "substrate/consensus", "substrate/node", diff --git a/substrate/tendermint/Cargo.toml b/substrate/tendermint/Cargo.toml new file mode 100644 index 000000000..aa53034bc --- /dev/null +++ b/substrate/tendermint/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tendermint-machine" +version = "0.1.0" +description = "An implementation of the Tendermint state machine in Rust" +license = "MIT" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/tendermint" +authors = ["Luke Parker "] +edition = "2021" + +[dependencies] +tokio = "1" diff --git a/substrate/tendermint/LICENSE b/substrate/tendermint/LICENSE new file mode 100644 index 000000000..f05b748b1 --- /dev/null +++ b/substrate/tendermint/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Luke Parker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/substrate/tendermint/README.md b/substrate/tendermint/README.md new file mode 100644 index 000000000..fbd2d83f9 --- /dev/null +++ b/substrate/tendermint/README.md @@ -0,0 +1,52 @@ +# Tendermint + +An implementation of the Tendermint state machine in Rust. + +This is solely the state machine, intended to be mapped to any arbitrary system. +It supports an arbitrary hash function, signature protocol, and block +definition accordingly. It is not intended to work with the Cosmos SDK, solely +be an implementation of the +[academic protocol](https://arxiv.org/pdf/1807.04938.pdf). + +### Paper + +The [paper](https://arxiv.org/abs/1807.04938) describes the algorithm with +pseudocode on page 6. This pseudocode is written as a series of conditions for +advancement. This is extremely archaic, as its a fraction of the actually +required code. This is due to its hand-waving away of data tracking, lack of +comments (beyond the entire rest of the paper, of course), and lack of +specification regarding faulty nodes. + +While the "hand-waving" is both legitimate and expected, as it's not the paper's +job to describe a full message processing loop nor efficient variable handling, +it does leave behind ambiguities and annoyances, not to mention an overall +structure which cannot be directly translated. This section is meant to be a +description of it as used for translation. + +The included pseudocode segments can be minimally described as follows: + +``` +01-09 Init +10-10 StartRound(0) +11-21 StartRound +22-27 Fresh proposal +28-33 Proposal building off a valid round with prevotes +34-35 2f+1 prevote -> schedule timeout prevote +36-43 First proposal with prevotes -> precommit Some +44-46 2f+1 nil prevote -> precommit nil +47-48 2f+1 precommit -> schedule timeout precommit +49-54 First proposal with precommits -> finalize +55-56 f+1 round > local round, jump +57-60 on timeout propose +61-64 on timeout prevote +65-67 on timeout precommit +``` + +Remaining: + +``` +36-43 First proposal with prevotes -> precommit Some +57-60 on timeout propose +61-64 on timeout prevote +65-67 on timeout precommit +``` diff --git a/substrate/consensus/src/tendermint/paper.md b/substrate/tendermint/paper.md similarity index 100% rename from substrate/consensus/src/tendermint/paper.md rename to substrate/tendermint/paper.md diff --git a/substrate/consensus/src/tendermint/mod.rs b/substrate/tendermint/src/lib.rs similarity index 100% rename from substrate/consensus/src/tendermint/mod.rs rename to substrate/tendermint/src/lib.rs From 1237c41c535097d44fcb9218f43b195a2f7b2a0b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 03:29:16 -0400 Subject: [PATCH 004/186] Delete the old paper doc --- substrate/tendermint/paper.md | 40 ----------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 substrate/tendermint/paper.md diff --git a/substrate/tendermint/paper.md b/substrate/tendermint/paper.md deleted file mode 100644 index bf8d10310..000000000 --- a/substrate/tendermint/paper.md +++ /dev/null @@ -1,40 +0,0 @@ -The [paper](https://arxiv.org/abs/1807.04938) describes the algorithm with -pseudocode on page 6. This pseudocode is written as a series of conditions for -advancement. This is extremely archaic, as its a fraction of the actually -required code. This is due to its hand-waving away of data tracking, lack of -comments (beyond the entire rest of the paper, of course), and lack of -specification regarding faulty nodes. - -While the "hand-waving" is both legitimate and expected, as it's not the paper's -job to describe a full message processing loop nor efficient variable handling, -it does leave behind ambiguities and annoyances, not to mention an overall -structure which cannot be directly translated. This document is meant to be a -description of it enabling translation. - -The described pseudocode segments can be minimally described as follows: - -``` -01-09 Init -10-10 StartRound(0) -11-21 StartRound -22-27 Fresh proposal -28-33 Proposal building off a valid round with prevotes -34-35 2f+1 prevote -> schedule timeout prevote -36-43 First proposal with prevotes -> precommit Some -44-46 2f+1 nil prevote -> precommit nil -47-48 2f+1 precommit -> schedule timeout precommit -49-54 First proposal with precommits -> finalize -55-56 f+1 round > local round, jump -57-60 on timeout propose -61-64 on timeout prevote -65-67 on timeout precommit -``` - -Remaining: - -``` -36-43 First proposal with prevotes -> precommit Some -57-60 on timeout propose -61-64 on timeout prevote -65-67 on timeout precommit -``` From a5f1ddaf1b4b1511d9cb78c9e0292371b99efc50 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 03:29:55 -0400 Subject: [PATCH 005/186] Refactor out external parts to generics Also creates a dedicated file for the message log. --- Cargo.lock | 7 + substrate/tendermint/src/ext.rs | 28 +++ substrate/tendermint/src/lib.rs | 253 +++++++++--------------- substrate/tendermint/src/message_log.rs | 84 ++++++++ substrate/tendermint/tests/ext.rs | 29 +++ 5 files changed, 243 insertions(+), 158 deletions(-) create mode 100644 substrate/tendermint/src/ext.rs create mode 100644 substrate/tendermint/src/message_log.rs create mode 100644 substrate/tendermint/tests/ext.rs diff --git a/Cargo.lock b/Cargo.lock index fdb001ba7..4c2cbfa0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8853,6 +8853,13 @@ dependencies = [ "winapi", ] +[[package]] +name = "tendermint-machine" +version = "0.1.0" +dependencies = [ + "tokio", +] + [[package]] name = "term" version = "0.7.0" diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs new file mode 100644 index 000000000..d46aa383b --- /dev/null +++ b/substrate/tendermint/src/ext.rs @@ -0,0 +1,28 @@ +use core::{hash::Hash, fmt::Debug}; + +pub trait ValidatorId: Clone + Copy + PartialEq + Eq + Hash + Debug {} +impl ValidatorId for V {} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum BlockError { + // Invalid behavior entirely + Fatal, + // Potentially valid behavior dependent on unsynchronized state + Temporal, +} + +pub trait Block: Clone + PartialEq { + type Id: Copy + Clone + PartialEq; + + fn id(&self) -> Self::Id; +} + +pub trait Network { + fn total_weight(&self) -> u64; + fn weight(&self, validator: V) -> u64; + fn threshold(&self) -> u64 { + ((self.total_weight() * 2) / 3) + 1 + } + + fn validate(&mut self, block: B) -> Result<(), BlockError>; +} diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 54c83c08a..08b056751 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -1,28 +1,13 @@ -use std::collections::HashMap; +pub mod ext; +use ext::*; -use tokio::{task::{JoinHandle, spawn}, sync::mpsc}; +mod message_log; -type ValidatorId = u16; -const VALIDATORS: ValidatorId = 5; - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -struct Hash; -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -struct Block { - hash: Hash -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum BlockError { - // Invalid behavior entirely - Fatal, - // Potentially valid behavior dependent on unsynchronized state - Temporal, -} - -fn valid(block: &Block) -> Result<(), BlockError> { - Ok(()) -} +// Type aliases which are distinct according to the type system +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub(crate) struct BlockNumber(u32); +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub(crate) struct Round(u32); #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] enum Step { @@ -31,14 +16,14 @@ enum Step { Precommit, } -#[derive(Clone, PartialEq, Eq, Debug)] -enum Data { - Proposal(Option, Block), - Prevote(Option), - Precommit(Option), +#[derive(Clone, PartialEq)] +enum Data { + Proposal(Option, B), + Prevote(Option), + Precommit(Option), } -impl Data { +impl Data { fn step(&self) -> Step { match self { Data::Proposal(..) => Step::Propose, @@ -48,26 +33,36 @@ impl Data { } } -#[derive(Clone, PartialEq, Eq, Debug)] -struct Message { - sender: ValidatorId, +#[derive(Clone, PartialEq)] +struct Message { + sender: V, - height: u32, - round: u32, + number: BlockNumber, + round: Round, - data: Data, + data: Data, } #[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum TendermintError { - MaliciousOrTemporal(u16), // TODO: Remove when we figure this out - Malicious(u16), - Offline(u16), +enum TendermintError { + Malicious(V), + Offline(V), Temporal, } -fn proposer(height: u32, round: u32) -> ValidatorId { - ValidatorId::try_from((height + round) % u32::try_from(VALIDATORS).unwrap()).unwrap() +/* +use std::collections::HashMap; + +use tokio::{ + task::{JoinHandle, spawn}, + sync::mpsc, +}; + +type ValidatorId = u16; +const VALIDATORS: ValidatorId = 5; + +fn proposer(number: u32, round: u32) -> ValidatorId { + ValidatorId::try_from((number + round) % u32::try_from(VALIDATORS).unwrap()).unwrap() } #[derive(Debug)] @@ -75,7 +70,7 @@ struct TendermintMachine { proposer: ValidatorId, personal_proposal: Option, - height: u32, + number: u32, log_map: HashMap>>, precommitted: HashMap, @@ -98,7 +93,7 @@ struct TendermintHandle { impl TendermintMachine { fn broadcast(&self, data: Data) -> Option { - let msg = Message { sender: self.proposer, height: self.height, round: self.round, data }; + let msg = Message { sender: self.proposer, number: self.number, round: self.round, data }; let res = self.message(msg).unwrap(); self.broadcast.send(msg).unwrap(); res @@ -106,15 +101,15 @@ impl TendermintMachine { // 14-21 fn round_propose(&mut self) { - // This will happen if it's a new height and propose hasn't been called yet + // This will happen if it's a new block and propose hasn't been called yet if self.personal_proposal.is_none() { - // Ensure it's actually a new height. Else, the caller failed to provide necessary data yet + // Ensure it's actually a new block. Else, the caller failed to provide necessary data yet // is still executing the machine debug_assert_eq!(self.round, 0); return; } - if proposer(self.height, self.round) == self.proposer { + if proposer(self.number, self.round) == self.proposer { let (round, block) = if let Some((round, block)) = self.valid { (Some(round), block) } else { @@ -122,7 +117,7 @@ impl TendermintMachine { }; debug_assert!(self.broadcast(Data::Proposal(round, block)).is_none()); } else { - self.timeouts.write().unwrap().insert(Step::Precommit, self.timeout(Step::Precommit)); + self.timeouts.write().unwrap().insert(Step::Propose, self.timeout(Step::Propose)); } } @@ -133,7 +128,7 @@ impl TendermintMachine { self.round_propose(); } - /// Called whenever a new height occurs + /// Called whenever a new block occurs fn propose(&mut self, block: Block) { self.personal_proposal = Some(block); self.round_propose(); @@ -143,7 +138,7 @@ impl TendermintMachine { fn reset(&mut self) { self.personal_proposal = None; - self.height += 1; + self.number += 1; self.log_map = HashMap::new(); self.precommitted = HashMap::new(); @@ -155,7 +150,7 @@ impl TendermintMachine { } // 10 - pub fn new(proposer: ValidatorId, height: u32) -> TendermintHandle { + pub fn new(proposer: ValidatorId, number: u32) -> TendermintHandle { let block = Arc::new(RwLock::new(None)); let (msg_send, mut msg_recv) = mpsc::channel(100); // Backlog to accept. Currently arbitrary let (broadcast_send, broadcast_recv) = mpsc::channel(5); @@ -168,7 +163,7 @@ impl TendermintMachine { proposer, personal_proposal: None, - height, + number, log_map: HashMap::new(), precommitted: HashMap::new(), @@ -177,7 +172,7 @@ impl TendermintMachine { valid: None, round: 0, - step: Step::Propose + step: Step::Propose, }; loop { @@ -193,106 +188,31 @@ impl TendermintMachine { let now = Instant::now(); let (t1, t2, t3) = { -let timeouts = self.timeouts.read().unwrap(); -let ready = |step| timeouts.get(step).unwrap_or(now) < now; -(ready(Step::Propose), ready(Step::Prevote), ready(Step::Precommit)) -}; + let timeouts = self.timeouts.read().unwrap(); + let ready = |step| timeouts.get(step).unwrap_or(now) < now; + (ready(Step::Propose), ready(Step::Prevote), ready(Step::Precommit)) + }; -if t1 { // Propose timeout -} -if t2 { // Prevote timeout -} -if t3 { // Precommit timeout -} + if t1 { // Propose timeout + } + if t2 { // Prevote timeout + } + if t3 { // Precommit timeout + } match recv.try_recv() { Ok(msg) => machine.message(msg), Err(TryRecvError::Empty) => tokio::yield_now().await, - Err(TryRecvError::Disconnected) => break + Err(TryRecvError::Disconnected) => break, } } - }) - } - } - - // Returns true if it's a new message - fn log(&mut self, msg: Message) -> Result { - if matches!(msg.data, Data::Proposal(..)) && (msg.sender != proposer(msg.height, msg.round)) { - Err(TendermintError::Malicious(msg.sender))?; - }; - - if !self.log_map.contains_key(&msg.round) { - self.log_map.insert(msg.round, HashMap::new()); - } - let log = self.log_map.get_mut(&msg.round).unwrap(); - if !log.contains_key(&msg.sender) { - log.insert(msg.sender, HashMap::new()); - } - let log = log.get_mut(&msg.sender).unwrap(); - - // Handle message replays without issue. It's only multiple messages which is malicious - let step = msg.data.step(); - if let Some(existing) = log.get(&step) { - if existing != &msg.data { - Err(TendermintError::Malicious(msg.sender))?; - } - return Ok(false); - } - - // If they already precommitted to a distinct hash, error - if let Data::Precommit(Some(hash)) = msg.data { - if let Some(prev) = self.precommitted.get(&msg.sender) { - if hash != *prev { - Err(TendermintError::Malicious(msg.sender))?; - } - } - self.precommitted.insert(msg.sender, hash); + }), } - - log.insert(step, msg.data); - Ok(true) - } - - fn message_instances(&self, round: u32, data: Data) -> (usize, usize) { - let participating = 0; - let weight = 0; - for participant in self.log_map[&round].values() { - if let Some(msg) = participant.get(&data.step()) { - let validator_weight = 1; // TODO - participating += validator_weight; - if &data == msg { - weight += validator_weight; - } - - // If the msg exists, yet has a distinct hash, this validator is faulty - // (at least for precommit) - // TODO - } - } - (participating, weight) - } - - fn participation(&self, round: u32, step: Step) -> usize { - let (participating, _) = self.message_instances(round, match step { - Step::Propose => panic!("Checking for participation on Propose"), - Step::Prevote => Data::Prevote(None), - Step::Precommit => Data::Precommit(None), - }); - participating - } - - fn has_participation(&self, round: u32, step: Step) -> bool { - self.participation(round, step) >= ((VALIDATORS / 3 * 2) + 1).into() - } - - fn has_consensus(&self, round: u32, data: Data) -> bool { - let (_, weight) = self.message_instances(round, data); - weight >= ((VALIDATORS / 3 * 2) + 1).into() } // 49-54 fn check_committed(&mut self, round_num: u32) -> Option { - let proposer = proposer(self.height, round_num); + let proposer = proposer(self.number, round_num); // Safe as we only check for rounds which we received a message for let round = self.log_map[&round_num]; @@ -302,7 +222,8 @@ if t3 { // Precommit timeout debug_assert!(matches!(proposal, Data::Proposal(..))); if let Data::Proposal(_, block) = proposal { // Check if it has gotten a sufficient amount of precommits - let (participants, weight) = self.message_instances(round_num, Data::Precommit(Some(block.hash))); + let (participants, weight) = + self.message_instances(round_num, Data::Precommit(Some(block.hash))); let threshold = ((VALIDATORS / 3) * 2) + 1; if weight >= threshold.into() { @@ -310,8 +231,12 @@ if t3 { // Precommit timeout return Some(*block); } - if (participants >= threshold.into()) && first { - schedule timeoutPrecommit(self.height, round); + // 47-48 + if participants >= threshold.into() { + let map = self.timeouts.write().unwrap(); + if !map.contains_key(Step::Precommit) { + map.insert(Step::Precommit, self.timeout(Step::Precommit)); + } } } } @@ -320,10 +245,14 @@ if t3 { // Precommit timeout } fn message(&mut self, msg: Message) -> Result, TendermintError> { - if msg.height != self.height { + if msg.number != self.number { Err(TendermintError::Temporal)?; } + if matches!(msg.data, Data::Proposal(..)) && (msg.sender != proposer(msg.height, msg.round)) { + Err(TendermintError::Malicious(msg.sender))?; + }; + if !self.log(msg)? { return Ok(None); } @@ -352,18 +281,23 @@ if t3 { // Precommit timeout } if self.step == Step::Propose { - if let Some(proposal) = round.get(&proposer(self.height, self.round)).map(|p| p.get(&Step::Propose)).flatten() { + if let Some(proposal) = + round.get(&proposer(self.number, self.round)).map(|p| p.get(&Step::Propose)).flatten() + { debug_assert!(matches!(proposal, Data::Proposal(..))); if let Data::Proposal(vr, block) = proposal { if let Some(vr) = vr { // 28-33 let vr = *vr; if (vr < self.round) && self.has_consensus(vr, Data::Prevote(Some(block.hash))) { - debug_assert!(self.broadcast( - Data::Prevote( - Some(block.hash).filter(|_| self.locked.map(|(round, value)| (round <= vr) || (block == &value)).unwrap_or(true)) - ) - ).is_none()); + debug_assert!(self + .broadcast(Data::Prevote(Some(block.hash).filter(|_| { + self + .locked + .map(|(round, value)| (round <= vr) || (block == &value)) + .unwrap_or(true) + }))) + .is_none()); self.step = Step::Prevote; } else { Err(TendermintError::Malicious(msg.sender))?; @@ -371,7 +305,11 @@ if t3 { // Precommit timeout } else { // 22-27 valid(&block).map_err(|_| TendermintError::Malicious(msg.sender))?; - debug_assert!(self.broadcast(Data::Prevote(Some(block.hash).filter(|_| self.locked.is_none() || self.locked.map(|locked| &locked.1) == Some(block)))).is_none()); + debug_assert!(self + .broadcast(Data::Prevote(Some(block.hash).filter( + |_| self.locked.is_none() || self.locked.map(|locked| &locked.1) == Some(block) + ))) + .is_none()); self.step = Step::Prevote; } } @@ -381,8 +319,11 @@ if t3 { // Precommit timeout if self.step == Step::Prevote { let (participation, weight) = self.message_instances(self.round, Data::Prevote(None)); // 34-35 - if (participation > (((VALIDATORS / 3) * 2) + 1).into()) && first { - self.timeouts.write().unwrap().insert(Step::Prevote, self.timeout(Step::Prevote)); + if participation > (((VALIDATORS / 3) * 2) + 1).into() { + let map = self.timeouts.write().unwrap(); + if !map.contains_key(Step::Prevote) { + map.insert(Step::Prevote, self.timeout(Step::Prevote)) + } } // 44-46 @@ -392,11 +333,7 @@ if t3 { // Precommit timeout } } - // 47-48 - if self.has_participation(self.round, Step::Precommit) && first { - self.timeouts.write().unwrap().insert(Step::Precommit, self.timeout(Step::Precommit)); - } - Ok(None) } } +*/ diff --git a/substrate/tendermint/src/message_log.rs b/substrate/tendermint/src/message_log.rs new file mode 100644 index 000000000..ad240ba01 --- /dev/null +++ b/substrate/tendermint/src/message_log.rs @@ -0,0 +1,84 @@ +use std::{sync::Arc, collections::HashMap}; + +use crate::{ext::*, Round, Step, Data, Message, TendermintError}; + +pub(crate) struct MessageLog> { + network: Arc, + precommitted: HashMap, + log: HashMap>>>, +} + +impl> MessageLog { + pub(crate) fn new(network: Arc) -> MessageLog { + MessageLog { network, precommitted: HashMap::new(), log: HashMap::new() } + } + + // Returns true if it's a new message + pub(crate) fn log(&mut self, msg: Message) -> Result> { + let round = self.log.entry(msg.round).or_insert_with(HashMap::new); + let msgs = round.entry(msg.sender).or_insert_with(HashMap::new); + + // Handle message replays without issue. It's only multiple messages which is malicious + let step = msg.data.step(); + if let Some(existing) = msgs.get(&step) { + if existing != &msg.data { + Err(TendermintError::Malicious(msg.sender))?; + } + return Ok(false); + } + + // If they already precommitted to a distinct hash, error + if let Data::Precommit(Some(hash)) = msg.data { + if let Some(prev) = self.precommitted.get(&msg.sender) { + if hash != *prev { + Err(TendermintError::Malicious(msg.sender))?; + } + } + self.precommitted.insert(msg.sender, hash); + } + + msgs.insert(step, msg.data); + Ok(true) + } + + // For a given round, return the participating weight for this step, and the weight agreeing with + // the data. + pub(crate) fn message_instances(&self, round: Round, data: Data) -> (u64, u64) { + let mut participating = 0; + let mut weight = 0; + for (participant, msgs) in &self.log[&round] { + if let Some(msg) = msgs.get(&data.step()) { + let validator_weight = self.network.weight(*participant); + participating += validator_weight; + if &data == msg { + weight += validator_weight; + } + } + } + (participating, weight) + } + + // Get the participation in a given round for a given step. + pub(crate) fn participation(&self, round: Round, step: Step) -> u64 { + let (participating, _) = self.message_instances( + round, + match step { + Step::Propose => panic!("Checking for participation on Propose"), + Step::Prevote => Data::Prevote(None), + Step::Precommit => Data::Precommit(None), + }, + ); + participating + } + + // Check if there's been a BFT level of participation + pub(crate) fn has_participation(&self, round: Round, step: Step) -> bool { + self.participation(round, step) >= self.network.threshold() + } + + // Check if consensus has been reached on a specific piece of data + pub(crate) fn has_consensus(&self, round: Round, data: Data) -> bool { + let (_, weight) = self.message_instances(round, data); + weight >= self.network.threshold() + } +} diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs new file mode 100644 index 000000000..f4a379229 --- /dev/null +++ b/substrate/tendermint/tests/ext.rs @@ -0,0 +1,29 @@ +use tendermint_machine::ext::{BlockError, Block, Network}; + +#[derive(Clone, PartialEq)] +struct TestBlock { + id: u32, + valid: Result<(), BlockError>, +} + +impl Block for TestBlock { + type Id = u32; + + fn id(&self) -> u32 { + self.id + } +} + +struct TestNetwork; +impl Network for TestNetwork { + fn total_weight(&self) -> u64 { + 5 + } + fn weight(&self, id: u16) -> u64 { + [1, 1, 1, 1, 1][usize::try_from(id).unwrap()] + } + + fn validate(&mut self, block: TestBlock) -> Result<(), BlockError> { + block.valid + } +} From f79321233db4988972b9abfbd980523882b2759a Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 03:55:39 -0400 Subject: [PATCH 006/186] Refactor to type V, type B --- substrate/tendermint/src/ext.rs | 17 ++++++++++++--- substrate/tendermint/src/lib.rs | 13 ------------ substrate/tendermint/src/message_log.rs | 28 ++++++++++++++++++------- substrate/tendermint/tests/ext.rs | 11 ++++++++-- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index d46aa383b..ed6150c49 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -3,6 +3,12 @@ use core::{hash::Hash, fmt::Debug}; pub trait ValidatorId: Clone + Copy + PartialEq + Eq + Hash + Debug {} impl ValidatorId for V {} +// Type aliases which are distinct according to the type system +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct BlockNumber(pub u32); +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct Round(pub u16); + #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum BlockError { // Invalid behavior entirely @@ -17,12 +23,17 @@ pub trait Block: Clone + PartialEq { fn id(&self) -> Self::Id; } -pub trait Network { +pub trait Network { + type ValidatorId: ValidatorId; + type Block: Block; + fn total_weight(&self) -> u64; - fn weight(&self, validator: V) -> u64; + fn weight(&self, validator: Self::ValidatorId) -> u64; fn threshold(&self) -> u64 { ((self.total_weight() * 2) / 3) + 1 } - fn validate(&mut self, block: B) -> Result<(), BlockError>; + fn proposer(&self, number: BlockNumber, round: Round) -> Self::ValidatorId; + + fn validate(&mut self, block: Self::Block) -> Result<(), BlockError>; } diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 08b056751..7bb15baf1 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -3,12 +3,6 @@ use ext::*; mod message_log; -// Type aliases which are distinct according to the type system -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub(crate) struct BlockNumber(u32); -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub(crate) struct Round(u32); - #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] enum Step { Propose, @@ -58,13 +52,6 @@ use tokio::{ sync::mpsc, }; -type ValidatorId = u16; -const VALIDATORS: ValidatorId = 5; - -fn proposer(number: u32, round: u32) -> ValidatorId { - ValidatorId::try_from((number + round) % u32::try_from(VALIDATORS).unwrap()).unwrap() -} - #[derive(Debug)] struct TendermintMachine { proposer: ValidatorId, diff --git a/substrate/tendermint/src/message_log.rs b/substrate/tendermint/src/message_log.rs index ad240ba01..2df03548a 100644 --- a/substrate/tendermint/src/message_log.rs +++ b/substrate/tendermint/src/message_log.rs @@ -2,19 +2,22 @@ use std::{sync::Arc, collections::HashMap}; use crate::{ext::*, Round, Step, Data, Message, TendermintError}; -pub(crate) struct MessageLog> { +pub(crate) struct MessageLog { network: Arc, - precommitted: HashMap, - log: HashMap>>>, + precommitted: HashMap::Id>, + log: HashMap>>>, } -impl> MessageLog { - pub(crate) fn new(network: Arc) -> MessageLog { +impl MessageLog { + pub(crate) fn new(network: Arc) -> MessageLog { MessageLog { network, precommitted: HashMap::new(), log: HashMap::new() } } // Returns true if it's a new message - pub(crate) fn log(&mut self, msg: Message) -> Result> { + pub(crate) fn log( + &mut self, + msg: Message, + ) -> Result> { let round = self.log.entry(msg.round).or_insert_with(HashMap::new); let msgs = round.entry(msg.sender).or_insert_with(HashMap::new); @@ -43,7 +46,7 @@ impl> MessageLog { // For a given round, return the participating weight for this step, and the weight agreeing with // the data. - pub(crate) fn message_instances(&self, round: Round, data: Data) -> (u64, u64) { + pub(crate) fn message_instances(&self, round: Round, data: Data) -> (u64, u64) { let mut participating = 0; let mut weight = 0; for (participant, msgs) in &self.log[&round] { @@ -77,8 +80,17 @@ impl> MessageLog { } // Check if consensus has been reached on a specific piece of data - pub(crate) fn has_consensus(&self, round: Round, data: Data) -> bool { + pub(crate) fn has_consensus(&self, round: Round, data: Data) -> bool { let (_, weight) = self.message_instances(round, data); weight >= self.network.threshold() } + + pub(crate) fn get( + &self, + round: Round, + sender: N::ValidatorId, + step: Step, + ) -> Option<&Data> { + self.log.get(&round).and_then(|round| round.get(&sender).and_then(|msgs| msgs.get(&step))) + } } diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index f4a379229..fe967b8be 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -1,4 +1,4 @@ -use tendermint_machine::ext::{BlockError, Block, Network}; +use tendermint_machine::ext::*; #[derive(Clone, PartialEq)] struct TestBlock { @@ -15,7 +15,10 @@ impl Block for TestBlock { } struct TestNetwork; -impl Network for TestNetwork { +impl Network for TestNetwork { + type ValidatorId = u16; + type Block = TestBlock; + fn total_weight(&self) -> u64 { 5 } @@ -23,6 +26,10 @@ impl Network for TestNetwork { [1, 1, 1, 1, 1][usize::try_from(id).unwrap()] } + fn proposer(&self, number: BlockNumber, round: Round) -> u16 { + u16::try_from((number.0 + u32::from(round.0)) % 5).unwrap() + } + fn validate(&mut self, block: TestBlock) -> Result<(), BlockError> { block.valid } From 77ba1c00e245c63fc6de0ce755bab999424e9943 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 07:30:11 -0400 Subject: [PATCH 007/186] Successfully compiling --- Cargo.lock | 13 + substrate/tendermint/Cargo.toml | 4 +- substrate/tendermint/src/ext.rs | 36 ++- substrate/tendermint/src/lib.rs | 306 +++++++++++++----------- substrate/tendermint/src/message_log.rs | 23 +- substrate/tendermint/tests/ext.rs | 89 ++++++- 6 files changed, 305 insertions(+), 166 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c2cbfa0a..ac7fd1883 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,6 +256,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "async-recursion" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-std" version = "1.12.0" @@ -8857,6 +8868,8 @@ dependencies = [ name = "tendermint-machine" version = "0.1.0" dependencies = [ + "async-recursion", + "async-trait", "tokio", ] diff --git a/substrate/tendermint/Cargo.toml b/substrate/tendermint/Cargo.toml index aa53034bc..d626357aa 100644 --- a/substrate/tendermint/Cargo.toml +++ b/substrate/tendermint/Cargo.toml @@ -8,4 +8,6 @@ authors = ["Luke Parker "] edition = "2021" [dependencies] -tokio = "1" +async-recursion = "1.0" +async-trait = "0.1" +tokio = { version = "1", features = ["macros", "rt", "sync"] } diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index ed6150c49..3df8b62ab 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -1,7 +1,10 @@ use core::{hash::Hash, fmt::Debug}; +use std::sync::Arc; -pub trait ValidatorId: Clone + Copy + PartialEq + Eq + Hash + Debug {} -impl ValidatorId for V {} +use crate::Message; + +pub trait ValidatorId: Send + Sync + Clone + Copy + PartialEq + Eq + Hash + Debug {} +impl ValidatorId for V {} // Type aliases which are distinct according to the type system #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] @@ -17,23 +20,42 @@ pub enum BlockError { Temporal, } -pub trait Block: Clone + PartialEq { - type Id: Copy + Clone + PartialEq; +pub trait Block: Send + Sync + Clone + PartialEq + Debug { + type Id: Send + Sync + Copy + Clone + PartialEq + Debug; fn id(&self) -> Self::Id; } -pub trait Network { +pub trait Weights: Send + Sync { type ValidatorId: ValidatorId; - type Block: Block; fn total_weight(&self) -> u64; fn weight(&self, validator: Self::ValidatorId) -> u64; fn threshold(&self) -> u64 { ((self.total_weight() * 2) / 3) + 1 } + fn fault_thresold(&self) -> u64 { + (self.total_weight() - self.threshold()) + 1 + } + /// Weighted round robin function. fn proposer(&self, number: BlockNumber, round: Round) -> Self::ValidatorId; +} + +#[async_trait::async_trait] +pub trait Network: Send + Sync { + type ValidatorId: ValidatorId; + type Weights: Weights; + type Block: Block; + + fn weights(&self) -> Arc; + + async fn broadcast(&mut self, msg: Message); + + // TODO: Should this take a verifiable reason? + async fn slash(&mut self, validator: Self::ValidatorId); - fn validate(&mut self, block: Self::Block) -> Result<(), BlockError>; + fn validate(&mut self, block: &Self::Block) -> Result<(), BlockError>; + // Add a block and return the proposal for the next one + fn add_block(&mut self, block: Self::Block) -> Self::Block; } diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 7bb15baf1..e36c0750d 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -1,7 +1,18 @@ +use std::{sync::Arc, time::Instant, collections::HashMap}; + +use tokio::{ + task::{JoinHandle, yield_now}, + sync::{ + RwLock, + mpsc::{self, error::TryRecvError}, + }, +}; + pub mod ext; use ext::*; mod message_log; +use message_log::MessageLog; #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] enum Step { @@ -10,9 +21,9 @@ enum Step { Precommit, } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Debug)] enum Data { - Proposal(Option, B), + Proposal(Option, B), Prevote(Option), Precommit(Option), } @@ -27,8 +38,8 @@ impl Data { } } -#[derive(Clone, PartialEq)] -struct Message { +#[derive(Clone, PartialEq, Debug)] +pub struct Message { sender: V, number: BlockNumber, @@ -38,158 +49,155 @@ struct Message { } #[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum TendermintError { +pub enum TendermintError { Malicious(V), - Offline(V), Temporal, } -/* -use std::collections::HashMap; - -use tokio::{ - task::{JoinHandle, spawn}, - sync::mpsc, -}; - -#[derive(Debug)] -struct TendermintMachine { - proposer: ValidatorId, - personal_proposal: Option, +pub struct TendermintMachine { + network: Arc>, + weights: Arc, + proposer: N::ValidatorId, - number: u32, - - log_map: HashMap>>, - precommitted: HashMap, + number: BlockNumber, + personal_proposal: N::Block, - round: u32, + log: MessageLog, + round: Round, step: Step, - locked: Option<(u32, Block)>, - valid: Option<(u32, Block)>, - timeouts: Arc>>, // TODO: Remove Arc RwLock + locked: Option<(Round, N::Block)>, + valid: Option<(Round, N::Block)>, + + timeouts: HashMap, } -#[derive(Debug)] -struct TendermintHandle { - block: Arc>>, - messages: mpsc::Sender, - broadcast: mpsc::Receiver, - handle: JoinHandle<()>, +pub struct TendermintHandle { + // Messages received + pub messages: mpsc::Sender>, + // Async task executing the machine + pub handle: JoinHandle<()>, } -impl TendermintMachine { - fn broadcast(&self, data: Data) -> Option { +impl TendermintMachine { + fn timeout(&self, step: Step) -> Instant { + todo!() + } + + #[async_recursion::async_recursion] + async fn broadcast(&mut self, data: Data) -> Option { let msg = Message { sender: self.proposer, number: self.number, round: self.round, data }; - let res = self.message(msg).unwrap(); - self.broadcast.send(msg).unwrap(); + let res = self.message(msg.clone()).await.unwrap(); + self.network.write().await.broadcast(msg).await; res } // 14-21 - fn round_propose(&mut self) { - // This will happen if it's a new block and propose hasn't been called yet - if self.personal_proposal.is_none() { - // Ensure it's actually a new block. Else, the caller failed to provide necessary data yet - // is still executing the machine - debug_assert_eq!(self.round, 0); - return; - } - - if proposer(self.number, self.round) == self.proposer { - let (round, block) = if let Some((round, block)) = self.valid { - (Some(round), block) + async fn round_propose(&mut self) { + if self.weights.proposer(self.number, self.round) == self.proposer { + let (round, block) = if let Some((round, block)) = &self.valid { + (Some(*round), block.clone()) } else { - (None, self.personal_proposal.unwrap()) + (None, self.personal_proposal.clone()) }; - debug_assert!(self.broadcast(Data::Proposal(round, block)).is_none()); + debug_assert!(self.broadcast(Data::Proposal(round, block)).await.is_none()); } else { - self.timeouts.write().unwrap().insert(Step::Propose, self.timeout(Step::Propose)); + self.timeouts.insert(Step::Propose, self.timeout(Step::Propose)); } } // 11-13 - fn round(&mut self, round: u32) { + async fn round(&mut self, round: Round) { self.round = round; self.step = Step::Propose; - self.round_propose(); - } - - /// Called whenever a new block occurs - fn propose(&mut self, block: Block) { - self.personal_proposal = Some(block); - self.round_propose(); + self.round_propose().await; } // 1-9 - fn reset(&mut self) { - self.personal_proposal = None; - - self.number += 1; + async fn reset(&mut self, proposal: N::Block) { + self.number.0 += 1; + self.personal_proposal = proposal; - self.log_map = HashMap::new(); - self.precommitted = HashMap::new(); + self.log = MessageLog::new(self.network.read().await.weights()); self.locked = None; self.valid = None; - self.round(0); + self.timeouts = HashMap::new(); + + self.round(Round(0)).await; } // 10 - pub fn new(proposer: ValidatorId, number: u32) -> TendermintHandle { - let block = Arc::new(RwLock::new(None)); + pub fn new( + network: N, + proposer: N::ValidatorId, + number: BlockNumber, + proposal: N::Block, + ) -> TendermintHandle { let (msg_send, mut msg_recv) = mpsc::channel(100); // Backlog to accept. Currently arbitrary - let (broadcast_send, broadcast_recv) = mpsc::channel(5); TendermintHandle { - block: block.clone(), messages: msg_send, - broadcast: broadcast_recv, - handle: tokio::spawn(async { - let machine = TendermintMachine { + handle: tokio::spawn(async move { + let weights = network.weights(); + let network = Arc::new(RwLock::new(network)); + let mut machine = TendermintMachine { + network, + weights: weights.clone(), proposer, - personal_proposal: None, number, + personal_proposal: proposal, - log_map: HashMap::new(), - precommitted: HashMap::new(), + log: MessageLog::new(weights), + round: Round(0), + step: Step::Propose, locked: None, valid: None, - round: 0, - step: Step::Propose, + timeouts: HashMap::new(), }; + dbg!("Proposing"); + machine.round_propose().await; loop { - if self.personal_proposal.is_none() { - let block = block.lock().unwrap(); - if block.is_some() { - self.personal_proposal = Some(block.take()); - } else { - tokio::yield_now().await; - continue; - } - } - + // Check if any timeouts have been triggered let now = Instant::now(); let (t1, t2, t3) = { - let timeouts = self.timeouts.read().unwrap(); - let ready = |step| timeouts.get(step).unwrap_or(now) < now; + let ready = |step| machine.timeouts.get(&step).unwrap_or(&now) < &now; (ready(Step::Propose), ready(Step::Prevote), ready(Step::Precommit)) }; - if t1 { // Propose timeout + // Propose timeout + if t1 { + todo!() } - if t2 { // Prevote timeout + + // Prevote timeout + if t2 { + todo!() } - if t3 { // Precommit timeout + + // Precommit timeout + if t3 { + todo!() } - match recv.try_recv() { - Ok(msg) => machine.message(msg), - Err(TryRecvError::Empty) => tokio::yield_now().await, + // If there's a message, handle it + match msg_recv.try_recv() { + Ok(msg) => match machine.message(msg).await { + Ok(None) => (), + Ok(Some(block)) => { + let proposal = machine.network.write().await.add_block(block); + machine.reset(proposal).await + } + Err(TendermintError::Malicious(validator)) => { + machine.network.write().await.slash(validator).await + } + Err(TendermintError::Temporal) => (), + }, + Err(TryRecvError::Empty) => yield_now().await, Err(TryRecvError::Disconnected) => break, } } @@ -198,32 +206,27 @@ impl TendermintMachine { } // 49-54 - fn check_committed(&mut self, round_num: u32) -> Option { - let proposer = proposer(self.number, round_num); - // Safe as we only check for rounds which we received a message for - let round = self.log_map[&round_num]; + fn check_committed(&mut self, round: Round) -> Option { + let proposer = self.weights.proposer(self.number, round); // Get the proposal - if let Some(proposal) = round.get(&proposer).map(|p| p.get(&Step::Propose)).flatten() { + if let Some(proposal) = self.log.get(round, proposer, Step::Propose) { // Destructure debug_assert!(matches!(proposal, Data::Proposal(..))); if let Data::Proposal(_, block) = proposal { // Check if it has gotten a sufficient amount of precommits let (participants, weight) = - self.message_instances(round_num, Data::Precommit(Some(block.hash))); + self.log.message_instances(round, Data::Precommit(Some(block.id()))); - let threshold = ((VALIDATORS / 3) * 2) + 1; - if weight >= threshold.into() { - self.reset(); - return Some(*block); + let threshold = self.weights.threshold(); + if weight >= threshold { + return Some(block.clone()); } // 47-48 - if participants >= threshold.into() { - let map = self.timeouts.write().unwrap(); - if !map.contains_key(Step::Precommit) { - map.insert(Step::Precommit, self.timeout(Step::Precommit)); - } + if participants >= threshold { + let timeout = self.timeout(Step::Precommit); + self.timeouts.entry(Step::Precommit).or_insert(timeout); } } } @@ -231,16 +234,21 @@ impl TendermintMachine { None } - fn message(&mut self, msg: Message) -> Result, TendermintError> { + async fn message( + &mut self, + msg: Message, + ) -> Result, TendermintError> { if msg.number != self.number { Err(TendermintError::Temporal)?; } - if matches!(msg.data, Data::Proposal(..)) && (msg.sender != proposer(msg.height, msg.round)) { + if matches!(msg.data, Data::Proposal(..)) && + (msg.sender != self.weights.proposer(msg.number, msg.round)) + { Err(TendermintError::Malicious(msg.sender))?; }; - if !self.log(msg)? { + if !self.log.log(msg.clone())? { return Ok(None); } @@ -254,36 +262,38 @@ impl TendermintMachine { } // Else, check if we need to jump ahead - let round = self.log_map[&self.round]; - if msg.round < self.round { + if msg.round.0 < self.round.0 { return Ok(None); - } else if msg.round > self.round { + } else if msg.round.0 > self.round.0 { // 55-56 - // TODO: Move to weight - if round.len() > ((VALIDATORS / 3) + 1).into() { + if self.log.round_participation(self.round) > self.weights.fault_thresold() { self.round(msg.round); } else { return Ok(None); } } + let proposal = self + .log + .get(self.round, self.weights.proposer(self.number, self.round), Step::Propose) + .cloned(); if self.step == Step::Propose { - if let Some(proposal) = - round.get(&proposer(self.number, self.round)).map(|p| p.get(&Step::Propose)).flatten() - { + if let Some(proposal) = &proposal { debug_assert!(matches!(proposal, Data::Proposal(..))); if let Data::Proposal(vr, block) = proposal { if let Some(vr) = vr { // 28-33 - let vr = *vr; - if (vr < self.round) && self.has_consensus(vr, Data::Prevote(Some(block.hash))) { + if (vr.0 < self.round.0) && self.log.has_consensus(*vr, Data::Prevote(Some(block.id()))) + { debug_assert!(self - .broadcast(Data::Prevote(Some(block.hash).filter(|_| { + .broadcast(Data::Prevote(Some(block.id()).filter(|_| { self .locked - .map(|(round, value)| (round <= vr) || (block == &value)) + .as_ref() + .map(|(round, value)| (round.0 <= vr.0) || (block.id() == value.id())) .unwrap_or(true) }))) + .await .is_none()); self.step = Step::Prevote; } else { @@ -291,11 +301,16 @@ impl TendermintMachine { } } else { // 22-27 - valid(&block).map_err(|_| TendermintError::Malicious(msg.sender))?; + self + .network + .write() + .await + .validate(block) + .map_err(|_| TendermintError::Malicious(msg.sender))?; debug_assert!(self - .broadcast(Data::Prevote(Some(block.hash).filter( - |_| self.locked.is_none() || self.locked.map(|locked| &locked.1) == Some(block) - ))) + .broadcast(Data::Prevote(Some(block.id()).filter(|_| self.locked.is_none() || + self.locked.as_ref().map(|locked| locked.1.id()) == Some(block.id())))) + .await .is_none()); self.step = Step::Prevote; } @@ -304,23 +319,36 @@ impl TendermintMachine { } if self.step == Step::Prevote { - let (participation, weight) = self.message_instances(self.round, Data::Prevote(None)); + let (participation, weight) = self.log.message_instances(self.round, Data::Prevote(None)); // 34-35 - if participation > (((VALIDATORS / 3) * 2) + 1).into() { - let map = self.timeouts.write().unwrap(); - if !map.contains_key(Step::Prevote) { - map.insert(Step::Prevote, self.timeout(Step::Prevote)) - } + if participation > self.weights.threshold() { + let timeout = self.timeout(Step::Prevote); + self.timeouts.entry(Step::Prevote).or_insert(timeout); } // 44-46 - if (weight > (((VALIDATORS / 3) * 2) + 1).into()) && first { - debug_assert!(self.broadcast(Data::Precommit(None)).is_none()); + if weight > self.weights.threshold() { + debug_assert!(self.broadcast(Data::Precommit(None)).await.is_none()); self.step = Step::Precommit; } } + if (self.valid.is_none()) && ((self.step == Step::Prevote) || (self.step == Step::Precommit)) { + if let Some(proposal) = proposal { + debug_assert!(matches!(proposal, Data::Proposal(..))); + if let Data::Proposal(_, block) = proposal { + if self.log.has_consensus(self.round, Data::Prevote(Some(block.id()))) { + self.valid = Some((self.round, block.clone())); + if self.step == Step::Prevote { + self.locked = self.valid.clone(); + self.step = Step::Precommit; + return Ok(self.broadcast(Data::Precommit(Some(block.id()))).await); + } + } + } + } + } + Ok(None) } } -*/ diff --git a/substrate/tendermint/src/message_log.rs b/substrate/tendermint/src/message_log.rs index 2df03548a..45505db4e 100644 --- a/substrate/tendermint/src/message_log.rs +++ b/substrate/tendermint/src/message_log.rs @@ -3,14 +3,14 @@ use std::{sync::Arc, collections::HashMap}; use crate::{ext::*, Round, Step, Data, Message, TendermintError}; pub(crate) struct MessageLog { - network: Arc, + weights: Arc, precommitted: HashMap::Id>, log: HashMap>>>, } impl MessageLog { - pub(crate) fn new(network: Arc) -> MessageLog { - MessageLog { network, precommitted: HashMap::new(), log: HashMap::new() } + pub(crate) fn new(weights: Arc) -> MessageLog { + MessageLog { weights, precommitted: HashMap::new(), log: HashMap::new() } } // Returns true if it's a new message @@ -51,7 +51,7 @@ impl MessageLog { let mut weight = 0; for (participant, msgs) in &self.log[&round] { if let Some(msg) = msgs.get(&data.step()) { - let validator_weight = self.network.weight(*participant); + let validator_weight = self.weights.weight(*participant); participating += validator_weight; if &data == msg { weight += validator_weight; @@ -61,6 +61,17 @@ impl MessageLog { (participating, weight) } + // Get the participation in a given round + pub(crate) fn round_participation(&self, round: Round) -> u64 { + let mut weight = 0; + if let Some(round) = self.log.get(&round) { + for participant in round.keys() { + weight += self.weights.weight(*participant); + } + }; + weight + } + // Get the participation in a given round for a given step. pub(crate) fn participation(&self, round: Round, step: Step) -> u64 { let (participating, _) = self.message_instances( @@ -76,13 +87,13 @@ impl MessageLog { // Check if there's been a BFT level of participation pub(crate) fn has_participation(&self, round: Round, step: Step) -> bool { - self.participation(round, step) >= self.network.threshold() + self.participation(round, step) >= self.weights.threshold() } // Check if consensus has been reached on a specific piece of data pub(crate) fn has_consensus(&self, round: Round, data: Data) -> bool { let (_, weight) = self.message_instances(round, data); - weight >= self.network.threshold() + weight >= self.weights.threshold() } pub(crate) fn get( diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index fe967b8be..ef19a973c 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -1,36 +1,99 @@ -use tendermint_machine::ext::*; +use std::sync::Arc; -#[derive(Clone, PartialEq)] +use tokio::sync::{RwLock, mpsc}; + +use tendermint_machine::{ext::*, Message, TendermintMachine, TendermintHandle}; + +type TestValidatorId = u16; +type TestBlockId = u32; + +#[derive(Clone, PartialEq, Debug)] struct TestBlock { - id: u32, + id: TestBlockId, valid: Result<(), BlockError>, } impl Block for TestBlock { - type Id = u32; + type Id = TestBlockId; - fn id(&self) -> u32 { + fn id(&self) -> TestBlockId { self.id } } -struct TestNetwork; -impl Network for TestNetwork { - type ValidatorId = u16; - type Block = TestBlock; +struct TestWeights; +impl Weights for TestWeights { + type ValidatorId = TestValidatorId; fn total_weight(&self) -> u64 { 5 } - fn weight(&self, id: u16) -> u64 { + fn weight(&self, id: TestValidatorId) -> u64 { [1, 1, 1, 1, 1][usize::try_from(id).unwrap()] } - fn proposer(&self, number: BlockNumber, round: Round) -> u16 { - u16::try_from((number.0 + u32::from(round.0)) % 5).unwrap() + fn proposer(&self, number: BlockNumber, round: Round) -> TestValidatorId { + TestValidatorId::try_from((number.0 + u32::from(round.0)) % 5).unwrap() + } +} + +struct TestNetwork(Arc>>>); + +#[async_trait::async_trait] +impl Network for TestNetwork { + type ValidatorId = TestValidatorId; + type Weights = TestWeights; + type Block = TestBlock; + + fn weights(&self) -> Arc { + Arc::new(TestWeights) + } + + async fn broadcast(&mut self, msg: Message) { + for handle in self.0.write().await.iter_mut() { + handle.messages.send(msg.clone()).await.unwrap(); + } + } + + async fn slash(&mut self, validator: TestValidatorId) { + dbg!("Slash"); + todo!() } - fn validate(&mut self, block: TestBlock) -> Result<(), BlockError> { + fn validate(&mut self, block: &TestBlock) -> Result<(), BlockError> { block.valid } + + fn add_block(&mut self, block: TestBlock) -> TestBlock { + dbg!("Adding ", &block); + assert!(block.valid.is_ok()); + TestBlock { id: block.id + 1, valid: Ok(()) } + } +} + +impl TestNetwork { + async fn new(validators: usize) -> Arc>>> { + let arc = Arc::new(RwLock::new(vec![])); + { + let mut write = arc.write().await; + for i in 0 .. validators { + write.push(TendermintMachine::new( + TestNetwork(arc.clone()), + u16::try_from(i).unwrap(), + BlockNumber(1), + TestBlock { id: 1, valid: Ok(()) }, + )); + } + } + dbg!("Created all machines"); + arc + } +} + +#[tokio::test] +async fn test() { + TestNetwork::new(4).await; + loop { + tokio::task::yield_now().await; + } } From 079eee931ab5a35bf028505e95d5667cd7c2d73d Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 07:54:07 -0400 Subject: [PATCH 008/186] Calculate timeouts --- substrate/tendermint/src/ext.rs | 3 +++ substrate/tendermint/src/lib.rs | 25 +++++++++++++++++++++++-- substrate/tendermint/tests/ext.rs | 2 ++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 3df8b62ab..642553c98 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -48,6 +48,9 @@ pub trait Network: Send + Sync { type Weights: Weights; type Block: Block; + // Block time in seconds + const BLOCK_TIME: u32; + fn weights(&self) -> Arc; async fn broadcast(&mut self, msg: Message); diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index e36c0750d..9f221bc9d 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -1,4 +1,8 @@ -use std::{sync::Arc, time::Instant, collections::HashMap}; +use std::{ + sync::Arc, + time::{Instant, Duration}, + collections::HashMap, +}; use tokio::{ task::{JoinHandle, yield_now}, @@ -60,6 +64,7 @@ pub struct TendermintMachine { proposer: N::ValidatorId, number: BlockNumber, + start_time: Instant, personal_proposal: N::Block, log: MessageLog, @@ -81,7 +86,16 @@ pub struct TendermintHandle { impl TendermintMachine { fn timeout(&self, step: Step) -> Instant { - todo!() + let mut round_time = Duration::from_secs(N::BLOCK_TIME.into()); + round_time *= (self.round.0 + 1).into(); + let step_time = round_time / 3; + + let offset = match step { + Step::Propose => step_time, + Step::Prevote => step_time * 2, + Step::Precommit => step_time * 3, + }; + self.start_time + offset } #[async_recursion::async_recursion] @@ -108,6 +122,11 @@ impl TendermintMachine { // 11-13 async fn round(&mut self, round: Round) { + // Correct the start time + for _ in self.round.0 .. round.0 { + self.start_time = self.timeout(Step::Precommit); + } + self.round = round; self.step = Step::Propose; self.round_propose().await; @@ -116,6 +135,7 @@ impl TendermintMachine { // 1-9 async fn reset(&mut self, proposal: N::Block) { self.number.0 += 1; + self.start_time = Instant::now(); self.personal_proposal = proposal; self.log = MessageLog::new(self.network.read().await.weights()); @@ -147,6 +167,7 @@ impl TendermintMachine { proposer, number, + start_time: Instant::now(), personal_proposal: proposal, log: MessageLog::new(weights), diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index ef19a973c..fe7732470 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -45,6 +45,8 @@ impl Network for TestNetwork { type Weights = TestWeights; type Block = TestBlock; + const BLOCK_TIME: u32 = 1; + fn weights(&self) -> Arc { Arc::new(TestWeights) } From 3b2352baedd9b909576570bd3681035c5ff6dda2 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 09:09:05 -0400 Subject: [PATCH 009/186] Fix test --- substrate/tendermint/tests/ext.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index fe7732470..fb1949da9 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -26,14 +26,14 @@ impl Weights for TestWeights { type ValidatorId = TestValidatorId; fn total_weight(&self) -> u64 { - 5 + 4 } fn weight(&self, id: TestValidatorId) -> u64 { - [1, 1, 1, 1, 1][usize::try_from(id).unwrap()] + [1; 4][usize::try_from(id).unwrap()] } fn proposer(&self, number: BlockNumber, round: Round) -> TestValidatorId { - TestValidatorId::try_from((number.0 + u32::from(round.0)) % 5).unwrap() + TestValidatorId::try_from((number.0 + u32::from(round.0)) % 4).unwrap() } } From c53c15fd959975384418241e363320b5e3a7c4ab Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 09:09:14 -0400 Subject: [PATCH 010/186] Finish timeouts --- substrate/tendermint/src/lib.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 9f221bc9d..f5d62e8e1 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -100,8 +100,10 @@ impl TendermintMachine { #[async_recursion::async_recursion] async fn broadcast(&mut self, data: Data) -> Option { + let step = data.step(); let msg = Message { sender: self.proposer, number: self.number, round: self.round, data }; let res = self.message(msg.clone()).await.unwrap(); + self.step = step; // TODO: Before or after the above handling call? self.network.write().await.broadcast(msg).await; res } @@ -122,13 +124,15 @@ impl TendermintMachine { // 11-13 async fn round(&mut self, round: Round) { + // Clear timeouts + self.timeouts = HashMap::new(); + // Correct the start time for _ in self.round.0 .. round.0 { self.start_time = self.timeout(Step::Precommit); } self.round = round; - self.step = Step::Propose; self.round_propose().await; } @@ -143,8 +147,6 @@ impl TendermintMachine { self.locked = None; self.valid = None; - self.timeouts = HashMap::new(); - self.round(Round(0)).await; } @@ -179,7 +181,6 @@ impl TendermintMachine { timeouts: HashMap::new(), }; - dbg!("Proposing"); machine.round_propose().await; loop { @@ -191,18 +192,18 @@ impl TendermintMachine { }; // Propose timeout - if t1 { - todo!() + if t1 && (machine.step == Step::Propose) { + debug_assert!(machine.broadcast(Data::Prevote(None)).await.is_none()); } // Prevote timeout - if t2 { - todo!() + if t2 && (machine.step == Step::Prevote) { + debug_assert!(machine.broadcast(Data::Precommit(None)).await.is_none()); } // Precommit timeout if t3 { - todo!() + machine.round(Round(machine.round.0 + 1)).await; } // If there's a message, handle it @@ -288,7 +289,7 @@ impl TendermintMachine { } else if msg.round.0 > self.round.0 { // 55-56 if self.log.round_participation(self.round) > self.weights.fault_thresold() { - self.round(msg.round); + self.round(msg.round).await; } else { return Ok(None); } @@ -316,7 +317,6 @@ impl TendermintMachine { }))) .await .is_none()); - self.step = Step::Prevote; } else { Err(TendermintError::Malicious(msg.sender))?; } @@ -333,7 +333,6 @@ impl TendermintMachine { self.locked.as_ref().map(|locked| locked.1.id()) == Some(block.id())))) .await .is_none()); - self.step = Step::Prevote; } } } @@ -350,10 +349,10 @@ impl TendermintMachine { // 44-46 if weight > self.weights.threshold() { debug_assert!(self.broadcast(Data::Precommit(None)).await.is_none()); - self.step = Step::Precommit; } } + // 36-43 if (self.valid.is_none()) && ((self.step == Step::Prevote) || (self.step == Step::Precommit)) { if let Some(proposal) = proposal { debug_assert!(matches!(proposal, Data::Proposal(..))); @@ -362,7 +361,6 @@ impl TendermintMachine { self.valid = Some((self.round, block.clone())); if self.step == Step::Prevote { self.locked = self.valid.clone(); - self.step = Step::Precommit; return Ok(self.broadcast(Data::Precommit(Some(block.id()))).await); } } From a0bc9dc3e55dcc232b555a40446203d46ca4e6d1 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 09:16:44 -0400 Subject: [PATCH 011/186] Misc cleanup --- substrate/tendermint/src/lib.rs | 9 +++++++-- substrate/tendermint/src/message_log.rs | 18 ------------------ substrate/tendermint/tests/ext.rs | 3 +-- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index f5d62e8e1..f0abb0dd9 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -124,15 +124,18 @@ impl TendermintMachine { // 11-13 async fn round(&mut self, round: Round) { - // Clear timeouts - self.timeouts = HashMap::new(); + dbg!(round); // Correct the start time for _ in self.round.0 .. round.0 { self.start_time = self.timeout(Step::Precommit); } + // Clear timeouts + self.timeouts = HashMap::new(); + self.round = round; + self.step = Step::Propose; self.round_propose().await; } @@ -151,6 +154,7 @@ impl TendermintMachine { } // 10 + #[allow(clippy::new_ret_no_self)] pub fn new( network: N, proposer: N::ValidatorId, @@ -284,6 +288,7 @@ impl TendermintMachine { } // Else, check if we need to jump ahead + #[allow(clippy::comparison_chain)] if msg.round.0 < self.round.0 { return Ok(None); } else if msg.round.0 > self.round.0 { diff --git a/substrate/tendermint/src/message_log.rs b/substrate/tendermint/src/message_log.rs index 45505db4e..f367cb91a 100644 --- a/substrate/tendermint/src/message_log.rs +++ b/substrate/tendermint/src/message_log.rs @@ -72,24 +72,6 @@ impl MessageLog { weight } - // Get the participation in a given round for a given step. - pub(crate) fn participation(&self, round: Round, step: Step) -> u64 { - let (participating, _) = self.message_instances( - round, - match step { - Step::Propose => panic!("Checking for participation on Propose"), - Step::Prevote => Data::Prevote(None), - Step::Precommit => Data::Precommit(None), - }, - ); - participating - } - - // Check if there's been a BFT level of participation - pub(crate) fn has_participation(&self, round: Round, step: Step) -> bool { - self.participation(round, step) >= self.weights.threshold() - } - // Check if consensus has been reached on a specific piece of data pub(crate) fn has_consensus(&self, round: Round, data: Data) -> bool { let (_, weight) = self.message_instances(round, data); diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index fb1949da9..ce15507f6 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use tokio::sync::{RwLock, mpsc}; +use tokio::sync::RwLock; use tendermint_machine::{ext::*, Message, TendermintMachine, TendermintHandle}; @@ -87,7 +87,6 @@ impl TestNetwork { )); } } - dbg!("Created all machines"); arc } } From 85962c00a9b95e77a0d0a659321e2cc6b0e59a5a Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 09:42:33 -0400 Subject: [PATCH 012/186] Define a signature scheme trait --- substrate/tendermint/src/ext.rs | 36 ++++++++++++++------- substrate/tendermint/tests/ext.rs | 54 +++++++++++++++++++++++-------- 2 files changed, 66 insertions(+), 24 deletions(-) diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 642553c98..7e0f9ec3f 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -12,18 +12,16 @@ pub struct BlockNumber(pub u32); #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct Round(pub u16); -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum BlockError { - // Invalid behavior entirely - Fatal, - // Potentially valid behavior dependent on unsynchronized state - Temporal, -} - -pub trait Block: Send + Sync + Clone + PartialEq + Debug { - type Id: Send + Sync + Copy + Clone + PartialEq + Debug; +pub trait SignatureScheme { + type ValidatorId: ValidatorId; + type Signature: Clone + Copy + PartialEq; + type AggregateSignature: Clone + PartialEq; - fn id(&self) -> Self::Id; + fn sign(&self, msg: &[u8]) -> Self::Signature; + #[must_use] + fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: Self::Signature) -> bool; + // Intended to be a BLS signature, a Schnorr signature half-aggregation, or a Vec. + fn aggregate(signatures: &[Self::Signature]) -> Self::AggregateSignature; } pub trait Weights: Send + Sync { @@ -42,15 +40,31 @@ pub trait Weights: Send + Sync { fn proposer(&self, number: BlockNumber, round: Round) -> Self::ValidatorId; } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum BlockError { + // Invalid behavior entirely + Fatal, + // Potentially valid behavior dependent on unsynchronized state + Temporal, +} + +pub trait Block: Send + Sync + Clone + PartialEq + Debug { + type Id: Send + Sync + Copy + Clone + PartialEq + Debug; + + fn id(&self) -> Self::Id; +} + #[async_trait::async_trait] pub trait Network: Send + Sync { type ValidatorId: ValidatorId; + type SignatureScheme: SignatureScheme; type Weights: Weights; type Block: Block; // Block time in seconds const BLOCK_TIME: u32; + fn signature_scheme(&self) -> Arc; fn weights(&self) -> Arc; async fn broadcast(&mut self, msg: Message); diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index ce15507f6..5846b8fce 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -7,17 +7,25 @@ use tendermint_machine::{ext::*, Message, TendermintMachine, TendermintHandle}; type TestValidatorId = u16; type TestBlockId = u32; -#[derive(Clone, PartialEq, Debug)] -struct TestBlock { - id: TestBlockId, - valid: Result<(), BlockError>, -} +struct TestSignatureScheme(u16); +impl SignatureScheme for TestSignatureScheme { + type ValidatorId = TestValidatorId; + type Signature = [u8; 32]; + type AggregateSignature = Vec<[u8; 32]>; + + fn sign(&self, msg: &[u8]) -> [u8; 32] { + let mut sig = [0; 32]; + sig[.. 2].copy_from_slice(&self.0.to_le_bytes()); + sig[2 .. (2 + 30.min(msg.len()))].copy_from_slice(msg); + sig + } -impl Block for TestBlock { - type Id = TestBlockId; + fn verify(&self, validator: u16, msg: &[u8], sig: [u8; 32]) -> bool { + (sig[.. 2] == validator.to_le_bytes()) && (&sig[2 ..] == &[msg, &[0; 30]].concat()[.. 30]) + } - fn id(&self) -> TestBlockId { - self.id + fn aggregate(sigs: &[[u8; 32]]) -> Vec<[u8; 32]> { + sigs.to_vec() } } @@ -37,22 +45,41 @@ impl Weights for TestWeights { } } -struct TestNetwork(Arc>>>); +#[derive(Clone, PartialEq, Debug)] +struct TestBlock { + id: TestBlockId, + valid: Result<(), BlockError>, +} + +impl Block for TestBlock { + type Id = TestBlockId; + + fn id(&self) -> TestBlockId { + self.id + } +} + +struct TestNetwork(u16, Arc>>>); #[async_trait::async_trait] impl Network for TestNetwork { type ValidatorId = TestValidatorId; + type SignatureScheme = TestSignatureScheme; type Weights = TestWeights; type Block = TestBlock; const BLOCK_TIME: u32 = 1; + fn signature_scheme(&self) -> Arc { + Arc::new(TestSignatureScheme(self.0)) + } + fn weights(&self) -> Arc { Arc::new(TestWeights) } async fn broadcast(&mut self, msg: Message) { - for handle in self.0.write().await.iter_mut() { + for handle in self.1.write().await.iter_mut() { handle.messages.send(msg.clone()).await.unwrap(); } } @@ -79,9 +106,10 @@ impl TestNetwork { { let mut write = arc.write().await; for i in 0 .. validators { + let i = u16::try_from(i).unwrap(); write.push(TendermintMachine::new( - TestNetwork(arc.clone()), - u16::try_from(i).unwrap(), + TestNetwork(i, arc.clone()), + i, BlockNumber(1), TestBlock { id: 1, valid: Ok(()) }, )); From 987aa5189a7761011f29afa2064431f0e57da495 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 10:06:27 -0400 Subject: [PATCH 013/186] Implement serialization via parity's scale codec Ideally, this would be generic. Unfortunately, the generic API serde doesn't natively support borsh, nor SCALE, and while there is a serde SCALE crate, it's old. While it may be complete, it's not worth working with. While we could still grab bincode, and a variety of other formats, it wasn't worth it to go custom and for Serai, we'll be using SCALE almost everywhere anyways. --- Cargo.lock | 1 + substrate/tendermint/Cargo.toml | 2 ++ substrate/tendermint/src/ext.rs | 22 +++++++++++++++------- substrate/tendermint/src/lib.rs | 10 ++++++---- substrate/tendermint/tests/ext.rs | 4 +++- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac7fd1883..08e23cb53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8870,6 +8870,7 @@ version = "0.1.0" dependencies = [ "async-recursion", "async-trait", + "parity-scale-codec", "tokio", ] diff --git a/substrate/tendermint/Cargo.toml b/substrate/tendermint/Cargo.toml index d626357aa..6b59433af 100644 --- a/substrate/tendermint/Cargo.toml +++ b/substrate/tendermint/Cargo.toml @@ -8,6 +8,8 @@ authors = ["Luke Parker "] edition = "2021" [dependencies] +parity-scale-codec = { version = "3.2", features = ["derive"] } + async-recursion = "1.0" async-trait = "0.1" tokio = { version = "1", features = ["macros", "rt", "sync"] } diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 7e0f9ec3f..407baf616 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -1,15 +1,23 @@ use core::{hash::Hash, fmt::Debug}; use std::sync::Arc; +use parity_scale_codec::{Encode, Decode}; + use crate::Message; -pub trait ValidatorId: Send + Sync + Clone + Copy + PartialEq + Eq + Hash + Debug {} -impl ValidatorId for V {} +pub trait ValidatorId: + Send + Sync + Clone + Copy + PartialEq + Eq + Hash + Debug + Encode + Decode +{ +} +impl ValidatorId + for V +{ +} // Type aliases which are distinct according to the type system -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)] pub struct BlockNumber(pub u32); -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)] pub struct Round(pub u16); pub trait SignatureScheme { @@ -40,7 +48,7 @@ pub trait Weights: Send + Sync { fn proposer(&self, number: BlockNumber, round: Round) -> Self::ValidatorId; } -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode)] pub enum BlockError { // Invalid behavior entirely Fatal, @@ -48,8 +56,8 @@ pub enum BlockError { Temporal, } -pub trait Block: Send + Sync + Clone + PartialEq + Debug { - type Id: Send + Sync + Copy + Clone + PartialEq + Debug; +pub trait Block: Send + Sync + Clone + PartialEq + Debug + Encode + Decode { + type Id: Send + Sync + Copy + Clone + PartialEq + Debug + Encode + Decode; fn id(&self) -> Self::Id; } diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index f0abb0dd9..7b7666eca 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -4,6 +4,8 @@ use std::{ collections::HashMap, }; +use parity_scale_codec::{Encode, Decode}; + use tokio::{ task::{JoinHandle, yield_now}, sync::{ @@ -18,14 +20,14 @@ use ext::*; mod message_log; use message_log::MessageLog; -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)] enum Step { Propose, Prevote, Precommit, } -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug, Encode, Decode)] enum Data { Proposal(Option, B), Prevote(Option), @@ -42,7 +44,7 @@ impl Data { } } -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug, Encode, Decode)] pub struct Message { sender: V, @@ -52,7 +54,7 @@ pub struct Message { data: Data, } -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode)] pub enum TendermintError { Malicious(V), Temporal, diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index 5846b8fce..7c15f57b7 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use parity_scale_codec::{Encode, Decode}; + use tokio::sync::RwLock; use tendermint_machine::{ext::*, Message, TendermintMachine, TendermintHandle}; @@ -45,7 +47,7 @@ impl Weights for TestWeights { } } -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug, Encode, Decode)] struct TestBlock { id: TestBlockId, valid: Result<(), BlockError>, From 329a48c19d0fe7212ee59a66d04380794d116e4b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 10:20:29 -0400 Subject: [PATCH 014/186] Implement usage of the signature scheme --- substrate/tendermint/src/ext.rs | 19 ++++++++----- substrate/tendermint/src/lib.rs | 44 +++++++++++++++++++++++-------- substrate/tendermint/tests/ext.rs | 4 +-- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 407baf616..969eaf006 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use parity_scale_codec::{Encode, Decode}; -use crate::Message; +use crate::SignedMessage; pub trait ValidatorId: Send + Sync + Clone + Copy + PartialEq + Eq + Hash + Debug + Encode + Decode @@ -20,10 +20,10 @@ pub struct BlockNumber(pub u32); #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)] pub struct Round(pub u16); -pub trait SignatureScheme { +pub trait SignatureScheme: Send + Sync { type ValidatorId: ValidatorId; - type Signature: Clone + Copy + PartialEq; - type AggregateSignature: Clone + PartialEq; + type Signature: Send + Sync + Clone + Copy + PartialEq + Debug + Encode + Decode; + type AggregateSignature: Send + Sync + Clone + PartialEq + Debug + Encode + Decode; fn sign(&self, msg: &[u8]) -> Self::Signature; #[must_use] @@ -65,7 +65,7 @@ pub trait Block: Send + Sync + Clone + PartialEq + Debug + Encode + Decode { #[async_trait::async_trait] pub trait Network: Send + Sync { type ValidatorId: ValidatorId; - type SignatureScheme: SignatureScheme; + type SignatureScheme: SignatureScheme; type Weights: Weights; type Block: Block; @@ -75,7 +75,14 @@ pub trait Network: Send + Sync { fn signature_scheme(&self) -> Arc; fn weights(&self) -> Arc; - async fn broadcast(&mut self, msg: Message); + async fn broadcast( + &mut self, + msg: SignedMessage< + Self::ValidatorId, + Self::Block, + ::Signature, + >, + ); // TODO: Should this take a verifiable reason? async fn slash(&mut self, validator: Self::ValidatorId); diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 7b7666eca..f413a6f65 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -1,3 +1,5 @@ +use core::fmt::Debug; + use std::{ sync::Arc, time::{Instant, Duration}, @@ -54,6 +56,12 @@ pub struct Message { data: Data, } +#[derive(Clone, PartialEq, Debug, Encode, Decode)] +pub struct SignedMessage { + msg: Message, + sig: S, +} + #[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode)] pub enum TendermintError { Malicious(V), @@ -62,6 +70,7 @@ pub enum TendermintError { pub struct TendermintMachine { network: Arc>, + signer: Arc, weights: Arc, proposer: N::ValidatorId, @@ -81,7 +90,9 @@ pub struct TendermintMachine { pub struct TendermintHandle { // Messages received - pub messages: mpsc::Sender>, + pub messages: mpsc::Sender< + SignedMessage::Signature>, + >, // Async task executing the machine pub handle: JoinHandle<()>, } @@ -106,7 +117,9 @@ impl TendermintMachine { let msg = Message { sender: self.proposer, number: self.number, round: self.round, data }; let res = self.message(msg.clone()).await.unwrap(); self.step = step; // TODO: Before or after the above handling call? - self.network.write().await.broadcast(msg).await; + + let sig = self.signer.sign(&msg.encode()); + self.network.write().await.broadcast(SignedMessage { msg, sig }).await; res } @@ -167,10 +180,12 @@ impl TendermintMachine { TendermintHandle { messages: msg_send, handle: tokio::spawn(async move { + let signer = network.signature_scheme(); let weights = network.weights(); let network = Arc::new(RwLock::new(network)); let mut machine = TendermintMachine { network, + signer, weights: weights.clone(), proposer, @@ -214,17 +229,24 @@ impl TendermintMachine { // If there's a message, handle it match msg_recv.try_recv() { - Ok(msg) => match machine.message(msg).await { - Ok(None) => (), - Ok(Some(block)) => { - let proposal = machine.network.write().await.add_block(block); - machine.reset(proposal).await + Ok(msg) => { + if !machine.signer.verify(msg.msg.sender, &msg.msg.encode(), msg.sig) { + yield_now().await; + continue; } - Err(TendermintError::Malicious(validator)) => { - machine.network.write().await.slash(validator).await + + match machine.message(msg.msg).await { + Ok(None) => (), + Ok(Some(block)) => { + let proposal = machine.network.write().await.add_block(block); + machine.reset(proposal).await + } + Err(TendermintError::Malicious(validator)) => { + machine.network.write().await.slash(validator).await + } + Err(TendermintError::Temporal) => (), } - Err(TendermintError::Temporal) => (), - }, + } Err(TryRecvError::Empty) => yield_now().await, Err(TryRecvError::Disconnected) => break, } diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index 7c15f57b7..c0b45c919 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -4,7 +4,7 @@ use parity_scale_codec::{Encode, Decode}; use tokio::sync::RwLock; -use tendermint_machine::{ext::*, Message, TendermintMachine, TendermintHandle}; +use tendermint_machine::{ext::*, SignedMessage, TendermintMachine, TendermintHandle}; type TestValidatorId = u16; type TestBlockId = u32; @@ -80,7 +80,7 @@ impl Network for TestNetwork { Arc::new(TestWeights) } - async fn broadcast(&mut self, msg: Message) { + async fn broadcast(&mut self, msg: SignedMessage) { for handle in self.1.write().await.iter_mut() { handle.messages.send(msg.clone()).await.unwrap(); } From 1c71e2523471de1cd42b592e9d567fbdaa4a14b1 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Oct 2022 10:25:36 -0400 Subject: [PATCH 015/186] Make the infinite test non-infinite --- substrate/tendermint/tests/ext.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index c0b45c919..3fe55e1e2 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -124,7 +124,7 @@ impl TestNetwork { #[tokio::test] async fn test() { TestNetwork::new(4).await; - loop { + for _ in 0 .. 100 { tokio::task::yield_now().await; } } From b993ff1cc8914db807213a580e1a61c56ea80684 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 17 Oct 2022 02:32:45 -0400 Subject: [PATCH 016/186] Provide a dedicated signature in Precommit of just the block hash Greatly simplifies verifying when syncing. --- substrate/tendermint/src/ext.rs | 15 ++++-- substrate/tendermint/src/lib.rs | 65 +++++++++++++++++++------ substrate/tendermint/src/message_log.rs | 37 ++++++++++---- substrate/tendermint/tests/ext.rs | 11 ++--- 4 files changed, 93 insertions(+), 35 deletions(-) diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 969eaf006..56d4a20d2 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -14,6 +14,9 @@ impl Signature for S {} + // Type aliases which are distinct according to the type system #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)] pub struct BlockNumber(pub u32); @@ -22,14 +25,12 @@ pub struct Round(pub u16); pub trait SignatureScheme: Send + Sync { type ValidatorId: ValidatorId; - type Signature: Send + Sync + Clone + Copy + PartialEq + Debug + Encode + Decode; - type AggregateSignature: Send + Sync + Clone + PartialEq + Debug + Encode + Decode; + type Signature: Signature; + type AggregateSignature: Signature; fn sign(&self, msg: &[u8]) -> Self::Signature; #[must_use] fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: Self::Signature) -> bool; - // Intended to be a BLS signature, a Schnorr signature half-aggregation, or a Vec. - fn aggregate(signatures: &[Self::Signature]) -> Self::AggregateSignature; } pub trait Weights: Send + Sync { @@ -89,5 +90,9 @@ pub trait Network: Send + Sync { fn validate(&mut self, block: &Self::Block) -> Result<(), BlockError>; // Add a block and return the proposal for the next one - fn add_block(&mut self, block: Self::Block) -> Self::Block; + fn add_block( + &mut self, + block: Self::Block, + sigs: Vec<(Self::ValidatorId, ::Signature)>, + ) -> Self::Block; } diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index f413a6f65..a1b4d3878 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -29,14 +29,26 @@ enum Step { Precommit, } -#[derive(Clone, PartialEq, Debug, Encode, Decode)] -enum Data { +#[derive(Clone, Debug, Encode, Decode)] +enum Data { Proposal(Option, B), Prevote(Option), - Precommit(Option), + Precommit(Option<(B::Id, S)>), +} + +impl PartialEq for Data { + fn eq(&self, other: &Data) -> bool { + match (self, other) { + (Data::Proposal(r, b), Data::Proposal(r2, b2)) => (r == r2) && (b == b2), + (Data::Prevote(i), Data::Prevote(i2)) => i == i2, + (Data::Precommit(None), Data::Precommit(None)) => true, + (Data::Precommit(Some((i, _))), Data::Precommit(Some((i2, _)))) => i == i2, + _ => false, + } + } } -impl Data { +impl Data { fn step(&self) -> Step { match self { Data::Proposal(..) => Step::Propose, @@ -47,18 +59,18 @@ impl Data { } #[derive(Clone, PartialEq, Debug, Encode, Decode)] -pub struct Message { +pub struct Message { sender: V, number: BlockNumber, round: Round, - data: Data, + data: Data, } #[derive(Clone, PartialEq, Debug, Encode, Decode)] -pub struct SignedMessage { - msg: Message, +pub struct SignedMessage { + msg: Message, sig: S, } @@ -112,7 +124,10 @@ impl TendermintMachine { } #[async_recursion::async_recursion] - async fn broadcast(&mut self, data: Data) -> Option { + async fn broadcast( + &mut self, + data: Data::Signature>, + ) -> Option { let step = data.step(); let msg = Message { sender: self.proposer, number: self.number, round: self.round, data }; let res = self.message(msg.clone()).await.unwrap(); @@ -238,7 +253,15 @@ impl TendermintMachine { match machine.message(msg.msg).await { Ok(None) => (), Ok(Some(block)) => { - let proposal = machine.network.write().await.add_block(block); + let sigs = machine + .log + .precommitted + .iter() + .filter_map(|(k, (id, sig))| { + Some((*k, sig.clone())).filter(|_| id == &block.id()) + }) + .collect(); + let proposal = machine.network.write().await.add_block(block, sigs); machine.reset(proposal).await } Err(TendermintError::Malicious(validator)) => { @@ -265,8 +288,9 @@ impl TendermintMachine { debug_assert!(matches!(proposal, Data::Proposal(..))); if let Data::Proposal(_, block) = proposal { // Check if it has gotten a sufficient amount of precommits - let (participants, weight) = - self.log.message_instances(round, Data::Precommit(Some(block.id()))); + let (participants, weight) = self + .log + .message_instances(round, Data::Precommit(Some((block.id(), self.signer.sign(&[]))))); let threshold = self.weights.threshold(); if weight >= threshold { @@ -286,8 +310,14 @@ impl TendermintMachine { async fn message( &mut self, - msg: Message, + msg: Message::Signature>, ) -> Result, TendermintError> { + if let Data::Precommit(Some((id, sig))) = &msg.data { + if !self.signer.verify(msg.sender, &id.encode(), sig.clone()) { + Err(TendermintError::Malicious(msg.sender))?; + } + } + if msg.number != self.number { Err(TendermintError::Temporal)?; } @@ -390,7 +420,14 @@ impl TendermintMachine { self.valid = Some((self.round, block.clone())); if self.step == Step::Prevote { self.locked = self.valid.clone(); - return Ok(self.broadcast(Data::Precommit(Some(block.id()))).await); + return Ok( + self + .broadcast(Data::Precommit(Some(( + block.id(), + self.signer.sign(&block.id().encode()), + )))) + .await, + ); } } } diff --git a/substrate/tendermint/src/message_log.rs b/substrate/tendermint/src/message_log.rs index f367cb91a..5b8174676 100644 --- a/substrate/tendermint/src/message_log.rs +++ b/substrate/tendermint/src/message_log.rs @@ -4,8 +4,17 @@ use crate::{ext::*, Round, Step, Data, Message, TendermintError}; pub(crate) struct MessageLog { weights: Arc, - precommitted: HashMap::Id>, - log: HashMap>>>, + pub(crate) precommitted: HashMap< + N::ValidatorId, + (::Id, ::Signature), + >, + log: HashMap< + Round, + HashMap< + N::ValidatorId, + HashMap::Signature>>, + >, + >, } impl MessageLog { @@ -16,7 +25,7 @@ impl MessageLog { // Returns true if it's a new message pub(crate) fn log( &mut self, - msg: Message, + msg: Message::Signature>, ) -> Result> { let round = self.log.entry(msg.round).or_insert_with(HashMap::new); let msgs = round.entry(msg.sender).or_insert_with(HashMap::new); @@ -31,13 +40,13 @@ impl MessageLog { } // If they already precommitted to a distinct hash, error - if let Data::Precommit(Some(hash)) = msg.data { - if let Some(prev) = self.precommitted.get(&msg.sender) { - if hash != *prev { + if let Data::Precommit(Some((hash, sig))) = &msg.data { + if let Some((prev, _)) = self.precommitted.get(&msg.sender) { + if hash != prev { Err(TendermintError::Malicious(msg.sender))?; } } - self.precommitted.insert(msg.sender, hash); + self.precommitted.insert(msg.sender, (*hash, sig.clone())); } msgs.insert(step, msg.data); @@ -46,7 +55,11 @@ impl MessageLog { // For a given round, return the participating weight for this step, and the weight agreeing with // the data. - pub(crate) fn message_instances(&self, round: Round, data: Data) -> (u64, u64) { + pub(crate) fn message_instances( + &self, + round: Round, + data: Data::Signature>, + ) -> (u64, u64) { let mut participating = 0; let mut weight = 0; for (participant, msgs) in &self.log[&round] { @@ -73,7 +86,11 @@ impl MessageLog { } // Check if consensus has been reached on a specific piece of data - pub(crate) fn has_consensus(&self, round: Round, data: Data) -> bool { + pub(crate) fn has_consensus( + &self, + round: Round, + data: Data::Signature>, + ) -> bool { let (_, weight) = self.message_instances(round, data); weight >= self.weights.threshold() } @@ -83,7 +100,7 @@ impl MessageLog { round: Round, sender: N::ValidatorId, step: Step, - ) -> Option<&Data> { + ) -> Option<&Data::Signature>> { self.log.get(&round).and_then(|round| round.get(&sender).and_then(|msgs| msgs.get(&step))) } } diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index 3fe55e1e2..31dfca0c7 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -18,17 +18,13 @@ impl SignatureScheme for TestSignatureScheme { fn sign(&self, msg: &[u8]) -> [u8; 32] { let mut sig = [0; 32]; sig[.. 2].copy_from_slice(&self.0.to_le_bytes()); - sig[2 .. (2 + 30.min(msg.len()))].copy_from_slice(msg); + sig[2 .. (2 + 30.min(msg.len()))].copy_from_slice(&msg[.. 30.min(msg.len())]); sig } fn verify(&self, validator: u16, msg: &[u8], sig: [u8; 32]) -> bool { (sig[.. 2] == validator.to_le_bytes()) && (&sig[2 ..] == &[msg, &[0; 30]].concat()[.. 30]) } - - fn aggregate(sigs: &[[u8; 32]]) -> Vec<[u8; 32]> { - sigs.to_vec() - } } struct TestWeights; @@ -95,9 +91,12 @@ impl Network for TestNetwork { block.valid } - fn add_block(&mut self, block: TestBlock) -> TestBlock { + fn add_block(&mut self, block: TestBlock, sigs: Vec<(u16, [u8; 32])>) -> TestBlock { dbg!("Adding ", &block); assert!(block.valid.is_ok()); + for sig in sigs { + assert!(TestSignatureScheme(u16::MAX).verify(sig.0, &block.id().encode(), sig.1)); + } TestBlock { id: block.id + 1, valid: Ok(()) } } } From 6155d12160a485fe628fb63faefdf817cf4501ba Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 17 Oct 2022 03:15:13 -0400 Subject: [PATCH 017/186] Dedicated Commit object Restores sig aggregation API. --- substrate/tendermint/src/ext.rs | 37 ++++++++++++++++++++++++++----- substrate/tendermint/src/lib.rs | 26 +++++++++++++--------- substrate/tendermint/tests/ext.rs | 20 +++++++++++++---- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 56d4a20d2..748150d79 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -31,6 +31,21 @@ pub trait SignatureScheme: Send + Sync { fn sign(&self, msg: &[u8]) -> Self::Signature; #[must_use] fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: Self::Signature) -> bool; + + fn aggregate(sigs: &[Self::Signature]) -> Self::AggregateSignature; + #[must_use] + fn verify_aggregate( + &self, + msg: &[u8], + signers: &[Self::ValidatorId], + sig: Self::AggregateSignature, + ) -> bool; +} + +#[derive(Clone, PartialEq, Debug, Encode, Decode)] +pub struct Commit { + pub validators: Vec, + pub signature: S::AggregateSignature, } pub trait Weights: Send + Sync { @@ -76,6 +91,21 @@ pub trait Network: Send + Sync { fn signature_scheme(&self) -> Arc; fn weights(&self) -> Arc; + #[must_use] + fn verify_commit( + &self, + id: ::Id, + commit: Commit, + ) -> bool { + if !self.signature_scheme().verify_aggregate(&id.encode(), &commit.validators, commit.signature) + { + return false; + } + + let weights = self.weights(); + commit.validators.iter().map(|v| weights.weight(*v)).sum::() >= weights.threshold() + } + async fn broadcast( &mut self, msg: SignedMessage< @@ -90,9 +120,6 @@ pub trait Network: Send + Sync { fn validate(&mut self, block: &Self::Block) -> Result<(), BlockError>; // Add a block and return the proposal for the next one - fn add_block( - &mut self, - block: Self::Block, - sigs: Vec<(Self::ValidatorId, ::Signature)>, - ) -> Self::Block; + fn add_block(&mut self, block: Self::Block, commit: Commit) + -> Self::Block; } diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index a1b4d3878..7b398bfcd 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -253,15 +253,19 @@ impl TendermintMachine { match machine.message(msg.msg).await { Ok(None) => (), Ok(Some(block)) => { - let sigs = machine - .log - .precommitted - .iter() - .filter_map(|(k, (id, sig))| { - Some((*k, sig.clone())).filter(|_| id == &block.id()) - }) - .collect(); - let proposal = machine.network.write().await.add_block(block, sigs); + let mut validators = vec![]; + let mut sigs = vec![]; + for (v, sig) in machine.log.precommitted.iter().filter_map(|(k, (id, sig))| { + Some((*k, sig.clone())).filter(|_| id == &block.id()) + }) { + validators.push(v); + sigs.push(sig); + } + + let proposal = machine.network.write().await.add_block( + block, + Commit { validators, signature: N::SignatureScheme::aggregate(&sigs) }, + ); machine.reset(proposal).await } Err(TendermintError::Malicious(validator)) => { @@ -400,13 +404,13 @@ impl TendermintMachine { if self.step == Step::Prevote { let (participation, weight) = self.log.message_instances(self.round, Data::Prevote(None)); // 34-35 - if participation > self.weights.threshold() { + if participation >= self.weights.threshold() { let timeout = self.timeout(Step::Prevote); self.timeouts.entry(Step::Prevote).or_insert(timeout); } // 44-46 - if weight > self.weights.threshold() { + if weight >= self.weights.threshold() { debug_assert!(self.broadcast(Data::Precommit(None)).await.is_none()); } } diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index 31dfca0c7..478e213eb 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -22,9 +22,23 @@ impl SignatureScheme for TestSignatureScheme { sig } + #[must_use] fn verify(&self, validator: u16, msg: &[u8], sig: [u8; 32]) -> bool { (sig[.. 2] == validator.to_le_bytes()) && (&sig[2 ..] == &[msg, &[0; 30]].concat()[.. 30]) } + + fn aggregate(sigs: &[[u8; 32]]) -> Vec<[u8; 32]> { + sigs.to_vec() + } + + #[must_use] + fn verify_aggregate(&self, msg: &[u8], signers: &[TestValidatorId], sigs: Vec<[u8; 32]>) -> bool { + assert_eq!(signers.len(), sigs.len()); + for sig in signers.iter().zip(sigs.iter()) { + assert!(self.verify(*sig.0, msg, *sig.1)); + } + true + } } struct TestWeights; @@ -91,12 +105,10 @@ impl Network for TestNetwork { block.valid } - fn add_block(&mut self, block: TestBlock, sigs: Vec<(u16, [u8; 32])>) -> TestBlock { + fn add_block(&mut self, block: TestBlock, commit: Commit) -> TestBlock { dbg!("Adding ", &block); assert!(block.valid.is_ok()); - for sig in sigs { - assert!(TestSignatureScheme(u16::MAX).verify(sig.0, &block.id().encode(), sig.1)); - } + assert!(self.verify_commit(block.id(), commit)); TestBlock { id: block.id + 1, valid: Ok(()) } } } From 0501ff259ea56918476aad4579e7570ada2fda65 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 17 Oct 2022 03:15:22 -0400 Subject: [PATCH 018/186] Tidy README --- substrate/tendermint/README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/substrate/tendermint/README.md b/substrate/tendermint/README.md index fbd2d83f9..8f0fd5e14 100644 --- a/substrate/tendermint/README.md +++ b/substrate/tendermint/README.md @@ -41,12 +41,3 @@ The included pseudocode segments can be minimally described as follows: 61-64 on timeout prevote 65-67 on timeout precommit ``` - -Remaining: - -``` -36-43 First proposal with prevotes -> precommit Some -57-60 on timeout propose -61-64 on timeout prevote -65-67 on timeout precommit -``` From f28d412f78e9da60d2ef9dba16f4f585263382dc Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 17 Oct 2022 08:07:23 -0400 Subject: [PATCH 019/186] Document tendermint --- substrate/tendermint/README.md | 35 +++++++++++++-- substrate/tendermint/src/ext.rs | 75 ++++++++++++++++++++++++++----- substrate/tendermint/src/lib.rs | 37 ++++++++------- substrate/tendermint/tests/ext.rs | 11 +++-- 4 files changed, 125 insertions(+), 33 deletions(-) diff --git a/substrate/tendermint/README.md b/substrate/tendermint/README.md index 8f0fd5e14..0135adbb0 100644 --- a/substrate/tendermint/README.md +++ b/substrate/tendermint/README.md @@ -3,10 +3,34 @@ An implementation of the Tendermint state machine in Rust. This is solely the state machine, intended to be mapped to any arbitrary system. -It supports an arbitrary hash function, signature protocol, and block -definition accordingly. It is not intended to work with the Cosmos SDK, solely -be an implementation of the -[academic protocol](https://arxiv.org/pdf/1807.04938.pdf). +It supports an arbitrary signature scheme, weighting, and block definition +accordingly. It is not intended to work with the Cosmos SDK, solely to be an +implementation of the [academic protocol](https://arxiv.org/pdf/1807.04938.pdf). + +### Caveats + +- Only SCALE serialization is supported currently. Ideally, everything from + SCALE to borsh to bincode would be supported. SCALE was chosen due to this + being under Serai, which uses Substrate, which uses SCALE. Accordingly, when + deciding which of the three (mutually incompatible) options to support... + +- tokio is explicitly used for the asynchronous task which runs the Tendermint + machine. Ideally, `futures-rs` would be used enabling any async runtime to be + used. + +- It is possible for `add_block` to be called on a block which failed (or never + went through in the first place) validation. This is a break from the paper + which is accepted here. This is for two reasons. + + 1) Serai needing this functionality. + 2) If a block is committed which is invalid, either there's a malicious + majority now defining consensus OR the local node is malicious by virtue of + being faulty. Considering how either represents a fatal circumstance, + except with regards to system like Serai which have their own logic for + pseudo-valid blocks, it is accepted as a possible behavior with the caveat + any consumers must be aware of it. No machine will vote nor precommit to a + block it considers invalid, so for a network with an honest majority, this + is a non-issue. ### Paper @@ -41,3 +65,6 @@ The included pseudocode segments can be minimally described as follows: 61-64 on timeout prevote 65-67 on timeout precommit ``` + +The corresponding Rust code implementing these tasks are marked with their +related line numbers. diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 748150d79..8bc19f8ce 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -5,6 +5,8 @@ use parity_scale_codec::{Encode, Decode}; use crate::SignedMessage; +/// An alias for a series of traits required for a type to be usable as a validator ID, +/// automatically implemented for all types satisfying those traits. pub trait ValidatorId: Send + Sync + Clone + Copy + PartialEq + Eq + Hash + Debug + Encode + Decode { @@ -14,48 +16,73 @@ impl Signature for S {} // Type aliases which are distinct according to the type system + +/// A struct containing a Block Number, wrapped to have a distinct type. #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)] -pub struct BlockNumber(pub u32); +pub struct BlockNumber(pub u64); +/// A struct containing a round number, wrapped to have a distinct type. #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)] -pub struct Round(pub u16); +pub struct Round(pub u32); +/// A signature scheme used by validators. pub trait SignatureScheme: Send + Sync { + // Type used to identify validators. type ValidatorId: ValidatorId; + /// Signature type. type Signature: Signature; + /// Type representing an aggregate signature. This would presumably be a BLS signature, + /// yet even with Schnorr signatures + /// [half-aggregation is possible](https://eprint.iacr.org/2021/350). + /// It could even be a threshold signature scheme, though that's currently unexpected. type AggregateSignature: Signature; + /// Sign a signature with the current validator's private key. fn sign(&self, msg: &[u8]) -> Self::Signature; + /// Verify a signature from the validator in question. #[must_use] fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: Self::Signature) -> bool; + /// Aggregate signatures. fn aggregate(sigs: &[Self::Signature]) -> Self::AggregateSignature; + /// Verify an aggregate signature for the list of signers. #[must_use] fn verify_aggregate( &self, msg: &[u8], signers: &[Self::ValidatorId], - sig: Self::AggregateSignature, + sig: &Self::AggregateSignature, ) -> bool; } +/// A commit for a specific block. The list of validators have weight exceeding the threshold for +/// a valid commit. #[derive(Clone, PartialEq, Debug, Encode, Decode)] pub struct Commit { + /// Validators participating in the signature. pub validators: Vec, + /// Aggregate signature. pub signature: S::AggregateSignature, } +/// Weights for the validators present. pub trait Weights: Send + Sync { type ValidatorId: ValidatorId; + /// Total weight of all validators. fn total_weight(&self) -> u64; + /// Weight for a specific validator. fn weight(&self, validator: Self::ValidatorId) -> u64; + /// Threshold needed for BFT consensus. fn threshold(&self) -> u64 { ((self.total_weight() * 2) / 3) + 1 } + /// Threshold preventing BFT consensus. fn fault_thresold(&self) -> u64 { (self.total_weight() - self.threshold()) + 1 } @@ -64,41 +91,59 @@ pub trait Weights: Send + Sync { fn proposer(&self, number: BlockNumber, round: Round) -> Self::ValidatorId; } +/// Simplified error enum representing a block's validity. #[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode)] pub enum BlockError { - // Invalid behavior entirely + /// Malformed block which is wholly invalid. Fatal, - // Potentially valid behavior dependent on unsynchronized state + /// Valid block by syntax, with semantics which may or may not be valid yet are locally + /// considered invalid. If a block fails to validate with this, a slash will not be triggered. Temporal, } +/// Trait representing a Block. pub trait Block: Send + Sync + Clone + PartialEq + Debug + Encode + Decode { + // Type used to identify blocks. Presumably a cryptographic hash of the block. type Id: Send + Sync + Copy + Clone + PartialEq + Debug + Encode + Decode; + /// Return the deterministic, unique ID for this block. fn id(&self) -> Self::Id; } +/// Trait representing the distributed system Tendermint is providing consensus over. #[async_trait::async_trait] pub trait Network: Send + Sync { + // Type used to identify validators. type ValidatorId: ValidatorId; + /// Signature scheme used by validators. type SignatureScheme: SignatureScheme; + /// Object representing the weights of validators. type Weights: Weights; + /// Type used for ordered blocks of information. type Block: Block; // Block time in seconds const BLOCK_TIME: u32; + /// Return the signature scheme in use. The instance is expected to have the validators' public + /// keys, along with an instance of the private key of the current validator. fn signature_scheme(&self) -> Arc; + /// Return a reference to the validators' weights. fn weights(&self) -> Arc; + /// Verify a commit for a given block. Intended for use when syncing or when not an active + /// validator. #[must_use] fn verify_commit( &self, id: ::Id, - commit: Commit, + commit: &Commit, ) -> bool { - if !self.signature_scheme().verify_aggregate(&id.encode(), &commit.validators, commit.signature) - { + if !self.signature_scheme().verify_aggregate( + &id.encode(), + &commit.validators, + &commit.signature, + ) { return false; } @@ -106,6 +151,10 @@ pub trait Network: Send + Sync { commit.validators.iter().map(|v| weights.weight(*v)).sum::() >= weights.threshold() } + /// Broadcast a message to the other validators. If authenticated channels have already been + /// established, this will double-authenticate. Switching to unauthenticated channels in a system + /// already providing authenticated channels is not recommended as this is a minor, temporal + /// inefficiency while downgrading channels may have wider implications. async fn broadcast( &mut self, msg: SignedMessage< @@ -115,11 +164,17 @@ pub trait Network: Send + Sync { >, ); - // TODO: Should this take a verifiable reason? + /// Trigger a slash for the validator in question who was definitively malicious. + /// The exact process of triggering a slash is undefined and left to the network as a whole. async fn slash(&mut self, validator: Self::ValidatorId); + /// Validate a block. fn validate(&mut self, block: &Self::Block) -> Result<(), BlockError>; - // Add a block and return the proposal for the next one + /// Add a block, returning the proposal for the next one. It's possible a block, which was never + /// validated or even failed validation, may be passed here if a supermajority of validators did + /// consider it valid and created a commit for it. This deviates from the paper which will have a + /// local node refuse to decide on a block it considers invalid. This library acknowledges the + /// network did decide on it, leaving handling of it to the network, and outside of this scope. fn add_block(&mut self, block: Self::Block, commit: Commit) -> Self::Block; } diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 7b398bfcd..75f9de305 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -16,6 +16,7 @@ use tokio::{ }, }; +/// Traits and types of the external network being integrated with to provide consensus over. pub mod ext; use ext::*; @@ -59,7 +60,7 @@ impl Data { } #[derive(Clone, PartialEq, Debug, Encode, Decode)] -pub struct Message { +struct Message { sender: V, number: BlockNumber, @@ -68,18 +69,20 @@ pub struct Message { data: Data, } +/// A signed Tendermint consensus message to be broadcast to the other validators. #[derive(Clone, PartialEq, Debug, Encode, Decode)] pub struct SignedMessage { msg: Message, sig: S, } -#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode)] -pub enum TendermintError { +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum TendermintError { Malicious(V), Temporal, } +/// A machine executing the Tendermint protocol. pub struct TendermintMachine { network: Arc>, signer: Arc, @@ -100,19 +103,21 @@ pub struct TendermintMachine { timeouts: HashMap, } +/// A handle to an asynchronous task, along with a channel to inform of it of messages received. pub struct TendermintHandle { - // Messages received + /// Channel to send messages received from the P2P layer. pub messages: mpsc::Sender< SignedMessage::Signature>, >, - // Async task executing the machine + /// Handle for the asynchronous task executing the machine. The task will automatically exit + /// when the channel is dropped. pub handle: JoinHandle<()>, } impl TendermintMachine { fn timeout(&self, step: Step) -> Instant { let mut round_time = Duration::from_secs(N::BLOCK_TIME.into()); - round_time *= (self.round.0 + 1).into(); + round_time *= self.round.0 + 1; let step_time = round_time / 3; let offset = match step { @@ -183,6 +188,8 @@ impl TendermintMachine { self.round(Round(0)).await; } + /// Create a new Tendermint machine, for the specified proposer, from the specified block, with + /// the specified block as the one to propose next, returning a handle for the machine. // 10 #[allow(clippy::new_ret_no_self)] pub fn new( @@ -262,10 +269,10 @@ impl TendermintMachine { sigs.push(sig); } - let proposal = machine.network.write().await.add_block( - block, - Commit { validators, signature: N::SignatureScheme::aggregate(&sigs) }, - ); + let commit = + Commit { validators, signature: N::SignatureScheme::aggregate(&sigs) }; + debug_assert!(machine.network.read().await.verify_commit(block.id(), &commit)); + let proposal = machine.network.write().await.add_block(block, commit); machine.reset(proposal).await } Err(TendermintError::Malicious(validator)) => { @@ -385,12 +392,10 @@ impl TendermintMachine { } } else { // 22-27 - self - .network - .write() - .await - .validate(block) - .map_err(|_| TendermintError::Malicious(msg.sender))?; + self.network.write().await.validate(block).map_err(|e| match e { + BlockError::Temporal => TendermintError::Temporal, + BlockError::Fatal => TendermintError::Malicious(msg.sender), + })?; debug_assert!(self .broadcast(Data::Prevote(Some(block.id()).filter(|_| self.locked.is_none() || self.locked.as_ref().map(|locked| locked.1.id()) == Some(block.id())))) diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index 478e213eb..16bf2bcec 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -32,7 +32,12 @@ impl SignatureScheme for TestSignatureScheme { } #[must_use] - fn verify_aggregate(&self, msg: &[u8], signers: &[TestValidatorId], sigs: Vec<[u8; 32]>) -> bool { + fn verify_aggregate( + &self, + msg: &[u8], + signers: &[TestValidatorId], + sigs: &Vec<[u8; 32]>, + ) -> bool { assert_eq!(signers.len(), sigs.len()); for sig in signers.iter().zip(sigs.iter()) { assert!(self.verify(*sig.0, msg, *sig.1)); @@ -53,7 +58,7 @@ impl Weights for TestWeights { } fn proposer(&self, number: BlockNumber, round: Round) -> TestValidatorId { - TestValidatorId::try_from((number.0 + u32::from(round.0)) % 4).unwrap() + TestValidatorId::try_from((number.0 + u64::from(round.0)) % 4).unwrap() } } @@ -108,7 +113,7 @@ impl Network for TestNetwork { fn add_block(&mut self, block: TestBlock, commit: Commit) -> TestBlock { dbg!("Adding ", &block); assert!(block.valid.is_ok()); - assert!(self.verify_commit(block.id(), commit)); + assert!(self.verify_commit(block.id(), &commit)); TestBlock { id: block.id + 1, valid: Ok(()) } } } From 5724f52816a2351809d9271cbb0248a11931c44f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 17 Oct 2022 08:16:01 -0400 Subject: [PATCH 020/186] Sign the ID directly instead of its SCALE encoding For a hash, which is fixed-size, these should be the same yet this helps move past the dependency on SCALE. It also, for any type where the two values are different, smooths integration. --- substrate/tendermint/src/ext.rs | 2 +- substrate/tendermint/src/lib.rs | 5 +++-- substrate/tendermint/tests/ext.rs | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 8bc19f8ce..94b95b616 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -104,7 +104,7 @@ pub enum BlockError { /// Trait representing a Block. pub trait Block: Send + Sync + Clone + PartialEq + Debug + Encode + Decode { // Type used to identify blocks. Presumably a cryptographic hash of the block. - type Id: Send + Sync + Copy + Clone + PartialEq + Debug + Encode + Decode; + type Id: Send + Sync + Copy + Clone + PartialEq + AsRef<[u8]> + Debug + Encode + Decode; /// Return the deterministic, unique ID for this block. fn id(&self) -> Self::Id; diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 75f9de305..b35c56c94 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -301,6 +301,7 @@ impl TendermintMachine { // Check if it has gotten a sufficient amount of precommits let (participants, weight) = self .log + // Use a junk signature since message equality is irrelevant to the signature .message_instances(round, Data::Precommit(Some((block.id(), self.signer.sign(&[]))))); let threshold = self.weights.threshold(); @@ -324,7 +325,7 @@ impl TendermintMachine { msg: Message::Signature>, ) -> Result, TendermintError> { if let Data::Precommit(Some((id, sig))) = &msg.data { - if !self.signer.verify(msg.sender, &id.encode(), sig.clone()) { + if !self.signer.verify(msg.sender, id.as_ref(), sig.clone()) { Err(TendermintError::Malicious(msg.sender))?; } } @@ -433,7 +434,7 @@ impl TendermintMachine { self .broadcast(Data::Precommit(Some(( block.id(), - self.signer.sign(&block.id().encode()), + self.signer.sign(block.id().as_ref()), )))) .await, ); diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index 16bf2bcec..21e35187d 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -7,7 +7,7 @@ use tokio::sync::RwLock; use tendermint_machine::{ext::*, SignedMessage, TendermintMachine, TendermintHandle}; type TestValidatorId = u16; -type TestBlockId = u32; +type TestBlockId = [u8; 4]; struct TestSignatureScheme(u16); impl SignatureScheme for TestSignatureScheme { @@ -114,7 +114,7 @@ impl Network for TestNetwork { dbg!("Adding ", &block); assert!(block.valid.is_ok()); assert!(self.verify_commit(block.id(), &commit)); - TestBlock { id: block.id + 1, valid: Ok(()) } + TestBlock { id: (u32::from_le_bytes(block.id) + 1).to_le_bytes(), valid: Ok(()) } } } @@ -129,7 +129,7 @@ impl TestNetwork { TestNetwork(i, arc.clone()), i, BlockNumber(1), - TestBlock { id: 1, valid: Ok(()) }, + TestBlock { id: 1u32.to_le_bytes(), valid: Ok(()) }, )); } } From 8b6eb1172fc0fdcb00147b7869a5949ac69a6ab4 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 17 Oct 2022 10:37:30 -0400 Subject: [PATCH 021/186] Litany of bug fixes Also attempts to make the code more readable while updating/correcting documentation. --- substrate/tendermint/src/lib.rs | 209 ++++++++++++------------ substrate/tendermint/src/message_log.rs | 11 ++ 2 files changed, 118 insertions(+), 102 deletions(-) diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index b35c56c94..7c8e79dd3 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -97,7 +97,7 @@ pub struct TendermintMachine { round: Round, step: Step, - locked: Option<(Round, N::Block)>, + locked: Option<(Round, ::Id)>, valid: Option<(Round, N::Block)>, timeouts: HashMap, @@ -117,7 +117,7 @@ pub struct TendermintHandle { impl TendermintMachine { fn timeout(&self, step: Step) -> Instant { let mut round_time = Duration::from_secs(N::BLOCK_TIME.into()); - round_time *= self.round.0 + 1; + round_time *= self.round.0.wrapping_add(1); let step_time = round_time / 3; let offset = match step { @@ -136,6 +136,7 @@ impl TendermintMachine { let step = data.step(); let msg = Message { sender: self.proposer, number: self.number, round: self.round, data }; let res = self.message(msg.clone()).await.unwrap(); + // 27, 33, 41, 46, 60, 64 self.step = step; // TODO: Before or after the above handling call? let sig = self.signer.sign(&msg.encode()); @@ -146,18 +147,17 @@ impl TendermintMachine { // 14-21 async fn round_propose(&mut self) { if self.weights.proposer(self.number, self.round) == self.proposer { - let (round, block) = if let Some((round, block)) = &self.valid { - (Some(*round), block.clone()) - } else { - (None, self.personal_proposal.clone()) - }; + let (round, block) = self + .valid + .clone() + .map(|(r, b)| (Some(r), b)) + .unwrap_or((None, self.personal_proposal.clone())); debug_assert!(self.broadcast(Data::Proposal(round, block)).await.is_none()); } else { self.timeouts.insert(Step::Propose, self.timeout(Step::Propose)); } } - // 11-13 async fn round(&mut self, round: Round) { dbg!(round); @@ -166,6 +166,7 @@ impl TendermintMachine { self.start_time = self.timeout(Step::Precommit); } + // 11-13 // Clear timeouts self.timeouts = HashMap::new(); @@ -174,7 +175,7 @@ impl TendermintMachine { self.round_propose().await; } - // 1-9 + // 53-54 async fn reset(&mut self, proposal: N::Block) { self.number.0 += 1; self.start_time = Instant::now(); @@ -190,7 +191,6 @@ impl TendermintMachine { /// Create a new Tendermint machine, for the specified proposer, from the specified block, with /// the specified block as the one to propose next, returning a handle for the machine. - // 10 #[allow(clippy::new_ret_no_self)] pub fn new( network: N, @@ -205,6 +205,7 @@ impl TendermintMachine { let signer = network.signature_scheme(); let weights = network.weights(); let network = Arc::new(RwLock::new(network)); + // 01-10 let mut machine = TendermintMachine { network, signer, @@ -212,6 +213,7 @@ impl TendermintMachine { proposer, number, + // TODO: Use a non-local start time start_time: Instant::now(), personal_proposal: proposal, @@ -246,7 +248,7 @@ impl TendermintMachine { // Precommit timeout if t3 { - machine.round(Round(machine.round.0 + 1)).await; + machine.round(Round(machine.round.0.wrapping_add(1))).await; } // If there's a message, handle it @@ -272,6 +274,7 @@ impl TendermintMachine { let commit = Commit { validators, signature: N::SignatureScheme::aggregate(&sigs) }; debug_assert!(machine.network.read().await.verify_commit(block.id(), &commit)); + let proposal = machine.network.write().await.add_block(block, commit); machine.reset(proposal).await } @@ -289,43 +292,14 @@ impl TendermintMachine { } } - // 49-54 - fn check_committed(&mut self, round: Round) -> Option { - let proposer = self.weights.proposer(self.number, round); - - // Get the proposal - if let Some(proposal) = self.log.get(round, proposer, Step::Propose) { - // Destructure - debug_assert!(matches!(proposal, Data::Proposal(..))); - if let Data::Proposal(_, block) = proposal { - // Check if it has gotten a sufficient amount of precommits - let (participants, weight) = self - .log - // Use a junk signature since message equality is irrelevant to the signature - .message_instances(round, Data::Precommit(Some((block.id(), self.signer.sign(&[]))))); - - let threshold = self.weights.threshold(); - if weight >= threshold { - return Some(block.clone()); - } - - // 47-48 - if participants >= threshold { - let timeout = self.timeout(Step::Precommit); - self.timeouts.entry(Step::Precommit).or_insert(timeout); - } - } - } - - None - } - async fn message( &mut self, msg: Message::Signature>, ) -> Result, TendermintError> { + // Verify the signature if this is a precommit if let Data::Precommit(Some((id, sig))) = &msg.data { if !self.signer.verify(msg.sender, id.as_ref(), sig.clone()) { + // Since we verified this validator actually sent the message, they're malicious Err(TendermintError::Malicious(msg.sender))?; } } @@ -334,6 +308,7 @@ impl TendermintMachine { Err(TendermintError::Temporal)?; } + // Only let the proposer propose if matches!(msg.data, Data::Proposal(..)) && (msg.sender != self.weights.proposer(msg.number, msg.round)) { @@ -345,69 +320,114 @@ impl TendermintMachine { } // All functions, except for the finalizer and the jump, are locked to the current round + // Run the finalizer to see if it applies + // 49-52 if matches!(msg.data, Data::Proposal(..)) || matches!(msg.data, Data::Precommit(_)) { - let block = self.check_committed(msg.round); - if block.is_some() { - return Ok(block); + let proposer = self.weights.proposer(self.number, msg.round); + + // Get the proposal + if let Some(Data::Proposal(_, block)) = self.log.get(msg.round, proposer, Step::Propose) { + // Check if it has gotten a sufficient amount of precommits + // Use a junk signature since message equality disregards the signature + if self + .log + .has_consensus(msg.round, Data::Precommit(Some((block.id(), self.signer.sign(&[]))))) + { + return Ok(Some(block.clone())); + } } } // Else, check if we need to jump ahead #[allow(clippy::comparison_chain)] if msg.round.0 < self.round.0 { + // Prior round, disregard if not finalizing return Ok(None); } else if msg.round.0 > self.round.0 { // 55-56 + // Jump, enabling processing by the below code if self.log.round_participation(self.round) > self.weights.fault_thresold() { self.round(msg.round).await; } else { + // Future round which we aren't ready to jump to, so return for now return Ok(None); } } - let proposal = self - .log - .get(self.round, self.weights.proposer(self.number, self.round), Step::Propose) - .cloned(); - if self.step == Step::Propose { - if let Some(proposal) = &proposal { - debug_assert!(matches!(proposal, Data::Proposal(..))); - if let Data::Proposal(vr, block) = proposal { - if let Some(vr) = vr { - // 28-33 - if (vr.0 < self.round.0) && self.log.has_consensus(*vr, Data::Prevote(Some(block.id()))) - { - debug_assert!(self - .broadcast(Data::Prevote(Some(block.id()).filter(|_| { - self - .locked - .as_ref() - .map(|(round, value)| (round.0 <= vr.0) || (block.id() == value.id())) - .unwrap_or(true) - }))) - .await - .is_none()); - } else { - Err(TendermintError::Malicious(msg.sender))?; + let proposer = self.weights.proposer(self.number, self.round); + if let Some(Data::Proposal(vr, block)) = self.log.get(self.round, proposer, Step::Propose) { + // 22-33 + if self.step == Step::Propose { + // Delay error handling (triggering a slash) until after we vote. + let (valid, err) = match self.network.write().await.validate(block) { + Ok(_) => (true, Ok(())), + Err(BlockError::Temporal) => (false, Ok(())), + Err(BlockError::Fatal) => (false, Err(TendermintError::Malicious(proposer))), + }; + // Create a raw vote which only requires block validity as a basis for the actual vote. + let raw_vote = Some(block.id()).filter(|_| valid); + + // If locked is none, it has a round of -1 according to the protocol. That satisfies + // 23 and 29. If it's some, both are satisfied if they're for the same ID. If it's some + // with different IDs, the function on 22 rejects yet the function on 28 has one other + // condition + let locked = self.locked.as_ref().map(|(_, id)| id == &block.id()).unwrap_or(true); + let mut vote = raw_vote.filter(|_| locked); + + if let Some(vr) = vr { + // Malformed message + if vr.0 >= self.round.0 { + Err(TendermintError::Malicious(msg.sender))?; + } + + if self.log.has_consensus(*vr, Data::Prevote(Some(block.id()))) { + // Allow differing locked values if the proposal has a newer valid round + // This is the other condition described above + if let Some((locked_round, _)) = self.locked.as_ref() { + vote = vote.or_else(|| raw_vote.filter(|_| locked_round.0 <= vr.0)); } - } else { - // 22-27 - self.network.write().await.validate(block).map_err(|e| match e { - BlockError::Temporal => TendermintError::Temporal, - BlockError::Fatal => TendermintError::Malicious(msg.sender), - })?; - debug_assert!(self - .broadcast(Data::Prevote(Some(block.id()).filter(|_| self.locked.is_none() || - self.locked.as_ref().map(|locked| locked.1.id()) == Some(block.id())))) - .await - .is_none()); + + debug_assert!(self.broadcast(Data::Prevote(vote)).await.is_none()); + } + } else { + debug_assert!(self.broadcast(Data::Prevote(vote)).await.is_none()); + } + + err?; + } else if self.valid.as_ref().map(|(round, _)| round != &self.round).unwrap_or(true) { + // 36-43 + + // The run once condition is implemented above. Sinve valid will always be set, it not + // being set, or only being set historically, means this has yet to be run + + if self.log.has_consensus(self.round, Data::Prevote(Some(block.id()))) { + match self.network.write().await.validate(block) { + Ok(_) => (), + Err(BlockError::Temporal) => (), + Err(BlockError::Fatal) => Err(TendermintError::Malicious(proposer))?, + }; + + self.valid = Some((self.round, block.clone())); + if self.step == Step::Prevote { + self.locked = Some((self.round, block.id())); + return Ok( + self + .broadcast(Data::Precommit(Some(( + block.id(), + self.signer.sign(block.id().as_ref()), + )))) + .await, + ); } } } } - if self.step == Step::Prevote { + // The paper executes these checks when the step is prevote. Making sure this message warrants + // rerunning these checks is a sane optimization since message instances is a full iteration + // of the round map + if (self.step == Step::Prevote) && matches!(msg.data, Data::Prevote(_)) { let (participation, weight) = self.log.message_instances(self.round, Data::Prevote(None)); // 34-35 if participation >= self.weights.threshold() { @@ -421,27 +441,12 @@ impl TendermintMachine { } } - // 36-43 - if (self.valid.is_none()) && ((self.step == Step::Prevote) || (self.step == Step::Precommit)) { - if let Some(proposal) = proposal { - debug_assert!(matches!(proposal, Data::Proposal(..))); - if let Data::Proposal(_, block) = proposal { - if self.log.has_consensus(self.round, Data::Prevote(Some(block.id()))) { - self.valid = Some((self.round, block.clone())); - if self.step == Step::Prevote { - self.locked = self.valid.clone(); - return Ok( - self - .broadcast(Data::Precommit(Some(( - block.id(), - self.signer.sign(block.id().as_ref()), - )))) - .await, - ); - } - } - } - } + // 47-48 + if matches!(msg.data, Data::Precommit(_)) && + self.log.has_participation(self.round, Step::Precommit) + { + let timeout = self.timeout(Step::Precommit); + self.timeouts.entry(Step::Precommit).or_insert(timeout); } Ok(None) diff --git a/substrate/tendermint/src/message_log.rs b/substrate/tendermint/src/message_log.rs index 5b8174676..2536cad11 100644 --- a/substrate/tendermint/src/message_log.rs +++ b/substrate/tendermint/src/message_log.rs @@ -85,6 +85,17 @@ impl MessageLog { weight } + // Check if a supermajority of nodes have participated on a specific step + pub(crate) fn has_participation(&self, round: Round, step: Step) -> bool { + let mut participating = 0; + for (participant, msgs) in &self.log[&round] { + if msgs.get(&step).is_some() { + participating += self.weights.weight(*participant); + } + } + participating >= self.weights.threshold() + } + // Check if consensus has been reached on a specific piece of data pub(crate) fn has_consensus( &self, From 6b56510da9d4a321385542122b4198a0bc3f1729 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 17 Oct 2022 12:04:59 -0400 Subject: [PATCH 022/186] Remove async recursion Greatly increases safety as well by ensuring only one message is processed at once. --- Cargo.lock | 12 -- substrate/tendermint/Cargo.toml | 1 - substrate/tendermint/src/ext.rs | 1 + substrate/tendermint/src/lib.rs | 206 ++++++++++++++++++-------------- 4 files changed, 118 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08e23cb53..13ef2895c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,17 +256,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "async-recursion" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-std" version = "1.12.0" @@ -8868,7 +8857,6 @@ dependencies = [ name = "tendermint-machine" version = "0.1.0" dependencies = [ - "async-recursion", "async-trait", "parity-scale-codec", "tokio", diff --git a/substrate/tendermint/Cargo.toml b/substrate/tendermint/Cargo.toml index 6b59433af..1c7d65cb3 100644 --- a/substrate/tendermint/Cargo.toml +++ b/substrate/tendermint/Cargo.toml @@ -10,6 +10,5 @@ edition = "2021" [dependencies] parity-scale-codec = { version = "3.2", features = ["derive"] } -async-recursion = "1.0" async-trait = "0.1" tokio = { version = "1", features = ["macros", "rt", "sync"] } diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 94b95b616..9ede72167 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -166,6 +166,7 @@ pub trait Network: Send + Sync { /// Trigger a slash for the validator in question who was definitively malicious. /// The exact process of triggering a slash is undefined and left to the network as a whole. + // TODO: This is spammed right now. async fn slash(&mut self, validator: Self::ValidatorId); /// Validate a block. diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 7c8e79dd3..4e3d44f21 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -93,6 +93,11 @@ pub struct TendermintMachine { start_time: Instant, personal_proposal: N::Block, + queue: Vec<( + bool, + Message::Signature>, + )>, + log: MessageLog, round: Round, step: Step, @@ -128,37 +133,36 @@ impl TendermintMachine { self.start_time + offset } - #[async_recursion::async_recursion] - async fn broadcast( + fn broadcast( &mut self, data: Data::Signature>, - ) -> Option { + ) { let step = data.step(); - let msg = Message { sender: self.proposer, number: self.number, round: self.round, data }; - let res = self.message(msg.clone()).await.unwrap(); // 27, 33, 41, 46, 60, 64 - self.step = step; // TODO: Before or after the above handling call? - - let sig = self.signer.sign(&msg.encode()); - self.network.write().await.broadcast(SignedMessage { msg, sig }).await; - res + self.step = step; + self.queue.push(( + true, + Message { sender: self.proposer, number: self.number, round: self.round, data }, + )); } // 14-21 - async fn round_propose(&mut self) { + fn round_propose(&mut self) -> bool { if self.weights.proposer(self.number, self.round) == self.proposer { let (round, block) = self .valid .clone() .map(|(r, b)| (Some(r), b)) .unwrap_or((None, self.personal_proposal.clone())); - debug_assert!(self.broadcast(Data::Proposal(round, block)).await.is_none()); + self.broadcast(Data::Proposal(round, block)); + true } else { self.timeouts.insert(Step::Propose, self.timeout(Step::Propose)); + false } } - async fn round(&mut self, round: Round) { + fn round(&mut self, round: Round) -> bool { dbg!(round); // Correct the start time @@ -172,7 +176,7 @@ impl TendermintMachine { self.round = round; self.step = Step::Propose; - self.round_propose().await; + self.round_propose() } // 53-54 @@ -186,7 +190,7 @@ impl TendermintMachine { self.locked = None; self.valid = None; - self.round(Round(0)).await; + self.round(Round(0)); } /// Create a new Tendermint machine, for the specified proposer, from the specified block, with @@ -217,6 +221,8 @@ impl TendermintMachine { start_time: Instant::now(), personal_proposal: proposal, + queue: vec![], + log: MessageLog::new(weights), round: Round(0), step: Step::Propose, @@ -226,7 +232,7 @@ impl TendermintMachine { timeouts: HashMap::new(), }; - machine.round_propose().await; + machine.round_propose(); loop { // Check if any timeouts have been triggered @@ -238,55 +244,76 @@ impl TendermintMachine { // Propose timeout if t1 && (machine.step == Step::Propose) { - debug_assert!(machine.broadcast(Data::Prevote(None)).await.is_none()); + machine.broadcast(Data::Prevote(None)); } // Prevote timeout if t2 && (machine.step == Step::Prevote) { - debug_assert!(machine.broadcast(Data::Precommit(None)).await.is_none()); + machine.broadcast(Data::Precommit(None)); } // Precommit timeout if t3 { - machine.round(Round(machine.round.0.wrapping_add(1))).await; + machine.round(Round(machine.round.0.wrapping_add(1))); } - // If there's a message, handle it - match msg_recv.try_recv() { - Ok(msg) => { - if !machine.signer.verify(msg.msg.sender, &msg.msg.encode(), msg.sig) { - yield_now().await; - continue; + // Drain the channel of messages + let mut broken = false; + loop { + match msg_recv.try_recv() { + Ok(msg) => { + if !machine.signer.verify(msg.msg.sender, &msg.msg.encode(), msg.sig) { + continue; + } + machine.queue.push((false, msg.msg)); } + Err(TryRecvError::Empty) => break, + Err(TryRecvError::Disconnected) => broken = true, + } + } + if broken { + break; + } - match machine.message(msg.msg).await { - Ok(None) => (), - Ok(Some(block)) => { - let mut validators = vec![]; - let mut sigs = vec![]; - for (v, sig) in machine.log.precommitted.iter().filter_map(|(k, (id, sig))| { - Some((*k, sig.clone())).filter(|_| id == &block.id()) - }) { - validators.push(v); - sigs.push(sig); - } - - let commit = - Commit { validators, signature: N::SignatureScheme::aggregate(&sigs) }; - debug_assert!(machine.network.read().await.verify_commit(block.id(), &commit)); - - let proposal = machine.network.write().await.add_block(block, commit); - machine.reset(proposal).await - } - Err(TendermintError::Malicious(validator)) => { - machine.network.write().await.slash(validator).await + // Handle the queue + let mut queue = machine.queue.drain(..).collect::>(); + for (broadcast, msg) in queue.drain(..) { + let res = machine.message(msg.clone()).await; + if res.is_err() && broadcast { + panic!("honest node had invalid behavior"); + } + + match res { + Ok(None) => (), + Ok(Some(block)) => { + let mut validators = vec![]; + let mut sigs = vec![]; + for (v, sig) in machine.log.precommitted.iter().filter_map(|(k, (id, sig))| { + Some((*k, sig.clone())).filter(|_| id == &block.id()) + }) { + validators.push(v); + sigs.push(sig); } - Err(TendermintError::Temporal) => (), + + let commit = Commit { validators, signature: N::SignatureScheme::aggregate(&sigs) }; + debug_assert!(machine.network.read().await.verify_commit(block.id(), &commit)); + + let proposal = machine.network.write().await.add_block(block, commit); + machine.reset(proposal).await; + } + Err(TendermintError::Malicious(validator)) => { + machine.network.write().await.slash(validator).await; } + Err(TendermintError::Temporal) => (), + } + + if broadcast { + let sig = machine.signer.sign(&msg.encode()); + machine.network.write().await.broadcast(SignedMessage { msg, sig }).await; } - Err(TryRecvError::Empty) => yield_now().await, - Err(TryRecvError::Disconnected) => break, } + + yield_now().await; } }), } @@ -348,21 +375,50 @@ impl TendermintMachine { // 55-56 // Jump, enabling processing by the below code if self.log.round_participation(self.round) > self.weights.fault_thresold() { - self.round(msg.round).await; + // If we're the proposer, return to avoid a double process + if self.round(msg.round) { + return Ok(None); + } } else { // Future round which we aren't ready to jump to, so return for now return Ok(None); } } + // The paper executes these checks when the step is prevote. Making sure this message warrants + // rerunning these checks is a sane optimization since message instances is a full iteration + // of the round map + if (self.step == Step::Prevote) && matches!(msg.data, Data::Prevote(_)) { + let (participation, weight) = self.log.message_instances(self.round, Data::Prevote(None)); + // 34-35 + if participation >= self.weights.threshold() { + let timeout = self.timeout(Step::Prevote); + self.timeouts.entry(Step::Prevote).or_insert(timeout); + } + + // 44-46 + if weight >= self.weights.threshold() { + self.broadcast(Data::Precommit(None)); + return Ok(None); + } + } + + // 47-48 + if matches!(msg.data, Data::Precommit(_)) && + self.log.has_participation(self.round, Step::Precommit) + { + let timeout = self.timeout(Step::Precommit); + self.timeouts.entry(Step::Precommit).or_insert(timeout); + } + let proposer = self.weights.proposer(self.number, self.round); if let Some(Data::Proposal(vr, block)) = self.log.get(self.round, proposer, Step::Propose) { // 22-33 if self.step == Step::Propose { // Delay error handling (triggering a slash) until after we vote. let (valid, err) = match self.network.write().await.validate(block) { - Ok(_) => (true, Ok(())), - Err(BlockError::Temporal) => (false, Ok(())), + Ok(_) => (true, Ok(None)), + Err(BlockError::Temporal) => (false, Ok(None)), Err(BlockError::Fatal) => (false, Err(TendermintError::Malicious(proposer))), }; // Create a raw vote which only requires block validity as a basis for the actual vote. @@ -388,13 +444,13 @@ impl TendermintMachine { vote = vote.or_else(|| raw_vote.filter(|_| locked_round.0 <= vr.0)); } - debug_assert!(self.broadcast(Data::Prevote(vote)).await.is_none()); + self.broadcast(Data::Prevote(vote)); + return err; } } else { - debug_assert!(self.broadcast(Data::Prevote(vote)).await.is_none()); + self.broadcast(Data::Prevote(vote)); + return err; } - - err?; } else if self.valid.as_ref().map(|(round, _)| round != &self.round).unwrap_or(true) { // 36-43 @@ -411,44 +467,16 @@ impl TendermintMachine { self.valid = Some((self.round, block.clone())); if self.step == Step::Prevote { self.locked = Some((self.round, block.id())); - return Ok( - self - .broadcast(Data::Precommit(Some(( - block.id(), - self.signer.sign(block.id().as_ref()), - )))) - .await, - ); + self.broadcast(Data::Precommit(Some(( + block.id(), + self.signer.sign(block.id().as_ref()), + )))); + return Ok(None); } } } } - // The paper executes these checks when the step is prevote. Making sure this message warrants - // rerunning these checks is a sane optimization since message instances is a full iteration - // of the round map - if (self.step == Step::Prevote) && matches!(msg.data, Data::Prevote(_)) { - let (participation, weight) = self.log.message_instances(self.round, Data::Prevote(None)); - // 34-35 - if participation >= self.weights.threshold() { - let timeout = self.timeout(Step::Prevote); - self.timeouts.entry(Step::Prevote).or_insert(timeout); - } - - // 44-46 - if weight >= self.weights.threshold() { - debug_assert!(self.broadcast(Data::Precommit(None)).await.is_none()); - } - } - - // 47-48 - if matches!(msg.data, Data::Precommit(_)) && - self.log.has_participation(self.round, Step::Precommit) - { - let timeout = self.timeout(Step::Precommit); - self.timeouts.entry(Step::Precommit).or_insert(timeout); - } - Ok(None) } } From ff41e9f031d90807881857896a8fd51b20aacdde Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 20 Oct 2022 00:21:14 -0400 Subject: [PATCH 023/186] Correct timing issues 1) Commit didn't include the round, leaving the clock in question. 2) Machines started with a local time, instead of a proper start time. 3) Machines immediately started the next block instead of waiting for the block time. --- substrate/tendermint/Cargo.toml | 2 +- substrate/tendermint/src/ext.rs | 6 +++-- substrate/tendermint/src/lib.rs | 42 +++++++++++++++++++++++-------- substrate/tendermint/tests/ext.rs | 4 +-- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/substrate/tendermint/Cargo.toml b/substrate/tendermint/Cargo.toml index 1c7d65cb3..a68996724 100644 --- a/substrate/tendermint/Cargo.toml +++ b/substrate/tendermint/Cargo.toml @@ -11,4 +11,4 @@ edition = "2021" parity-scale-codec = { version = "3.2", features = ["derive"] } async-trait = "0.1" -tokio = { version = "1", features = ["macros", "rt", "sync"] } +tokio = { version = "1", features = ["macros", "sync", "time", "rt"] } diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 9ede72167..e2f047ebf 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use parity_scale_codec::{Encode, Decode}; -use crate::SignedMessage; +use crate::{SignedMessage, commit_msg}; /// An alias for a series of traits required for a type to be usable as a validator ID, /// automatically implemented for all types satisfying those traits. @@ -64,6 +64,8 @@ pub trait SignatureScheme: Send + Sync { /// a valid commit. #[derive(Clone, PartialEq, Debug, Encode, Decode)] pub struct Commit { + /// Round which created this commit. + pub round: Round, /// Validators participating in the signature. pub validators: Vec, /// Aggregate signature. @@ -140,7 +142,7 @@ pub trait Network: Send + Sync { commit: &Commit, ) -> bool { if !self.signature_scheme().verify_aggregate( - &id.encode(), + &commit_msg(commit.round, id.as_ref()), &commit.validators, &commit.signature, ) { diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 4e3d44f21..5472ce6d9 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -2,7 +2,7 @@ use core::fmt::Debug; use std::{ sync::Arc, - time::{Instant, Duration}, + time::{SystemTime, Instant, Duration}, collections::HashMap, }; @@ -14,6 +14,7 @@ use tokio::{ RwLock, mpsc::{self, error::TryRecvError}, }, + time::sleep, }; /// Traits and types of the external network being integrated with to provide consensus over. @@ -23,6 +24,10 @@ use ext::*; mod message_log; use message_log::MessageLog; +pub(crate) fn commit_msg(round: Round, id: &[u8]) -> Vec { + [&round.0.to_le_bytes(), id].concat().to_vec() +} + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)] enum Step { Propose, @@ -163,8 +168,6 @@ impl TendermintMachine { } fn round(&mut self, round: Round) -> bool { - dbg!(round); - // Correct the start time for _ in self.round.0 .. round.0 { self.start_time = self.timeout(Step::Precommit); @@ -181,10 +184,16 @@ impl TendermintMachine { // 53-54 async fn reset(&mut self, proposal: N::Block) { + // Wait for the next block interval + let round_end = self.timeout(Step::Precommit); + sleep(round_end - Instant::now()).await; + self.number.0 += 1; - self.start_time = Instant::now(); + self.start_time = round_end; self.personal_proposal = proposal; + self.queue = self.queue.drain(..).filter(|msg| msg.1.number == self.number).collect(); + self.log = MessageLog::new(self.network.read().await.weights()); self.locked = None; @@ -199,9 +208,17 @@ impl TendermintMachine { pub fn new( network: N, proposer: N::ValidatorId, - number: BlockNumber, + start: (BlockNumber, SystemTime), proposal: N::Block, ) -> TendermintHandle { + // Convert the start time to an instant + // This is imprecise yet should be precise enough + let start_time = { + let instant_now = Instant::now(); + let sys_now = SystemTime::now(); + instant_now - sys_now.duration_since(start.1).unwrap_or(Duration::ZERO) + }; + let (msg_send, mut msg_recv) = mpsc::channel(100); // Backlog to accept. Currently arbitrary TendermintHandle { messages: msg_send, @@ -216,9 +233,8 @@ impl TendermintMachine { weights: weights.clone(), proposer, - number, - // TODO: Use a non-local start time - start_time: Instant::now(), + number: start.0, + start_time, personal_proposal: proposal, queue: vec![], @@ -295,7 +311,11 @@ impl TendermintMachine { sigs.push(sig); } - let commit = Commit { validators, signature: N::SignatureScheme::aggregate(&sigs) }; + let commit = Commit { + round: msg.round, + validators, + signature: N::SignatureScheme::aggregate(&sigs), + }; debug_assert!(machine.network.read().await.verify_commit(block.id(), &commit)); let proposal = machine.network.write().await.add_block(block, commit); @@ -325,7 +345,7 @@ impl TendermintMachine { ) -> Result, TendermintError> { // Verify the signature if this is a precommit if let Data::Precommit(Some((id, sig))) = &msg.data { - if !self.signer.verify(msg.sender, id.as_ref(), sig.clone()) { + if !self.signer.verify(msg.sender, &commit_msg(msg.round, id.as_ref()), sig.clone()) { // Since we verified this validator actually sent the message, they're malicious Err(TendermintError::Malicious(msg.sender))?; } @@ -469,7 +489,7 @@ impl TendermintMachine { self.locked = Some((self.round, block.id())); self.broadcast(Data::Precommit(Some(( block.id(), - self.signer.sign(block.id().as_ref()), + self.signer.sign(&commit_msg(self.round, block.id().as_ref())), )))); return Ok(None); } diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index 21e35187d..51751d9e3 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{sync::Arc, time::SystemTime}; use parity_scale_codec::{Encode, Decode}; @@ -128,7 +128,7 @@ impl TestNetwork { write.push(TendermintMachine::new( TestNetwork(i, arc.clone()), i, - BlockNumber(1), + (BlockNumber(1), SystemTime::now()), TestBlock { id: 1u32.to_le_bytes(), valid: Ok(()) }, )); } From 3c6ea6e55d679dbcf9400732dbe284ee6d67e838 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 20 Oct 2022 03:40:16 -0400 Subject: [PATCH 024/186] Replace MultiSignature with sr25519::Signature --- substrate/runtime/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index bc6de59fd..1c9d06351 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -5,11 +5,12 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +pub use sp_core::sr25519::Signature; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, Verify}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, MultiSignature, Perbill, + ApplyExtrinsicResult, Perbill, }; use sp_std::prelude::*; #[cfg(feature = "std")] @@ -34,9 +35,6 @@ use pallet_transaction_payment::CurrencyAdapter; /// An index to a block. pub type BlockNumber = u32; -/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. -pub type Signature = MultiSignature; - /// Some way of identifying an account on the chain. We intentionally make it equivalent /// to the public key of our transaction signing scheme. pub type AccountId = <::Signer as IdentifyAccount>::AccountId; From 9db42f7d837b476f9944c339a43a0d1004397da0 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 20 Oct 2022 03:40:46 -0400 Subject: [PATCH 025/186] Minor SignatureScheme API changes --- substrate/tendermint/src/ext.rs | 6 +++--- substrate/tendermint/src/lib.rs | 4 ++-- substrate/tendermint/tests/ext.rs | 17 ++++++++++------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index e2f047ebf..383376f12 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -46,7 +46,7 @@ pub trait SignatureScheme: Send + Sync { fn sign(&self, msg: &[u8]) -> Self::Signature; /// Verify a signature from the validator in question. #[must_use] - fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: Self::Signature) -> bool; + fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: &Self::Signature) -> bool; /// Aggregate signatures. fn aggregate(sigs: &[Self::Signature]) -> Self::AggregateSignature; @@ -54,8 +54,8 @@ pub trait SignatureScheme: Send + Sync { #[must_use] fn verify_aggregate( &self, - msg: &[u8], signers: &[Self::ValidatorId], + msg: &[u8], sig: &Self::AggregateSignature, ) -> bool; } @@ -142,8 +142,8 @@ pub trait Network: Send + Sync { commit: &Commit, ) -> bool { if !self.signature_scheme().verify_aggregate( - &commit_msg(commit.round, id.as_ref()), &commit.validators, + &commit_msg(commit.round, id.as_ref()), &commit.signature, ) { return false; diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 5472ce6d9..5b65b3b99 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -278,7 +278,7 @@ impl TendermintMachine { loop { match msg_recv.try_recv() { Ok(msg) => { - if !machine.signer.verify(msg.msg.sender, &msg.msg.encode(), msg.sig) { + if !machine.signer.verify(msg.msg.sender, &msg.msg.encode(), &msg.sig) { continue; } machine.queue.push((false, msg.msg)); @@ -345,7 +345,7 @@ impl TendermintMachine { ) -> Result, TendermintError> { // Verify the signature if this is a precommit if let Data::Precommit(Some((id, sig))) = &msg.data { - if !self.signer.verify(msg.sender, &commit_msg(msg.round, id.as_ref()), sig.clone()) { + if !self.signer.verify(msg.sender, &commit_msg(msg.round, id.as_ref()), sig) { // Since we verified this validator actually sent the message, they're malicious Err(TendermintError::Malicious(msg.sender))?; } diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index 51751d9e3..6f489f7a0 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -1,8 +1,11 @@ -use std::{sync::Arc, time::SystemTime}; +use std::{ + sync::Arc, + time::{SystemTime, Duration}, +}; use parity_scale_codec::{Encode, Decode}; -use tokio::sync::RwLock; +use tokio::{sync::RwLock, time::sleep}; use tendermint_machine::{ext::*, SignedMessage, TendermintMachine, TendermintHandle}; @@ -23,7 +26,7 @@ impl SignatureScheme for TestSignatureScheme { } #[must_use] - fn verify(&self, validator: u16, msg: &[u8], sig: [u8; 32]) -> bool { + fn verify(&self, validator: u16, msg: &[u8], sig: &[u8; 32]) -> bool { (sig[.. 2] == validator.to_le_bytes()) && (&sig[2 ..] == &[msg, &[0; 30]].concat()[.. 30]) } @@ -34,13 +37,13 @@ impl SignatureScheme for TestSignatureScheme { #[must_use] fn verify_aggregate( &self, - msg: &[u8], signers: &[TestValidatorId], + msg: &[u8], sigs: &Vec<[u8; 32]>, ) -> bool { assert_eq!(signers.len(), sigs.len()); for sig in signers.iter().zip(sigs.iter()) { - assert!(self.verify(*sig.0, msg, *sig.1)); + assert!(self.verify(*sig.0, msg, sig.1)); } true } @@ -140,7 +143,7 @@ impl TestNetwork { #[tokio::test] async fn test() { TestNetwork::new(4).await; - for _ in 0 .. 100 { - tokio::task::yield_now().await; + for _ in 0 .. 10 { + sleep(Duration::from_secs(1)).await; } } From 975c9d7456ca7600aefb74c0b877f7e2b94d3aec Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 20 Oct 2022 03:46:09 -0400 Subject: [PATCH 026/186] Map TM SignatureScheme to Substrate's sr25519 --- substrate/consensus/src/signature_scheme.rs | 41 +++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 substrate/consensus/src/signature_scheme.rs diff --git a/substrate/consensus/src/signature_scheme.rs b/substrate/consensus/src/signature_scheme.rs new file mode 100644 index 000000000..bf3c3e726 --- /dev/null +++ b/substrate/consensus/src/signature_scheme.rs @@ -0,0 +1,41 @@ +use sp_application_crypto::{ + RuntimePublic as PublicTrait, Pair as PairTrait, + sr25519::{Public, Pair, Signature}, +}; + +use tendermint_machine::ext::SignatureScheme; + +pub(crate) struct TendermintSigner { + keys: Pair, + lookup: Vec, +} + +impl SignatureScheme for TendermintSigner { + type ValidatorId = u16; + type Signature = Signature; + type AggregateSignature = Vec; + + fn sign(&self, msg: &[u8]) -> Signature { + self.keys.sign(msg) + } + + fn verify(&self, validator: u16, msg: &[u8], sig: &Signature) -> bool { + self.lookup[usize::try_from(validator).unwrap()].verify(&msg, sig) + } + + fn aggregate(sigs: &[Signature]) -> Vec { + sigs.to_vec() + } + + fn verify_aggregate(&self, validators: &[u16], msg: &[u8], sigs: &Vec) -> bool { + if validators.len() != sigs.len() { + return false; + } + for (v, sig) in validators.iter().zip(sigs.iter()) { + if !self.verify(*v, msg, sig) { + return false; + } + } + true + } +} From eb59dd5a55b57bd27894db10d51861aa474c3215 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 20 Oct 2022 03:50:06 -0400 Subject: [PATCH 027/186] Initial work on an import queue --- Cargo.lock | 5 ++ substrate/consensus/Cargo.toml | 19 ++++- substrate/consensus/src/import.rs | 131 ++++++++++++++++++++++++++++++ substrate/consensus/src/lib.rs | 5 +- 4 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 substrate/consensus/src/import.rs diff --git a/Cargo.lock b/Cargo.lock index d596e7b1f..df6df0fba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7426,6 +7426,7 @@ checksum = "930c0acf610d3fdb5e2ab6213019aaa04e227ebe9547b0649ba599b16d788bd7" name = "serai-consensus" version = "0.1.0" dependencies = [ + "async-trait", "sc-basic-authorship", "sc-client-api", "sc-consensus", @@ -7436,13 +7437,17 @@ dependencies = [ "sc-transaction-pool", "serai-runtime", "sp-api", + "sp-application-crypto", + "sp-blockchain", "sp-consensus", "sp-consensus-pow", "sp-core", + "sp-inherents", "sp-runtime", "sp-timestamp", "sp-trie", "substrate-prometheus-endpoint", + "tendermint-machine", "tokio", ] diff --git a/substrate/consensus/Cargo.toml b/substrate/consensus/Cargo.toml index 892b648e2..4f58cde59 100644 --- a/substrate/consensus/Cargo.toml +++ b/substrate/consensus/Cargo.toml @@ -13,11 +13,24 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] +async-trait = "0.1" + sp-core = { git = "https://github.com/serai-dex/substrate" } +sp-application-crypto = { git = "https://github.com/serai-dex/substrate" } +sp-inherents = { git = "https://github.com/serai-dex/substrate" } +sp-runtime = { git = "https://github.com/serai-dex/substrate" } +sp-blockchain = { git = "https://github.com/serai-dex/substrate" } +sp-api = { git = "https://github.com/serai-dex/substrate" } + +sp-consensus = { git = "https://github.com/serai-dex/substrate" } +sc-consensus = { git = "https://github.com/serai-dex/substrate" } + +tendermint-machine = { path = "../tendermint" } + +# -- + sp-trie = { git = "https://github.com/serai-dex/substrate" } sp-timestamp = { git = "https://github.com/serai-dex/substrate" } -sc-consensus = { git = "https://github.com/serai-dex/substrate" } -sp-consensus = { git = "https://github.com/serai-dex/substrate" } sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" } sc-basic-authorship = { git = "https://github.com/serai-dex/substrate" } sc-consensus-pow = { git = "https://github.com/serai-dex/substrate" } @@ -26,12 +39,10 @@ sp-consensus-pow = { git = "https://github.com/serai-dex/substrate" } sc-network = { git = "https://github.com/serai-dex/substrate" } sc-service = { git = "https://github.com/serai-dex/substrate", features = ["wasmtime"] } sc-executor = { git = "https://github.com/serai-dex/substrate", features = ["wasmtime"] } -sp-runtime = { git = "https://github.com/serai-dex/substrate" } substrate-prometheus-endpoint = { git = "https://github.com/serai-dex/substrate" } sc-client-api = { git = "https://github.com/serai-dex/substrate" } -sp-api = { git = "https://github.com/serai-dex/substrate" } serai-runtime = { path = "../runtime" } diff --git a/substrate/consensus/src/import.rs b/substrate/consensus/src/import.rs new file mode 100644 index 000000000..0a02976c2 --- /dev/null +++ b/substrate/consensus/src/import.rs @@ -0,0 +1,131 @@ +use std::{marker::PhantomData, sync::Arc, collections::HashMap}; + +use sp_core::Decode; +use sp_inherents::CreateInherentDataProviders; +use sp_runtime::traits::{Header, Block}; +use sp_blockchain::HeaderBackend; +use sp_api::{ProvideRuntimeApi, TransactionFor}; + +use sp_consensus::{Error, CacheKeyId}; +#[rustfmt::skip] +use sc_consensus::{ + ForkChoiceStrategy, + BlockCheckParams, + BlockImportParams, + ImportResult, + BlockImport, +}; + +use tendermint_machine::ext::*; + +use crate::signature_scheme::TendermintSigner; + +struct TendermintBlockImport< + B: Block, + C: Send + Sync + HeaderBackend + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport>, + CIDP: CreateInherentDataProviders, +> { + _block: PhantomData, + client: Arc, + inner: I, + providers: Arc, +} + +impl< + B: Block, + C: Send + Sync + HeaderBackend + ProvideRuntimeApi, + I: Send + Sync + BlockImport>, + CIDP: CreateInherentDataProviders, + > TendermintBlockImport +{ + async fn check_inherents( + &self, + block: B, + providers: CIDP::InherentDataProviders, + ) -> Result<(), Error> { + todo!() + } +} + +// The Tendermint machine will call add_block for any block which is committed to, regardless of +// validity. To determine validity, it expects a validate function, which Substrate doesn't +// directly offer, and an add function. In order to comply with Serai's modified view of inherent +// transactions, validate MUST check inherents, yet add_block must not. +// +// In order to acquire a validate function, any block proposed by a legitimate proposer is +// imported. This performs full validation and makes the block available as a tip. While this would +// be incredibly unsafe thanks to the unchecked inherents, it's defined as a tip with less work, +// despite being a child of some parent. This means it won't be moved to nor operated on by the +// node. +// +// When Tendermint completes, the block is finalized, setting it as the tip regardless of work. + +const CONSENSUS_ID: [u8; 4] = *b"tend"; + +#[async_trait::async_trait] +impl< + B: Block, + C: Send + Sync + HeaderBackend + ProvideRuntimeApi, + I: Send + Sync + BlockImport>, + CIDP: CreateInherentDataProviders, + > BlockImport for TendermintBlockImport +where + I::Error: Into, +{ + type Error = Error; + type Transaction = TransactionFor; + + async fn check_block(&mut self, block: BlockCheckParams) -> Result { + self.inner.check_block(block).await.map_err(Into::into) + } + + async fn import_block( + &mut self, + mut block: BlockImportParams, + new_cache: HashMap>, + ) -> Result { + let parent_hash = *block.header.parent_hash(); + if self.client.info().best_hash != parent_hash { + Err(Error::Other("non-sequential import".into()))?; + } + + if let Some(body) = block.body.clone() { + if let Some(justifications) = block.justifications { + let mut iter = justifications.iter(); + let next = iter.next(); + if next.is_none() || iter.next().is_some() { + Err(Error::InvalidJustification)?; + } + let justification = next.unwrap(); + + let commit: Commit = + Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; + if justification.0 != CONSENSUS_ID { + Err(Error::InvalidJustification)?; + } + + // verify_commit + todo!() + } else { + self + .check_inherents( + B::new(block.header.clone(), body), + self.providers.create_inherent_data_providers(parent_hash, ()).await?, + ) + .await?; + } + } + + if !block.post_digests.is_empty() { + Err(Error::Other("post-digests included".into()))?; + } + if !block.auxiliary.is_empty() { + Err(Error::Other("auxiliary included".into()))?; + } + + block.fork_choice = Some(ForkChoiceStrategy::Custom(false)); + self.inner.import_block(block, new_cache).await.map_err(Into::into) + } +} + diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index c6c902d9c..e81c793fb 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -9,7 +9,10 @@ use sc_service::TaskManager; use serai_runtime::{self, opaque::Block, RuntimeApi}; mod algorithm; -mod tendermint; + +mod signature_scheme; +mod import; +//mod tendermint; pub struct ExecutorDispatch; impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { From 49a26e5841ceb933f7533fb907123813af0400fe Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 20 Oct 2022 04:27:53 -0400 Subject: [PATCH 028/186] Properly use check_block --- substrate/consensus/src/import.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/substrate/consensus/src/import.rs b/substrate/consensus/src/import.rs index 0a02976c2..62fd39bb8 100644 --- a/substrate/consensus/src/import.rs +++ b/substrate/consensus/src/import.rs @@ -76,7 +76,18 @@ where type Error = Error; type Transaction = TransactionFor; - async fn check_block(&mut self, block: BlockCheckParams) -> Result { + async fn check_block( + &mut self, + mut block: BlockCheckParams, + ) -> Result { + let info = self.client.info(); + if (info.best_hash != block.parent_hash) || ((info.best_number + 1u16.into()) != block.number) { + Err(Error::Other("non-sequential import".into()))?; + } + + block.allow_missing_state = false; + block.allow_missing_parent = false; + self.inner.check_block(block).await.map_err(Into::into) } @@ -85,11 +96,6 @@ where mut block: BlockImportParams, new_cache: HashMap>, ) -> Result { - let parent_hash = *block.header.parent_hash(); - if self.client.info().best_hash != parent_hash { - Err(Error::Other("non-sequential import".into()))?; - } - if let Some(body) = block.body.clone() { if let Some(justifications) = block.justifications { let mut iter = justifications.iter(); @@ -111,7 +117,7 @@ where self .check_inherents( B::new(block.header.clone(), body), - self.providers.create_inherent_data_providers(parent_hash, ()).await?, + self.providers.create_inherent_data_providers(*block.header.parent_hash(), ()).await?, ) .await?; } From 280683142a0bd57b3ca914faf246c58b235a7869 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 20 Oct 2022 04:28:26 -0400 Subject: [PATCH 029/186] Rename import to import_queue --- substrate/consensus/src/{import.rs => import_queue.rs} | 1 - 1 file changed, 1 deletion(-) rename substrate/consensus/src/{import.rs => import_queue.rs} (99%) diff --git a/substrate/consensus/src/import.rs b/substrate/consensus/src/import_queue.rs similarity index 99% rename from substrate/consensus/src/import.rs rename to substrate/consensus/src/import_queue.rs index 62fd39bb8..fc13bd801 100644 --- a/substrate/consensus/src/import.rs +++ b/substrate/consensus/src/import_queue.rs @@ -134,4 +134,3 @@ where self.inner.import_block(block, new_cache).await.map_err(Into::into) } } - From 5c46edbe98ec669adc454607076f819bf0a2e96e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 02:06:48 -0400 Subject: [PATCH 030/186] Implement tendermint_machine::Block for Substrate Blocks Unfortunately, this immediately makes Tendermint machine capable of deployment as crate since it uses a git reference. In the future, a Cargo.toml patch section for serai/substrate should be investigated. This is being done regardless as it's the quickest way forward and this is for Serai. --- Cargo.lock | 1 + substrate/consensus/Cargo.toml | 2 +- substrate/tendermint/Cargo.toml | 5 +++++ substrate/tendermint/src/ext.rs | 8 ++++++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index df6df0fba..a929d3376 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8774,6 +8774,7 @@ version = "0.1.0" dependencies = [ "async-trait", "parity-scale-codec", + "sp-runtime", "tokio", ] diff --git a/substrate/consensus/Cargo.toml b/substrate/consensus/Cargo.toml index 4f58cde59..df8e54d9f 100644 --- a/substrate/consensus/Cargo.toml +++ b/substrate/consensus/Cargo.toml @@ -25,7 +25,7 @@ sp-api = { git = "https://github.com/serai-dex/substrate" } sp-consensus = { git = "https://github.com/serai-dex/substrate" } sc-consensus = { git = "https://github.com/serai-dex/substrate" } -tendermint-machine = { path = "../tendermint" } +tendermint-machine = { path = "../tendermint", features = ["substrate"] } # -- diff --git a/substrate/tendermint/Cargo.toml b/substrate/tendermint/Cargo.toml index a68996724..b590e1469 100644 --- a/substrate/tendermint/Cargo.toml +++ b/substrate/tendermint/Cargo.toml @@ -12,3 +12,8 @@ parity-scale-codec = { version = "3.2", features = ["derive"] } async-trait = "0.1" tokio = { version = "1", features = ["macros", "sync", "time", "rt"] } + +sp-runtime = { git = "https://github.com/serai-dex/substrate", optional = true } + +[features] +substrate = ["sp-runtime"] diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 383376f12..aadb14e2b 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -112,6 +112,14 @@ pub trait Block: Send + Sync + Clone + PartialEq + Debug + Encode + Decode { fn id(&self) -> Self::Id; } +#[cfg(feature = "substrate")] +impl Block for B { + type Id = B::Hash; + fn id(&self) -> B::Hash { + self.hash() + } +} + /// Trait representing the distributed system Tendermint is providing consensus over. #[async_trait::async_trait] pub trait Network: Send + Sync { From 2cf1573c52ad0188fc94ea643295c2f8e0e2cc66 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 02:13:44 -0400 Subject: [PATCH 031/186] Dummy Weights --- substrate/consensus/src/weights.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 substrate/consensus/src/weights.rs diff --git a/substrate/consensus/src/weights.rs b/substrate/consensus/src/weights.rs new file mode 100644 index 000000000..d9d5c4058 --- /dev/null +++ b/substrate/consensus/src/weights.rs @@ -0,0 +1,21 @@ +// TODO + +use tendermint_machine::ext::{BlockNumber, Round, Weights}; + +const VALIDATORS: usize = 1; + +pub(crate) struct TendermintWeights; +impl Weights for TendermintWeights { + type ValidatorId = u16; + + fn total_weight(&self) -> u64 { + VALIDATORS.try_into().unwrap() + } + fn weight(&self, id: u16) -> u64 { + [1; VALIDATORS][usize::try_from(id).unwrap()] + } + + fn proposer(&self, number: BlockNumber, round: Round) -> u16 { + u16::try_from((number.0 + u64::from(round.0)) % u64::try_from(VALIDATORS).unwrap()).unwrap() + } +} From 56afb13ed57760aba6e72d1f2c9aa9cb4d3128a2 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 02:14:48 -0400 Subject: [PATCH 032/186] Move documentation to the top of the file --- substrate/consensus/src/import_queue.rs | 28 ++++++++++++------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index fc13bd801..5593c290a 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -1,4 +1,16 @@ use std::{marker::PhantomData, sync::Arc, collections::HashMap}; +// The Tendermint machine will call add_block for any block which is committed to, regardless of +// validity. To determine validity, it expects a validate function, which Substrate doesn't +// directly offer, and an add function. In order to comply with Serai's modified view of inherent +// transactions, validate MUST check inherents, yet add_block must not. +// +// In order to acquire a validate function, any block proposed by a legitimate proposer is +// imported. This performs full validation and makes the block available as a tip. While this would +// be incredibly unsafe thanks to the unchecked inherents, it's defined as a tip with less work, +// despite being a child of some parent. This means it won't be moved to nor operated on by the +// node. +// +// When Tendermint completes, the block is finalized, setting it as the tip regardless of work. use sp_core::Decode; use sp_inherents::CreateInherentDataProviders; @@ -19,6 +31,7 @@ use sc_consensus::{ use tendermint_machine::ext::*; use crate::signature_scheme::TendermintSigner; +const CONSENSUS_ID: [u8; 4] = *b"tend"; struct TendermintBlockImport< B: Block, @@ -48,21 +61,6 @@ impl< } } -// The Tendermint machine will call add_block for any block which is committed to, regardless of -// validity. To determine validity, it expects a validate function, which Substrate doesn't -// directly offer, and an add function. In order to comply with Serai's modified view of inherent -// transactions, validate MUST check inherents, yet add_block must not. -// -// In order to acquire a validate function, any block proposed by a legitimate proposer is -// imported. This performs full validation and makes the block available as a tip. While this would -// be incredibly unsafe thanks to the unchecked inherents, it's defined as a tip with less work, -// despite being a child of some parent. This means it won't be moved to nor operated on by the -// node. -// -// When Tendermint completes, the block is finalized, setting it as the tip regardless of work. - -const CONSENSUS_ID: [u8; 4] = *b"tend"; - #[async_trait::async_trait] impl< B: Block, From bdd0b42419a6629eab5ed20624a3216e5e0deea4 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 02:17:40 -0400 Subject: [PATCH 033/186] Move logic into TendermintImport itself Multiple traits exist to verify/handle blocks. I'm unsure exactly when each will be called in the pipeline, so the easiest solution is to have every step run every check. That would be extremely computationally expensive if we ran EVERY check, yet we rely on Substrate for execution (and according checks), which are limited to just the actual import function. Since we're calling this code from many places, it makes sense for it to be consolidated under TendermintImport. --- substrate/consensus/src/import_queue.rs | 196 +++++++++++++++++------- 1 file changed, 141 insertions(+), 55 deletions(-) diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index 5593c290a..d88d1f91c 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -1,4 +1,3 @@ -use std::{marker::PhantomData, sync::Arc, collections::HashMap}; // The Tendermint machine will call add_block for any block which is committed to, regardless of // validity. To determine validity, it expects a validate function, which Substrate doesn't // directly offer, and an add function. In order to comply with Serai's modified view of inherent @@ -12,45 +11,97 @@ use std::{marker::PhantomData, sync::Arc, collections::HashMap}; // // When Tendermint completes, the block is finalized, setting it as the tip regardless of work. +use std::{ + marker::PhantomData, + sync::{Arc, RwLock}, + collections::HashMap, +}; + +use async_trait::async_trait; + +use tokio::sync::RwLock as AsyncRwLock; + use sp_core::Decode; +use sp_application_crypto::sr25519::Signature; use sp_inherents::CreateInherentDataProviders; -use sp_runtime::traits::{Header, Block}; +use sp_runtime::{ + traits::{Header, Block}, + Justification, +}; use sp_blockchain::HeaderBackend; -use sp_api::{ProvideRuntimeApi, TransactionFor}; +use sp_api::{BlockId, TransactionFor, ProvideRuntimeApi}; use sp_consensus::{Error, CacheKeyId}; -#[rustfmt::skip] +#[rustfmt::skip] // rustfmt doesn't know how to handle this line use sc_consensus::{ ForkChoiceStrategy, BlockCheckParams, BlockImportParams, ImportResult, BlockImport, + JustificationImport, + BasicQueue, +}; + +use sc_client_api::{Backend, Finalizer}; + +use substrate_prometheus_endpoint::Registry; + +use tendermint_machine::{ + ext::{BlockError, Commit, Network}, + SignedMessage, }; -use tendermint_machine::ext::*; +use crate::{signature_scheme::TendermintSigner, weights::TendermintWeights}; -use crate::signature_scheme::TendermintSigner; const CONSENSUS_ID: [u8; 4] = *b"tend"; -struct TendermintBlockImport< +struct TendermintImport< B: Block, - C: Send + Sync + HeaderBackend + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport>, - CIDP: CreateInherentDataProviders, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, > { _block: PhantomData, + _backend: PhantomData, + + importing_block: Arc>>, + client: Arc, - inner: I, + inner: Arc>, providers: Arc, } impl< B: Block, - C: Send + Sync + HeaderBackend + ProvideRuntimeApi, - I: Send + Sync + BlockImport>, - CIDP: CreateInherentDataProviders, - > TendermintBlockImport + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + > Clone for TendermintImport +{ + fn clone(&self) -> Self { + TendermintImport { + _block: PhantomData, + _backend: PhantomData, + + importing_block: self.importing_block.clone(), + + client: self.client.clone(), + inner: self.inner.clone(), + providers: self.providers.clone(), + } + } +} + +impl< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + > TendermintImport { async fn check_inherents( &self, @@ -59,59 +110,92 @@ impl< ) -> Result<(), Error> { todo!() } -} - -#[async_trait::async_trait] -impl< - B: Block, - C: Send + Sync + HeaderBackend + ProvideRuntimeApi, - I: Send + Sync + BlockImport>, - CIDP: CreateInherentDataProviders, - > BlockImport for TendermintBlockImport -where - I::Error: Into, -{ - type Error = Error; - type Transaction = TransactionFor; - async fn check_block( - &mut self, - mut block: BlockCheckParams, - ) -> Result { + // Ensure this is part of a sequential import + fn verify_order( + &self, + parent: B::Hash, + number: ::Number, + ) -> Result<(), Error> { let info = self.client.info(); - if (info.best_hash != block.parent_hash) || ((info.best_number + 1u16.into()) != block.number) { + if (info.best_hash != parent) || ((info.best_number + 1u16.into()) != number) { Err(Error::Other("non-sequential import".into()))?; } + Ok(()) + } + + // Do not allow blocks from the traditional network to be broadcast + // Only allow blocks from Tendermint + // Tendermint's propose message could be rewritten as a seal OR Tendermint could produce blocks + // which this checks the proposer slot for, and then tells the Tendermint machine + // While those would be more seamless with Substrate, there's no actual benefit to doing so + fn verify_origin(&self, hash: B::Hash) -> Result<(), Error> { + if let Some(tm_hash) = *self.importing_block.read().unwrap() { + if hash == tm_hash { + return Ok(()); + } + } + Err(Error::Other("block created outside of tendermint".into())) + } - block.allow_missing_state = false; - block.allow_missing_parent = false; + // Errors if the justification isn't valid + fn verify_justification( + &self, + hash: B::Hash, + justification: &Justification, + ) -> Result<(), Error> { + if justification.0 != CONSENSUS_ID { + Err(Error::InvalidJustification)?; + } - self.inner.check_block(block).await.map_err(Into::into) + let commit: Commit = + Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; + if !self.verify_commit(hash, &commit) { + Err(Error::InvalidJustification)?; + } + Ok(()) } - async fn import_block( - &mut self, - mut block: BlockImportParams, - new_cache: HashMap>, - ) -> Result { - if let Some(body) = block.body.clone() { - if let Some(justifications) = block.justifications { + // Verifies the justifications aren't malformed, not that the block is justified + // Errors if justifications is neither empty nor a sinlge Tendermint justification + // If the block does have a justification, finalized will be set to true + fn verify_justifications(&self, block: &mut BlockImportParams) -> Result<(), Error> { + if !block.finalized { + if let Some(justifications) = &block.justifications { let mut iter = justifications.iter(); let next = iter.next(); if next.is_none() || iter.next().is_some() { Err(Error::InvalidJustification)?; } - let justification = next.unwrap(); + self.verify_justification(block.header.hash(), next.unwrap())?; - let commit: Commit = - Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; - if justification.0 != CONSENSUS_ID { - Err(Error::InvalidJustification)?; - } + block.finalized = true; // TODO: Is this setting valid? + } + } + Ok(()) + } + + async fn check(&self, block: &mut BlockImportParams) -> Result<(), Error> { + if block.finalized { + if block.fork_choice.is_none() { + // Since we alw1ays set the fork choice, this means something else marked the block as + // finalized, which shouldn't be possible. Ensuring nothing else is setting blocks as + // finalized ensures our security + panic!("block was finalized despite not setting the fork choice"); + } + return Ok(()); + } + + // Set the block as a worse choice + block.fork_choice = Some(ForkChoiceStrategy::Custom(false)); + + self.verify_order(*block.header.parent_hash(), *block.header.number())?; + self.verify_justifications(block)?; - // verify_commit - todo!() - } else { + // If the block wasn't finalized, verify the origin and validity of its inherents + if !block.finalized { + self.verify_origin(block.header.hash())?; + if let Some(body) = block.body.clone() { self .check_inherents( B::new(block.header.clone(), body), @@ -121,6 +205,9 @@ where } } + // Additionally check these fields are empty + // They *should* be unused, so requiring their emptiness prevents malleability and ensures + // nothing slips through if !block.post_digests.is_empty() { Err(Error::Other("post-digests included".into()))?; } @@ -128,7 +215,6 @@ where Err(Error::Other("auxiliary included".into()))?; } - block.fork_choice = Some(ForkChoiceStrategy::Custom(false)); - self.inner.import_block(block, new_cache).await.map_err(Into::into) + Ok(()) } } From 3b08633445b569ff409b5c94c74671d9b6fb0097 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 02:18:51 -0400 Subject: [PATCH 034/186] BlockImport, JustificationImport, Verifier, and import_queue function --- substrate/consensus/src/import_queue.rs | 167 ++++++++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index d88d1f91c..cdf6ced39 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -37,6 +37,7 @@ use sc_consensus::{ ForkChoiceStrategy, BlockCheckParams, BlockImportParams, + Verifier, ImportResult, BlockImport, JustificationImport, @@ -218,3 +219,169 @@ impl< Ok(()) } } + +#[async_trait] +impl< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + > BlockImport for TendermintImport +where + I::Error: Into, +{ + type Error = Error; + type Transaction = TransactionFor; + + // TODO: Is there a DoS where you send a block without justifications, causing it to error, + // yet adding it to the blacklist in the process preventing further syncing? + async fn check_block( + &mut self, + mut block: BlockCheckParams, + ) -> Result { + self.verify_order(block.parent_hash, block.number)?; + + // Does not verify origin here as origin only applies to unfinalized blocks + // We don't have context on if this block has justifications or not + + block.allow_missing_state = false; + block.allow_missing_parent = false; + + self.inner.write().await.check_block(block).await.map_err(Into::into) + } + + async fn import_block( + &mut self, + mut block: BlockImportParams>, + new_cache: HashMap>, + ) -> Result { + self.check(&mut block).await?; + self.inner.write().await.import_block(block, new_cache).await.map_err(Into::into) + } +} + +#[async_trait] +impl< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + > JustificationImport for TendermintImport +{ + type Error = Error; + + async fn on_start(&mut self) -> Vec<(B::Hash, ::Number)> { + vec![] + } + + async fn import_justification( + &mut self, + hash: B::Hash, + _: ::Number, + justification: Justification, + ) -> Result<(), Error> { + self.verify_justification(hash, &justification)?; + self + .client + .finalize_block(BlockId::Hash(hash), Some(justification), true) + .map_err(|_| Error::InvalidJustification) + } +} + +#[async_trait] +impl< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + > Verifier for TendermintImport +{ + async fn verify( + &mut self, + mut block: BlockImportParams, + ) -> Result<(BlockImportParams, Option)>>), String> { + self.check(&mut block).await.map_err(|e| format!("{}", e))?; + Ok((block, None)) + } +} + +#[async_trait] +impl< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + > Network for TendermintImport +{ + type ValidatorId = u16; + type SignatureScheme = TendermintSigner; + type Weights = TendermintWeights; + type Block = B; + + const BLOCK_TIME: u32 = { (serai_runtime::MILLISECS_PER_BLOCK / 1000) as u32 }; + + fn signature_scheme(&self) -> Arc { + todo!() + } + + fn weights(&self) -> Arc { + Arc::new(TendermintWeights) + } + + async fn broadcast(&mut self, msg: SignedMessage) { + todo!() + } + + async fn slash(&mut self, validator: u16) { + todo!() + } + + fn validate(&mut self, block: &B) -> Result<(), BlockError> { + todo!() + // self.check_block().map_err(|_| BlockError::Temporal)?; + // self.import_block().map_err(|_| BlockError::Temporal)?; + // Ok(()) + } + + fn add_block(&mut self, block: B, commit: Commit) -> B { + todo!() + } +} + +pub type TendermintImportQueue = BasicQueue; + +pub fn import_queue< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, +>( + client: Arc, + inner: I, + providers: Arc, + spawner: &impl sp_core::traits::SpawnEssentialNamed, + registry: Option<&Registry>, +) -> TendermintImportQueue> +where + I::Error: Into, +{ + let import = TendermintImport { + _block: PhantomData, + _backend: PhantomData, + + importing_block: Arc::new(RwLock::new(None)), + + client, + inner: Arc::new(AsyncRwLock::new(inner)), + providers, + }; + let boxed = Box::new(import.clone()); + + // TODO: Fully read BasicQueue in otder to understand it + BasicQueue::new(import, boxed.clone(), Some(boxed), spawner, registry) +} From c0432e122ce06119452a3a4b06b0af27e907afbb Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 02:30:38 -0400 Subject: [PATCH 035/186] Update consensus/lib.rs from PoW to Tendermint Not possible to be used as the previous consensus could. It will not produce blocks nor does it currenly even instantiate a machine. This is just he next step. --- substrate/consensus/src/algorithm.rs | 27 ------------ substrate/consensus/src/lib.rs | 64 ++++++++++++++-------------- 2 files changed, 33 insertions(+), 58 deletions(-) delete mode 100644 substrate/consensus/src/algorithm.rs diff --git a/substrate/consensus/src/algorithm.rs b/substrate/consensus/src/algorithm.rs deleted file mode 100644 index 9d51e4e15..000000000 --- a/substrate/consensus/src/algorithm.rs +++ /dev/null @@ -1,27 +0,0 @@ -use sp_core::U256; - -use sc_consensus_pow::{Error, PowAlgorithm}; -use sp_consensus_pow::Seal; - -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; - -#[derive(Clone)] -pub struct AcceptAny; -impl PowAlgorithm for AcceptAny { - type Difficulty = U256; - - fn difficulty(&self, _: B::Hash) -> Result> { - Ok(U256::one()) - } - - fn verify( - &self, - _: &BlockId, - _: &B::Hash, - _: Option<&[u8]>, - _: &Seal, - _: Self::Difficulty, - ) -> Result> { - Ok(true) - } -} diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index e81c793fb..762f8c204 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -1,21 +1,23 @@ -use std::{marker::Sync, sync::Arc, time::Duration}; +use std::sync::Arc; -use substrate_prometheus_endpoint::Registry; +use sp_api::TransactionFor; +use sp_consensus::Error; -use sc_consensus_pow as sc_pow; -use sc_executor::NativeElseWasmExecutor; -use sc_service::TaskManager; +use sc_executor::{NativeVersion, NativeExecutionDispatch, NativeElseWasmExecutor}; +use sc_service::{TaskManager, TFullClient}; -use serai_runtime::{self, opaque::Block, RuntimeApi}; +use substrate_prometheus_endpoint::Registry; -mod algorithm; +use serai_runtime::{self, opaque::Block, RuntimeApi}; mod signature_scheme; -mod import; -//mod tendermint; +mod weights; + +mod import_queue; +use import_queue::TendermintImportQueue; pub struct ExecutorDispatch; -impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { +impl NativeExecutionDispatch for ExecutorDispatch { #[cfg(feature = "runtime-benchmarks")] type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; #[cfg(not(feature = "runtime-benchmarks"))] @@ -25,41 +27,40 @@ impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { serai_runtime::api::dispatch(method, data) } - fn native_version() -> sc_executor::NativeVersion { + fn native_version() -> NativeVersion { serai_runtime::native_version() } } -pub type FullClient = - sc_service::TFullClient>; - -type Db = sp_trie::PrefixedMemoryDB; +pub type FullClient = TFullClient>; -pub fn import_queue + 'static>( +pub fn import_queue( task_manager: &TaskManager, client: Arc, - select_chain: S, registry: Option<&Registry>, -) -> Result, sp_consensus::Error> { - let pow_block_import = Box::new(sc_pow::PowBlockImport::new( +) -> Result>, Error> { + Ok(import_queue::import_queue( client.clone(), client, - algorithm::AcceptAny, - 0, - select_chain, - |_, _| async { Ok(sp_timestamp::InherentDataProvider::from_system_time()) }, - )); - - sc_pow::import_queue( - pow_block_import, - None, - algorithm::AcceptAny, + Arc::new(|_, _| async { Ok(()) }), &task_manager.spawn_essential_handle(), registry, - ) + )) +} + +// If we're an authority, produce blocks +pub fn authority( + task_manager: &TaskManager, + client: Arc, + network: Arc::Hash>>, + pool: Arc>, + registry: Option<&Registry>, +) { + todo!() } -// Produce a block every 5 seconds +/* +// Produce a block every 6 seconds async fn produce< Block: sp_api::BlockT, Algorithm: sc_pow::PowAlgorithm + Send + Sync + 'static, @@ -126,3 +127,4 @@ pub fn authority + 'static>( task_manager.spawn_essential_handle().spawn("producer", None, produce(worker)); } +*/ From 976948e9d97a12092841517b8d2cc9c28d4ff11f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 03:17:02 -0400 Subject: [PATCH 036/186] Update Cargo.tomls for substrate packages --- Cargo.lock | 56 ---------------------------------- substrate/consensus/Cargo.toml | 26 ++++++---------- substrate/consensus/src/lib.rs | 2 +- substrate/node/Cargo.toml | 36 +++++++++++----------- substrate/runtime/Cargo.toml | 27 ++++++++-------- 5 files changed, 41 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a929d3376..dbaec9d51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2591,21 +2591,6 @@ dependencies = [ "sp-weights", ] -[[package]] -name = "frame-system-benchmarking" -version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-runtime", - "sp-std", -] - [[package]] name = "frame-system-rpc-runtime-api" version = "4.0.0-dev" @@ -6577,31 +6562,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "sc-consensus-pow" -version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" -dependencies = [ - "async-trait", - "futures", - "futures-timer", - "log", - "parity-scale-codec", - "parking_lot 0.12.1", - "sc-client-api", - "sc-consensus", - "sp-api", - "sp-block-builder", - "sp-blockchain", - "sp-consensus", - "sp-consensus-pow", - "sp-core", - "sp-inherents", - "sp-runtime", - "substrate-prometheus-endpoint", - "thiserror", -] - [[package]] name = "sc-executor" version = "0.10.0-dev" @@ -7430,7 +7390,6 @@ dependencies = [ "sc-basic-authorship", "sc-client-api", "sc-consensus", - "sc-consensus-pow", "sc-executor", "sc-network", "sc-service", @@ -7440,12 +7399,10 @@ dependencies = [ "sp-application-crypto", "sp-blockchain", "sp-consensus", - "sp-consensus-pow", "sp-core", "sp-inherents", "sp-runtime", "sp-timestamp", - "sp-trie", "substrate-prometheus-endpoint", "tendermint-machine", "tokio", @@ -7539,7 +7496,6 @@ dependencies = [ "frame-executive", "frame-support", "frame-system", - "frame-system-benchmarking", "frame-system-rpc-runtime-api", "hex-literal", "pallet-balances", @@ -7948,18 +7904,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "sp-consensus-pow" -version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" -dependencies = [ - "parity-scale-codec", - "sp-api", - "sp-core", - "sp-runtime", - "sp-std", -] - [[package]] name = "sp-core" version = "6.0.0" diff --git a/substrate/consensus/Cargo.toml b/substrate/consensus/Cargo.toml index df8e54d9f..f1bfb9841 100644 --- a/substrate/consensus/Cargo.toml +++ b/substrate/consensus/Cargo.toml @@ -15,35 +15,27 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] async-trait = "0.1" +tokio = "1" + sp-core = { git = "https://github.com/serai-dex/substrate" } sp-application-crypto = { git = "https://github.com/serai-dex/substrate" } sp-inherents = { git = "https://github.com/serai-dex/substrate" } -sp-runtime = { git = "https://github.com/serai-dex/substrate" } +sp-timestamp = { git = "https://github.com/serai-dex/substrate" } sp-blockchain = { git = "https://github.com/serai-dex/substrate" } +sp-runtime = { git = "https://github.com/serai-dex/substrate" } sp-api = { git = "https://github.com/serai-dex/substrate" } - sp-consensus = { git = "https://github.com/serai-dex/substrate" } -sc-consensus = { git = "https://github.com/serai-dex/substrate" } - -tendermint-machine = { path = "../tendermint", features = ["substrate"] } -# -- - -sp-trie = { git = "https://github.com/serai-dex/substrate" } -sp-timestamp = { git = "https://github.com/serai-dex/substrate" } sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" } sc-basic-authorship = { git = "https://github.com/serai-dex/substrate" } -sc-consensus-pow = { git = "https://github.com/serai-dex/substrate" } -sp-consensus-pow = { git = "https://github.com/serai-dex/substrate" } - +sc-executor = { git = "https://github.com/serai-dex/substrate" } sc-network = { git = "https://github.com/serai-dex/substrate" } -sc-service = { git = "https://github.com/serai-dex/substrate", features = ["wasmtime"] } -sc-executor = { git = "https://github.com/serai-dex/substrate", features = ["wasmtime"] } +sc-service = { git = "https://github.com/serai-dex/substrate" } +sc-client-api = { git = "https://github.com/serai-dex/substrate" } +sc-consensus = { git = "https://github.com/serai-dex/substrate" } substrate-prometheus-endpoint = { git = "https://github.com/serai-dex/substrate" } -sc-client-api = { git = "https://github.com/serai-dex/substrate" } +tendermint-machine = { path = "../tendermint", features = ["substrate"] } serai-runtime = { path = "../runtime" } - -tokio = "1" diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index 762f8c204..56734f57e 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -42,7 +42,7 @@ pub fn import_queue( Ok(import_queue::import_queue( client.clone(), client, - Arc::new(|_, _| async { Ok(()) }), + Arc::new(|_, _| async { Ok(sp_timestamp::InherentDataProvider::from_system_time()) }), &task_manager.spawn_essential_handle(), registry, )) diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index 58c507856..a25473e74 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -13,41 +13,40 @@ name = "serai-node" [dependencies] clap = { version = "4", features = ["derive"] } +jsonrpsee = { version = "0.15", features = ["server"] } -sc-cli = { git = "https://github.com/serai-dex/substrate", features = ["wasmtime"] } sp-core = { git = "https://github.com/serai-dex/substrate" } -sc-executor = { git = "https://github.com/serai-dex/substrate", features = ["wasmtime"] } -sc-service = { git = "https://github.com/serai-dex/substrate", features = ["wasmtime"] } +sp-runtime = { git = "https://github.com/serai-dex/substrate" } +sp-timestamp = { git = "https://github.com/serai-dex/substrate" } +sp-inherents = { git = "https://github.com/serai-dex/substrate" } +sp-keyring = { git = "https://github.com/serai-dex/substrate" } +sp-api = { git = "https://github.com/serai-dex/substrate" } +sp-blockchain = { git = "https://github.com/serai-dex/substrate" } +sp-block-builder = { git = "https://github.com/serai-dex/substrate" } + +sc-cli = { git = "https://github.com/serai-dex/substrate" } +sc-executor = { git = "https://github.com/serai-dex/substrate" } +sc-service = { git = "https://github.com/serai-dex/substrate" } sc-telemetry = { git = "https://github.com/serai-dex/substrate" } sc-keystore = { git = "https://github.com/serai-dex/substrate" } sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" } sc-transaction-pool-api = { git = "https://github.com/serai-dex/substrate" } sc-consensus = { git = "https://github.com/serai-dex/substrate" } sc-client-api = { git = "https://github.com/serai-dex/substrate" } -sp-runtime = { git = "https://github.com/serai-dex/substrate" } -sp-timestamp = { git = "https://github.com/serai-dex/substrate" } -sp-inherents = { git = "https://github.com/serai-dex/substrate" } -sp-keyring = { git = "https://github.com/serai-dex/substrate" } + frame-system = { git = "https://github.com/serai-dex/substrate" } +frame-benchmarking = { git = "https://github.com/serai-dex/substrate" } +frame-benchmarking-cli = { git = "https://github.com/serai-dex/substrate" } pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false } -# These dependencies are used for the node template's RPCs -jsonrpsee = { version = "0.15", features = ["server"] } sc-rpc = { git = "https://github.com/serai-dex/substrate" } -sp-api = { git = "https://github.com/serai-dex/substrate" } sc-rpc-api = { git = "https://github.com/serai-dex/substrate" } -sp-blockchain = { git = "https://github.com/serai-dex/substrate" } -sp-block-builder = { git = "https://github.com/serai-dex/substrate" } + substrate-frame-rpc-system = { git = "https://github.com/serai-dex/substrate" } pallet-transaction-payment-rpc = { git = "https://github.com/serai-dex/substrate" } -# These dependencies are used for runtime benchmarking -frame-benchmarking = { git = "https://github.com/serai-dex/substrate" } -frame-benchmarking-cli = { git = "https://github.com/serai-dex/substrate" } - -# Local dependencies -serai-consensus = { path = "../consensus" } serai-runtime = { path = "../runtime" } +serai-consensus = { path = "../consensus" } [build-dependencies] substrate-build-script-utils = { git = "https://github.com/serai-dex/substrate.git" } @@ -57,5 +56,6 @@ default = [] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-benchmarking-cli/runtime-benchmarks", + "serai-runtime/runtime-benchmarks" ] diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index 048039c8e..76b425417 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -28,26 +28,22 @@ sp-block-builder = { git = "https://github.com/serai-dex/substrate", default-fea sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-api = { git = "https://github.com/serai-dex/substrate", default-features = false } -frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false } +frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } frame-executive = { git = "https://github.com/serai-dex/substrate", default-features = false } +frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true } -pallet-randomness-collective-flip = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-timestamp = { git = "https://github.com/serai-dex/substrate", default-features = false } +pallet-randomness-collective-flip = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-balances = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-contracts-primitives = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-contracts = { git = "https://github.com/serai-dex/substrate", default-features = false } -# Used for the node template's RPCs frame-system-rpc-runtime-api = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/serai-dex/substrate", default-features = false } -# Used for runtime benchmarking -frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true } -frame-system-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true } - [build-dependencies] substrate-wasm-builder = { git = "https://github.com/serai-dex/substrate" } @@ -67,28 +63,31 @@ std = [ "sp-runtime/std", "sp-api/std", - "frame-support/std", - "frame-system-rpc-runtime-api/std", "frame-system/std", + "frame-support/std", "frame-executive/std", - "pallet-randomness-collective-flip/std", "pallet-timestamp/std", + "pallet-randomness-collective-flip/std", "pallet-balances/std", "pallet-transaction-payment/std", - "pallet-transaction-payment-rpc-runtime-api/std", "pallet-contracts/std", "pallet-contracts-primitives/std", + + "frame-system-rpc-runtime-api/std", + "pallet-transaction-payment-rpc-runtime-api/std", ] runtime-benchmarks = [ "hex-literal", + "sp-runtime/runtime-benchmarks", - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system-benchmarking", + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", "pallet-balances/runtime-benchmarks", ] From 123b8ad11b171b4ac2525fa2672cf12b81484fad Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 05:28:26 -0400 Subject: [PATCH 037/186] Tendermint SelectChain This is incompatible with Substrate's expectations, yet should be valid for ours --- substrate/consensus/src/lib.rs | 3 ++ substrate/consensus/src/select_chain.rs | 55 +++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 substrate/consensus/src/select_chain.rs diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index 56734f57e..7dc9e730c 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -16,6 +16,9 @@ mod weights; mod import_queue; use import_queue::TendermintImportQueue; +mod select_chain; +pub use select_chain::TendermintSelectChain; + pub struct ExecutorDispatch; impl NativeExecutionDispatch for ExecutorDispatch { #[cfg(feature = "runtime-benchmarks")] diff --git a/substrate/consensus/src/select_chain.rs b/substrate/consensus/src/select_chain.rs new file mode 100644 index 000000000..e10910e21 --- /dev/null +++ b/substrate/consensus/src/select_chain.rs @@ -0,0 +1,55 @@ +use std::{marker::PhantomData, sync::Arc}; + +use async_trait::async_trait; + +use sp_api::BlockId; +use sp_runtime::traits::Block; +use sp_blockchain::{HeaderBackend, Backend as BlockchainBackend}; +use sc_client_api::Backend; +use sp_consensus::{Error, SelectChain}; + +pub struct TendermintSelectChain>(Arc, PhantomData); + +impl> Clone for TendermintSelectChain { + fn clone(&self) -> Self { + TendermintSelectChain(self.0.clone(), PhantomData) + } +} + +impl> TendermintSelectChain { + pub fn new(backend: Arc) -> TendermintSelectChain { + TendermintSelectChain(backend, PhantomData) + } +} + +#[async_trait] +impl> SelectChain for TendermintSelectChain { + async fn leaves(&self) -> Result, Error> { + panic!("should never be called") + + // Substrate may call this at some point in the future? + // It doesn't appear to do so now, and we have the question of what to do if/when it does + // Either we return the chain tip, which is the true leaf yet breaks the documented definition, + // or we return the actual leaves, when those don't contribute value + // + // Both become risky as best_chain, which is presumably used for block building, is explicitly + // defined as one of these leaves. If it's returning the best chain, the finalized chain tip, + // then it's wrong. The real comment is that this API does not support the Tendermint model + // + // Since it appears the blockchain operations happen on the Backend's leaves, not the + // SelectChain's, leaving this as a panic for now should be optimal + // + // TODO: Triple check this isn't reachable + } + + async fn best_chain(&self) -> Result { + Ok( + self + .0 + .blockchain() + .header(BlockId::Hash(self.0.blockchain().last_finalized().unwrap())) + .unwrap() + .unwrap(), + ) + } +} From b8bff6501e61083fb1fbdc52f8fb69eaeffa6de7 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 05:29:01 -0400 Subject: [PATCH 038/186] Move the node over to the new SelectChain --- substrate/node/src/service.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 6832fd1d3..a5dbb58c7 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -2,13 +2,14 @@ use std::sync::Arc; use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; use sc_executor::NativeElseWasmExecutor; +use sc_client_api::Backend; use sc_telemetry::{Telemetry, TelemetryWorker}; use serai_runtime::{self, opaque::Block, RuntimeApi}; pub(crate) use serai_consensus::{ExecutorDispatch, FullClient}; type FullBackend = sc_service::TFullBackend; -type FullSelectChain = sc_consensus::LongestChain; +type FullSelectChain = serai_consensus::TendermintSelectChain; type PartialComponents = sc_service::PartialComponents< FullClient, @@ -55,8 +56,6 @@ pub fn new_partial(config: &Configuration) -> Result Result Result { client, network, transaction_pool, - select_chain, prometheus_registry.as_ref(), ); } From 0a58d6695800697f6ae1d2acd6eb9f570ba402c7 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 05:35:37 -0400 Subject: [PATCH 039/186] Minor tweaks --- substrate/node/src/service.rs | 3 +-- substrate/runtime/src/lib.rs | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index a5dbb58c7..fe8671fc7 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; use sc_executor::NativeElseWasmExecutor; -use sc_client_api::Backend; use sc_telemetry::{Telemetry, TelemetryWorker}; use serai_runtime::{self, opaque::Block, RuntimeApi}; @@ -88,7 +87,7 @@ pub fn new_full(config: Configuration) -> Result { mut task_manager, import_queue, keystore_container, - select_chain, + select_chain: _, other: mut telemetry, transaction_pool, } = new_partial(&config)?; diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 3a4111923..09d7b90ef 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -35,9 +35,6 @@ use pallet_transaction_payment::CurrencyAdapter; /// An index to a block. pub type BlockNumber = u32; -/// Signature type -pub type Signature = sp_core::sr25519::Signature; - /// Account ID type, equivalent to a public key pub type AccountId = sp_core::sr25519::Public; From 0218db0084b2819c7cdc9932d1f3e56e8ee8fb88 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 05:53:15 -0400 Subject: [PATCH 040/186] Update SelectChain documentation --- substrate/consensus/src/select_chain.rs | 34 ++++++++++++++----------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/substrate/consensus/src/select_chain.rs b/substrate/consensus/src/select_chain.rs index e10910e21..32a409ea9 100644 --- a/substrate/consensus/src/select_chain.rs +++ b/substrate/consensus/src/select_chain.rs @@ -1,3 +1,19 @@ +// SelectChain, while provided by Substrate and part of PartialComponents, isn't used by Substrate +// It's common between various block-production/finality crates, yet Substrate as a system doesn't +// rely on it, which is good, because its definition is explicitly incompatible with Tendermint +// +// leaves is supposed to return all leaves of the blockchain. While Tendermint maintains that view, +// an honest node will only build on the most recently finalized block, so it is a 'leaf' despite +// having descendants +// +// best_chain will always be this finalized block, yet Substrate explicitly defines it as one of +// the above leaves, which this finalized block is explicitly not included in. Accordingly, we +// can never provide a compatible decision +// +// Since PartialComponents expects it, an implementation which does its best is provided. It panics +// if leaves is called, yet returns the finalized chain tip for best_chain, as that's intended to +// be the header to build upon + use std::{marker::PhantomData, sync::Arc}; use async_trait::async_trait; @@ -25,21 +41,7 @@ impl> TendermintSelectChain { #[async_trait] impl> SelectChain for TendermintSelectChain { async fn leaves(&self) -> Result, Error> { - panic!("should never be called") - - // Substrate may call this at some point in the future? - // It doesn't appear to do so now, and we have the question of what to do if/when it does - // Either we return the chain tip, which is the true leaf yet breaks the documented definition, - // or we return the actual leaves, when those don't contribute value - // - // Both become risky as best_chain, which is presumably used for block building, is explicitly - // defined as one of these leaves. If it's returning the best chain, the finalized chain tip, - // then it's wrong. The real comment is that this API does not support the Tendermint model - // - // Since it appears the blockchain operations happen on the Backend's leaves, not the - // SelectChain's, leaving this as a panic for now should be optimal - // - // TODO: Triple check this isn't reachable + panic!("Substrate definition of leaves is incompatible with Tendermint") } async fn best_chain(&self) -> Result { @@ -47,7 +49,9 @@ impl> SelectChain for TendermintSelectChain { self .0 .blockchain() + // There should always be a finalized block .header(BlockId::Hash(self.0.blockchain().last_finalized().unwrap())) + // There should not be an error in retrieving it and since it's finalized, it should exist .unwrap() .unwrap(), ) From 802f87385cd378f3026a3dfe356a173f3b6977e9 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 05:56:53 -0400 Subject: [PATCH 041/186] Remove substrate/node lib.rs This shouldn't be used as a library AFAIK. While runtime should be, and arguably should even be published, I have yet to see node in the same way. Helps tighten API boundaries. --- substrate/node/src/command.rs | 2 +- substrate/node/src/lib.rs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 substrate/node/src/lib.rs diff --git a/substrate/node/src/command.rs b/substrate/node/src/command.rs index c313d1a54..c087d817a 100644 --- a/substrate/node/src/command.rs +++ b/substrate/node/src/command.rs @@ -117,7 +117,7 @@ pub fn run() -> sc_cli::Result<()> { } BenchmarkCmd::Extrinsic(cmd) => { - let PartialComponents { client, .. } = service::new_partial(&config)?; + let client = service::new_partial(&config)?.client; cmd.run( client.clone(), inherent_benchmark_data()?, diff --git a/substrate/node/src/lib.rs b/substrate/node/src/lib.rs deleted file mode 100644 index f117b8aae..000000000 --- a/substrate/node/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod chain_spec; -pub mod rpc; -pub mod service; From 5019f4cb6533ba312e2dbfcc0eff93bb8217a152 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 06:00:18 -0400 Subject: [PATCH 042/186] Remove unused macro_use --- substrate/node/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/substrate/node/src/main.rs b/substrate/node/src/main.rs index 57565f625..af50c8330 100644 --- a/substrate/node/src/main.rs +++ b/substrate/node/src/main.rs @@ -1,5 +1,4 @@ mod chain_spec; -#[macro_use] mod service; mod command_helper; From 422f7e3e2fb552091228d999549073f554ba380f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 21:14:05 -0400 Subject: [PATCH 043/186] Replace panicking todos with stubs and // TODO Enables progress. --- substrate/consensus/src/import_queue.rs | 29 ++++++++++++++------- substrate/consensus/src/signature_scheme.rs | 8 ++++++ substrate/consensus/src/weights.rs | 1 + 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index cdf6ced39..e887b82d8 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -21,7 +21,7 @@ use async_trait::async_trait; use tokio::sync::RwLock as AsyncRwLock; -use sp_core::Decode; +use sp_core::{Encode, Decode}; use sp_application_crypto::sr25519::Signature; use sp_inherents::CreateInherentDataProviders; use sp_runtime::{ @@ -109,7 +109,8 @@ impl< block: B, providers: CIDP::InherentDataProviders, ) -> Result<(), Error> { - todo!() + // TODO + Ok(()) } // Ensure this is part of a sequential import @@ -218,6 +219,18 @@ impl< Ok(()) } + + fn import_justification_actual( + &mut self, + hash: B::Hash, + justification: Justification, + ) -> Result<(), Error> { + self.verify_justification(hash, &justification)?; + self + .client + .finalize_block(BlockId::Hash(hash), Some(justification), true) + .map_err(|_| Error::InvalidJustification) + } } #[async_trait] @@ -282,11 +295,7 @@ impl< _: ::Number, justification: Justification, ) -> Result<(), Error> { - self.verify_justification(hash, &justification)?; - self - .client - .finalize_block(BlockId::Hash(hash), Some(justification), true) - .map_err(|_| Error::InvalidJustification) + self.import_justification_actual(hash, justification) } } @@ -325,7 +334,7 @@ impl< const BLOCK_TIME: u32 = { (serai_runtime::MILLISECS_PER_BLOCK / 1000) as u32 }; fn signature_scheme(&self) -> Arc { - todo!() + Arc::new(TendermintSigner::new()) } fn weights(&self) -> Arc { @@ -333,7 +342,7 @@ impl< } async fn broadcast(&mut self, msg: SignedMessage) { - todo!() + // TODO } async fn slash(&mut self, validator: u16) { @@ -348,6 +357,8 @@ impl< } fn add_block(&mut self, block: B, commit: Commit) -> B { + self.import_justification_actual(block.hash(), (CONSENSUS_ID, commit.encode())).unwrap(); + // Next block proposal todo!() } } diff --git a/substrate/consensus/src/signature_scheme.rs b/substrate/consensus/src/signature_scheme.rs index bf3c3e726..bf552f366 100644 --- a/substrate/consensus/src/signature_scheme.rs +++ b/substrate/consensus/src/signature_scheme.rs @@ -10,6 +10,14 @@ pub(crate) struct TendermintSigner { lookup: Vec, } +impl TendermintSigner { + pub(crate) fn new() -> TendermintSigner { + // TODO + let keys = Pair::from_string("//Alice", None).unwrap(); + TendermintSigner { lookup: vec![keys.public()], keys } + } +} + impl SignatureScheme for TendermintSigner { type ValidatorId = u16; type Signature = Signature; diff --git a/substrate/consensus/src/weights.rs b/substrate/consensus/src/weights.rs index d9d5c4058..f6c801e18 100644 --- a/substrate/consensus/src/weights.rs +++ b/substrate/consensus/src/weights.rs @@ -4,6 +4,7 @@ use tendermint_machine::ext::{BlockNumber, Round, Weights}; const VALIDATORS: usize = 1; +// TODO: Move to sp_session pub(crate) struct TendermintWeights; impl Weights for TendermintWeights { type ValidatorId = u16; From 4bfc8d7954235268f0afc5b549ae824f3b7a5b46 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 21:14:33 -0400 Subject: [PATCH 044/186] Reduce chain_spec and use more accurate naming --- substrate/node/src/chain_spec.rs | 40 ++++++++++++++------------------ 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index 001061b95..88fc09498 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -1,24 +1,18 @@ use sc_service::ChainType; use sp_runtime::traits::Verify; -use sp_core::{sr25519, Pair, Public}; - -use sp_runtime::traits::IdentifyAccount; +use sp_core::{Pair as PairTrait, sr25519::Pair}; use serai_runtime::{WASM_BINARY, AccountId, Signature, GenesisConfig, SystemConfig, BalancesConfig}; pub type ChainSpec = sc_service::GenericChainSpec; -type AccountPublic = ::Signer; -fn get_from_seed(seed: &'static str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None).unwrap().public() +fn insecure_pair_from_name(name: &'static str) -> Pair { + Pair::from_string(&format!("//{}", name), None).unwrap() } -fn get_account_id_from_seed(seed: &'static str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() +fn account_id_from_name(name: &'static str) -> AccountId { + insecure_pair_from_name(name).public() } fn testnet_genesis(wasm_binary: &[u8], endowed_accounts: Vec) -> GenesisConfig { @@ -44,18 +38,18 @@ pub fn development_config() -> Result { testnet_genesis( wasm_binary, vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), + account_id_from_name("Alice"), + account_id_from_name("Bob"), + account_id_from_name("Charlie"), + account_id_from_name("Dave"), + account_id_from_name("Eve"), + account_id_from_name("Ferdie"), + account_id_from_name("Alice//stash"), + account_id_from_name("Bob//stash"), + account_id_from_name("Charlie//stash"), + account_id_from_name("Dave//stash"), + account_id_from_name("Eve//stash"), + account_id_from_name("Ferdie//stash"), ], ) }, From bf5bdb89c2d6f86d13f8c3f6950534c021576ad8 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 21 Oct 2022 23:36:24 -0400 Subject: [PATCH 045/186] Implement block proposal logic --- Cargo.lock | 1 + substrate/consensus/Cargo.toml | 2 + substrate/consensus/src/import_queue.rs | 72 ++++++++++++++++++++----- substrate/consensus/src/lib.rs | 13 ++++- substrate/node/src/chain_spec.rs | 3 +- substrate/node/src/service.rs | 8 ++- substrate/tendermint/src/ext.rs | 7 ++- substrate/tendermint/src/lib.rs | 2 +- substrate/tendermint/tests/ext.rs | 6 ++- 9 files changed, 91 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5844d3ec5..f87293fbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7387,6 +7387,7 @@ name = "serai-consensus" version = "0.1.0" dependencies = [ "async-trait", + "log", "sc-basic-authorship", "sc-client-api", "sc-consensus", diff --git a/substrate/consensus/Cargo.toml b/substrate/consensus/Cargo.toml index f1bfb9841..756ac11db 100644 --- a/substrate/consensus/Cargo.toml +++ b/substrate/consensus/Cargo.toml @@ -15,6 +15,8 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] async-trait = "0.1" +log = "0.4" + tokio = "1" sp-core = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index e887b82d8..c00c2e319 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -14,24 +14,27 @@ use std::{ marker::PhantomData, sync::{Arc, RwLock}, + time::Duration, collections::HashMap, }; use async_trait::async_trait; +use log::warn; + use tokio::sync::RwLock as AsyncRwLock; use sp_core::{Encode, Decode}; use sp_application_crypto::sr25519::Signature; -use sp_inherents::CreateInherentDataProviders; +use sp_inherents::{InherentData, InherentDataProvider, CreateInherentDataProviders}; use sp_runtime::{ traits::{Header, Block}, - Justification, + Digest, Justification, }; use sp_blockchain::HeaderBackend; use sp_api::{BlockId, TransactionFor, ProvideRuntimeApi}; -use sp_consensus::{Error, CacheKeyId}; +use sp_consensus::{Error, CacheKeyId, Proposer, Environment}; #[rustfmt::skip] // rustfmt doesn't know how to handle this line use sc_consensus::{ ForkChoiceStrategy, @@ -63,6 +66,7 @@ struct TendermintImport< C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, + E: Send + Sync + Environment + 'static, > { _block: PhantomData, _backend: PhantomData, @@ -72,6 +76,8 @@ struct TendermintImport< client: Arc, inner: Arc>, providers: Arc, + + env: Arc>, } impl< @@ -80,7 +86,8 @@ impl< C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, - > Clone for TendermintImport + E: Send + Sync + Environment + 'static, + > Clone for TendermintImport { fn clone(&self) -> Self { TendermintImport { @@ -92,6 +99,8 @@ impl< client: self.client.clone(), inner: self.inner.clone(), providers: self.providers.clone(), + + env: self.env.clone(), } } } @@ -102,7 +111,8 @@ impl< C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, - > TendermintImport + E: Send + Sync + Environment + 'static, + > TendermintImport { async fn check_inherents( &self, @@ -240,7 +250,8 @@ impl< C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, - > BlockImport for TendermintImport + E: Send + Sync + Environment + 'static, + > BlockImport for TendermintImport where I::Error: Into, { @@ -281,7 +292,8 @@ impl< C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, - > JustificationImport for TendermintImport + E: Send + Sync + Environment + 'static, + > JustificationImport for TendermintImport { type Error = Error; @@ -306,7 +318,8 @@ impl< C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, - > Verifier for TendermintImport + E: Send + Sync + Environment + 'static, + > Verifier for TendermintImport { async fn verify( &mut self, @@ -324,7 +337,8 @@ impl< C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, - > Network for TendermintImport + E: Send + Sync + Environment + 'static, + > Network for TendermintImport { type ValidatorId = u16; type SignatureScheme = TendermintSigner; @@ -356,10 +370,38 @@ impl< // Ok(()) } - fn add_block(&mut self, block: B, commit: Commit) -> B { - self.import_justification_actual(block.hash(), (CONSENSUS_ID, commit.encode())).unwrap(); - // Next block proposal - todo!() + async fn add_block(&mut self, block: B, commit: Commit) -> B { + let hash = block.hash(); + self.import_justification_actual(hash, (CONSENSUS_ID, commit.encode())).unwrap(); + + let inherent_data = match self.providers.create_inherent_data_providers(hash, ()).await { + Ok(providers) => match providers.create_inherent_data() { + Ok(data) => Some(data), + Err(err) => { + warn!(target: "tendermint", "Failed to create inherent data: {}", err); + None + } + }, + Err(err) => { + warn!(target: "tendermint", "Failed to create inherent data providers: {}", err); + None + } + } + .unwrap_or_else(InherentData::new); + + let proposer = self + .env + .write() + .await + .init(block.header()) + .await + .expect("Failed to create a proposer for the new block"); + // TODO: Production time, size limit + let proposal = proposer + .propose(inherent_data, Digest::default(), Duration::from_secs(1), None) + .await + .expect("Failed to crate a new block proposal"); + proposal.block } } @@ -371,10 +413,12 @@ pub fn import_queue< C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, + E: Send + Sync + Environment + 'static, >( client: Arc, inner: I, providers: Arc, + env: E, spawner: &impl sp_core::traits::SpawnEssentialNamed, registry: Option<&Registry>, ) -> TendermintImportQueue> @@ -390,6 +434,8 @@ where client, inner: Arc::new(AsyncRwLock::new(inner)), providers, + + env: Arc::new(AsyncRwLock::new(env)), }; let boxed = Box::new(import.clone()); diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index 7dc9e730c..a912d29e5 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -4,6 +4,7 @@ use sp_api::TransactionFor; use sp_consensus::Error; use sc_executor::{NativeVersion, NativeExecutionDispatch, NativeElseWasmExecutor}; +use sc_transaction_pool::FullPool; use sc_service::{TaskManager, TFullClient}; use substrate_prometheus_endpoint::Registry; @@ -40,12 +41,20 @@ pub type FullClient = TFullClient, + pool: Arc>, registry: Option<&Registry>, ) -> Result>, Error> { Ok(import_queue::import_queue( client.clone(), - client, + client.clone(), Arc::new(|_, _| async { Ok(sp_timestamp::InherentDataProvider::from_system_time()) }), + sc_basic_authorship::ProposerFactory::new( + task_manager.spawn_handle(), + client, + pool, + registry, + None, + ), &task_manager.spawn_essential_handle(), registry, )) @@ -56,7 +65,7 @@ pub fn authority( task_manager: &TaskManager, client: Arc, network: Arc::Hash>>, - pool: Arc>, + pool: Arc>, registry: Option<&Registry>, ) { todo!() diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index 88fc09498..fb21bec72 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -1,9 +1,8 @@ use sc_service::ChainType; -use sp_runtime::traits::Verify; use sp_core::{Pair as PairTrait, sr25519::Pair}; -use serai_runtime::{WASM_BINARY, AccountId, Signature, GenesisConfig, SystemConfig, BalancesConfig}; +use serai_runtime::{WASM_BINARY, AccountId, GenesisConfig, SystemConfig, BalancesConfig}; pub type ChainSpec = sc_service::GenericChainSpec; diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index fe8671fc7..b97ce8901 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -63,8 +63,12 @@ pub fn new_partial(config: &Configuration) -> Result) - -> Self::Block; + async fn add_block( + &mut self, + block: Self::Block, + commit: Commit, + ) -> Self::Block; } diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 5b65b3b99..b420f5dca 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -318,7 +318,7 @@ impl TendermintMachine { }; debug_assert!(machine.network.read().await.verify_commit(block.id(), &commit)); - let proposal = machine.network.write().await.add_block(block, commit); + let proposal = machine.network.write().await.add_block(block, commit).await; machine.reset(proposal).await; } Err(TendermintError::Malicious(validator)) => { diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index 6f489f7a0..6122c9390 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -113,7 +113,11 @@ impl Network for TestNetwork { block.valid } - fn add_block(&mut self, block: TestBlock, commit: Commit) -> TestBlock { + async fn add_block( + &mut self, + block: TestBlock, + commit: Commit, + ) -> TestBlock { dbg!("Adding ", &block); assert!(block.valid.is_ok()); assert!(self.verify_commit(block.id(), &commit)); From 5e52817dcd45e574a36494edd29c9b0ee283068e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 22 Oct 2022 00:47:38 -0400 Subject: [PATCH 046/186] Modularize to get_proposal --- substrate/consensus/src/import_queue.rs | 65 +++++++++++++------------ 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index c00c2e319..48c3ec00b 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -230,6 +230,38 @@ impl< Ok(()) } + async fn get_proposal(&mut self, block: &B) -> B { + let inherent_data = match self.providers.create_inherent_data_providers(block.hash(), ()).await + { + Ok(providers) => match providers.create_inherent_data() { + Ok(data) => Some(data), + Err(err) => { + warn!(target: "tendermint", "Failed to create inherent data: {}", err); + None + } + }, + Err(err) => { + warn!(target: "tendermint", "Failed to create inherent data providers: {}", err); + None + } + } + .unwrap_or_else(InherentData::new); + + let proposer = self + .env + .write() + .await + .init(block.header()) + .await + .expect("Failed to create a proposer for the new block"); + // TODO: Production time, size limit + proposer + .propose(inherent_data, Digest::default(), Duration::from_secs(1), None) + .await + .expect("Failed to crate a new block proposal") + .block + } + fn import_justification_actual( &mut self, hash: B::Hash, @@ -371,37 +403,8 @@ impl< } async fn add_block(&mut self, block: B, commit: Commit) -> B { - let hash = block.hash(); - self.import_justification_actual(hash, (CONSENSUS_ID, commit.encode())).unwrap(); - - let inherent_data = match self.providers.create_inherent_data_providers(hash, ()).await { - Ok(providers) => match providers.create_inherent_data() { - Ok(data) => Some(data), - Err(err) => { - warn!(target: "tendermint", "Failed to create inherent data: {}", err); - None - } - }, - Err(err) => { - warn!(target: "tendermint", "Failed to create inherent data providers: {}", err); - None - } - } - .unwrap_or_else(InherentData::new); - - let proposer = self - .env - .write() - .await - .init(block.header()) - .await - .expect("Failed to create a proposer for the new block"); - // TODO: Production time, size limit - let proposal = proposer - .propose(inherent_data, Digest::default(), Duration::from_secs(1), None) - .await - .expect("Failed to crate a new block proposal"); - proposal.block + self.import_justification_actual(block.hash(), (CONSENSUS_ID, commit.encode())).unwrap(); + self.get_proposal(&block).await } } From 8ed0f1f1cfa0a0a56c352a9c3b75eace574c17e8 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 22 Oct 2022 00:48:09 -0400 Subject: [PATCH 047/186] Trigger block importing Doesn't wait for the response yet, which it needs to. --- substrate/consensus/src/import_queue.rs | 50 +++++++++++++++++++++---- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index 48c3ec00b..2be10e4cd 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -34,7 +34,7 @@ use sp_runtime::{ use sp_blockchain::HeaderBackend; use sp_api::{BlockId, TransactionFor, ProvideRuntimeApi}; -use sp_consensus::{Error, CacheKeyId, Proposer, Environment}; +use sp_consensus::{Error, CacheKeyId, BlockOrigin, Proposer, Environment}; #[rustfmt::skip] // rustfmt doesn't know how to handle this line use sc_consensus::{ ForkChoiceStrategy, @@ -44,9 +44,11 @@ use sc_consensus::{ ImportResult, BlockImport, JustificationImport, + import_queue::IncomingBlock, BasicQueue, }; +use sc_service::ImportQueue; use sc_client_api::{Backend, Finalizer}; use substrate_prometheus_endpoint::Registry; @@ -60,6 +62,8 @@ use crate::{signature_scheme::TendermintSigner, weights::TendermintWeights}; const CONSENSUS_ID: [u8; 4] = *b"tend"; +pub type TendermintImportQueue = BasicQueue; + struct TendermintImport< B: Block, Be: Backend + 'static, @@ -78,6 +82,7 @@ struct TendermintImport< providers: Arc, env: Arc>, + queue: Arc>>>>, } impl< @@ -101,6 +106,7 @@ impl< providers: self.providers.clone(), env: self.env.clone(), + queue: self.queue.clone(), } } } @@ -113,6 +119,8 @@ impl< CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, > TendermintImport +where + TransactionFor: Send + Sync + 'static, { async fn check_inherents( &self, @@ -286,6 +294,7 @@ impl< > BlockImport for TendermintImport where I::Error: Into, + TransactionFor: Send + Sync + 'static, { type Error = Error; type Transaction = TransactionFor; @@ -326,6 +335,8 @@ impl< CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, > JustificationImport for TendermintImport +where + TransactionFor: Send + Sync + 'static, { type Error = Error; @@ -352,6 +363,8 @@ impl< CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, > Verifier for TendermintImport +where + TransactionFor: Send + Sync + 'static, { async fn verify( &mut self, @@ -371,6 +384,8 @@ impl< CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, > Network for TendermintImport +where + TransactionFor: Send + Sync + 'static, { type ValidatorId = u16; type SignatureScheme = TendermintSigner; @@ -396,10 +411,27 @@ impl< } fn validate(&mut self, block: &B) -> Result<(), BlockError> { + let hash = block.hash(); + let (header, body) = block.clone().deconstruct(); + *self.importing_block.write().unwrap() = Some(hash); + self.queue.write().unwrap().as_mut().unwrap().import_blocks( + BlockOrigin::NetworkBroadcast, + vec![IncomingBlock { + hash, + header: Some(header), + body: Some(body), + indexed_body: None, + justifications: None, + origin: None, + allow_missing_state: false, + skip_execution: false, + // TODO: Only set to true if block was rejected due to its inherents + import_existing: true, + state: None, + }], + ); todo!() - // self.check_block().map_err(|_| BlockError::Temporal)?; - // self.import_block().map_err(|_| BlockError::Temporal)?; - // Ok(()) + // self.queue.poll_actions } async fn add_block(&mut self, block: B, commit: Commit) -> B { @@ -408,8 +440,6 @@ impl< } } -pub type TendermintImportQueue = BasicQueue; - pub fn import_queue< B: Block, Be: Backend + 'static, @@ -427,6 +457,7 @@ pub fn import_queue< ) -> TendermintImportQueue> where I::Error: Into, + TransactionFor: Send + Sync + 'static, { let import = TendermintImport { _block: PhantomData, @@ -439,9 +470,12 @@ where providers, env: Arc::new(AsyncRwLock::new(env)), + queue: Arc::new(RwLock::new(None)), }; let boxed = Box::new(import.clone()); - // TODO: Fully read BasicQueue in otder to understand it - BasicQueue::new(import, boxed.clone(), Some(boxed), spawner, registry) + let queue = + || BasicQueue::new(import.clone(), boxed.clone(), Some(boxed.clone()), spawner, registry); + *import.queue.write().unwrap() = Some(queue()); + queue() } From 193281e387bbddfd2093fc5cb52dc968e9180bb8 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 22 Oct 2022 01:43:07 -0400 Subject: [PATCH 048/186] Get the result of block importing --- substrate/consensus/Cargo.toml | 2 +- substrate/consensus/src/import_queue.rs | 66 ++++++++++++++++++++++--- substrate/tendermint/src/ext.rs | 2 +- substrate/tendermint/src/lib.rs | 4 +- substrate/tendermint/tests/ext.rs | 2 +- 5 files changed, 63 insertions(+), 13 deletions(-) diff --git a/substrate/consensus/Cargo.toml b/substrate/consensus/Cargo.toml index 756ac11db..0de507e44 100644 --- a/substrate/consensus/Cargo.toml +++ b/substrate/consensus/Cargo.toml @@ -17,7 +17,7 @@ async-trait = "0.1" log = "0.4" -tokio = "1" +tokio = { version = "1", features = ["sync", "rt"] } sp-core = { git = "https://github.com/serai-dex/substrate" } sp-application-crypto = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index 2be10e4cd..9b4f24eb8 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -13,7 +13,10 @@ use std::{ marker::PhantomData, + pin::Pin, sync::{Arc, RwLock}, + task::{Poll, Context}, + future::Future, time::Duration, collections::HashMap, }; @@ -22,7 +25,7 @@ use async_trait::async_trait; use log::warn; -use tokio::sync::RwLock as AsyncRwLock; +use tokio::{sync::RwLock as AsyncRwLock, runtime::Handle}; use sp_core::{Encode, Decode}; use sp_application_crypto::sr25519::Signature; @@ -45,6 +48,9 @@ use sc_consensus::{ BlockImport, JustificationImport, import_queue::IncomingBlock, + BlockImportStatus, + BlockImportError, + Link, BasicQueue, }; @@ -82,7 +88,7 @@ struct TendermintImport< providers: Arc, env: Arc>, - queue: Arc>>>>, + queue: Arc>>>>, } impl< @@ -375,6 +381,46 @@ where } } +// Custom helpers for ImportQueue in order to obtain the result of a block's importing +struct ValidateLink(Option<(B::Hash, bool)>); +impl Link for ValidateLink { + fn blocks_processed( + &mut self, + imported: usize, + count: usize, + results: Vec<( + Result::Number>, BlockImportError>, + B::Hash, + )>, + ) { + assert_eq!(imported, 1); + assert_eq!(count, 1); + self.0 = Some((results[0].1, results[0].0.is_ok())); + } +} + +struct ImportFuture<'a, B: Block, T: Send>(B::Hash, RwLock<&'a mut TendermintImportQueue>); +impl<'a, B: Block, T: Send> ImportFuture<'a, B, T> { + fn new(hash: B::Hash, queue: &'a mut TendermintImportQueue) -> ImportFuture { + ImportFuture(hash, RwLock::new(queue)) + } +} + +impl<'a, B: Block, T: Send> Future for ImportFuture<'a, B, T> { + type Output = bool; + + fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { + let mut link = ValidateLink(None); + self.1.write().unwrap().poll_actions(ctx, &mut link); + if let Some(res) = link.0 { + assert_eq!(res.0, self.0); + Poll::Ready(res.1) + } else { + Poll::Pending + } + } +} + #[async_trait] impl< B: Block, @@ -410,11 +456,11 @@ where todo!() } - fn validate(&mut self, block: &B) -> Result<(), BlockError> { + async fn validate(&mut self, block: &B) -> Result<(), BlockError> { let hash = block.hash(); let (header, body) = block.clone().deconstruct(); *self.importing_block.write().unwrap() = Some(hash); - self.queue.write().unwrap().as_mut().unwrap().import_blocks( + self.queue.write().await.as_mut().unwrap().import_blocks( BlockOrigin::NetworkBroadcast, vec![IncomingBlock { hash, @@ -430,8 +476,12 @@ where state: None, }], ); - todo!() - // self.queue.poll_actions + + if ImportFuture::new(hash, self.queue.write().await.as_mut().unwrap()).await { + Ok(()) + } else { + todo!() + } } async fn add_block(&mut self, block: B, commit: Commit) -> B { @@ -470,12 +520,12 @@ where providers, env: Arc::new(AsyncRwLock::new(env)), - queue: Arc::new(RwLock::new(None)), + queue: Arc::new(AsyncRwLock::new(None)), }; let boxed = Box::new(import.clone()); let queue = || BasicQueue::new(import.clone(), boxed.clone(), Some(boxed.clone()), spawner, registry); - *import.queue.write().unwrap() = Some(queue()); + *Handle::current().block_on(import.queue.write()) = Some(queue()); queue() } diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index e015eee00..96536e127 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -180,7 +180,7 @@ pub trait Network: Send + Sync { async fn slash(&mut self, validator: Self::ValidatorId); /// Validate a block. - fn validate(&mut self, block: &Self::Block) -> Result<(), BlockError>; + async fn validate(&mut self, block: &Self::Block) -> Result<(), BlockError>; /// Add a block, returning the proposal for the next one. It's possible a block, which was never /// validated or even failed validation, may be passed here if a supermajority of validators did /// consider it valid and created a commit for it. This deviates from the paper which will have a diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index b420f5dca..56dea0c30 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -436,7 +436,7 @@ impl TendermintMachine { // 22-33 if self.step == Step::Propose { // Delay error handling (triggering a slash) until after we vote. - let (valid, err) = match self.network.write().await.validate(block) { + let (valid, err) = match self.network.write().await.validate(block).await { Ok(_) => (true, Ok(None)), Err(BlockError::Temporal) => (false, Ok(None)), Err(BlockError::Fatal) => (false, Err(TendermintError::Malicious(proposer))), @@ -478,7 +478,7 @@ impl TendermintMachine { // being set, or only being set historically, means this has yet to be run if self.log.has_consensus(self.round, Data::Prevote(Some(block.id()))) { - match self.network.write().await.validate(block) { + match self.network.write().await.validate(block).await { Ok(_) => (), Err(BlockError::Temporal) => (), Err(BlockError::Fatal) => Err(TendermintError::Malicious(proposer))?, diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index 6122c9390..9361d8da9 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -109,7 +109,7 @@ impl Network for TestNetwork { todo!() } - fn validate(&mut self, block: &TestBlock) -> Result<(), BlockError> { + async fn validate(&mut self, block: &TestBlock) -> Result<(), BlockError> { block.valid } From 39984bd07b007f2ab4e1c0d88b483f8b2e512823 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 22 Oct 2022 02:15:22 -0400 Subject: [PATCH 049/186] Split import_queue into a series of files --- substrate/consensus/src/block_import.rs | 58 +++ substrate/consensus/src/import_queue.rs | 464 +----------------- .../consensus/src/justification_import.rs | 46 ++ substrate/consensus/src/lib.rs | 30 +- substrate/consensus/src/tendermint.rs | 360 ++++++++++++++ substrate/consensus/src/verifier.rs | 34 ++ 6 files changed, 521 insertions(+), 471 deletions(-) create mode 100644 substrate/consensus/src/block_import.rs create mode 100644 substrate/consensus/src/justification_import.rs create mode 100644 substrate/consensus/src/tendermint.rs create mode 100644 substrate/consensus/src/verifier.rs diff --git a/substrate/consensus/src/block_import.rs b/substrate/consensus/src/block_import.rs new file mode 100644 index 000000000..9672f2ceb --- /dev/null +++ b/substrate/consensus/src/block_import.rs @@ -0,0 +1,58 @@ +use std::collections::HashMap; + +use async_trait::async_trait; + +use sp_inherents::CreateInherentDataProviders; +use sp_runtime::traits::Block; +use sp_blockchain::HeaderBackend; +use sp_api::{TransactionFor, ProvideRuntimeApi}; + +use sp_consensus::{Error, CacheKeyId, Environment}; +use sc_consensus::{BlockCheckParams, BlockImportParams, ImportResult, BlockImport}; + +use sc_client_api::{Backend, Finalizer}; + +use crate::tendermint::TendermintImport; + +#[async_trait] +impl< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + E: Send + Sync + Environment + 'static, + > BlockImport for TendermintImport +where + I::Error: Into, + TransactionFor: Send + Sync + 'static, +{ + type Error = Error; + type Transaction = TransactionFor; + + // TODO: Is there a DoS where you send a block without justifications, causing it to error, + // yet adding it to the blacklist in the process preventing further syncing? + async fn check_block( + &mut self, + mut block: BlockCheckParams, + ) -> Result { + self.verify_order(block.parent_hash, block.number)?; + + // Does not verify origin here as origin only applies to unfinalized blocks + // We don't have context on if this block has justifications or not + + block.allow_missing_state = false; + block.allow_missing_parent = false; + + self.inner.write().await.check_block(block).await.map_err(Into::into) + } + + async fn import_block( + &mut self, + mut block: BlockImportParams>, + new_cache: HashMap>, + ) -> Result { + self.check(&mut block).await?; + self.inner.write().await.import_block(block, new_cache).await.map_err(Into::into) + } +} diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index 9b4f24eb8..0a8d433aa 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -1,386 +1,29 @@ -// The Tendermint machine will call add_block for any block which is committed to, regardless of -// validity. To determine validity, it expects a validate function, which Substrate doesn't -// directly offer, and an add function. In order to comply with Serai's modified view of inherent -// transactions, validate MUST check inherents, yet add_block must not. -// -// In order to acquire a validate function, any block proposed by a legitimate proposer is -// imported. This performs full validation and makes the block available as a tip. While this would -// be incredibly unsafe thanks to the unchecked inherents, it's defined as a tip with less work, -// despite being a child of some parent. This means it won't be moved to nor operated on by the -// node. -// -// When Tendermint completes, the block is finalized, setting it as the tip regardless of work. - use std::{ - marker::PhantomData, pin::Pin, sync::{Arc, RwLock}, task::{Poll, Context}, future::Future, - time::Duration, - collections::HashMap, }; -use async_trait::async_trait; - -use log::warn; +use tokio::runtime::Handle; -use tokio::{sync::RwLock as AsyncRwLock, runtime::Handle}; - -use sp_core::{Encode, Decode}; -use sp_application_crypto::sr25519::Signature; -use sp_inherents::{InherentData, InherentDataProvider, CreateInherentDataProviders}; -use sp_runtime::{ - traits::{Header, Block}, - Digest, Justification, -}; +use sp_inherents::CreateInherentDataProviders; +use sp_runtime::traits::{Header, Block}; use sp_blockchain::HeaderBackend; -use sp_api::{BlockId, TransactionFor, ProvideRuntimeApi}; +use sp_api::{TransactionFor, ProvideRuntimeApi}; -use sp_consensus::{Error, CacheKeyId, BlockOrigin, Proposer, Environment}; -#[rustfmt::skip] // rustfmt doesn't know how to handle this line -use sc_consensus::{ - ForkChoiceStrategy, - BlockCheckParams, - BlockImportParams, - Verifier, - ImportResult, - BlockImport, - JustificationImport, - import_queue::IncomingBlock, - BlockImportStatus, - BlockImportError, - Link, - BasicQueue, -}; +use sp_consensus::{Error, Environment}; +use sc_consensus::{BlockImport, BlockImportStatus, BlockImportError, Link, BasicQueue}; use sc_service::ImportQueue; use sc_client_api::{Backend, Finalizer}; use substrate_prometheus_endpoint::Registry; -use tendermint_machine::{ - ext::{BlockError, Commit, Network}, - SignedMessage, -}; - -use crate::{signature_scheme::TendermintSigner, weights::TendermintWeights}; - -const CONSENSUS_ID: [u8; 4] = *b"tend"; +use crate::tendermint::TendermintImport; pub type TendermintImportQueue = BasicQueue; -struct TendermintImport< - B: Block, - Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, -> { - _block: PhantomData, - _backend: PhantomData, - - importing_block: Arc>>, - - client: Arc, - inner: Arc>, - providers: Arc, - - env: Arc>, - queue: Arc>>>>, -} - -impl< - B: Block, - Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - > Clone for TendermintImport -{ - fn clone(&self) -> Self { - TendermintImport { - _block: PhantomData, - _backend: PhantomData, - - importing_block: self.importing_block.clone(), - - client: self.client.clone(), - inner: self.inner.clone(), - providers: self.providers.clone(), - - env: self.env.clone(), - queue: self.queue.clone(), - } - } -} - -impl< - B: Block, - Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - > TendermintImport -where - TransactionFor: Send + Sync + 'static, -{ - async fn check_inherents( - &self, - block: B, - providers: CIDP::InherentDataProviders, - ) -> Result<(), Error> { - // TODO - Ok(()) - } - - // Ensure this is part of a sequential import - fn verify_order( - &self, - parent: B::Hash, - number: ::Number, - ) -> Result<(), Error> { - let info = self.client.info(); - if (info.best_hash != parent) || ((info.best_number + 1u16.into()) != number) { - Err(Error::Other("non-sequential import".into()))?; - } - Ok(()) - } - - // Do not allow blocks from the traditional network to be broadcast - // Only allow blocks from Tendermint - // Tendermint's propose message could be rewritten as a seal OR Tendermint could produce blocks - // which this checks the proposer slot for, and then tells the Tendermint machine - // While those would be more seamless with Substrate, there's no actual benefit to doing so - fn verify_origin(&self, hash: B::Hash) -> Result<(), Error> { - if let Some(tm_hash) = *self.importing_block.read().unwrap() { - if hash == tm_hash { - return Ok(()); - } - } - Err(Error::Other("block created outside of tendermint".into())) - } - - // Errors if the justification isn't valid - fn verify_justification( - &self, - hash: B::Hash, - justification: &Justification, - ) -> Result<(), Error> { - if justification.0 != CONSENSUS_ID { - Err(Error::InvalidJustification)?; - } - - let commit: Commit = - Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; - if !self.verify_commit(hash, &commit) { - Err(Error::InvalidJustification)?; - } - Ok(()) - } - - // Verifies the justifications aren't malformed, not that the block is justified - // Errors if justifications is neither empty nor a sinlge Tendermint justification - // If the block does have a justification, finalized will be set to true - fn verify_justifications(&self, block: &mut BlockImportParams) -> Result<(), Error> { - if !block.finalized { - if let Some(justifications) = &block.justifications { - let mut iter = justifications.iter(); - let next = iter.next(); - if next.is_none() || iter.next().is_some() { - Err(Error::InvalidJustification)?; - } - self.verify_justification(block.header.hash(), next.unwrap())?; - - block.finalized = true; // TODO: Is this setting valid? - } - } - Ok(()) - } - - async fn check(&self, block: &mut BlockImportParams) -> Result<(), Error> { - if block.finalized { - if block.fork_choice.is_none() { - // Since we alw1ays set the fork choice, this means something else marked the block as - // finalized, which shouldn't be possible. Ensuring nothing else is setting blocks as - // finalized ensures our security - panic!("block was finalized despite not setting the fork choice"); - } - return Ok(()); - } - - // Set the block as a worse choice - block.fork_choice = Some(ForkChoiceStrategy::Custom(false)); - - self.verify_order(*block.header.parent_hash(), *block.header.number())?; - self.verify_justifications(block)?; - - // If the block wasn't finalized, verify the origin and validity of its inherents - if !block.finalized { - self.verify_origin(block.header.hash())?; - if let Some(body) = block.body.clone() { - self - .check_inherents( - B::new(block.header.clone(), body), - self.providers.create_inherent_data_providers(*block.header.parent_hash(), ()).await?, - ) - .await?; - } - } - - // Additionally check these fields are empty - // They *should* be unused, so requiring their emptiness prevents malleability and ensures - // nothing slips through - if !block.post_digests.is_empty() { - Err(Error::Other("post-digests included".into()))?; - } - if !block.auxiliary.is_empty() { - Err(Error::Other("auxiliary included".into()))?; - } - - Ok(()) - } - - async fn get_proposal(&mut self, block: &B) -> B { - let inherent_data = match self.providers.create_inherent_data_providers(block.hash(), ()).await - { - Ok(providers) => match providers.create_inherent_data() { - Ok(data) => Some(data), - Err(err) => { - warn!(target: "tendermint", "Failed to create inherent data: {}", err); - None - } - }, - Err(err) => { - warn!(target: "tendermint", "Failed to create inherent data providers: {}", err); - None - } - } - .unwrap_or_else(InherentData::new); - - let proposer = self - .env - .write() - .await - .init(block.header()) - .await - .expect("Failed to create a proposer for the new block"); - // TODO: Production time, size limit - proposer - .propose(inherent_data, Digest::default(), Duration::from_secs(1), None) - .await - .expect("Failed to crate a new block proposal") - .block - } - - fn import_justification_actual( - &mut self, - hash: B::Hash, - justification: Justification, - ) -> Result<(), Error> { - self.verify_justification(hash, &justification)?; - self - .client - .finalize_block(BlockId::Hash(hash), Some(justification), true) - .map_err(|_| Error::InvalidJustification) - } -} - -#[async_trait] -impl< - B: Block, - Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - > BlockImport for TendermintImport -where - I::Error: Into, - TransactionFor: Send + Sync + 'static, -{ - type Error = Error; - type Transaction = TransactionFor; - - // TODO: Is there a DoS where you send a block without justifications, causing it to error, - // yet adding it to the blacklist in the process preventing further syncing? - async fn check_block( - &mut self, - mut block: BlockCheckParams, - ) -> Result { - self.verify_order(block.parent_hash, block.number)?; - - // Does not verify origin here as origin only applies to unfinalized blocks - // We don't have context on if this block has justifications or not - - block.allow_missing_state = false; - block.allow_missing_parent = false; - - self.inner.write().await.check_block(block).await.map_err(Into::into) - } - - async fn import_block( - &mut self, - mut block: BlockImportParams>, - new_cache: HashMap>, - ) -> Result { - self.check(&mut block).await?; - self.inner.write().await.import_block(block, new_cache).await.map_err(Into::into) - } -} - -#[async_trait] -impl< - B: Block, - Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - > JustificationImport for TendermintImport -where - TransactionFor: Send + Sync + 'static, -{ - type Error = Error; - - async fn on_start(&mut self) -> Vec<(B::Hash, ::Number)> { - vec![] - } - - async fn import_justification( - &mut self, - hash: B::Hash, - _: ::Number, - justification: Justification, - ) -> Result<(), Error> { - self.import_justification_actual(hash, justification) - } -} - -#[async_trait] -impl< - B: Block, - Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - > Verifier for TendermintImport -where - TransactionFor: Send + Sync + 'static, -{ - async fn verify( - &mut self, - mut block: BlockImportParams, - ) -> Result<(BlockImportParams, Option)>>), String> { - self.check(&mut block).await.map_err(|e| format!("{}", e))?; - Ok((block, None)) - } -} - // Custom helpers for ImportQueue in order to obtain the result of a block's importing struct ValidateLink(Option<(B::Hash, bool)>); impl Link for ValidateLink { @@ -399,9 +42,15 @@ impl Link for ValidateLink { } } -struct ImportFuture<'a, B: Block, T: Send>(B::Hash, RwLock<&'a mut TendermintImportQueue>); +pub(crate) struct ImportFuture<'a, B: Block, T: Send>( + B::Hash, + RwLock<&'a mut TendermintImportQueue>, +); impl<'a, B: Block, T: Send> ImportFuture<'a, B, T> { - fn new(hash: B::Hash, queue: &'a mut TendermintImportQueue) -> ImportFuture { + pub(crate) fn new( + hash: B::Hash, + queue: &'a mut TendermintImportQueue, + ) -> ImportFuture { ImportFuture(hash, RwLock::new(queue)) } } @@ -421,75 +70,6 @@ impl<'a, B: Block, T: Send> Future for ImportFuture<'a, B, T> { } } -#[async_trait] -impl< - B: Block, - Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - > Network for TendermintImport -where - TransactionFor: Send + Sync + 'static, -{ - type ValidatorId = u16; - type SignatureScheme = TendermintSigner; - type Weights = TendermintWeights; - type Block = B; - - const BLOCK_TIME: u32 = { (serai_runtime::MILLISECS_PER_BLOCK / 1000) as u32 }; - - fn signature_scheme(&self) -> Arc { - Arc::new(TendermintSigner::new()) - } - - fn weights(&self) -> Arc { - Arc::new(TendermintWeights) - } - - async fn broadcast(&mut self, msg: SignedMessage) { - // TODO - } - - async fn slash(&mut self, validator: u16) { - todo!() - } - - async fn validate(&mut self, block: &B) -> Result<(), BlockError> { - let hash = block.hash(); - let (header, body) = block.clone().deconstruct(); - *self.importing_block.write().unwrap() = Some(hash); - self.queue.write().await.as_mut().unwrap().import_blocks( - BlockOrigin::NetworkBroadcast, - vec![IncomingBlock { - hash, - header: Some(header), - body: Some(body), - indexed_body: None, - justifications: None, - origin: None, - allow_missing_state: false, - skip_execution: false, - // TODO: Only set to true if block was rejected due to its inherents - import_existing: true, - state: None, - }], - ); - - if ImportFuture::new(hash, self.queue.write().await.as_mut().unwrap()).await { - Ok(()) - } else { - todo!() - } - } - - async fn add_block(&mut self, block: B, commit: Commit) -> B { - self.import_justification_actual(block.hash(), (CONSENSUS_ID, commit.encode())).unwrap(); - self.get_proposal(&block).await - } -} - pub fn import_queue< B: Block, Be: Backend + 'static, @@ -509,19 +89,7 @@ where I::Error: Into, TransactionFor: Send + Sync + 'static, { - let import = TendermintImport { - _block: PhantomData, - _backend: PhantomData, - - importing_block: Arc::new(RwLock::new(None)), - - client, - inner: Arc::new(AsyncRwLock::new(inner)), - providers, - - env: Arc::new(AsyncRwLock::new(env)), - queue: Arc::new(AsyncRwLock::new(None)), - }; + let import = TendermintImport::new(client, inner, providers, env); let boxed = Box::new(import.clone()); let queue = diff --git a/substrate/consensus/src/justification_import.rs b/substrate/consensus/src/justification_import.rs new file mode 100644 index 000000000..fb5b5b53c --- /dev/null +++ b/substrate/consensus/src/justification_import.rs @@ -0,0 +1,46 @@ +use async_trait::async_trait; + +use sp_inherents::CreateInherentDataProviders; +use sp_runtime::{ + traits::{Header, Block}, + Justification, +}; +use sp_blockchain::HeaderBackend; +use sp_api::{TransactionFor, ProvideRuntimeApi}; + +use sp_consensus::{Error, Environment}; +use sc_consensus::{BlockImport, JustificationImport, BasicQueue}; + +use sc_client_api::{Backend, Finalizer}; + +use crate::tendermint::TendermintImport; + +pub type TendermintImportQueue = BasicQueue; + +#[async_trait] +impl< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + E: Send + Sync + Environment + 'static, + > JustificationImport for TendermintImport +where + TransactionFor: Send + Sync + 'static, +{ + type Error = Error; + + async fn on_start(&mut self) -> Vec<(B::Hash, ::Number)> { + vec![] + } + + async fn import_justification( + &mut self, + hash: B::Hash, + _: ::Number, + justification: Justification, + ) -> Result<(), Error> { + self.import_justification_actual(hash, justification) + } +} diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index a912d29e5..4d7233c9e 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -14,12 +14,19 @@ use serai_runtime::{self, opaque::Block, RuntimeApi}; mod signature_scheme; mod weights; +mod tendermint; +mod block_import; +mod justification_import; +mod verifier; + mod import_queue; use import_queue::TendermintImportQueue; mod select_chain; pub use select_chain::TendermintSelectChain; +const CONSENSUS_ID: [u8; 4] = *b"tend"; + pub struct ExecutorDispatch; impl NativeExecutionDispatch for ExecutorDispatch { #[cfg(feature = "runtime-benchmarks")] @@ -72,29 +79,6 @@ pub fn authority( } /* -// Produce a block every 6 seconds -async fn produce< - Block: sp_api::BlockT, - Algorithm: sc_pow::PowAlgorithm + Send + Sync + 'static, - C: sp_api::ProvideRuntimeApi + 'static, - Link: sc_consensus::JustificationSyncLink + 'static, - P: Send + 'static, ->( - worker: sc_pow::MiningHandle, -) where - sp_api::TransactionFor: Send + 'static, -{ - loop { - let worker_clone = worker.clone(); - std::thread::spawn(move || { - tokio::runtime::Runtime::new().unwrap().handle().block_on(async { - worker_clone.submit(vec![]).await; - }); - }); - tokio::time::sleep(Duration::from_secs(6)).await; - } -} - // If we're an authority, produce blocks pub fn authority + 'static>( task_manager: &TaskManager, diff --git a/substrate/consensus/src/tendermint.rs b/substrate/consensus/src/tendermint.rs new file mode 100644 index 000000000..bc1d1d57e --- /dev/null +++ b/substrate/consensus/src/tendermint.rs @@ -0,0 +1,360 @@ +use std::{ + marker::PhantomData, + sync::{Arc, RwLock}, + time::Duration, +}; + +use async_trait::async_trait; + +use log::warn; + +use tokio::sync::RwLock as AsyncRwLock; + +use sp_core::{Encode, Decode}; +use sp_application_crypto::sr25519::Signature; +use sp_inherents::{InherentData, InherentDataProvider, CreateInherentDataProviders}; +use sp_runtime::{ + traits::{Header, Block}, + Digest, Justification, +}; +use sp_blockchain::HeaderBackend; +use sp_api::{BlockId, TransactionFor, ProvideRuntimeApi}; + +use sp_consensus::{Error, BlockOrigin, Proposer, Environment}; +use sc_consensus::{ForkChoiceStrategy, BlockImportParams, BlockImport, import_queue::IncomingBlock}; + +use sc_service::ImportQueue; +use sc_client_api::{Backend, Finalizer}; + +use tendermint_machine::{ + ext::{BlockError, Commit, Network}, + SignedMessage, +}; + +use crate::{ + CONSENSUS_ID, + signature_scheme::TendermintSigner, + weights::TendermintWeights, + import_queue::{ImportFuture, TendermintImportQueue}, +}; + +pub(crate) struct TendermintImport< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + E: Send + Sync + Environment + 'static, +> { + _block: PhantomData, + _backend: PhantomData, + + importing_block: Arc>>, + + client: Arc, + pub(crate) inner: Arc>, + providers: Arc, + + env: Arc>, + pub(crate) queue: Arc>>>>, +} + +impl< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + E: Send + Sync + Environment + 'static, + > Clone for TendermintImport +{ + fn clone(&self) -> Self { + TendermintImport { + _block: PhantomData, + _backend: PhantomData, + + importing_block: self.importing_block.clone(), + + client: self.client.clone(), + inner: self.inner.clone(), + providers: self.providers.clone(), + + env: self.env.clone(), + queue: self.queue.clone(), + } + } +} + +impl< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + E: Send + Sync + Environment + 'static, + > TendermintImport +where + TransactionFor: Send + Sync + 'static, +{ + pub(crate) fn new( + client: Arc, + inner: I, + providers: Arc, + env: E, + ) -> TendermintImport { + TendermintImport { + _block: PhantomData, + _backend: PhantomData, + + importing_block: Arc::new(RwLock::new(None)), + + client, + inner: Arc::new(AsyncRwLock::new(inner)), + providers, + + env: Arc::new(AsyncRwLock::new(env)), + queue: Arc::new(AsyncRwLock::new(None)), + } + } + + async fn check_inherents( + &self, + block: B, + providers: CIDP::InherentDataProviders, + ) -> Result<(), Error> { + // TODO + Ok(()) + } + + // Ensure this is part of a sequential import + pub(crate) fn verify_order( + &self, + parent: B::Hash, + number: ::Number, + ) -> Result<(), Error> { + let info = self.client.info(); + if (info.best_hash != parent) || ((info.best_number + 1u16.into()) != number) { + Err(Error::Other("non-sequential import".into()))?; + } + Ok(()) + } + + // Do not allow blocks from the traditional network to be broadcast + // Only allow blocks from Tendermint + // Tendermint's propose message could be rewritten as a seal OR Tendermint could produce blocks + // which this checks the proposer slot for, and then tells the Tendermint machine + // While those would be more seamless with Substrate, there's no actual benefit to doing so + fn verify_origin(&self, hash: B::Hash) -> Result<(), Error> { + if let Some(tm_hash) = *self.importing_block.read().unwrap() { + if hash == tm_hash { + return Ok(()); + } + } + Err(Error::Other("block created outside of tendermint".into())) + } + + // Errors if the justification isn't valid + fn verify_justification( + &self, + hash: B::Hash, + justification: &Justification, + ) -> Result<(), Error> { + if justification.0 != CONSENSUS_ID { + Err(Error::InvalidJustification)?; + } + + let commit: Commit = + Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; + if !self.verify_commit(hash, &commit) { + Err(Error::InvalidJustification)?; + } + Ok(()) + } + + // Verifies the justifications aren't malformed, not that the block is justified + // Errors if justifications is neither empty nor a sinlge Tendermint justification + // If the block does have a justification, finalized will be set to true + fn verify_justifications(&self, block: &mut BlockImportParams) -> Result<(), Error> { + if !block.finalized { + if let Some(justifications) = &block.justifications { + let mut iter = justifications.iter(); + let next = iter.next(); + if next.is_none() || iter.next().is_some() { + Err(Error::InvalidJustification)?; + } + self.verify_justification(block.header.hash(), next.unwrap())?; + + block.finalized = true; // TODO: Is this setting valid? + } + } + Ok(()) + } + + pub(crate) async fn check(&self, block: &mut BlockImportParams) -> Result<(), Error> { + if block.finalized { + if block.fork_choice.is_none() { + // Since we alw1ays set the fork choice, this means something else marked the block as + // finalized, which shouldn't be possible. Ensuring nothing else is setting blocks as + // finalized ensures our security + panic!("block was finalized despite not setting the fork choice"); + } + return Ok(()); + } + + // Set the block as a worse choice + block.fork_choice = Some(ForkChoiceStrategy::Custom(false)); + + self.verify_order(*block.header.parent_hash(), *block.header.number())?; + self.verify_justifications(block)?; + + // If the block wasn't finalized, verify the origin and validity of its inherents + if !block.finalized { + self.verify_origin(block.header.hash())?; + if let Some(body) = block.body.clone() { + self + .check_inherents( + B::new(block.header.clone(), body), + self.providers.create_inherent_data_providers(*block.header.parent_hash(), ()).await?, + ) + .await?; + } + } + + // Additionally check these fields are empty + // They *should* be unused, so requiring their emptiness prevents malleability and ensures + // nothing slips through + if !block.post_digests.is_empty() { + Err(Error::Other("post-digests included".into()))?; + } + if !block.auxiliary.is_empty() { + Err(Error::Other("auxiliary included".into()))?; + } + + Ok(()) + } + + async fn get_proposal(&mut self, block: &B) -> B { + let inherent_data = match self.providers.create_inherent_data_providers(block.hash(), ()).await + { + Ok(providers) => match providers.create_inherent_data() { + Ok(data) => Some(data), + Err(err) => { + warn!(target: "tendermint", "Failed to create inherent data: {}", err); + None + } + }, + Err(err) => { + warn!(target: "tendermint", "Failed to create inherent data providers: {}", err); + None + } + } + .unwrap_or_else(InherentData::new); + + let proposer = self + .env + .write() + .await + .init(block.header()) + .await + .expect("Failed to create a proposer for the new block"); + // TODO: Production time, size limit + proposer + .propose(inherent_data, Digest::default(), Duration::from_secs(1), None) + .await + .expect("Failed to crate a new block proposal") + .block + } + + pub(crate) fn import_justification_actual( + &mut self, + hash: B::Hash, + justification: Justification, + ) -> Result<(), Error> { + self.verify_justification(hash, &justification)?; + self + .client + .finalize_block(BlockId::Hash(hash), Some(justification), true) + .map_err(|_| Error::InvalidJustification) + } +} + +#[async_trait] +impl< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + E: Send + Sync + Environment + 'static, + > Network for TendermintImport +where + TransactionFor: Send + Sync + 'static, +{ + type ValidatorId = u16; + type SignatureScheme = TendermintSigner; + type Weights = TendermintWeights; + type Block = B; + + const BLOCK_TIME: u32 = { (serai_runtime::MILLISECS_PER_BLOCK / 1000) as u32 }; + + fn signature_scheme(&self) -> Arc { + Arc::new(TendermintSigner::new()) + } + + fn weights(&self) -> Arc { + Arc::new(TendermintWeights) + } + + async fn broadcast(&mut self, msg: SignedMessage) { + // TODO + } + + async fn slash(&mut self, validator: u16) { + todo!() + } + + // The Tendermint machine will call add_block for any block which is committed to, regardless of + // validity. To determine validity, it expects a validate function, which Substrate doesn't + // directly offer, and an add function. In order to comply with Serai's modified view of inherent + // transactions, validate MUST check inherents, yet add_block must not. + // + // In order to acquire a validate function, any block proposed by a legitimate proposer is + // imported. This performs full validation and makes the block available as a tip. While this + // would be incredibly unsafe thanks to the unchecked inherents, it's defined as a tip with less + // work, despite being a child of some parent. This means it won't be moved to nor operated on by + // the node. + // + // When Tendermint completes, the block is finalized, setting it as the tip regardless of work. + async fn validate(&mut self, block: &B) -> Result<(), BlockError> { + let hash = block.hash(); + let (header, body) = block.clone().deconstruct(); + *self.importing_block.write().unwrap() = Some(hash); + self.queue.write().await.as_mut().unwrap().import_blocks( + BlockOrigin::NetworkBroadcast, + vec![IncomingBlock { + hash, + header: Some(header), + body: Some(body), + indexed_body: None, + justifications: None, + origin: None, + allow_missing_state: false, + skip_execution: false, + // TODO: Only set to true if block was rejected due to its inherents + import_existing: true, + state: None, + }], + ); + + if ImportFuture::new(hash, self.queue.write().await.as_mut().unwrap()).await { + Ok(()) + } else { + todo!() + } + } + + async fn add_block(&mut self, block: B, commit: Commit) -> B { + self.import_justification_actual(block.hash(), (CONSENSUS_ID, commit.encode())).unwrap(); + self.get_proposal(&block).await + } +} diff --git a/substrate/consensus/src/verifier.rs b/substrate/consensus/src/verifier.rs new file mode 100644 index 000000000..f5427c3fe --- /dev/null +++ b/substrate/consensus/src/verifier.rs @@ -0,0 +1,34 @@ +use async_trait::async_trait; + +use sp_inherents::CreateInherentDataProviders; +use sp_runtime::traits::Block; +use sp_blockchain::HeaderBackend; +use sp_api::{TransactionFor, ProvideRuntimeApi}; + +use sp_consensus::{CacheKeyId, Environment}; +use sc_consensus::{BlockImportParams, Verifier, BlockImport}; + +use sc_client_api::{Backend, Finalizer}; + +use crate::tendermint::TendermintImport; + +#[async_trait] +impl< + B: Block, + Be: Backend + 'static, + C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport> + 'static, + CIDP: CreateInherentDataProviders + 'static, + E: Send + Sync + Environment + 'static, + > Verifier for TendermintImport +where + TransactionFor: Send + Sync + 'static, +{ + async fn verify( + &mut self, + mut block: BlockImportParams, + ) -> Result<(BlockImportParams, Option)>>), String> { + self.check(&mut block).await.map_err(|e| format!("{}", e))?; + Ok((block, None)) + } +} From 9b0dca06d037bfb0c102eaa4e579be8e283eaf9c Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 22 Oct 2022 03:41:49 -0400 Subject: [PATCH 050/186] Provide a way to create the machine The BasicQueue returned obscures the TendermintImport struct. Accordingly, a Future scoped with access is returned upwards, which when awaited will create the machine. This makes creating the machine optional while maintaining scope boundaries. Is sufficient to create a 1-node net which produces and finalizes blocks. --- Cargo.lock | 1 + substrate/consensus/Cargo.toml | 1 + substrate/consensus/src/import_queue.rs | 32 ++++++-- .../consensus/src/justification_import.rs | 4 +- substrate/consensus/src/lib.rs | 20 +---- substrate/consensus/src/tendermint.rs | 41 ++++++----- substrate/node/src/command.rs | 21 +++--- substrate/node/src/service.rs | 73 ++++++++++--------- 8 files changed, 104 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f87293fbd..346eb5d11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7387,6 +7387,7 @@ name = "serai-consensus" version = "0.1.0" dependencies = [ "async-trait", + "futures", "log", "sc-basic-authorship", "sc-client-api", diff --git a/substrate/consensus/Cargo.toml b/substrate/consensus/Cargo.toml index 0de507e44..e5e398cc3 100644 --- a/substrate/consensus/Cargo.toml +++ b/substrate/consensus/Cargo.toml @@ -17,6 +17,7 @@ async-trait = "0.1" log = "0.4" +futures = "0.3" tokio = { version = "1", features = ["sync", "rt"] } sp-core = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index 0a8d433aa..0313b3775 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -1,16 +1,15 @@ use std::{ pin::Pin, sync::{Arc, RwLock}, - task::{Poll, Context}, + task::{Poll, /* Wake, Waker, */ Context}, future::Future, + time::SystemTime, }; -use tokio::runtime::Handle; - use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::{Header, Block}; use sp_blockchain::HeaderBackend; -use sp_api::{TransactionFor, ProvideRuntimeApi}; +use sp_api::{BlockId, TransactionFor, ProvideRuntimeApi}; use sp_consensus::{Error, Environment}; use sc_consensus::{BlockImport, BlockImportStatus, BlockImportError, Link, BasicQueue}; @@ -20,6 +19,8 @@ use sc_client_api::{Backend, Finalizer}; use substrate_prometheus_endpoint::Registry; +use tendermint_machine::{ext::BlockNumber, TendermintMachine}; + use crate::tendermint::TendermintImport; pub type TendermintImportQueue = BasicQueue; @@ -84,16 +85,33 @@ pub fn import_queue< env: E, spawner: &impl sp_core::traits::SpawnEssentialNamed, registry: Option<&Registry>, -) -> TendermintImportQueue> +) -> (impl Future, TendermintImportQueue>) where I::Error: Into, TransactionFor: Send + Sync + 'static, { let import = TendermintImport::new(client, inner, providers, env); + + let authority = { + let machine_clone = import.machine.clone(); + let mut import_clone = import.clone(); + async move { + *machine_clone.write().unwrap() = Some(TendermintMachine::new( + import_clone.clone(), + // TODO + 0, + (BlockNumber(1), SystemTime::now()), + import_clone + .get_proposal(&import_clone.client.header(BlockId::Number(0u8.into())).unwrap().unwrap()) + .await, + )); + } + }; + let boxed = Box::new(import.clone()); let queue = || BasicQueue::new(import.clone(), boxed.clone(), Some(boxed.clone()), spawner, registry); - *Handle::current().block_on(import.queue.write()) = Some(queue()); - queue() + *futures::executor::block_on(import.queue.write()) = Some(queue()); + (authority, queue()) } diff --git a/substrate/consensus/src/justification_import.rs b/substrate/consensus/src/justification_import.rs index fb5b5b53c..7245029b8 100644 --- a/substrate/consensus/src/justification_import.rs +++ b/substrate/consensus/src/justification_import.rs @@ -9,14 +9,12 @@ use sp_blockchain::HeaderBackend; use sp_api::{TransactionFor, ProvideRuntimeApi}; use sp_consensus::{Error, Environment}; -use sc_consensus::{BlockImport, JustificationImport, BasicQueue}; +use sc_consensus::{BlockImport, JustificationImport}; use sc_client_api::{Backend, Finalizer}; use crate::tendermint::TendermintImport; -pub type TendermintImportQueue = BasicQueue; - #[async_trait] impl< B: Block, diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index 4d7233c9e..e5c31ec81 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -1,7 +1,6 @@ -use std::sync::Arc; +use std::{sync::Arc, future::Future}; use sp_api::TransactionFor; -use sp_consensus::Error; use sc_executor::{NativeVersion, NativeExecutionDispatch, NativeElseWasmExecutor}; use sc_transaction_pool::FullPool; @@ -50,8 +49,8 @@ pub fn import_queue( client: Arc, pool: Arc>, registry: Option<&Registry>, -) -> Result>, Error> { - Ok(import_queue::import_queue( +) -> (impl Future, TendermintImportQueue>) { + import_queue::import_queue( client.clone(), client.clone(), Arc::new(|_, _| async { Ok(sp_timestamp::InherentDataProvider::from_system_time()) }), @@ -64,18 +63,7 @@ pub fn import_queue( ), &task_manager.spawn_essential_handle(), registry, - )) -} - -// If we're an authority, produce blocks -pub fn authority( - task_manager: &TaskManager, - client: Arc, - network: Arc::Hash>>, - pool: Arc>, - registry: Option<&Registry>, -) { - todo!() + ) } /* diff --git a/substrate/consensus/src/tendermint.rs b/substrate/consensus/src/tendermint.rs index bc1d1d57e..cb88a77ac 100644 --- a/substrate/consensus/src/tendermint.rs +++ b/substrate/consensus/src/tendermint.rs @@ -28,7 +28,7 @@ use sc_client_api::{Backend, Finalizer}; use tendermint_machine::{ ext::{BlockError, Commit, Network}, - SignedMessage, + SignedMessage, TendermintHandle, }; use crate::{ @@ -45,13 +45,16 @@ pub(crate) struct TendermintImport< I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, -> { +> where + TransactionFor: Send + Sync + 'static, +{ _block: PhantomData, _backend: PhantomData, importing_block: Arc>>, + pub(crate) machine: Arc>>>, - client: Arc, + pub(crate) client: Arc, pub(crate) inner: Arc>, providers: Arc, @@ -67,6 +70,8 @@ impl< CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, > Clone for TendermintImport +where + TransactionFor: Send + Sync + 'static, { fn clone(&self) -> Self { TendermintImport { @@ -74,6 +79,7 @@ impl< _backend: PhantomData, importing_block: self.importing_block.clone(), + machine: self.machine.clone(), client: self.client.clone(), inner: self.inner.clone(), @@ -107,6 +113,7 @@ where _backend: PhantomData, importing_block: Arc::new(RwLock::new(None)), + machine: Arc::new(RwLock::new(None)), client, inner: Arc::new(AsyncRwLock::new(inner)), @@ -233,28 +240,28 @@ where Ok(()) } - async fn get_proposal(&mut self, block: &B) -> B { - let inherent_data = match self.providers.create_inherent_data_providers(block.hash(), ()).await - { - Ok(providers) => match providers.create_inherent_data() { - Ok(data) => Some(data), + pub(crate) async fn get_proposal(&mut self, header: &B::Header) -> B { + let inherent_data = + match self.providers.create_inherent_data_providers(header.hash(), ()).await { + Ok(providers) => match providers.create_inherent_data() { + Ok(data) => Some(data), + Err(err) => { + warn!(target: "tendermint", "Failed to create inherent data: {}", err); + None + } + }, Err(err) => { - warn!(target: "tendermint", "Failed to create inherent data: {}", err); + warn!(target: "tendermint", "Failed to create inherent data providers: {}", err); None } - }, - Err(err) => { - warn!(target: "tendermint", "Failed to create inherent data providers: {}", err); - None } - } - .unwrap_or_else(InherentData::new); + .unwrap_or_else(InherentData::new); let proposer = self .env .write() .await - .init(block.header()) + .init(header) .await .expect("Failed to create a proposer for the new block"); // TODO: Production time, size limit @@ -355,6 +362,6 @@ where async fn add_block(&mut self, block: B, commit: Commit) -> B { self.import_justification_actual(block.hash(), (CONSENSUS_ID, commit.encode())).unwrap(); - self.get_proposal(&block).await + self.get_proposal(block.header()).await } } diff --git a/substrate/node/src/command.rs b/substrate/node/src/command.rs index c087d817a..03fbc5331 100644 --- a/substrate/node/src/command.rs +++ b/substrate/node/src/command.rs @@ -60,23 +60,23 @@ pub fn run() -> sc_cli::Result<()> { Some(Subcommand::CheckBlock(cmd)) => cli.create_runner(cmd)?.async_run(|config| { let PartialComponents { client, task_manager, import_queue, .. } = - service::new_partial(&config)?; + service::new_partial(&config)?.1; Ok((cmd.run(client, import_queue), task_manager)) }), Some(Subcommand::ExportBlocks(cmd)) => cli.create_runner(cmd)?.async_run(|config| { - let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?; + let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?.1; Ok((cmd.run(client, config.database), task_manager)) }), Some(Subcommand::ExportState(cmd)) => cli.create_runner(cmd)?.async_run(|config| { - let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?; + let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?.1; Ok((cmd.run(client, config.chain_spec), task_manager)) }), Some(Subcommand::ImportBlocks(cmd)) => cli.create_runner(cmd)?.async_run(|config| { let PartialComponents { client, task_manager, import_queue, .. } = - service::new_partial(&config)?; + service::new_partial(&config)?.1; Ok((cmd.run(client, import_queue), task_manager)) }), @@ -85,14 +85,15 @@ pub fn run() -> sc_cli::Result<()> { } Some(Subcommand::Revert(cmd)) => cli.create_runner(cmd)?.async_run(|config| { - let PartialComponents { client, task_manager, backend, .. } = service::new_partial(&config)?; + let PartialComponents { client, task_manager, backend, .. } = + service::new_partial(&config)?.1; Ok((cmd.run(client, backend, None), task_manager)) }), Some(Subcommand::Benchmark(cmd)) => cli.create_runner(cmd)?.sync_run(|config| match cmd { BenchmarkCmd::Pallet(cmd) => cmd.run::(config), - BenchmarkCmd::Block(cmd) => cmd.run(service::new_partial(&config)?.client), + BenchmarkCmd::Block(cmd) => cmd.run(service::new_partial(&config)?.1.client), #[cfg(not(feature = "runtime-benchmarks"))] BenchmarkCmd::Storage(_) => { @@ -101,12 +102,12 @@ pub fn run() -> sc_cli::Result<()> { #[cfg(feature = "runtime-benchmarks")] BenchmarkCmd::Storage(cmd) => { - let PartialComponents { client, backend, .. } = service::new_partial(&config)?; + let PartialComponents { client, backend, .. } = service::new_partial(&config)?.1; cmd.run(config, client, backend.expose_db(), backend.expose_storage()) } BenchmarkCmd::Overhead(cmd) => { - let client = service::new_partial(&config)?.client; + let client = service::new_partial(&config)?.1.client; cmd.run( config, client.clone(), @@ -117,7 +118,7 @@ pub fn run() -> sc_cli::Result<()> { } BenchmarkCmd::Extrinsic(cmd) => { - let client = service::new_partial(&config)?.client; + let client = service::new_partial(&config)?.1.client; cmd.run( client.clone(), inherent_benchmark_data()?, @@ -134,7 +135,7 @@ pub fn run() -> sc_cli::Result<()> { } None => cli.create_runner(&cli.run)?.run_node_until_exit(|config| async { - service::new_full(config).map_err(sc_cli::Error::Service) + service::new_full(config).await.map_err(sc_cli::Error::Service) }), } } diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index b97ce8901..214ae0d60 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{sync::Arc, future::Future}; use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; use sc_executor::NativeElseWasmExecutor; @@ -19,7 +19,9 @@ type PartialComponents = sc_service::PartialComponents< Option, >; -pub fn new_partial(config: &Configuration) -> Result { +pub fn new_partial( + config: &Configuration, +) -> Result<(impl Future, PartialComponents), ServiceError> { if config.keystore_remote.is_some() { return Err(ServiceError::Other("Remote Keystores are not supported".to_string())); } @@ -63,38 +65,44 @@ pub fn new_partial(config: &Configuration) -> Result Result { - let sc_service::PartialComponents { - client, - backend, - mut task_manager, - import_queue, - keystore_container, - select_chain: _, - other: mut telemetry, - transaction_pool, - } = new_partial(&config)?; +pub async fn new_full(config: Configuration) -> Result { + let ( + authority, + sc_service::PartialComponents { + client, + backend, + mut task_manager, + import_queue, + keystore_container, + select_chain: _, + other: mut telemetry, + transaction_pool, + }, + ) = new_partial(&config)?; let (network, system_rpc_tx, tx_handler_controller, network_starter) = sc_service::build_network(sc_service::BuildNetworkParams { @@ -116,9 +124,6 @@ pub fn new_full(config: Configuration) -> Result { ); } - let role = config.role.clone(); - let prometheus_registry = config.prometheus_registry().cloned(); - let rpc_extensions_builder = { let client = client.clone(); let pool = transaction_pool.clone(); @@ -133,6 +138,8 @@ pub fn new_full(config: Configuration) -> Result { }) }; + let is_authority = config.role.is_authority(); + sc_service::spawn_tasks(sc_service::SpawnTasksParams { network: network.clone(), client: client.clone(), @@ -147,14 +154,8 @@ pub fn new_full(config: Configuration) -> Result { telemetry: telemetry.as_mut(), })?; - if role.is_authority() { - serai_consensus::authority( - &task_manager, - client, - network, - transaction_pool, - prometheus_registry.as_ref(), - ); + if is_authority { + authority.await; } network_starter.start_network(); From 4206ed3b183678468f5c3b58c6f07579fd2806f1 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 22 Oct 2022 04:39:27 -0400 Subject: [PATCH 051/186] Don't import justifications multiple times Also don't broadcast blocks which were solely proposed. --- .../consensus/src/justification_import.rs | 4 +-- substrate/consensus/src/tendermint.rs | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/substrate/consensus/src/justification_import.rs b/substrate/consensus/src/justification_import.rs index 7245029b8..03c52ea2e 100644 --- a/substrate/consensus/src/justification_import.rs +++ b/substrate/consensus/src/justification_import.rs @@ -36,9 +36,9 @@ where async fn import_justification( &mut self, hash: B::Hash, - _: ::Number, + number: ::Number, justification: Justification, ) -> Result<(), Error> { - self.import_justification_actual(hash, justification) + self.import_justification_actual(number, hash, justification) } } diff --git a/substrate/consensus/src/tendermint.rs b/substrate/consensus/src/tendermint.rs index cb88a77ac..e98696114 100644 --- a/substrate/consensus/src/tendermint.rs +++ b/substrate/consensus/src/tendermint.rs @@ -1,6 +1,7 @@ use std::{ marker::PhantomData, sync::{Arc, RwLock}, + cmp::Ordering, time::Duration, }; @@ -274,9 +275,23 @@ where pub(crate) fn import_justification_actual( &mut self, + number: ::Number, hash: B::Hash, justification: Justification, ) -> Result<(), Error> { + let info = self.client.info(); + match info.best_number.cmp(&number) { + Ordering::Greater => return Ok(()), + Ordering::Equal => { + if info.best_hash == hash { + return Ok(()); + } else { + Err(Error::InvalidJustification)? + } + } + Ordering::Less => (), + } + self.verify_justification(hash, &justification)?; self .client @@ -337,7 +352,10 @@ where let (header, body) = block.clone().deconstruct(); *self.importing_block.write().unwrap() = Some(hash); self.queue.write().await.as_mut().unwrap().import_blocks( - BlockOrigin::NetworkBroadcast, + // We do not want this block, which hasn't been confirmed, to be broadcast over the net + // Substrate will generate notifications unless it's Genesis, which this isn't, InitialSync, + // which changes telemtry behavior, or File, which is... close enough + BlockOrigin::File, vec![IncomingBlock { hash, header: Some(header), @@ -361,7 +379,13 @@ where } async fn add_block(&mut self, block: B, commit: Commit) -> B { - self.import_justification_actual(block.hash(), (CONSENSUS_ID, commit.encode())).unwrap(); + self + .import_justification_actual( + *block.header().number(), + block.hash(), + (CONSENSUS_ID, commit.encode()), + ) + .unwrap(); self.get_proposal(block.header()).await } } From dee6993ac83e85d0d2926dfe95b5ffb5843c534c Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 22 Oct 2022 06:24:39 -0400 Subject: [PATCH 052/186] Correct justication import pipeline Removes JustificationImport as it should never be used. --- substrate/consensus/src/import_queue.rs | 5 ++- .../consensus/src/justification_import.rs | 44 ------------------- substrate/consensus/src/lib.rs | 1 - substrate/consensus/src/tendermint.rs | 44 +++++-------------- 4 files changed, 13 insertions(+), 81 deletions(-) delete mode 100644 substrate/consensus/src/justification_import.rs diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index 0313b3775..fe17b7e30 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -109,9 +109,10 @@ where }; let boxed = Box::new(import.clone()); + // Use None for the justification importer since justifications always come with blocks + // Therefore, they're never imported after the fact, mandating a importer + let queue = || BasicQueue::new(import.clone(), boxed.clone(), None, spawner, registry); - let queue = - || BasicQueue::new(import.clone(), boxed.clone(), Some(boxed.clone()), spawner, registry); *futures::executor::block_on(import.queue.write()) = Some(queue()); (authority, queue()) } diff --git a/substrate/consensus/src/justification_import.rs b/substrate/consensus/src/justification_import.rs deleted file mode 100644 index 03c52ea2e..000000000 --- a/substrate/consensus/src/justification_import.rs +++ /dev/null @@ -1,44 +0,0 @@ -use async_trait::async_trait; - -use sp_inherents::CreateInherentDataProviders; -use sp_runtime::{ - traits::{Header, Block}, - Justification, -}; -use sp_blockchain::HeaderBackend; -use sp_api::{TransactionFor, ProvideRuntimeApi}; - -use sp_consensus::{Error, Environment}; -use sc_consensus::{BlockImport, JustificationImport}; - -use sc_client_api::{Backend, Finalizer}; - -use crate::tendermint::TendermintImport; - -#[async_trait] -impl< - B: Block, - Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - > JustificationImport for TendermintImport -where - TransactionFor: Send + Sync + 'static, -{ - type Error = Error; - - async fn on_start(&mut self) -> Vec<(B::Hash, ::Number)> { - vec![] - } - - async fn import_justification( - &mut self, - hash: B::Hash, - number: ::Number, - justification: Justification, - ) -> Result<(), Error> { - self.import_justification_actual(number, hash, justification) - } -} diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index e5c31ec81..a97a91dc8 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -15,7 +15,6 @@ mod weights; mod tendermint; mod block_import; -mod justification_import; mod verifier; mod import_queue; diff --git a/substrate/consensus/src/tendermint.rs b/substrate/consensus/src/tendermint.rs index e98696114..809bb8538 100644 --- a/substrate/consensus/src/tendermint.rs +++ b/substrate/consensus/src/tendermint.rs @@ -1,7 +1,6 @@ use std::{ marker::PhantomData, sync::{Arc, RwLock}, - cmp::Ordering, time::Duration, }; @@ -162,7 +161,7 @@ where } // Errors if the justification isn't valid - fn verify_justification( + pub(crate) fn verify_justification( &self, hash: B::Hash, justification: &Justification, @@ -192,7 +191,7 @@ where } self.verify_justification(block.header.hash(), next.unwrap())?; - block.finalized = true; // TODO: Is this setting valid? + block.finalized = true; } } Ok(()) @@ -272,32 +271,6 @@ where .expect("Failed to crate a new block proposal") .block } - - pub(crate) fn import_justification_actual( - &mut self, - number: ::Number, - hash: B::Hash, - justification: Justification, - ) -> Result<(), Error> { - let info = self.client.info(); - match info.best_number.cmp(&number) { - Ordering::Greater => return Ok(()), - Ordering::Equal => { - if info.best_hash == hash { - return Ok(()); - } else { - Err(Error::InvalidJustification)? - } - } - Ordering::Less => (), - } - - self.verify_justification(hash, &justification)?; - self - .client - .finalize_block(BlockId::Hash(hash), Some(justification), true) - .map_err(|_| Error::InvalidJustification) - } } #[async_trait] @@ -379,13 +352,16 @@ where } async fn add_block(&mut self, block: B, commit: Commit) -> B { + let hash = block.hash(); + let justification = (CONSENSUS_ID, commit.encode()); + debug_assert!(self.verify_justification(hash, &justification).is_ok()); + self - .import_justification_actual( - *block.header().number(), - block.hash(), - (CONSENSUS_ID, commit.encode()), - ) + .client + .finalize_block(BlockId::Hash(hash), Some(justification), true) + .map_err(|_| Error::InvalidJustification) .unwrap(); + self.get_proposal(block.header()).await } } From 8a682cd25c43d48095d3dad4357c46107a770378 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 22 Oct 2022 07:36:13 -0400 Subject: [PATCH 053/186] Announce blocks By claiming File, they're not sent ovber the P2P network before they have a justification, as desired. Unfortunately, they never were. This works around that. --- Cargo.lock | 1 + substrate/consensus/src/block_import.rs | 5 ++-- substrate/consensus/src/import_queue.rs | 6 +++-- substrate/consensus/src/lib.rs | 9 ++++++- substrate/consensus/src/tendermint.rs | 18 +++++++++++--- substrate/consensus/src/verifier.rs | 5 ++-- substrate/node/Cargo.toml | 11 +++++---- substrate/node/src/service.rs | 33 ++++++++++++++++++++----- 8 files changed, 66 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 346eb5d11..dea472532 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7450,6 +7450,7 @@ dependencies = [ "sc-consensus", "sc-executor", "sc-keystore", + "sc-network", "sc-rpc", "sc-rpc-api", "sc-service", diff --git a/substrate/consensus/src/block_import.rs b/substrate/consensus/src/block_import.rs index 9672f2ceb..0ae477d7b 100644 --- a/substrate/consensus/src/block_import.rs +++ b/substrate/consensus/src/block_import.rs @@ -12,7 +12,7 @@ use sc_consensus::{BlockCheckParams, BlockImportParams, ImportResult, BlockImpor use sc_client_api::{Backend, Finalizer}; -use crate::tendermint::TendermintImport; +use crate::{tendermint::TendermintImport, Announce}; #[async_trait] impl< @@ -22,7 +22,8 @@ impl< I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, - > BlockImport for TendermintImport + A: Announce, + > BlockImport for TendermintImport where I::Error: Into, TransactionFor: Send + Sync + 'static, diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index fe17b7e30..20139c7d2 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -21,7 +21,7 @@ use substrate_prometheus_endpoint::Registry; use tendermint_machine::{ext::BlockNumber, TendermintMachine}; -use crate::tendermint::TendermintImport; +use crate::{tendermint::TendermintImport, Announce}; pub type TendermintImportQueue = BasicQueue; @@ -78,9 +78,11 @@ pub fn import_queue< I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, + A: Announce, >( client: Arc, inner: I, + announce: A, providers: Arc, env: E, spawner: &impl sp_core::traits::SpawnEssentialNamed, @@ -90,7 +92,7 @@ where I::Error: Into, TransactionFor: Send + Sync + 'static, { - let import = TendermintImport::new(client, inner, providers, env); + let import = TendermintImport::new(client, inner, announce, providers, env); let authority = { let machine_clone = import.machine.clone(); diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index a97a91dc8..57c1d27af 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -1,5 +1,6 @@ use std::{sync::Arc, future::Future}; +use sp_runtime::traits::Block as BlockTrait; use sp_api::TransactionFor; use sc_executor::{NativeVersion, NativeExecutionDispatch, NativeElseWasmExecutor}; @@ -43,15 +44,21 @@ impl NativeExecutionDispatch for ExecutorDispatch { pub type FullClient = TFullClient>; -pub fn import_queue( +pub trait Announce: Send + Sync + Clone + 'static { + fn announce(&self, hash: B::Hash); +} + +pub fn import_queue>( task_manager: &TaskManager, client: Arc, + announce: A, pool: Arc>, registry: Option<&Registry>, ) -> (impl Future, TendermintImportQueue>) { import_queue::import_queue( client.clone(), client.clone(), + announce, Arc::new(|_, _| async { Ok(sp_timestamp::InherentDataProvider::from_system_time()) }), sc_basic_authorship::ProposerFactory::new( task_manager.spawn_handle(), diff --git a/substrate/consensus/src/tendermint.rs b/substrate/consensus/src/tendermint.rs index 809bb8538..037af3270 100644 --- a/substrate/consensus/src/tendermint.rs +++ b/substrate/consensus/src/tendermint.rs @@ -36,6 +36,7 @@ use crate::{ signature_scheme::TendermintSigner, weights::TendermintWeights, import_queue::{ImportFuture, TendermintImportQueue}, + Announce, }; pub(crate) struct TendermintImport< @@ -45,6 +46,7 @@ pub(crate) struct TendermintImport< I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, + A: Announce, > where TransactionFor: Send + Sync + 'static, { @@ -56,6 +58,7 @@ pub(crate) struct TendermintImport< pub(crate) client: Arc, pub(crate) inner: Arc>, + announce: A, providers: Arc, env: Arc>, @@ -69,7 +72,8 @@ impl< I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, - > Clone for TendermintImport + A: Announce, + > Clone for TendermintImport where TransactionFor: Send + Sync + 'static, { @@ -83,6 +87,7 @@ where client: self.client.clone(), inner: self.inner.clone(), + announce: self.announce.clone(), providers: self.providers.clone(), env: self.env.clone(), @@ -98,16 +103,18 @@ impl< I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, - > TendermintImport + A: Announce, + > TendermintImport where TransactionFor: Send + Sync + 'static, { pub(crate) fn new( client: Arc, inner: I, + announce: A, providers: Arc, env: E, - ) -> TendermintImport { + ) -> TendermintImport { TendermintImport { _block: PhantomData, _backend: PhantomData, @@ -117,6 +124,7 @@ where client, inner: Arc::new(AsyncRwLock::new(inner)), + announce, providers, env: Arc::new(AsyncRwLock::new(env)), @@ -281,7 +289,8 @@ impl< I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, - > Network for TendermintImport + A: Announce, + > Network for TendermintImport where TransactionFor: Send + Sync + 'static, { @@ -361,6 +370,7 @@ where .finalize_block(BlockId::Hash(hash), Some(justification), true) .map_err(|_| Error::InvalidJustification) .unwrap(); + self.announce.announce(hash); self.get_proposal(block.header()).await } diff --git a/substrate/consensus/src/verifier.rs b/substrate/consensus/src/verifier.rs index f5427c3fe..4dae0fc0f 100644 --- a/substrate/consensus/src/verifier.rs +++ b/substrate/consensus/src/verifier.rs @@ -10,7 +10,7 @@ use sc_consensus::{BlockImportParams, Verifier, BlockImport}; use sc_client_api::{Backend, Finalizer}; -use crate::tendermint::TendermintImport; +use crate::{tendermint::TendermintImport, Announce}; #[async_trait] impl< @@ -20,7 +20,8 @@ impl< I: Send + Sync + BlockImport> + 'static, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, - > Verifier for TendermintImport + A: Announce, + > Verifier for TendermintImport where TransactionFor: Send + Sync + 'static, { diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index a25473e74..262e6c202 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -24,15 +24,16 @@ sp-api = { git = "https://github.com/serai-dex/substrate" } sp-blockchain = { git = "https://github.com/serai-dex/substrate" } sp-block-builder = { git = "https://github.com/serai-dex/substrate" } -sc-cli = { git = "https://github.com/serai-dex/substrate" } -sc-executor = { git = "https://github.com/serai-dex/substrate" } -sc-service = { git = "https://github.com/serai-dex/substrate" } -sc-telemetry = { git = "https://github.com/serai-dex/substrate" } sc-keystore = { git = "https://github.com/serai-dex/substrate" } sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" } sc-transaction-pool-api = { git = "https://github.com/serai-dex/substrate" } -sc-consensus = { git = "https://github.com/serai-dex/substrate" } +sc-executor = { git = "https://github.com/serai-dex/substrate" } +sc-service = { git = "https://github.com/serai-dex/substrate" } sc-client-api = { git = "https://github.com/serai-dex/substrate" } +sc-network = { git = "https://github.com/serai-dex/substrate" } +sc-consensus = { git = "https://github.com/serai-dex/substrate" } +sc-telemetry = { git = "https://github.com/serai-dex/substrate" } +sc-cli = { git = "https://github.com/serai-dex/substrate" } frame-system = { git = "https://github.com/serai-dex/substrate" } frame-benchmarking = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 214ae0d60..b4f402a6b 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -1,11 +1,14 @@ -use std::{sync::Arc, future::Future}; +use std::{sync::{Arc, RwLock}, future::Future}; + +use sp_core::H256; -use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; use sc_executor::NativeElseWasmExecutor; +use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; +use sc_network::{NetworkService, NetworkBlock}; use sc_telemetry::{Telemetry, TelemetryWorker}; use serai_runtime::{self, opaque::Block, RuntimeApi}; -pub(crate) use serai_consensus::{ExecutorDispatch, FullClient}; +pub(crate) use serai_consensus::{ExecutorDispatch, Announce, FullClient}; type FullBackend = sc_service::TFullBackend; type FullSelectChain = serai_consensus::TendermintSelectChain; @@ -19,9 +22,24 @@ type PartialComponents = sc_service::PartialComponents< Option, >; +#[derive(Clone)] +pub struct NetworkAnnounce(Arc>>>>); +impl NetworkAnnounce { + fn new() -> NetworkAnnounce { + NetworkAnnounce(Arc::new(RwLock::new(None))) + } +} +impl Announce for NetworkAnnounce { + fn announce(&self, hash: H256) { + if let Some(network) = self.0.read().unwrap().as_ref() { + network.announce_block(hash, None); + } + } +} + pub fn new_partial( config: &Configuration, -) -> Result<(impl Future, PartialComponents), ServiceError> { +) -> Result<((NetworkAnnounce, impl Future), PartialComponents), ServiceError> { if config.keystore_remote.is_some() { return Err(ServiceError::Other("Remote Keystores are not supported".to_string())); } @@ -65,9 +83,11 @@ pub fn new_partial( client.clone(), ); + let announce = NetworkAnnounce::new(); let (authority, import_queue) = serai_consensus::import_queue( &task_manager, client.clone(), + announce.clone(), transaction_pool.clone(), config.prometheus_registry(), ); @@ -75,7 +95,7 @@ pub fn new_partial( let select_chain = serai_consensus::TendermintSelectChain::new(backend.clone()); Ok(( - authority, + (announce, authority), sc_service::PartialComponents { client, backend, @@ -91,7 +111,7 @@ pub fn new_partial( pub async fn new_full(config: Configuration) -> Result { let ( - authority, + (announce, authority), sc_service::PartialComponents { client, backend, @@ -114,6 +134,7 @@ pub async fn new_full(config: Configuration) -> Result Date: Sat, 22 Oct 2022 07:57:30 -0400 Subject: [PATCH 054/186] Add an assert to verify proposed children aren't best --- substrate/consensus/src/tendermint.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/substrate/consensus/src/tendermint.rs b/substrate/consensus/src/tendermint.rs index 037af3270..711a9cfa0 100644 --- a/substrate/consensus/src/tendermint.rs +++ b/substrate/consensus/src/tendermint.rs @@ -198,7 +198,6 @@ where Err(Error::InvalidJustification)?; } self.verify_justification(block.header.hash(), next.unwrap())?; - block.finalized = true; } } @@ -332,6 +331,7 @@ where async fn validate(&mut self, block: &B) -> Result<(), BlockError> { let hash = block.hash(); let (header, body) = block.clone().deconstruct(); + let parent = *header.parent_hash(); *self.importing_block.write().unwrap() = Some(hash); self.queue.write().await.as_mut().unwrap().import_blocks( // We do not want this block, which hasn't been confirmed, to be broadcast over the net @@ -353,11 +353,12 @@ where }], ); - if ImportFuture::new(hash, self.queue.write().await.as_mut().unwrap()).await { - Ok(()) - } else { + if !ImportFuture::new(hash, self.queue.write().await.as_mut().unwrap()).await { todo!() } + assert_eq!(self.client.info().best_hash, parent); + + Ok(()) } async fn add_block(&mut self, block: B, commit: Commit) -> B { From b6dddc469f0a0b7501002f1d89d6dc730e73e009 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 24 Oct 2022 04:43:25 -0400 Subject: [PATCH 055/186] Consolidate C and I generics into a TendermintClient trait alias --- substrate/consensus/src/block_import.rs | 24 +++++++------ substrate/consensus/src/import_queue.rs | 23 ++++++------ substrate/consensus/src/lib.rs | 1 - substrate/consensus/src/tendermint.rs | 48 ++++++++++++++++--------- substrate/consensus/src/verifier.rs | 23 +++++++----- 5 files changed, 71 insertions(+), 48 deletions(-) diff --git a/substrate/consensus/src/block_import.rs b/substrate/consensus/src/block_import.rs index 0ae477d7b..69d088b63 100644 --- a/substrate/consensus/src/block_import.rs +++ b/substrate/consensus/src/block_import.rs @@ -1,32 +1,34 @@ -use std::collections::HashMap; +use std::{sync::Arc, collections::HashMap}; use async_trait::async_trait; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::Block; -use sp_blockchain::HeaderBackend; -use sp_api::{TransactionFor, ProvideRuntimeApi}; +use sp_api::TransactionFor; use sp_consensus::{Error, CacheKeyId, Environment}; use sc_consensus::{BlockCheckParams, BlockImportParams, ImportResult, BlockImport}; -use sc_client_api::{Backend, Finalizer}; +use sc_client_api::Backend; -use crate::{tendermint::TendermintImport, Announce}; +use crate::{ + tendermint::{TendermintClient, TendermintImport}, + Announce, +}; #[async_trait] impl< B: Block, Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, + C: TendermintClient, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, A: Announce, - > BlockImport for TendermintImport + > BlockImport for TendermintImport where - I::Error: Into, TransactionFor: Send + Sync + 'static, + Arc: BlockImport>, + as BlockImport>::Error: Into, { type Error = Error; type Transaction = TransactionFor; @@ -45,7 +47,7 @@ where block.allow_missing_state = false; block.allow_missing_parent = false; - self.inner.write().await.check_block(block).await.map_err(Into::into) + self.client.check_block(block).await.map_err(Into::into) } async fn import_block( @@ -54,6 +56,6 @@ where new_cache: HashMap>, ) -> Result { self.check(&mut block).await?; - self.inner.write().await.import_block(block, new_cache).await.map_err(Into::into) + self.client.import_block(block, new_cache).await.map_err(Into::into) } } diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index 20139c7d2..64cefaa72 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -1,27 +1,29 @@ use std::{ pin::Pin, sync::{Arc, RwLock}, - task::{Poll, /* Wake, Waker, */ Context}, + task::{Poll, Context}, future::Future, time::SystemTime, }; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::{Header, Block}; -use sp_blockchain::HeaderBackend; -use sp_api::{BlockId, TransactionFor, ProvideRuntimeApi}; +use sp_api::{BlockId, TransactionFor}; use sp_consensus::{Error, Environment}; -use sc_consensus::{BlockImport, BlockImportStatus, BlockImportError, Link, BasicQueue}; +use sc_consensus::{BlockImportStatus, BlockImportError, BlockImport, Link, BasicQueue}; use sc_service::ImportQueue; -use sc_client_api::{Backend, Finalizer}; +use sc_client_api::Backend; use substrate_prometheus_endpoint::Registry; use tendermint_machine::{ext::BlockNumber, TendermintMachine}; -use crate::{tendermint::TendermintImport, Announce}; +use crate::{ + tendermint::{TendermintClient, TendermintImport}, + Announce, +}; pub type TendermintImportQueue = BasicQueue; @@ -74,14 +76,12 @@ impl<'a, B: Block, T: Send> Future for ImportFuture<'a, B, T> { pub fn import_queue< B: Block, Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, + C: TendermintClient, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, A: Announce, >( client: Arc, - inner: I, announce: A, providers: Arc, env: E, @@ -89,10 +89,11 @@ pub fn import_queue< registry: Option<&Registry>, ) -> (impl Future, TendermintImportQueue>) where - I::Error: Into, TransactionFor: Send + Sync + 'static, + Arc: BlockImport>, + as BlockImport>::Error: Into, { - let import = TendermintImport::new(client, inner, announce, providers, env); + let import = TendermintImport::new(client, announce, providers, env); let authority = { let machine_clone = import.machine.clone(); diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index 57c1d27af..99c1f2c86 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -56,7 +56,6 @@ pub fn import_queue>( registry: Option<&Registry>, ) -> (impl Future, TendermintImportQueue>) { import_queue::import_queue( - client.clone(), client.clone(), announce, Arc::new(|_, _| async { Ok(sp_timestamp::InherentDataProvider::from_system_time()) }), diff --git a/substrate/consensus/src/tendermint.rs b/substrate/consensus/src/tendermint.rs index 711a9cfa0..4193a2551 100644 --- a/substrate/consensus/src/tendermint.rs +++ b/substrate/consensus/src/tendermint.rs @@ -39,11 +39,34 @@ use crate::{ Announce, }; +pub trait TendermintClient + 'static>: + Send + + Sync + + HeaderBackend + + BlockImport> + + Finalizer + + ProvideRuntimeApi + + 'static +{ +} +impl< + B: Send + Sync + Block + 'static, + Be: Send + Sync + Backend + 'static, + C: Send + + Sync + + HeaderBackend + + BlockImport> + + Finalizer + + ProvideRuntimeApi + + 'static, + > TendermintClient for C +{ +} + pub(crate) struct TendermintImport< B: Block, Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, + C: TendermintClient, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, A: Announce, @@ -57,7 +80,6 @@ pub(crate) struct TendermintImport< pub(crate) machine: Arc>>>, pub(crate) client: Arc, - pub(crate) inner: Arc>, announce: A, providers: Arc, @@ -68,12 +90,11 @@ pub(crate) struct TendermintImport< impl< B: Block, Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, + C: TendermintClient, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, A: Announce, - > Clone for TendermintImport + > Clone for TendermintImport where TransactionFor: Send + Sync + 'static, { @@ -86,7 +107,6 @@ where machine: self.machine.clone(), client: self.client.clone(), - inner: self.inner.clone(), announce: self.announce.clone(), providers: self.providers.clone(), @@ -99,22 +119,20 @@ where impl< B: Block, Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, + C: TendermintClient, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, A: Announce, - > TendermintImport + > TendermintImport where TransactionFor: Send + Sync + 'static, { pub(crate) fn new( client: Arc, - inner: I, announce: A, providers: Arc, env: E, - ) -> TendermintImport { + ) -> TendermintImport { TendermintImport { _block: PhantomData, _backend: PhantomData, @@ -123,7 +141,6 @@ where machine: Arc::new(RwLock::new(None)), client, - inner: Arc::new(AsyncRwLock::new(inner)), announce, providers, @@ -284,12 +301,11 @@ where impl< B: Block, Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, + C: TendermintClient, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, A: Announce, - > Network for TendermintImport + > Network for TendermintImport where TransactionFor: Send + Sync + 'static, { diff --git a/substrate/consensus/src/verifier.rs b/substrate/consensus/src/verifier.rs index 4dae0fc0f..d2379196b 100644 --- a/substrate/consensus/src/verifier.rs +++ b/substrate/consensus/src/verifier.rs @@ -1,29 +1,34 @@ +use std::sync::Arc; + use async_trait::async_trait; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::Block; -use sp_blockchain::HeaderBackend; -use sp_api::{TransactionFor, ProvideRuntimeApi}; +use sp_api::TransactionFor; -use sp_consensus::{CacheKeyId, Environment}; -use sc_consensus::{BlockImportParams, Verifier, BlockImport}; +use sp_consensus::{Error, CacheKeyId, Environment}; +use sc_consensus::{BlockImportParams, BlockImport, Verifier}; -use sc_client_api::{Backend, Finalizer}; +use sc_client_api::Backend; -use crate::{tendermint::TendermintImport, Announce}; +use crate::{ + tendermint::{TendermintClient, TendermintImport}, + Announce, +}; #[async_trait] impl< B: Block, Be: Backend + 'static, - C: Send + Sync + HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, - I: Send + Sync + BlockImport> + 'static, + C: TendermintClient, CIDP: CreateInherentDataProviders + 'static, E: Send + Sync + Environment + 'static, A: Announce, - > Verifier for TendermintImport + > Verifier for TendermintImport where TransactionFor: Send + Sync + 'static, + Arc: BlockImport>, + as BlockImport>::Error: Into, { async fn verify( &mut self, From 78fa292230a363e4eef19e10c7d677f3022f0567 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 24 Oct 2022 04:43:59 -0400 Subject: [PATCH 056/186] Expand sanity checks Substrate doesn't expect nor officially support children with less work than their parents. It's a trick used here. Accordingly, ensure the trick's validity. --- substrate/consensus/src/tendermint.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/substrate/consensus/src/tendermint.rs b/substrate/consensus/src/tendermint.rs index 4193a2551..80bf68ee8 100644 --- a/substrate/consensus/src/tendermint.rs +++ b/substrate/consensus/src/tendermint.rs @@ -348,6 +348,7 @@ where let hash = block.hash(); let (header, body) = block.clone().deconstruct(); let parent = *header.parent_hash(); + let number = *header.number(); *self.importing_block.write().unwrap() = Some(hash); self.queue.write().await.as_mut().unwrap().import_blocks( // We do not want this block, which hasn't been confirmed, to be broadcast over the net @@ -372,7 +373,15 @@ where if !ImportFuture::new(hash, self.queue.write().await.as_mut().unwrap()).await { todo!() } - assert_eq!(self.client.info().best_hash, parent); + + // Sanity checks that a child block can have less work than its parent + { + let info = self.client.info(); + assert_eq!(info.best_hash, parent); + assert_eq!(info.finalized_hash, parent); + assert_eq!(info.best_number, number - 1); + assert_eq!(info.finalized_number, number - 1); + } Ok(()) } From b9c091c5d007a58bc93653b476be3185e145c7d9 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 24 Oct 2022 04:48:17 -0400 Subject: [PATCH 057/186] When resetting, use the end time of the round which was committed to The machine reset to the end time of the current round. For a delayed network connection, a machine may move ahead in rounds and only later realize a prior round succeeded. Despite acknowledging that round's success, it would maintain its delay when moving to the next block, bricking it. Done by tracking the end time for each round as they occur. --- substrate/tendermint/src/lib.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 56dea0c30..f9b9de562 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -105,6 +105,7 @@ pub struct TendermintMachine { log: MessageLog, round: Round, + end_time: HashMap, step: Step, locked: Option<(Round, ::Id)>, @@ -169,8 +170,10 @@ impl TendermintMachine { fn round(&mut self, round: Round) -> bool { // Correct the start time - for _ in self.round.0 .. round.0 { - self.start_time = self.timeout(Step::Precommit); + for r in self.round.0 .. round.0 { + let end = self.timeout(Step::Precommit); + self.end_time.insert(Round(r), end); + self.start_time = end; } // 11-13 @@ -178,15 +181,16 @@ impl TendermintMachine { self.timeouts = HashMap::new(); self.round = round; + self.end_time.insert(round, self.timeout(Step::Precommit)); self.step = Step::Propose; self.round_propose() } // 53-54 - async fn reset(&mut self, proposal: N::Block) { + async fn reset(&mut self, end_round: Round, proposal: N::Block) { // Wait for the next block interval - let round_end = self.timeout(Step::Precommit); - sleep(round_end - Instant::now()).await; + let round_end = self.end_time[&end_round]; + sleep(round_end.saturating_duration_since(Instant::now())).await; self.number.0 += 1; self.start_time = round_end; @@ -195,6 +199,7 @@ impl TendermintMachine { self.queue = self.queue.drain(..).filter(|msg| msg.1.number == self.number).collect(); self.log = MessageLog::new(self.network.read().await.weights()); + self.end_time = HashMap::new(); self.locked = None; self.valid = None; @@ -241,6 +246,7 @@ impl TendermintMachine { log: MessageLog::new(weights), round: Round(0), + end_time: HashMap::new(), step: Step::Propose, locked: None, @@ -319,7 +325,7 @@ impl TendermintMachine { debug_assert!(machine.network.read().await.verify_commit(block.id(), &commit)); let proposal = machine.network.write().await.add_block(block, commit).await; - machine.reset(proposal).await; + machine.reset(msg.round, proposal).await; } Err(TendermintError::Malicious(validator)) => { machine.network.write().await.slash(validator).await; From a7f4804749605ef2a362d8af9d6d88f1a5582d03 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 24 Oct 2022 05:28:21 -0400 Subject: [PATCH 058/186] Move Commit from including the round to including the round's end_time The round was usable to build the current clock in an accumulated fashion, relative to the previous round. The end time is the absolute metric of it, which can be used to calculate the round number (with all previous end times). Substrate now builds off the best block, not genesis, using the end time included in the justification to start its machine in a synchronized state. Knowing the end time of a round, or the round in which block was committed to, is necessary for nodes to sync up with Tendermint. Encoding it in the commit ensures it's long lasting and makes it readily available, without the load of an entire transaction. --- substrate/consensus/src/import_queue.rs | 30 +++++++++++++++-- substrate/consensus/src/tendermint.rs | 8 +++-- substrate/tendermint/src/ext.rs | 6 ++-- substrate/tendermint/src/lib.rs | 44 +++++++++++++++++++------ 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index 64cefaa72..bf9b2cebb 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -3,9 +3,9 @@ use std::{ sync::{Arc, RwLock}, task::{Poll, Context}, future::Future, - time::SystemTime, }; +use sp_core::Decode; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::{Header, Block}; use sp_api::{BlockId, TransactionFor}; @@ -18,9 +18,14 @@ use sc_client_api::Backend; use substrate_prometheus_endpoint::Registry; -use tendermint_machine::{ext::BlockNumber, TendermintMachine}; +use tendermint_machine::{ + ext::{BlockNumber, Commit}, + TendermintMachine, +}; use crate::{ + CONSENSUS_ID, + signature_scheme::TendermintSigner, tendermint::{TendermintClient, TendermintImport}, Announce, }; @@ -98,12 +103,31 @@ where let authority = { let machine_clone = import.machine.clone(); let mut import_clone = import.clone(); + let best = import.client.info().best_number; async move { *machine_clone.write().unwrap() = Some(TendermintMachine::new( import_clone.clone(), // TODO 0, - (BlockNumber(1), SystemTime::now()), + ( + // Header::Number: TryInto doesn't implement Debug and can't be unwrapped + match best.try_into() { + Ok(best) => BlockNumber(best), + Err(_) => panic!("BlockNumber exceeded u64"), + }, + Commit::::decode( + &mut import_clone + .client + .justifications(&BlockId::Number(best)) + .unwrap() + .unwrap() + .get(CONSENSUS_ID) + .unwrap() + .as_ref(), + ) + .unwrap() + .end_time, + ), import_clone .get_proposal(&import_clone.client.header(BlockId::Number(0u8.into())).unwrap().unwrap()) .await, diff --git a/substrate/consensus/src/tendermint.rs b/substrate/consensus/src/tendermint.rs index 80bf68ee8..ec6a8d6c5 100644 --- a/substrate/consensus/src/tendermint.rs +++ b/substrate/consensus/src/tendermint.rs @@ -24,7 +24,7 @@ use sp_consensus::{Error, BlockOrigin, Proposer, Environment}; use sc_consensus::{ForkChoiceStrategy, BlockImportParams, BlockImport, import_queue::IncomingBlock}; use sc_service::ImportQueue; -use sc_client_api::{Backend, Finalizer}; +use sc_client_api::{BlockBackend, Backend, Finalizer}; use tendermint_machine::{ ext::{BlockError, Commit, Network}, @@ -43,6 +43,7 @@ pub trait TendermintClient + 'static>: Send + Sync + HeaderBackend + + BlockBackend + BlockImport> + Finalizer + ProvideRuntimeApi @@ -55,6 +56,7 @@ impl< C: Send + Sync + HeaderBackend + + BlockBackend + BlockImport> + Finalizer + ProvideRuntimeApi @@ -379,8 +381,8 @@ where let info = self.client.info(); assert_eq!(info.best_hash, parent); assert_eq!(info.finalized_hash, parent); - assert_eq!(info.best_number, number - 1); - assert_eq!(info.finalized_number, number - 1); + assert_eq!(info.best_number, number - 1u8.into()); + assert_eq!(info.finalized_number, number - 1u8.into()); } Ok(()) diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/src/ext.rs index 96536e127..6becaa7a5 100644 --- a/substrate/tendermint/src/ext.rs +++ b/substrate/tendermint/src/ext.rs @@ -64,8 +64,8 @@ pub trait SignatureScheme: Send + Sync { /// a valid commit. #[derive(Clone, PartialEq, Debug, Encode, Decode)] pub struct Commit { - /// Round which created this commit. - pub round: Round, + /// End time of the round, used as the start time of next round. + pub end_time: u64, /// Validators participating in the signature. pub validators: Vec, /// Aggregate signature. @@ -151,7 +151,7 @@ pub trait Network: Send + Sync { ) -> bool { if !self.signature_scheme().verify_aggregate( &commit.validators, - &commit_msg(commit.round, id.as_ref()), + &commit_msg(commit.end_time, id.as_ref()), &commit.signature, ) { return false; diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index f9b9de562..4a6a3884d 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -2,7 +2,7 @@ use core::fmt::Debug; use std::{ sync::Arc, - time::{SystemTime, Instant, Duration}, + time::{UNIX_EPOCH, SystemTime, Instant, Duration}, collections::HashMap, }; @@ -24,8 +24,8 @@ use ext::*; mod message_log; use message_log::MessageLog; -pub(crate) fn commit_msg(round: Round, id: &[u8]) -> Vec { - [&round.0.to_le_bytes(), id].concat().to_vec() +pub(crate) fn commit_msg(end_time: u64, id: &[u8]) -> Vec { + [&end_time.to_le_bytes(), id].concat().to_vec() } #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)] @@ -95,6 +95,7 @@ pub struct TendermintMachine { proposer: N::ValidatorId, number: BlockNumber, + canonical_start_time: u64, start_time: Instant, personal_proposal: N::Block, @@ -126,9 +127,21 @@ pub struct TendermintHandle { } impl TendermintMachine { + // Get the canonical end time for a given round, represented as seconds since the epoch + // While we have the Instant already in end_time, converting it to a SystemTime would be lossy, + // potentially enough to cause a consensus failure. Independently tracking this variable ensures + // that won't happen + fn canonical_end_time(&self, round: Round) -> u64 { + let mut time = self.canonical_start_time; + for r in 0 .. u64::from(round.0 + 1) { + time += (r + 1) * u64::from(N::BLOCK_TIME); + } + time + } + fn timeout(&self, step: Step) -> Instant { let mut round_time = Duration::from_secs(N::BLOCK_TIME.into()); - round_time *= self.round.0.wrapping_add(1); + round_time *= self.round.0 + 1; let step_time = round_time / 3; let offset = match step { @@ -193,6 +206,7 @@ impl TendermintMachine { sleep(round_end.saturating_duration_since(Instant::now())).await; self.number.0 += 1; + self.canonical_start_time = self.canonical_end_time(end_round); self.start_time = round_end; self.personal_proposal = proposal; @@ -213,7 +227,7 @@ impl TendermintMachine { pub fn new( network: N, proposer: N::ValidatorId, - start: (BlockNumber, SystemTime), + start: (BlockNumber, u64), proposal: N::Block, ) -> TendermintHandle { // Convert the start time to an instant @@ -221,7 +235,10 @@ impl TendermintMachine { let start_time = { let instant_now = Instant::now(); let sys_now = SystemTime::now(); - instant_now - sys_now.duration_since(start.1).unwrap_or(Duration::ZERO) + instant_now - + sys_now + .duration_since(UNIX_EPOCH + Duration::from_secs(start.1)) + .unwrap_or(Duration::ZERO) }; let (msg_send, mut msg_recv) = mpsc::channel(100); // Backlog to accept. Currently arbitrary @@ -239,6 +256,7 @@ impl TendermintMachine { proposer, number: start.0, + canonical_start_time: start.1, start_time, personal_proposal: proposal, @@ -318,7 +336,7 @@ impl TendermintMachine { } let commit = Commit { - round: msg.round, + end_time: machine.canonical_end_time(msg.round), validators, signature: N::SignatureScheme::aggregate(&sigs), }; @@ -349,9 +367,13 @@ impl TendermintMachine { &mut self, msg: Message::Signature>, ) -> Result, TendermintError> { - // Verify the signature if this is a precommit + // Verify the end time and signature if this is a precommit if let Data::Precommit(Some((id, sig))) = &msg.data { - if !self.signer.verify(msg.sender, &commit_msg(msg.round, id.as_ref()), sig) { + if !self.signer.verify( + msg.sender, + &commit_msg(self.canonical_end_time(msg.round), id.as_ref()), + sig, + ) { // Since we verified this validator actually sent the message, they're malicious Err(TendermintError::Malicious(msg.sender))?; } @@ -495,7 +517,9 @@ impl TendermintMachine { self.locked = Some((self.round, block.id())); self.broadcast(Data::Precommit(Some(( block.id(), - self.signer.sign(&commit_msg(self.round, block.id().as_ref())), + self + .signer + .sign(&commit_msg(self.canonical_end_time(self.round), block.id().as_ref())), )))); return Ok(None); } From b5cb8a9be2402f6261ac9512a5adc9cf660b8023 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 24 Oct 2022 05:37:43 -0400 Subject: [PATCH 059/186] Add a TODO on Tendermint --- substrate/consensus/src/block_import.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/substrate/consensus/src/block_import.rs b/substrate/consensus/src/block_import.rs index 69d088b63..c5a9a2854 100644 --- a/substrate/consensus/src/block_import.rs +++ b/substrate/consensus/src/block_import.rs @@ -57,5 +57,8 @@ where ) -> Result { self.check(&mut block).await?; self.client.import_block(block, new_cache).await.map_err(Into::into) + + // TODO: If we're a validator who just successfully synced a block, recreate the tendermint + // machine with the new height } } From 05be5c14c360545271b82518787749e5cf2467bf Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 24 Oct 2022 05:52:40 -0400 Subject: [PATCH 060/186] Misc bug fixes --- substrate/consensus/src/import_queue.rs | 10 +++++----- substrate/node/src/service.rs | 5 ++++- substrate/tendermint/src/lib.rs | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index bf9b2cebb..a6a2b154b 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -120,13 +120,13 @@ where .client .justifications(&BlockId::Number(best)) .unwrap() - .unwrap() - .get(CONSENSUS_ID) - .unwrap() + .map(|justifications| justifications.get(CONSENSUS_ID).cloned().unwrap()) + .unwrap_or_default() .as_ref(), ) - .unwrap() - .end_time, + .map(|commit| commit.end_time) + // TODO: Genesis start time + .unwrap_or(0), ), import_clone .get_proposal(&import_clone.client.header(BlockId::Number(0u8.into())).unwrap().unwrap()) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index b4f402a6b..0feba0756 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -1,4 +1,7 @@ -use std::{sync::{Arc, RwLock}, future::Future}; +use std::{ + sync::{Arc, RwLock}, + future::Future, +}; use sp_core::H256; diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 4a6a3884d..0a1ac8a85 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -272,7 +272,7 @@ impl TendermintMachine { timeouts: HashMap::new(), }; - machine.round_propose(); + machine.round(Round(0)); loop { // Check if any timeouts have been triggered From 9b8f2f4487253cb2778bb0041015ff1cdf58f1d0 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 24 Oct 2022 06:18:16 -0400 Subject: [PATCH 061/186] More misc bug fixes --- substrate/consensus/src/import_queue.rs | 3 ++- substrate/tendermint/src/lib.rs | 8 ++++---- substrate/tendermint/tests/ext.rs | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index a6a2b154b..ef74bc6cb 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -3,6 +3,7 @@ use std::{ sync::{Arc, RwLock}, task::{Poll, Context}, future::Future, + time::{UNIX_EPOCH, SystemTime}, }; use sp_core::Decode; @@ -126,7 +127,7 @@ where ) .map(|commit| commit.end_time) // TODO: Genesis start time - .unwrap_or(0), + .unwrap_or_else(|_| SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()), ), import_clone .get_proposal(&import_clone.client.header(BlockId::Number(0u8.into())).unwrap().unwrap()) diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/src/lib.rs index 0a1ac8a85..657b03703 100644 --- a/substrate/tendermint/src/lib.rs +++ b/substrate/tendermint/src/lib.rs @@ -367,6 +367,10 @@ impl TendermintMachine { &mut self, msg: Message::Signature>, ) -> Result, TendermintError> { + if msg.number != self.number { + Err(TendermintError::Temporal)?; + } + // Verify the end time and signature if this is a precommit if let Data::Precommit(Some((id, sig))) = &msg.data { if !self.signer.verify( @@ -379,10 +383,6 @@ impl TendermintMachine { } } - if msg.number != self.number { - Err(TendermintError::Temporal)?; - } - // Only let the proposer propose if matches!(msg.data, Data::Proposal(..)) && (msg.sender != self.weights.proposer(msg.number, msg.round)) diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/tests/ext.rs index 9361d8da9..dc9f3ed5a 100644 --- a/substrate/tendermint/tests/ext.rs +++ b/substrate/tendermint/tests/ext.rs @@ -1,6 +1,6 @@ use std::{ sync::Arc, - time::{SystemTime, Duration}, + time::{UNIX_EPOCH, SystemTime, Duration}, }; use parity_scale_codec::{Encode, Decode}; @@ -104,7 +104,7 @@ impl Network for TestNetwork { } } - async fn slash(&mut self, validator: TestValidatorId) { + async fn slash(&mut self, _: TestValidatorId) { dbg!("Slash"); todo!() } @@ -135,7 +135,7 @@ impl TestNetwork { write.push(TendermintMachine::new( TestNetwork(i, arc.clone()), i, - (BlockNumber(1), SystemTime::now()), + (BlockNumber(1), (SystemTime::now().duration_since(UNIX_EPOCH)).unwrap().as_secs()), TestBlock { id: 1u32.to_le_bytes(), valid: Ok(()) }, )); } From 5839f442901e6e48c02da20a784a999be75e4a8c Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 25 Oct 2022 02:45:20 -0400 Subject: [PATCH 062/186] Clean up lock acquisition --- substrate/consensus/src/tendermint.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/substrate/consensus/src/tendermint.rs b/substrate/consensus/src/tendermint.rs index ec6a8d6c5..077be1a5e 100644 --- a/substrate/consensus/src/tendermint.rs +++ b/substrate/consensus/src/tendermint.rs @@ -351,8 +351,11 @@ where let (header, body) = block.clone().deconstruct(); let parent = *header.parent_hash(); let number = *header.number(); + + let mut queue_write = self.queue.write().await; *self.importing_block.write().unwrap() = Some(hash); - self.queue.write().await.as_mut().unwrap().import_blocks( + + queue_write.as_mut().unwrap().import_blocks( // We do not want this block, which hasn't been confirmed, to be broadcast over the net // Substrate will generate notifications unless it's Genesis, which this isn't, InitialSync, // which changes telemtry behavior, or File, which is... close enough @@ -372,7 +375,7 @@ where }], ); - if !ImportFuture::new(hash, self.queue.write().await.as_mut().unwrap()).await { + if !ImportFuture::new(hash, queue_write.as_mut().unwrap()).await { todo!() } From 285152b6e2a395756df5d2c4bc219e81f70c2863 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 25 Oct 2022 02:51:33 -0400 Subject: [PATCH 063/186] Merge weights and signing scheme into validators, documenting needed changes --- substrate/consensus/src/import_queue.rs | 4 +-- substrate/consensus/src/lib.rs | 3 +- substrate/consensus/src/tendermint.rs | 19 +++++----- .../{signature_scheme.rs => validators.rs} | 36 ++++++++++++++----- substrate/consensus/src/weights.rs | 22 ------------ 5 files changed, 39 insertions(+), 45 deletions(-) rename substrate/consensus/src/{signature_scheme.rs => validators.rs} (50%) delete mode 100644 substrate/consensus/src/weights.rs diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index ef74bc6cb..4a5a30317 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -26,7 +26,7 @@ use tendermint_machine::{ use crate::{ CONSENSUS_ID, - signature_scheme::TendermintSigner, + validators::TendermintValidators, tendermint::{TendermintClient, TendermintImport}, Announce, }; @@ -116,7 +116,7 @@ where Ok(best) => BlockNumber(best), Err(_) => panic!("BlockNumber exceeded u64"), }, - Commit::::decode( + Commit::::decode( &mut import_clone .client .justifications(&BlockId::Number(best)) diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index 99c1f2c86..5c6413b47 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -11,8 +11,7 @@ use substrate_prometheus_endpoint::Registry; use serai_runtime::{self, opaque::Block, RuntimeApi}; -mod signature_scheme; -mod weights; +mod validators; mod tendermint; mod block_import; diff --git a/substrate/consensus/src/tendermint.rs b/substrate/consensus/src/tendermint.rs index 077be1a5e..2b92c0044 100644 --- a/substrate/consensus/src/tendermint.rs +++ b/substrate/consensus/src/tendermint.rs @@ -33,8 +33,7 @@ use tendermint_machine::{ use crate::{ CONSENSUS_ID, - signature_scheme::TendermintSigner, - weights::TendermintWeights, + validators::TendermintValidators, import_queue::{ImportFuture, TendermintImportQueue}, Announce, }; @@ -197,7 +196,7 @@ where Err(Error::InvalidJustification)?; } - let commit: Commit = + let commit: Commit = Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; if !self.verify_commit(hash, &commit) { Err(Error::InvalidJustification)?; @@ -312,18 +311,18 @@ where TransactionFor: Send + Sync + 'static, { type ValidatorId = u16; - type SignatureScheme = TendermintSigner; - type Weights = TendermintWeights; + type SignatureScheme = TendermintValidators; + type Weights = TendermintValidators; type Block = B; const BLOCK_TIME: u32 = { (serai_runtime::MILLISECS_PER_BLOCK / 1000) as u32 }; - fn signature_scheme(&self) -> Arc { - Arc::new(TendermintSigner::new()) + fn signature_scheme(&self) -> Arc { + Arc::new(TendermintValidators::new()) } - fn weights(&self) -> Arc { - Arc::new(TendermintWeights) + fn weights(&self) -> Arc { + Arc::new(TendermintValidators::new()) } async fn broadcast(&mut self, msg: SignedMessage) { @@ -391,7 +390,7 @@ where Ok(()) } - async fn add_block(&mut self, block: B, commit: Commit) -> B { + async fn add_block(&mut self, block: B, commit: Commit) -> B { let hash = block.hash(); let justification = (CONSENSUS_ID, commit.encode()); debug_assert!(self.verify_justification(hash, &justification).is_ok()); diff --git a/substrate/consensus/src/signature_scheme.rs b/substrate/consensus/src/validators.rs similarity index 50% rename from substrate/consensus/src/signature_scheme.rs rename to substrate/consensus/src/validators.rs index bf552f366..d537ffd50 100644 --- a/substrate/consensus/src/signature_scheme.rs +++ b/substrate/consensus/src/validators.rs @@ -1,24 +1,27 @@ +// TODO: This should be built around pallet_sessions (and pallet_staking?). + use sp_application_crypto::{ RuntimePublic as PublicTrait, Pair as PairTrait, sr25519::{Public, Pair, Signature}, }; -use tendermint_machine::ext::SignatureScheme; +use tendermint_machine::ext::{BlockNumber, Round, Weights, SignatureScheme}; + +const VALIDATORS: usize = 1; -pub(crate) struct TendermintSigner { - keys: Pair, - lookup: Vec, +pub(crate) struct TendermintValidators { + keys: Pair, // sp_keystore + lookup: Vec, // sessions } -impl TendermintSigner { - pub(crate) fn new() -> TendermintSigner { - // TODO +impl TendermintValidators { + pub(crate) fn new() -> TendermintValidators { let keys = Pair::from_string("//Alice", None).unwrap(); - TendermintSigner { lookup: vec![keys.public()], keys } + TendermintValidators { lookup: vec![keys.public()], keys } } } -impl SignatureScheme for TendermintSigner { +impl SignatureScheme for TendermintValidators { type ValidatorId = u16; type Signature = Signature; type AggregateSignature = Vec; @@ -47,3 +50,18 @@ impl SignatureScheme for TendermintSigner { true } } + +impl Weights for TendermintValidators { + type ValidatorId = u16; + + fn total_weight(&self) -> u64 { + VALIDATORS.try_into().unwrap() + } + fn weight(&self, id: u16) -> u64 { + [1; VALIDATORS][usize::try_from(id).unwrap()] + } + + fn proposer(&self, number: BlockNumber, round: Round) -> u16 { + u16::try_from((number.0 + u64::from(round.0)) % u64::try_from(VALIDATORS).unwrap()).unwrap() + } +} diff --git a/substrate/consensus/src/weights.rs b/substrate/consensus/src/weights.rs deleted file mode 100644 index f6c801e18..000000000 --- a/substrate/consensus/src/weights.rs +++ /dev/null @@ -1,22 +0,0 @@ -// TODO - -use tendermint_machine::ext::{BlockNumber, Round, Weights}; - -const VALIDATORS: usize = 1; - -// TODO: Move to sp_session -pub(crate) struct TendermintWeights; -impl Weights for TendermintWeights { - type ValidatorId = u16; - - fn total_weight(&self) -> u64 { - VALIDATORS.try_into().unwrap() - } - fn weight(&self, id: u16) -> u64 { - [1; VALIDATORS][usize::try_from(id).unwrap()] - } - - fn proposer(&self, number: BlockNumber, round: Round) -> u16 { - u16::try_from((number.0 + u64::from(round.0)) % u64::try_from(VALIDATORS).unwrap()).unwrap() - } -} From 49ab26209df495070119016763455436e9cfd960 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 27 Oct 2022 05:05:41 -0400 Subject: [PATCH 064/186] Add pallet sessions to runtime, create pallet-tendermint --- Cargo.lock | 38 +++++++++++++++- Cargo.toml | 5 ++- substrate/consensus/Cargo.toml | 3 ++ substrate/pallet-tendermint/Cargo.toml | 37 ++++++++++++++++ substrate/pallet-tendermint/src/lib.rs | 60 ++++++++++++++++++++++++++ substrate/runtime/Cargo.toml | 12 +++++- substrate/runtime/src/lib.rs | 54 +++++++++++++++-------- 7 files changed, 188 insertions(+), 21 deletions(-) create mode 100644 substrate/pallet-tendermint/Cargo.toml create mode 100644 substrate/pallet-tendermint/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index dea472532..adc085207 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5085,6 +5085,38 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-session" +version = "4.0.0-dev" +source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-tendermint" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", +] + [[package]] name = "pallet-timestamp" version = "4.0.0-dev" @@ -7389,6 +7421,7 @@ dependencies = [ "async-trait", "futures", "log", + "pallet-session", "sc-basic-authorship", "sc-client-api", "sc-consensus", @@ -7404,6 +7437,7 @@ dependencies = [ "sp-core", "sp-inherents", "sp-runtime", + "sp-staking", "sp-timestamp", "substrate-prometheus-endpoint", "tendermint-machine", @@ -7505,18 +7539,20 @@ dependencies = [ "pallet-contracts", "pallet-contracts-primitives", "pallet-randomness-collective-flip", + "pallet-session", + "pallet-tendermint", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "scale-info", "sp-api", + "sp-application-crypto", "sp-block-builder", "sp-core", "sp-inherents", "sp-offchain", "sp-runtime", - "sp-session", "sp-std", "sp-transaction-pool", "sp-version", diff --git a/Cargo.toml b/Cargo.toml index c5721b0d1..0ceceae61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,11 @@ members = [ "processor", - "substrate/runtime", "substrate/tendermint", + + "substrate/pallet-tendermint", + "substrate/runtime", + "substrate/consensus", "substrate/node", diff --git a/substrate/consensus/Cargo.toml b/substrate/consensus/Cargo.toml index e5e398cc3..06bde9f14 100644 --- a/substrate/consensus/Cargo.toml +++ b/substrate/consensus/Cargo.toml @@ -24,6 +24,7 @@ sp-core = { git = "https://github.com/serai-dex/substrate" } sp-application-crypto = { git = "https://github.com/serai-dex/substrate" } sp-inherents = { git = "https://github.com/serai-dex/substrate" } sp-timestamp = { git = "https://github.com/serai-dex/substrate" } +sp-staking = { git = "https://github.com/serai-dex/substrate" } sp-blockchain = { git = "https://github.com/serai-dex/substrate" } sp-runtime = { git = "https://github.com/serai-dex/substrate" } sp-api = { git = "https://github.com/serai-dex/substrate" } @@ -37,6 +38,8 @@ sc-service = { git = "https://github.com/serai-dex/substrate" } sc-client-api = { git = "https://github.com/serai-dex/substrate" } sc-consensus = { git = "https://github.com/serai-dex/substrate" } +pallet-session = { git = "https://github.com/serai-dex/substrate" } + substrate-prometheus-endpoint = { git = "https://github.com/serai-dex/substrate" } tendermint-machine = { path = "../tendermint", features = ["substrate"] } diff --git a/substrate/pallet-tendermint/Cargo.toml b/substrate/pallet-tendermint/Cargo.toml new file mode 100644 index 000000000..6b3228c87 --- /dev/null +++ b/substrate/pallet-tendermint/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "pallet-tendermint" +version = "0.1.0" +description = "Tendermint pallet for Substrate" +license = "AGPL-3.0-only" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/pallet-tendermint" +authors = ["Luke Parker "] +edition = "2021" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] + +parity-scale-codec = { version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"] } + +sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false } + +frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false } +frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } + +[features] +std = [ + "sp-application-crypto/std", + + "frame-system/std", + "frame-support/std", +] + +runtime-benchmarks = [ + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", +] + +default = ["std"] diff --git a/substrate/pallet-tendermint/src/lib.rs b/substrate/pallet-tendermint/src/lib.rs new file mode 100644 index 000000000..f5a1b91ce --- /dev/null +++ b/substrate/pallet-tendermint/src/lib.rs @@ -0,0 +1,60 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::traits::OneSessionHandler; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(PhantomData); +} + +pub use pallet::*; + +pub mod crypto { + use sp_application_crypto::{KeyTypeId, app_crypto, sr25519}; + app_crypto!(sr25519, KeyTypeId(*b"tend")); + + impl sp_application_crypto::BoundToRuntimeAppPublic for crate::Pallet { + type Public = Public; + } + + sp_application_crypto::with_pair! { + pub type AuthorityPair = Pair; + } + pub type AuthoritySignature = Signature; + pub type AuthorityId = Public; +} + +impl OneSessionHandler for Pallet { + type Key = crypto::Public; + + fn on_genesis_session<'a, I: 'a>(_validators: I) + where + I: Iterator, + V: 'a, + { + } + + fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, _queued: I) + where + I: Iterator, + V: 'a, + { + /* + if !changed { + return; + } + + for validator in validators { + ... + } + */ + } + + fn on_disabled(_validator_index: u32) {} +} diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index 76b425417..62f13bd30 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -18,11 +18,11 @@ codec = { package = "parity-scale-codec", version = "3", default-features = fals scale-info = { version = "2", default-features = false, features = ["derive"] } sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-version = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-inherents = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-offchain = { git = "https://github.com/serai-dex/substrate", default-features = false } -sp-session = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-transaction-pool = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-block-builder = { git = "https://github.com/serai-dex/substrate", default-features = false} sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false } @@ -41,6 +41,9 @@ pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", d pallet-contracts-primitives = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-contracts = { git = "https://github.com/serai-dex/substrate", default-features = false } +pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false } +pallet-tendermint = { path = "../pallet-tendermint", default-features = false } + frame-system-rpc-runtime-api = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/serai-dex/substrate", default-features = false } @@ -53,11 +56,11 @@ std = [ "scale-info/std", "sp-core/std", + "sp-application-crypto/std", "sp-std/std", "sp-version/std", "sp-inherents/std", "sp-offchain/std", - "sp-session/std", "sp-transaction-pool/std", "sp-block-builder/std", "sp-runtime/std", @@ -75,6 +78,9 @@ std = [ "pallet-contracts/std", "pallet-contracts-primitives/std", + "pallet-session/std", + "pallet-tendermint/std", + "frame-system-rpc-runtime-api/std", "pallet-transaction-payment-rpc-runtime-api/std", ] @@ -90,6 +96,8 @@ runtime-benchmarks = [ "pallet-timestamp/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + + "pallet-tendermint/runtime-benchmarks", ] default = ["std"] diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 09d7b90ef..95c528c5e 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -4,11 +4,11 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; -pub use sp_core::sr25519::Signature; +use sp_core::OpaqueMetadata; +pub use sp_core::sr25519::{Public, Signature}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{IdentityLookup, BlakeTwo256, Block as BlockT}, + traits::{Convert, OpaqueKeys, IdentityLookup, BlakeTwo256, Block as BlockT}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, Perbill, }; @@ -32,11 +32,13 @@ pub use pallet_timestamp::Call as TimestampCall; pub use pallet_balances::Call as BalancesCall; use pallet_transaction_payment::CurrencyAdapter; +use pallet_session::PeriodicSessions; + /// An index to a block. pub type BlockNumber = u32; /// Account ID type, equivalent to a public key -pub type AccountId = sp_core::sr25519::Public; +pub type AccountId = Public; /// Balance of an account. pub type Balance = u64; @@ -57,10 +59,14 @@ pub mod opaque { pub type BlockId = generic::BlockId; impl_opaque_keys! { - pub struct SessionKeys {} + pub struct SessionKeys { + pub tendermint: Tendermint, + } } } +use opaque::SessionKeys; + #[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("serai"), @@ -206,6 +212,30 @@ impl pallet_contracts::Config for Runtime { type MaxStorageKeyLen = ConstU32<128>; } +impl pallet_tendermint::Config for Runtime {} + +const SESSION_LENGTH: BlockNumber = 5 * DAYS; +type Sessions = PeriodicSessions, ConstU32<{ SESSION_LENGTH }>>; + +pub struct IdentityValidatorIdOf; +impl Convert> for IdentityValidatorIdOf { + fn convert(key: Public) -> Option { + Some(key) + } +} + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = AccountId; + type ValidatorIdOf = IdentityValidatorIdOf; + type ShouldEndSession = Sessions; + type NextSessionRotation = Sessions; + type SessionManager = (); + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + type WeightInfo = pallet_session::weights::SubstrateWeight; +} + pub type Address = AccountId; pub type Header = generic::Header; pub type Block = generic::Block; @@ -242,6 +272,8 @@ construct_runtime!( Balances: pallet_balances, TransactionPayment: pallet_transaction_payment, Contracts: pallet_contracts, + Session: pallet_session, + Tendermint: pallet_tendermint, } ); @@ -317,18 +349,6 @@ sp_api::impl_runtime_apis! { } } - impl sp_session::SessionKeys for Runtime { - fn generate_session_keys(seed: Option>) -> Vec { - opaque::SessionKeys::generate(seed) - } - - fn decode_session_keys( - encoded: Vec, - ) -> Option, KeyTypeId)>> { - opaque::SessionKeys::decode_into_raw_public_keys(&encoded) - } - } - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(account: AccountId) -> Index { System::account_nonce(account) From fa7a03bf60d90636b30828f99c785c4bbf2c2033 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 27 Oct 2022 05:23:53 -0400 Subject: [PATCH 065/186] Update node to use pallet sessions --- Cargo.lock | 3 ++ substrate/consensus/src/validators.rs | 75 ++++++++++++++++++++++----- substrate/node/Cargo.toml | 2 + substrate/node/src/chain_spec.rs | 10 +++- substrate/runtime/Cargo.toml | 2 + substrate/runtime/src/lib.rs | 14 ++++- 6 files changed, 92 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index adc085207..74572d581 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7477,6 +7477,7 @@ dependencies = [ "frame-benchmarking-cli", "frame-system", "jsonrpsee", + "pallet-tendermint", "pallet-transaction-payment", "pallet-transaction-payment-rpc", "sc-cli", @@ -7494,6 +7495,7 @@ dependencies = [ "serai-consensus", "serai-runtime", "sp-api", + "sp-application-crypto", "sp-block-builder", "sp-blockchain", "sp-core", @@ -7553,6 +7555,7 @@ dependencies = [ "sp-inherents", "sp-offchain", "sp-runtime", + "sp-session", "sp-std", "sp-transaction-pool", "sp-version", diff --git a/substrate/consensus/src/validators.rs b/substrate/consensus/src/validators.rs index d537ffd50..49b498119 100644 --- a/substrate/consensus/src/validators.rs +++ b/substrate/consensus/src/validators.rs @@ -1,23 +1,72 @@ -// TODO: This should be built around pallet_sessions (and pallet_staking?). +use core::ops::Deref; +use std::sync::{Arc, RwLock}; use sp_application_crypto::{ RuntimePublic as PublicTrait, Pair as PairTrait, sr25519::{Public, Pair, Signature}, }; +use sp_staking::SessionIndex; +use pallet_session::Pallet as Session; + use tendermint_machine::ext::{BlockNumber, Round, Weights, SignatureScheme}; -const VALIDATORS: usize = 1; +struct TendermintValidatorsStruct { + session: SessionIndex, + + total_weight: u64, + weights: Vec, + + keys: Pair, // TODO: sp_keystore + lookup: Vec, // TODO: sessions +} + +impl TendermintValidatorsStruct { + fn from_module() -> TendermintValidatorsStruct { + let validators = Session::::validators(); + assert_eq!(validators.len(), 1); + let keys = Pair::from_string("//Alice", None).unwrap(); + TendermintValidatorsStruct { + session: Session::::current_index(), + + // TODO + total_weight: validators.len().try_into().unwrap(), + weights: vec![1; validators.len()], -pub(crate) struct TendermintValidators { - keys: Pair, // sp_keystore - lookup: Vec, // sessions + lookup: vec![keys.public()], + keys, + } + } } +// Wrap every access of the validators struct in something which forces calling refresh +struct Refresh { + _refresh: Arc>, +} +impl Refresh { + // If the session has changed, re-create the struct with the data on it + fn refresh(&self) { + let session = self._refresh.read().unwrap().session; + if session != Session::::current_index() { + *self._refresh.write().unwrap() = TendermintValidatorsStruct::from_module(); + } + } +} + +impl Deref for Refresh { + type Target = RwLock; + fn deref(&self) -> &RwLock { + self.refresh(); + &self._refresh + } +} + +pub(crate) struct TendermintValidators(Refresh); impl TendermintValidators { pub(crate) fn new() -> TendermintValidators { - let keys = Pair::from_string("//Alice", None).unwrap(); - TendermintValidators { lookup: vec![keys.public()], keys } + TendermintValidators(Refresh { + _refresh: Arc::new(RwLock::new(TendermintValidatorsStruct::from_module())), + }) } } @@ -27,11 +76,11 @@ impl SignatureScheme for TendermintValidators { type AggregateSignature = Vec; fn sign(&self, msg: &[u8]) -> Signature { - self.keys.sign(msg) + self.0.read().unwrap().keys.sign(msg) } fn verify(&self, validator: u16, msg: &[u8], sig: &Signature) -> bool { - self.lookup[usize::try_from(validator).unwrap()].verify(&msg, sig) + self.0.read().unwrap().lookup[usize::try_from(validator).unwrap()].verify(&msg, sig) } fn aggregate(sigs: &[Signature]) -> Vec { @@ -55,13 +104,15 @@ impl Weights for TendermintValidators { type ValidatorId = u16; fn total_weight(&self) -> u64 { - VALIDATORS.try_into().unwrap() + self.0.read().unwrap().total_weight } + fn weight(&self, id: u16) -> u64 { - [1; VALIDATORS][usize::try_from(id).unwrap()] + self.0.read().unwrap().weights[usize::try_from(id).unwrap()] } + // TODO fn proposer(&self, number: BlockNumber, round: Round) -> u16 { - u16::try_from((number.0 + u64::from(round.0)) % u64::try_from(VALIDATORS).unwrap()).unwrap() + u16::try_from(number.0 + u64::from(round.0)).unwrap() } } diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index 262e6c202..316c458e9 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -16,6 +16,7 @@ clap = { version = "4", features = ["derive"] } jsonrpsee = { version = "0.15", features = ["server"] } sp-core = { git = "https://github.com/serai-dex/substrate" } +sp-application-crypto = { git = "https://github.com/serai-dex/substrate" } sp-runtime = { git = "https://github.com/serai-dex/substrate" } sp-timestamp = { git = "https://github.com/serai-dex/substrate" } sp-inherents = { git = "https://github.com/serai-dex/substrate" } @@ -46,6 +47,7 @@ sc-rpc-api = { git = "https://github.com/serai-dex/substrate" } substrate-frame-rpc-system = { git = "https://github.com/serai-dex/substrate" } pallet-transaction-payment-rpc = { git = "https://github.com/serai-dex/substrate" } +pallet-tendermint = { path = "../pallet-tendermint", default-features = false } serai-runtime = { path = "../runtime" } serai-consensus = { path = "../consensus" } diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index fb21bec72..ab0d1f56f 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -1,8 +1,12 @@ use sc_service::ChainType; use sp_core::{Pair as PairTrait, sr25519::Pair}; +use pallet_tendermint::crypto::Public; -use serai_runtime::{WASM_BINARY, AccountId, GenesisConfig, SystemConfig, BalancesConfig}; +use serai_runtime::{ + WASM_BINARY, AccountId, opaque::SessionKeys, GenesisConfig, SystemConfig, BalancesConfig, + SessionConfig, +}; pub type ChainSpec = sc_service::GenericChainSpec; @@ -15,12 +19,16 @@ fn account_id_from_name(name: &'static str) -> AccountId { } fn testnet_genesis(wasm_binary: &[u8], endowed_accounts: Vec) -> GenesisConfig { + let alice = account_id_from_name("Alice"); GenesisConfig { system: SystemConfig { code: wasm_binary.to_vec() }, balances: BalancesConfig { balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), }, transaction_payment: Default::default(), + session: SessionConfig { + keys: vec![(alice, alice, SessionKeys { tendermint: Public::from(alice) })], + }, } } diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index 62f13bd30..be0f08cde 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -23,6 +23,7 @@ sp-std = { git = "https://github.com/serai-dex/substrate", default-features = fa sp-version = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-inherents = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-offchain = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-session = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-transaction-pool = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-block-builder = { git = "https://github.com/serai-dex/substrate", default-features = false} sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false } @@ -61,6 +62,7 @@ std = [ "sp-version/std", "sp-inherents/std", "sp-offchain/std", + "sp-session/std", "sp-transaction-pool/std", "sp-block-builder/std", "sp-runtime/std", diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 95c528c5e..3f7625957 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -7,7 +7,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); use sp_core::OpaqueMetadata; pub use sp_core::sr25519::{Public, Signature}; use sp_runtime::{ - create_runtime_str, generic, impl_opaque_keys, + create_runtime_str, generic, impl_opaque_keys, KeyTypeId, traits::{Convert, OpaqueKeys, IdentityLookup, BlakeTwo256, Block as BlockT}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, Perbill, @@ -349,6 +349,18 @@ sp_api::impl_runtime_apis! { } } + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + opaque::SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + opaque::SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(account: AccountId) -> Index { System::account_nonce(account) From eb418448ebca5abc01922765898d25f5e8eb474e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 27 Oct 2022 05:45:55 -0400 Subject: [PATCH 066/186] Update support URL --- substrate/node/src/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/node/src/command.rs b/substrate/node/src/command.rs index 03fbc5331..683923ad4 100644 --- a/substrate/node/src/command.rs +++ b/substrate/node/src/command.rs @@ -29,7 +29,7 @@ impl SubstrateCli for Cli { } fn support_url() -> String { - "serai.exchange".to_string() + "https://github.com/serai-dex/serai/issues/new".to_string() } fn copyright_start_year() -> i32 { From 4c2dd9b30683595364e617fc4ead969f8a5f2b55 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 27 Oct 2022 06:29:56 -0400 Subject: [PATCH 067/186] Partial work on correcting pallet calls --- Cargo.lock | 1 + substrate/consensus/Cargo.toml | 1 + substrate/consensus/src/block_import.rs | 4 ++ substrate/consensus/src/import_queue.rs | 7 ++- substrate/consensus/src/tendermint.rs | 40 ++++++++++++----- substrate/consensus/src/validators.rs | 59 ++++++++++++++++++------- substrate/consensus/src/verifier.rs | 4 ++ 7 files changed, 89 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74572d581..26b922f7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7419,6 +7419,7 @@ name = "serai-consensus" version = "0.1.0" dependencies = [ "async-trait", + "frame-support", "futures", "log", "pallet-session", diff --git a/substrate/consensus/Cargo.toml b/substrate/consensus/Cargo.toml index 06bde9f14..4102601b3 100644 --- a/substrate/consensus/Cargo.toml +++ b/substrate/consensus/Cargo.toml @@ -38,6 +38,7 @@ sc-service = { git = "https://github.com/serai-dex/substrate" } sc-client-api = { git = "https://github.com/serai-dex/substrate" } sc-consensus = { git = "https://github.com/serai-dex/substrate" } +frame-support = { git = "https://github.com/serai-dex/substrate" } pallet-session = { git = "https://github.com/serai-dex/substrate" } substrate-prometheus-endpoint = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/consensus/src/block_import.rs b/substrate/consensus/src/block_import.rs index c5a9a2854..46dac4b17 100644 --- a/substrate/consensus/src/block_import.rs +++ b/substrate/consensus/src/block_import.rs @@ -2,6 +2,7 @@ use std::{sync::Arc, collections::HashMap}; use async_trait::async_trait; +use sp_core::sr25519::Public; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::Block; use sp_api::TransactionFor; @@ -11,6 +12,8 @@ use sc_consensus::{BlockCheckParams, BlockImportParams, ImportResult, BlockImpor use sc_client_api::Backend; +use frame_support::traits::ValidatorSet; + use crate::{ tendermint::{TendermintClient, TendermintImport}, Announce, @@ -29,6 +32,7 @@ where TransactionFor: Send + Sync + 'static, Arc: BlockImport>, as BlockImport>::Error: Into, + C::Api: ValidatorSet, { type Error = Error; type Transaction = TransactionFor; diff --git a/substrate/consensus/src/import_queue.rs b/substrate/consensus/src/import_queue.rs index 4a5a30317..fb31d307d 100644 --- a/substrate/consensus/src/import_queue.rs +++ b/substrate/consensus/src/import_queue.rs @@ -6,7 +6,7 @@ use std::{ time::{UNIX_EPOCH, SystemTime}, }; -use sp_core::Decode; +use sp_core::{Decode, sr25519::Public}; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::{Header, Block}; use sp_api::{BlockId, TransactionFor}; @@ -19,6 +19,8 @@ use sc_client_api::Backend; use substrate_prometheus_endpoint::Registry; +use frame_support::traits::ValidatorSet; + use tendermint_machine::{ ext::{BlockNumber, Commit}, TendermintMachine, @@ -98,6 +100,7 @@ where TransactionFor: Send + Sync + 'static, Arc: BlockImport>, as BlockImport>::Error: Into, + C::Api: ValidatorSet, { let import = TendermintImport::new(client, announce, providers, env); @@ -116,7 +119,7 @@ where Ok(best) => BlockNumber(best), Err(_) => panic!("BlockNumber exceeded u64"), }, - Commit::::decode( + Commit::>::decode( &mut import_clone .client .justifications(&BlockId::Number(best)) diff --git a/substrate/consensus/src/tendermint.rs b/substrate/consensus/src/tendermint.rs index 2b92c0044..089721c03 100644 --- a/substrate/consensus/src/tendermint.rs +++ b/substrate/consensus/src/tendermint.rs @@ -10,8 +10,10 @@ use log::warn; use tokio::sync::RwLock as AsyncRwLock; -use sp_core::{Encode, Decode}; -use sp_application_crypto::sr25519::Signature; +use sp_core::{ + Encode, Decode, + sr25519::{Public, Signature}, +}; use sp_inherents::{InherentData, InherentDataProvider, CreateInherentDataProviders}; use sp_runtime::{ traits::{Header, Block}, @@ -26,6 +28,8 @@ use sc_consensus::{ForkChoiceStrategy, BlockImportParams, BlockImport, import_qu use sc_service::ImportQueue; use sc_client_api::{BlockBackend, Backend, Finalizer}; +use frame_support::traits::ValidatorSet; + use tendermint_machine::{ ext::{BlockError, Commit, Network}, SignedMessage, TendermintHandle, @@ -47,6 +51,9 @@ pub trait TendermintClient + 'static>: + Finalizer + ProvideRuntimeApi + 'static +where + TransactionFor: Send + Sync + 'static, + Self::Api: ValidatorSet, { } impl< @@ -61,6 +68,9 @@ impl< + ProvideRuntimeApi + 'static, > TendermintClient for C +where + TransactionFor: Send + Sync + 'static, + C::Api: ValidatorSet, { } @@ -73,10 +83,13 @@ pub(crate) struct TendermintImport< A: Announce, > where TransactionFor: Send + Sync + 'static, + C::Api: ValidatorSet, { _block: PhantomData, _backend: PhantomData, + validators: Arc>, + importing_block: Arc>>, pub(crate) machine: Arc>>>, @@ -98,12 +111,15 @@ impl< > Clone for TendermintImport where TransactionFor: Send + Sync + 'static, + C::Api: ValidatorSet, { fn clone(&self) -> Self { TendermintImport { _block: PhantomData, _backend: PhantomData, + validators: self.validators.clone(), + importing_block: self.importing_block.clone(), machine: self.machine.clone(), @@ -127,6 +143,7 @@ impl< > TendermintImport where TransactionFor: Send + Sync + 'static, + C::Api: ValidatorSet, { pub(crate) fn new( client: Arc, @@ -138,6 +155,8 @@ where _block: PhantomData, _backend: PhantomData, + validators: TendermintValidators::new(client), + importing_block: Arc::new(RwLock::new(None)), machine: Arc::new(RwLock::new(None)), @@ -196,7 +215,7 @@ where Err(Error::InvalidJustification)?; } - let commit: Commit = + let commit: Commit> = Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; if !self.verify_commit(hash, &commit) { Err(Error::InvalidJustification)?; @@ -309,20 +328,21 @@ impl< > Network for TendermintImport where TransactionFor: Send + Sync + 'static, + C::Api: ValidatorSet, { type ValidatorId = u16; - type SignatureScheme = TendermintValidators; - type Weights = TendermintValidators; + type SignatureScheme = TendermintValidators; + type Weights = TendermintValidators; type Block = B; const BLOCK_TIME: u32 = { (serai_runtime::MILLISECS_PER_BLOCK / 1000) as u32 }; - fn signature_scheme(&self) -> Arc { - Arc::new(TendermintValidators::new()) + fn signature_scheme(&self) -> Arc> { + self.validators.clone() } - fn weights(&self) -> Arc { - Arc::new(TendermintValidators::new()) + fn weights(&self) -> Arc> { + self.validators.clone() } async fn broadcast(&mut self, msg: SignedMessage) { @@ -390,7 +410,7 @@ where Ok(()) } - async fn add_block(&mut self, block: B, commit: Commit) -> B { + async fn add_block(&mut self, block: B, commit: Commit>) -> B { let hash = block.hash(); let justification = (CONSENSUS_ID, commit.encode()); debug_assert!(self.verify_justification(hash, &justification).is_ok()); diff --git a/substrate/consensus/src/validators.rs b/substrate/consensus/src/validators.rs index 49b498119..8ebd31edb 100644 --- a/substrate/consensus/src/validators.rs +++ b/substrate/consensus/src/validators.rs @@ -1,4 +1,4 @@ -use core::ops::Deref; +use core::{marker::PhantomData, ops::Deref}; use std::sync::{Arc, RwLock}; use sp_application_crypto::{ @@ -6,8 +6,11 @@ use sp_application_crypto::{ sr25519::{Public, Pair, Signature}, }; +use sp_runtime::traits::Block; use sp_staking::SessionIndex; -use pallet_session::Pallet as Session; +use sp_api::ProvideRuntimeApi; + +use frame_support::traits::ValidatorSet; use tendermint_machine::ext::{BlockNumber, Round, Weights, SignatureScheme}; @@ -22,12 +25,17 @@ struct TendermintValidatorsStruct { } impl TendermintValidatorsStruct { - fn from_module() -> TendermintValidatorsStruct { - let validators = Session::::validators(); + fn from_module>( + client: C, + ) -> TendermintValidatorsStruct + where + C::Api: ValidatorSet, + { + let validators = client.runtime_api().validators(); assert_eq!(validators.len(), 1); let keys = Pair::from_string("//Alice", None).unwrap(); TendermintValidatorsStruct { - session: Session::::current_index(), + session: client.runtime_api().session_index(), // TODO total_weight: validators.len().try_into().unwrap(), @@ -40,20 +48,28 @@ impl TendermintValidatorsStruct { } // Wrap every access of the validators struct in something which forces calling refresh -struct Refresh { +struct Refresh> { + _block: PhantomData, + client: C, _refresh: Arc>, } -impl Refresh { +impl> Refresh +where + C::Api: ValidatorSet, +{ // If the session has changed, re-create the struct with the data on it fn refresh(&self) { let session = self._refresh.read().unwrap().session; - if session != Session::::current_index() { - *self._refresh.write().unwrap() = TendermintValidatorsStruct::from_module(); + if session != self.client.runtime_api().session_index() { + *self._refresh.write().unwrap() = TendermintValidatorsStruct::from_module(self.client); } } } -impl Deref for Refresh { +impl> Deref for Refresh +where + C::Api: ValidatorSet, +{ type Target = RwLock; fn deref(&self) -> &RwLock { self.refresh(); @@ -61,16 +77,26 @@ impl Deref for Refresh { } } -pub(crate) struct TendermintValidators(Refresh); -impl TendermintValidators { - pub(crate) fn new() -> TendermintValidators { +pub(crate) struct TendermintValidators>( + Refresh, +); +impl> TendermintValidators +where + C::Api: ValidatorSet, +{ + pub(crate) fn new(client: C) -> TendermintValidators { TendermintValidators(Refresh { + _block: PhantomData, + client, _refresh: Arc::new(RwLock::new(TendermintValidatorsStruct::from_module())), }) } } -impl SignatureScheme for TendermintValidators { +impl> SignatureScheme for TendermintValidators +where + C::Api: ValidatorSet, +{ type ValidatorId = u16; type Signature = Signature; type AggregateSignature = Vec; @@ -100,7 +126,10 @@ impl SignatureScheme for TendermintValidators { } } -impl Weights for TendermintValidators { +impl> Weights for TendermintValidators +where + C::Api: ValidatorSet, +{ type ValidatorId = u16; fn total_weight(&self) -> u64 { diff --git a/substrate/consensus/src/verifier.rs b/substrate/consensus/src/verifier.rs index d2379196b..d7a2f8a4c 100644 --- a/substrate/consensus/src/verifier.rs +++ b/substrate/consensus/src/verifier.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use async_trait::async_trait; +use sp_core::sr25519::Public; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::Block; use sp_api::TransactionFor; @@ -11,6 +12,8 @@ use sc_consensus::{BlockImportParams, BlockImport, Verifier}; use sc_client_api::Backend; +use frame_support::traits::ValidatorSet; + use crate::{ tendermint::{TendermintClient, TendermintImport}, Announce, @@ -29,6 +32,7 @@ where TransactionFor: Send + Sync + 'static, Arc: BlockImport>, as BlockImport>::Error: Into, + C::Api: ValidatorSet, { async fn verify( &mut self, From 66f7663cb224f10f3787addc222162e7f216f5b8 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 27 Oct 2022 06:33:58 -0400 Subject: [PATCH 068/186] Redo Tendermint folder structure --- Cargo.toml | 8 ++++---- .../{consensus => tendermint/client}/Cargo.toml | 2 +- .../{consensus => tendermint/client}/LICENSE | 0 .../client}/src/block_import.rs | 0 .../client}/src/import_queue.rs | 0 .../{consensus => tendermint/client}/src/lib.rs | 0 .../client}/src/select_chain.rs | 0 .../client}/src/tendermint.rs | 0 .../client}/src/validators.rs | 0 .../client}/src/verifier.rs | 0 substrate/tendermint/{ => machine}/Cargo.toml | 2 +- substrate/tendermint/{ => machine}/LICENSE | 0 substrate/tendermint/{ => machine}/README.md | 0 substrate/tendermint/{ => machine}/src/ext.rs | 0 substrate/tendermint/{ => machine}/src/lib.rs | 0 .../tendermint/{ => machine}/src/message_log.rs | 0 substrate/tendermint/{ => machine}/tests/ext.rs | 0 .../pallet}/Cargo.toml | 3 +-- substrate/tendermint/pallet/LICENSE | 15 +++++++++++++++ .../pallet}/src/lib.rs | 0 substrate/tendermint/primitives/Cargo.toml | 12 ++++++++++++ substrate/tendermint/primitives/LICENSE | 15 +++++++++++++++ substrate/tendermint/primitives/src/lib.rs | 7 +++++++ 23 files changed, 56 insertions(+), 8 deletions(-) rename substrate/{consensus => tendermint/client}/Cargo.toml (98%) rename substrate/{consensus => tendermint/client}/LICENSE (100%) rename substrate/{consensus => tendermint/client}/src/block_import.rs (100%) rename substrate/{consensus => tendermint/client}/src/import_queue.rs (100%) rename substrate/{consensus => tendermint/client}/src/lib.rs (100%) rename substrate/{consensus => tendermint/client}/src/select_chain.rs (100%) rename substrate/{consensus => tendermint/client}/src/tendermint.rs (100%) rename substrate/{consensus => tendermint/client}/src/validators.rs (100%) rename substrate/{consensus => tendermint/client}/src/verifier.rs (100%) rename substrate/tendermint/{ => machine}/Cargo.toml (95%) rename substrate/tendermint/{ => machine}/LICENSE (100%) rename substrate/tendermint/{ => machine}/README.md (100%) rename substrate/tendermint/{ => machine}/src/ext.rs (100%) rename substrate/tendermint/{ => machine}/src/lib.rs (100%) rename substrate/tendermint/{ => machine}/src/message_log.rs (100%) rename substrate/tendermint/{ => machine}/tests/ext.rs (100%) rename substrate/{pallet-tendermint => tendermint/pallet}/Cargo.toml (97%) create mode 100644 substrate/tendermint/pallet/LICENSE rename substrate/{pallet-tendermint => tendermint/pallet}/src/lib.rs (100%) create mode 100644 substrate/tendermint/primitives/Cargo.toml create mode 100644 substrate/tendermint/primitives/LICENSE create mode 100644 substrate/tendermint/primitives/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 0ceceae61..85057ee7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,12 +16,12 @@ members = [ "processor", - "substrate/tendermint", + "substrate/tendermint/machine", + "substrate/tendermint/primitives", + "substrate/tendermint/client", + "substrate/tendermint/pallet", - "substrate/pallet-tendermint", "substrate/runtime", - - "substrate/consensus", "substrate/node", "contracts/extension", diff --git a/substrate/consensus/Cargo.toml b/substrate/tendermint/client/Cargo.toml similarity index 98% rename from substrate/consensus/Cargo.toml rename to substrate/tendermint/client/Cargo.toml index 4102601b3..76cf2863b 100644 --- a/substrate/consensus/Cargo.toml +++ b/substrate/tendermint/client/Cargo.toml @@ -3,7 +3,7 @@ name = "serai-consensus" version = "0.1.0" description = "Serai consensus module" license = "AGPL-3.0-only" -repository = "https://github.com/serai-dex/serai/tree/develop/substrate/consensus" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/tendermint/client" authors = ["Luke Parker "] edition = "2021" publish = false diff --git a/substrate/consensus/LICENSE b/substrate/tendermint/client/LICENSE similarity index 100% rename from substrate/consensus/LICENSE rename to substrate/tendermint/client/LICENSE diff --git a/substrate/consensus/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs similarity index 100% rename from substrate/consensus/src/block_import.rs rename to substrate/tendermint/client/src/block_import.rs diff --git a/substrate/consensus/src/import_queue.rs b/substrate/tendermint/client/src/import_queue.rs similarity index 100% rename from substrate/consensus/src/import_queue.rs rename to substrate/tendermint/client/src/import_queue.rs diff --git a/substrate/consensus/src/lib.rs b/substrate/tendermint/client/src/lib.rs similarity index 100% rename from substrate/consensus/src/lib.rs rename to substrate/tendermint/client/src/lib.rs diff --git a/substrate/consensus/src/select_chain.rs b/substrate/tendermint/client/src/select_chain.rs similarity index 100% rename from substrate/consensus/src/select_chain.rs rename to substrate/tendermint/client/src/select_chain.rs diff --git a/substrate/consensus/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs similarity index 100% rename from substrate/consensus/src/tendermint.rs rename to substrate/tendermint/client/src/tendermint.rs diff --git a/substrate/consensus/src/validators.rs b/substrate/tendermint/client/src/validators.rs similarity index 100% rename from substrate/consensus/src/validators.rs rename to substrate/tendermint/client/src/validators.rs diff --git a/substrate/consensus/src/verifier.rs b/substrate/tendermint/client/src/verifier.rs similarity index 100% rename from substrate/consensus/src/verifier.rs rename to substrate/tendermint/client/src/verifier.rs diff --git a/substrate/tendermint/Cargo.toml b/substrate/tendermint/machine/Cargo.toml similarity index 95% rename from substrate/tendermint/Cargo.toml rename to substrate/tendermint/machine/Cargo.toml index b590e1469..330c685af 100644 --- a/substrate/tendermint/Cargo.toml +++ b/substrate/tendermint/machine/Cargo.toml @@ -3,7 +3,7 @@ name = "tendermint-machine" version = "0.1.0" description = "An implementation of the Tendermint state machine in Rust" license = "MIT" -repository = "https://github.com/serai-dex/serai/tree/develop/substrate/tendermint" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/tendermint/machine" authors = ["Luke Parker "] edition = "2021" diff --git a/substrate/tendermint/LICENSE b/substrate/tendermint/machine/LICENSE similarity index 100% rename from substrate/tendermint/LICENSE rename to substrate/tendermint/machine/LICENSE diff --git a/substrate/tendermint/README.md b/substrate/tendermint/machine/README.md similarity index 100% rename from substrate/tendermint/README.md rename to substrate/tendermint/machine/README.md diff --git a/substrate/tendermint/src/ext.rs b/substrate/tendermint/machine/src/ext.rs similarity index 100% rename from substrate/tendermint/src/ext.rs rename to substrate/tendermint/machine/src/ext.rs diff --git a/substrate/tendermint/src/lib.rs b/substrate/tendermint/machine/src/lib.rs similarity index 100% rename from substrate/tendermint/src/lib.rs rename to substrate/tendermint/machine/src/lib.rs diff --git a/substrate/tendermint/src/message_log.rs b/substrate/tendermint/machine/src/message_log.rs similarity index 100% rename from substrate/tendermint/src/message_log.rs rename to substrate/tendermint/machine/src/message_log.rs diff --git a/substrate/tendermint/tests/ext.rs b/substrate/tendermint/machine/tests/ext.rs similarity index 100% rename from substrate/tendermint/tests/ext.rs rename to substrate/tendermint/machine/tests/ext.rs diff --git a/substrate/pallet-tendermint/Cargo.toml b/substrate/tendermint/pallet/Cargo.toml similarity index 97% rename from substrate/pallet-tendermint/Cargo.toml rename to substrate/tendermint/pallet/Cargo.toml index 6b3228c87..6b4412304 100644 --- a/substrate/pallet-tendermint/Cargo.toml +++ b/substrate/tendermint/pallet/Cargo.toml @@ -3,7 +3,7 @@ name = "pallet-tendermint" version = "0.1.0" description = "Tendermint pallet for Substrate" license = "AGPL-3.0-only" -repository = "https://github.com/serai-dex/serai/tree/develop/substrate/pallet-tendermint" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/tendermint/pallet" authors = ["Luke Parker "] edition = "2021" @@ -12,7 +12,6 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] - parity-scale-codec = { version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2", default-features = false, features = ["derive"] } diff --git a/substrate/tendermint/pallet/LICENSE b/substrate/tendermint/pallet/LICENSE new file mode 100644 index 000000000..d6e1814a2 --- /dev/null +++ b/substrate/tendermint/pallet/LICENSE @@ -0,0 +1,15 @@ +AGPL-3.0-only license + +Copyright (c) 2022 Luke Parker + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License Version 3 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/substrate/pallet-tendermint/src/lib.rs b/substrate/tendermint/pallet/src/lib.rs similarity index 100% rename from substrate/pallet-tendermint/src/lib.rs rename to substrate/tendermint/pallet/src/lib.rs diff --git a/substrate/tendermint/primitives/Cargo.toml b/substrate/tendermint/primitives/Cargo.toml new file mode 100644 index 000000000..a877df514 --- /dev/null +++ b/substrate/tendermint/primitives/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "sp-tendermint" +version = "0.1.0" +description = "Substrate primitives for Tendermint" +license = "AGPL-3.0-only" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/tendermint/primitives" +authors = ["Luke Parker "] +edition = "2021" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/substrate/tendermint/primitives/LICENSE b/substrate/tendermint/primitives/LICENSE new file mode 100644 index 000000000..d6e1814a2 --- /dev/null +++ b/substrate/tendermint/primitives/LICENSE @@ -0,0 +1,15 @@ +AGPL-3.0-only license + +Copyright (c) 2022 Luke Parker + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License Version 3 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/substrate/tendermint/primitives/src/lib.rs b/substrate/tendermint/primitives/src/lib.rs new file mode 100644 index 000000000..71e29beb1 --- /dev/null +++ b/substrate/tendermint/primitives/src/lib.rs @@ -0,0 +1,7 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_core::sr25519::Public; + +trait TendermintApi { + fn validators() -> Vec; +} From 5c08fa97017c48f5ebdeb482e3599b2f45fc43bf Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 27 Oct 2022 08:44:53 -0400 Subject: [PATCH 069/186] TendermintApi, compilation fixes --- Cargo.lock | 13 +++ substrate/node/Cargo.toml | 4 +- substrate/runtime/Cargo.toml | 6 +- substrate/runtime/src/lib.rs | 10 +++ substrate/tendermint/client/Cargo.toml | 6 +- .../tendermint/client/src/block_import.rs | 5 +- .../tendermint/client/src/import_queue.rs | 10 +-- substrate/tendermint/client/src/tendermint.rs | 37 ++++---- substrate/tendermint/client/src/validators.rs | 88 +++++++++++++------ substrate/tendermint/client/src/verifier.rs | 5 +- substrate/tendermint/pallet/Cargo.toml | 2 + substrate/tendermint/pallet/src/lib.rs | 87 ++++++++++-------- substrate/tendermint/primitives/Cargo.toml | 9 ++ substrate/tendermint/primitives/src/lib.rs | 12 ++- 14 files changed, 193 insertions(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26b922f7a..edbeca400 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5115,6 +5115,8 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-application-crypto", + "sp-core", + "sp-std", ] [[package]] @@ -7439,6 +7441,7 @@ dependencies = [ "sp-inherents", "sp-runtime", "sp-staking", + "sp-tendermint", "sp-timestamp", "substrate-prometheus-endpoint", "tendermint-machine", @@ -7558,6 +7561,7 @@ dependencies = [ "sp-runtime", "sp-session", "sp-std", + "sp-tendermint", "sp-transaction-pool", "sp-version", "substrate-wasm-builder", @@ -8318,6 +8322,15 @@ dependencies = [ "sp-std", ] +[[package]] +name = "sp-tendermint" +version = "0.1.0" +dependencies = [ + "sp-api", + "sp-core", + "sp-std", +] + [[package]] name = "sp-timestamp" version = "4.0.0-dev" diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index 316c458e9..4dfb37002 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -47,9 +47,9 @@ sc-rpc-api = { git = "https://github.com/serai-dex/substrate" } substrate-frame-rpc-system = { git = "https://github.com/serai-dex/substrate" } pallet-transaction-payment-rpc = { git = "https://github.com/serai-dex/substrate" } -pallet-tendermint = { path = "../pallet-tendermint", default-features = false } +pallet-tendermint = { path = "../tendermint/pallet", default-features = false } serai-runtime = { path = "../runtime" } -serai-consensus = { path = "../consensus" } +serai-consensus = { path = "../tendermint/client" } [build-dependencies] substrate-build-script-utils = { git = "https://github.com/serai-dex/substrate.git" } diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index be0f08cde..39ce6bb4a 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -29,6 +29,8 @@ sp-block-builder = { git = "https://github.com/serai-dex/substrate", default-fea sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-api = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-tendermint = { path = "../tendermint/primitives", default-features = false } + frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false } frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } frame-executive = { git = "https://github.com/serai-dex/substrate", default-features = false } @@ -43,7 +45,7 @@ pallet-contracts-primitives = { git = "https://github.com/serai-dex/substrate", pallet-contracts = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false } -pallet-tendermint = { path = "../pallet-tendermint", default-features = false } +pallet-tendermint = { path = "../tendermint/pallet", default-features = false } frame-system-rpc-runtime-api = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/serai-dex/substrate", default-features = false } @@ -68,6 +70,8 @@ std = [ "sp-runtime/std", "sp-api/std", + "sp-tendermint/std", + "frame-system/std", "frame-support/std", "frame-executive/std", diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 3f7625957..7e8b29f1e 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -361,6 +361,16 @@ sp_api::impl_runtime_apis! { } } + impl sp_tendermint::TendermintApi for Runtime { + fn current_session() -> u32 { + Tendermint::session() + } + + fn validators() -> Vec { + Session::validators() + } + } + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(account: AccountId) -> Index { System::account_nonce(account) diff --git a/substrate/tendermint/client/Cargo.toml b/substrate/tendermint/client/Cargo.toml index 76cf2863b..37dde6d1f 100644 --- a/substrate/tendermint/client/Cargo.toml +++ b/substrate/tendermint/client/Cargo.toml @@ -30,6 +30,8 @@ sp-runtime = { git = "https://github.com/serai-dex/substrate" } sp-api = { git = "https://github.com/serai-dex/substrate" } sp-consensus = { git = "https://github.com/serai-dex/substrate" } +sp-tendermint = { path = "../primitives" } + sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" } sc-basic-authorship = { git = "https://github.com/serai-dex/substrate" } sc-executor = { git = "https://github.com/serai-dex/substrate" } @@ -43,6 +45,6 @@ pallet-session = { git = "https://github.com/serai-dex/substrate" } substrate-prometheus-endpoint = { git = "https://github.com/serai-dex/substrate" } -tendermint-machine = { path = "../tendermint", features = ["substrate"] } +tendermint-machine = { path = "../machine", features = ["substrate"] } -serai-runtime = { path = "../runtime" } +serai-runtime = { path = "../../runtime" } diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index 46dac4b17..ae654351e 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -2,7 +2,6 @@ use std::{sync::Arc, collections::HashMap}; use async_trait::async_trait; -use sp_core::sr25519::Public; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::Block; use sp_api::TransactionFor; @@ -12,7 +11,7 @@ use sc_consensus::{BlockCheckParams, BlockImportParams, ImportResult, BlockImpor use sc_client_api::Backend; -use frame_support::traits::ValidatorSet; +use sp_tendermint::TendermintApi; use crate::{ tendermint::{TendermintClient, TendermintImport}, @@ -32,7 +31,7 @@ where TransactionFor: Send + Sync + 'static, Arc: BlockImport>, as BlockImport>::Error: Into, - C::Api: ValidatorSet, + C::Api: TendermintApi, { type Error = Error; type Transaction = TransactionFor; diff --git a/substrate/tendermint/client/src/import_queue.rs b/substrate/tendermint/client/src/import_queue.rs index fb31d307d..cf641d6a2 100644 --- a/substrate/tendermint/client/src/import_queue.rs +++ b/substrate/tendermint/client/src/import_queue.rs @@ -6,7 +6,7 @@ use std::{ time::{UNIX_EPOCH, SystemTime}, }; -use sp_core::{Decode, sr25519::Public}; +use sp_core::Decode; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::{Header, Block}; use sp_api::{BlockId, TransactionFor}; @@ -19,13 +19,13 @@ use sc_client_api::Backend; use substrate_prometheus_endpoint::Registry; -use frame_support::traits::ValidatorSet; - use tendermint_machine::{ ext::{BlockNumber, Commit}, TendermintMachine, }; +use sp_tendermint::TendermintApi; + use crate::{ CONSENSUS_ID, validators::TendermintValidators, @@ -100,7 +100,7 @@ where TransactionFor: Send + Sync + 'static, Arc: BlockImport>, as BlockImport>::Error: Into, - C::Api: ValidatorSet, + C::Api: TendermintApi, { let import = TendermintImport::new(client, announce, providers, env); @@ -119,7 +119,7 @@ where Ok(best) => BlockNumber(best), Err(_) => panic!("BlockNumber exceeded u64"), }, - Commit::>::decode( + Commit::>::decode( &mut import_clone .client .justifications(&BlockId::Number(best)) diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 089721c03..a2ab4bd80 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -10,10 +10,7 @@ use log::warn; use tokio::sync::RwLock as AsyncRwLock; -use sp_core::{ - Encode, Decode, - sr25519::{Public, Signature}, -}; +use sp_core::{Encode, Decode, sr25519::Signature}; use sp_inherents::{InherentData, InherentDataProvider, CreateInherentDataProviders}; use sp_runtime::{ traits::{Header, Block}, @@ -28,13 +25,13 @@ use sc_consensus::{ForkChoiceStrategy, BlockImportParams, BlockImport, import_qu use sc_service::ImportQueue; use sc_client_api::{BlockBackend, Backend, Finalizer}; -use frame_support::traits::ValidatorSet; - use tendermint_machine::{ ext::{BlockError, Commit, Network}, SignedMessage, TendermintHandle, }; +use sp_tendermint::TendermintApi; + use crate::{ CONSENSUS_ID, validators::TendermintValidators, @@ -53,7 +50,7 @@ pub trait TendermintClient + 'static>: + 'static where TransactionFor: Send + Sync + 'static, - Self::Api: ValidatorSet, + Self::Api: TendermintApi, { } impl< @@ -70,7 +67,7 @@ impl< > TendermintClient for C where TransactionFor: Send + Sync + 'static, - C::Api: ValidatorSet, + C::Api: TendermintApi, { } @@ -83,12 +80,12 @@ pub(crate) struct TendermintImport< A: Announce, > where TransactionFor: Send + Sync + 'static, - C::Api: ValidatorSet, + C::Api: TendermintApi, { _block: PhantomData, _backend: PhantomData, - validators: Arc>, + validators: Arc>, importing_block: Arc>>, pub(crate) machine: Arc>>>, @@ -111,7 +108,7 @@ impl< > Clone for TendermintImport where TransactionFor: Send + Sync + 'static, - C::Api: ValidatorSet, + C::Api: TendermintApi, { fn clone(&self) -> Self { TendermintImport { @@ -143,7 +140,7 @@ impl< > TendermintImport where TransactionFor: Send + Sync + 'static, - C::Api: ValidatorSet, + C::Api: TendermintApi, { pub(crate) fn new( client: Arc, @@ -155,7 +152,7 @@ where _block: PhantomData, _backend: PhantomData, - validators: TendermintValidators::new(client), + validators: Arc::new(TendermintValidators::new(client.clone())), importing_block: Arc::new(RwLock::new(None)), machine: Arc::new(RwLock::new(None)), @@ -215,7 +212,7 @@ where Err(Error::InvalidJustification)?; } - let commit: Commit> = + let commit: Commit> = Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; if !self.verify_commit(hash, &commit) { Err(Error::InvalidJustification)?; @@ -328,20 +325,20 @@ impl< > Network for TendermintImport where TransactionFor: Send + Sync + 'static, - C::Api: ValidatorSet, + C::Api: TendermintApi, { type ValidatorId = u16; - type SignatureScheme = TendermintValidators; - type Weights = TendermintValidators; + type SignatureScheme = TendermintValidators; + type Weights = TendermintValidators; type Block = B; const BLOCK_TIME: u32 = { (serai_runtime::MILLISECS_PER_BLOCK / 1000) as u32 }; - fn signature_scheme(&self) -> Arc> { + fn signature_scheme(&self) -> Arc> { self.validators.clone() } - fn weights(&self) -> Arc> { + fn weights(&self) -> Arc> { self.validators.clone() } @@ -410,7 +407,7 @@ where Ok(()) } - async fn add_block(&mut self, block: B, commit: Commit>) -> B { + async fn add_block(&mut self, block: B, commit: Commit>) -> B { let hash = block.hash(); let justification = (CONSENSUS_ID, commit.encode()); debug_assert!(self.verify_justification(hash, &justification).is_ok()); diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 8ebd31edb..87e1b0169 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -8,12 +8,16 @@ use sp_application_crypto::{ use sp_runtime::traits::Block; use sp_staking::SessionIndex; -use sp_api::ProvideRuntimeApi; +use sp_api::{BlockId, TransactionFor}; -use frame_support::traits::ValidatorSet; +use sc_client_api::Backend; use tendermint_machine::ext::{BlockNumber, Round, Weights, SignatureScheme}; +use sp_tendermint::TendermintApi; + +use crate::tendermint::TendermintClient; + struct TendermintValidatorsStruct { session: SessionIndex, @@ -25,17 +29,21 @@ struct TendermintValidatorsStruct { } impl TendermintValidatorsStruct { - fn from_module>( - client: C, + fn from_module + 'static, C: TendermintClient>( + client: &Arc, ) -> TendermintValidatorsStruct where - C::Api: ValidatorSet, + TransactionFor: Send + Sync + 'static, + C::Api: TendermintApi, { - let validators = client.runtime_api().validators(); + let last = client.info().best_hash; + let api = client.runtime_api(); + let session = api.current_session(&BlockId::Hash(last)).unwrap(); + let validators = api.validators(&BlockId::Hash(last)).unwrap(); assert_eq!(validators.len(), 1); let keys = Pair::from_string("//Alice", None).unwrap(); TendermintValidatorsStruct { - session: client.runtime_api().session_index(), + session, // TODO total_weight: validators.len().try_into().unwrap(), @@ -48,27 +56,42 @@ impl TendermintValidatorsStruct { } // Wrap every access of the validators struct in something which forces calling refresh -struct Refresh> { +struct Refresh + 'static, C: TendermintClient> +where + TransactionFor: Send + Sync + 'static, + C::Api: TendermintApi, +{ _block: PhantomData, - client: C, + _backend: PhantomData, + + client: Arc, _refresh: Arc>, } -impl> Refresh + +impl + 'static, C: TendermintClient> Refresh where - C::Api: ValidatorSet, + TransactionFor: Send + Sync + 'static, + C::Api: TendermintApi, { // If the session has changed, re-create the struct with the data on it fn refresh(&self) { let session = self._refresh.read().unwrap().session; - if session != self.client.runtime_api().session_index() { - *self._refresh.write().unwrap() = TendermintValidatorsStruct::from_module(self.client); + if session != + self + .client + .runtime_api() + .current_session(&BlockId::Hash(self.client.info().best_hash)) + .unwrap() + { + *self._refresh.write().unwrap() = TendermintValidatorsStruct::from_module(&self.client); } } } -impl> Deref for Refresh +impl + 'static, C: TendermintClient> Deref for Refresh where - C::Api: ValidatorSet, + TransactionFor: Send + Sync + 'static, + C::Api: TendermintApi, { type Target = RwLock; fn deref(&self) -> &RwLock { @@ -77,25 +100,36 @@ where } } -pub(crate) struct TendermintValidators>( - Refresh, -); -impl> TendermintValidators +pub(crate) struct TendermintValidators< + B: Block, + Be: Backend + 'static, + C: TendermintClient, +>(Refresh) +where + TransactionFor: Send + Sync + 'static, + C::Api: TendermintApi; + +impl + 'static, C: TendermintClient> TendermintValidators where - C::Api: ValidatorSet, + TransactionFor: Send + Sync + 'static, + C::Api: TendermintApi, { - pub(crate) fn new(client: C) -> TendermintValidators { + pub(crate) fn new(client: Arc) -> TendermintValidators { TendermintValidators(Refresh { _block: PhantomData, + _backend: PhantomData, + + _refresh: Arc::new(RwLock::new(TendermintValidatorsStruct::from_module(&client))), client, - _refresh: Arc::new(RwLock::new(TendermintValidatorsStruct::from_module())), }) } } -impl> SignatureScheme for TendermintValidators +impl + 'static, C: TendermintClient> SignatureScheme + for TendermintValidators where - C::Api: ValidatorSet, + TransactionFor: Send + Sync + 'static, + C::Api: TendermintApi, { type ValidatorId = u16; type Signature = Signature; @@ -126,9 +160,11 @@ where } } -impl> Weights for TendermintValidators +impl + 'static, C: TendermintClient> Weights + for TendermintValidators where - C::Api: ValidatorSet, + TransactionFor: Send + Sync + 'static, + C::Api: TendermintApi, { type ValidatorId = u16; diff --git a/substrate/tendermint/client/src/verifier.rs b/substrate/tendermint/client/src/verifier.rs index d7a2f8a4c..b587a52e4 100644 --- a/substrate/tendermint/client/src/verifier.rs +++ b/substrate/tendermint/client/src/verifier.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use async_trait::async_trait; -use sp_core::sr25519::Public; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::Block; use sp_api::TransactionFor; @@ -12,7 +11,7 @@ use sc_consensus::{BlockImportParams, BlockImport, Verifier}; use sc_client_api::Backend; -use frame_support::traits::ValidatorSet; +use sp_tendermint::TendermintApi; use crate::{ tendermint::{TendermintClient, TendermintImport}, @@ -32,7 +31,7 @@ where TransactionFor: Send + Sync + 'static, Arc: BlockImport>, as BlockImport>::Error: Into, - C::Api: ValidatorSet, + C::Api: TendermintApi, { async fn verify( &mut self, diff --git a/substrate/tendermint/pallet/Cargo.toml b/substrate/tendermint/pallet/Cargo.toml index 6b4412304..4958dbecb 100644 --- a/substrate/tendermint/pallet/Cargo.toml +++ b/substrate/tendermint/pallet/Cargo.toml @@ -15,6 +15,8 @@ rustdoc-args = ["--cfg", "docsrs"] parity-scale-codec = { version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2", default-features = false, features = ["derive"] } +sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false } sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false } frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false } diff --git a/substrate/tendermint/pallet/src/lib.rs b/substrate/tendermint/pallet/src/lib.rs index f5a1b91ce..f8b92db85 100644 --- a/substrate/tendermint/pallet/src/lib.rs +++ b/substrate/tendermint/pallet/src/lib.rs @@ -1,60 +1,73 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::OneSessionHandler; - #[frame_support::pallet] pub mod pallet { + use sp_std::vec::Vec; + use sp_core::sr25519::Public; + use frame_support::pallet_prelude::*; + use frame_support::traits::{ConstU32, OneSessionHandler}; + + type MaxValidators = ConstU32<{ u16::MAX as u32 }>; #[pallet::config] pub trait Config: frame_system::Config {} #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(PhantomData); -} -pub use pallet::*; + #[pallet::storage] + #[pallet::getter(fn session)] + pub type Session = StorageValue<_, u32, ValueQuery>; -pub mod crypto { - use sp_application_crypto::{KeyTypeId, app_crypto, sr25519}; - app_crypto!(sr25519, KeyTypeId(*b"tend")); + #[pallet::storage] + #[pallet::getter(fn validators)] + pub type Validators = StorageValue<_, BoundedVec, ValueQuery>; - impl sp_application_crypto::BoundToRuntimeAppPublic for crate::Pallet { - type Public = Public; - } - - sp_application_crypto::with_pair! { - pub type AuthorityPair = Pair; - } - pub type AuthoritySignature = Signature; - pub type AuthorityId = Public; -} + pub mod crypto { + use sp_application_crypto::{KeyTypeId, app_crypto, sr25519}; + app_crypto!(sr25519, KeyTypeId(*b"tend")); -impl OneSessionHandler for Pallet { - type Key = crypto::Public; + impl sp_application_crypto::BoundToRuntimeAppPublic for crate::Pallet { + type Public = Public; + } - fn on_genesis_session<'a, I: 'a>(_validators: I) - where - I: Iterator, - V: 'a, - { + sp_application_crypto::with_pair! { + pub type AuthorityPair = Pair; + } + pub type AuthoritySignature = Signature; + pub type AuthorityId = Public; } - fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, _queued: I) - where - I: Iterator, - V: 'a, - { - /* - if !changed { - return; + impl OneSessionHandler for Pallet { + type Key = crypto::Public; + + fn on_genesis_session<'a, I: 'a>(_validators: I) + where + I: Iterator, + V: 'a, + { } - for validator in validators { - ... + fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued: I) + where + I: Iterator, + V: 'a, + { + if !changed { + return; + } + + Session::::put(Self::session() + 1); + Validators::::put( + BoundedVec::try_from(validators.map(|(_, key)| key.into()).collect::>()) + .unwrap(), + ); } - */ - } - fn on_disabled(_validator_index: u32) {} + fn on_disabled(_validator_index: u32) {} + } } + +pub use pallet::*; diff --git a/substrate/tendermint/primitives/Cargo.toml b/substrate/tendermint/primitives/Cargo.toml index a877df514..a152fa7da 100644 --- a/substrate/tendermint/primitives/Cargo.toml +++ b/substrate/tendermint/primitives/Cargo.toml @@ -10,3 +10,12 @@ edition = "2021" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-api = { git = "https://github.com/serai-dex/substrate", default-features = false } + +[features] +std = ["sp-core/std", "sp-std/std", "sp-api/std"] +default = ["std"] diff --git a/substrate/tendermint/primitives/src/lib.rs b/substrate/tendermint/primitives/src/lib.rs index 71e29beb1..50cc78d12 100644 --- a/substrate/tendermint/primitives/src/lib.rs +++ b/substrate/tendermint/primitives/src/lib.rs @@ -1,7 +1,15 @@ #![cfg_attr(not(feature = "std"), no_std)] use sp_core::sr25519::Public; +use sp_std::vec::Vec; -trait TendermintApi { - fn validators() -> Vec; +sp_api::decl_runtime_apis! { + pub trait TendermintApi { + /// Current session number. A session is NOT a fixed length of blocks, yet rather a continuous + /// set of validators. + fn current_session() -> u32; + + /// Current validators. + fn validators() -> Vec; + } } From f91c081f300c463858c9cc69bfd0dc18d018109f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 27 Oct 2022 08:49:36 -0400 Subject: [PATCH 070/186] Fix the stub round robin At some point, the modulus was removed causing it to exceed the validators list and stop proposing. --- substrate/tendermint/client/src/validators.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 87e1b0169..9ecd3db89 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -178,6 +178,9 @@ where // TODO fn proposer(&self, number: BlockNumber, round: Round) -> u16 { - u16::try_from(number.0 + u64::from(round.0)).unwrap() + u16::try_from( + (number.0 + u64::from(round.0)) % u64::try_from(self.0.read().unwrap().lookup.len()).unwrap(), + ) + .unwrap() } } From a0c892dfc35c84e5637b41508f469294811d61d5 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 29 Oct 2022 06:00:58 -0400 Subject: [PATCH 071/186] Use the validators list from the session pallet --- substrate/tendermint/client/src/validators.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 9ecd3db89..189805b7b 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -49,7 +49,7 @@ impl TendermintValidatorsStruct { total_weight: validators.len().try_into().unwrap(), weights: vec![1; validators.len()], - lookup: vec![keys.public()], + lookup: validators, keys, } } From 9a5431774379f317614d2c64042d514c9364de21 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 01:21:10 -0400 Subject: [PATCH 072/186] Basic Gossip Validator --- Cargo.lock | 19 ++++++++++ substrate/tendermint/client/Cargo.toml | 1 + substrate/tendermint/client/src/gossip.rs | 43 +++++++++++++++++++++++ substrate/tendermint/client/src/lib.rs | 2 ++ substrate/tendermint/machine/src/lib.rs | 17 ++++++++- 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 substrate/tendermint/client/src/gossip.rs diff --git a/Cargo.lock b/Cargo.lock index 42bf19867..ebf24e6db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6878,6 +6878,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "sc-network-gossip" +version = "0.10.0-dev" +source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +dependencies = [ + "ahash", + "futures", + "futures-timer", + "libp2p", + "log", + "lru 0.7.8", + "sc-network-common", + "sc-peerset", + "sp-runtime", + "substrate-prometheus-endpoint", + "tracing", +] + [[package]] name = "sc-network-light" version = "0.10.0-dev" @@ -7521,6 +7539,7 @@ dependencies = [ "sc-consensus", "sc-executor", "sc-network", + "sc-network-gossip", "sc-service", "sc-transaction-pool", "serai-runtime", diff --git a/substrate/tendermint/client/Cargo.toml b/substrate/tendermint/client/Cargo.toml index 37dde6d1f..8e0a9e5a5 100644 --- a/substrate/tendermint/client/Cargo.toml +++ b/substrate/tendermint/client/Cargo.toml @@ -36,6 +36,7 @@ sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" } sc-basic-authorship = { git = "https://github.com/serai-dex/substrate" } sc-executor = { git = "https://github.com/serai-dex/substrate" } sc-network = { git = "https://github.com/serai-dex/substrate" } +sc-network-gossip = { git = "https://github.com/serai-dex/substrate" } sc-service = { git = "https://github.com/serai-dex/substrate" } sc-client-api = { git = "https://github.com/serai-dex/substrate" } sc-consensus = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/tendermint/client/src/gossip.rs b/substrate/tendermint/client/src/gossip.rs new file mode 100644 index 000000000..0c8cc4381 --- /dev/null +++ b/substrate/tendermint/client/src/gossip.rs @@ -0,0 +1,43 @@ +use std::sync::{Arc, RwLock}; + +use sp_core::{Decode, sr25519::Signature}; +use sp_runtime::traits::{Hash, Header, Block}; + +use sc_network::PeerId; +use sc_network_gossip::{Validator, ValidatorContext, ValidationResult}; + +use tendermint_machine::{SignedMessage, ext::SignatureScheme}; + +#[derive(Clone)] +struct TendermintGossip> { + number: Arc>, + signature_scheme: Arc, +} + +impl> Validator + for TendermintGossip +{ + fn validate( + &self, + _: &mut dyn ValidatorContext, + _: &PeerId, + data: &[u8], + ) -> ValidationResult { + let msg = match SignedMessage::::decode(&mut &*data) { + Ok(msg) => msg, + Err(_) => return ValidationResult::Discard, + }; + + if msg.number().0 < *self.number.read().unwrap() { + return ValidationResult::Discard; + } + + if !msg.verify_signature(&self.signature_scheme) { + return ValidationResult::Discard; + } + + ValidationResult::ProcessAndKeep(<::Hashing as Hash>::hash( + &[b"Tendermint Topic".as_ref(), &msg.number().0.to_le_bytes()].concat(), + )) + } +} diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 5c6413b47..e567c0cc3 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -20,6 +20,8 @@ mod verifier; mod import_queue; use import_queue::TendermintImportQueue; +mod gossip; + mod select_chain; pub use select_chain::TendermintSelectChain; diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 657b03703..a2134cd94 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -81,6 +81,21 @@ pub struct SignedMessage { sig: S, } +impl SignedMessage { + /// Number of the block this message is attempting to add to the chain. + pub fn number(&self) -> BlockNumber { + self.msg.number + } + + #[must_use] + pub fn verify_signature>( + &self, + signer: &Arc, + ) -> bool { + signer.verify(self.msg.sender, &self.msg.encode(), &self.sig) + } +} + #[derive(Clone, Copy, PartialEq, Eq, Debug)] enum TendermintError { Malicious(V), @@ -302,7 +317,7 @@ impl TendermintMachine { loop { match msg_recv.try_recv() { Ok(msg) => { - if !machine.signer.verify(msg.msg.sender, &msg.msg.encode(), &msg.sig) { + if !msg.verify_signature(&machine.signer) { continue; } machine.queue.push((false, msg.msg)); From 8d3efd6259bb05c171b545a47b5526b623b3060b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 01:22:11 -0400 Subject: [PATCH 073/186] Correct Substrate Tendermint start block The Tendermint machine uses the passed in number as the block's being worked on number. Substrate passed in the already finalized block's number. Also updates misc comments. --- substrate/tendermint/client/src/import_queue.rs | 5 +++-- substrate/tendermint/client/src/validators.rs | 4 ++-- substrate/tendermint/machine/src/lib.rs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/substrate/tendermint/client/src/import_queue.rs b/substrate/tendermint/client/src/import_queue.rs index cf641d6a2..14256978b 100644 --- a/substrate/tendermint/client/src/import_queue.rs +++ b/substrate/tendermint/client/src/import_queue.rs @@ -1,4 +1,5 @@ use std::{ + convert::TryInto, pin::Pin, sync::{Arc, RwLock}, task::{Poll, Context}, @@ -115,8 +116,8 @@ where 0, ( // Header::Number: TryInto doesn't implement Debug and can't be unwrapped - match best.try_into() { - Ok(best) => BlockNumber(best), + match TryInto::::try_into(best) { + Ok(best) => BlockNumber(best + 1), Err(_) => panic!("BlockNumber exceeded u64"), }, Commit::>::decode( diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 189805b7b..45511aac9 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -24,8 +24,8 @@ struct TendermintValidatorsStruct { total_weight: u64, weights: Vec, - keys: Pair, // TODO: sp_keystore - lookup: Vec, // TODO: sessions + keys: Pair, // TODO: sp_keystore + lookup: Vec, } impl TendermintValidatorsStruct { diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index a2134cd94..7fb02bf6e 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -157,7 +157,7 @@ impl TendermintMachine { fn timeout(&self, step: Step) -> Instant { let mut round_time = Duration::from_secs(N::BLOCK_TIME.into()); round_time *= self.round.0 + 1; - let step_time = round_time / 3; + let step_time = round_time / 3; // TODO: Non-uniform timeouts let offset = match step { Step::Propose => step_time, From 6838d5c9220dd1237234c5480b67e4568c3d2a4f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 03:26:31 -0400 Subject: [PATCH 074/186] Clean generics in Tendermint with a monolith with associated types --- Cargo.lock | 1 + substrate/tendermint/client/Cargo.toml | 1 + .../tendermint/client/src/block_import.rs | 36 +--- .../tendermint/client/src/import_queue.rs | 44 ++--- substrate/tendermint/client/src/lib.rs | 46 ++++- substrate/tendermint/client/src/tendermint.rs | 174 ++++++------------ substrate/tendermint/client/src/types.rs | 75 ++++++++ substrate/tendermint/client/src/validators.rs | 80 ++------ substrate/tendermint/client/src/verifier.rs | 34 +--- 9 files changed, 220 insertions(+), 271 deletions(-) create mode 100644 substrate/tendermint/client/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index ebf24e6db..2b6ba1709 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7536,6 +7536,7 @@ dependencies = [ "pallet-session", "sc-basic-authorship", "sc-client-api", + "sc-client-db", "sc-consensus", "sc-executor", "sc-network", diff --git a/substrate/tendermint/client/Cargo.toml b/substrate/tendermint/client/Cargo.toml index 8e0a9e5a5..3b2f25864 100644 --- a/substrate/tendermint/client/Cargo.toml +++ b/substrate/tendermint/client/Cargo.toml @@ -38,6 +38,7 @@ sc-executor = { git = "https://github.com/serai-dex/substrate" } sc-network = { git = "https://github.com/serai-dex/substrate" } sc-network-gossip = { git = "https://github.com/serai-dex/substrate" } sc-service = { git = "https://github.com/serai-dex/substrate" } +sc-client-db = { git = "https://github.com/serai-dex/substrate" } sc-client-api = { git = "https://github.com/serai-dex/substrate" } sc-consensus = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index ae654351e..c2e3b9d97 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -2,45 +2,25 @@ use std::{sync::Arc, collections::HashMap}; use async_trait::async_trait; -use sp_inherents::CreateInherentDataProviders; -use sp_runtime::traits::Block; -use sp_api::TransactionFor; - -use sp_consensus::{Error, CacheKeyId, Environment}; +use sp_consensus::{Error, CacheKeyId}; use sc_consensus::{BlockCheckParams, BlockImportParams, ImportResult, BlockImport}; -use sc_client_api::Backend; - -use sp_tendermint::TendermintApi; - -use crate::{ - tendermint::{TendermintClient, TendermintImport}, - Announce, -}; +use crate::{types::TendermintAuthor, tendermint::TendermintImport}; #[async_trait] -impl< - B: Block, - Be: Backend + 'static, - C: TendermintClient, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - A: Announce, - > BlockImport for TendermintImport +impl BlockImport for TendermintImport where - TransactionFor: Send + Sync + 'static, - Arc: BlockImport>, - as BlockImport>::Error: Into, - C::Api: TendermintApi, + Arc: BlockImport, + as BlockImport>::Error: Into, { type Error = Error; - type Transaction = TransactionFor; + type Transaction = T::BackendTransaction; // TODO: Is there a DoS where you send a block without justifications, causing it to error, // yet adding it to the blacklist in the process preventing further syncing? async fn check_block( &mut self, - mut block: BlockCheckParams, + mut block: BlockCheckParams, ) -> Result { self.verify_order(block.parent_hash, block.number)?; @@ -55,7 +35,7 @@ where async fn import_block( &mut self, - mut block: BlockImportParams>, + mut block: BlockImportParams, new_cache: HashMap>, ) -> Result { self.check(&mut block).await?; diff --git a/substrate/tendermint/client/src/import_queue.rs b/substrate/tendermint/client/src/import_queue.rs index 14256978b..aaf7e2829 100644 --- a/substrate/tendermint/client/src/import_queue.rs +++ b/substrate/tendermint/client/src/import_queue.rs @@ -8,15 +8,14 @@ use std::{ }; use sp_core::Decode; -use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::{Header, Block}; -use sp_api::{BlockId, TransactionFor}; +use sp_api::BlockId; -use sp_consensus::{Error, Environment}; +use sp_consensus::Error; use sc_consensus::{BlockImportStatus, BlockImportError, BlockImport, Link, BasicQueue}; use sc_service::ImportQueue; -use sc_client_api::Backend; +use sc_client_api::{HeaderBackend, BlockBackend}; use substrate_prometheus_endpoint::Registry; @@ -25,13 +24,9 @@ use tendermint_machine::{ TendermintMachine, }; -use sp_tendermint::TendermintApi; - use crate::{ - CONSENSUS_ID, - validators::TendermintValidators, - tendermint::{TendermintClient, TendermintImport}, - Announce, + CONSENSUS_ID, types::TendermintAuthor, validators::TendermintValidators, + tendermint::TendermintImport, }; pub type TendermintImportQueue = BasicQueue; @@ -82,28 +77,19 @@ impl<'a, B: Block, T: Send> Future for ImportFuture<'a, B, T> { } } -pub fn import_queue< - B: Block, - Be: Backend + 'static, - C: TendermintClient, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - A: Announce, ->( - client: Arc, - announce: A, - providers: Arc, - env: E, +pub fn import_queue( + client: Arc, + announce: T::Announce, + providers: Arc, + env: T::Environment, spawner: &impl sp_core::traits::SpawnEssentialNamed, registry: Option<&Registry>, -) -> (impl Future, TendermintImportQueue>) +) -> (impl Future, TendermintImportQueue) where - TransactionFor: Send + Sync + 'static, - Arc: BlockImport>, - as BlockImport>::Error: Into, - C::Api: TendermintApi, + Arc: BlockImport, + as BlockImport>::Error: Into, { - let import = TendermintImport::new(client, announce, providers, env); + let import = TendermintImport::::new(client, announce, providers, env); let authority = { let machine_clone = import.machine.clone(); @@ -120,7 +106,7 @@ where Ok(best) => BlockNumber(best + 1), Err(_) => panic!("BlockNumber exceeded u64"), }, - Commit::>::decode( + Commit::>::decode( &mut import_clone .client .justifications(&BlockId::Number(best)) diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index e567c0cc3..0a552eb00 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -1,7 +1,9 @@ -use std::{sync::Arc, future::Future}; +use std::{marker::PhantomData, boxed::Box, sync::Arc, future::Future, error::Error}; use sp_runtime::traits::Block as BlockTrait; -use sp_api::TransactionFor; +use sp_inherents::CreateInherentDataProviders; +use sp_consensus::DisableProofRecording; +use sp_api::{TransactionFor, ProvideRuntimeApi}; use sc_executor::{NativeVersion, NativeExecutionDispatch, NativeElseWasmExecutor}; use sc_transaction_pool::FullPool; @@ -11,6 +13,9 @@ use substrate_prometheus_endpoint::Registry; use serai_runtime::{self, opaque::Block, RuntimeApi}; +mod types; +use types::{TendermintClientMinimal, TendermintAuthor}; + mod validators; mod tendermint; @@ -49,6 +54,39 @@ pub trait Announce: Send + Sync + Clone + 'static { fn announce(&self, hash: B::Hash); } +struct Cidp; +#[async_trait::async_trait] +impl CreateInherentDataProviders for Cidp { + type InherentDataProviders = (sp_timestamp::InherentDataProvider,); + async fn create_inherent_data_providers( + &self, + _: ::Hash, + _: (), + ) -> Result> { + Ok((sp_timestamp::InherentDataProvider::from_system_time(),)) + } +} + +struct TendermintAuthorFirm>(PhantomData); +impl> TendermintClientMinimal for TendermintAuthorFirm { + type Block = Block; + type Backend = sc_client_db::Backend; + type Api = >::Api; + type Client = FullClient; +} + +impl> TendermintAuthor for TendermintAuthorFirm { + type CIDP = Cidp; + type Environment = sc_basic_authorship::ProposerFactory< + FullPool, + Self::Backend, + Self::Client, + DisableProofRecording, + >; + + type Announce = A; +} + pub fn import_queue>( task_manager: &TaskManager, client: Arc, @@ -56,10 +94,10 @@ pub fn import_queue>( pool: Arc>, registry: Option<&Registry>, ) -> (impl Future, TendermintImportQueue>) { - import_queue::import_queue( + import_queue::import_queue::>( client.clone(), announce, - Arc::new(|_, _| async { Ok(sp_timestamp::InherentDataProvider::from_system_time()) }), + Arc::new(Cidp), sc_basic_authorship::ProposerFactory::new( task_manager.spawn_handle(), client, diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index a2ab4bd80..d40b67bd7 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -17,103 +17,48 @@ use sp_runtime::{ Digest, Justification, }; use sp_blockchain::HeaderBackend; -use sp_api::{BlockId, TransactionFor, ProvideRuntimeApi}; +use sp_api::BlockId; use sp_consensus::{Error, BlockOrigin, Proposer, Environment}; -use sc_consensus::{ForkChoiceStrategy, BlockImportParams, BlockImport, import_queue::IncomingBlock}; +use sc_consensus::{ForkChoiceStrategy, BlockImportParams, import_queue::IncomingBlock}; use sc_service::ImportQueue; -use sc_client_api::{BlockBackend, Backend, Finalizer}; +use sc_client_api::Finalizer; use tendermint_machine::{ ext::{BlockError, Commit, Network}, SignedMessage, TendermintHandle, }; -use sp_tendermint::TendermintApi; - use crate::{ CONSENSUS_ID, + types::TendermintAuthor, validators::TendermintValidators, import_queue::{ImportFuture, TendermintImportQueue}, Announce, }; -pub trait TendermintClient + 'static>: - Send - + Sync - + HeaderBackend - + BlockBackend - + BlockImport> - + Finalizer - + ProvideRuntimeApi - + 'static -where - TransactionFor: Send + Sync + 'static, - Self::Api: TendermintApi, -{ -} -impl< - B: Send + Sync + Block + 'static, - Be: Send + Sync + Backend + 'static, - C: Send - + Sync - + HeaderBackend - + BlockBackend - + BlockImport> - + Finalizer - + ProvideRuntimeApi - + 'static, - > TendermintClient for C -where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi, -{ -} +pub(crate) struct TendermintImport { + _ta: PhantomData, + + validators: Arc>, -pub(crate) struct TendermintImport< - B: Block, - Be: Backend + 'static, - C: TendermintClient, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - A: Announce, -> where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi, -{ - _block: PhantomData, - _backend: PhantomData, - - validators: Arc>, - - importing_block: Arc>>, + importing_block: Arc::Hash>>>, pub(crate) machine: Arc>>>, - pub(crate) client: Arc, - announce: A, - providers: Arc, + pub(crate) client: Arc, + announce: T::Announce, + providers: Arc, - env: Arc>, - pub(crate) queue: Arc>>>>, + env: Arc>, + pub(crate) queue: + Arc>>>, } -impl< - B: Block, - Be: Backend + 'static, - C: TendermintClient, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - A: Announce, - > Clone for TendermintImport -where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi, -{ +impl Clone for TendermintImport { fn clone(&self) -> Self { TendermintImport { - _block: PhantomData, - _backend: PhantomData, + _ta: PhantomData, validators: self.validators.clone(), @@ -130,27 +75,15 @@ where } } -impl< - B: Block, - Be: Backend + 'static, - C: TendermintClient, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - A: Announce, - > TendermintImport -where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi, -{ +impl TendermintImport { pub(crate) fn new( - client: Arc, - announce: A, - providers: Arc, - env: E, - ) -> TendermintImport { + client: Arc, + announce: T::Announce, + providers: Arc, + env: T::Environment, + ) -> TendermintImport { TendermintImport { - _block: PhantomData, - _backend: PhantomData, + _ta: PhantomData, validators: Arc::new(TendermintValidators::new(client.clone())), @@ -168,8 +101,8 @@ where async fn check_inherents( &self, - block: B, - providers: CIDP::InherentDataProviders, + block: T::Block, + providers: >::InherentDataProviders, ) -> Result<(), Error> { // TODO Ok(()) @@ -178,8 +111,8 @@ where // Ensure this is part of a sequential import pub(crate) fn verify_order( &self, - parent: B::Hash, - number: ::Number, + parent: ::Hash, + number: <::Header as Header>::Number, ) -> Result<(), Error> { let info = self.client.info(); if (info.best_hash != parent) || ((info.best_number + 1u16.into()) != number) { @@ -193,7 +126,7 @@ where // Tendermint's propose message could be rewritten as a seal OR Tendermint could produce blocks // which this checks the proposer slot for, and then tells the Tendermint machine // While those would be more seamless with Substrate, there's no actual benefit to doing so - fn verify_origin(&self, hash: B::Hash) -> Result<(), Error> { + fn verify_origin(&self, hash: ::Hash) -> Result<(), Error> { if let Some(tm_hash) = *self.importing_block.read().unwrap() { if hash == tm_hash { return Ok(()); @@ -205,14 +138,14 @@ where // Errors if the justification isn't valid pub(crate) fn verify_justification( &self, - hash: B::Hash, + hash: ::Hash, justification: &Justification, ) -> Result<(), Error> { if justification.0 != CONSENSUS_ID { Err(Error::InvalidJustification)?; } - let commit: Commit> = + let commit: Commit> = Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; if !self.verify_commit(hash, &commit) { Err(Error::InvalidJustification)?; @@ -223,7 +156,10 @@ where // Verifies the justifications aren't malformed, not that the block is justified // Errors if justifications is neither empty nor a sinlge Tendermint justification // If the block does have a justification, finalized will be set to true - fn verify_justifications(&self, block: &mut BlockImportParams) -> Result<(), Error> { + fn verify_justifications( + &self, + block: &mut BlockImportParams, + ) -> Result<(), Error> { if !block.finalized { if let Some(justifications) = &block.justifications { let mut iter = justifications.iter(); @@ -238,7 +174,10 @@ where Ok(()) } - pub(crate) async fn check(&self, block: &mut BlockImportParams) -> Result<(), Error> { + pub(crate) async fn check( + &self, + block: &mut BlockImportParams, + ) -> Result<(), Error> { if block.finalized { if block.fork_choice.is_none() { // Since we alw1ays set the fork choice, this means something else marked the block as @@ -261,7 +200,7 @@ where if let Some(body) = block.body.clone() { self .check_inherents( - B::new(block.header.clone(), body), + T::Block::new(block.header.clone(), body), self.providers.create_inherent_data_providers(*block.header.parent_hash(), ()).await?, ) .await?; @@ -281,7 +220,7 @@ where Ok(()) } - pub(crate) async fn get_proposal(&mut self, header: &B::Header) -> B { + pub(crate) async fn get_proposal(&mut self, header: &::Header) -> T::Block { let inherent_data = match self.providers.create_inherent_data_providers(header.hash(), ()).await { Ok(providers) => match providers.create_inherent_data() { @@ -315,30 +254,19 @@ where } #[async_trait] -impl< - B: Block, - Be: Backend + 'static, - C: TendermintClient, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - A: Announce, - > Network for TendermintImport -where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi, -{ +impl Network for TendermintImport { type ValidatorId = u16; - type SignatureScheme = TendermintValidators; - type Weights = TendermintValidators; - type Block = B; + type SignatureScheme = TendermintValidators; + type Weights = TendermintValidators; + type Block = T::Block; const BLOCK_TIME: u32 = { (serai_runtime::MILLISECS_PER_BLOCK / 1000) as u32 }; - fn signature_scheme(&self) -> Arc> { + fn signature_scheme(&self) -> Arc> { self.validators.clone() } - fn weights(&self) -> Arc> { + fn weights(&self) -> Arc> { self.validators.clone() } @@ -362,7 +290,7 @@ where // the node. // // When Tendermint completes, the block is finalized, setting it as the tip regardless of work. - async fn validate(&mut self, block: &B) -> Result<(), BlockError> { + async fn validate(&mut self, block: &T::Block) -> Result<(), BlockError> { let hash = block.hash(); let (header, body) = block.clone().deconstruct(); let parent = *header.parent_hash(); @@ -407,7 +335,11 @@ where Ok(()) } - async fn add_block(&mut self, block: B, commit: Commit>) -> B { + async fn add_block( + &mut self, + block: T::Block, + commit: Commit>, + ) -> T::Block { let hash = block.hash(); let justification = (CONSENSUS_ID, commit.encode()); debug_assert!(self.verify_justification(hash, &justification).is_ok()); diff --git a/substrate/tendermint/client/src/types.rs b/substrate/tendermint/client/src/types.rs new file mode 100644 index 000000000..0573a6947 --- /dev/null +++ b/substrate/tendermint/client/src/types.rs @@ -0,0 +1,75 @@ +use sp_inherents::CreateInherentDataProviders; +use sp_runtime::traits::{Header, Block}; + +use sp_blockchain::HeaderBackend; +use sp_api::{StateBackend, StateBackendFor, TransactionFor, ApiExt, ProvideRuntimeApi}; + +use sp_consensus::Environment; +use sc_consensus::BlockImport; + +use sc_client_api::{BlockBackend, Backend, Finalizer}; + +use sp_tendermint::TendermintApi; + +use crate::Announce; + +/// Trait consolidating all generics required by sc_tendermint for processing. +pub trait TendermintClient: Send + Sync + 'static { + type Block: Block; + type Backend: Backend + 'static; + + /// TransactionFor + type BackendTransaction: Send + Sync + 'static; + /// StateBackendFor + type StateBackend: StateBackend< + <::Header as Header>::Hashing, + Transaction = Self::BackendTransaction, + >; + // Client::Api + type Api: ApiExt + TendermintApi; + type Client: Send + + Sync + + HeaderBackend + + BlockBackend + + BlockImport + + Finalizer + + ProvideRuntimeApi + + 'static; +} + +/// Trait implementable on firm types to automatically provide a full TendermintClient impl. +pub trait TendermintClientMinimal: Send + Sync + 'static { + type Block: Block; + type Backend: Backend + 'static; + type Api: ApiExt + TendermintApi; + type Client: Send + + Sync + + HeaderBackend + + BlockBackend + + BlockImport> + + Finalizer + + ProvideRuntimeApi + + 'static; +} + +impl TendermintClient for T +where + >::Api: TendermintApi, + TransactionFor: Send + Sync + 'static, +{ + type Block = T::Block; + type Backend = T::Backend; + + type BackendTransaction = TransactionFor; + type StateBackend = StateBackendFor; + type Api = >::Api; + type Client = T::Client; +} + +/// Trait consolidating additional generics required by sc_tendermint for authoring. +pub trait TendermintAuthor: TendermintClient { + type CIDP: CreateInherentDataProviders + 'static; + type Environment: Send + Sync + Environment + 'static; + + type Announce: Announce; +} diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 45511aac9..c21e3274d 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -6,17 +6,16 @@ use sp_application_crypto::{ sr25519::{Public, Pair, Signature}, }; -use sp_runtime::traits::Block; use sp_staking::SessionIndex; -use sp_api::{BlockId, TransactionFor}; +use sp_api::{BlockId, ProvideRuntimeApi}; -use sc_client_api::Backend; +use sc_client_api::HeaderBackend; use tendermint_machine::ext::{BlockNumber, Round, Weights, SignatureScheme}; use sp_tendermint::TendermintApi; -use crate::tendermint::TendermintClient; +use crate::types::TendermintClient; struct TendermintValidatorsStruct { session: SessionIndex, @@ -29,13 +28,7 @@ struct TendermintValidatorsStruct { } impl TendermintValidatorsStruct { - fn from_module + 'static, C: TendermintClient>( - client: &Arc, - ) -> TendermintValidatorsStruct - where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi, - { + fn from_module(client: &Arc) -> TendermintValidatorsStruct { let last = client.info().best_hash; let api = client.runtime_api(); let session = api.current_session(&BlockId::Hash(last)).unwrap(); @@ -56,23 +49,13 @@ impl TendermintValidatorsStruct { } // Wrap every access of the validators struct in something which forces calling refresh -struct Refresh + 'static, C: TendermintClient> -where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi, -{ - _block: PhantomData, - _backend: PhantomData, - - client: Arc, +struct Refresh { + _tc: PhantomData, + client: Arc, _refresh: Arc>, } -impl + 'static, C: TendermintClient> Refresh -where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi, -{ +impl Refresh { // If the session has changed, re-create the struct with the data on it fn refresh(&self) { let session = self._refresh.read().unwrap().session; @@ -83,16 +66,12 @@ where .current_session(&BlockId::Hash(self.client.info().best_hash)) .unwrap() { - *self._refresh.write().unwrap() = TendermintValidatorsStruct::from_module(&self.client); + *self._refresh.write().unwrap() = TendermintValidatorsStruct::from_module::(&self.client); } } } -impl + 'static, C: TendermintClient> Deref for Refresh -where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi, -{ +impl Deref for Refresh { type Target = RwLock; fn deref(&self) -> &RwLock { self.refresh(); @@ -100,37 +79,19 @@ where } } -pub(crate) struct TendermintValidators< - B: Block, - Be: Backend + 'static, - C: TendermintClient, ->(Refresh) -where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi; - -impl + 'static, C: TendermintClient> TendermintValidators -where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi, -{ - pub(crate) fn new(client: Arc) -> TendermintValidators { - TendermintValidators(Refresh { - _block: PhantomData, - _backend: PhantomData, +pub(crate) struct TendermintValidators(Refresh); - _refresh: Arc::new(RwLock::new(TendermintValidatorsStruct::from_module(&client))), +impl TendermintValidators { + pub(crate) fn new(client: Arc) -> TendermintValidators { + TendermintValidators(Refresh { + _tc: PhantomData, + _refresh: Arc::new(RwLock::new(TendermintValidatorsStruct::from_module::(&client))), client, }) } } -impl + 'static, C: TendermintClient> SignatureScheme - for TendermintValidators -where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi, -{ +impl SignatureScheme for TendermintValidators { type ValidatorId = u16; type Signature = Signature; type AggregateSignature = Vec; @@ -160,12 +121,7 @@ where } } -impl + 'static, C: TendermintClient> Weights - for TendermintValidators -where - TransactionFor: Send + Sync + 'static, - C::Api: TendermintApi, -{ +impl Weights for TendermintValidators { type ValidatorId = u16; fn total_weight(&self) -> u64 { diff --git a/substrate/tendermint/client/src/verifier.rs b/substrate/tendermint/client/src/verifier.rs index b587a52e4..d064a84f0 100644 --- a/substrate/tendermint/client/src/verifier.rs +++ b/substrate/tendermint/client/src/verifier.rs @@ -2,41 +2,21 @@ use std::sync::Arc; use async_trait::async_trait; -use sp_inherents::CreateInherentDataProviders; -use sp_runtime::traits::Block; -use sp_api::TransactionFor; - -use sp_consensus::{Error, CacheKeyId, Environment}; +use sp_consensus::{Error, CacheKeyId}; use sc_consensus::{BlockImportParams, BlockImport, Verifier}; -use sc_client_api::Backend; - -use sp_tendermint::TendermintApi; - -use crate::{ - tendermint::{TendermintClient, TendermintImport}, - Announce, -}; +use crate::{types::TendermintAuthor, tendermint::TendermintImport}; #[async_trait] -impl< - B: Block, - Be: Backend + 'static, - C: TendermintClient, - CIDP: CreateInherentDataProviders + 'static, - E: Send + Sync + Environment + 'static, - A: Announce, - > Verifier for TendermintImport +impl Verifier for TendermintImport where - TransactionFor: Send + Sync + 'static, - Arc: BlockImport>, - as BlockImport>::Error: Into, - C::Api: TendermintApi, + Arc: BlockImport, + as BlockImport>::Error: Into, { async fn verify( &mut self, - mut block: BlockImportParams, - ) -> Result<(BlockImportParams, Option)>>), String> { + mut block: BlockImportParams, + ) -> Result<(BlockImportParams, Option)>>), String> { self.check(&mut block).await.map_err(|e| format!("{}", e))?; Ok((block, None)) } From edb2e00db75e3ae4db58d444939ca62996325d89 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 04:08:33 -0400 Subject: [PATCH 075/186] Remove the Future triggering the machine for an async fn Enables passing data in, such as the network. --- substrate/node/src/service.rs | 19 ++++--- .../tendermint/client/src/block_import.rs | 4 +- .../tendermint/client/src/import_queue.rs | 54 ++---------------- substrate/tendermint/client/src/lib.rs | 20 ++++--- substrate/tendermint/client/src/tendermint.rs | 57 ++++++++++++++++--- substrate/tendermint/client/src/types.rs | 2 +- substrate/tendermint/client/src/verifier.rs | 4 +- 7 files changed, 82 insertions(+), 78 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 0feba0756..e0aa7e4bd 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -1,7 +1,4 @@ -use std::{ - sync::{Arc, RwLock}, - future::Future, -}; +use std::sync::{Arc, RwLock}; use sp_core::H256; @@ -11,7 +8,9 @@ use sc_network::{NetworkService, NetworkBlock}; use sc_telemetry::{Telemetry, TelemetryWorker}; use serai_runtime::{self, opaque::Block, RuntimeApi}; -pub(crate) use serai_consensus::{ExecutorDispatch, Announce, FullClient}; +pub(crate) use serai_consensus::{ + TendermintAuthority, ExecutorDispatch, Announce, FullClient, TendermintValidatorFirm, +}; type FullBackend = sc_service::TFullBackend; type FullSelectChain = serai_consensus::TendermintSelectChain; @@ -42,7 +41,13 @@ impl Announce for NetworkAnnounce { pub fn new_partial( config: &Configuration, -) -> Result<((NetworkAnnounce, impl Future), PartialComponents), ServiceError> { +) -> Result< + ( + (NetworkAnnounce, TendermintAuthority>), + PartialComponents, + ), + ServiceError, +> { if config.keystore_remote.is_some() { return Err(ServiceError::Other("Remote Keystores are not supported".to_string())); } @@ -179,7 +184,7 @@ pub async fn new_full(config: Configuration) -> Result BlockImport for TendermintImport +impl BlockImport for TendermintImport where Arc: BlockImport, as BlockImport>::Error: Into, diff --git a/substrate/tendermint/client/src/import_queue.rs b/substrate/tendermint/client/src/import_queue.rs index aaf7e2829..8e3fac019 100644 --- a/substrate/tendermint/client/src/import_queue.rs +++ b/substrate/tendermint/client/src/import_queue.rs @@ -1,32 +1,22 @@ use std::{ - convert::TryInto, pin::Pin, sync::{Arc, RwLock}, task::{Poll, Context}, future::Future, - time::{UNIX_EPOCH, SystemTime}, }; -use sp_core::Decode; use sp_runtime::traits::{Header, Block}; -use sp_api::BlockId; use sp_consensus::Error; use sc_consensus::{BlockImportStatus, BlockImportError, BlockImport, Link, BasicQueue}; use sc_service::ImportQueue; -use sc_client_api::{HeaderBackend, BlockBackend}; use substrate_prometheus_endpoint::Registry; -use tendermint_machine::{ - ext::{BlockNumber, Commit}, - TendermintMachine, -}; - use crate::{ - CONSENSUS_ID, types::TendermintAuthor, validators::TendermintValidators, - tendermint::TendermintImport, + types::TendermintValidator, + tendermint::{TendermintImport, TendermintAuthority}, }; pub type TendermintImportQueue = BasicQueue; @@ -77,54 +67,20 @@ impl<'a, B: Block, T: Send> Future for ImportFuture<'a, B, T> { } } -pub fn import_queue( +pub fn import_queue( client: Arc, announce: T::Announce, providers: Arc, env: T::Environment, spawner: &impl sp_core::traits::SpawnEssentialNamed, registry: Option<&Registry>, -) -> (impl Future, TendermintImportQueue) +) -> (TendermintAuthority, TendermintImportQueue) where Arc: BlockImport, as BlockImport>::Error: Into, { let import = TendermintImport::::new(client, announce, providers, env); - - let authority = { - let machine_clone = import.machine.clone(); - let mut import_clone = import.clone(); - let best = import.client.info().best_number; - async move { - *machine_clone.write().unwrap() = Some(TendermintMachine::new( - import_clone.clone(), - // TODO - 0, - ( - // Header::Number: TryInto doesn't implement Debug and can't be unwrapped - match TryInto::::try_into(best) { - Ok(best) => BlockNumber(best + 1), - Err(_) => panic!("BlockNumber exceeded u64"), - }, - Commit::>::decode( - &mut import_clone - .client - .justifications(&BlockId::Number(best)) - .unwrap() - .map(|justifications| justifications.get(CONSENSUS_ID).cloned().unwrap()) - .unwrap_or_default() - .as_ref(), - ) - .map(|commit| commit.end_time) - // TODO: Genesis start time - .unwrap_or_else(|_| SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()), - ), - import_clone - .get_proposal(&import_clone.client.header(BlockId::Number(0u8.into())).unwrap().unwrap()) - .await, - )); - } - }; + let authority = TendermintAuthority(import.clone()); let boxed = Box::new(import.clone()); // Use None for the justification importer since justifications always come with blocks diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 0a552eb00..3343a3fdf 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -1,4 +1,4 @@ -use std::{marker::PhantomData, boxed::Box, sync::Arc, future::Future, error::Error}; +use std::{marker::PhantomData, boxed::Box, sync::Arc, error::Error}; use sp_runtime::traits::Block as BlockTrait; use sp_inherents::CreateInherentDataProviders; @@ -14,11 +14,12 @@ use substrate_prometheus_endpoint::Registry; use serai_runtime::{self, opaque::Block, RuntimeApi}; mod types; -use types::{TendermintClientMinimal, TendermintAuthor}; +use types::{TendermintClientMinimal, TendermintValidator}; mod validators; mod tendermint; +pub use tendermint::TendermintAuthority; mod block_import; mod verifier; @@ -54,7 +55,7 @@ pub trait Announce: Send + Sync + Clone + 'static { fn announce(&self, hash: B::Hash); } -struct Cidp; +pub struct Cidp; #[async_trait::async_trait] impl CreateInherentDataProviders for Cidp { type InherentDataProviders = (sp_timestamp::InherentDataProvider,); @@ -67,15 +68,15 @@ impl CreateInherentDataProviders for Cidp { } } -struct TendermintAuthorFirm>(PhantomData); -impl> TendermintClientMinimal for TendermintAuthorFirm { +pub struct TendermintValidatorFirm>(PhantomData); +impl> TendermintClientMinimal for TendermintValidatorFirm { type Block = Block; type Backend = sc_client_db::Backend; type Api = >::Api; type Client = FullClient; } -impl> TendermintAuthor for TendermintAuthorFirm { +impl> TendermintValidator for TendermintValidatorFirm { type CIDP = Cidp; type Environment = sc_basic_authorship::ProposerFactory< FullPool, @@ -93,8 +94,11 @@ pub fn import_queue>( announce: A, pool: Arc>, registry: Option<&Registry>, -) -> (impl Future, TendermintImportQueue>) { - import_queue::import_queue::>( +) -> ( + TendermintAuthority>, + TendermintImportQueue>, +) { + import_queue::import_queue::>( client.clone(), announce, Arc::new(Cidp), diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index d40b67bd7..f70e3c9ab 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -1,7 +1,7 @@ use std::{ marker::PhantomData, sync::{Arc, RwLock}, - time::Duration, + time::{UNIX_EPOCH, SystemTime, Duration}, }; use async_trait::async_trait; @@ -23,22 +23,22 @@ use sp_consensus::{Error, BlockOrigin, Proposer, Environment}; use sc_consensus::{ForkChoiceStrategy, BlockImportParams, import_queue::IncomingBlock}; use sc_service::ImportQueue; -use sc_client_api::Finalizer; +use sc_client_api::{BlockBackend, Finalizer}; use tendermint_machine::{ - ext::{BlockError, Commit, Network}, - SignedMessage, TendermintHandle, + ext::{BlockError, BlockNumber, Commit, Network}, + SignedMessage, TendermintMachine, TendermintHandle, }; use crate::{ CONSENSUS_ID, - types::TendermintAuthor, + types::TendermintValidator, validators::TendermintValidators, import_queue::{ImportFuture, TendermintImportQueue}, Announce, }; -pub(crate) struct TendermintImport { +pub(crate) struct TendermintImport { _ta: PhantomData, validators: Arc>, @@ -55,7 +55,46 @@ pub(crate) struct TendermintImport { Arc>>>, } -impl Clone for TendermintImport { +pub struct TendermintAuthority(pub(crate) TendermintImport); +impl TendermintAuthority { + pub async fn validate(mut self) { + let info = self.0.client.info(); + + // Header::Number: TryInto doesn't implement Debug and can't be unwrapped + let start_number = match TryInto::::try_into(info.best_number) { + Ok(best) => BlockNumber(best + 1), + Err(_) => panic!("BlockNumber exceeded u64"), + }; + let start_time = Commit::>::decode( + &mut self + .0 + .client + .justifications(&BlockId::Hash(info.best_hash)) + .unwrap() + .map(|justifications| justifications.get(CONSENSUS_ID).cloned().unwrap()) + .unwrap_or_default() + .as_ref(), + ) + .map(|commit| commit.end_time) + // TODO: Genesis start time + .unwrap_or_else(|_| SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()); + + let proposal = self + .0 + .get_proposal(&self.0.client.header(BlockId::Hash(info.best_hash)).unwrap().unwrap()) + .await; + + *self.0.machine.write().unwrap() = Some(TendermintMachine::new( + self.0.clone(), + // TODO + 0, // ValidatorId + (start_number, start_time), + proposal, + )); + } +} + +impl Clone for TendermintImport { fn clone(&self) -> Self { TendermintImport { _ta: PhantomData, @@ -75,7 +114,7 @@ impl Clone for TendermintImport { } } -impl TendermintImport { +impl TendermintImport { pub(crate) fn new( client: Arc, announce: T::Announce, @@ -254,7 +293,7 @@ impl TendermintImport { } #[async_trait] -impl Network for TendermintImport { +impl Network for TendermintImport { type ValidatorId = u16; type SignatureScheme = TendermintValidators; type Weights = TendermintValidators; diff --git a/substrate/tendermint/client/src/types.rs b/substrate/tendermint/client/src/types.rs index 0573a6947..a21ae9773 100644 --- a/substrate/tendermint/client/src/types.rs +++ b/substrate/tendermint/client/src/types.rs @@ -67,7 +67,7 @@ where } /// Trait consolidating additional generics required by sc_tendermint for authoring. -pub trait TendermintAuthor: TendermintClient { +pub trait TendermintValidator: TendermintClient { type CIDP: CreateInherentDataProviders + 'static; type Environment: Send + Sync + Environment + 'static; diff --git a/substrate/tendermint/client/src/verifier.rs b/substrate/tendermint/client/src/verifier.rs index d064a84f0..9b7c2a921 100644 --- a/substrate/tendermint/client/src/verifier.rs +++ b/substrate/tendermint/client/src/verifier.rs @@ -5,10 +5,10 @@ use async_trait::async_trait; use sp_consensus::{Error, CacheKeyId}; use sc_consensus::{BlockImportParams, BlockImport, Verifier}; -use crate::{types::TendermintAuthor, tendermint::TendermintImport}; +use crate::{types::TendermintValidator, tendermint::TendermintImport}; #[async_trait] -impl Verifier for TendermintImport +impl Verifier for TendermintImport where Arc: BlockImport, as BlockImport>::Error: Into, From 1af6117155f0b887ae2b4350a2bfe38300272ae6 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 04:27:15 -0400 Subject: [PATCH 076/186] Move TendermintMachine from start_num, time to last_num, time Provides an explicitly clear API clearer to program around. Also adds additional time code to handle an edge case. --- substrate/tendermint/client/src/tendermint.rs | 10 ++--- substrate/tendermint/machine/src/lib.rs | 45 ++++++++++++------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index f70e3c9ab..7b8051c40 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -61,11 +61,11 @@ impl TendermintAuthority { let info = self.0.client.info(); // Header::Number: TryInto doesn't implement Debug and can't be unwrapped - let start_number = match TryInto::::try_into(info.best_number) { - Ok(best) => BlockNumber(best + 1), + let last_number = match info.best_number.try_into() { + Ok(best) => BlockNumber(best), Err(_) => panic!("BlockNumber exceeded u64"), }; - let start_time = Commit::>::decode( + let last_time = Commit::>::decode( &mut self .0 .client @@ -76,7 +76,7 @@ impl TendermintAuthority { .as_ref(), ) .map(|commit| commit.end_time) - // TODO: Genesis start time + // TODO: Genesis start time + BLOCK_TIME .unwrap_or_else(|_| SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()); let proposal = self @@ -88,7 +88,7 @@ impl TendermintAuthority { self.0.clone(), // TODO 0, // ValidatorId - (start_number, start_time), + (last_number, last_time), proposal, )); } diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 7fb02bf6e..cb73bcf05 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -242,24 +242,32 @@ impl TendermintMachine { pub fn new( network: N, proposer: N::ValidatorId, - start: (BlockNumber, u64), + last: (BlockNumber, u64), proposal: N::Block, ) -> TendermintHandle { - // Convert the start time to an instant - // This is imprecise yet should be precise enough - let start_time = { - let instant_now = Instant::now(); - let sys_now = SystemTime::now(); - instant_now - - sys_now - .duration_since(UNIX_EPOCH + Duration::from_secs(start.1)) - .unwrap_or(Duration::ZERO) - }; - let (msg_send, mut msg_recv) = mpsc::channel(100); // Backlog to accept. Currently arbitrary TendermintHandle { messages: msg_send, handle: tokio::spawn(async move { + let last_end = UNIX_EPOCH + Duration::from_secs(last.1); + + // If the last block hasn't ended yet, sleep until it has + { + let now = SystemTime::now(); + if last_end > now { + sleep(last_end.duration_since(now).unwrap_or(Duration::ZERO)).await; + } + } + + // Convert the last time to an instant + // This is imprecise yet should be precise enough, given this library only has + // second accuracy + let last_time = { + let instant_now = Instant::now(); + let sys_now = SystemTime::now(); + instant_now - sys_now.duration_since(last_end).unwrap_or(Duration::ZERO) + }; + let signer = network.signature_scheme(); let weights = network.weights(); let network = Arc::new(RwLock::new(network)); @@ -270,9 +278,16 @@ impl TendermintMachine { weights: weights.clone(), proposer, - number: start.0, - canonical_start_time: start.1, - start_time, + number: BlockNumber(last.0.0 + 1), + canonical_start_time: last.1, + // The end time of the last block is the start time for this one + // The Commit explicitly contains the end time, so loading the last commit will provide + // this. The only exception is for the genesis block, which doesn't have a commit + // Using the genesis time in place will cause this block to be created immediately after + // it, without the standard amount of separation (so their times will be equivalent or + // minimally offset) + // For callers wishing to avoid this, they should pass (0, GENESIS + BLOCK_TIME) + start_time: last_time, personal_proposal: proposal, queue: vec![], From 6c54289fb47512b713aea3b08fd7996ed0696101 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 05:16:25 -0400 Subject: [PATCH 077/186] Connect the Tendermint machine to a GossipEngine --- substrate/node/src/service.rs | 6 +- substrate/tendermint/client/src/gossip.rs | 18 ++++-- substrate/tendermint/client/src/lib.rs | 4 +- substrate/tendermint/client/src/tendermint.rs | 58 +++++++++++++++++-- substrate/tendermint/client/src/types.rs | 2 + 5 files changed, 78 insertions(+), 10 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index e0aa7e4bd..20579990c 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -184,7 +184,11 @@ pub async fn new_full(config: Configuration) -> Result> { +pub struct TendermintGossip> { number: Arc>, signature_scheme: Arc, } +impl> TendermintGossip { + pub(crate) fn new(number: Arc>, signature_scheme: Arc) -> TendermintGossip { + TendermintGossip { number, signature_scheme } + } + + pub(crate) fn topic(number: u64) -> B::Hash { + <::Hashing as Hash>::hash( + &[b"Tendermint Block Topic".as_ref(), &number.to_le_bytes()].concat(), + ) + } +} + impl> Validator for TendermintGossip { @@ -36,8 +48,6 @@ impl> Val return ValidationResult::Discard; } - ValidationResult::ProcessAndKeep(<::Hashing as Hash>::hash( - &[b"Tendermint Topic".as_ref(), &msg.number().0.to_le_bytes()].concat(), - )) + ValidationResult::ProcessAndKeep(Self::topic::(msg.number().0)) } } diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 3343a3fdf..7f2cdd703 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -7,6 +7,7 @@ use sp_api::{TransactionFor, ProvideRuntimeApi}; use sc_executor::{NativeVersion, NativeExecutionDispatch, NativeElseWasmExecutor}; use sc_transaction_pool::FullPool; +use sc_network::NetworkService; use sc_service::{TaskManager, TFullClient}; use substrate_prometheus_endpoint::Registry; @@ -26,7 +27,7 @@ mod verifier; mod import_queue; use import_queue::TendermintImportQueue; -mod gossip; +pub(crate) mod gossip; mod select_chain; pub use select_chain::TendermintSelectChain; @@ -85,6 +86,7 @@ impl> TendermintValidator for TendermintValidatorFirm { DisableProofRecording, >; + type Network = Arc::Hash>>; type Announce = A; } diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 7b8051c40..54f9e6ca6 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -8,7 +8,7 @@ use async_trait::async_trait; use log::warn; -use tokio::sync::RwLock as AsyncRwLock; +use tokio::{sync::RwLock as AsyncRwLock, task::yield_now}; use sp_core::{Encode, Decode, sr25519::Signature}; use sp_inherents::{InherentData, InherentDataProvider, CreateInherentDataProviders}; @@ -24,6 +24,9 @@ use sc_consensus::{ForkChoiceStrategy, BlockImportParams, import_queue::Incoming use sc_service::ImportQueue; use sc_client_api::{BlockBackend, Finalizer}; +use sc_network_gossip::GossipEngine; + +use substrate_prometheus_endpoint::Registry; use tendermint_machine::{ ext::{BlockError, BlockNumber, Commit, Network}, @@ -35,6 +38,7 @@ use crate::{ types::TendermintValidator, validators::TendermintValidators, import_queue::{ImportFuture, TendermintImportQueue}, + gossip::TendermintGossip, Announce, }; @@ -43,6 +47,7 @@ pub(crate) struct TendermintImport { validators: Arc>, + number: Arc>, importing_block: Arc::Hash>>>, pub(crate) machine: Arc>>>, @@ -57,7 +62,7 @@ pub(crate) struct TendermintImport { pub struct TendermintAuthority(pub(crate) TendermintImport); impl TendermintAuthority { - pub async fn validate(mut self) { + pub async fn validate(mut self, network: T::Network, registry: Option<&Registry>) { let info = self.0.client.info(); // Header::Number: TryInto doesn't implement Debug and can't be unwrapped @@ -84,13 +89,55 @@ impl TendermintAuthority { .get_proposal(&self.0.client.header(BlockId::Hash(info.best_hash)).unwrap().unwrap()) .await; - *self.0.machine.write().unwrap() = Some(TendermintMachine::new( + *self.0.number.write().unwrap() = last_number.0 + 1; + let mut gossip = GossipEngine::new( + network, + "tendermint", + Arc::new(TendermintGossip::new(self.0.number.clone(), self.0.validators.clone())), + registry, + ); + + let handle = TendermintMachine::new( self.0.clone(), // TODO 0, // ValidatorId (last_number, last_time), proposal, - )); + ); + + let mut last_number = last_number.0 + 1; + let mut recv = gossip + .messages_for(TendermintGossip::>::topic::(last_number)); + loop { + match recv.try_next() { + Ok(Some(msg)) => handle + .messages + .send(match SignedMessage::decode(&mut msg.message.as_ref()) { + Ok(msg) => msg, + Err(e) => { + warn!("couldn't decode valid message: {}", e); + continue; + } + }) + .await + .unwrap(), + Ok(None) => break, + // No messages available + Err(_) => { + // Check if we the block updated and should be listening on a different topic + let curr = *self.0.number.read().unwrap(); + if last_number != curr { + last_number = curr; + // TODO: Will this return existing messages on the new height? Or will those have been + // ignored and are now gone? + recv = gossip.messages_for(TendermintGossip::>::topic::< + T::Block, + >(last_number)); + } + yield_now().await; + } + } + } } } @@ -101,6 +148,7 @@ impl Clone for TendermintImport { validators: self.validators.clone(), + number: self.number.clone(), importing_block: self.importing_block.clone(), machine: self.machine.clone(), @@ -126,6 +174,7 @@ impl TendermintImport { validators: Arc::new(TendermintValidators::new(client.clone())), + number: Arc::new(RwLock::new(0)), importing_block: Arc::new(RwLock::new(None)), machine: Arc::new(RwLock::new(None)), @@ -388,6 +437,7 @@ impl Network for TendermintImport { .finalize_block(BlockId::Hash(hash), Some(justification), true) .map_err(|_| Error::InvalidJustification) .unwrap(); + *self.number.write().unwrap() += 1; self.announce.announce(hash); self.get_proposal(block.header()).await diff --git a/substrate/tendermint/client/src/types.rs b/substrate/tendermint/client/src/types.rs index a21ae9773..5b2ce8ba6 100644 --- a/substrate/tendermint/client/src/types.rs +++ b/substrate/tendermint/client/src/types.rs @@ -8,6 +8,7 @@ use sp_consensus::Environment; use sc_consensus::BlockImport; use sc_client_api::{BlockBackend, Backend, Finalizer}; +use sc_network_gossip::Network; use sp_tendermint::TendermintApi; @@ -71,5 +72,6 @@ pub trait TendermintValidator: TendermintClient { type CIDP: CreateInherentDataProviders + 'static; type Environment: Send + Sync + Environment + 'static; + type Network: Clone + Send + Sync + Network + 'static; type Announce: Announce; } From aee0bde45d40f3be7a0fb43bd0a3e4c4b486b4bc Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 05:37:23 -0400 Subject: [PATCH 078/186] Connect broadcast --- substrate/tendermint/client/src/tendermint.rs | 70 ++++++++++++------- substrate/tendermint/machine/src/lib.rs | 2 +- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 54f9e6ca6..deb51c5f0 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -48,6 +48,7 @@ pub(crate) struct TendermintImport { validators: Arc>, number: Arc>, + gossip_queue: Arc>>>, importing_block: Arc::Hash>>>, pub(crate) machine: Arc>>>, @@ -108,33 +109,48 @@ impl TendermintAuthority { let mut last_number = last_number.0 + 1; let mut recv = gossip .messages_for(TendermintGossip::>::topic::(last_number)); - loop { - match recv.try_next() { - Ok(Some(msg)) => handle - .messages - .send(match SignedMessage::decode(&mut msg.message.as_ref()) { - Ok(msg) => msg, - Err(e) => { - warn!("couldn't decode valid message: {}", e); - continue; + 'outer: loop { + // Send out any queued messages + let mut queue = self.0.gossip_queue.write().unwrap().drain(..).collect::>(); + for msg in queue.drain(..) { + gossip.gossip_message( + TendermintGossip::>::topic::(msg.number().0), + msg.encode(), + false, + ); + } + + // Handle any received messages + // Makes sure to handle all pending messages before acquiring the out-queue lock again + 'inner: loop { + match recv.try_next() { + Ok(Some(msg)) => handle + .messages + .send(match SignedMessage::decode(&mut msg.message.as_ref()) { + Ok(msg) => msg, + Err(e) => { + warn!("couldn't decode valid message: {}", e); + continue; + } + }) + .await + .unwrap(), + Ok(None) => break 'outer, + // No messages available + Err(_) => { + // Check if we the block updated and should be listening on a different topic + let curr = *self.0.number.read().unwrap(); + if last_number != curr { + last_number = curr; + // TODO: Will this return existing messages on the new height? Or will those have been + // ignored and are now gone? + recv = gossip.messages_for(TendermintGossip::>::topic::< + T::Block, + >(last_number)); } - }) - .await - .unwrap(), - Ok(None) => break, - // No messages available - Err(_) => { - // Check if we the block updated and should be listening on a different topic - let curr = *self.0.number.read().unwrap(); - if last_number != curr { - last_number = curr; - // TODO: Will this return existing messages on the new height? Or will those have been - // ignored and are now gone? - recv = gossip.messages_for(TendermintGossip::>::topic::< - T::Block, - >(last_number)); + yield_now().await; + break 'inner; } - yield_now().await; } } } @@ -149,6 +165,7 @@ impl Clone for TendermintImport { validators: self.validators.clone(), number: self.number.clone(), + gossip_queue: self.gossip_queue.clone(), importing_block: self.importing_block.clone(), machine: self.machine.clone(), @@ -175,6 +192,7 @@ impl TendermintImport { validators: Arc::new(TendermintValidators::new(client.clone())), number: Arc::new(RwLock::new(0)), + gossip_queue: Arc::new(RwLock::new(vec![])), importing_block: Arc::new(RwLock::new(None)), machine: Arc::new(RwLock::new(None)), @@ -359,7 +377,7 @@ impl Network for TendermintImport { } async fn broadcast(&mut self, msg: SignedMessage) { - // TODO + self.gossip_queue.write().unwrap().push(msg); } async fn slash(&mut self, validator: u16) { diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index cb73bcf05..6efc16fd3 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -278,7 +278,7 @@ impl TendermintMachine { weights: weights.clone(), proposer, - number: BlockNumber(last.0.0 + 1), + number: BlockNumber(last.0 .0 + 1), canonical_start_time: last.1, // The end time of the last block is the start time for this one // The Commit explicitly contains the end time, so loading the last commit will provide From f37adf4febc02b196a67c8fa4d946ddc30e028cf Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 06:25:30 -0400 Subject: [PATCH 079/186] Remove machine from TendermintImport It's not used there at all. --- substrate/tendermint/client/src/tendermint.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index deb51c5f0..9ea023635 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -30,7 +30,7 @@ use substrate_prometheus_endpoint::Registry; use tendermint_machine::{ ext::{BlockError, BlockNumber, Commit, Network}, - SignedMessage, TendermintMachine, TendermintHandle, + SignedMessage, TendermintMachine, }; use crate::{ @@ -50,7 +50,6 @@ pub(crate) struct TendermintImport { number: Arc>, gossip_queue: Arc>>>, importing_block: Arc::Hash>>>, - pub(crate) machine: Arc>>>, pub(crate) client: Arc, announce: T::Announce, @@ -167,7 +166,6 @@ impl Clone for TendermintImport { number: self.number.clone(), gossip_queue: self.gossip_queue.clone(), importing_block: self.importing_block.clone(), - machine: self.machine.clone(), client: self.client.clone(), announce: self.announce.clone(), @@ -194,7 +192,6 @@ impl TendermintImport { number: Arc::new(RwLock::new(0)), gossip_queue: Arc::new(RwLock::new(vec![])), importing_block: Arc::new(RwLock::new(None)), - machine: Arc::new(RwLock::new(None)), client, announce, From 066bc40a884cf4e2f16a9bee969caa83c887648a Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 06:30:44 -0400 Subject: [PATCH 080/186] Merge Verifier into block_import.rs These two files were largely the same, just hooking into sync structs with almost identical imports. As this project shapes up, removing dead weight is appreciated. --- .../tendermint/client/src/block_import.rs | 17 +++++++++++++- substrate/tendermint/client/src/lib.rs | 1 - substrate/tendermint/client/src/verifier.rs | 23 ------------------- 3 files changed, 16 insertions(+), 25 deletions(-) delete mode 100644 substrate/tendermint/client/src/verifier.rs diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index 21d5782c2..379cb1e5e 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -3,7 +3,7 @@ use std::{sync::Arc, collections::HashMap}; use async_trait::async_trait; use sp_consensus::{Error, CacheKeyId}; -use sc_consensus::{BlockCheckParams, BlockImportParams, ImportResult, BlockImport}; +use sc_consensus::{BlockCheckParams, BlockImportParams, ImportResult, BlockImport, Verifier}; use crate::{types::TendermintValidator, tendermint::TendermintImport}; @@ -45,3 +45,18 @@ where // machine with the new height } } + +#[async_trait] +impl Verifier for TendermintImport +where + Arc: BlockImport, + as BlockImport>::Error: Into, +{ + async fn verify( + &mut self, + mut block: BlockImportParams, + ) -> Result<(BlockImportParams, Option)>>), String> { + self.check(&mut block).await.map_err(|e| format!("{}", e))?; + Ok((block, None)) + } +} diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 7f2cdd703..36e3e3491 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -22,7 +22,6 @@ mod validators; mod tendermint; pub use tendermint::TendermintAuthority; mod block_import; -mod verifier; mod import_queue; use import_queue::TendermintImportQueue; diff --git a/substrate/tendermint/client/src/verifier.rs b/substrate/tendermint/client/src/verifier.rs deleted file mode 100644 index 9b7c2a921..000000000 --- a/substrate/tendermint/client/src/verifier.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::sync::Arc; - -use async_trait::async_trait; - -use sp_consensus::{Error, CacheKeyId}; -use sc_consensus::{BlockImportParams, BlockImport, Verifier}; - -use crate::{types::TendermintValidator, tendermint::TendermintImport}; - -#[async_trait] -impl Verifier for TendermintImport -where - Arc: BlockImport, - as BlockImport>::Error: Into, -{ - async fn verify( - &mut self, - mut block: BlockImportParams, - ) -> Result<(BlockImportParams, Option)>>), String> { - self.check(&mut block).await.map_err(|e| format!("{}", e))?; - Ok((block, None)) - } -} From 3d7c12adcdf9550fdbea4d138a01e396bc868eba Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 10:08:35 -0400 Subject: [PATCH 081/186] Create a dedicated file for being a Tendermint authority --- Cargo.lock | 1 + substrate/node/Cargo.toml | 1 + substrate/node/src/service.rs | 54 +-- substrate/tendermint/client/src/authority.rs | 348 ++++++++++++++++++ .../tendermint/client/src/import_queue.rs | 19 +- substrate/tendermint/client/src/lib.rs | 55 +-- substrate/tendermint/client/src/tendermint.rs | 320 +--------------- substrate/tendermint/client/src/types.rs | 17 +- substrate/tendermint/client/src/validators.rs | 2 +- 9 files changed, 422 insertions(+), 395 deletions(-) create mode 100644 substrate/tendermint/client/src/authority.rs diff --git a/Cargo.lock b/Cargo.lock index 2b6ba1709..917e0fd8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7595,6 +7595,7 @@ dependencies = [ "pallet-tendermint", "pallet-transaction-payment", "pallet-transaction-payment-rpc", + "sc-basic-authorship", "sc-cli", "sc-client-api", "sc-consensus", diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index 4dfb37002..90c392f3c 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -27,6 +27,7 @@ sp-block-builder = { git = "https://github.com/serai-dex/substrate" } sc-keystore = { git = "https://github.com/serai-dex/substrate" } sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" } +sc-basic-authorship = { git = "https://github.com/serai-dex/substrate" } sc-transaction-pool-api = { git = "https://github.com/serai-dex/substrate" } sc-executor = { git = "https://github.com/serai-dex/substrate" } sc-service = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 20579990c..ac9921b0b 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -1,15 +1,12 @@ -use std::sync::{Arc, RwLock}; - -use sp_core::H256; +use std::sync::Arc; use sc_executor::NativeElseWasmExecutor; use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; -use sc_network::{NetworkService, NetworkBlock}; use sc_telemetry::{Telemetry, TelemetryWorker}; use serai_runtime::{self, opaque::Block, RuntimeApi}; pub(crate) use serai_consensus::{ - TendermintAuthority, ExecutorDispatch, Announce, FullClient, TendermintValidatorFirm, + TendermintImport, TendermintAuthority, ExecutorDispatch, FullClient, TendermintValidatorFirm, }; type FullBackend = sc_service::TFullBackend; @@ -24,30 +21,9 @@ type PartialComponents = sc_service::PartialComponents< Option, >; -#[derive(Clone)] -pub struct NetworkAnnounce(Arc>>>>); -impl NetworkAnnounce { - fn new() -> NetworkAnnounce { - NetworkAnnounce(Arc::new(RwLock::new(None))) - } -} -impl Announce for NetworkAnnounce { - fn announce(&self, hash: H256) { - if let Some(network) = self.0.read().unwrap().as_ref() { - network.announce_block(hash, None); - } - } -} - pub fn new_partial( config: &Configuration, -) -> Result< - ( - (NetworkAnnounce, TendermintAuthority>), - PartialComponents, - ), - ServiceError, -> { +) -> Result<(TendermintImport, PartialComponents), ServiceError> { if config.keystore_remote.is_some() { return Err(ServiceError::Other("Remote Keystores are not supported".to_string())); } @@ -91,19 +67,16 @@ pub fn new_partial( client.clone(), ); - let announce = NetworkAnnounce::new(); let (authority, import_queue) = serai_consensus::import_queue( - &task_manager, + &task_manager.spawn_essential_handle(), client.clone(), - announce.clone(), - transaction_pool.clone(), config.prometheus_registry(), ); let select_chain = serai_consensus::TendermintSelectChain::new(backend.clone()); Ok(( - (announce, authority), + authority, sc_service::PartialComponents { client, backend, @@ -119,7 +92,7 @@ pub fn new_partial( pub async fn new_full(config: Configuration) -> Result { let ( - (announce, authority), + authority, sc_service::PartialComponents { client, backend, @@ -142,7 +115,6 @@ pub async fn new_full(config: Configuration) -> Result Result Result { + // Block whose gossip is being tracked + number: Arc>, + // Outgoing message queue, placed here as the GossipEngine itself can't be + gossip_queue: Arc>>>, + + // Block producer + env: T::Environment, + announce: T::Network, +} + +pub struct TendermintAuthority { + import: TendermintImport, + active: Option>, +} + +impl TendermintAuthority { + pub fn new(import: TendermintImport) -> Self { + Self { import, active: None } + } + + fn get_last(&self) -> (::Hash, (BlockNumber, u64)) { + let info = self.import.client.info(); + + ( + info.best_hash, + ( + // Header::Number: TryInto doesn't implement Debug and can't be unwrapped + match info.best_number.try_into() { + Ok(best) => BlockNumber(best), + Err(_) => panic!("BlockNumber exceeded u64"), + }, + // Get the last time by grabbing the last block's justification and reading the time from + // that + Commit::>::decode( + &mut self + .import + .client + .justifications(&BlockId::Hash(info.best_hash)) + .unwrap() + .map(|justifications| justifications.get(CONSENSUS_ID).cloned().unwrap()) + .unwrap_or_default() + .as_ref(), + ) + .map(|commit| commit.end_time) + // TODO: Genesis start time + BLOCK_TIME + .unwrap_or_else(|_| SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()), + ), + ) + } + + pub(crate) async fn get_proposal(&mut self, header: &::Header) -> T::Block { + let inherent_data = match self + .import + .providers + .read() + .await + .as_ref() + .unwrap() + .create_inherent_data_providers(header.hash(), ()) + .await + { + Ok(providers) => match providers.create_inherent_data() { + Ok(data) => Some(data), + Err(err) => { + warn!(target: "tendermint", "Failed to create inherent data: {}", err); + None + } + }, + Err(err) => { + warn!(target: "tendermint", "Failed to create inherent data providers: {}", err); + None + } + } + .unwrap_or_else(InherentData::new); + + let proposer = self + .active + .as_mut() + .unwrap() + .env + .init(header) + .await + .expect("Failed to create a proposer for the new block"); + // TODO: Production time, size limit + proposer + .propose(inherent_data, Digest::default(), Duration::from_secs(1), None) + .await + .expect("Failed to crate a new block proposal") + .block + } + + /// Act as a network authority, proposing and voting on blocks. This should be spawned on a task + /// as it will not return until the P2P stack shuts down. + pub async fn authority( + mut self, + providers: T::CIDP, + env: T::Environment, + network: T::Network, + registry: Option<&Registry>, + ) { + let (best_hash, last) = self.get_last(); + let mut last_number = last.0 .0 + 1; + + // Shared references between us and the Tendermint machine (and its actions via its Network + // trait) + let number = Arc::new(RwLock::new(last_number)); + let gossip_queue = Arc::new(RwLock::new(vec![])); + + // Create the gossip network + let mut gossip = GossipEngine::new( + network.clone(), + "tendermint", + Arc::new(TendermintGossip::new(number.clone(), self.import.validators.clone())), + registry, + ); + + // Create the Tendermint machine + let handle = { + // Set this struct as active + *self.import.providers.write().await = Some(providers); + self.active = Some(ActiveAuthority { + number: number.clone(), + gossip_queue: gossip_queue.clone(), + + env, + announce: network, + }); + + let proposal = self + .get_proposal(&self.import.client.header(BlockId::Hash(best_hash)).unwrap().unwrap()) + .await; + + TendermintMachine::new( + self, // We no longer need self, so let TendermintMachine become its owner + 0, // TODO: ValidatorId + last, proposal, + ) + }; + + // Start receiving messages about the Tendermint process for this block + let mut recv = gossip + .messages_for(TendermintGossip::>::topic::(last_number)); + + 'outer: loop { + // Send out any queued messages + let mut queue = gossip_queue.write().unwrap().drain(..).collect::>(); + for msg in queue.drain(..) { + gossip.gossip_message( + TendermintGossip::>::topic::(msg.number().0), + msg.encode(), + false, + ); + } + + // Handle any received messages + // This inner loop enables handling all pending messages before acquiring the out-queue lock + // again + 'inner: loop { + match recv.try_next() { + Ok(Some(msg)) => handle + .messages + .send(match SignedMessage::decode(&mut msg.message.as_ref()) { + Ok(msg) => msg, + Err(e) => { + warn!(target: "tendermint", "Couldn't decode valid message: {}", e); + continue; + } + }) + .await + .unwrap(), + + // Ok(None) IS NOT when there aren't messages available. It's when the channel is closed + // If we're no longer receiving messages from the network, it must no longer be running + // We should no longer be accordingly + Ok(None) => break 'outer, + + // No messages available + Err(_) => { + // Check if we the block updated and should be listening on a different topic + let curr = *number.read().unwrap(); + if last_number != curr { + last_number = curr; + // TODO: Will this return existing messages on the new height? Or will those have + // been ignored and are now gone? + recv = gossip.messages_for(TendermintGossip::>::topic::< + T::Block, + >(last_number)); + } + + // If there are no messages available, yield to not hog the thread, then return to the + // outer loop + yield_now().await; + break 'inner; + } + } + } + } + } +} + +#[async_trait] +impl Network for TendermintAuthority { + type ValidatorId = u16; + type SignatureScheme = TendermintValidators; + type Weights = TendermintValidators; + type Block = T::Block; + + const BLOCK_TIME: u32 = T::BLOCK_TIME_IN_SECONDS; + + fn signature_scheme(&self) -> Arc> { + self.import.validators.clone() + } + + fn weights(&self) -> Arc> { + self.import.validators.clone() + } + + async fn broadcast(&mut self, msg: SignedMessage) { + self.active.as_mut().unwrap().gossip_queue.write().unwrap().push(msg); + } + + async fn slash(&mut self, validator: u16) { + todo!() + } + + // The Tendermint machine will call add_block for any block which is committed to, regardless of + // validity. To determine validity, it expects a validate function, which Substrate doesn't + // directly offer, and an add function. In order to comply with Serai's modified view of inherent + // transactions, validate MUST check inherents, yet add_block must not. + // + // In order to acquire a validate function, any block proposed by a legitimate proposer is + // imported. This performs full validation and makes the block available as a tip. While this + // would be incredibly unsafe thanks to the unchecked inherents, it's defined as a tip with less + // work, despite being a child of some parent. This means it won't be moved to nor operated on by + // the node. + // + // When Tendermint completes, the block is finalized, setting it as the tip regardless of work. + async fn validate(&mut self, block: &T::Block) -> Result<(), BlockError> { + let hash = block.hash(); + let (header, body) = block.clone().deconstruct(); + let parent = *header.parent_hash(); + let number = *header.number(); + + let mut queue_write = self.import.queue.write().await; + *self.import.importing_block.write().unwrap() = Some(hash); + + queue_write.as_mut().unwrap().import_blocks( + // We do not want this block, which hasn't been confirmed, to be broadcast over the net + // Substrate will generate notifications unless it's Genesis, which this isn't, InitialSync, + // which changes telemtry behavior, or File, which is... close enough + BlockOrigin::File, + vec![IncomingBlock { + hash, + header: Some(header), + body: Some(body), + indexed_body: None, + justifications: None, + origin: None, + allow_missing_state: false, + skip_execution: false, + // TODO: Only set to true if block was rejected due to its inherents + import_existing: true, + state: None, + }], + ); + + if !ImportFuture::new(hash, queue_write.as_mut().unwrap()).await { + todo!() + } + + // Sanity checks that a child block can have less work than its parent + { + let info = self.import.client.info(); + assert_eq!(info.best_hash, parent); + assert_eq!(info.finalized_hash, parent); + assert_eq!(info.best_number, number - 1u8.into()); + assert_eq!(info.finalized_number, number - 1u8.into()); + } + + Ok(()) + } + + async fn add_block( + &mut self, + block: T::Block, + commit: Commit>, + ) -> T::Block { + let hash = block.hash(); + let justification = (CONSENSUS_ID, commit.encode()); + debug_assert!(self.import.verify_justification(hash, &justification).is_ok()); + + self + .import + .client + .finalize_block(BlockId::Hash(hash), Some(justification), true) + .map_err(|_| Error::InvalidJustification) + .unwrap(); + *self.active.as_mut().unwrap().number.write().unwrap() += 1; + self.active.as_ref().unwrap().announce.announce_block(hash, None); + + self.get_proposal(block.header()).await + } +} diff --git a/substrate/tendermint/client/src/import_queue.rs b/substrate/tendermint/client/src/import_queue.rs index 8e3fac019..5c6c58bad 100644 --- a/substrate/tendermint/client/src/import_queue.rs +++ b/substrate/tendermint/client/src/import_queue.rs @@ -14,10 +14,7 @@ use sc_service::ImportQueue; use substrate_prometheus_endpoint::Registry; -use crate::{ - types::TendermintValidator, - tendermint::{TendermintImport, TendermintAuthority}, -}; +use crate::{types::TendermintValidator, TendermintImport}; pub type TendermintImportQueue = BasicQueue; @@ -68,25 +65,21 @@ impl<'a, B: Block, T: Send> Future for ImportFuture<'a, B, T> { } pub fn import_queue( - client: Arc, - announce: T::Announce, - providers: Arc, - env: T::Environment, spawner: &impl sp_core::traits::SpawnEssentialNamed, + client: Arc, registry: Option<&Registry>, -) -> (TendermintAuthority, TendermintImportQueue) +) -> (TendermintImport, TendermintImportQueue) where Arc: BlockImport, as BlockImport>::Error: Into, { - let import = TendermintImport::::new(client, announce, providers, env); - let authority = TendermintAuthority(import.clone()); + let import = TendermintImport::::new(client); let boxed = Box::new(import.clone()); // Use None for the justification importer since justifications always come with blocks - // Therefore, they're never imported after the fact, mandating a importer + // Therefore, they're never imported after the fact, which is what mandates an importer let queue = || BasicQueue::new(import.clone(), boxed.clone(), None, spawner, registry); *futures::executor::block_on(import.queue.write()) = Some(queue()); - (authority, queue()) + (import.clone(), queue()) } diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 36e3e3491..a9196735c 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -1,16 +1,14 @@ -use std::{marker::PhantomData, boxed::Box, sync::Arc, error::Error}; +use std::{boxed::Box, sync::Arc, error::Error}; use sp_runtime::traits::Block as BlockTrait; use sp_inherents::CreateInherentDataProviders; use sp_consensus::DisableProofRecording; -use sp_api::{TransactionFor, ProvideRuntimeApi}; +use sp_api::ProvideRuntimeApi; use sc_executor::{NativeVersion, NativeExecutionDispatch, NativeElseWasmExecutor}; use sc_transaction_pool::FullPool; use sc_network::NetworkService; -use sc_service::{TaskManager, TFullClient}; - -use substrate_prometheus_endpoint::Registry; +use sc_service::TFullClient; use serai_runtime::{self, opaque::Block, RuntimeApi}; @@ -19,14 +17,16 @@ use types::{TendermintClientMinimal, TendermintValidator}; mod validators; -mod tendermint; -pub use tendermint::TendermintAuthority; +pub(crate) mod tendermint; +pub use tendermint::TendermintImport; mod block_import; mod import_queue; -use import_queue::TendermintImportQueue; +pub use import_queue::{TendermintImportQueue, import_queue}; pub(crate) mod gossip; +pub(crate) mod authority; +pub use authority::TendermintAuthority; mod select_chain; pub use select_chain::TendermintSelectChain; @@ -51,10 +51,6 @@ impl NativeExecutionDispatch for ExecutorDispatch { pub type FullClient = TFullClient>; -pub trait Announce: Send + Sync + Clone + 'static { - fn announce(&self, hash: B::Hash); -} - pub struct Cidp; #[async_trait::async_trait] impl CreateInherentDataProviders for Cidp { @@ -68,15 +64,17 @@ impl CreateInherentDataProviders for Cidp { } } -pub struct TendermintValidatorFirm>(PhantomData); -impl> TendermintClientMinimal for TendermintValidatorFirm { +pub struct TendermintValidatorFirm; +impl TendermintClientMinimal for TendermintValidatorFirm { + const BLOCK_TIME_IN_SECONDS: u32 = { (serai_runtime::MILLISECS_PER_BLOCK / 1000) as u32 }; + type Block = Block; type Backend = sc_client_db::Backend; type Api = >::Api; type Client = FullClient; } -impl> TendermintValidator for TendermintValidatorFirm { +impl TendermintValidator for TendermintValidatorFirm { type CIDP = Cidp; type Environment = sc_basic_authorship::ProposerFactory< FullPool, @@ -86,33 +84,6 @@ impl> TendermintValidator for TendermintValidatorFirm { >; type Network = Arc::Hash>>; - type Announce = A; -} - -pub fn import_queue>( - task_manager: &TaskManager, - client: Arc, - announce: A, - pool: Arc>, - registry: Option<&Registry>, -) -> ( - TendermintAuthority>, - TendermintImportQueue>, -) { - import_queue::import_queue::>( - client.clone(), - announce, - Arc::new(Cidp), - sc_basic_authorship::ProposerFactory::new( - task_manager.spawn_handle(), - client, - pool, - registry, - None, - ), - &task_manager.spawn_essential_handle(), - registry, - ) } /* diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 9ea023635..99e0f4cac 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -1,161 +1,40 @@ use std::{ marker::PhantomData, sync::{Arc, RwLock}, - time::{UNIX_EPOCH, SystemTime, Duration}, }; -use async_trait::async_trait; +use tokio::sync::RwLock as AsyncRwLock; -use log::warn; - -use tokio::{sync::RwLock as AsyncRwLock, task::yield_now}; - -use sp_core::{Encode, Decode, sr25519::Signature}; -use sp_inherents::{InherentData, InherentDataProvider, CreateInherentDataProviders}; +use sp_core::Decode; use sp_runtime::{ traits::{Header, Block}, - Digest, Justification, + Justification, }; use sp_blockchain::HeaderBackend; -use sp_api::BlockId; -use sp_consensus::{Error, BlockOrigin, Proposer, Environment}; -use sc_consensus::{ForkChoiceStrategy, BlockImportParams, import_queue::IncomingBlock}; +use sp_consensus::Error; +use sc_consensus::{ForkChoiceStrategy, BlockImportParams}; -use sc_service::ImportQueue; -use sc_client_api::{BlockBackend, Finalizer}; -use sc_network_gossip::GossipEngine; - -use substrate_prometheus_endpoint::Registry; - -use tendermint_machine::{ - ext::{BlockError, BlockNumber, Commit, Network}, - SignedMessage, TendermintMachine, -}; +use tendermint_machine::ext::{Commit, Network}; use crate::{ - CONSENSUS_ID, - types::TendermintValidator, - validators::TendermintValidators, - import_queue::{ImportFuture, TendermintImportQueue}, - gossip::TendermintGossip, - Announce, + CONSENSUS_ID, types::TendermintValidator, validators::TendermintValidators, + import_queue::TendermintImportQueue, authority::TendermintAuthority, }; -pub(crate) struct TendermintImport { +pub struct TendermintImport { _ta: PhantomData, - validators: Arc>, + pub(crate) validators: Arc>, - number: Arc>, - gossip_queue: Arc>>>, - importing_block: Arc::Hash>>>, + pub(crate) providers: Arc>>, + pub(crate) importing_block: Arc::Hash>>>, pub(crate) client: Arc, - announce: T::Announce, - providers: Arc, - - env: Arc>, pub(crate) queue: Arc>>>, } -pub struct TendermintAuthority(pub(crate) TendermintImport); -impl TendermintAuthority { - pub async fn validate(mut self, network: T::Network, registry: Option<&Registry>) { - let info = self.0.client.info(); - - // Header::Number: TryInto doesn't implement Debug and can't be unwrapped - let last_number = match info.best_number.try_into() { - Ok(best) => BlockNumber(best), - Err(_) => panic!("BlockNumber exceeded u64"), - }; - let last_time = Commit::>::decode( - &mut self - .0 - .client - .justifications(&BlockId::Hash(info.best_hash)) - .unwrap() - .map(|justifications| justifications.get(CONSENSUS_ID).cloned().unwrap()) - .unwrap_or_default() - .as_ref(), - ) - .map(|commit| commit.end_time) - // TODO: Genesis start time + BLOCK_TIME - .unwrap_or_else(|_| SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()); - - let proposal = self - .0 - .get_proposal(&self.0.client.header(BlockId::Hash(info.best_hash)).unwrap().unwrap()) - .await; - - *self.0.number.write().unwrap() = last_number.0 + 1; - let mut gossip = GossipEngine::new( - network, - "tendermint", - Arc::new(TendermintGossip::new(self.0.number.clone(), self.0.validators.clone())), - registry, - ); - - let handle = TendermintMachine::new( - self.0.clone(), - // TODO - 0, // ValidatorId - (last_number, last_time), - proposal, - ); - - let mut last_number = last_number.0 + 1; - let mut recv = gossip - .messages_for(TendermintGossip::>::topic::(last_number)); - 'outer: loop { - // Send out any queued messages - let mut queue = self.0.gossip_queue.write().unwrap().drain(..).collect::>(); - for msg in queue.drain(..) { - gossip.gossip_message( - TendermintGossip::>::topic::(msg.number().0), - msg.encode(), - false, - ); - } - - // Handle any received messages - // Makes sure to handle all pending messages before acquiring the out-queue lock again - 'inner: loop { - match recv.try_next() { - Ok(Some(msg)) => handle - .messages - .send(match SignedMessage::decode(&mut msg.message.as_ref()) { - Ok(msg) => msg, - Err(e) => { - warn!("couldn't decode valid message: {}", e); - continue; - } - }) - .await - .unwrap(), - Ok(None) => break 'outer, - // No messages available - Err(_) => { - // Check if we the block updated and should be listening on a different topic - let curr = *self.0.number.read().unwrap(); - if last_number != curr { - last_number = curr; - // TODO: Will this return existing messages on the new height? Or will those have been - // ignored and are now gone? - recv = gossip.messages_for(TendermintGossip::>::topic::< - T::Block, - >(last_number)); - } - yield_now().await; - break 'inner; - } - } - } - } - } -} - impl Clone for TendermintImport { fn clone(&self) -> Self { TendermintImport { @@ -163,50 +42,31 @@ impl Clone for TendermintImport { validators: self.validators.clone(), - number: self.number.clone(), - gossip_queue: self.gossip_queue.clone(), + providers: self.providers.clone(), importing_block: self.importing_block.clone(), client: self.client.clone(), - announce: self.announce.clone(), - providers: self.providers.clone(), - - env: self.env.clone(), queue: self.queue.clone(), } } } impl TendermintImport { - pub(crate) fn new( - client: Arc, - announce: T::Announce, - providers: Arc, - env: T::Environment, - ) -> TendermintImport { + pub(crate) fn new(client: Arc) -> TendermintImport { TendermintImport { _ta: PhantomData, validators: Arc::new(TendermintValidators::new(client.clone())), - number: Arc::new(RwLock::new(0)), - gossip_queue: Arc::new(RwLock::new(vec![])), + providers: Arc::new(AsyncRwLock::new(None)), importing_block: Arc::new(RwLock::new(None)), client, - announce, - providers, - - env: Arc::new(AsyncRwLock::new(env)), queue: Arc::new(AsyncRwLock::new(None)), } } - async fn check_inherents( - &self, - block: T::Block, - providers: >::InherentDataProviders, - ) -> Result<(), Error> { + async fn check_inherents(&self, block: T::Block) -> Result<(), Error> { // TODO Ok(()) } @@ -250,7 +110,7 @@ impl TendermintImport { let commit: Commit> = Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; - if !self.verify_commit(hash, &commit) { + if !TendermintAuthority::new(self.clone()).verify_commit(hash, &commit) { Err(Error::InvalidJustification)?; } Ok(()) @@ -282,10 +142,10 @@ impl TendermintImport { block: &mut BlockImportParams, ) -> Result<(), Error> { if block.finalized { - if block.fork_choice.is_none() { + if block.fork_choice != Some(ForkChoiceStrategy::Custom(false)) { // Since we alw1ays set the fork choice, this means something else marked the block as // finalized, which shouldn't be possible. Ensuring nothing else is setting blocks as - // finalized ensures our security + // finalized helps ensure our security panic!("block was finalized despite not setting the fork choice"); } return Ok(()); @@ -301,12 +161,7 @@ impl TendermintImport { if !block.finalized { self.verify_origin(block.header.hash())?; if let Some(body) = block.body.clone() { - self - .check_inherents( - T::Block::new(block.header.clone(), body), - self.providers.create_inherent_data_providers(*block.header.parent_hash(), ()).await?, - ) - .await?; + self.check_inherents(T::Block::new(block.header.clone(), body)).await?; } } @@ -322,139 +177,4 @@ impl TendermintImport { Ok(()) } - - pub(crate) async fn get_proposal(&mut self, header: &::Header) -> T::Block { - let inherent_data = - match self.providers.create_inherent_data_providers(header.hash(), ()).await { - Ok(providers) => match providers.create_inherent_data() { - Ok(data) => Some(data), - Err(err) => { - warn!(target: "tendermint", "Failed to create inherent data: {}", err); - None - } - }, - Err(err) => { - warn!(target: "tendermint", "Failed to create inherent data providers: {}", err); - None - } - } - .unwrap_or_else(InherentData::new); - - let proposer = self - .env - .write() - .await - .init(header) - .await - .expect("Failed to create a proposer for the new block"); - // TODO: Production time, size limit - proposer - .propose(inherent_data, Digest::default(), Duration::from_secs(1), None) - .await - .expect("Failed to crate a new block proposal") - .block - } -} - -#[async_trait] -impl Network for TendermintImport { - type ValidatorId = u16; - type SignatureScheme = TendermintValidators; - type Weights = TendermintValidators; - type Block = T::Block; - - const BLOCK_TIME: u32 = { (serai_runtime::MILLISECS_PER_BLOCK / 1000) as u32 }; - - fn signature_scheme(&self) -> Arc> { - self.validators.clone() - } - - fn weights(&self) -> Arc> { - self.validators.clone() - } - - async fn broadcast(&mut self, msg: SignedMessage) { - self.gossip_queue.write().unwrap().push(msg); - } - - async fn slash(&mut self, validator: u16) { - todo!() - } - - // The Tendermint machine will call add_block for any block which is committed to, regardless of - // validity. To determine validity, it expects a validate function, which Substrate doesn't - // directly offer, and an add function. In order to comply with Serai's modified view of inherent - // transactions, validate MUST check inherents, yet add_block must not. - // - // In order to acquire a validate function, any block proposed by a legitimate proposer is - // imported. This performs full validation and makes the block available as a tip. While this - // would be incredibly unsafe thanks to the unchecked inherents, it's defined as a tip with less - // work, despite being a child of some parent. This means it won't be moved to nor operated on by - // the node. - // - // When Tendermint completes, the block is finalized, setting it as the tip regardless of work. - async fn validate(&mut self, block: &T::Block) -> Result<(), BlockError> { - let hash = block.hash(); - let (header, body) = block.clone().deconstruct(); - let parent = *header.parent_hash(); - let number = *header.number(); - - let mut queue_write = self.queue.write().await; - *self.importing_block.write().unwrap() = Some(hash); - - queue_write.as_mut().unwrap().import_blocks( - // We do not want this block, which hasn't been confirmed, to be broadcast over the net - // Substrate will generate notifications unless it's Genesis, which this isn't, InitialSync, - // which changes telemtry behavior, or File, which is... close enough - BlockOrigin::File, - vec![IncomingBlock { - hash, - header: Some(header), - body: Some(body), - indexed_body: None, - justifications: None, - origin: None, - allow_missing_state: false, - skip_execution: false, - // TODO: Only set to true if block was rejected due to its inherents - import_existing: true, - state: None, - }], - ); - - if !ImportFuture::new(hash, queue_write.as_mut().unwrap()).await { - todo!() - } - - // Sanity checks that a child block can have less work than its parent - { - let info = self.client.info(); - assert_eq!(info.best_hash, parent); - assert_eq!(info.finalized_hash, parent); - assert_eq!(info.best_number, number - 1u8.into()); - assert_eq!(info.finalized_number, number - 1u8.into()); - } - - Ok(()) - } - - async fn add_block( - &mut self, - block: T::Block, - commit: Commit>, - ) -> T::Block { - let hash = block.hash(); - let justification = (CONSENSUS_ID, commit.encode()); - debug_assert!(self.verify_justification(hash, &justification).is_ok()); - - self - .client - .finalize_block(BlockId::Hash(hash), Some(justification), true) - .map_err(|_| Error::InvalidJustification) - .unwrap(); - *self.number.write().unwrap() += 1; - self.announce.announce(hash); - - self.get_proposal(block.header()).await - } } diff --git a/substrate/tendermint/client/src/types.rs b/substrate/tendermint/client/src/types.rs index 5b2ce8ba6..a1474ff50 100644 --- a/substrate/tendermint/client/src/types.rs +++ b/substrate/tendermint/client/src/types.rs @@ -8,14 +8,15 @@ use sp_consensus::Environment; use sc_consensus::BlockImport; use sc_client_api::{BlockBackend, Backend, Finalizer}; +use sc_network::NetworkBlock; use sc_network_gossip::Network; use sp_tendermint::TendermintApi; -use crate::Announce; - /// Trait consolidating all generics required by sc_tendermint for processing. pub trait TendermintClient: Send + Sync + 'static { + const BLOCK_TIME_IN_SECONDS: u32; + type Block: Block; type Backend: Backend + 'static; @@ -40,6 +41,8 @@ pub trait TendermintClient: Send + Sync + 'static { /// Trait implementable on firm types to automatically provide a full TendermintClient impl. pub trait TendermintClientMinimal: Send + Sync + 'static { + const BLOCK_TIME_IN_SECONDS: u32; + type Block: Block; type Backend: Backend + 'static; type Api: ApiExt + TendermintApi; @@ -58,6 +61,8 @@ where >::Api: TendermintApi, TransactionFor: Send + Sync + 'static, { + const BLOCK_TIME_IN_SECONDS: u32 = T::BLOCK_TIME_IN_SECONDS; + type Block = T::Block; type Backend = T::Backend; @@ -72,6 +77,10 @@ pub trait TendermintValidator: TendermintClient { type CIDP: CreateInherentDataProviders + 'static; type Environment: Send + Sync + Environment + 'static; - type Network: Clone + Send + Sync + Network + 'static; - type Announce: Announce; + type Network: Clone + + Send + + Sync + + Network + + NetworkBlock<::Hash, <::Header as Header>::Number> + + 'static; } diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index c21e3274d..5398e127a 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -79,7 +79,7 @@ impl Deref for Refresh { } } -pub(crate) struct TendermintValidators(Refresh); +pub struct TendermintValidators(Refresh); impl TendermintValidators { pub(crate) fn new(client: Arc) -> TendermintValidators { From a0e0545c49fb852346ad09a833c18454ff241f03 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 10:10:17 -0400 Subject: [PATCH 082/186] Deleted comment code related to PoW --- substrate/tendermint/client/src/lib.rs | 47 -------------------------- 1 file changed, 47 deletions(-) diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index a9196735c..c31ad687d 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -85,50 +85,3 @@ impl TendermintValidator for TendermintValidatorFirm { type Network = Arc::Hash>>; } - -/* -// If we're an authority, produce blocks -pub fn authority + 'static>( - task_manager: &TaskManager, - client: Arc, - network: Arc::Hash>>, - pool: Arc>, - select_chain: S, - registry: Option<&Registry>, -) { - let proposer = sc_basic_authorship::ProposerFactory::new( - task_manager.spawn_handle(), - client.clone(), - pool, - registry, - None, - ); - - let pow_block_import = Box::new(sc_pow::PowBlockImport::new( - client.clone(), - client.clone(), - algorithm::AcceptAny, - 0, // Block to start checking inherents at - select_chain.clone(), - move |_, _| async { Ok(sp_timestamp::InherentDataProvider::from_system_time()) }, - )); - - let (worker, worker_task) = sc_pow::start_mining_worker( - pow_block_import, - client, - select_chain, - algorithm::AcceptAny, - proposer, - network.clone(), - network, - None, - move |_, _| async { Ok(sp_timestamp::InherentDataProvider::from_system_time()) }, - Duration::from_secs(6), - Duration::from_secs(2), - ); - - task_manager.spawn_essential_handle().spawn_blocking("pow", None, worker_task); - - task_manager.spawn_essential_handle().spawn("producer", None, produce(worker)); -} -*/ From 91ae2b7112181c41dd49e616863d79f6c2841e5e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 10:54:17 -0400 Subject: [PATCH 083/186] Move serai_runtime specific code from tendermint/client to node Renames serai-consensus to sc_tendermint --- Cargo.lock | 67 +++++++++--------- substrate/node/Cargo.toml | 17 +++-- substrate/node/src/service.rs | 82 +++++++++++++++++++--- substrate/tendermint/client/Cargo.toml | 12 +--- substrate/tendermint/client/src/lib.rs | 71 +------------------ substrate/tendermint/primitives/Cargo.toml | 2 +- 6 files changed, 120 insertions(+), 131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 917e0fd8b..067c49b7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7321,6 +7321,34 @@ dependencies = [ "prometheus", ] +[[package]] +name = "sc_tendermint" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures", + "log", + "sc-client-api", + "sc-consensus", + "sc-executor", + "sc-network", + "sc-network-gossip", + "sc-service", + "sc-transaction-pool", + "sp-api", + "sp-application-crypto", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-staking", + "sp-tendermint", + "substrate-prometheus-endpoint", + "tendermint-machine", + "tokio", +] + [[package]] name = "scale-info" version = "2.2.0" @@ -7525,40 +7553,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "930c0acf610d3fdb5e2ab6213019aaa04e227ebe9547b0649ba599b16d788bd7" -[[package]] -name = "serai-consensus" -version = "0.1.0" -dependencies = [ - "async-trait", - "frame-support", - "futures", - "log", - "pallet-session", - "sc-basic-authorship", - "sc-client-api", - "sc-client-db", - "sc-consensus", - "sc-executor", - "sc-network", - "sc-network-gossip", - "sc-service", - "sc-transaction-pool", - "serai-runtime", - "sp-api", - "sp-application-crypto", - "sp-blockchain", - "sp-consensus", - "sp-core", - "sp-inherents", - "sp-runtime", - "sp-staking", - "sp-tendermint", - "sp-timestamp", - "substrate-prometheus-endpoint", - "tendermint-machine", - "tokio", -] - [[package]] name = "serai-extension" version = "0.1.0" @@ -7587,6 +7581,7 @@ dependencies = [ name = "serai-node" version = "0.1.0" dependencies = [ + "async-trait", "clap 4.0.18", "frame-benchmarking", "frame-benchmarking-cli", @@ -7598,6 +7593,7 @@ dependencies = [ "sc-basic-authorship", "sc-cli", "sc-client-api", + "sc-client-db", "sc-consensus", "sc-executor", "sc-keystore", @@ -7608,12 +7604,13 @@ dependencies = [ "sc-telemetry", "sc-transaction-pool", "sc-transaction-pool-api", - "serai-consensus", + "sc_tendermint", "serai-runtime", "sp-api", "sp-application-crypto", "sp-block-builder", "sp-blockchain", + "sp-consensus", "sp-core", "sp-inherents", "sp-keyring", diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index 90c392f3c..a6b74b5e9 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -12,28 +12,33 @@ publish = false name = "serai-node" [dependencies] +async-trait = "0.1" + clap = { version = "4", features = ["derive"] } jsonrpsee = { version = "0.15", features = ["server"] } sp-core = { git = "https://github.com/serai-dex/substrate" } sp-application-crypto = { git = "https://github.com/serai-dex/substrate" } -sp-runtime = { git = "https://github.com/serai-dex/substrate" } -sp-timestamp = { git = "https://github.com/serai-dex/substrate" } -sp-inherents = { git = "https://github.com/serai-dex/substrate" } sp-keyring = { git = "https://github.com/serai-dex/substrate" } -sp-api = { git = "https://github.com/serai-dex/substrate" } +sp-inherents = { git = "https://github.com/serai-dex/substrate" } +sp-timestamp = { git = "https://github.com/serai-dex/substrate" } +sp-runtime = { git = "https://github.com/serai-dex/substrate" } sp-blockchain = { git = "https://github.com/serai-dex/substrate" } +sp-api = { git = "https://github.com/serai-dex/substrate" } sp-block-builder = { git = "https://github.com/serai-dex/substrate" } +sp-consensus = { git = "https://github.com/serai-dex/substrate" } sc-keystore = { git = "https://github.com/serai-dex/substrate" } sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" } -sc-basic-authorship = { git = "https://github.com/serai-dex/substrate" } sc-transaction-pool-api = { git = "https://github.com/serai-dex/substrate" } +sc-basic-authorship = { git = "https://github.com/serai-dex/substrate" } sc-executor = { git = "https://github.com/serai-dex/substrate" } sc-service = { git = "https://github.com/serai-dex/substrate" } +sc-client-db = { git = "https://github.com/serai-dex/substrate" } sc-client-api = { git = "https://github.com/serai-dex/substrate" } sc-network = { git = "https://github.com/serai-dex/substrate" } sc-consensus = { git = "https://github.com/serai-dex/substrate" } + sc-telemetry = { git = "https://github.com/serai-dex/substrate" } sc-cli = { git = "https://github.com/serai-dex/substrate" } @@ -50,7 +55,7 @@ pallet-transaction-payment-rpc = { git = "https://github.com/serai-dex/substrate pallet-tendermint = { path = "../tendermint/pallet", default-features = false } serai-runtime = { path = "../runtime" } -serai-consensus = { path = "../tendermint/client" } +sc_tendermint = { path = "../tendermint/client" } [build-dependencies] substrate-build-script-utils = { git = "https://github.com/serai-dex/substrate.git" } diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index ac9921b0b..bc2e9745d 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -1,16 +1,25 @@ -use std::sync::Arc; +use std::{boxed::Box, sync::Arc, error::Error}; + +use sp_runtime::traits::{Block as BlockTrait}; +use sp_inherents::CreateInherentDataProviders; +use sp_consensus::DisableProofRecording; +use sp_api::ProvideRuntimeApi; + +use sc_executor::{NativeVersion, NativeExecutionDispatch, NativeElseWasmExecutor}; +use sc_transaction_pool::FullPool; +use sc_network::NetworkService; +use sc_service::{error::Error as ServiceError, Configuration, TaskManager, TFullClient}; -use sc_executor::NativeElseWasmExecutor; -use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; use sc_telemetry::{Telemetry, TelemetryWorker}; -use serai_runtime::{self, opaque::Block, RuntimeApi}; -pub(crate) use serai_consensus::{ - TendermintImport, TendermintAuthority, ExecutorDispatch, FullClient, TendermintValidatorFirm, +pub(crate) use sc_tendermint::{ + TendermintClientMinimal, TendermintValidator, TendermintImport, TendermintAuthority, + TendermintSelectChain, import_queue, }; +use serai_runtime::{self, MILLISECS_PER_BLOCK, opaque::Block, RuntimeApi}; type FullBackend = sc_service::TFullBackend; -type FullSelectChain = serai_consensus::TendermintSelectChain; +type FullSelectChain = TendermintSelectChain; type PartialComponents = sc_service::PartialComponents< FullClient, @@ -21,6 +30,59 @@ type PartialComponents = sc_service::PartialComponents< Option, >; +pub struct ExecutorDispatch; +impl NativeExecutionDispatch for ExecutorDispatch { + #[cfg(feature = "runtime-benchmarks")] + type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + #[cfg(not(feature = "runtime-benchmarks"))] + type ExtendHostFunctions = (); + + fn dispatch(method: &str, data: &[u8]) -> Option> { + serai_runtime::api::dispatch(method, data) + } + + fn native_version() -> NativeVersion { + serai_runtime::native_version() + } +} + +pub type FullClient = TFullClient>; + +pub struct Cidp; +#[async_trait::async_trait] +impl CreateInherentDataProviders for Cidp { + type InherentDataProviders = (sp_timestamp::InherentDataProvider,); + async fn create_inherent_data_providers( + &self, + _: ::Hash, + _: (), + ) -> Result> { + Ok((sp_timestamp::InherentDataProvider::from_system_time(),)) + } +} + +pub struct TendermintValidatorFirm; +impl TendermintClientMinimal for TendermintValidatorFirm { + const BLOCK_TIME_IN_SECONDS: u32 = { (MILLISECS_PER_BLOCK / 1000) as u32 }; + + type Block = Block; + type Backend = sc_client_db::Backend; + type Api = >::Api; + type Client = FullClient; +} + +impl TendermintValidator for TendermintValidatorFirm { + type CIDP = Cidp; + type Environment = sc_basic_authorship::ProposerFactory< + FullPool, + Self::Backend, + Self::Client, + DisableProofRecording, + >; + + type Network = Arc::Hash>>; +} + pub fn new_partial( config: &Configuration, ) -> Result<(TendermintImport, PartialComponents), ServiceError> { @@ -67,13 +129,13 @@ pub fn new_partial( client.clone(), ); - let (authority, import_queue) = serai_consensus::import_queue( + let (authority, import_queue) = import_queue( &task_manager.spawn_essential_handle(), client.clone(), config.prometheus_registry(), ); - let select_chain = serai_consensus::TendermintSelectChain::new(backend.clone()); + let select_chain = TendermintSelectChain::new(backend.clone()); Ok(( authority, @@ -161,7 +223,7 @@ pub async fn new_full(config: Configuration) -> Result"] @@ -23,7 +23,6 @@ tokio = { version = "1", features = ["sync", "rt"] } sp-core = { git = "https://github.com/serai-dex/substrate" } sp-application-crypto = { git = "https://github.com/serai-dex/substrate" } sp-inherents = { git = "https://github.com/serai-dex/substrate" } -sp-timestamp = { git = "https://github.com/serai-dex/substrate" } sp-staking = { git = "https://github.com/serai-dex/substrate" } sp-blockchain = { git = "https://github.com/serai-dex/substrate" } sp-runtime = { git = "https://github.com/serai-dex/substrate" } @@ -33,20 +32,13 @@ sp-consensus = { git = "https://github.com/serai-dex/substrate" } sp-tendermint = { path = "../primitives" } sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" } -sc-basic-authorship = { git = "https://github.com/serai-dex/substrate" } sc-executor = { git = "https://github.com/serai-dex/substrate" } sc-network = { git = "https://github.com/serai-dex/substrate" } sc-network-gossip = { git = "https://github.com/serai-dex/substrate" } sc-service = { git = "https://github.com/serai-dex/substrate" } -sc-client-db = { git = "https://github.com/serai-dex/substrate" } sc-client-api = { git = "https://github.com/serai-dex/substrate" } sc-consensus = { git = "https://github.com/serai-dex/substrate" } -frame-support = { git = "https://github.com/serai-dex/substrate" } -pallet-session = { git = "https://github.com/serai-dex/substrate" } - substrate-prometheus-endpoint = { git = "https://github.com/serai-dex/substrate" } tendermint-machine = { path = "../machine", features = ["substrate"] } - -serai-runtime = { path = "../../runtime" } diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index c31ad687d..0888db560 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -1,26 +1,12 @@ -use std::{boxed::Box, sync::Arc, error::Error}; - -use sp_runtime::traits::Block as BlockTrait; -use sp_inherents::CreateInherentDataProviders; -use sp_consensus::DisableProofRecording; -use sp_api::ProvideRuntimeApi; - -use sc_executor::{NativeVersion, NativeExecutionDispatch, NativeElseWasmExecutor}; -use sc_transaction_pool::FullPool; -use sc_network::NetworkService; -use sc_service::TFullClient; - -use serai_runtime::{self, opaque::Block, RuntimeApi}; - mod types; -use types::{TendermintClientMinimal, TendermintValidator}; +pub use types::{TendermintClientMinimal, TendermintValidator}; mod validators; pub(crate) mod tendermint; pub use tendermint::TendermintImport; -mod block_import; +mod block_import; mod import_queue; pub use import_queue::{TendermintImportQueue, import_queue}; @@ -32,56 +18,3 @@ mod select_chain; pub use select_chain::TendermintSelectChain; const CONSENSUS_ID: [u8; 4] = *b"tend"; - -pub struct ExecutorDispatch; -impl NativeExecutionDispatch for ExecutorDispatch { - #[cfg(feature = "runtime-benchmarks")] - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; - #[cfg(not(feature = "runtime-benchmarks"))] - type ExtendHostFunctions = (); - - fn dispatch(method: &str, data: &[u8]) -> Option> { - serai_runtime::api::dispatch(method, data) - } - - fn native_version() -> NativeVersion { - serai_runtime::native_version() - } -} - -pub type FullClient = TFullClient>; - -pub struct Cidp; -#[async_trait::async_trait] -impl CreateInherentDataProviders for Cidp { - type InherentDataProviders = (sp_timestamp::InherentDataProvider,); - async fn create_inherent_data_providers( - &self, - _: ::Hash, - _: (), - ) -> Result> { - Ok((sp_timestamp::InherentDataProvider::from_system_time(),)) - } -} - -pub struct TendermintValidatorFirm; -impl TendermintClientMinimal for TendermintValidatorFirm { - const BLOCK_TIME_IN_SECONDS: u32 = { (serai_runtime::MILLISECS_PER_BLOCK / 1000) as u32 }; - - type Block = Block; - type Backend = sc_client_db::Backend; - type Api = >::Api; - type Client = FullClient; -} - -impl TendermintValidator for TendermintValidatorFirm { - type CIDP = Cidp; - type Environment = sc_basic_authorship::ProposerFactory< - FullPool, - Self::Backend, - Self::Client, - DisableProofRecording, - >; - - type Network = Arc::Hash>>; -} diff --git a/substrate/tendermint/primitives/Cargo.toml b/substrate/tendermint/primitives/Cargo.toml index a152fa7da..6200add5c 100644 --- a/substrate/tendermint/primitives/Cargo.toml +++ b/substrate/tendermint/primitives/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sp-tendermint" version = "0.1.0" -description = "Substrate primitives for Tendermint" +description = "Tendermint primitives for Substrate" license = "AGPL-3.0-only" repository = "https://github.com/serai-dex/serai/tree/develop/substrate/tendermint/primitives" authors = ["Luke Parker "] From c0056643c8b4f28722e680bc6eaca3a9ecf2df86 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 11:08:12 -0400 Subject: [PATCH 084/186] Consolidate file structure in sc_tendermint --- .../client/src/{ => authority}/gossip.rs | 0 .../import_future.rs} | 31 +---- .../src/{authority.rs => authority/mod.rs} | 9 +- .../tendermint/client/src/block_import.rs | 61 ++++++++- substrate/tendermint/client/src/lib.rs | 119 ++++++++++++++++-- .../tendermint/client/src/select_chain.rs | 59 --------- substrate/tendermint/client/src/tendermint.rs | 4 +- substrate/tendermint/client/src/types.rs | 86 ------------- substrate/tendermint/client/src/validators.rs | 2 +- 9 files changed, 182 insertions(+), 189 deletions(-) rename substrate/tendermint/client/src/{ => authority}/gossip.rs (100%) rename substrate/tendermint/client/src/{import_queue.rs => authority/import_future.rs} (54%) rename substrate/tendermint/client/src/{authority.rs => authority/mod.rs} (98%) delete mode 100644 substrate/tendermint/client/src/select_chain.rs delete mode 100644 substrate/tendermint/client/src/types.rs diff --git a/substrate/tendermint/client/src/gossip.rs b/substrate/tendermint/client/src/authority/gossip.rs similarity index 100% rename from substrate/tendermint/client/src/gossip.rs rename to substrate/tendermint/client/src/authority/gossip.rs diff --git a/substrate/tendermint/client/src/import_queue.rs b/substrate/tendermint/client/src/authority/import_future.rs similarity index 54% rename from substrate/tendermint/client/src/import_queue.rs rename to substrate/tendermint/client/src/authority/import_future.rs index 5c6c58bad..ba372c4bc 100644 --- a/substrate/tendermint/client/src/import_queue.rs +++ b/substrate/tendermint/client/src/authority/import_future.rs @@ -1,22 +1,17 @@ use std::{ pin::Pin, - sync::{Arc, RwLock}, + sync::RwLock, task::{Poll, Context}, future::Future, }; use sp_runtime::traits::{Header, Block}; -use sp_consensus::Error; -use sc_consensus::{BlockImportStatus, BlockImportError, BlockImport, Link, BasicQueue}; +use sc_consensus::{BlockImportStatus, BlockImportError, Link}; use sc_service::ImportQueue; -use substrate_prometheus_endpoint::Registry; - -use crate::{types::TendermintValidator, TendermintImport}; - -pub type TendermintImportQueue = BasicQueue; +use crate::TendermintImportQueue; // Custom helpers for ImportQueue in order to obtain the result of a block's importing struct ValidateLink(Option<(B::Hash, bool)>); @@ -63,23 +58,3 @@ impl<'a, B: Block, T: Send> Future for ImportFuture<'a, B, T> { } } } - -pub fn import_queue( - spawner: &impl sp_core::traits::SpawnEssentialNamed, - client: Arc, - registry: Option<&Registry>, -) -> (TendermintImport, TendermintImportQueue) -where - Arc: BlockImport, - as BlockImport>::Error: Into, -{ - let import = TendermintImport::::new(client); - - let boxed = Box::new(import.clone()); - // Use None for the justification importer since justifications always come with blocks - // Therefore, they're never imported after the fact, which is what mandates an importer - let queue = || BasicQueue::new(import.clone(), boxed.clone(), None, spawner, registry); - - *futures::executor::block_on(import.queue.write()) = Some(queue()); - (import.clone(), queue()) -} diff --git a/substrate/tendermint/client/src/authority.rs b/substrate/tendermint/client/src/authority/mod.rs similarity index 98% rename from substrate/tendermint/client/src/authority.rs rename to substrate/tendermint/client/src/authority/mod.rs index 4b37c9bdb..c49078021 100644 --- a/substrate/tendermint/client/src/authority.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -34,10 +34,15 @@ use tendermint_machine::{ }; use crate::{ - CONSENSUS_ID, types::TendermintValidator, validators::TendermintValidators, - import_queue::ImportFuture, tendermint::TendermintImport, gossip::TendermintGossip, + CONSENSUS_ID, TendermintValidator, validators::TendermintValidators, tendermint::TendermintImport, }; +mod gossip; +use gossip::TendermintGossip; + +mod import_future; +use import_future::ImportFuture; + // Data for an active validator // This is distinct as even when we aren't an authority, we still create stubbed Authority objects // as it's only Authority which implements tendermint_machine::ext::Network. Network has diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index 379cb1e5e..21ea44844 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -1,11 +1,17 @@ -use std::{sync::Arc, collections::HashMap}; +use std::{marker::PhantomData, sync::Arc, collections::HashMap}; use async_trait::async_trait; -use sp_consensus::{Error, CacheKeyId}; +use sp_api::BlockId; +use sp_runtime::traits::Block; +use sp_blockchain::{HeaderBackend, Backend as BlockchainBackend}; +use sp_consensus::{Error, CacheKeyId, SelectChain}; + use sc_consensus::{BlockCheckParams, BlockImportParams, ImportResult, BlockImport, Verifier}; -use crate::{types::TendermintValidator, tendermint::TendermintImport}; +use sc_client_api::Backend; + +use crate::{TendermintValidator, tendermint::TendermintImport}; #[async_trait] impl BlockImport for TendermintImport @@ -60,3 +66,52 @@ where Ok((block, None)) } } + +// SelectChain, while provided by Substrate and part of PartialComponents, isn't used by Substrate +// It's common between various block-production/finality crates, yet Substrate as a system doesn't +// rely on it, which is good, because its definition is explicitly incompatible with Tendermint +// +// leaves is supposed to return all leaves of the blockchain. While Tendermint maintains that view, +// an honest node will only build on the most recently finalized block, so it is a 'leaf' despite +// having descendants +// +// best_chain will always be this finalized block, yet Substrate explicitly defines it as one of +// the above leaves, which this finalized block is explicitly not included in. Accordingly, we +// can never provide a compatible decision +// +// Since PartialComponents expects it, an implementation which does its best is provided. It panics +// if leaves is called, yet returns the finalized chain tip for best_chain, as that's intended to +// be the header to build upon +pub struct TendermintSelectChain>(Arc, PhantomData); + +impl> Clone for TendermintSelectChain { + fn clone(&self) -> Self { + TendermintSelectChain(self.0.clone(), PhantomData) + } +} + +impl> TendermintSelectChain { + pub fn new(backend: Arc) -> TendermintSelectChain { + TendermintSelectChain(backend, PhantomData) + } +} + +#[async_trait] +impl> SelectChain for TendermintSelectChain { + async fn leaves(&self) -> Result, Error> { + panic!("Substrate definition of leaves is incompatible with Tendermint") + } + + async fn best_chain(&self) -> Result { + Ok( + self + .0 + .blockchain() + // There should always be a finalized block + .header(BlockId::Hash(self.0.blockchain().last_finalized().unwrap())) + // There should not be an error in retrieving it and since it's finalized, it should exist + .unwrap() + .unwrap(), + ) + } +} diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 0888db560..bb370e494 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -1,5 +1,19 @@ -mod types; -pub use types::{TendermintClientMinimal, TendermintValidator}; +use std::sync::Arc; + +use sp_inherents::CreateInherentDataProviders; +use sp_runtime::traits::{Header, Block}; +use sp_blockchain::HeaderBackend; +use sp_api::{StateBackend, StateBackendFor, TransactionFor, ApiExt, ProvideRuntimeApi}; +use sp_consensus::{Error, Environment}; + +use sc_client_api::{BlockBackend, Backend, Finalizer}; +use sc_consensus::{BlockImport, BasicQueue}; +use sc_network::NetworkBlock; +use sc_network_gossip::Network; + +use sp_tendermint::TendermintApi; + +use substrate_prometheus_endpoint::Registry; mod validators; @@ -7,14 +21,103 @@ pub(crate) mod tendermint; pub use tendermint::TendermintImport; mod block_import; -mod import_queue; -pub use import_queue::{TendermintImportQueue, import_queue}; +pub use block_import::TendermintSelectChain; -pub(crate) mod gossip; pub(crate) mod authority; pub use authority::TendermintAuthority; -mod select_chain; -pub use select_chain::TendermintSelectChain; - const CONSENSUS_ID: [u8; 4] = *b"tend"; + +/// Trait consolidating all generics required by sc_tendermint for processing. +pub trait TendermintClient: Send + Sync + 'static { + const BLOCK_TIME_IN_SECONDS: u32; + + type Block: Block; + type Backend: Backend + 'static; + + /// TransactionFor + type BackendTransaction: Send + Sync + 'static; + /// StateBackendFor + type StateBackend: StateBackend< + <::Header as Header>::Hashing, + Transaction = Self::BackendTransaction, + >; + // Client::Api + type Api: ApiExt + TendermintApi; + type Client: Send + + Sync + + HeaderBackend + + BlockBackend + + BlockImport + + Finalizer + + ProvideRuntimeApi + + 'static; +} + +/// Trait implementable on firm types to automatically provide a full TendermintClient impl. +pub trait TendermintClientMinimal: Send + Sync + 'static { + const BLOCK_TIME_IN_SECONDS: u32; + + type Block: Block; + type Backend: Backend + 'static; + type Api: ApiExt + TendermintApi; + type Client: Send + + Sync + + HeaderBackend + + BlockBackend + + BlockImport> + + Finalizer + + ProvideRuntimeApi + + 'static; +} + +impl TendermintClient for T +where + >::Api: TendermintApi, + TransactionFor: Send + Sync + 'static, +{ + const BLOCK_TIME_IN_SECONDS: u32 = T::BLOCK_TIME_IN_SECONDS; + + type Block = T::Block; + type Backend = T::Backend; + + type BackendTransaction = TransactionFor; + type StateBackend = StateBackendFor; + type Api = >::Api; + type Client = T::Client; +} + +/// Trait consolidating additional generics required by sc_tendermint for authoring. +pub trait TendermintValidator: TendermintClient { + type CIDP: CreateInherentDataProviders + 'static; + type Environment: Send + Sync + Environment + 'static; + + type Network: Clone + + Send + + Sync + + Network + + NetworkBlock<::Hash, <::Header as Header>::Number> + + 'static; +} + +pub type TendermintImportQueue = BasicQueue; + +pub fn import_queue( + spawner: &impl sp_core::traits::SpawnEssentialNamed, + client: Arc, + registry: Option<&Registry>, +) -> (TendermintImport, TendermintImportQueue) +where + Arc: BlockImport, + as BlockImport>::Error: Into, +{ + let import = TendermintImport::::new(client); + + let boxed = Box::new(import.clone()); + // Use None for the justification importer since justifications always come with blocks + // Therefore, they're never imported after the fact, which is what mandates an importer + let queue = || BasicQueue::new(import.clone(), boxed.clone(), None, spawner, registry); + + *futures::executor::block_on(import.queue.write()) = Some(queue()); + (import.clone(), queue()) +} diff --git a/substrate/tendermint/client/src/select_chain.rs b/substrate/tendermint/client/src/select_chain.rs deleted file mode 100644 index 32a409ea9..000000000 --- a/substrate/tendermint/client/src/select_chain.rs +++ /dev/null @@ -1,59 +0,0 @@ -// SelectChain, while provided by Substrate and part of PartialComponents, isn't used by Substrate -// It's common between various block-production/finality crates, yet Substrate as a system doesn't -// rely on it, which is good, because its definition is explicitly incompatible with Tendermint -// -// leaves is supposed to return all leaves of the blockchain. While Tendermint maintains that view, -// an honest node will only build on the most recently finalized block, so it is a 'leaf' despite -// having descendants -// -// best_chain will always be this finalized block, yet Substrate explicitly defines it as one of -// the above leaves, which this finalized block is explicitly not included in. Accordingly, we -// can never provide a compatible decision -// -// Since PartialComponents expects it, an implementation which does its best is provided. It panics -// if leaves is called, yet returns the finalized chain tip for best_chain, as that's intended to -// be the header to build upon - -use std::{marker::PhantomData, sync::Arc}; - -use async_trait::async_trait; - -use sp_api::BlockId; -use sp_runtime::traits::Block; -use sp_blockchain::{HeaderBackend, Backend as BlockchainBackend}; -use sc_client_api::Backend; -use sp_consensus::{Error, SelectChain}; - -pub struct TendermintSelectChain>(Arc, PhantomData); - -impl> Clone for TendermintSelectChain { - fn clone(&self) -> Self { - TendermintSelectChain(self.0.clone(), PhantomData) - } -} - -impl> TendermintSelectChain { - pub fn new(backend: Arc) -> TendermintSelectChain { - TendermintSelectChain(backend, PhantomData) - } -} - -#[async_trait] -impl> SelectChain for TendermintSelectChain { - async fn leaves(&self) -> Result, Error> { - panic!("Substrate definition of leaves is incompatible with Tendermint") - } - - async fn best_chain(&self) -> Result { - Ok( - self - .0 - .blockchain() - // There should always be a finalized block - .header(BlockId::Hash(self.0.blockchain().last_finalized().unwrap())) - // There should not be an error in retrieving it and since it's finalized, it should exist - .unwrap() - .unwrap(), - ) - } -} diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 99e0f4cac..5fddd5bb8 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -18,8 +18,8 @@ use sc_consensus::{ForkChoiceStrategy, BlockImportParams}; use tendermint_machine::ext::{Commit, Network}; use crate::{ - CONSENSUS_ID, types::TendermintValidator, validators::TendermintValidators, - import_queue::TendermintImportQueue, authority::TendermintAuthority, + CONSENSUS_ID, TendermintValidator, validators::TendermintValidators, TendermintImportQueue, + authority::TendermintAuthority, }; pub struct TendermintImport { diff --git a/substrate/tendermint/client/src/types.rs b/substrate/tendermint/client/src/types.rs deleted file mode 100644 index a1474ff50..000000000 --- a/substrate/tendermint/client/src/types.rs +++ /dev/null @@ -1,86 +0,0 @@ -use sp_inherents::CreateInherentDataProviders; -use sp_runtime::traits::{Header, Block}; - -use sp_blockchain::HeaderBackend; -use sp_api::{StateBackend, StateBackendFor, TransactionFor, ApiExt, ProvideRuntimeApi}; - -use sp_consensus::Environment; -use sc_consensus::BlockImport; - -use sc_client_api::{BlockBackend, Backend, Finalizer}; -use sc_network::NetworkBlock; -use sc_network_gossip::Network; - -use sp_tendermint::TendermintApi; - -/// Trait consolidating all generics required by sc_tendermint for processing. -pub trait TendermintClient: Send + Sync + 'static { - const BLOCK_TIME_IN_SECONDS: u32; - - type Block: Block; - type Backend: Backend + 'static; - - /// TransactionFor - type BackendTransaction: Send + Sync + 'static; - /// StateBackendFor - type StateBackend: StateBackend< - <::Header as Header>::Hashing, - Transaction = Self::BackendTransaction, - >; - // Client::Api - type Api: ApiExt + TendermintApi; - type Client: Send - + Sync - + HeaderBackend - + BlockBackend - + BlockImport - + Finalizer - + ProvideRuntimeApi - + 'static; -} - -/// Trait implementable on firm types to automatically provide a full TendermintClient impl. -pub trait TendermintClientMinimal: Send + Sync + 'static { - const BLOCK_TIME_IN_SECONDS: u32; - - type Block: Block; - type Backend: Backend + 'static; - type Api: ApiExt + TendermintApi; - type Client: Send - + Sync - + HeaderBackend - + BlockBackend - + BlockImport> - + Finalizer - + ProvideRuntimeApi - + 'static; -} - -impl TendermintClient for T -where - >::Api: TendermintApi, - TransactionFor: Send + Sync + 'static, -{ - const BLOCK_TIME_IN_SECONDS: u32 = T::BLOCK_TIME_IN_SECONDS; - - type Block = T::Block; - type Backend = T::Backend; - - type BackendTransaction = TransactionFor; - type StateBackend = StateBackendFor; - type Api = >::Api; - type Client = T::Client; -} - -/// Trait consolidating additional generics required by sc_tendermint for authoring. -pub trait TendermintValidator: TendermintClient { - type CIDP: CreateInherentDataProviders + 'static; - type Environment: Send + Sync + Environment + 'static; - - type Network: Clone - + Send - + Sync - + Network - + NetworkBlock<::Hash, <::Header as Header>::Number> - + 'static; -} diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 5398e127a..046a2738c 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -15,7 +15,7 @@ use tendermint_machine::ext::{BlockNumber, Round, Weights, SignatureScheme}; use sp_tendermint::TendermintApi; -use crate::types::TendermintClient; +use crate::TendermintClient; struct TendermintValidatorsStruct { session: SessionIndex, From 503adfee2fc6e5483bf3b2d7e37e2d9a91c082ec Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 11:13:47 -0400 Subject: [PATCH 085/186] Replace best_* with finalized_* We test their equivalency yet still better to use finalized_* in general. --- substrate/tendermint/client/src/authority/mod.rs | 6 +++--- substrate/tendermint/client/src/tendermint.rs | 2 +- substrate/tendermint/client/src/validators.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index c49078021..e7fb97d01 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -72,10 +72,10 @@ impl TendermintAuthority { let info = self.import.client.info(); ( - info.best_hash, + info.finalized_hash, ( // Header::Number: TryInto doesn't implement Debug and can't be unwrapped - match info.best_number.try_into() { + match info.finalized_number.try_into() { Ok(best) => BlockNumber(best), Err(_) => panic!("BlockNumber exceeded u64"), }, @@ -85,7 +85,7 @@ impl TendermintAuthority { &mut self .import .client - .justifications(&BlockId::Hash(info.best_hash)) + .justifications(&BlockId::Hash(info.finalized_hash)) .unwrap() .map(|justifications| justifications.get(CONSENSUS_ID).cloned().unwrap()) .unwrap_or_default() diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 5fddd5bb8..d184d8fd8 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -78,7 +78,7 @@ impl TendermintImport { number: <::Header as Header>::Number, ) -> Result<(), Error> { let info = self.client.info(); - if (info.best_hash != parent) || ((info.best_number + 1u16.into()) != number) { + if (info.finalized_hash != parent) || ((info.finalized_number + 1u16.into()) != number) { Err(Error::Other("non-sequential import".into()))?; } Ok(()) diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 046a2738c..177e7425f 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -29,7 +29,7 @@ struct TendermintValidatorsStruct { impl TendermintValidatorsStruct { fn from_module(client: &Arc) -> TendermintValidatorsStruct { - let last = client.info().best_hash; + let last = client.info().finalized_hash; let api = client.runtime_api(); let session = api.current_session(&BlockId::Hash(last)).unwrap(); let validators = api.validators(&BlockId::Hash(last)).unwrap(); @@ -63,7 +63,7 @@ impl Refresh { self .client .runtime_api() - .current_session(&BlockId::Hash(self.client.info().best_hash)) + .current_session(&BlockId::Hash(self.client.info().finalized_hash)) .unwrap() { *self._refresh.write().unwrap() = TendermintValidatorsStruct::from_module::(&self.client); From c4976ff97dd51cda42692b7d3b267cbc10901ada Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 11:24:52 -0400 Subject: [PATCH 086/186] Consolidate references to sr25519 in sc_tendermint --- .../tendermint/client/src/authority/gossip.rs | 38 +++++++++++-------- .../tendermint/client/src/authority/mod.rs | 28 +++++++------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/substrate/tendermint/client/src/authority/gossip.rs b/substrate/tendermint/client/src/authority/gossip.rs index 79350f60b..9e1b54eff 100644 --- a/substrate/tendermint/client/src/authority/gossip.rs +++ b/substrate/tendermint/client/src/authority/gossip.rs @@ -1,41 +1,49 @@ use std::sync::{Arc, RwLock}; -use sp_core::{Decode, sr25519::Signature}; +use sp_core::Decode; use sp_runtime::traits::{Hash, Header, Block}; use sc_network::PeerId; use sc_network_gossip::{Validator, ValidatorContext, ValidationResult}; -use tendermint_machine::{SignedMessage, ext::SignatureScheme}; +use tendermint_machine::{ext::SignatureScheme, SignedMessage}; + +use crate::{TendermintValidator, validators::TendermintValidators}; #[derive(Clone)] -pub struct TendermintGossip> { +pub(crate) struct TendermintGossip { number: Arc>, - signature_scheme: Arc, + signature_scheme: Arc>, } -impl> TendermintGossip { - pub(crate) fn new(number: Arc>, signature_scheme: Arc) -> TendermintGossip { +impl TendermintGossip { + pub(crate) fn new( + number: Arc>, + signature_scheme: Arc>, + ) -> Self { TendermintGossip { number, signature_scheme } } - pub(crate) fn topic(number: u64) -> B::Hash { - <::Hashing as Hash>::hash( + pub(crate) fn topic(number: u64) -> ::Hash { + <<::Header as Header>::Hashing as Hash>::hash( &[b"Tendermint Block Topic".as_ref(), &number.to_le_bytes()].concat(), ) } } -impl> Validator - for TendermintGossip -{ +impl Validator for TendermintGossip { fn validate( &self, - _: &mut dyn ValidatorContext, + _: &mut dyn ValidatorContext, _: &PeerId, data: &[u8], - ) -> ValidationResult { - let msg = match SignedMessage::::decode(&mut &*data) { + ) -> ValidationResult<::Hash> { + let msg = match SignedMessage::< + u16, + T::Block, + as SignatureScheme>::Signature, + >::decode(&mut &*data) + { Ok(msg) => msg, Err(_) => return ValidationResult::Discard, }; @@ -48,6 +56,6 @@ impl> Val return ValidationResult::Discard; } - ValidationResult::ProcessAndKeep(Self::topic::(msg.number().0)) + ValidationResult::ProcessAndKeep(Self::topic(msg.number().0)) } } diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index e7fb97d01..51d1c923b 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -9,7 +9,7 @@ use log::warn; use tokio::task::yield_now; -use sp_core::{Encode, Decode, sr25519::Signature}; +use sp_core::{Encode, Decode}; use sp_inherents::{InherentData, InherentDataProvider, CreateInherentDataProviders}; use sp_runtime::{ traits::{Header, Block}, @@ -29,7 +29,7 @@ use sc_network_gossip::GossipEngine; use substrate_prometheus_endpoint::Registry; use tendermint_machine::{ - ext::{BlockError, BlockNumber, Commit, Network}, + ext::{BlockError, BlockNumber, Commit, SignatureScheme, Network}, SignedMessage, TendermintMachine, }; @@ -51,7 +51,11 @@ struct ActiveAuthority { // Block whose gossip is being tracked number: Arc>, // Outgoing message queue, placed here as the GossipEngine itself can't be - gossip_queue: Arc>>>, + gossip_queue: Arc< + RwLock< + Vec as SignatureScheme>::Signature>>, + >, + >, // Block producer env: T::Environment, @@ -188,18 +192,13 @@ impl TendermintAuthority { }; // Start receiving messages about the Tendermint process for this block - let mut recv = gossip - .messages_for(TendermintGossip::>::topic::(last_number)); + let mut recv = gossip.messages_for(TendermintGossip::::topic(last_number)); 'outer: loop { // Send out any queued messages let mut queue = gossip_queue.write().unwrap().drain(..).collect::>(); for msg in queue.drain(..) { - gossip.gossip_message( - TendermintGossip::>::topic::(msg.number().0), - msg.encode(), - false, - ); + gossip.gossip_message(TendermintGossip::::topic(msg.number().0), msg.encode(), false); } // Handle any received messages @@ -232,9 +231,7 @@ impl TendermintAuthority { last_number = curr; // TODO: Will this return existing messages on the new height? Or will those have // been ignored and are now gone? - recv = gossip.messages_for(TendermintGossip::>::topic::< - T::Block, - >(last_number)); + recv = gossip.messages_for(TendermintGossip::::topic(last_number)); } // If there are no messages available, yield to not hog the thread, then return to the @@ -265,7 +262,10 @@ impl Network for TendermintAuthority { self.import.validators.clone() } - async fn broadcast(&mut self, msg: SignedMessage) { + async fn broadcast( + &mut self, + msg: SignedMessage as SignatureScheme>::Signature>, + ) { self.active.as_mut().unwrap().gossip_queue.write().unwrap().push(msg); } From 8f065533dc3f529b79bd81abd3a4de5dfcc052b6 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 30 Oct 2022 12:27:16 -0400 Subject: [PATCH 087/186] Add documentation to public structs/functions in sc_tendermint --- substrate/tendermint/client/src/authority/mod.rs | 2 ++ substrate/tendermint/client/src/block_import.rs | 6 ++++++ substrate/tendermint/client/src/lib.rs | 2 ++ substrate/tendermint/client/src/tendermint.rs | 1 + substrate/tendermint/client/src/validators.rs | 1 + 5 files changed, 12 insertions(+) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 51d1c923b..2280de78a 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -62,12 +62,14 @@ struct ActiveAuthority { announce: T::Network, } +/// Tendermint Authority. Participates in the block proposal and voting process. pub struct TendermintAuthority { import: TendermintImport, active: Option>, } impl TendermintAuthority { + /// Create a new TendermintAuthority. pub fn new(import: TendermintImport) -> Self { Self { import, active: None } } diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index 21ea44844..b5ebb3a4a 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -67,6 +67,12 @@ where } } +/// Tendermint's Select Chain, where the best chain is defined as the most recently finalized +/// block. +/// +/// leaves panics on call due to not being applicable under Tendermint. Any provided answer would +/// have conflicts best left unraised. +// // SelectChain, while provided by Substrate and part of PartialComponents, isn't used by Substrate // It's common between various block-production/finality crates, yet Substrate as a system doesn't // rely on it, which is good, because its definition is explicitly incompatible with Tendermint diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index bb370e494..7ec59218b 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -102,6 +102,8 @@ pub trait TendermintValidator: TendermintClient { pub type TendermintImportQueue = BasicQueue; +/// Create an import queue, additionally returning the Tendermint Import object iself, enabling +/// creating an author later as well. pub fn import_queue( spawner: &impl sp_core::traits::SpawnEssentialNamed, client: Arc, diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index d184d8fd8..bfc8af392 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -22,6 +22,7 @@ use crate::{ authority::TendermintAuthority, }; +/// Tendermint import handler. pub struct TendermintImport { _ta: PhantomData, diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 177e7425f..9a1983906 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -79,6 +79,7 @@ impl Deref for Refresh { } } +/// Tendermint validators observer, providing data on the active validators. pub struct TendermintValidators(Refresh); impl TendermintValidators { From 45a5d3eb1d107a3e532ed7c5cb8fe1b3c1438f6e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 31 Oct 2022 23:56:13 -0400 Subject: [PATCH 088/186] Add another missing comment --- substrate/tendermint/primitives/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/substrate/tendermint/primitives/src/lib.rs b/substrate/tendermint/primitives/src/lib.rs index 50cc78d12..b5a1d6b24 100644 --- a/substrate/tendermint/primitives/src/lib.rs +++ b/substrate/tendermint/primitives/src/lib.rs @@ -4,6 +4,7 @@ use sp_core::sr25519::Public; use sp_std::vec::Vec; sp_api::decl_runtime_apis! { + /// TendermintApi trait for runtimes to implement. pub trait TendermintApi { /// Current session number. A session is NOT a fixed length of blocks, yet rather a continuous /// set of validators. From 2947ef08e3672420f90182e065923d2dbb903b98 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 1 Nov 2022 15:10:50 -0400 Subject: [PATCH 089/186] Make sign asynchronous Some relation to https://github.com/serai-dex/serai/issues/95. --- substrate/tendermint/machine/src/ext.rs | 7 +++++-- substrate/tendermint/machine/src/lib.rs | 17 ++++++++++------- substrate/tendermint/machine/tests/ext.rs | 7 +++++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/substrate/tendermint/machine/src/ext.rs b/substrate/tendermint/machine/src/ext.rs index 6becaa7a5..a466c1251 100644 --- a/substrate/tendermint/machine/src/ext.rs +++ b/substrate/tendermint/machine/src/ext.rs @@ -1,6 +1,8 @@ use core::{hash::Hash, fmt::Debug}; use std::sync::Arc; +use async_trait::async_trait; + use parity_scale_codec::{Encode, Decode}; use crate::{SignedMessage, commit_msg}; @@ -31,6 +33,7 @@ pub struct BlockNumber(pub u64); pub struct Round(pub u32); /// A signature scheme used by validators. +#[async_trait] pub trait SignatureScheme: Send + Sync { // Type used to identify validators. type ValidatorId: ValidatorId; @@ -43,7 +46,7 @@ pub trait SignatureScheme: Send + Sync { type AggregateSignature: Signature; /// Sign a signature with the current validator's private key. - fn sign(&self, msg: &[u8]) -> Self::Signature; + async fn sign(&self, msg: &[u8]) -> Self::Signature; /// Verify a signature from the validator in question. #[must_use] fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: &Self::Signature) -> bool; @@ -121,7 +124,7 @@ impl Block for B { } /// Trait representing the distributed system Tendermint is providing consensus over. -#[async_trait::async_trait] +#[async_trait] pub trait Network: Send + Sync { // Type used to identify validators. type ValidatorId: ValidatorId; diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 6efc16fd3..b0ba1a3dc 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -157,7 +157,9 @@ impl TendermintMachine { fn timeout(&self, step: Step) -> Instant { let mut round_time = Duration::from_secs(N::BLOCK_TIME.into()); round_time *= self.round.0 + 1; - let step_time = round_time / 3; // TODO: Non-uniform timeouts + // TODO: Non-uniform timeouts. Proposal has to validate the block which will take much longer + // than any other step + let step_time = round_time / 3; let offset = match step { Step::Propose => step_time, @@ -382,7 +384,7 @@ impl TendermintMachine { } if broadcast { - let sig = machine.signer.sign(&msg.encode()); + let sig = machine.signer.sign(&msg.encode()).await; machine.network.write().await.broadcast(SignedMessage { msg, sig }).await; } } @@ -435,10 +437,10 @@ impl TendermintMachine { if let Some(Data::Proposal(_, block)) = self.log.get(msg.round, proposer, Step::Propose) { // Check if it has gotten a sufficient amount of precommits // Use a junk signature since message equality disregards the signature - if self - .log - .has_consensus(msg.round, Data::Precommit(Some((block.id(), self.signer.sign(&[]))))) - { + if self.log.has_consensus( + msg.round, + Data::Precommit(Some((block.id(), self.signer.sign(&[]).await))), + ) { return Ok(Some(block.clone())); } } @@ -549,7 +551,8 @@ impl TendermintMachine { block.id(), self .signer - .sign(&commit_msg(self.canonical_end_time(self.round), block.id().as_ref())), + .sign(&commit_msg(self.canonical_end_time(self.round), block.id().as_ref())) + .await, )))); return Ok(None); } diff --git a/substrate/tendermint/machine/tests/ext.rs b/substrate/tendermint/machine/tests/ext.rs index dc9f3ed5a..5e9e7d52e 100644 --- a/substrate/tendermint/machine/tests/ext.rs +++ b/substrate/tendermint/machine/tests/ext.rs @@ -3,6 +3,8 @@ use std::{ time::{UNIX_EPOCH, SystemTime, Duration}, }; +use async_trait::async_trait; + use parity_scale_codec::{Encode, Decode}; use tokio::{sync::RwLock, time::sleep}; @@ -13,12 +15,13 @@ type TestValidatorId = u16; type TestBlockId = [u8; 4]; struct TestSignatureScheme(u16); +#[async_trait] impl SignatureScheme for TestSignatureScheme { type ValidatorId = TestValidatorId; type Signature = [u8; 32]; type AggregateSignature = Vec<[u8; 32]>; - fn sign(&self, msg: &[u8]) -> [u8; 32] { + async fn sign(&self, msg: &[u8]) -> [u8; 32] { let mut sig = [0; 32]; sig[.. 2].copy_from_slice(&self.0.to_le_bytes()); sig[2 .. (2 + 30.min(msg.len()))].copy_from_slice(&msg[.. 30.min(msg.len())]); @@ -81,7 +84,7 @@ impl Block for TestBlock { struct TestNetwork(u16, Arc>>>); -#[async_trait::async_trait] +#[async_trait] impl Network for TestNetwork { type ValidatorId = TestValidatorId; type SignatureScheme = TestSignatureScheme; From 19154cf8e1b4177c39812db67336b64ca5911055 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 1 Nov 2022 16:28:08 -0400 Subject: [PATCH 090/186] Move sc_tendermint to async sign --- substrate/tendermint/client/src/validators.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 9a1983906..58fdde288 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -1,6 +1,8 @@ use core::{marker::PhantomData, ops::Deref}; use std::sync::{Arc, RwLock}; +use async_trait::async_trait; + use sp_application_crypto::{ RuntimePublic as PublicTrait, Pair as PairTrait, sr25519::{Public, Pair, Signature}, @@ -92,12 +94,13 @@ impl TendermintValidators { } } +#[async_trait] impl SignatureScheme for TendermintValidators { type ValidatorId = u16; type Signature = Signature; type AggregateSignature = Vec; - fn sign(&self, msg: &[u8]) -> Signature { + async fn sign(&self, msg: &[u8]) -> Signature { self.0.read().unwrap().keys.sign(msg) } From aa0a4cf10657be4d4bd44d454b50a02e257b1d2d Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 1 Nov 2022 16:37:50 -0400 Subject: [PATCH 091/186] Implement proper checking of inherents --- Cargo.lock | 2 + substrate/tendermint/client/Cargo.toml | 1 + .../client/src/authority/import_future.rs | 20 ++++++-- .../tendermint/client/src/authority/mod.rs | 39 ++++----------- substrate/tendermint/client/src/lib.rs | 10 ++-- substrate/tendermint/client/src/tendermint.rs | 49 +++++++++++++++++-- substrate/tendermint/machine/Cargo.toml | 4 +- substrate/tendermint/machine/src/ext.rs | 5 +- 8 files changed, 89 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 067c49b7b..7d54c17a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7328,6 +7328,7 @@ dependencies = [ "async-trait", "futures", "log", + "sc-block-builder", "sc-client-api", "sc-consensus", "sc-executor", @@ -8884,6 +8885,7 @@ dependencies = [ "async-trait", "parity-scale-codec", "sp-runtime", + "thiserror", "tokio", ] diff --git a/substrate/tendermint/client/Cargo.toml b/substrate/tendermint/client/Cargo.toml index 72bb1b1e7..ab814f18a 100644 --- a/substrate/tendermint/client/Cargo.toml +++ b/substrate/tendermint/client/Cargo.toml @@ -37,6 +37,7 @@ sc-network = { git = "https://github.com/serai-dex/substrate" } sc-network-gossip = { git = "https://github.com/serai-dex/substrate" } sc-service = { git = "https://github.com/serai-dex/substrate" } sc-client-api = { git = "https://github.com/serai-dex/substrate" } +sc-block-builder = { git = "https://github.com/serai-dex/substrate" } sc-consensus = { git = "https://github.com/serai-dex/substrate" } substrate-prometheus-endpoint = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/tendermint/client/src/authority/import_future.rs b/substrate/tendermint/client/src/authority/import_future.rs index ba372c4bc..9239bc29e 100644 --- a/substrate/tendermint/client/src/authority/import_future.rs +++ b/substrate/tendermint/client/src/authority/import_future.rs @@ -7,27 +7,39 @@ use std::{ use sp_runtime::traits::{Header, Block}; +use sp_consensus::Error; use sc_consensus::{BlockImportStatus, BlockImportError, Link}; use sc_service::ImportQueue; +use tendermint_machine::ext::BlockError; + use crate::TendermintImportQueue; // Custom helpers for ImportQueue in order to obtain the result of a block's importing -struct ValidateLink(Option<(B::Hash, bool)>); +struct ValidateLink(Option<(B::Hash, Result<(), BlockError>)>); impl Link for ValidateLink { fn blocks_processed( &mut self, imported: usize, count: usize, - results: Vec<( + mut results: Vec<( Result::Number>, BlockImportError>, B::Hash, )>, ) { assert_eq!(imported, 1); assert_eq!(count, 1); - self.0 = Some((results[0].1, results[0].0.is_ok())); + self.0 = Some(( + results[0].1, + match results.swap_remove(0).0 { + Ok(_) => Ok(()), + Err(BlockImportError::Other(Error::Other(err))) => Err( + err.downcast::().map(|boxed| *boxed.as_ref()).unwrap_or(BlockError::Fatal), + ), + _ => Err(BlockError::Fatal), + }, + )); } } @@ -45,7 +57,7 @@ impl<'a, B: Block, T: Send> ImportFuture<'a, B, T> { } impl<'a, B: Block, T: Send> Future for ImportFuture<'a, B, T> { - type Output = bool; + type Output = Result<(), BlockError>; fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { let mut link = ValidateLink(None); diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 2280de78a..97be84edb 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -10,7 +10,6 @@ use log::warn; use tokio::task::yield_now; use sp_core::{Encode, Decode}; -use sp_inherents::{InherentData, InherentDataProvider, CreateInherentDataProviders}; use sp_runtime::{ traits::{Header, Block}, Digest, @@ -105,29 +104,7 @@ impl TendermintAuthority { } pub(crate) async fn get_proposal(&mut self, header: &::Header) -> T::Block { - let inherent_data = match self - .import - .providers - .read() - .await - .as_ref() - .unwrap() - .create_inherent_data_providers(header.hash(), ()) - .await - { - Ok(providers) => match providers.create_inherent_data() { - Ok(data) => Some(data), - Err(err) => { - warn!(target: "tendermint", "Failed to create inherent data: {}", err); - None - } - }, - Err(err) => { - warn!(target: "tendermint", "Failed to create inherent data providers: {}", err); - None - } - } - .unwrap_or_else(InherentData::new); + let parent = *header.parent_hash(); let proposer = self .active @@ -137,9 +114,15 @@ impl TendermintAuthority { .init(header) .await .expect("Failed to create a proposer for the new block"); - // TODO: Production time, size limit + proposer - .propose(inherent_data, Digest::default(), Duration::from_secs(1), None) + .propose( + self.import.inherent_data(parent).await, + Digest::default(), + // TODO: Production time, size limit + Duration::from_secs(1), + None, + ) .await .expect("Failed to crate a new block proposal") .block @@ -316,9 +299,7 @@ impl Network for TendermintAuthority { }], ); - if !ImportFuture::new(hash, queue_write.as_mut().unwrap()).await { - todo!() - } + ImportFuture::new(hash, queue_write.as_mut().unwrap()).await?; // Sanity checks that a child block can have less work than its parent { diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 7ec59218b..e40cd5acb 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -7,6 +7,7 @@ use sp_api::{StateBackend, StateBackendFor, TransactionFor, ApiExt, ProvideRunti use sp_consensus::{Error, Environment}; use sc_client_api::{BlockBackend, Backend, Finalizer}; +use sc_block_builder::BlockBuilderApi; use sc_consensus::{BlockImport, BasicQueue}; use sc_network::NetworkBlock; use sc_network_gossip::Network; @@ -43,7 +44,9 @@ pub trait TendermintClient: Send + Sync + 'static { Transaction = Self::BackendTransaction, >; // Client::Api - type Api: ApiExt + TendermintApi; + type Api: ApiExt + + BlockBuilderApi + + TendermintApi; type Client: Send + Sync + HeaderBackend @@ -60,7 +63,7 @@ pub trait TendermintClientMinimal: Send + Sync + 'static { type Block: Block; type Backend: Backend + 'static; - type Api: ApiExt + TendermintApi; + type Api: ApiExt + BlockBuilderApi + TendermintApi; type Client: Send + Sync + HeaderBackend @@ -73,7 +76,8 @@ pub trait TendermintClientMinimal: Send + Sync + 'static { impl TendermintClient for T where - >::Api: TendermintApi, + >::Api: + BlockBuilderApi + TendermintApi, TransactionFor: Send + Sync + 'static, { const BLOCK_TIME_IN_SECONDS: u32 = T::BLOCK_TIME_IN_SECONDS; diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index bfc8af392..4fc9384cd 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -3,6 +3,8 @@ use std::{ sync::{Arc, RwLock}, }; +use log::warn; + use tokio::sync::RwLock as AsyncRwLock; use sp_core::Decode; @@ -10,12 +12,16 @@ use sp_runtime::{ traits::{Header, Block}, Justification, }; +use sp_inherents::{InherentData, InherentDataProvider, CreateInherentDataProviders}; use sp_blockchain::HeaderBackend; +use sp_api::{BlockId, ProvideRuntimeApi}; use sp_consensus::Error; use sc_consensus::{ForkChoiceStrategy, BlockImportParams}; -use tendermint_machine::ext::{Commit, Network}; +use sc_block_builder::BlockBuilderApi; + +use tendermint_machine::ext::{BlockError, Commit, Network}; use crate::{ CONSENSUS_ID, TendermintValidator, validators::TendermintValidators, TendermintImportQueue, @@ -67,9 +73,46 @@ impl TendermintImport { } } + pub(crate) async fn inherent_data(&self, parent: ::Hash) -> InherentData { + match self + .providers + .read() + .await + .as_ref() + .unwrap() + .create_inherent_data_providers(parent, ()) + .await + { + Ok(providers) => match providers.create_inherent_data() { + Ok(data) => Some(data), + Err(err) => { + warn!(target: "tendermint", "Failed to create inherent data: {}", err); + None + } + }, + Err(err) => { + warn!(target: "tendermint", "Failed to create inherent data providers: {}", err); + None + } + } + .unwrap_or_else(InherentData::new) + } + async fn check_inherents(&self, block: T::Block) -> Result<(), Error> { - // TODO - Ok(()) + let inherent_data = self.inherent_data(*block.header().parent_hash()).await; + let err = self + .client + .runtime_api() + .check_inherents(&BlockId::Hash(self.client.info().finalized_hash), block, inherent_data) + .map_err(|_| Error::Other(BlockError::Fatal.into()))?; + + if err.ok() { + Ok(()) + } else if err.fatal_error() { + Err(Error::Other(BlockError::Fatal.into())) + } else { + Err(Error::Other(BlockError::Temporal.into())) + } } // Ensure this is part of a sequential import diff --git a/substrate/tendermint/machine/Cargo.toml b/substrate/tendermint/machine/Cargo.toml index 330c685af..7fc41bbce 100644 --- a/substrate/tendermint/machine/Cargo.toml +++ b/substrate/tendermint/machine/Cargo.toml @@ -8,9 +8,11 @@ authors = ["Luke Parker "] edition = "2021" [dependencies] +async-trait = "0.1" +thiserror = "1" + parity-scale-codec = { version = "3.2", features = ["derive"] } -async-trait = "0.1" tokio = { version = "1", features = ["macros", "sync", "time", "rt"] } sp-runtime = { git = "https://github.com/serai-dex/substrate", optional = true } diff --git a/substrate/tendermint/machine/src/ext.rs b/substrate/tendermint/machine/src/ext.rs index a466c1251..f30300274 100644 --- a/substrate/tendermint/machine/src/ext.rs +++ b/substrate/tendermint/machine/src/ext.rs @@ -2,6 +2,7 @@ use core::{hash::Hash, fmt::Debug}; use std::sync::Arc; use async_trait::async_trait; +use thiserror::Error; use parity_scale_codec::{Encode, Decode}; @@ -97,12 +98,14 @@ pub trait Weights: Send + Sync { } /// Simplified error enum representing a block's validity. -#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Error, Encode, Decode)] pub enum BlockError { /// Malformed block which is wholly invalid. + #[error("invalid block")] Fatal, /// Valid block by syntax, with semantics which may or may not be valid yet are locally /// considered invalid. If a block fails to validate with this, a slash will not be triggered. + #[error("invalid block under local view")] Temporal, } From 5832007a4584a977ef45b4847d7699124462bc47 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 1 Nov 2022 20:06:42 -0400 Subject: [PATCH 092/186] Take in a Keystore and validator ID --- Cargo.lock | 1 + substrate/tendermint/client/Cargo.toml | 1 + .../tendermint/client/src/authority/mod.rs | 10 ++-- substrate/tendermint/client/src/lib.rs | 9 +++ substrate/tendermint/client/src/validators.rs | 55 ++++++++++++++----- 5 files changed, 56 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d54c17a7..cc72ce407 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7342,6 +7342,7 @@ dependencies = [ "sp-consensus", "sp-core", "sp-inherents", + "sp-keystore", "sp-runtime", "sp-staking", "sp-tendermint", diff --git a/substrate/tendermint/client/Cargo.toml b/substrate/tendermint/client/Cargo.toml index ab814f18a..802153101 100644 --- a/substrate/tendermint/client/Cargo.toml +++ b/substrate/tendermint/client/Cargo.toml @@ -22,6 +22,7 @@ tokio = { version = "1", features = ["sync", "rt"] } sp-core = { git = "https://github.com/serai-dex/substrate" } sp-application-crypto = { git = "https://github.com/serai-dex/substrate" } +sp-keystore = { git = "https://github.com/serai-dex/substrate" } sp-inherents = { git = "https://github.com/serai-dex/substrate" } sp-staking = { git = "https://github.com/serai-dex/substrate" } sp-blockchain = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 97be84edb..cdc547bf8 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -135,6 +135,7 @@ impl TendermintAuthority { providers: T::CIDP, env: T::Environment, network: T::Network, + validator: (u16, T::Keystore), registry: Option<&Registry>, ) { let (best_hash, last) = self.get_last(); @@ -164,16 +165,15 @@ impl TendermintAuthority { env, announce: network, }); + let (validator, keys) = validator; + self.import.validators.set_keys(keys).await; let proposal = self .get_proposal(&self.import.client.header(BlockId::Hash(best_hash)).unwrap().unwrap()) .await; - TendermintMachine::new( - self, // We no longer need self, so let TendermintMachine become its owner - 0, // TODO: ValidatorId - last, proposal, - ) + // We no longer need self, so let TendermintMachine become its owner + TendermintMachine::new(self, validator, last, proposal) }; // Start receiving messages about the Tendermint process for this block diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index e40cd5acb..859bd304f 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use sp_core::crypto::KeyTypeId; +use sp_keystore::CryptoStore; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::{Header, Block}; use sp_blockchain::HeaderBackend; @@ -28,6 +30,7 @@ pub(crate) mod authority; pub use authority::TendermintAuthority; const CONSENSUS_ID: [u8; 4] = *b"tend"; +const KEY_TYPE_ID: KeyTypeId = KeyTypeId(CONSENSUS_ID); /// Trait consolidating all generics required by sc_tendermint for processing. pub trait TendermintClient: Send + Sync + 'static { @@ -55,6 +58,8 @@ pub trait TendermintClient: Send + Sync + 'static { + Finalizer + ProvideRuntimeApi + 'static; + + type Keystore: CryptoStore; } /// Trait implementable on firm types to automatically provide a full TendermintClient impl. @@ -72,6 +77,8 @@ pub trait TendermintClientMinimal: Send + Sync + 'static { + Finalizer + ProvideRuntimeApi + 'static; + + type Keystore: CryptoStore; } impl TendermintClient for T @@ -89,6 +96,8 @@ where type StateBackend = StateBackendFor; type Api = >::Api; type Client = T::Client; + + type Keystore = T::Keystore; } /// Trait consolidating additional generics required by sc_tendermint for authoring. diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 58fdde288..210178d9b 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -3,10 +3,14 @@ use std::sync::{Arc, RwLock}; use async_trait::async_trait; +use tokio::sync::RwLock as AsyncRwLock; + +use sp_core::Decode; use sp_application_crypto::{ - RuntimePublic as PublicTrait, Pair as PairTrait, - sr25519::{Public, Pair, Signature}, + RuntimePublic as PublicTrait, + sr25519::{Public, Signature}, }; +use sp_keystore::CryptoStore; use sp_staking::SessionIndex; use sp_api::{BlockId, ProvideRuntimeApi}; @@ -17,7 +21,7 @@ use tendermint_machine::ext::{BlockNumber, Round, Weights, SignatureScheme}; use sp_tendermint::TendermintApi; -use crate::TendermintClient; +use crate::{KEY_TYPE_ID, TendermintClient}; struct TendermintValidatorsStruct { session: SessionIndex, @@ -25,19 +29,18 @@ struct TendermintValidatorsStruct { total_weight: u64, weights: Vec, - keys: Pair, // TODO: sp_keystore lookup: Vec, } impl TendermintValidatorsStruct { - fn from_module(client: &Arc) -> TendermintValidatorsStruct { + fn from_module(client: &Arc) -> Self { let last = client.info().finalized_hash; let api = client.runtime_api(); let session = api.current_session(&BlockId::Hash(last)).unwrap(); let validators = api.validators(&BlockId::Hash(last)).unwrap(); assert_eq!(validators.len(), 1); - let keys = Pair::from_string("//Alice", None).unwrap(); - TendermintValidatorsStruct { + + Self { session, // TODO @@ -45,7 +48,6 @@ impl TendermintValidatorsStruct { weights: vec![1; validators.len()], lookup: validators, - keys, } } } @@ -82,15 +84,25 @@ impl Deref for Refresh { } /// Tendermint validators observer, providing data on the active validators. -pub struct TendermintValidators(Refresh); +pub struct TendermintValidators( + Refresh, + Arc>>, +); impl TendermintValidators { pub(crate) fn new(client: Arc) -> TendermintValidators { - TendermintValidators(Refresh { - _tc: PhantomData, - _refresh: Arc::new(RwLock::new(TendermintValidatorsStruct::from_module::(&client))), - client, - }) + TendermintValidators( + Refresh { + _tc: PhantomData, + _refresh: Arc::new(RwLock::new(TendermintValidatorsStruct::from_module::(&client))), + client, + }, + Arc::new(AsyncRwLock::new(None)), + ) + } + + pub(crate) async fn set_keys(&self, keys: T::Keystore) { + *self.1.write().await = Some(keys); } } @@ -101,7 +113,20 @@ impl SignatureScheme for TendermintValidators { type AggregateSignature = Vec; async fn sign(&self, msg: &[u8]) -> Signature { - self.0.read().unwrap().keys.sign(msg) + let read = self.1.read().await; + let keys = read.as_ref().unwrap(); + let key = { + let pubs = keys.sr25519_public_keys(KEY_TYPE_ID).await; + if pubs.is_empty() { + keys.sr25519_generate_new(KEY_TYPE_ID, None).await.unwrap() + } else { + pubs[0] + } + }; + Signature::decode( + &mut keys.sign_with(KEY_TYPE_ID, &key.into(), msg).await.unwrap().unwrap().as_ref(), + ) + .unwrap() } fn verify(&self, validator: u16, msg: &[u8], sig: &Signature) -> bool { From 9a26ac6899c56e74fee7b3b39f5ab6db332d1e8b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 1 Nov 2022 20:58:59 -0400 Subject: [PATCH 093/186] Remove unnecessary PhantomDatas --- substrate/tendermint/client/src/tendermint.rs | 11 +---------- substrate/tendermint/client/src/validators.rs | 4 +--- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 4fc9384cd..285075d69 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -1,7 +1,4 @@ -use std::{ - marker::PhantomData, - sync::{Arc, RwLock}, -}; +use std::sync::{Arc, RwLock}; use log::warn; @@ -30,8 +27,6 @@ use crate::{ /// Tendermint import handler. pub struct TendermintImport { - _ta: PhantomData, - pub(crate) validators: Arc>, pub(crate) providers: Arc>>, @@ -45,8 +40,6 @@ pub struct TendermintImport { impl Clone for TendermintImport { fn clone(&self) -> Self { TendermintImport { - _ta: PhantomData, - validators: self.validators.clone(), providers: self.providers.clone(), @@ -61,8 +54,6 @@ impl Clone for TendermintImport { impl TendermintImport { pub(crate) fn new(client: Arc) -> TendermintImport { TendermintImport { - _ta: PhantomData, - validators: Arc::new(TendermintValidators::new(client.clone())), providers: Arc::new(AsyncRwLock::new(None)), diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 210178d9b..fe718b858 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -1,4 +1,4 @@ -use core::{marker::PhantomData, ops::Deref}; +use core::ops::Deref; use std::sync::{Arc, RwLock}; use async_trait::async_trait; @@ -54,7 +54,6 @@ impl TendermintValidatorsStruct { // Wrap every access of the validators struct in something which forces calling refresh struct Refresh { - _tc: PhantomData, client: Arc, _refresh: Arc>, } @@ -93,7 +92,6 @@ impl TendermintValidators { pub(crate) fn new(client: Arc) -> TendermintValidators { TendermintValidators( Refresh { - _tc: PhantomData, _refresh: Arc::new(RwLock::new(TendermintValidatorsStruct::from_module::(&client))), client, }, From 86aaadaea01569c6685d900e55fa0ab6029b1365 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 1 Nov 2022 21:32:18 -0400 Subject: [PATCH 094/186] Update node to latest sc_tendermint --- substrate/node/src/service.rs | 1 + substrate/tendermint/client/src/authority/mod.rs | 3 ++- substrate/tendermint/client/src/lib.rs | 7 ------- substrate/tendermint/client/src/validators.rs | 4 ++-- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index bc2e9745d..88610c513 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -223,6 +223,7 @@ pub async fn new_full(config: Configuration) -> Result TendermintAuthority { /// as it will not return until the P2P stack shuts down. pub async fn authority( mut self, + validator: (u16, Arc), providers: T::CIDP, env: T::Environment, network: T::Network, - validator: (u16, T::Keystore), registry: Option<&Registry>, ) { let (best_hash, last) = self.get_last(); diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 859bd304f..c9175d881 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use sp_core::crypto::KeyTypeId; -use sp_keystore::CryptoStore; use sp_inherents::CreateInherentDataProviders; use sp_runtime::traits::{Header, Block}; use sp_blockchain::HeaderBackend; @@ -58,8 +57,6 @@ pub trait TendermintClient: Send + Sync + 'static { + Finalizer + ProvideRuntimeApi + 'static; - - type Keystore: CryptoStore; } /// Trait implementable on firm types to automatically provide a full TendermintClient impl. @@ -77,8 +74,6 @@ pub trait TendermintClientMinimal: Send + Sync + 'static { + Finalizer + ProvideRuntimeApi + 'static; - - type Keystore: CryptoStore; } impl TendermintClient for T @@ -96,8 +91,6 @@ where type StateBackend = StateBackendFor; type Api = >::Api; type Client = T::Client; - - type Keystore = T::Keystore; } /// Trait consolidating additional generics required by sc_tendermint for authoring. diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index fe718b858..01b374433 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -85,7 +85,7 @@ impl Deref for Refresh { /// Tendermint validators observer, providing data on the active validators. pub struct TendermintValidators( Refresh, - Arc>>, + Arc>>>, ); impl TendermintValidators { @@ -99,7 +99,7 @@ impl TendermintValidators { ) } - pub(crate) async fn set_keys(&self, keys: T::Keystore) { + pub(crate) async fn set_keys(&self, keys: Arc) { *self.1.write().await = Some(keys); } } From e3fc3f28fbf27adeb3f0de738c511bd18c438189 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 1 Nov 2022 23:10:36 -0400 Subject: [PATCH 095/186] Configure node for a multi-node testnet --- Cargo.lock | 3 + deploy/docker-compose.yml | 10 ++-- deploy/serai/Dockerfile | 3 +- deploy/serai/scripts/entry-dev.sh | 4 +- substrate/node/Cargo.toml | 4 ++ substrate/node/src/chain_spec.rs | 8 ++- substrate/node/src/service.rs | 59 +++++++++++++------ substrate/tendermint/client/src/lib.rs | 4 +- substrate/tendermint/client/src/validators.rs | 1 - 9 files changed, 65 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc72ce407..d5adcdbf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7589,6 +7589,7 @@ dependencies = [ "frame-benchmarking-cli", "frame-system", "jsonrpsee", + "log", "pallet-tendermint", "pallet-transaction-payment", "pallet-transaction-payment-rpc", @@ -7616,7 +7617,9 @@ dependencies = [ "sp-core", "sp-inherents", "sp-keyring", + "sp-keystore", "sp-runtime", + "sp-tendermint", "sp-timestamp", "substrate-build-script-utils", "substrate-frame-rpc-system", diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 6f1e48555..232d0a2cf 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -25,7 +25,6 @@ volumes: services: - _serai: &serai_defaults restart: unless-stopped @@ -61,7 +60,7 @@ services: - cluster-coins-lg environment: CHAIN: dev - NAME: Alice + NAME: alice VALIDATOR: true serai-bob: @@ -75,7 +74,8 @@ services: - cluster-coins-lg environment: CHAIN: dev - NAME: Bob + NAME: bob + VALIDATOR: true serai-charlie: <<: *serai_defaults @@ -88,7 +88,8 @@ services: - cluster-coins-lg environment: CHAIN: dev - NAME: Charlie + NAME: charlie + VALIDATOR: true serai-dave: <<: *serai_defaults @@ -122,6 +123,7 @@ services: environment: CHAIN: dev NAME: Ferdie + # Processor Services # Coin Services diff --git a/deploy/serai/Dockerfile b/deploy/serai/Dockerfile index b46543cdb..9c7f13706 100644 --- a/deploy/serai/Dockerfile +++ b/deploy/serai/Dockerfile @@ -26,12 +26,11 @@ RUN --mount=type=cache,target=/usr/local/cargo/git \ --mount=type=cache,target=/serai/target/release/build \ --mount=type=cache,target=/serai/target/release/deps \ --mount=type=cache,target=/serai/target/release/.fingerprint \ - --mount=type=cache,target=/serai/target/release/incremental \ + --mount=type=cache,target=/serai/target/release/incremental \ --mount=type=cache,target=/serai/target/release/wbuild \ --mount=type=cache,target=/serai/target/release/lib* \ cargo build --release - # Prepare Image FROM ubuntu:latest as image LABEL description="STAGE 2: Copy and Run" diff --git a/deploy/serai/scripts/entry-dev.sh b/deploy/serai/scripts/entry-dev.sh index 5e8353b9e..911da8f5f 100755 --- a/deploy/serai/scripts/entry-dev.sh +++ b/deploy/serai/scripts/entry-dev.sh @@ -1,6 +1,6 @@ #!/bin/bash if [[ -z $VALIDATOR ]]; then - serai-node --chain $CHAIN --name $NAME + serai-node --chain $CHAIN --$NAME else - serai-node --chain $CHAIN --name $NAME --validator + serai-node --chain $CHAIN --$NAME --validator fi diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index a6b74b5e9..50394c7fe 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -14,11 +14,14 @@ name = "serai-node" [dependencies] async-trait = "0.1" +log = "0.4" + clap = { version = "4", features = ["derive"] } jsonrpsee = { version = "0.15", features = ["server"] } sp-core = { git = "https://github.com/serai-dex/substrate" } sp-application-crypto = { git = "https://github.com/serai-dex/substrate" } +sp-keystore = { git = "https://github.com/serai-dex/substrate" } sp-keyring = { git = "https://github.com/serai-dex/substrate" } sp-inherents = { git = "https://github.com/serai-dex/substrate" } sp-timestamp = { git = "https://github.com/serai-dex/substrate" } @@ -53,6 +56,7 @@ sc-rpc-api = { git = "https://github.com/serai-dex/substrate" } substrate-frame-rpc-system = { git = "https://github.com/serai-dex/substrate" } pallet-transaction-payment-rpc = { git = "https://github.com/serai-dex/substrate" } +sp-tendermint = { path = "../tendermint/primitives" } pallet-tendermint = { path = "../tendermint/pallet", default-features = false } serai-runtime = { path = "../runtime" } sc_tendermint = { path = "../tendermint/client" } diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index ab0d1f56f..aac15e502 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -19,7 +19,11 @@ fn account_id_from_name(name: &'static str) -> AccountId { } fn testnet_genesis(wasm_binary: &[u8], endowed_accounts: Vec) -> GenesisConfig { - let alice = account_id_from_name("Alice"); + let session_key = |name| { + let key = account_id_from_name(name); + (key, key, SessionKeys { tendermint: Public::from(key) }) + }; + GenesisConfig { system: SystemConfig { code: wasm_binary.to_vec() }, balances: BalancesConfig { @@ -27,7 +31,7 @@ fn testnet_genesis(wasm_binary: &[u8], endowed_accounts: Vec) -> Gene }, transaction_payment: Default::default(), session: SessionConfig { - keys: vec![(alice, alice, SessionKeys { tendermint: Public::from(alice) })], + keys: vec![session_key("Alice"), session_key("Bob"), session_key("Charlie")], }, } } diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 88610c513..5a122df12 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -1,9 +1,11 @@ use std::{boxed::Box, sync::Arc, error::Error}; +use sp_keystore::SyncCryptoStore; use sp_runtime::traits::{Block as BlockTrait}; use sp_inherents::CreateInherentDataProviders; use sp_consensus::DisableProofRecording; -use sp_api::ProvideRuntimeApi; +use sp_api::{BlockId, ProvideRuntimeApi}; +use sp_tendermint::TendermintApi; use sc_executor::{NativeVersion, NativeExecutionDispatch, NativeElseWasmExecutor}; use sc_transaction_pool::FullPool; @@ -219,23 +221,44 @@ pub async fn new_full(config: Configuration) -> Result Date: Wed, 2 Nov 2022 02:43:08 -0400 Subject: [PATCH 096/186] Fix handling of the GossipEngine --- substrate/node/src/service.rs | 12 +++++++++++- substrate/tendermint/client/src/authority/mod.rs | 6 ++++-- substrate/tendermint/client/src/lib.rs | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 5a122df12..fcf863ffb 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -154,7 +154,7 @@ pub fn new_partial( )) } -pub async fn new_full(config: Configuration) -> Result { +pub async fn new_full(mut config: Configuration) -> Result { let ( authority, sc_service::PartialComponents { @@ -169,6 +169,16 @@ pub async fn new_full(config: Configuration) -> Result TendermintAuthority { // Create the gossip network let mut gossip = GossipEngine::new( network.clone(), - "tendermint", + PROTOCOL_NAME, Arc::new(TendermintGossip::new(number.clone(), self.import.validators.clone())), registry, ); @@ -190,6 +191,7 @@ impl TendermintAuthority { // Handle any received messages // This inner loop enables handling all pending messages before acquiring the out-queue lock // again + futures::poll!(&mut gossip); 'inner: loop { match recv.try_next() { Ok(Some(msg)) => handle diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 6b12eaab0..5a9e1fdd5 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -30,6 +30,7 @@ pub use authority::TendermintAuthority; pub const CONSENSUS_ID: [u8; 4] = *b"tend"; pub const KEY_TYPE_ID: KeyTypeId = KeyTypeId(CONSENSUS_ID); +pub const PROTOCOL_NAME: &str = "/serai/tendermint/1"; /// Trait consolidating all generics required by sc_tendermint for processing. pub trait TendermintClient: Send + Sync + 'static { From ca043f55ad3726d8caa70283d6538bdbe5cf7f3c Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 03:05:04 -0400 Subject: [PATCH 097/186] Use a rounded genesis to obtain sufficient synchrony within the Docker env --- substrate/tendermint/client/src/authority/mod.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 281ca0bcd..1f1768769 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -78,6 +78,11 @@ impl TendermintAuthority { fn get_last(&self) -> (::Hash, (BlockNumber, u64)) { let info = self.import.client.info(); + // TODO: Genesis start time + BLOCK_TIME + let mut fake_genesis = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + // Round up to the nearest 5s increment + fake_genesis += 5 - (fake_genesis % 5); + ( info.finalized_hash, ( @@ -99,8 +104,7 @@ impl TendermintAuthority { .as_ref(), ) .map(|commit| commit.end_time) - // TODO: Genesis start time + BLOCK_TIME - .unwrap_or_else(|_| SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()), + .unwrap_or(fake_genesis), ), ) } @@ -189,9 +193,11 @@ impl TendermintAuthority { } // Handle any received messages - // This inner loop enables handling all pending messages before acquiring the out-queue lock + // This inner loop enables handling all pending messages before acquiring the out-queue lock // again - futures::poll!(&mut gossip); + // TODO: Move to a select model. The disadvantage of this is we'll more frequently acquire + // the above lock, despite lack of reason to do so + let _ = futures::poll!(&mut gossip); 'inner: loop { match recv.try_next() { Ok(Some(msg)) => handle From 2182b6641a6621b37f353fc55b74ee3a3ad1e677 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 03:18:49 -0400 Subject: [PATCH 098/186] Correct Serai d-f names in Docker --- deploy/docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 232d0a2cf..e69188d9a 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -100,7 +100,7 @@ services: - cluster-coins-lg environment: CHAIN: dev - NAME: Dave + NAME: dave serai-eve: <<: *serai_defaults @@ -111,7 +111,7 @@ services: - cluster-coins-lg environment: CHAIN: dev - NAME: Eve + NAME: eve serai-ferdie: <<: *serai_defaults @@ -122,7 +122,7 @@ services: - cluster-coins-lg environment: CHAIN: dev - NAME: Ferdie + NAME: ferdie # Processor Services From 16065ccd4e47655d8d4d7da71b7dc520353f88a0 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 03:29:04 -0400 Subject: [PATCH 099/186] Remove an attempt at caching I don't believe would ever hit --- substrate/tendermint/client/src/tendermint.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 285075d69..901c591e6 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -176,16 +176,6 @@ impl TendermintImport { &self, block: &mut BlockImportParams, ) -> Result<(), Error> { - if block.finalized { - if block.fork_choice != Some(ForkChoiceStrategy::Custom(false)) { - // Since we alw1ays set the fork choice, this means something else marked the block as - // finalized, which shouldn't be possible. Ensuring nothing else is setting blocks as - // finalized helps ensure our security - panic!("block was finalized despite not setting the fork choice"); - } - return Ok(()); - } - // Set the block as a worse choice block.fork_choice = Some(ForkChoiceStrategy::Custom(false)); From 40b6cb71067e1765658e13e155706a0b2bafb6f5 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 03:35:46 -0400 Subject: [PATCH 100/186] Add an already in chain check to block import While the inner should do this for us, we call verify_order on our end *before* inner to ensure sequential import. Accordingly, we need to provide our own check. Removes errors of "non-sequential import" when trying to re-import an existing block. --- substrate/tendermint/client/src/block_import.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index b5ebb3a4a..64c989c04 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use sp_api::BlockId; use sp_runtime::traits::Block; -use sp_blockchain::{HeaderBackend, Backend as BlockchainBackend}; +use sp_blockchain::{BlockStatus, HeaderBackend, Backend as BlockchainBackend}; use sp_consensus::{Error, CacheKeyId, SelectChain}; use sc_consensus::{BlockCheckParams, BlockImportParams, ImportResult, BlockImport, Verifier}; @@ -28,6 +28,9 @@ where &mut self, mut block: BlockCheckParams, ) -> Result { + if self.client.status(BlockId::Hash(block.hash)).unwrap() == BlockStatus::InChain { + return Ok(ImportResult::AlreadyInChain); + } self.verify_order(block.parent_hash, block.number)?; // Does not verify origin here as origin only applies to unfinalized blocks From 5cfe2d5c59668b038a119b8c1f4b925b2e1b699c Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 04:07:15 -0400 Subject: [PATCH 101/186] Update the consensus documentation It was incredibly out of date. --- docs/Serai.md | 6 +++--- docs/protocol/Consensus.md | 43 ++++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/docs/Serai.md b/docs/Serai.md index 23b2269be..683bc8975 100644 --- a/docs/Serai.md +++ b/docs/Serai.md @@ -1,8 +1,8 @@ # Serai -Serai is a decentralization execution layer whose validators form multisig -wallets for various connected networks, offering secure decentralized custody of -foreign assets to applications built on it. +Serai is a decentralized execution layer whose validators form multisig wallets +for various connected networks, offering secure decentralized custody of foreign +assets to applications built on it. Serai is exemplified by Serai DEX, an automated-market-maker (AMM) decentralized exchange, allowing swapping BTC, ETH, USDC, DAI, and XMR. It is the premier diff --git a/docs/protocol/Consensus.md b/docs/protocol/Consensus.md index ddb42e963..46616ec32 100644 --- a/docs/protocol/Consensus.md +++ b/docs/protocol/Consensus.md @@ -1,25 +1,32 @@ -# Oraclization (message) - -`Oraclization` messages are published by the current block producer and communicate an external event being communicated to the native chain. This is presumably some other cryptocurrency, such as BTC, being sent to the multisig wallet, triggering a privileged call enabling relevant actions. - -# Report (message) - -`Report` reports a validator for malicious or invalid behavior. This may be publishing a false `Oraclization` or failing to participate as expected. These apply a penalty to the validator's assigned rewards, which is distinct from the bond which must be kept as a multiple of 1m. If the amount deducted exceeds their assigned rewards, they are scheduled for removal with an appropriately reduced bond. - # Consensus -Consensus is a modified Aura implementation with the following notes: +### Inherent Transactions + +Inherent transactions are a feature of Substrate enabling block producers to +include transactions without overhead. This enables forming a leader protocol +for including various forms of information on chain, such as In Instruction. By +having a single node include the data, we prevent having pointless replicas on +chain. -- Stateful nodes exist in two forms. Contextless and contextual. -- Context is inserted by external programs which are run under the same umbrella as the node and trusted. -- Contextless nodes do not perform verification beyond technical validity on `Oraclization` and `Report`. -- Contextual nodes do perform verification on `Oraclization` and `Report` and will reject transactions which do not represent the actual context. -- If a block is finalized under Aura, contextual checks are always stated to be passing, even if the local context conflicts with it. +In order to ensure the validity of the inherent transactions, the consensus +process validates them. Under Substrate, a block with inherents is checked by +all nodes, and independently accepted or rejected. Under Serai, a block with +inherents is checked by the validators, and if a BFT majority of validators +agree it's legitimate, it is, regardless of the node's perception. -Since validators will not accept a block which breaks context, it will never be finalized, bypassing the contextual checks. If validators do finalize a block which seemingly breaks context, the majority of validators are saying it doesn't, signifying a locally invalid context state (perhaps simply one which is behind). By disabling contextual checks accordingly, nodes can still keep up to date with the chain and validate/participate in other contextual areas (assuming only one local contextual area is invalid). +### Consensus -By moving context based checks into consensus itself, we allow transforming the `Oraclization` and `Report` messages into a leader protocol. Instead of every validator publishing their own message and waiting for the chain's implementation to note 66% of the weight agrees on the duplicated messages, the validators agreeing on the block, which already happens under BFT consensus, ensures message accuracy. +Serai uses Tendermint to obtain consensus on its blockchain. Tendermint details +both block production and finalization, finalizing each block as it's produced. -Aura may be further optimizable by moving to either BLS or FROST signatures. BLS is easy to work with yet has a significance performance overhead. Considering we already have FROST, it may be ideal to use, yet it is a 2-round protocol which exponentially scales for key generation. While GRANDPA, an alternative consensus protocol, is 2-round and therefore could be seamlessly extended with FROST, it's not used here as it finalizes multiple blocks at a time. Given the contextual validity checks, it's simplest to finalize each block on their own to prevent malicious/improper chains from growing too large. +Validators operate contextually. They are expected to know how to create +inherent transactions and actually do so, additionally verifying inherent +transactions proposed by other nodes. Verification comes from ensuring perfect +consistency with what the validator would've proposed themselves. -If the complexity challenge can be overcame, BABE's VRF selecting a block producer should be used to limit DoS attacks. The main issue is that BABE is traditionally partnered with GRANDPA and represents a more complex system than Aura. Further research is needed here. +While Substrate prefers block production and finalization to be distinct, such +a model would allow unchecked inherent transactions to proliferate on Serai. +Since inherent transactions detail the flow of external funds in relation to +Serai, any operations on such blocks would be unsafe to a potentially fatal +degree. Accordingly, re-bundling the two to ensure the only data in the system +is that which has been fully checked was decided as the best move forward. From 83caa8b4137a96edc151d4a8fc19a4d2c1abfd42 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 05:03:00 -0400 Subject: [PATCH 102/186] Add a _ to the validator arg in slash --- substrate/tendermint/client/src/authority/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 1f1768769..c87c83947 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -263,7 +263,7 @@ impl Network for TendermintAuthority { self.active.as_mut().unwrap().gossip_queue.write().unwrap().push(msg); } - async fn slash(&mut self, validator: u16) { + async fn slash(&mut self, _validator: u16) { todo!() } From 083198ecf2544afa850a16427b965b611888ae87 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 19:58:37 -0400 Subject: [PATCH 103/186] Make the dev profile a local testnet profile Restores a dev profile which only has one validator, locally running. --- deploy/docker-compose.yml | 14 ++++---- substrate/node/src/chain_spec.rs | 55 +++++++++++++++++++++++++++++--- substrate/node/src/command.rs | 1 + 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index e69188d9a..d7ce91b92 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -46,7 +46,7 @@ services: profiles: - base environment: - CHAIN: dev + CHAIN: local NAME: base serai-alice: @@ -59,7 +59,7 @@ services: - cluster-lg - cluster-coins-lg environment: - CHAIN: dev + CHAIN: local NAME: alice VALIDATOR: true @@ -73,7 +73,7 @@ services: - cluster-lg - cluster-coins-lg environment: - CHAIN: dev + CHAIN: local NAME: bob VALIDATOR: true @@ -87,7 +87,7 @@ services: - cluster-lg - cluster-coins-lg environment: - CHAIN: dev + CHAIN: local NAME: charlie VALIDATOR: true @@ -99,7 +99,7 @@ services: - cluster-lg - cluster-coins-lg environment: - CHAIN: dev + CHAIN: local NAME: dave serai-eve: @@ -110,7 +110,7 @@ services: - cluster-lg - cluster-coins-lg environment: - CHAIN: dev + CHAIN: local NAME: eve serai-ferdie: @@ -121,7 +121,7 @@ services: - cluster-lg - cluster-coins-lg environment: - CHAIN: dev + CHAIN: local NAME: ferdie # Processor Services diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index aac15e502..992afc028 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -18,7 +18,11 @@ fn account_id_from_name(name: &'static str) -> AccountId { insecure_pair_from_name(name).public() } -fn testnet_genesis(wasm_binary: &[u8], endowed_accounts: Vec) -> GenesisConfig { +fn testnet_genesis( + wasm_binary: &[u8], + validators: &[&'static str], + endowed_accounts: Vec, +) -> GenesisConfig { let session_key = |name| { let key = account_id_from_name(name); (key, key, SessionKeys { tendermint: Public::from(key) }) @@ -30,9 +34,7 @@ fn testnet_genesis(wasm_binary: &[u8], endowed_accounts: Vec) -> Gene balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), }, transaction_payment: Default::default(), - session: SessionConfig { - keys: vec![session_key("Alice"), session_key("Bob"), session_key("Charlie")], - }, + session: SessionConfig { keys: validators.iter().map(|name| session_key(*name)).collect() }, } } @@ -48,6 +50,51 @@ pub fn development_config() -> Result { || { testnet_genesis( wasm_binary, + &["Alice"], + vec![ + account_id_from_name("Alice"), + account_id_from_name("Bob"), + account_id_from_name("Charlie"), + account_id_from_name("Dave"), + account_id_from_name("Eve"), + account_id_from_name("Ferdie"), + account_id_from_name("Alice//stash"), + account_id_from_name("Bob//stash"), + account_id_from_name("Charlie//stash"), + account_id_from_name("Dave//stash"), + account_id_from_name("Eve//stash"), + account_id_from_name("Ferdie//stash"), + ], + ) + }, + // Bootnodes + vec![], + // Telemetry + None, + // Protocol ID + Some("serai"), + // Fork ID + None, + // Properties + None, + // Extensions + None, + )) +} + +pub fn testnet_config() -> Result { + let wasm_binary = WASM_BINARY.ok_or("Testnet wasm not available")?; + + Ok(ChainSpec::from_genesis( + // Name + "Local Test Network", + // ID + "local", + ChainType::Local, + || { + testnet_genesis( + wasm_binary, + &["Alice", "Bob", "Charlie"], vec![ account_id_from_name("Alice"), account_id_from_name("Bob"), diff --git a/substrate/node/src/command.rs b/substrate/node/src/command.rs index 683923ad4..c5c5008d2 100644 --- a/substrate/node/src/command.rs +++ b/substrate/node/src/command.rs @@ -39,6 +39,7 @@ impl SubstrateCli for Cli { fn load_spec(&self, id: &str) -> Result, String> { match id { "dev" => Ok(Box::new(chain_spec::development_config()?)), + "local" => Ok(Box::new(chain_spec::testnet_config()?)), _ => panic!("Unknown network ID"), } } From f3e177109d08ebdc72849612e72a70a0b9e8abcc Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 21:04:26 -0400 Subject: [PATCH 104/186] Reduce Arcs in TendermintMachine, split Signer from SignatureScheme --- substrate/tendermint/machine/src/ext.rs | 86 ++++++++++++++++++++--- substrate/tendermint/machine/src/lib.rs | 57 ++++++++------- substrate/tendermint/machine/tests/ext.rs | 30 +++++--- 3 files changed, 128 insertions(+), 45 deletions(-) diff --git a/substrate/tendermint/machine/src/ext.rs b/substrate/tendermint/machine/src/ext.rs index f30300274..9852f5284 100644 --- a/substrate/tendermint/machine/src/ext.rs +++ b/substrate/tendermint/machine/src/ext.rs @@ -33,8 +33,35 @@ pub struct BlockNumber(pub u64); #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)] pub struct Round(pub u32); -/// A signature scheme used by validators. +/// A signer for a validator. +#[async_trait] +pub trait Signer: Send + Sync { + // Type used to identify validators. + type ValidatorId: ValidatorId; + /// Signature type. + type Signature: Signature; + + /// Returns the validator's current ID. + async fn validator_id(&self) -> Self::ValidatorId; + /// Sign a signature with the current validator's private key. + async fn sign(&self, msg: &[u8]) -> Self::Signature; +} + #[async_trait] +impl Signer for Arc { + type ValidatorId = S::ValidatorId; + type Signature = S::Signature; + + async fn validator_id(&self) -> Self::ValidatorId { + self.as_ref().validator_id().await + } + + async fn sign(&self, msg: &[u8]) -> Self::Signature { + self.as_ref().sign(msg).await + } +} + +/// A signature scheme used by validators. pub trait SignatureScheme: Send + Sync { // Type used to identify validators. type ValidatorId: ValidatorId; @@ -46,8 +73,9 @@ pub trait SignatureScheme: Send + Sync { /// It could even be a threshold signature scheme, though that's currently unexpected. type AggregateSignature: Signature; - /// Sign a signature with the current validator's private key. - async fn sign(&self, msg: &[u8]) -> Self::Signature; + /// Type representing a signer of this scheme. + type Signer: Signer; + /// Verify a signature from the validator in question. #[must_use] fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: &Self::Signature) -> bool; @@ -64,6 +92,31 @@ pub trait SignatureScheme: Send + Sync { ) -> bool; } +impl SignatureScheme for Arc { + type ValidatorId = S::ValidatorId; + type Signature = S::Signature; + type AggregateSignature = S::AggregateSignature; + type Signer = S::Signer; + + fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: &Self::Signature) -> bool { + self.as_ref().verify(validator, msg, sig) + } + + fn aggregate(sigs: &[Self::Signature]) -> Self::AggregateSignature { + S::aggregate(sigs) + } + + #[must_use] + fn verify_aggregate( + &self, + signers: &[Self::ValidatorId], + msg: &[u8], + sig: &Self::AggregateSignature, + ) -> bool { + self.as_ref().verify_aggregate(signers, msg, sig) + } +} + /// A commit for a specific block. The list of validators have weight exceeding the threshold for /// a valid commit. #[derive(Clone, PartialEq, Debug, Encode, Decode)] @@ -97,6 +150,22 @@ pub trait Weights: Send + Sync { fn proposer(&self, number: BlockNumber, round: Round) -> Self::ValidatorId; } +impl Weights for Arc { + type ValidatorId = W::ValidatorId; + + fn total_weight(&self) -> u64 { + self.as_ref().total_weight() + } + + fn weight(&self, validator: Self::ValidatorId) -> u64 { + self.as_ref().weight(validator) + } + + fn proposer(&self, number: BlockNumber, round: Round) -> Self::ValidatorId { + self.as_ref().proposer(number, round) + } +} + /// Simplified error enum representing a block's validity. #[derive(Clone, Copy, PartialEq, Eq, Debug, Error, Encode, Decode)] pub enum BlockError { @@ -141,11 +210,12 @@ pub trait Network: Send + Sync { // Block time in seconds const BLOCK_TIME: u32; - /// Return the signature scheme in use. The instance is expected to have the validators' public - /// keys, along with an instance of the private key of the current validator. - fn signature_scheme(&self) -> Arc; - /// Return a reference to the validators' weights. - fn weights(&self) -> Arc; + /// Return a handle on the signer in use, usable for the entire lifetime of the machine. + fn signer(&self) -> ::Signer; + /// Return a handle on the signing scheme in use, usable for the entire lifetime of the machine. + fn signature_scheme(&self) -> Self::SignatureScheme; + /// Return a handle on the validators' weights, usable for the entire lifetime of the machine. + fn weights(&self) -> Self::Weights; /// Verify a commit for a given block. Intended for use when syncing or when not an active /// validator. diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index b0ba1a3dc..2eb0fb28e 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -10,10 +10,7 @@ use parity_scale_codec::{Encode, Decode}; use tokio::{ task::{JoinHandle, yield_now}, - sync::{ - RwLock, - mpsc::{self, error::TryRecvError}, - }, + sync::mpsc::{self, error::TryRecvError}, time::sleep, }; @@ -90,7 +87,7 @@ impl SignedMessage { #[must_use] pub fn verify_signature>( &self, - signer: &Arc, + signer: &Scheme, ) -> bool { signer.verify(self.msg.sender, &self.msg.encode(), &self.sig) } @@ -104,10 +101,12 @@ enum TendermintError { /// A machine executing the Tendermint protocol. pub struct TendermintMachine { - network: Arc>, - signer: Arc, + network: N, + signer: ::Signer, + validators: N::SignatureScheme, weights: Arc, - proposer: N::ValidatorId, + + validator_id: N::ValidatorId, number: BlockNumber, canonical_start_time: u64, @@ -178,13 +177,13 @@ impl TendermintMachine { self.step = step; self.queue.push(( true, - Message { sender: self.proposer, number: self.number, round: self.round, data }, + Message { sender: self.validator_id, number: self.number, round: self.round, data }, )); } // 14-21 fn round_propose(&mut self) -> bool { - if self.weights.proposer(self.number, self.round) == self.proposer { + if self.weights.proposer(self.number, self.round) == self.validator_id { let (round, block) = self .valid .clone() @@ -222,6 +221,8 @@ impl TendermintMachine { let round_end = self.end_time[&end_round]; sleep(round_end.saturating_duration_since(Instant::now())).await; + self.validator_id = self.signer.validator_id().await; + self.number.0 += 1; self.canonical_start_time = self.canonical_end_time(end_round); self.start_time = round_end; @@ -229,7 +230,7 @@ impl TendermintMachine { self.queue = self.queue.drain(..).filter(|msg| msg.1.number == self.number).collect(); - self.log = MessageLog::new(self.network.read().await.weights()); + self.log = MessageLog::new(self.weights.clone()); self.end_time = HashMap::new(); self.locked = None; @@ -241,12 +242,7 @@ impl TendermintMachine { /// Create a new Tendermint machine, for the specified proposer, from the specified block, with /// the specified block as the one to propose next, returning a handle for the machine. #[allow(clippy::new_ret_no_self)] - pub fn new( - network: N, - proposer: N::ValidatorId, - last: (BlockNumber, u64), - proposal: N::Block, - ) -> TendermintHandle { + pub fn new(network: N, last: (BlockNumber, u64), proposal: N::Block) -> TendermintHandle { let (msg_send, mut msg_recv) = mpsc::channel(100); // Backlog to accept. Currently arbitrary TendermintHandle { messages: msg_send, @@ -270,15 +266,18 @@ impl TendermintMachine { instant_now - sys_now.duration_since(last_end).unwrap_or(Duration::ZERO) }; - let signer = network.signature_scheme(); - let weights = network.weights(); - let network = Arc::new(RwLock::new(network)); + let signer = network.signer(); + let validators = network.signature_scheme(); + let weights = Arc::new(network.weights()); + let validator_id = signer.validator_id().await; // 01-10 let mut machine = TendermintMachine { network, signer, + validators, weights: weights.clone(), - proposer, + + validator_id, number: BlockNumber(last.0 .0 + 1), canonical_start_time: last.1, @@ -334,7 +333,7 @@ impl TendermintMachine { loop { match msg_recv.try_recv() { Ok(msg) => { - if !msg.verify_signature(&machine.signer) { + if !msg.verify_signature(&machine.validators) { continue; } machine.queue.push((false, msg.msg)); @@ -372,20 +371,20 @@ impl TendermintMachine { validators, signature: N::SignatureScheme::aggregate(&sigs), }; - debug_assert!(machine.network.read().await.verify_commit(block.id(), &commit)); + debug_assert!(machine.network.verify_commit(block.id(), &commit)); - let proposal = machine.network.write().await.add_block(block, commit).await; + let proposal = machine.network.add_block(block, commit).await; machine.reset(msg.round, proposal).await; } Err(TendermintError::Malicious(validator)) => { - machine.network.write().await.slash(validator).await; + machine.network.slash(validator).await; } Err(TendermintError::Temporal) => (), } if broadcast { let sig = machine.signer.sign(&msg.encode()).await; - machine.network.write().await.broadcast(SignedMessage { msg, sig }).await; + machine.network.broadcast(SignedMessage { msg, sig }).await; } } @@ -405,7 +404,7 @@ impl TendermintMachine { // Verify the end time and signature if this is a precommit if let Data::Precommit(Some((id, sig))) = &msg.data { - if !self.signer.verify( + if !self.validators.verify( msg.sender, &commit_msg(self.canonical_end_time(msg.round), id.as_ref()), sig, @@ -496,7 +495,7 @@ impl TendermintMachine { // 22-33 if self.step == Step::Propose { // Delay error handling (triggering a slash) until after we vote. - let (valid, err) = match self.network.write().await.validate(block).await { + let (valid, err) = match self.network.validate(block).await { Ok(_) => (true, Ok(None)), Err(BlockError::Temporal) => (false, Ok(None)), Err(BlockError::Fatal) => (false, Err(TendermintError::Malicious(proposer))), @@ -538,7 +537,7 @@ impl TendermintMachine { // being set, or only being set historically, means this has yet to be run if self.log.has_consensus(self.round, Data::Prevote(Some(block.id()))) { - match self.network.write().await.validate(block).await { + match self.network.validate(block).await { Ok(_) => (), Err(BlockError::Temporal) => (), Err(BlockError::Fatal) => Err(TendermintError::Malicious(proposer))?, diff --git a/substrate/tendermint/machine/tests/ext.rs b/substrate/tendermint/machine/tests/ext.rs index 5e9e7d52e..0dd9c331b 100644 --- a/substrate/tendermint/machine/tests/ext.rs +++ b/substrate/tendermint/machine/tests/ext.rs @@ -14,12 +14,15 @@ use tendermint_machine::{ext::*, SignedMessage, TendermintMachine, TendermintHan type TestValidatorId = u16; type TestBlockId = [u8; 4]; -struct TestSignatureScheme(u16); +struct TestSigner(u16); #[async_trait] -impl SignatureScheme for TestSignatureScheme { +impl Signer for TestSigner { type ValidatorId = TestValidatorId; type Signature = [u8; 32]; - type AggregateSignature = Vec<[u8; 32]>; + + async fn validator_id(&self) -> TestValidatorId { + self.0 + } async fn sign(&self, msg: &[u8]) -> [u8; 32] { let mut sig = [0; 32]; @@ -27,6 +30,14 @@ impl SignatureScheme for TestSignatureScheme { sig[2 .. (2 + 30.min(msg.len()))].copy_from_slice(&msg[.. 30.min(msg.len())]); sig } +} + +struct TestSignatureScheme; +impl SignatureScheme for TestSignatureScheme { + type ValidatorId = TestValidatorId; + type Signature = [u8; 32]; + type AggregateSignature = Vec<[u8; 32]>; + type Signer = TestSigner; #[must_use] fn verify(&self, validator: u16, msg: &[u8], sig: &[u8; 32]) -> bool { @@ -93,12 +104,16 @@ impl Network for TestNetwork { const BLOCK_TIME: u32 = 1; - fn signature_scheme(&self) -> Arc { - Arc::new(TestSignatureScheme(self.0)) + fn signer(&self) -> TestSigner { + TestSigner(self.0) + } + + fn signature_scheme(&self) -> TestSignatureScheme { + TestSignatureScheme } - fn weights(&self) -> Arc { - Arc::new(TestWeights) + fn weights(&self) -> TestWeights { + TestWeights } async fn broadcast(&mut self, msg: SignedMessage) { @@ -137,7 +152,6 @@ impl TestNetwork { let i = u16::try_from(i).unwrap(); write.push(TendermintMachine::new( TestNetwork(i, arc.clone()), - i, (BlockNumber(1), (SystemTime::now().duration_since(UNIX_EPOCH)).unwrap().as_secs()), TestBlock { id: 1u32.to_le_bytes(), valid: Ok(()) }, )); From 2b503b6f427c3aa1c12698fd92d842b707fc28dd Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 21:37:06 -0400 Subject: [PATCH 105/186] Update sc_tendermint per previous commit --- substrate/node/src/service.rs | 59 ++++--------- .../tendermint/client/src/authority/gossip.rs | 7 +- .../tendermint/client/src/authority/mod.rs | 21 +++-- substrate/tendermint/client/src/tendermint.rs | 4 +- substrate/tendermint/client/src/validators.rs | 88 +++++++++++++------ 5 files changed, 95 insertions(+), 84 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index fcf863ffb..c3d570db9 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -1,11 +1,9 @@ use std::{boxed::Box, sync::Arc, error::Error}; -use sp_keystore::SyncCryptoStore; use sp_runtime::traits::{Block as BlockTrait}; use sp_inherents::CreateInherentDataProviders; use sp_consensus::DisableProofRecording; -use sp_api::{BlockId, ProvideRuntimeApi}; -use sp_tendermint::TendermintApi; +use sp_api::ProvideRuntimeApi; use sc_executor::{NativeVersion, NativeExecutionDispatch, NativeElseWasmExecutor}; use sc_transaction_pool::FullPool; @@ -231,44 +229,23 @@ pub async fn new_full(mut config: Configuration) -> Result { number: Arc>, - signature_scheme: Arc>, + signature_scheme: TendermintValidators, } impl TendermintGossip { - pub(crate) fn new( - number: Arc>, - signature_scheme: Arc>, - ) -> Self { + pub(crate) fn new(number: Arc>, signature_scheme: TendermintValidators) -> Self { TendermintGossip { number, signature_scheme } } diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index c87c83947..7d9101588 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -34,7 +34,8 @@ use tendermint_machine::{ }; use crate::{ - CONSENSUS_ID, PROTOCOL_NAME, TendermintValidator, validators::TendermintValidators, + CONSENSUS_ID, PROTOCOL_NAME, TendermintValidator, + validators::{TendermintSigner, TendermintValidators}, tendermint::TendermintImport, }; @@ -49,6 +50,8 @@ use import_future::ImportFuture; // as it's only Authority which implements tendermint_machine::ext::Network. Network has // verify_commit provided, and even non-authorities have to verify commits struct ActiveAuthority { + signer: TendermintSigner, + // Block whose gossip is being tracked number: Arc>, // Outgoing message queue, placed here as the GossipEngine itself can't be @@ -138,7 +141,7 @@ impl TendermintAuthority { /// as it will not return until the P2P stack shuts down. pub async fn authority( mut self, - validator: (u16, Arc), + keys: Arc, providers: T::CIDP, env: T::Environment, network: T::Network, @@ -165,21 +168,21 @@ impl TendermintAuthority { // Set this struct as active *self.import.providers.write().await = Some(providers); self.active = Some(ActiveAuthority { + signer: TendermintSigner(keys, self.import.validators.clone()), + number: number.clone(), gossip_queue: gossip_queue.clone(), env, announce: network, }); - let (validator, keys) = validator; - self.import.validators.set_keys(keys).await; let proposal = self .get_proposal(&self.import.client.header(BlockId::Hash(best_hash)).unwrap().unwrap()) .await; // We no longer need self, so let TendermintMachine become its owner - TendermintMachine::new(self, validator, last, proposal) + TendermintMachine::new(self, last, proposal) }; // Start receiving messages about the Tendermint process for this block @@ -248,11 +251,15 @@ impl Network for TendermintAuthority { const BLOCK_TIME: u32 = T::BLOCK_TIME_IN_SECONDS; - fn signature_scheme(&self) -> Arc> { + fn signer(&self) -> TendermintSigner { + self.active.as_ref().unwrap().signer.clone() + } + + fn signature_scheme(&self) -> TendermintValidators { self.import.validators.clone() } - fn weights(&self) -> Arc> { + fn weights(&self) -> TendermintValidators { self.import.validators.clone() } diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 901c591e6..042a69028 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -27,7 +27,7 @@ use crate::{ /// Tendermint import handler. pub struct TendermintImport { - pub(crate) validators: Arc>, + pub(crate) validators: TendermintValidators, pub(crate) providers: Arc>>, pub(crate) importing_block: Arc::Hash>>>, @@ -54,7 +54,7 @@ impl Clone for TendermintImport { impl TendermintImport { pub(crate) fn new(client: Arc) -> TendermintImport { TendermintImport { - validators: Arc::new(TendermintValidators::new(client.clone())), + validators: TendermintValidators::new(client.clone()), providers: Arc::new(AsyncRwLock::new(None)), importing_block: Arc::new(RwLock::new(None)), diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index c32995068..409733c72 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -3,8 +3,6 @@ use std::sync::{Arc, RwLock}; use async_trait::async_trait; -use tokio::sync::RwLock as AsyncRwLock; - use sp_core::Decode; use sp_application_crypto::{ RuntimePublic as PublicTrait, @@ -17,7 +15,7 @@ use sp_api::{BlockId, ProvideRuntimeApi}; use sc_client_api::HeaderBackend; -use tendermint_machine::ext::{BlockNumber, Round, Weights, SignatureScheme}; +use tendermint_machine::ext::{BlockNumber, Round, Weights, Signer, SignatureScheme}; use sp_tendermint::TendermintApi; @@ -82,49 +80,81 @@ impl Deref for Refresh { } /// Tendermint validators observer, providing data on the active validators. -pub struct TendermintValidators( - Refresh, - Arc>>>, -); +pub struct TendermintValidators(Refresh); +impl Clone for TendermintValidators { + fn clone(&self) -> Self { + Self(Refresh { _refresh: self.0._refresh.clone(), client: self.0.client.clone() }) + } +} impl TendermintValidators { pub(crate) fn new(client: Arc) -> TendermintValidators { - TendermintValidators( - Refresh { - _refresh: Arc::new(RwLock::new(TendermintValidatorsStruct::from_module::(&client))), - client, - }, - Arc::new(AsyncRwLock::new(None)), - ) + TendermintValidators(Refresh { + _refresh: Arc::new(RwLock::new(TendermintValidatorsStruct::from_module::(&client))), + client, + }) + } +} + +pub struct TendermintSigner( + pub(crate) Arc, + pub(crate) TendermintValidators, +); + +impl Clone for TendermintSigner { + fn clone(&self) -> Self { + Self(self.0.clone(), self.1.clone()) } +} - pub(crate) async fn set_keys(&self, keys: Arc) { - *self.1.write().await = Some(keys); +impl TendermintSigner { + async fn get_public_key(&self) -> Public { + let pubs = self.0.sr25519_public_keys(KEY_TYPE_ID).await; + if pubs.is_empty() { + self.0.sr25519_generate_new(KEY_TYPE_ID, None).await.unwrap() + } else { + pubs[0] + } } } #[async_trait] -impl SignatureScheme for TendermintValidators { +impl Signer for TendermintSigner { type ValidatorId = u16; type Signature = Signature; - type AggregateSignature = Vec; - async fn sign(&self, msg: &[u8]) -> Signature { - let read = self.1.read().await; - let keys = read.as_ref().unwrap(); - let key = { - let pubs = keys.sr25519_public_keys(KEY_TYPE_ID).await; - if pubs.is_empty() { - keys.sr25519_generate_new(KEY_TYPE_ID, None).await.unwrap() - } else { - pubs[0] + async fn validator_id(&self) -> u16 { + let key = self.get_public_key().await; + for (i, k) in (*self.1 .0).read().unwrap().lookup.iter().enumerate() { + if k == &key { + return u16::try_from(i).unwrap(); } - }; + } + // TODO: Enable switching between being a validator and not being one, likely be returning + // Option here. Non-validators should be able to simply not broadcast when they think + // they have messages. + panic!("not a validator"); + } + + async fn sign(&self, msg: &[u8]) -> Signature { Signature::decode( - &mut keys.sign_with(KEY_TYPE_ID, &key.into(), msg).await.unwrap().unwrap().as_ref(), + &mut self + .0 + .sign_with(KEY_TYPE_ID, &self.get_public_key().await.into(), msg) + .await + .unwrap() + .unwrap() + .as_ref(), ) .unwrap() } +} + +impl SignatureScheme for TendermintValidators { + type ValidatorId = u16; + type Signature = Signature; + type AggregateSignature = Vec; + type Signer = TendermintSigner; fn verify(&self, validator: u16, msg: &[u8], sig: &Signature) -> bool { self.0.read().unwrap().lookup[usize::try_from(validator).unwrap()].verify(&msg, sig) From f4d622a34c4f0869c911e5f52192823647100250 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 22:39:39 -0400 Subject: [PATCH 106/186] Restore cache --- substrate/tendermint/client/src/tendermint.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 042a69028..c6b0140d8 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -176,6 +176,16 @@ impl TendermintImport { &self, block: &mut BlockImportParams, ) -> Result<(), Error> { + if block.finalized { + if block.fork_choice != Some(ForkChoiceStrategy::Custom(false)) { + // Since we alw1ays set the fork choice, this means something else marked the block as + // finalized, which shouldn't be possible. Ensuring nothing else is setting blocks as + // finalized helps ensure our security + panic!("block was finalized despite not setting the fork choice"); + } + return Ok(()); + } + // Set the block as a worse choice block.fork_choice = Some(ForkChoiceStrategy::Custom(false)); From de0e6724bf70e816655c5f50858d9aba99bbe433 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 22:39:49 -0400 Subject: [PATCH 107/186] Remove error case which shouldn't be an error --- substrate/tendermint/client/src/block_import.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index 64c989c04..be8926ac9 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -47,6 +47,9 @@ where mut block: BlockImportParams, new_cache: HashMap>, ) -> Result { + if self.client.status(BlockId::Hash(block.hash)).unwrap() == BlockStatus::InChain { + return Ok(ImportResult::AlreadyInChain); + } self.check(&mut block).await?; self.client.import_block(block, new_cache).await.map_err(Into::into) From cf8bdf2126c1fa1bdf7f943239fa602eb11ac04d Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 22:52:20 -0400 Subject: [PATCH 108/186] Stop returning errors on already existing blocks entirely --- .../tendermint/client/src/block_import.rs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index be8926ac9..82278c268 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -3,16 +3,31 @@ use std::{marker::PhantomData, sync::Arc, collections::HashMap}; use async_trait::async_trait; use sp_api::BlockId; -use sp_runtime::traits::Block; +use sp_runtime::traits::{Header, Block}; use sp_blockchain::{BlockStatus, HeaderBackend, Backend as BlockchainBackend}; use sp_consensus::{Error, CacheKeyId, SelectChain}; use sc_consensus::{BlockCheckParams, BlockImportParams, ImportResult, BlockImport, Verifier}; -use sc_client_api::Backend; +use sc_client_api::{Backend, BlockBackend}; use crate::{TendermintValidator, tendermint::TendermintImport}; +impl TendermintImport { + fn check_already_in_chain(&self, hash: ::Hash) -> bool { + let id = BlockId::Hash(hash); + // If it's in chain, with justifications, return it's already on chain + // If it's in chain, without justifications, continue the block import process to import its + // justifications + if (self.client.status(id).unwrap() == BlockStatus::InChain) && + self.client.justifications(&id).unwrap().is_some() + { + return true; + } + false + } +} + #[async_trait] impl BlockImport for TendermintImport where @@ -28,7 +43,7 @@ where &mut self, mut block: BlockCheckParams, ) -> Result { - if self.client.status(BlockId::Hash(block.hash)).unwrap() == BlockStatus::InChain { + if self.check_already_in_chain(block.hash) { return Ok(ImportResult::AlreadyInChain); } self.verify_order(block.parent_hash, block.number)?; @@ -47,9 +62,10 @@ where mut block: BlockImportParams, new_cache: HashMap>, ) -> Result { - if self.client.status(BlockId::Hash(block.hash)).unwrap() == BlockStatus::InChain { + if self.check_already_in_chain(block.header.hash()) { return Ok(ImportResult::AlreadyInChain); } + self.check(&mut block).await?; self.client.import_block(block, new_cache).await.map_err(Into::into) @@ -68,6 +84,10 @@ where &mut self, mut block: BlockImportParams, ) -> Result<(BlockImportParams, Option)>>), String> { + if self.check_already_in_chain(block.header.hash()) { + return Ok((block, None)); + } + self.check(&mut block).await.map_err(|e| format!("{}", e))?; Ok((block, None)) } From 131355b10fc14ff2439fb9c367796549751e3fa0 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 23:31:19 -0400 Subject: [PATCH 109/186] Correct Dave, Eve, and Ferdie to not run as validators --- deploy/serai/scripts/entry-dev.sh | 4 ++-- substrate/tendermint/client/src/block_import.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/deploy/serai/scripts/entry-dev.sh b/deploy/serai/scripts/entry-dev.sh index 911da8f5f..56be45954 100755 --- a/deploy/serai/scripts/entry-dev.sh +++ b/deploy/serai/scripts/entry-dev.sh @@ -1,6 +1,6 @@ #!/bin/bash if [[ -z $VALIDATOR ]]; then - serai-node --chain $CHAIN --$NAME + serai-node --chain $CHAIN --name $NAME else - serai-node --chain $CHAIN --$NAME --validator + serai-node --chain $CHAIN --$NAME fi diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index 82278c268..7b4ddf270 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -19,6 +19,8 @@ impl TendermintImport { // If it's in chain, with justifications, return it's already on chain // If it's in chain, without justifications, continue the block import process to import its // justifications + // This can be triggered if the validators add a block, without justifications, yet the p2p + // process then broadcasts it with its justifications if (self.client.status(id).unwrap() == BlockStatus::InChain) && self.client.justifications(&id).unwrap().is_some() { From 63df908d3b4abb6a4b0a303ad83cebcae5aa34ad Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 23:32:52 -0400 Subject: [PATCH 110/186] Rename dev to devnet --dev still works thanks to the |. Acheieves a personal preference of mine with some historical meaning. --- substrate/node/src/chain_spec.rs | 2 +- substrate/node/src/command.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index 992afc028..71517c0bb 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -45,7 +45,7 @@ pub fn development_config() -> Result { // Name "Development Network", // ID - "dev", + "devnet", ChainType::Development, || { testnet_genesis( diff --git a/substrate/node/src/command.rs b/substrate/node/src/command.rs index c5c5008d2..43c67a3ff 100644 --- a/substrate/node/src/command.rs +++ b/substrate/node/src/command.rs @@ -38,7 +38,7 @@ impl SubstrateCli for Cli { fn load_spec(&self, id: &str) -> Result, String> { match id { - "dev" => Ok(Box::new(chain_spec::development_config()?)), + "dev" | "devnet" => Ok(Box::new(chain_spec::development_config()?)), "local" => Ok(Box::new(chain_spec::testnet_config()?)), _ => panic!("Unknown network ID"), } From bd08cd3c9b401d560f127809986ef97325aa3edf Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 2 Nov 2022 23:46:21 -0400 Subject: [PATCH 111/186] Add message expiry to the Tendermint gossip --- substrate/tendermint/client/src/authority/gossip.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/substrate/tendermint/client/src/authority/gossip.rs b/substrate/tendermint/client/src/authority/gossip.rs index fec5c8be8..b8af1a08f 100644 --- a/substrate/tendermint/client/src/authority/gossip.rs +++ b/substrate/tendermint/client/src/authority/gossip.rs @@ -55,4 +55,11 @@ impl Validator for TendermintGossip { ValidationResult::ProcessAndKeep(Self::topic(msg.number().0)) } + + fn message_expired<'a>( + &'a self, + ) -> Box::Hash, &[u8]) -> bool + 'a> { + let number = self.number.clone(); + Box::new(move |topic, _| topic != Self::topic(*number.read().unwrap())) + } } From 2315b3c79b4a34ffce110545c838d267e101dd66 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 3 Nov 2022 00:20:50 -0400 Subject: [PATCH 112/186] Localize the LibP2P protocol to the blockchain Follows convention by doing so. Theoretically enables running multiple blockchains over a single LibP2P connection. --- Cargo.lock | 2 ++ substrate/node/src/service.rs | 11 ++++------- substrate/tendermint/client/Cargo.toml | 2 ++ substrate/tendermint/client/src/lib.rs | 25 ++++++++++++++++++++++--- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5adcdbf2..35225434a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7327,12 +7327,14 @@ version = "0.1.0" dependencies = [ "async-trait", "futures", + "hex", "log", "sc-block-builder", "sc-client-api", "sc-consensus", "sc-executor", "sc-network", + "sc-network-common", "sc-network-gossip", "sc-service", "sc-transaction-pool", diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index c3d570db9..7d768fabb 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -168,13 +168,10 @@ pub async fn new_full(mut config: Configuration) -> Result>(genesis: Hash, fork: Option<&str>) -> ProtocolName { + let mut name = format!("/{}", hex::encode(genesis.as_ref())); + if let Some(fork) = fork { + name += &format!("/{}", fork); + } + name += PROTOCOL_NAME; + name.into() +} + +pub fn set_config>(genesis: Hash, fork: Option<&str>) -> NonDefaultSetConfig { + // TODO: 1 MiB Block Size + 1 KiB + let mut cfg = NonDefaultSetConfig::new(protocol_name(genesis, fork), (1024 * 1024) + 1024); + cfg.allow_non_reserved(25, 25); + cfg +} /// Trait consolidating all generics required by sc_tendermint for processing. pub trait TendermintClient: Send + Sync + 'static { From 1ff51c1a37267a5665fef2c45f063b5bc820e7ae Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 3 Nov 2022 00:24:04 -0400 Subject: [PATCH 113/186] Add a version to sp-runtime in tendermint-machine --- substrate/tendermint/machine/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/tendermint/machine/Cargo.toml b/substrate/tendermint/machine/Cargo.toml index 7fc41bbce..17de1434b 100644 --- a/substrate/tendermint/machine/Cargo.toml +++ b/substrate/tendermint/machine/Cargo.toml @@ -15,7 +15,7 @@ parity-scale-codec = { version = "3.2", features = ["derive"] } tokio = { version = "1", features = ["macros", "sync", "time", "rt"] } -sp-runtime = { git = "https://github.com/serai-dex/substrate", optional = true } +sp-runtime = { git = "https://github.com/serai-dex/substrate", version = "6.0.0", optional = true } [features] substrate = ["sp-runtime"] From ea646c898402854d21a8c1e29c3d373162f3f645 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 3 Nov 2022 00:58:45 -0400 Subject: [PATCH 114/186] Add missing trait --- substrate/node/src/service.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 7d768fabb..19618220d 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -10,6 +10,8 @@ use sc_transaction_pool::FullPool; use sc_network::NetworkService; use sc_service::{error::Error as ServiceError, Configuration, TaskManager, TFullClient}; +use sc_client_api::BlockBackend; + use sc_telemetry::{Telemetry, TelemetryWorker}; pub(crate) use sc_tendermint::{ From af63c3e5d28838f3bb6dff97e5d467a7d1e5b4f1 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 4 Nov 2022 07:54:18 -0400 Subject: [PATCH 115/186] Bump Substrate dependency Fixes #147. --- Cargo.lock | 208 +++++++++++++++++++++++++++-------------------------- 1 file changed, 105 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35225434a..ce175460b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2444,7 +2444,7 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "fork-tree" version = "3.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "parity-scale-codec", ] @@ -2467,7 +2467,7 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "frame-benchmarking" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "frame-support", "frame-system", @@ -2490,7 +2490,7 @@ dependencies = [ [[package]] name = "frame-benchmarking-cli" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "Inflector", "array-bytes", @@ -2541,7 +2541,7 @@ dependencies = [ [[package]] name = "frame-executive" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "frame-support", "frame-system", @@ -2569,7 +2569,7 @@ dependencies = [ [[package]] name = "frame-support" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "bitflags", "frame-metadata", @@ -2601,7 +2601,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "Inflector", "cfg-expr", @@ -2615,7 +2615,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate", @@ -2627,7 +2627,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" version = "3.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "proc-macro2", "quote", @@ -2637,7 +2637,7 @@ dependencies = [ [[package]] name = "frame-system" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "frame-support", "log", @@ -2655,7 +2655,7 @@ dependencies = [ [[package]] name = "frame-system-rpc-runtime-api" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "parity-scale-codec", "sp-api", @@ -5088,7 +5088,7 @@ dependencies = [ [[package]] name = "pallet-balances" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "frame-benchmarking", "frame-support", @@ -5103,7 +5103,7 @@ dependencies = [ [[package]] name = "pallet-contracts" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "bitflags", "frame-benchmarking", @@ -5131,7 +5131,7 @@ dependencies = [ [[package]] name = "pallet-contracts-primitives" version = "6.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "bitflags", "parity-scale-codec", @@ -5143,7 +5143,7 @@ dependencies = [ [[package]] name = "pallet-contracts-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "proc-macro2", "quote", @@ -5153,7 +5153,7 @@ dependencies = [ [[package]] name = "pallet-randomness-collective-flip" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "frame-support", "frame-system", @@ -5167,7 +5167,7 @@ dependencies = [ [[package]] name = "pallet-session" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "frame-support", "frame-system", @@ -5201,7 +5201,7 @@ dependencies = [ [[package]] name = "pallet-timestamp" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "frame-benchmarking", "frame-support", @@ -5219,7 +5219,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "frame-support", "frame-system", @@ -5235,7 +5235,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", @@ -5250,7 +5250,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "pallet-transaction-payment", "parity-scale-codec", @@ -6483,7 +6483,7 @@ dependencies = [ [[package]] name = "sc-allocator" version = "4.1.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "log", "sp-core", @@ -6494,7 +6494,7 @@ dependencies = [ [[package]] name = "sc-basic-authorship" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "futures", "futures-timer", @@ -6517,7 +6517,7 @@ dependencies = [ [[package]] name = "sc-block-builder" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "parity-scale-codec", "sc-client-api", @@ -6533,7 +6533,7 @@ dependencies = [ [[package]] name = "sc-chain-spec" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "impl-trait-for-tuples", "memmap2", @@ -6550,7 +6550,7 @@ dependencies = [ [[package]] name = "sc-chain-spec-derive" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -6561,7 +6561,7 @@ dependencies = [ [[package]] name = "sc-cli" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "array-bytes", "chrono", @@ -6601,7 +6601,7 @@ dependencies = [ [[package]] name = "sc-client-api" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "fnv", "futures", @@ -6629,7 +6629,7 @@ dependencies = [ [[package]] name = "sc-client-db" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "hash-db", "kvdb", @@ -6654,7 +6654,7 @@ dependencies = [ [[package]] name = "sc-consensus" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "async-trait", "futures", @@ -6678,7 +6678,7 @@ dependencies = [ [[package]] name = "sc-executor" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "lazy_static", "lru 0.7.8", @@ -6705,7 +6705,7 @@ dependencies = [ [[package]] name = "sc-executor-common" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "environmental", "parity-scale-codec", @@ -6721,7 +6721,7 @@ dependencies = [ [[package]] name = "sc-executor-wasmi" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "log", "parity-scale-codec", @@ -6736,7 +6736,7 @@ dependencies = [ [[package]] name = "sc-executor-wasmtime" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "cfg-if", "libc", @@ -6756,7 +6756,7 @@ dependencies = [ [[package]] name = "sc-informant" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "ansi_term", "futures", @@ -6773,7 +6773,7 @@ dependencies = [ [[package]] name = "sc-keystore" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "array-bytes", "async-trait", @@ -6788,7 +6788,7 @@ dependencies = [ [[package]] name = "sc-network" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "array-bytes", "async-trait", @@ -6835,7 +6835,7 @@ dependencies = [ [[package]] name = "sc-network-bitswap" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "cid", "futures", @@ -6855,7 +6855,7 @@ dependencies = [ [[package]] name = "sc-network-common" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "async-trait", "bitflags", @@ -6881,7 +6881,7 @@ dependencies = [ [[package]] name = "sc-network-gossip" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "ahash", "futures", @@ -6899,7 +6899,7 @@ dependencies = [ [[package]] name = "sc-network-light" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "array-bytes", "futures", @@ -6920,7 +6920,7 @@ dependencies = [ [[package]] name = "sc-network-sync" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "array-bytes", "fork-tree", @@ -6950,7 +6950,7 @@ dependencies = [ [[package]] name = "sc-network-transactions" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "array-bytes", "futures", @@ -6969,7 +6969,7 @@ dependencies = [ [[package]] name = "sc-offchain" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "array-bytes", "bytes", @@ -6999,7 +6999,7 @@ dependencies = [ [[package]] name = "sc-peerset" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "futures", "libp2p", @@ -7012,7 +7012,7 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "log", "substrate-prometheus-endpoint", @@ -7021,7 +7021,7 @@ dependencies = [ [[package]] name = "sc-rpc" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "futures", "hash-db", @@ -7051,7 +7051,7 @@ dependencies = [ [[package]] name = "sc-rpc-api" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "futures", "jsonrpsee", @@ -7074,7 +7074,7 @@ dependencies = [ [[package]] name = "sc-rpc-server" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "futures", "jsonrpsee", @@ -7087,7 +7087,7 @@ dependencies = [ [[package]] name = "sc-rpc-spec-v2" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "futures", "hex", @@ -7106,7 +7106,7 @@ dependencies = [ [[package]] name = "sc-service" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "async-trait", "directories", @@ -7177,7 +7177,7 @@ dependencies = [ [[package]] name = "sc-state-db" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "log", "parity-scale-codec", @@ -7191,7 +7191,7 @@ dependencies = [ [[package]] name = "sc-sysinfo" version = "6.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "futures", "libc", @@ -7210,7 +7210,7 @@ dependencies = [ [[package]] name = "sc-telemetry" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "chrono", "futures", @@ -7228,7 +7228,7 @@ dependencies = [ [[package]] name = "sc-tracing" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "ansi_term", "atty", @@ -7259,7 +7259,7 @@ dependencies = [ [[package]] name = "sc-tracing-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -7270,7 +7270,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "async-trait", "futures", @@ -7297,7 +7297,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool-api" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "async-trait", "futures", @@ -7311,7 +7311,7 @@ dependencies = [ [[package]] name = "sc-utils" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "futures", "futures-timer", @@ -7393,8 +7393,10 @@ dependencies = [ name = "schnorr-signatures" version = "0.1.0" dependencies = [ + "blake2", "ciphersuite", "dalek-ff-group", + "digest 0.10.5", "group", "multiexp", "rand_core 0.6.4", @@ -7963,7 +7965,7 @@ dependencies = [ [[package]] name = "sp-api" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "hash-db", "log", @@ -7981,7 +7983,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "blake2", "proc-macro-crate", @@ -7993,7 +7995,7 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "6.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "parity-scale-codec", "scale-info", @@ -8006,7 +8008,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" version = "5.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "integer-sqrt", "num-traits", @@ -8021,7 +8023,7 @@ dependencies = [ [[package]] name = "sp-block-builder" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "parity-scale-codec", "sp-api", @@ -8033,7 +8035,7 @@ dependencies = [ [[package]] name = "sp-blockchain" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "futures", "log", @@ -8051,7 +8053,7 @@ dependencies = [ [[package]] name = "sp-consensus" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "async-trait", "futures", @@ -8070,7 +8072,7 @@ dependencies = [ [[package]] name = "sp-core" version = "6.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "array-bytes", "base58 0.2.0", @@ -8116,7 +8118,7 @@ dependencies = [ [[package]] name = "sp-core-hashing" version = "4.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "blake2", "byteorder", @@ -8130,7 +8132,7 @@ dependencies = [ [[package]] name = "sp-core-hashing-proc-macro" version = "5.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "proc-macro2", "quote", @@ -8141,7 +8143,7 @@ dependencies = [ [[package]] name = "sp-database" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "kvdb", "parking_lot 0.12.1", @@ -8150,7 +8152,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "4.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "proc-macro2", "quote", @@ -8160,7 +8162,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.12.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "environmental", "parity-scale-codec", @@ -8171,7 +8173,7 @@ dependencies = [ [[package]] name = "sp-finality-grandpa" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "finality-grandpa", "log", @@ -8189,7 +8191,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -8203,7 +8205,7 @@ dependencies = [ [[package]] name = "sp-io" version = "6.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "bytes", "futures", @@ -8229,7 +8231,7 @@ dependencies = [ [[package]] name = "sp-keyring" version = "6.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "lazy_static", "sp-core", @@ -8240,7 +8242,7 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.12.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "async-trait", "futures", @@ -8257,7 +8259,7 @@ dependencies = [ [[package]] name = "sp-maybe-compressed-blob" version = "4.1.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "thiserror", "zstd", @@ -8266,7 +8268,7 @@ dependencies = [ [[package]] name = "sp-offchain" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "sp-api", "sp-core", @@ -8276,7 +8278,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "4.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "backtrace", "lazy_static", @@ -8286,7 +8288,7 @@ dependencies = [ [[package]] name = "sp-rpc" version = "6.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "rustc-hash", "serde", @@ -8296,7 +8298,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "6.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "either", "hash256-std-hasher", @@ -8319,7 +8321,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "6.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -8337,7 +8339,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "5.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "Inflector", "proc-macro-crate", @@ -8349,7 +8351,7 @@ dependencies = [ [[package]] name = "sp-sandbox" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "log", "parity-scale-codec", @@ -8363,7 +8365,7 @@ dependencies = [ [[package]] name = "sp-session" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "parity-scale-codec", "scale-info", @@ -8377,7 +8379,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "parity-scale-codec", "scale-info", @@ -8388,7 +8390,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.12.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "hash-db", "log", @@ -8410,12 +8412,12 @@ dependencies = [ [[package]] name = "sp-std" version = "4.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" [[package]] name = "sp-storage" version = "6.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "impl-serde 0.4.0", "parity-scale-codec", @@ -8428,7 +8430,7 @@ dependencies = [ [[package]] name = "sp-tasks" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "log", "sp-core", @@ -8450,7 +8452,7 @@ dependencies = [ [[package]] name = "sp-timestamp" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "async-trait", "futures-timer", @@ -8466,7 +8468,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "5.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "parity-scale-codec", "sp-std", @@ -8478,7 +8480,7 @@ dependencies = [ [[package]] name = "sp-transaction-pool" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "sp-api", "sp-runtime", @@ -8487,7 +8489,7 @@ dependencies = [ [[package]] name = "sp-transaction-storage-proof" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "async-trait", "log", @@ -8503,7 +8505,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "6.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "ahash", "hash-db", @@ -8526,7 +8528,7 @@ dependencies = [ [[package]] name = "sp-version" version = "5.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "impl-serde 0.4.0", "parity-scale-codec", @@ -8543,7 +8545,7 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "parity-scale-codec", "proc-macro2", @@ -8554,7 +8556,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "6.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "impl-trait-for-tuples", "log", @@ -8567,7 +8569,7 @@ dependencies = [ [[package]] name = "sp-weights" version = "4.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -8721,7 +8723,7 @@ dependencies = [ [[package]] name = "substrate-build-script-utils" version = "3.0.0" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "platforms", ] @@ -8729,7 +8731,7 @@ dependencies = [ [[package]] name = "substrate-frame-rpc-system" version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "frame-system-rpc-runtime-api", "futures", @@ -8750,7 +8752,7 @@ dependencies = [ [[package]] name = "substrate-prometheus-endpoint" version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "futures-util", "hyper", @@ -8763,7 +8765,7 @@ dependencies = [ [[package]] name = "substrate-wasm-builder" version = "5.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#176b4e8cfc110f339d88ebd414602bc3833da3c3" +source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" dependencies = [ "ansi_term", "build-helper", From 1a3b6dc4098afdbd797b6e0e863f9e8199f9b555 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 4 Nov 2022 08:03:29 -0400 Subject: [PATCH 116/186] Implement Schnorr half-aggregation from https://eprint.iacr.org/2021/350.pdf Relevant to https://github.com/serai-dex/serai/issues/99. --- crypto/schnorr/Cargo.toml | 3 + crypto/schnorr/src/aggregate.rs | 168 ++++++++++++++++++++++++++++++++ crypto/schnorr/src/lib.rs | 2 + crypto/schnorr/src/tests.rs | 49 ++++++++-- 4 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 crypto/schnorr/src/aggregate.rs diff --git a/crypto/schnorr/Cargo.toml b/crypto/schnorr/Cargo.toml index 83c862e60..2dfd98f4c 100644 --- a/crypto/schnorr/Cargo.toml +++ b/crypto/schnorr/Cargo.toml @@ -17,11 +17,14 @@ rand_core = "0.6" zeroize = { version = "1.5", features = ["zeroize_derive"] } +digest = "0.10" + group = "0.12" ciphersuite = { path = "../ciphersuite", version = "0.1" } multiexp = { path = "../multiexp", version = "0.2", features = ["batch"] } [dev-dependencies] +blake2 = "0.10" dalek-ff-group = { path = "../dalek-ff-group", version = "^0.1.2" } ciphersuite = { path = "../ciphersuite", version = "0.1", features = ["ristretto"] } diff --git a/crypto/schnorr/src/aggregate.rs b/crypto/schnorr/src/aggregate.rs new file mode 100644 index 000000000..362f14356 --- /dev/null +++ b/crypto/schnorr/src/aggregate.rs @@ -0,0 +1,168 @@ +use std::io::{self, Read, Write}; + +use zeroize::Zeroize; + +use digest::Digest; + +use group::{ + ff::{Field, PrimeField}, + Group, GroupEncoding, + prime::PrimeGroup, +}; + +use multiexp::multiexp_vartime; + +use ciphersuite::Ciphersuite; + +use crate::SchnorrSignature; + +fn digest() -> D { + D::new_with_prefix(b"Schnorr Aggregate") +} + +// A secure challenge will include the nonce and whatever message +// Depending on the environment, a secure challenge *may* not include the public key, even if +// the modern consensus is it should +// Accordingly, transcript both here, even if ideally only the latter would need to be +fn digest_accumulate(digest: &mut D, key: G, challenge: G::Scalar) { + digest.update(key.to_bytes().as_ref()); + digest.update(challenge.to_repr().as_ref()); +} + +// Performs a big-endian modular reduction of the hash value +// This is used by the below aggregator to prevent mutability +// Only an 128-bit scalar is needed to offer 128-bits of security against malleability per +// https://cr.yp.to/badbatch/badbatch-20120919.pdf +// Accordingly, while a 256-bit hash used here with a 256-bit ECC will have bias, it shouldn't be +// an issue +fn scalar_from_digest(digest: D) -> F { + let bytes = digest.finalize(); + debug_assert_eq!(bytes.len() % 8, 0); + + let mut res = F::zero(); + let mut i = 0; + while i < bytes.len() { + if i != 0 { + for _ in 0 .. 8 { + res += res; + } + } + res += F::from(u64::from_be_bytes(bytes[i .. (i + 8)].try_into().unwrap())); + i += 8; + } + res +} + +fn digest_yield(digest: D, i: usize) -> F { + scalar_from_digest(digest.chain_update( + u32::try_from(i).expect("more than 4 billion signatures in aggregate").to_le_bytes(), + )) +} + +/// Aggregate Schnorr signature as defined in https://eprint.iacr.org/2021/350.pdf. +#[allow(non_snake_case)] +#[derive(Clone, PartialEq, Eq, Debug, Zeroize)] +pub struct SchnorrAggregate { + pub Rs: Vec, + pub s: C::F, +} + +impl SchnorrAggregate { + /// Read a SchnorrAggregate from something implementing Read. + pub fn read(reader: &mut R) -> io::Result { + let mut len = [0; 4]; + reader.read_exact(&mut len)?; + + #[allow(non_snake_case)] + let mut Rs = vec![]; + for _ in 0 .. u32::from_le_bytes(len) { + Rs.push(C::read_G(reader)?); + } + + Ok(SchnorrAggregate { Rs, s: C::read_F(reader)? }) + } + + /// Write a SchnorrAggregate to something implementing Read. + pub fn write(&self, writer: &mut W) -> io::Result<()> { + writer.write_all( + &u32::try_from(self.Rs.len()) + .expect("more than 4 billion signatures in aggregate") + .to_le_bytes(), + )?; + #[allow(non_snake_case)] + for R in &self.Rs { + writer.write_all(R.to_bytes().as_ref())?; + } + writer.write_all(self.s.to_repr().as_ref()) + } + + /// Serialize a SchnorrAggregate, returning a Vec. + pub fn serialize(&self) -> Vec { + let mut buf = vec![]; + self.write(&mut buf).unwrap(); + buf + } + + /// Perform signature verification. + #[must_use] + pub fn verify(&self, keys_and_challenges: &[(C::G, C::F)]) -> bool { + if self.Rs.len() != keys_and_challenges.len() { + return false; + } + + let mut digest = digest::(); + for (key, challenge) in keys_and_challenges { + digest_accumulate(&mut digest, *key, *challenge); + } + + let mut pairs = Vec::with_capacity((2 * keys_and_challenges.len()) + 1); + for (i, (key, challenge)) in keys_and_challenges.iter().enumerate() { + let z = digest_yield(digest.clone(), i); + pairs.push((z, self.Rs[i])); + pairs.push((z * challenge, *key)); + } + pairs.push((-self.s, C::generator())); + multiexp_vartime(&pairs).is_identity().into() + } +} + +#[allow(non_snake_case)] +#[derive(Clone, Debug, Zeroize)] +pub struct SchnorrAggregator { + digest: D, + sigs: Vec>, +} + +impl Default for SchnorrAggregator { + fn default() -> Self { + Self { digest: digest(), sigs: vec![] } + } +} + +impl SchnorrAggregator { + /// Create a new aggregator. + pub fn new() -> Self { + Self::default() + } + + /// Aggregate a signature. + pub fn aggregate(&mut self, public_key: C::G, challenge: C::F, sig: SchnorrSignature) { + digest_accumulate(&mut self.digest, public_key, challenge); + self.sigs.push(sig); + } + + /// Complete aggregation, returning None if none were aggregated. + pub fn complete(self) -> Option> { + if self.sigs.is_empty() { + return None; + } + + let mut aggregate = + SchnorrAggregate { Rs: Vec::with_capacity(self.sigs.len()), s: C::F::zero() }; + for i in 0 .. self.sigs.len() { + aggregate.Rs.push(self.sigs[i].R); + aggregate.s += self.sigs[i].s * digest_yield::<_, C::F>(self.digest.clone(), i); + } + Some(aggregate) + } +} diff --git a/crypto/schnorr/src/lib.rs b/crypto/schnorr/src/lib.rs index b9a1dad8d..4a1fc1db3 100644 --- a/crypto/schnorr/src/lib.rs +++ b/crypto/schnorr/src/lib.rs @@ -13,6 +13,8 @@ use multiexp::BatchVerifier; use ciphersuite::Ciphersuite; +pub mod aggregate; + #[cfg(test)] mod tests; diff --git a/crypto/schnorr/src/tests.rs b/crypto/schnorr/src/tests.rs index 7aa1f08ba..280dd6e80 100644 --- a/crypto/schnorr/src/tests.rs +++ b/crypto/schnorr/src/tests.rs @@ -1,13 +1,19 @@ use rand_core::OsRng; +use blake2::{digest::typenum::U32, Blake2b}; +type Blake2b256 = Blake2b; + use group::{ff::Field, Group}; use multiexp::BatchVerifier; use ciphersuite::{Ciphersuite, Ristretto}; -use crate::SchnorrSignature; +use crate::{ + SchnorrSignature, + aggregate::{SchnorrAggregator, SchnorrAggregate}, +}; -pub(crate) fn core_sign() { +pub(crate) fn sign() { let private_key = C::random_nonzero_F(&mut OsRng); let nonce = C::random_nonzero_F(&mut OsRng); let challenge = C::random_nonzero_F(&mut OsRng); // Doesn't bother to craft an HRAm @@ -18,12 +24,12 @@ pub(crate) fn core_sign() { // The above sign function verifies signing works // This verifies invalid signatures don't pass, using zero signatures, which should effectively be // random -pub(crate) fn core_verify() { +pub(crate) fn verify() { assert!(!SchnorrSignature:: { R: C::G::identity(), s: C::F::zero() } .verify(C::generator() * C::random_nonzero_F(&mut OsRng), C::random_nonzero_F(&mut OsRng))); } -pub(crate) fn core_batch_verify() { +pub(crate) fn batch_verify() { // Create 5 signatures let mut keys = vec![]; let mut challenges = vec![]; @@ -64,9 +70,38 @@ pub(crate) fn core_batch_verify() { } } +pub(crate) fn aggregate() { + // Create 5 signatures + let mut keys = vec![]; + let mut challenges = vec![]; + let mut aggregator = SchnorrAggregator::::new(); + for i in 0 .. 5 { + keys.push(C::random_nonzero_F(&mut OsRng)); + challenges.push(C::random_nonzero_F(&mut OsRng)); + aggregator.aggregate( + C::generator() * keys[i], + challenges[i], + SchnorrSignature::::sign(keys[i], C::random_nonzero_F(&mut OsRng), challenges[i]), + ); + } + + let aggregate = aggregator.complete().unwrap(); + let aggregate = + SchnorrAggregate::::read::<&[u8]>(&mut aggregate.serialize().as_ref()).unwrap(); + assert!(aggregate.verify::( + keys + .iter() + .map(|key| C::generator() * key) + .zip(challenges.iter().cloned()) + .collect::>() + .as_ref() + )); +} + #[test] fn test() { - core_sign::(); - core_verify::(); - core_batch_verify::(); + sign::(); + verify::(); + batch_verify::(); + aggregate::(); } From c31a55cce03d9641a31023b7c719366acbe22a15 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 4 Nov 2022 08:08:16 -0400 Subject: [PATCH 117/186] cargo update (tendermint) --- Cargo.lock | 52 ---------------------------------------------------- 1 file changed, 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b43e4dba2..c03c24d45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2654,21 +2654,6 @@ dependencies = [ "sp-weights", ] -[[package]] -name = "frame-system-benchmarking" -version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-runtime", - "sp-std", -] - [[package]] name = "frame-system-rpc-runtime-api" version = "4.0.0-dev" @@ -6692,31 +6677,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "sc-consensus-pow" -version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" -dependencies = [ - "async-trait", - "futures", - "futures-timer", - "log", - "parity-scale-codec", - "parking_lot 0.12.1", - "sc-client-api", - "sc-consensus", - "sp-api", - "sp-block-builder", - "sp-blockchain", - "sp-consensus", - "sp-consensus-pow", - "sp-core", - "sp-inherents", - "sp-runtime", - "substrate-prometheus-endpoint", - "thiserror", -] - [[package]] name = "sc-executor" version = "0.10.0-dev" @@ -8111,18 +8071,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "sp-consensus-pow" -version = "0.10.0-dev" -source = "git+https://github.com/serai-dex/substrate#b62e1a4a520caa986bf7d5fefa24b1f8694b3667" -dependencies = [ - "parity-scale-codec", - "sp-api", - "sp-core", - "sp-runtime", - "sp-std", -] - [[package]] name = "sp-core" version = "6.0.0" From 5dab3352f29608e21a701da6d91dfe8e77e622cf Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 8 Nov 2022 02:14:39 -0500 Subject: [PATCH 118/186] Move from polling loops to a pure IO model for sc_tendermint's gossip --- .../tendermint/client/src/authority/gossip.rs | 2 + .../tendermint/client/src/authority/mod.rs | 143 ++++++++++-------- 2 files changed, 83 insertions(+), 62 deletions(-) diff --git a/substrate/tendermint/client/src/authority/gossip.rs b/substrate/tendermint/client/src/authority/gossip.rs index b8af1a08f..e7317031a 100644 --- a/substrate/tendermint/client/src/authority/gossip.rs +++ b/substrate/tendermint/client/src/authority/gossip.rs @@ -49,6 +49,8 @@ impl Validator for TendermintGossip { return ValidationResult::Discard; } + // Verify the signature here so we don't carry invalid messages in our gossip layer + // This will cause double verification of the signature, yet that's a minimal cost if !msg.verify_signature(&self.signature_scheme) { return ValidationResult::Discard; } diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 7d9101588..314d43692 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -5,9 +5,12 @@ use std::{ use async_trait::async_trait; -use log::warn; +use log::{warn, error}; -use tokio::task::yield_now; +use futures::{ + StreamExt, + channel::mpsc::{self, UnboundedSender}, +}; use sp_core::{Encode, Decode}; use sp_keystore::CryptoStore; @@ -52,13 +55,11 @@ use import_future::ImportFuture; struct ActiveAuthority { signer: TendermintSigner, - // Block whose gossip is being tracked - number: Arc>, + // Notification channel for when we start a new number + new_number: UnboundedSender, // Outgoing message queue, placed here as the GossipEngine itself can't be - gossip_queue: Arc< - RwLock< - Vec as SignatureScheme>::Signature>>, - >, + gossip: UnboundedSender< + SignedMessage as SignatureScheme>::Signature>, >, // Block producer @@ -148,21 +149,26 @@ impl TendermintAuthority { registry: Option<&Registry>, ) { let (best_hash, last) = self.get_last(); - let mut last_number = last.0 .0 + 1; + let new_number = last.0 .0 + 1; // Shared references between us and the Tendermint machine (and its actions via its Network // trait) - let number = Arc::new(RwLock::new(last_number)); - let gossip_queue = Arc::new(RwLock::new(vec![])); + let number = Arc::new(RwLock::new(new_number)); // Create the gossip network let mut gossip = GossipEngine::new( network.clone(), PROTOCOL_NAME, + protocol, Arc::new(TendermintGossip::new(number.clone(), self.import.validators.clone())), registry, ); + // This should only have a single value, yet a bounded channel with a capacity of 1 would cause + // a firm bound. It's not worth having a backlog crash the node since we aren't constrained + let (new_number_tx, mut new_number_rx) = mpsc::unbounded(); + let (gossip_tx, mut gossip_rx) = mpsc::unbounded(); + // Create the Tendermint machine let handle = { // Set this struct as active @@ -170,8 +176,8 @@ impl TendermintAuthority { self.active = Some(ActiveAuthority { signer: TendermintSigner(keys, self.import.validators.clone()), - number: number.clone(), - gossip_queue: gossip_queue.clone(), + new_number: new_number_tx, + gossip: gossip_tx, env, announce: network, @@ -186,55 +192,51 @@ impl TendermintAuthority { }; // Start receiving messages about the Tendermint process for this block - let mut recv = gossip.messages_for(TendermintGossip::::topic(last_number)); + let mut recv = gossip.messages_for(TendermintGossip::::topic(new_number)); + + loop { + futures::select_biased! { + // GossipEngine closed down + _ = gossip => break, + + // Machine reached a new height + new_number = new_number_rx.next() => { + if let Some(new_number) = new_number { + *number.write().unwrap() = new_number; + recv = gossip.messages_for(TendermintGossip::::topic(new_number)); + } else { + break; + } + }, - 'outer: loop { - // Send out any queued messages - let mut queue = gossip_queue.write().unwrap().drain(..).collect::>(); - for msg in queue.drain(..) { - gossip.gossip_message(TendermintGossip::::topic(msg.number().0), msg.encode(), false); - } + // Message to broadcast + msg = gossip_rx.next() => { + if let Some(msg) = msg { + let topic = TendermintGossip::::topic(msg.number().0); + gossip.gossip_message(topic, msg.encode(), false); + } else { + break; + } + }, - // Handle any received messages - // This inner loop enables handling all pending messages before acquiring the out-queue lock - // again - // TODO: Move to a select model. The disadvantage of this is we'll more frequently acquire - // the above lock, despite lack of reason to do so - let _ = futures::poll!(&mut gossip); - 'inner: loop { - match recv.try_next() { - Ok(Some(msg)) => handle - .messages - .send(match SignedMessage::decode(&mut msg.message.as_ref()) { - Ok(msg) => msg, - Err(e) => { - warn!(target: "tendermint", "Couldn't decode valid message: {}", e); - continue; - } - }) - .await - .unwrap(), - - // Ok(None) IS NOT when there aren't messages available. It's when the channel is closed - // If we're no longer receiving messages from the network, it must no longer be running - // We should no longer be accordingly - Ok(None) => break 'outer, - - // No messages available - Err(_) => { - // Check if we the block updated and should be listening on a different topic - let curr = *number.read().unwrap(); - if last_number != curr { - last_number = curr; - // TODO: Will this return existing messages on the new height? Or will those have - // been ignored and are now gone? - recv = gossip.messages_for(TendermintGossip::::topic(last_number)); - } - - // If there are no messages available, yield to not hog the thread, then return to the - // outer loop - yield_now().await; - break 'inner; + // Received a message + msg = recv.next() => { + if let Some(msg) = msg { + handle + .messages + .send(match SignedMessage::decode(&mut msg.message.as_ref()) { + Ok(msg) => msg, + Err(e) => { + // This is guaranteed to be valid thanks to to the gossip validator, assuming + // that pipeline is correct. That's why this doesn't panic + error!(target: "tendermint", "Couldn't decode valid message: {}", e); + continue; + } + }) + .await + .unwrap() + } else { + break; } } } @@ -267,7 +269,13 @@ impl Network for TendermintAuthority { &mut self, msg: SignedMessage as SignatureScheme>::Signature>, ) { - self.active.as_mut().unwrap().gossip_queue.write().unwrap().push(msg); + if self.active.as_mut().unwrap().gossip.unbounded_send(msg).is_err() { + warn!( + target: "tendermint", + "Attempted to broadcast a message except the gossip channel is closed. {}", + "Is the node shutting down?" + ); + } } async fn slash(&mut self, _validator: u16) { @@ -344,7 +352,18 @@ impl Network for TendermintAuthority { .finalize_block(BlockId::Hash(hash), Some(justification), true) .map_err(|_| Error::InvalidJustification) .unwrap(); - *self.active.as_mut().unwrap().number.write().unwrap() += 1; + + let number: u64 = match (*block.header().number()).try_into() { + Ok(number) => number, + Err(_) => panic!("BlockNumber exceeded u64"), + }; + if self.active.as_mut().unwrap().new_number.unbounded_send(number + 1).is_err() { + warn!( + target: "tendermint", + "Attempted to send a new number to the gossip handler except it's closed. {}", + "Is the node shutting down?" + ); + } self.active.as_ref().unwrap().announce.announce_block(hash, None); self.get_proposal(block.header()).await From 16a2c9a2dc761f0c1f87520402a13d090b4811b2 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 8 Nov 2022 02:14:49 -0500 Subject: [PATCH 119/186] Correct protocol name handling --- substrate/node/src/service.rs | 21 +++++++++---------- .../tendermint/client/src/authority/mod.rs | 6 +++--- substrate/tendermint/client/src/lib.rs | 6 +++--- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 19618220d..1de4e6ede 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -21,12 +21,12 @@ pub(crate) use sc_tendermint::{ use serai_runtime::{self, MILLISECS_PER_BLOCK, opaque::Block, RuntimeApi}; type FullBackend = sc_service::TFullBackend; -type FullSelectChain = TendermintSelectChain; +pub type FullClient = TFullClient>; type PartialComponents = sc_service::PartialComponents< FullClient, FullBackend, - FullSelectChain, + TendermintSelectChain, sc_consensus::DefaultImportQueue, sc_transaction_pool::FullPool, Option, @@ -48,8 +48,6 @@ impl NativeExecutionDispatch for ExecutorDispatch { } } -pub type FullClient = TFullClient>; - pub struct Cidp; #[async_trait::async_trait] impl CreateInherentDataProviders for Cidp { @@ -169,11 +167,13 @@ pub async fn new_full(mut config: Configuration) -> Result Result Result TendermintAuthority { /// as it will not return until the P2P stack shuts down. pub async fn authority( mut self, + protocol: ProtocolName, keys: Arc, providers: T::CIDP, env: T::Environment, @@ -158,7 +159,6 @@ impl TendermintAuthority { // Create the gossip network let mut gossip = GossipEngine::new( network.clone(), - PROTOCOL_NAME, protocol, Arc::new(TendermintGossip::new(number.clone(), self.import.validators.clone())), registry, diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 63b563e06..7c3e96c4e 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -35,7 +35,7 @@ pub(crate) const KEY_TYPE_ID: KeyTypeId = KeyTypeId(CONSENSUS_ID); const PROTOCOL_NAME: &str = "/tendermint/1"; -fn protocol_name>(genesis: Hash, fork: Option<&str>) -> ProtocolName { +pub fn protocol_name>(genesis: Hash, fork: Option<&str>) -> ProtocolName { let mut name = format!("/{}", hex::encode(genesis.as_ref())); if let Some(fork) = fork { name += &format!("/{}", fork); @@ -44,9 +44,9 @@ fn protocol_name>(genesis: Hash, fork: Option<&str>) -> Protoc name.into() } -pub fn set_config>(genesis: Hash, fork: Option<&str>) -> NonDefaultSetConfig { +pub fn set_config(protocol: ProtocolName) -> NonDefaultSetConfig { // TODO: 1 MiB Block Size + 1 KiB - let mut cfg = NonDefaultSetConfig::new(protocol_name(genesis, fork), (1024 * 1024) + 1024); + let mut cfg = NonDefaultSetConfig::new(protocol, (1024 * 1024) + 1024); cfg.allow_non_reserved(25, 25); cfg } From 56a21ca6a62a15693dfb7aebdc4af53536018588 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 8 Nov 2022 21:14:03 -0500 Subject: [PATCH 120/186] Use futures mpsc instead of tokio --- Cargo.lock | 1 + .../tendermint/client/src/authority/mod.rs | 4 +- substrate/tendermint/machine/Cargo.toml | 1 + substrate/tendermint/machine/src/lib.rs | 216 ++++++++++-------- substrate/tendermint/machine/tests/ext.rs | 18 +- 5 files changed, 132 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c03c24d45..e1bc51e68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8893,6 +8893,7 @@ name = "tendermint-machine" version = "0.1.0" dependencies = [ "async-trait", + "futures", "parity-scale-codec", "sp-runtime", "thiserror", diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index a51f9f714..06ea11c53 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -8,7 +8,7 @@ use async_trait::async_trait; use log::{warn, error}; use futures::{ - StreamExt, + SinkExt, StreamExt, channel::mpsc::{self, UnboundedSender}, }; @@ -170,7 +170,7 @@ impl TendermintAuthority { let (gossip_tx, mut gossip_rx) = mpsc::unbounded(); // Create the Tendermint machine - let handle = { + let mut handle = { // Set this struct as active *self.import.providers.write().await = Some(providers); self.active = Some(ActiveAuthority { diff --git a/substrate/tendermint/machine/Cargo.toml b/substrate/tendermint/machine/Cargo.toml index 17de1434b..32b6add68 100644 --- a/substrate/tendermint/machine/Cargo.toml +++ b/substrate/tendermint/machine/Cargo.toml @@ -13,6 +13,7 @@ thiserror = "1" parity-scale-codec = { version = "3.2", features = ["derive"] } +futures = "0.3" tokio = { version = "1", features = ["macros", "sync", "time", "rt"] } sp-runtime = { git = "https://github.com/serai-dex/substrate", version = "6.0.0", optional = true } diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 2eb0fb28e..364f44a55 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -3,16 +3,14 @@ use core::fmt::Debug; use std::{ sync::Arc, time::{UNIX_EPOCH, SystemTime, Instant, Duration}, - collections::HashMap, + collections::{VecDeque, HashMap}, }; use parity_scale_codec::{Encode, Decode}; -use tokio::{ - task::{JoinHandle, yield_now}, - sync::mpsc::{self, error::TryRecvError}, - time::sleep, -}; +use futures::{task::Poll, StreamExt, channel::mpsc}; + +use tokio::time::sleep; /// Traits and types of the external network being integrated with to provide consensus over. pub mod ext; @@ -113,10 +111,13 @@ pub struct TendermintMachine { start_time: Instant, personal_proposal: N::Block, - queue: Vec<( + queue: VecDeque<( bool, Message::Signature>, )>, + msg_recv: mpsc::UnboundedReceiver< + SignedMessage::Signature>, + >, log: MessageLog, round: Round, @@ -129,15 +130,20 @@ pub struct TendermintMachine { timeouts: HashMap, } -/// A handle to an asynchronous task, along with a channel to inform of it of messages received. +pub type MessageSender = mpsc::UnboundedSender< + SignedMessage< + ::ValidatorId, + ::Block, + <::SignatureScheme as SignatureScheme>::Signature, + >, +>; + +/// A Tendermint machine and its channel to receive messages from the gossip layer over. pub struct TendermintHandle { /// Channel to send messages received from the P2P layer. - pub messages: mpsc::Sender< - SignedMessage::Signature>, - >, - /// Handle for the asynchronous task executing the machine. The task will automatically exit - /// when the channel is dropped. - pub handle: JoinHandle<()>, + pub messages: MessageSender, + /// Tendermint machine to be run on an asynchronous task. + pub machine: TendermintMachine, } impl TendermintMachine { @@ -175,7 +181,7 @@ impl TendermintMachine { let step = data.step(); // 27, 33, 41, 46, 60, 64 self.step = step; - self.queue.push(( + self.queue.push_back(( true, Message { sender: self.validator_id, number: self.number, round: self.round, data }, )); @@ -239,14 +245,19 @@ impl TendermintMachine { self.round(Round(0)); } - /// Create a new Tendermint machine, for the specified proposer, from the specified block, with - /// the specified block as the one to propose next, returning a handle for the machine. + /// Create a new Tendermint machine, from the specified point, with the specified block as the + /// one to propose next. This will return a channel to send messages from the gossip layer and + /// the machine itself. The machine should have `run` called from an asynchronous task. #[allow(clippy::new_ret_no_self)] - pub fn new(network: N, last: (BlockNumber, u64), proposal: N::Block) -> TendermintHandle { - let (msg_send, mut msg_recv) = mpsc::channel(100); // Backlog to accept. Currently arbitrary + pub async fn new( + network: N, + last: (BlockNumber, u64), + proposal: N::Block, + ) -> TendermintHandle { + let (msg_send, msg_recv) = mpsc::unbounded(); TendermintHandle { messages: msg_send, - handle: tokio::spawn(async move { + machine: { let last_end = UNIX_EPOCH + Duration::from_secs(last.1); // If the last block hasn't ended yet, sleep until it has @@ -271,7 +282,7 @@ impl TendermintMachine { let weights = Arc::new(network.weights()); let validator_id = signer.validator_id().await; // 01-10 - let mut machine = TendermintMachine { + TendermintMachine { network, signer, validators, @@ -284,14 +295,15 @@ impl TendermintMachine { // The end time of the last block is the start time for this one // The Commit explicitly contains the end time, so loading the last commit will provide // this. The only exception is for the genesis block, which doesn't have a commit - // Using the genesis time in place will cause this block to be created immediately after - // it, without the standard amount of separation (so their times will be equivalent or - // minimally offset) + // Using the genesis time in place will cause this block to be created immediately + // after it, without the standard amount of separation (so their times will be + // equivalent or minimally offset) // For callers wishing to avoid this, they should pass (0, GENESIS + BLOCK_TIME) start_time: last_time, personal_proposal: proposal, - queue: vec![], + queue: VecDeque::new(), + msg_recv, log: MessageLog::new(weights), round: Round(0), @@ -302,95 +314,101 @@ impl TendermintMachine { valid: None, timeouts: HashMap::new(), - }; - machine.round(Round(0)); - - loop { - // Check if any timeouts have been triggered - let now = Instant::now(); - let (t1, t2, t3) = { - let ready = |step| machine.timeouts.get(&step).unwrap_or(&now) < &now; - (ready(Step::Propose), ready(Step::Prevote), ready(Step::Precommit)) - }; + } + }, + } + } - // Propose timeout - if t1 && (machine.step == Step::Propose) { - machine.broadcast(Data::Prevote(None)); - } + pub async fn run(mut self) { + self.round(Round(0)); - // Prevote timeout - if t2 && (machine.step == Step::Prevote) { - machine.broadcast(Data::Precommit(None)); - } + 'outer: loop { + // Check if any timeouts have been triggered + let now = Instant::now(); + let (t1, t2, t3) = { + let ready = |step| self.timeouts.get(&step).unwrap_or(&now) < &now; + (ready(Step::Propose), ready(Step::Prevote), ready(Step::Precommit)) + }; + + // Propose timeout + if t1 && (self.step == Step::Propose) { + self.broadcast(Data::Prevote(None)); + } - // Precommit timeout - if t3 { - machine.round(Round(machine.round.0.wrapping_add(1))); - } + // Prevote timeout + if t2 && (self.step == Step::Prevote) { + self.broadcast(Data::Precommit(None)); + } + + // Precommit timeout + if t3 { + self.round(Round(self.round.0.wrapping_add(1))); + } - // Drain the channel of messages - let mut broken = false; - loop { - match msg_recv.try_recv() { - Ok(msg) => { - if !msg.verify_signature(&machine.validators) { - continue; - } - machine.queue.push((false, msg.msg)); - } - Err(TryRecvError::Empty) => break, - Err(TryRecvError::Disconnected) => broken = true, + // Drain the channel of messages + loop { + match futures::poll!(self.msg_recv.next()) { + Poll::Ready(Some(msg)) => { + if !msg.verify_signature(&self.validators) { + continue; } + self.queue.push_back((false, msg.msg)); + } + Poll::Ready(None) => { + break 'outer; } - if broken { + Poll::Pending => { break; } + } + } - // Handle the queue - let mut queue = machine.queue.drain(..).collect::>(); - for (broadcast, msg) in queue.drain(..) { - let res = machine.message(msg.clone()).await; - if res.is_err() && broadcast { - panic!("honest node had invalid behavior"); - } + // Handle the queue + if let Some((broadcast, msg)) = self.queue.pop_front() { + let res = self.message(msg.clone()).await; + if res.is_err() && broadcast { + panic!("honest node had invalid behavior"); + } - match res { - Ok(None) => (), - Ok(Some(block)) => { - let mut validators = vec![]; - let mut sigs = vec![]; - for (v, sig) in machine.log.precommitted.iter().filter_map(|(k, (id, sig))| { - Some((*k, sig.clone())).filter(|_| id == &block.id()) - }) { - validators.push(v); - sigs.push(sig); - } - - let commit = Commit { - end_time: machine.canonical_end_time(msg.round), - validators, - signature: N::SignatureScheme::aggregate(&sigs), - }; - debug_assert!(machine.network.verify_commit(block.id(), &commit)); - - let proposal = machine.network.add_block(block, commit).await; - machine.reset(msg.round, proposal).await; - } - Err(TendermintError::Malicious(validator)) => { - machine.network.slash(validator).await; - } - Err(TendermintError::Temporal) => (), + match res { + Ok(None) => (), + Ok(Some(block)) => { + let mut validators = vec![]; + let mut sigs = vec![]; + for (v, sig) in self + .log + .precommitted + .iter() + .filter_map(|(k, (id, sig))| Some((*k, sig.clone())).filter(|_| id == &block.id())) + { + validators.push(v); + sigs.push(sig); } - if broadcast { - let sig = machine.signer.sign(&msg.encode()).await; - machine.network.broadcast(SignedMessage { msg, sig }).await; - } + let commit = Commit { + end_time: self.canonical_end_time(msg.round), + validators, + signature: N::SignatureScheme::aggregate(&sigs), + }; + debug_assert!(self.network.verify_commit(block.id(), &commit)); + + let proposal = self.network.add_block(block, commit).await; + self.reset(msg.round, proposal).await; } + Err(TendermintError::Malicious(validator)) => { + self.network.slash(validator).await; + } + Err(TendermintError::Temporal) => (), + } - yield_now().await; + if broadcast { + let sig = self.signer.sign(&msg.encode()).await; + self.network.broadcast(SignedMessage { msg, sig }).await; } - }), + } + + // futures::pending here does not work + tokio::task::yield_now().await; } } diff --git a/substrate/tendermint/machine/tests/ext.rs b/substrate/tendermint/machine/tests/ext.rs index 0dd9c331b..2f7cd666f 100644 --- a/substrate/tendermint/machine/tests/ext.rs +++ b/substrate/tendermint/machine/tests/ext.rs @@ -7,9 +7,10 @@ use async_trait::async_trait; use parity_scale_codec::{Encode, Decode}; +use futures::SinkExt; use tokio::{sync::RwLock, time::sleep}; -use tendermint_machine::{ext::*, SignedMessage, TendermintMachine, TendermintHandle}; +use tendermint_machine::{ext::*, SignedMessage, MessageSender, TendermintMachine, TendermintHandle}; type TestValidatorId = u16; type TestBlockId = [u8; 4]; @@ -93,7 +94,7 @@ impl Block for TestBlock { } } -struct TestNetwork(u16, Arc>>>); +struct TestNetwork(u16, Arc>>>); #[async_trait] impl Network for TestNetwork { @@ -117,8 +118,8 @@ impl Network for TestNetwork { } async fn broadcast(&mut self, msg: SignedMessage) { - for handle in self.1.write().await.iter_mut() { - handle.messages.send(msg.clone()).await.unwrap(); + for messages in self.1.write().await.iter_mut() { + messages.send(msg.clone()).await.unwrap(); } } @@ -144,17 +145,20 @@ impl Network for TestNetwork { } impl TestNetwork { - async fn new(validators: usize) -> Arc>>> { + async fn new(validators: usize) -> Arc>>> { let arc = Arc::new(RwLock::new(vec![])); { let mut write = arc.write().await; for i in 0 .. validators { let i = u16::try_from(i).unwrap(); - write.push(TendermintMachine::new( + let TendermintHandle { messages, machine } = TendermintMachine::new( TestNetwork(i, arc.clone()), (BlockNumber(1), (SystemTime::now().duration_since(UNIX_EPOCH)).unwrap().as_secs()), TestBlock { id: 1u32.to_le_bytes(), valid: Ok(()) }, - )); + ) + .await; + tokio::task::spawn(machine.run()); + write.push(messages); } } arc From 2cb1d35d89210e00856001130d446ef1b6389cca Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 8 Nov 2022 21:14:57 -0500 Subject: [PATCH 121/186] Timeout futures --- substrate/tendermint/machine/src/lib.rs | 26 +++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 364f44a55..a043ed248 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -8,7 +8,7 @@ use std::{ use parity_scale_codec::{Encode, Decode}; -use futures::{task::Poll, StreamExt, channel::mpsc}; +use futures::{task::Poll, future, StreamExt, channel::mpsc}; use tokio::time::sleep; @@ -324,24 +324,34 @@ impl TendermintMachine { 'outer: loop { // Check if any timeouts have been triggered - let now = Instant::now(); - let (t1, t2, t3) = { - let ready = |step| self.timeouts.get(&step).unwrap_or(&now) < &now; - (ready(Step::Propose), ready(Step::Prevote), ready(Step::Precommit)) + let timeout_future = |step| { + let timeout = self.timeouts.get(&step).copied(); + async move { + if let Some(timeout) = timeout { + sleep(timeout.saturating_duration_since(Instant::now())).await + } else { + future::pending::<()>().await + } + } + }; + tokio::pin! { + let propose_timeout = timeout_future(Step::Propose); + let prevote_timeout = timeout_future(Step::Prevote); + let precommit_timeout = timeout_future(Step::Precommit); }; // Propose timeout - if t1 && (self.step == Step::Propose) { + if futures::poll!(&mut propose_timeout).is_ready() && (self.step == Step::Propose) { self.broadcast(Data::Prevote(None)); } // Prevote timeout - if t2 && (self.step == Step::Prevote) { + if futures::poll!(&mut prevote_timeout).is_ready() && (self.step == Step::Prevote) { self.broadcast(Data::Precommit(None)); } // Precommit timeout - if t3 { + if futures::poll!(&mut precommit_timeout).is_ready() { self.round(Round(self.round.0.wrapping_add(1))); } From 1c8192218a3067018ad2c9e3358d234917ba52fb Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 8 Nov 2022 22:37:06 -0500 Subject: [PATCH 122/186] Move from a yielding loop to select in tendermint-machine --- substrate/tendermint/machine/src/lib.rs | 110 ++++++++++++++---------- 1 file changed, 63 insertions(+), 47 deletions(-) diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index a043ed248..d3fbd74ae 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -8,8 +8,11 @@ use std::{ use parity_scale_codec::{Encode, Decode}; -use futures::{task::Poll, future, StreamExt, channel::mpsc}; - +use futures::{ + FutureExt, StreamExt, + future::{self, Fuse}, + channel::mpsc, +}; use tokio::time::sleep; /// Traits and types of the external network being integrated with to provide consensus over. @@ -111,10 +114,8 @@ pub struct TendermintMachine { start_time: Instant, personal_proposal: N::Block, - queue: VecDeque<( - bool, - Message::Signature>, - )>, + queue: + VecDeque::Signature>>, msg_recv: mpsc::UnboundedReceiver< SignedMessage::Signature>, >, @@ -181,10 +182,12 @@ impl TendermintMachine { let step = data.step(); // 27, 33, 41, 46, 60, 64 self.step = step; - self.queue.push_back(( - true, - Message { sender: self.validator_id, number: self.number, round: self.round, data }, - )); + self.queue.push_back(Message { + sender: self.validator_id, + number: self.number, + round: self.round, + data, + }); } // 14-21 @@ -234,7 +237,7 @@ impl TendermintMachine { self.start_time = round_end; self.personal_proposal = proposal; - self.queue = self.queue.drain(..).filter(|msg| msg.1.number == self.number).collect(); + self.queue = self.queue.drain(..).filter(|msg| msg.number == self.number).collect(); self.log = MessageLog::new(self.weights.clone()); self.end_time = HashMap::new(); @@ -322,17 +325,18 @@ impl TendermintMachine { pub async fn run(mut self) { self.round(Round(0)); - 'outer: loop { - // Check if any timeouts have been triggered + loop { + // Create futures for the various timeouts let timeout_future = |step| { let timeout = self.timeouts.get(&step).copied(); - async move { + (async move { if let Some(timeout) = timeout { sleep(timeout.saturating_duration_since(Instant::now())).await } else { future::pending::<()>().await } - } + }) + .fuse() }; tokio::pin! { let propose_timeout = timeout_future(Step::Propose); @@ -340,41 +344,56 @@ impl TendermintMachine { let precommit_timeout = timeout_future(Step::Precommit); }; - // Propose timeout - if futures::poll!(&mut propose_timeout).is_ready() && (self.step == Step::Propose) { - self.broadcast(Data::Prevote(None)); - } - - // Prevote timeout - if futures::poll!(&mut prevote_timeout).is_ready() && (self.step == Step::Prevote) { - self.broadcast(Data::Precommit(None)); - } - - // Precommit timeout - if futures::poll!(&mut precommit_timeout).is_ready() { - self.round(Round(self.round.0.wrapping_add(1))); - } - - // Drain the channel of messages - loop { - match futures::poll!(self.msg_recv.next()) { - Poll::Ready(Some(msg)) => { + // Also create a future for if the queue has a message + // Does not pop_front as if another message has higher priority, its future will be handled + // instead in this loop, and the popped value would be dropped with the next iteration + // While no other message has a higher priority right now, this is a safer practice + let mut queue_future = + if self.queue.is_empty() { Fuse::terminated() } else { future::ready(()).fuse() }; + + if let Some((broadcast, msg)) = futures::select_biased! { + // Handle our messages + _ = queue_future => { + Some((true, self.queue.pop_front().unwrap())) + }, + + // Handle any timeouts + _ = &mut propose_timeout => { + // Remove the timeout so it doesn't persist, always being the selected future due to bias + // While this does enable the below get_entry calls to enter timeouts again, they'll + // never attempt to add a timeout after this timeout has expired + self.timeouts.remove(&Step::Propose); + if self.step == Step::Propose { + self.broadcast(Data::Prevote(None)); + } + None + }, + _ = &mut prevote_timeout => { + self.timeouts.remove(&Step::Prevote); + if self.step == Step::Prevote { + self.broadcast(Data::Precommit(None)); + } + None + }, + _ = &mut precommit_timeout => { + // Technically unnecessary since round() will clear the timeouts + self.timeouts.remove(&Step::Precommit); + self.round(Round(self.round.0.wrapping_add(1))); + continue; + }, + + // Handle any received messages + msg = self.msg_recv.next() => { + if let Some(msg) = msg { if !msg.verify_signature(&self.validators) { continue; } - self.queue.push_back((false, msg.msg)); - } - Poll::Ready(None) => { - break 'outer; - } - Poll::Pending => { + Some((false, msg.msg)) + } else { break; } } - } - - // Handle the queue - if let Some((broadcast, msg)) = self.queue.pop_front() { + } { let res = self.message(msg.clone()).await; if res.is_err() && broadcast { panic!("honest node had invalid behavior"); @@ -416,9 +435,6 @@ impl TendermintMachine { self.network.broadcast(SignedMessage { msg, sig }).await; } } - - // futures::pending here does not work - tokio::task::yield_now().await; } } From f7b1ff9f3bc1f9a83484a3582c632a4643801811 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 8 Nov 2022 22:51:31 -0500 Subject: [PATCH 123/186] Update Substrate to the new TendermintHandle --- substrate/node/src/service.rs | 1 + .../tendermint/client/src/authority/mod.rs | 35 ++++++++++--------- substrate/tendermint/machine/src/lib.rs | 6 ++-- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 1de4e6ede..7d35507db 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -233,6 +233,7 @@ pub async fn new_full(mut config: Configuration) -> Result TendermintAuthority { /// Act as a network authority, proposing and voting on blocks. This should be spawned on a task /// as it will not return until the P2P stack shuts down. + #[allow(clippy::too_many_arguments)] pub async fn authority( mut self, protocol: ProtocolName, keys: Arc, providers: T::CIDP, + spawner: impl SpawnEssentialNamed, env: T::Environment, network: T::Network, registry: Option<&Registry>, @@ -170,7 +172,7 @@ impl TendermintAuthority { let (gossip_tx, mut gossip_rx) = mpsc::unbounded(); // Create the Tendermint machine - let mut handle = { + let TendermintHandle { mut messages, machine } = { // Set this struct as active *self.import.providers.write().await = Some(providers); self.active = Some(ActiveAuthority { @@ -188,8 +190,9 @@ impl TendermintAuthority { .await; // We no longer need self, so let TendermintMachine become its owner - TendermintMachine::new(self, last, proposal) + TendermintMachine::new(self, last, proposal).await }; + spawner.spawn_essential("machine", Some("tendermint"), Box::pin(machine.run())); // Start receiving messages about the Tendermint process for this block let mut recv = gossip.messages_for(TendermintGossip::::topic(new_number)); @@ -222,19 +225,17 @@ impl TendermintAuthority { // Received a message msg = recv.next() => { if let Some(msg) = msg { - handle - .messages - .send(match SignedMessage::decode(&mut msg.message.as_ref()) { - Ok(msg) => msg, - Err(e) => { - // This is guaranteed to be valid thanks to to the gossip validator, assuming - // that pipeline is correct. That's why this doesn't panic - error!(target: "tendermint", "Couldn't decode valid message: {}", e); - continue; - } - }) - .await - .unwrap() + messages.send(match SignedMessage::decode(&mut msg.message.as_ref()) { + Ok(msg) => msg, + Err(e) => { + // This is guaranteed to be valid thanks to to the gossip validator, assuming + // that pipeline is correct. That's why this doesn't panic + error!(target: "tendermint", "Couldn't decode valid message: {}", e); + continue; + } + }) + .await + .unwrap() } else { break; } diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index d3fbd74ae..8b20536aa 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -285,7 +285,7 @@ impl TendermintMachine { let weights = Arc::new(network.weights()); let validator_id = signer.validator_id().await; // 01-10 - TendermintMachine { + let mut machine = TendermintMachine { network, signer, validators, @@ -317,7 +317,9 @@ impl TendermintMachine { valid: None, timeouts: HashMap::new(), - } + }; + machine.round(Round(0)); + machine }, } } From 7d46daa36e00523bae965242704dd0d0eb607996 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 8 Nov 2022 22:56:46 -0500 Subject: [PATCH 124/186] Use futures pin instead of tokio --- substrate/tendermint/machine/src/lib.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 8b20536aa..9dd6e4d8c 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -340,11 +340,10 @@ impl TendermintMachine { }) .fuse() }; - tokio::pin! { - let propose_timeout = timeout_future(Step::Propose); - let prevote_timeout = timeout_future(Step::Prevote); - let precommit_timeout = timeout_future(Step::Precommit); - }; + let propose_timeout = timeout_future(Step::Propose); + let prevote_timeout = timeout_future(Step::Prevote); + let precommit_timeout = timeout_future(Step::Precommit); + futures::pin_mut!(propose_timeout, prevote_timeout, precommit_timeout); // Also create a future for if the queue has a message // Does not pop_front as if another message has higher priority, its future will be handled From 6f74bade8b7ce785bcc7f2b45c3140b87bb33102 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 11 Nov 2022 02:17:10 -0500 Subject: [PATCH 125/186] Only recheck blocks with non-fatal inherent transaction errors --- .../tendermint/client/src/authority/mod.rs | 7 ++++-- substrate/tendermint/client/src/tendermint.rs | 23 +++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 4744a8380..964a8d2ef 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -1,6 +1,7 @@ use std::{ sync::{Arc, RwLock}, time::{UNIX_EPOCH, SystemTime, Duration}, + collections::HashSet, }; use async_trait::async_trait; @@ -318,8 +319,7 @@ impl Network for TendermintAuthority { origin: None, allow_missing_state: false, skip_execution: false, - // TODO: Only set to true if block was rejected due to its inherents - import_existing: true, + import_existing: self.import.recheck.read().unwrap().contains(&hash), state: None, }], ); @@ -354,6 +354,9 @@ impl Network for TendermintAuthority { .map_err(|_| Error::InvalidJustification) .unwrap(); + // Clear any blocks for the previous height we were willing to recheck + *self.import.recheck.write().unwrap() = HashSet::new(); + let number: u64 = match (*block.header().number()).try_into() { Ok(number) => number, Err(_) => panic!("BlockNumber exceeded u64"), diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index c6b0140d8..c70ce118e 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -1,4 +1,7 @@ -use std::sync::{Arc, RwLock}; +use std::{ + sync::{Arc, RwLock}, + collections::HashSet, +}; use log::warn; @@ -31,6 +34,7 @@ pub struct TendermintImport { pub(crate) providers: Arc>>, pub(crate) importing_block: Arc::Hash>>>, + pub(crate) recheck: Arc::Hash>>>, pub(crate) client: Arc, pub(crate) queue: @@ -44,6 +48,7 @@ impl Clone for TendermintImport { providers: self.providers.clone(), importing_block: self.importing_block.clone(), + recheck: self.recheck.clone(), client: self.client.clone(), queue: self.queue.clone(), @@ -58,6 +63,7 @@ impl TendermintImport { providers: Arc::new(AsyncRwLock::new(None)), importing_block: Arc::new(RwLock::new(None)), + recheck: Arc::new(RwLock::new(HashSet::new())), client, queue: Arc::new(AsyncRwLock::new(None)), @@ -89,7 +95,11 @@ impl TendermintImport { .unwrap_or_else(InherentData::new) } - async fn check_inherents(&self, block: T::Block) -> Result<(), Error> { + async fn check_inherents( + &mut self, + hash: ::Hash, + block: T::Block, + ) -> Result<(), Error> { let inherent_data = self.inherent_data(*block.header().parent_hash()).await; let err = self .client @@ -98,10 +108,12 @@ impl TendermintImport { .map_err(|_| Error::Other(BlockError::Fatal.into()))?; if err.ok() { + self.recheck.write().unwrap().remove(&hash); Ok(()) } else if err.fatal_error() { Err(Error::Other(BlockError::Fatal.into())) } else { + self.recheck.write().unwrap().insert(hash); Err(Error::Other(BlockError::Temporal.into())) } } @@ -173,7 +185,7 @@ impl TendermintImport { } pub(crate) async fn check( - &self, + &mut self, block: &mut BlockImportParams, ) -> Result<(), Error> { if block.finalized { @@ -194,9 +206,10 @@ impl TendermintImport { // If the block wasn't finalized, verify the origin and validity of its inherents if !block.finalized { - self.verify_origin(block.header.hash())?; + let hash = block.header.hash(); + self.verify_origin(hash)?; if let Some(body) = block.body.clone() { - self.check_inherents(T::Block::new(block.header.clone(), body)).await?; + self.check_inherents(hash, T::Block::new(block.header.clone(), body)).await?; } } From 2411660bd87579c0c88fdae53eeb7d7f8edb782f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 11 Nov 2022 03:49:02 -0500 Subject: [PATCH 126/186] Update to the latest substrate --- substrate/tendermint/client/src/authority/mod.rs | 4 ++-- substrate/tendermint/client/src/block_import.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 964a8d2ef..2d579e9cd 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -102,7 +102,7 @@ impl TendermintAuthority { &mut self .import .client - .justifications(&BlockId::Hash(info.finalized_hash)) + .justifications(info.finalized_hash) .unwrap() .map(|justifications| justifications.get(CONSENSUS_ID).cloned().unwrap()) .unwrap_or_default() @@ -350,7 +350,7 @@ impl Network for TendermintAuthority { self .import .client - .finalize_block(BlockId::Hash(hash), Some(justification), true) + .finalize_block(hash, Some(justification), true) .map_err(|_| Error::InvalidJustification) .unwrap(); diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index 7b4ddf270..dc285e66a 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -22,7 +22,7 @@ impl TendermintImport { // This can be triggered if the validators add a block, without justifications, yet the p2p // process then broadcasts it with its justifications if (self.client.status(id).unwrap() == BlockStatus::InChain) && - self.client.justifications(&id).unwrap().is_some() + self.client.justifications(hash).unwrap().is_some() { return true; } From fffb7a691459dcc23f859e96205a10f74bdfa07f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 11 Nov 2022 05:42:13 -0500 Subject: [PATCH 127/186] Separate the block processing time from the latency --- substrate/node/src/service.rs | 9 +++++-- substrate/runtime/src/lib.rs | 7 +++--- .../tendermint/client/src/authority/mod.rs | 8 +++--- substrate/tendermint/client/src/lib.rs | 9 ++++--- substrate/tendermint/machine/src/ext.rs | 12 +++++++-- substrate/tendermint/machine/src/lib.rs | 25 +++++++++---------- substrate/tendermint/machine/tests/ext.rs | 7 +++--- 7 files changed, 46 insertions(+), 31 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 7d35507db..436469650 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -18,7 +18,7 @@ pub(crate) use sc_tendermint::{ TendermintClientMinimal, TendermintValidator, TendermintImport, TendermintAuthority, TendermintSelectChain, import_queue, }; -use serai_runtime::{self, MILLISECS_PER_BLOCK, opaque::Block, RuntimeApi}; +use serai_runtime::{self, TARGET_BLOCK_TIME, opaque::Block, RuntimeApi}; type FullBackend = sc_service::TFullBackend; pub type FullClient = TFullClient>; @@ -63,7 +63,10 @@ impl CreateInherentDataProviders for Cidp { pub struct TendermintValidatorFirm; impl TendermintClientMinimal for TendermintValidatorFirm { - const BLOCK_TIME_IN_SECONDS: u32 = { (MILLISECS_PER_BLOCK / 1000) as u32 }; + // 3 seconds + const BLOCK_PROCESSING_TIME_IN_SECONDS: u32 = { (TARGET_BLOCK_TIME / 2 / 1000) as u32 }; + // 1 second + const LATENCY_TIME_IN_SECONDS: u32 = { (TARGET_BLOCK_TIME / 2 / 3 / 1000) as u32 }; type Block = Block; type Backend = sc_client_db::Backend; @@ -86,6 +89,8 @@ impl TendermintValidator for TendermintValidatorFirm { pub fn new_partial( config: &Configuration, ) -> Result<(TendermintImport, PartialComponents), ServiceError> { + debug_assert_eq!(TARGET_BLOCK_TIME, 6000); + if config.keystore_remote.is_some() { return Err(ServiceError::Other("Remote Keystores are not supported".to_string())); } diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 7e8b29f1e..64661d8bd 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -79,11 +79,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { state_version: 1, }; -pub const MILLISECS_PER_BLOCK: u64 = 6000; -pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; +pub const TARGET_BLOCK_TIME: u64 = 6000; /// Measured in blocks. -pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); +pub const MINUTES: BlockNumber = 60_000 / (TARGET_BLOCK_TIME as BlockNumber); pub const HOURS: BlockNumber = MINUTES * 60; pub const DAYS: BlockNumber = HOURS * 24; @@ -164,7 +163,7 @@ impl pallet_randomness_collective_flip::Config for Runtime {} impl pallet_timestamp::Config for Runtime { type Moment = u64; type OnTimestampSet = (); - type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; + type MinimumPeriod = ConstU64<{ TARGET_BLOCK_TIME / 2 }>; type WeightInfo = (); } diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 2d579e9cd..957bb6557 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -130,8 +130,9 @@ impl TendermintAuthority { .propose( self.import.inherent_data(parent).await, Digest::default(), - // TODO: Production time, size limit - Duration::from_secs(1), + // Assumes a block cannot take longer to download than it'll take to process + Duration::from_secs((T::BLOCK_PROCESSING_TIME_IN_SECONDS / 2).into()), + // TODO: Size limit None, ) .await @@ -253,7 +254,8 @@ impl Network for TendermintAuthority { type Weights = TendermintValidators; type Block = T::Block; - const BLOCK_TIME: u32 = T::BLOCK_TIME_IN_SECONDS; + const BLOCK_PROCESSING_TIME: u32 = T::BLOCK_PROCESSING_TIME_IN_SECONDS; + const LATENCY_TIME: u32 = T::LATENCY_TIME_IN_SECONDS; fn signer(&self) -> TendermintSigner { self.active.as_ref().unwrap().signer.clone() diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 7c3e96c4e..880b27a44 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -53,7 +53,8 @@ pub fn set_config(protocol: ProtocolName) -> NonDefaultSetConfig { /// Trait consolidating all generics required by sc_tendermint for processing. pub trait TendermintClient: Send + Sync + 'static { - const BLOCK_TIME_IN_SECONDS: u32; + const BLOCK_PROCESSING_TIME_IN_SECONDS: u32; + const LATENCY_TIME_IN_SECONDS: u32; type Block: Block; type Backend: Backend + 'static; @@ -81,7 +82,8 @@ pub trait TendermintClient: Send + Sync + 'static { /// Trait implementable on firm types to automatically provide a full TendermintClient impl. pub trait TendermintClientMinimal: Send + Sync + 'static { - const BLOCK_TIME_IN_SECONDS: u32; + const BLOCK_PROCESSING_TIME_IN_SECONDS: u32; + const LATENCY_TIME_IN_SECONDS: u32; type Block: Block; type Backend: Backend + 'static; @@ -102,7 +104,8 @@ where BlockBuilderApi + TendermintApi, TransactionFor: Send + Sync + 'static, { - const BLOCK_TIME_IN_SECONDS: u32 = T::BLOCK_TIME_IN_SECONDS; + const BLOCK_PROCESSING_TIME_IN_SECONDS: u32 = T::BLOCK_PROCESSING_TIME_IN_SECONDS; + const LATENCY_TIME_IN_SECONDS: u32 = T::LATENCY_TIME_IN_SECONDS; type Block = T::Block; type Backend = T::Backend; diff --git a/substrate/tendermint/machine/src/ext.rs b/substrate/tendermint/machine/src/ext.rs index 9852f5284..2858eea98 100644 --- a/substrate/tendermint/machine/src/ext.rs +++ b/substrate/tendermint/machine/src/ext.rs @@ -207,8 +207,16 @@ pub trait Network: Send + Sync { /// Type used for ordered blocks of information. type Block: Block; - // Block time in seconds - const BLOCK_TIME: u32; + /// Maximum block processing time in seconds. This should include both the actual processing time + /// and the time to download the block. + const BLOCK_PROCESSING_TIME: u32; + /// Network latency time in seconds. + const LATENCY_TIME: u32; + + /// The block time is defined as the processing time plus three times the latency. + fn block_time() -> u32 { + Self::BLOCK_PROCESSING_TIME + (3 * Self::LATENCY_TIME) + } /// Return a handle on the signer in use, usable for the entire lifetime of the machine. fn signer(&self) -> ::Signer; diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 9dd6e4d8c..ef2e580d5 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -155,23 +155,22 @@ impl TendermintMachine { fn canonical_end_time(&self, round: Round) -> u64 { let mut time = self.canonical_start_time; for r in 0 .. u64::from(round.0 + 1) { - time += (r + 1) * u64::from(N::BLOCK_TIME); + time += (r + 1) * u64::from(N::block_time()); } time } fn timeout(&self, step: Step) -> Instant { - let mut round_time = Duration::from_secs(N::BLOCK_TIME.into()); - round_time *= self.round.0 + 1; - // TODO: Non-uniform timeouts. Proposal has to validate the block which will take much longer - // than any other step - let step_time = round_time / 3; - - let offset = match step { - Step::Propose => step_time, - Step::Prevote => step_time * 2, - Step::Precommit => step_time * 3, - }; + let adjusted_block = N::BLOCK_PROCESSING_TIME * (self.round.0 + 1); + let adjusted_latency = N::LATENCY_TIME * (self.round.0 + 1); + let offset = Duration::from_secs( + (match step { + Step::Propose => adjusted_block + adjusted_latency, + Step::Prevote => adjusted_block + (2 * adjusted_latency), + Step::Precommit => adjusted_block + (3 * adjusted_latency), + }) + .into(), + ); self.start_time + offset } @@ -301,7 +300,7 @@ impl TendermintMachine { // Using the genesis time in place will cause this block to be created immediately // after it, without the standard amount of separation (so their times will be // equivalent or minimally offset) - // For callers wishing to avoid this, they should pass (0, GENESIS + BLOCK_TIME) + // For callers wishing to avoid this, they should pass (0, GENESIS + N::block_time()) start_time: last_time, personal_proposal: proposal, diff --git a/substrate/tendermint/machine/tests/ext.rs b/substrate/tendermint/machine/tests/ext.rs index 2f7cd666f..79a04496e 100644 --- a/substrate/tendermint/machine/tests/ext.rs +++ b/substrate/tendermint/machine/tests/ext.rs @@ -103,7 +103,8 @@ impl Network for TestNetwork { type Weights = TestWeights; type Block = TestBlock; - const BLOCK_TIME: u32 = 1; + const BLOCK_PROCESSING_TIME: u32 = 2; + const LATENCY_TIME: u32 = 1; fn signer(&self) -> TestSigner { TestSigner(self.0) @@ -168,7 +169,5 @@ impl TestNetwork { #[tokio::test] async fn test() { TestNetwork::new(4).await; - for _ in 0 .. 10 { - sleep(Duration::from_secs(1)).await; - } + sleep(Duration::from_secs(30)).await; } From 354bcefeb0744e6646c7babdc2b2e5d58d5213c0 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 11 Nov 2022 05:43:36 -0500 Subject: [PATCH 128/186] Add notes to the runtime --- substrate/runtime/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 64661d8bd..1be68ff23 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -70,8 +70,10 @@ use opaque::SessionKeys; #[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("serai"), + // TODO: "core"? impl_name: create_runtime_str!("turoctocrab"), authoring_version: 1, + // TODO: 1? Do we prefer some level of compatibility or our own path? spec_version: 100, impl_version: 1, apis: RUNTIME_API_VERSIONS, From dbcddb2fb036c44852953beb7c0b155c111dd5b5 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 11 Nov 2022 05:53:20 -0500 Subject: [PATCH 129/186] Don't spam slash Also adds a slash condition of failing to propose. --- substrate/tendermint/machine/src/ext.rs | 1 - substrate/tendermint/machine/src/lib.rs | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/substrate/tendermint/machine/src/ext.rs b/substrate/tendermint/machine/src/ext.rs index 2858eea98..1eb4827b1 100644 --- a/substrate/tendermint/machine/src/ext.rs +++ b/substrate/tendermint/machine/src/ext.rs @@ -260,7 +260,6 @@ pub trait Network: Send + Sync { /// Trigger a slash for the validator in question who was definitively malicious. /// The exact process of triggering a slash is undefined and left to the network as a whole. - // TODO: This is spammed right now. async fn slash(&mut self, validator: Self::ValidatorId); /// Validate a block. diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index ef2e580d5..9e457a5e6 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -3,7 +3,7 @@ use core::fmt::Debug; use std::{ sync::Arc, time::{UNIX_EPOCH, SystemTime, Instant, Duration}, - collections::{VecDeque, HashMap}, + collections::{VecDeque, HashSet, HashMap}, }; use parity_scale_codec::{Encode, Decode}; @@ -121,6 +121,7 @@ pub struct TendermintMachine { >, log: MessageLog, + slashes: HashSet, round: Round, end_time: HashMap, step: Step, @@ -239,6 +240,7 @@ impl TendermintMachine { self.queue = self.queue.drain(..).filter(|msg| msg.number == self.number).collect(); self.log = MessageLog::new(self.weights.clone()); + self.slashes = HashSet::new(); self.end_time = HashMap::new(); self.locked = None; @@ -247,6 +249,13 @@ impl TendermintMachine { self.round(Round(0)); } + async fn slash(&mut self, validator: N::ValidatorId) { + if !self.slashes.contains(&validator) { + self.slashes.insert(validator); + self.network.slash(validator).await; + } + } + /// Create a new Tendermint machine, from the specified point, with the specified block as the /// one to propose next. This will return a channel to send messages from the gossip layer and /// the machine itself. The machine should have `run` called from an asynchronous task. @@ -308,6 +317,7 @@ impl TendermintMachine { msg_recv, log: MessageLog::new(weights), + slashes: HashSet::new(), round: Round(0), end_time: HashMap::new(), step: Step::Propose, @@ -364,6 +374,8 @@ impl TendermintMachine { // never attempt to add a timeout after this timeout has expired self.timeouts.remove(&Step::Propose); if self.step == Step::Propose { + // Slash the validator for not proposing when they should've + self.slash(self.weights.proposer(self.number, self.round)).await; self.broadcast(Data::Prevote(None)); } None @@ -425,7 +437,7 @@ impl TendermintMachine { self.reset(msg.round, proposal).await; } Err(TendermintError::Malicious(validator)) => { - self.network.slash(validator).await; + self.slash(validator).await; } Err(TendermintError::Temporal) => (), } From 43b43bdbd9864b75310e1c8f72eabdb224103cfc Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 11 Nov 2022 06:04:37 -0500 Subject: [PATCH 130/186] Support running TendermintMachine when not a validator This supports validators who leave the current set, without crashing their nodes, along with nodes trying to become validators (who will now seamlessly transition in). --- substrate/tendermint/client/src/validators.rs | 9 +++----- substrate/tendermint/machine/src/ext.rs | 6 ++--- substrate/tendermint/machine/src/lib.rs | 23 ++++++++++--------- substrate/tendermint/machine/tests/ext.rs | 4 ++-- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 409733c72..084546383 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -123,17 +123,14 @@ impl Signer for TendermintSigner { type ValidatorId = u16; type Signature = Signature; - async fn validator_id(&self) -> u16 { + async fn validator_id(&self) -> Option { let key = self.get_public_key().await; for (i, k) in (*self.1 .0).read().unwrap().lookup.iter().enumerate() { if k == &key { - return u16::try_from(i).unwrap(); + return Some(u16::try_from(i).unwrap()); } } - // TODO: Enable switching between being a validator and not being one, likely be returning - // Option here. Non-validators should be able to simply not broadcast when they think - // they have messages. - panic!("not a validator"); + None } async fn sign(&self, msg: &[u8]) -> Signature { diff --git a/substrate/tendermint/machine/src/ext.rs b/substrate/tendermint/machine/src/ext.rs index 1eb4827b1..b9ffe5ccd 100644 --- a/substrate/tendermint/machine/src/ext.rs +++ b/substrate/tendermint/machine/src/ext.rs @@ -41,8 +41,8 @@ pub trait Signer: Send + Sync { /// Signature type. type Signature: Signature; - /// Returns the validator's current ID. - async fn validator_id(&self) -> Self::ValidatorId; + /// Returns the validator's current ID. Returns None if they aren't a current validator. + async fn validator_id(&self) -> Option; /// Sign a signature with the current validator's private key. async fn sign(&self, msg: &[u8]) -> Self::Signature; } @@ -52,7 +52,7 @@ impl Signer for Arc { type ValidatorId = S::ValidatorId; type Signature = S::Signature; - async fn validator_id(&self) -> Self::ValidatorId { + async fn validator_id(&self) -> Option { self.as_ref().validator_id().await } diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 9e457a5e6..506321f3f 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -107,7 +107,7 @@ pub struct TendermintMachine { validators: N::SignatureScheme, weights: Arc, - validator_id: N::ValidatorId, + validator_id: Option, number: BlockNumber, canonical_start_time: u64, @@ -179,20 +179,21 @@ impl TendermintMachine { &mut self, data: Data::Signature>, ) { - let step = data.step(); - // 27, 33, 41, 46, 60, 64 - self.step = step; - self.queue.push_back(Message { - sender: self.validator_id, - number: self.number, - round: self.round, - data, - }); + if let Some(validator_id) = &self.validator_id { + // 27, 33, 41, 46, 60, 64 + self.step = data.step(); + self.queue.push_back(Message { + sender: *validator_id, + number: self.number, + round: self.round, + data, + }); + } } // 14-21 fn round_propose(&mut self) -> bool { - if self.weights.proposer(self.number, self.round) == self.validator_id { + if Some(self.weights.proposer(self.number, self.round)) == self.validator_id { let (round, block) = self .valid .clone() diff --git a/substrate/tendermint/machine/tests/ext.rs b/substrate/tendermint/machine/tests/ext.rs index 79a04496e..3e3e52ce1 100644 --- a/substrate/tendermint/machine/tests/ext.rs +++ b/substrate/tendermint/machine/tests/ext.rs @@ -21,8 +21,8 @@ impl Signer for TestSigner { type ValidatorId = TestValidatorId; type Signature = [u8; 32]; - async fn validator_id(&self) -> TestValidatorId { - self.0 + async fn validator_id(&self) -> Option { + Some(self.0) } async fn sign(&self, msg: &[u8]) -> [u8; 32] { From 32ad6de00cf645ee5fb25520555256bdd17de547 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 11 Nov 2022 06:38:06 -0500 Subject: [PATCH 131/186] Properly define and pass around the block size --- substrate/node/src/service.rs | 11 +++++++++-- substrate/runtime/src/lib.rs | 5 ++++- substrate/tendermint/client/src/authority/mod.rs | 3 +-- substrate/tendermint/client/src/lib.rs | 13 ++++++++++--- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 436469650..abb3fcdab 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -18,7 +18,7 @@ pub(crate) use sc_tendermint::{ TendermintClientMinimal, TendermintValidator, TendermintImport, TendermintAuthority, TendermintSelectChain, import_queue, }; -use serai_runtime::{self, TARGET_BLOCK_TIME, opaque::Block, RuntimeApi}; +use serai_runtime::{self, BLOCK_SIZE, TARGET_BLOCK_TIME, opaque::Block, RuntimeApi}; type FullBackend = sc_service::TFullBackend; pub type FullClient = TFullClient>; @@ -63,6 +63,10 @@ impl CreateInherentDataProviders for Cidp { pub struct TendermintValidatorFirm; impl TendermintClientMinimal for TendermintValidatorFirm { + // TODO: This is passed directly to propose, which warns not to use the hard limit as finalize + // may grow the block. We don't use storage proofs and use the Executive finalize_block. Is that + // guaranteed not to grow the block? + const PROPOSED_BLOCK_SIZE_LIMIT: usize = { BLOCK_SIZE as usize }; // 3 seconds const BLOCK_PROCESSING_TIME_IN_SECONDS: u32 = { (TARGET_BLOCK_TIME / 2 / 1000) as u32 }; // 1 second @@ -178,7 +182,10 @@ pub async fn new_full(mut config: Configuration) -> Result TendermintAuthority { Digest::default(), // Assumes a block cannot take longer to download than it'll take to process Duration::from_secs((T::BLOCK_PROCESSING_TIME_IN_SECONDS / 2).into()), - // TODO: Size limit - None, + Some(T::PROPOSED_BLOCK_SIZE_LIMIT), ) .await .expect("Failed to crate a new block proposal") diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 880b27a44..4ebf6e475 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -44,15 +44,20 @@ pub fn protocol_name>(genesis: Hash, fork: Option<&str>) -> Pr name.into() } -pub fn set_config(protocol: ProtocolName) -> NonDefaultSetConfig { - // TODO: 1 MiB Block Size + 1 KiB - let mut cfg = NonDefaultSetConfig::new(protocol, (1024 * 1024) + 1024); +pub fn set_config(protocol: ProtocolName, block_size: u64) -> NonDefaultSetConfig { + // The extra 512 bytes is for the additional data part of Tendermint + // Even with BLS, that should just be 161 bytes in the worst case, for a perfect messaging scheme + // While 256 bytes would suffice there, it's unknown if any LibP2P overhead exists nor if + // anything here will be perfect. Considering this is miniscule compared to the block size, it's + // better safe than sorry. + let mut cfg = NonDefaultSetConfig::new(protocol, block_size + 512); cfg.allow_non_reserved(25, 25); cfg } /// Trait consolidating all generics required by sc_tendermint for processing. pub trait TendermintClient: Send + Sync + 'static { + const PROPOSED_BLOCK_SIZE_LIMIT: usize; const BLOCK_PROCESSING_TIME_IN_SECONDS: u32; const LATENCY_TIME_IN_SECONDS: u32; @@ -82,6 +87,7 @@ pub trait TendermintClient: Send + Sync + 'static { /// Trait implementable on firm types to automatically provide a full TendermintClient impl. pub trait TendermintClientMinimal: Send + Sync + 'static { + const PROPOSED_BLOCK_SIZE_LIMIT: usize; const BLOCK_PROCESSING_TIME_IN_SECONDS: u32; const LATENCY_TIME_IN_SECONDS: u32; @@ -104,6 +110,7 @@ where BlockBuilderApi + TendermintApi, TransactionFor: Send + Sync + 'static, { + const PROPOSED_BLOCK_SIZE_LIMIT: usize = T::PROPOSED_BLOCK_SIZE_LIMIT; const BLOCK_PROCESSING_TIME_IN_SECONDS: u32 = T::BLOCK_PROCESSING_TIME_IN_SECONDS; const LATENCY_TIME_IN_SECONDS: u32 = T::LATENCY_TIME_IN_SECONDS; From ecde185bbfdc742d4412c00ec39625f9c3c1f056 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Nov 2022 05:03:45 -0500 Subject: [PATCH 132/186] Correct the Duration timing The proposer will build it, send it, then process it (on the first round). Accordingly, it's / 3, not / 2, as / 2 only accounted for the latter events. --- substrate/tendermint/client/src/authority/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index a191e7e8b..8490b2f1d 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -130,8 +130,11 @@ impl TendermintAuthority { .propose( self.import.inherent_data(parent).await, Digest::default(), - // Assumes a block cannot take longer to download than it'll take to process - Duration::from_secs((T::BLOCK_PROCESSING_TIME_IN_SECONDS / 2).into()), + // The first processing time is to build the block. + // The second is for it to be downloaded (assumes a block won't take longer to download + // than it'll take to process) + // The third is for it to actually be processed + Duration::from_secs((T::BLOCK_PROCESSING_TIME_IN_SECONDS / 3).into()), Some(T::PROPOSED_BLOCK_SIZE_LIMIT), ) .await From ca3a29f616399f934eaed7296945da961e82a1aa Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Nov 2022 05:12:20 -0500 Subject: [PATCH 133/186] Correct time-adjustment code on round skip --- substrate/tendermint/machine/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 506321f3f..6e45f7e25 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -213,13 +213,14 @@ impl TendermintMachine { let end = self.timeout(Step::Precommit); self.end_time.insert(Round(r), end); self.start_time = end; + self.round.0 += 1; } + debug_assert_eq!(self.round, round); // 11-13 // Clear timeouts self.timeouts = HashMap::new(); - self.round = round; self.end_time.insert(round, self.timeout(Step::Precommit)); self.step = Step::Propose; self.round_propose() From b53759c6ece463317212936075fb2bfbe1db19c6 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Nov 2022 05:35:41 -0500 Subject: [PATCH 134/186] Have the machine respond to advances made by an external sync loop --- substrate/tendermint/machine/src/ext.rs | 2 +- substrate/tendermint/machine/src/lib.rs | 37 +++++++++++++++++++++++ substrate/tendermint/machine/tests/ext.rs | 2 +- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/substrate/tendermint/machine/src/ext.rs b/substrate/tendermint/machine/src/ext.rs index b9ffe5ccd..270a3b12e 100644 --- a/substrate/tendermint/machine/src/ext.rs +++ b/substrate/tendermint/machine/src/ext.rs @@ -121,7 +121,7 @@ impl SignatureScheme for Arc { /// a valid commit. #[derive(Clone, PartialEq, Debug, Encode, Decode)] pub struct Commit { - /// End time of the round, used as the start time of next round. + /// End time of the round which created this commit, used as the start time of the next block. pub end_time: u64, /// Validators participating in the signature. pub validators: Vec, diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 6e45f7e25..6b674ae55 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -119,6 +119,7 @@ pub struct TendermintMachine { msg_recv: mpsc::UnboundedReceiver< SignedMessage::Signature>, >, + step_recv: mpsc::UnboundedReceiver<(Commit, N::Block)>, log: MessageLog, slashes: HashSet, @@ -142,6 +143,9 @@ pub type MessageSender = mpsc::UnboundedSender< /// A Tendermint machine and its channel to receive messages from the gossip layer over. pub struct TendermintHandle { + /// Channel to trigger the machine to move to the next height. + /// Takes in the the previous block's commit, along with the new proposal. + pub step: mpsc::UnboundedSender<(Commit, N::Block)>, /// Channel to send messages received from the P2P layer. pub messages: MessageSender, /// Tendermint machine to be run on an asynchronous task. @@ -251,6 +255,26 @@ impl TendermintMachine { self.round(Round(0)); } + async fn reset_by_commit(&mut self, commit: Commit, proposal: N::Block) { + // Determine the Round number this commit ended on + let mut round = Round(0); + // Use < to prevent an infinite loop + while self.canonical_end_time(round) < commit.end_time { + round.0 += 1; + } + debug_assert_eq!( + self.canonical_end_time(round), + commit.end_time, + "resetting by commit for a different block" + ); + + // Populate the various pieces of round info + if self.round.0 < round.0 { + self.round(round); + } + self.reset(round, proposal).await; + } + async fn slash(&mut self, validator: N::ValidatorId) { if !self.slashes.contains(&validator) { self.slashes.insert(validator); @@ -268,7 +292,9 @@ impl TendermintMachine { proposal: N::Block, ) -> TendermintHandle { let (msg_send, msg_recv) = mpsc::unbounded(); + let (step_send, step_recv) = mpsc::unbounded(); TendermintHandle { + step: step_send, messages: msg_send, machine: { let last_end = UNIX_EPOCH + Duration::from_secs(last.1); @@ -317,6 +343,7 @@ impl TendermintMachine { queue: VecDeque::new(), msg_recv, + step_recv, log: MessageLog::new(weights), slashes: HashSet::new(), @@ -364,6 +391,16 @@ impl TendermintMachine { if self.queue.is_empty() { Fuse::terminated() } else { future::ready(()).fuse() }; if let Some((broadcast, msg)) = futures::select_biased! { + // Handle a new height occuring externally (an external sync loop) + msg = self.step_recv.next() => { + if let Some((commit, proposal)) = msg { + self.reset_by_commit(commit, proposal).await; + None + } else { + break; + } + }, + // Handle our messages _ = queue_future => { Some((true, self.queue.pop_front().unwrap())) diff --git a/substrate/tendermint/machine/tests/ext.rs b/substrate/tendermint/machine/tests/ext.rs index 3e3e52ce1..62552ff79 100644 --- a/substrate/tendermint/machine/tests/ext.rs +++ b/substrate/tendermint/machine/tests/ext.rs @@ -152,7 +152,7 @@ impl TestNetwork { let mut write = arc.write().await; for i in 0 .. validators { let i = u16::try_from(i).unwrap(); - let TendermintHandle { messages, machine } = TendermintMachine::new( + let TendermintHandle { messages, machine, .. } = TendermintMachine::new( TestNetwork(i, arc.clone()), (BlockNumber(1), (SystemTime::now().duration_since(UNIX_EPOCH)).unwrap().as_secs()), TestBlock { id: 1u32.to_le_bytes(), valid: Ok(()) }, From e2e7a70f1eb092f9702f780a3f09c73f960ccfed Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Nov 2022 07:12:05 -0500 Subject: [PATCH 135/186] Clean up time code in tendermint-machine --- substrate/tendermint/machine/src/lib.rs | 187 +++++++++--------- .../tendermint/machine/src/message_log.rs | 2 +- substrate/tendermint/machine/src/time.rs | 43 ++++ substrate/tendermint/machine/tests/ext.rs | 14 +- 4 files changed, 148 insertions(+), 98 deletions(-) create mode 100644 substrate/tendermint/machine/src/time.rs diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 6b674ae55..3e3880518 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -2,7 +2,7 @@ use core::fmt::Debug; use std::{ sync::Arc, - time::{UNIX_EPOCH, SystemTime, Instant, Duration}, + time::{SystemTime, Instant, Duration}, collections::{VecDeque, HashSet, HashMap}, }; @@ -15,13 +15,16 @@ use futures::{ }; use tokio::time::sleep; -/// Traits and types of the external network being integrated with to provide consensus over. -pub mod ext; -use ext::*; +mod time; +use time::{sys_time, CanonicalInstant}; mod message_log; use message_log::MessageLog; +/// Traits and types of the external network being integrated with to provide consensus over. +pub mod ext; +use ext::*; + pub(crate) fn commit_msg(end_time: u64, id: &[u8]) -> Vec { [&end_time.to_le_bytes(), id].concat().to_vec() } @@ -110,8 +113,6 @@ pub struct TendermintMachine { validator_id: Option, number: BlockNumber, - canonical_start_time: u64, - start_time: Instant, personal_proposal: N::Block, queue: @@ -123,8 +124,9 @@ pub struct TendermintMachine { log: MessageLog, slashes: HashSet, + end_time: HashMap, round: Round, - end_time: HashMap, + start_time: CanonicalInstant, step: Step, locked: Option<(Round, ::Id)>, @@ -133,6 +135,9 @@ pub struct TendermintMachine { timeouts: HashMap, } +pub type StepSender = + mpsc::UnboundedSender<(Commit<::SignatureScheme>, ::Block)>; + pub type MessageSender = mpsc::UnboundedSender< SignedMessage< ::ValidatorId, @@ -145,7 +150,7 @@ pub type MessageSender = mpsc::UnboundedSender< pub struct TendermintHandle { /// Channel to trigger the machine to move to the next height. /// Takes in the the previous block's commit, along with the new proposal. - pub step: mpsc::UnboundedSender<(Commit, N::Block)>, + pub step: StepSender, /// Channel to send messages received from the P2P layer. pub messages: MessageSender, /// Tendermint machine to be run on an asynchronous task. @@ -153,19 +158,7 @@ pub struct TendermintHandle { } impl TendermintMachine { - // Get the canonical end time for a given round, represented as seconds since the epoch - // While we have the Instant already in end_time, converting it to a SystemTime would be lossy, - // potentially enough to cause a consensus failure. Independently tracking this variable ensures - // that won't happen - fn canonical_end_time(&self, round: Round) -> u64 { - let mut time = self.canonical_start_time; - for r in 0 .. u64::from(round.0 + 1) { - time += (r + 1) * u64::from(N::block_time()); - } - time - } - - fn timeout(&self, step: Step) -> Instant { + fn timeout(&self, step: Step) -> CanonicalInstant { let adjusted_block = N::BLOCK_PROCESSING_TIME * (self.round.0 + 1); let adjusted_latency = N::LATENCY_TIME * (self.round.0 + 1); let offset = Duration::from_secs( @@ -206,27 +199,31 @@ impl TendermintMachine { self.broadcast(Data::Proposal(round, block)); true } else { - self.timeouts.insert(Step::Propose, self.timeout(Step::Propose)); + self.timeouts.insert(Step::Propose, self.timeout(Step::Propose).instant()); false } } fn round(&mut self, round: Round) -> bool { - // Correct the start time + // If moving to a new round, correct the start time and populate end_time for r in self.round.0 .. round.0 { let end = self.timeout(Step::Precommit); self.end_time.insert(Round(r), end); - self.start_time = end; self.round.0 += 1; + self.start_time = end; } - debug_assert_eq!(self.round, round); + + // Write the round regardless in case of reset // 11-13 + self.round = round; + self.step = Step::Propose; + + // Write the end time + self.end_time.insert(round, self.timeout(Step::Precommit)); // Clear timeouts self.timeouts = HashMap::new(); - self.end_time.insert(round, self.timeout(Step::Precommit)); - self.step = Step::Propose; self.round_propose() } @@ -234,13 +231,11 @@ impl TendermintMachine { async fn reset(&mut self, end_round: Round, proposal: N::Block) { // Wait for the next block interval let round_end = self.end_time[&end_round]; - sleep(round_end.saturating_duration_since(Instant::now())).await; + sleep(round_end.instant().saturating_duration_since(Instant::now())).await; self.validator_id = self.signer.validator_id().await; self.number.0 += 1; - self.canonical_start_time = self.canonical_end_time(end_round); - self.start_time = round_end; self.personal_proposal = proposal; self.queue = self.queue.drain(..).filter(|msg| msg.number == self.number).collect(); @@ -248,6 +243,7 @@ impl TendermintMachine { self.log = MessageLog::new(self.weights.clone()); self.slashes = HashSet::new(); self.end_time = HashMap::new(); + self.start_time = round_end; self.locked = None; self.valid = None; @@ -256,23 +252,27 @@ impl TendermintMachine { } async fn reset_by_commit(&mut self, commit: Commit, proposal: N::Block) { - // Determine the Round number this commit ended on - let mut round = Round(0); - // Use < to prevent an infinite loop - while self.canonical_end_time(round) < commit.end_time { - round.0 += 1; + let mut round = None; + // If our start time is >= the commit's end time, it's from a previous round + if self.start_time.canonical() >= commit.end_time { + for (round_i, end_time) in &self.end_time { + if end_time.canonical() == commit.end_time { + round = Some(*round_i); + break; + } + } + } else { + // Increment rounds until we find the round + while { + self.round(Round(self.round.0 + 1)); + // Use < to prevent an infinite loop + self.end_time[&self.round].canonical() < commit.end_time + } {} + round = + Some(self.round).filter(|_| self.end_time[&self.round].canonical() == commit.end_time); } - debug_assert_eq!( - self.canonical_end_time(round), - commit.end_time, - "resetting by commit for a different block" - ); - // Populate the various pieces of round info - if self.round.0 < round.0 { - self.round(round); - } - self.reset(round, proposal).await; + self.reset(round.expect("commit wasn't for the machine's next block"), proposal).await; } async fn slash(&mut self, validator: N::ValidatorId) { @@ -297,24 +297,9 @@ impl TendermintMachine { step: step_send, messages: msg_send, machine: { - let last_end = UNIX_EPOCH + Duration::from_secs(last.1); - + let last_time = sys_time(last.1); // If the last block hasn't ended yet, sleep until it has - { - let now = SystemTime::now(); - if last_end > now { - sleep(last_end.duration_since(now).unwrap_or(Duration::ZERO)).await; - } - } - - // Convert the last time to an instant - // This is imprecise yet should be precise enough, given this library only has - // second accuracy - let last_time = { - let instant_now = Instant::now(); - let sys_now = SystemTime::now(); - instant_now - sys_now.duration_since(last_end).unwrap_or(Duration::ZERO) - }; + sleep(last_time.duration_since(SystemTime::now()).unwrap_or(Duration::ZERO)).await; let signer = network.signer(); let validators = network.signature_scheme(); @@ -330,15 +315,6 @@ impl TendermintMachine { validator_id, number: BlockNumber(last.0 .0 + 1), - canonical_start_time: last.1, - // The end time of the last block is the start time for this one - // The Commit explicitly contains the end time, so loading the last commit will provide - // this. The only exception is for the genesis block, which doesn't have a commit - // Using the genesis time in place will cause this block to be created immediately - // after it, without the standard amount of separation (so their times will be - // equivalent or minimally offset) - // For callers wishing to avoid this, they should pass (0, GENESIS + N::block_time()) - start_time: last_time, personal_proposal: proposal, queue: VecDeque::new(), @@ -347,8 +323,16 @@ impl TendermintMachine { log: MessageLog::new(weights), slashes: HashSet::new(), - round: Round(0), end_time: HashMap::new(), + round: Round(0), + // The end time of the last block is the start time for this one + // The Commit explicitly contains the end time, so loading the last commit will provide + // this. The only exception is for the genesis block, which doesn't have a commit + // Using the genesis time in place will cause this block to be created immediately + // after it, without the standard amount of separation (so their times will be + // equivalent or minimally offset) + // For callers wishing to avoid this, they should pass (0, GENESIS + N::block_time()) + start_time: CanonicalInstant::new(last.1), step: Step::Propose, locked: None, @@ -371,9 +355,9 @@ impl TendermintMachine { let timeout = self.timeouts.get(&step).copied(); (async move { if let Some(timeout) = timeout { - sleep(timeout.saturating_duration_since(Instant::now())).await + sleep(timeout.saturating_duration_since(Instant::now())).await; } else { - future::pending::<()>().await + future::pending::<()>().await; } }) .fuse() @@ -466,7 +450,7 @@ impl TendermintMachine { } let commit = Commit { - end_time: self.canonical_end_time(msg.round), + end_time: self.end_time[&msg.round].canonical(), validators, signature: N::SignatureScheme::aggregate(&sigs), }; @@ -489,6 +473,26 @@ impl TendermintMachine { } } + fn verify_precommit_signature( + &self, + sender: N::ValidatorId, + round: Round, + data: &Data::Signature>, + ) -> Result<(), TendermintError> { + if let Data::Precommit(Some((id, sig))) = data { + // Also verify the end_time of the commit + // Only perform this verification if we already have the end_time + // Else, there's a DoS where we receive a precommit for some round infinitely in the future + // which forces to calculate every end time + if let Some(end_time) = self.end_time.get(&round) { + if !self.validators.verify(sender, &commit_msg(end_time.canonical(), id.as_ref()), sig) { + Err(TendermintError::Malicious(sender))?; + } + } + } + Ok(()) + } + async fn message( &mut self, msg: Message::Signature>, @@ -497,17 +501,8 @@ impl TendermintMachine { Err(TendermintError::Temporal)?; } - // Verify the end time and signature if this is a precommit - if let Data::Precommit(Some((id, sig))) = &msg.data { - if !self.validators.verify( - msg.sender, - &commit_msg(self.canonical_end_time(msg.round), id.as_ref()), - sig, - ) { - // Since we verified this validator actually sent the message, they're malicious - Err(TendermintError::Malicious(msg.sender))?; - } - } + // If this is a precommit, verify its signature + self.verify_precommit_signature(msg.sender, msg.round, &msg.data)?; // Only let the proposer propose if matches!(msg.data, Data::Proposal(..)) && @@ -548,8 +543,18 @@ impl TendermintMachine { } else if msg.round.0 > self.round.0 { // 55-56 // Jump, enabling processing by the below code - if self.log.round_participation(self.round) > self.weights.fault_thresold() { - // If we're the proposer, return to avoid a double process + if self.log.round_participation(msg.round) > self.weights.fault_thresold() { + // If this round already has precommit messages, verify their signatures + let round_msgs = self.log.log[&msg.round].clone(); + for (validator, msgs) in &round_msgs { + if let Some(data) = msgs.get(&Step::Precommit) { + if self.verify_precommit_signature(*validator, msg.round, data).is_err() { + self.slash(*validator).await; + } + } + } + // If we're the proposer, return now so we re-run processing with our proposal + // If we continue now, it'd just be wasted ops if self.round(msg.round) { return Ok(None); } @@ -567,7 +572,7 @@ impl TendermintMachine { // 34-35 if participation >= self.weights.threshold() { let timeout = self.timeout(Step::Prevote); - self.timeouts.entry(Step::Prevote).or_insert(timeout); + self.timeouts.entry(Step::Prevote).or_insert_with(|| timeout.instant()); } // 44-46 @@ -582,7 +587,7 @@ impl TendermintMachine { self.log.has_participation(self.round, Step::Precommit) { let timeout = self.timeout(Step::Precommit); - self.timeouts.entry(Step::Precommit).or_insert(timeout); + self.timeouts.entry(Step::Precommit).or_insert_with(|| timeout.instant()); } let proposer = self.weights.proposer(self.number, self.round); @@ -645,7 +650,7 @@ impl TendermintMachine { block.id(), self .signer - .sign(&commit_msg(self.canonical_end_time(self.round), block.id().as_ref())) + .sign(&commit_msg(self.end_time[&self.round].canonical(), block.id().as_ref())) .await, )))); return Ok(None); diff --git a/substrate/tendermint/machine/src/message_log.rs b/substrate/tendermint/machine/src/message_log.rs index 2536cad11..793ad7a25 100644 --- a/substrate/tendermint/machine/src/message_log.rs +++ b/substrate/tendermint/machine/src/message_log.rs @@ -8,7 +8,7 @@ pub(crate) struct MessageLog { N::ValidatorId, (::Id, ::Signature), >, - log: HashMap< + pub(crate) log: HashMap< Round, HashMap< N::ValidatorId, diff --git a/substrate/tendermint/machine/src/time.rs b/substrate/tendermint/machine/src/time.rs new file mode 100644 index 000000000..b6bd1c183 --- /dev/null +++ b/substrate/tendermint/machine/src/time.rs @@ -0,0 +1,43 @@ +use core::ops::Add; +use std::time::{UNIX_EPOCH, SystemTime, Instant, Duration}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(crate) struct CanonicalInstant { + /// Time since the epoch. + time: u64, + /// An Instant synchronized with the above time. + instant: Instant, +} + +pub(crate) fn sys_time(time: u64) -> SystemTime { + UNIX_EPOCH + Duration::from_secs(time) +} + +impl CanonicalInstant { + pub(crate) fn new(time: u64) -> CanonicalInstant { + // This is imprecise yet should be precise enough, as it'll resolve within a few ms + let instant_now = Instant::now(); + let sys_now = SystemTime::now(); + + // If the time is in the future, this will be off by that much time + let elapsed = sys_now.duration_since(sys_time(time)).unwrap_or(Duration::ZERO); + let synced_instant = instant_now - elapsed; + + CanonicalInstant { time, instant: synced_instant } + } + + pub(crate) fn canonical(&self) -> u64 { + self.time + } + + pub(crate) fn instant(&self) -> Instant { + self.instant + } +} + +impl Add for CanonicalInstant { + type Output = CanonicalInstant; + fn add(self, duration: Duration) -> CanonicalInstant { + CanonicalInstant { time: self.time + duration.as_secs(), instant: self.instant + duration } + } +} diff --git a/substrate/tendermint/machine/tests/ext.rs b/substrate/tendermint/machine/tests/ext.rs index 62552ff79..d94a2f3be 100644 --- a/substrate/tendermint/machine/tests/ext.rs +++ b/substrate/tendermint/machine/tests/ext.rs @@ -10,7 +10,9 @@ use parity_scale_codec::{Encode, Decode}; use futures::SinkExt; use tokio::{sync::RwLock, time::sleep}; -use tendermint_machine::{ext::*, SignedMessage, MessageSender, TendermintMachine, TendermintHandle}; +use tendermint_machine::{ + ext::*, SignedMessage, StepSender, MessageSender, TendermintMachine, TendermintHandle, +}; type TestValidatorId = u16; type TestBlockId = [u8; 4]; @@ -94,7 +96,7 @@ impl Block for TestBlock { } } -struct TestNetwork(u16, Arc>>>); +struct TestNetwork(u16, Arc, StepSender)>>>); #[async_trait] impl Network for TestNetwork { @@ -119,7 +121,7 @@ impl Network for TestNetwork { } async fn broadcast(&mut self, msg: SignedMessage) { - for messages in self.1.write().await.iter_mut() { + for (messages, _) in self.1.write().await.iter_mut() { messages.send(msg.clone()).await.unwrap(); } } @@ -146,20 +148,20 @@ impl Network for TestNetwork { } impl TestNetwork { - async fn new(validators: usize) -> Arc>>> { + async fn new(validators: usize) -> Arc, StepSender)>>> { let arc = Arc::new(RwLock::new(vec![])); { let mut write = arc.write().await; for i in 0 .. validators { let i = u16::try_from(i).unwrap(); - let TendermintHandle { messages, machine, .. } = TendermintMachine::new( + let TendermintHandle { messages, machine, step } = TendermintMachine::new( TestNetwork(i, arc.clone()), (BlockNumber(1), (SystemTime::now().duration_since(UNIX_EPOCH)).unwrap().as_secs()), TestBlock { id: 1u32.to_le_bytes(), valid: Ok(()) }, ) .await; tokio::task::spawn(machine.run()); - write.push(messages); + write.push((messages, step)); } } arc From 9e72f8737ea0965bdedd6f0efec7afb665423001 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Nov 2022 10:41:09 -0500 Subject: [PATCH 136/186] BlockData and RoundData structs --- substrate/tendermint/machine/src/lib.rs | 370 ++++++++++------------ substrate/tendermint/machine/src/round.rs | 82 +++++ 2 files changed, 257 insertions(+), 195 deletions(-) create mode 100644 substrate/tendermint/machine/src/round.rs diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 3e3880518..46386dc92 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -16,7 +16,10 @@ use futures::{ use tokio::time::sleep; mod time; -use time::{sys_time, CanonicalInstant}; +use time::*; + +mod round; +use round::*; mod message_log; use message_log::MessageLog; @@ -103,6 +106,21 @@ enum TendermintError { Temporal, } +struct BlockData { + number: BlockNumber, + validator_id: Option, + proposal: N::Block, + + log: MessageLog, + slashes: HashSet, + end_time: HashMap, + + round: RoundData, + + locked: Option<(Round, ::Id)>, + valid: Option<(Round, N::Block)>, +} + /// A machine executing the Tendermint protocol. pub struct TendermintMachine { network: N, @@ -110,11 +128,6 @@ pub struct TendermintMachine { validators: N::SignatureScheme, weights: Arc, - validator_id: Option, - - number: BlockNumber, - personal_proposal: N::Block, - queue: VecDeque::Signature>>, msg_recv: mpsc::UnboundedReceiver< @@ -122,17 +135,7 @@ pub struct TendermintMachine { >, step_recv: mpsc::UnboundedReceiver<(Commit, N::Block)>, - log: MessageLog, - slashes: HashSet, - end_time: HashMap, - round: Round, - start_time: CanonicalInstant, - step: Step, - - locked: Option<(Round, ::Id)>, - valid: Option<(Round, N::Block)>, - - timeouts: HashMap, + block: BlockData, } pub type StepSender = @@ -158,126 +161,113 @@ pub struct TendermintHandle { } impl TendermintMachine { - fn timeout(&self, step: Step) -> CanonicalInstant { - let adjusted_block = N::BLOCK_PROCESSING_TIME * (self.round.0 + 1); - let adjusted_latency = N::LATENCY_TIME * (self.round.0 + 1); - let offset = Duration::from_secs( - (match step { - Step::Propose => adjusted_block + adjusted_latency, - Step::Prevote => adjusted_block + (2 * adjusted_latency), - Step::Precommit => adjusted_block + (3 * adjusted_latency), - }) - .into(), - ); - self.start_time + offset - } - fn broadcast( &mut self, data: Data::Signature>, ) { - if let Some(validator_id) = &self.validator_id { + if let Some(validator_id) = &self.block.validator_id { // 27, 33, 41, 46, 60, 64 - self.step = data.step(); + self.block.round.step = data.step(); self.queue.push_back(Message { sender: *validator_id, - number: self.number, - round: self.round, + number: self.block.number, + round: self.block.round.round, data, }); } } - // 14-21 - fn round_propose(&mut self) -> bool { - if Some(self.weights.proposer(self.number, self.round)) == self.validator_id { - let (round, block) = self - .valid - .clone() - .map(|(r, b)| (Some(r), b)) - .unwrap_or((None, self.personal_proposal.clone())); - self.broadcast(Data::Proposal(round, block)); - true - } else { - self.timeouts.insert(Step::Propose, self.timeout(Step::Propose).instant()); - false + fn populate_end_time(&mut self, round: Round) { + for r in (self.block.round.round.0 + 1) .. round.0 { + self.block.end_time.insert( + Round(r), + RoundData::::new(Round(r), self.block.end_time[&Round(r - 1)]).end_time(), + ); } } - fn round(&mut self, round: Round) -> bool { - // If moving to a new round, correct the start time and populate end_time - for r in self.round.0 .. round.0 { - let end = self.timeout(Step::Precommit); - self.end_time.insert(Round(r), end); - self.round.0 += 1; - self.start_time = end; - } - - // Write the round regardless in case of reset + // Start a new round. Returns true if we were the proposer + fn round(&mut self, round: Round, time: Option) -> bool { + // If skipping rounds, populate end_time + self.populate_end_time(round); // 11-13 - self.round = round; - self.step = Step::Propose; - - // Write the end time - self.end_time.insert(round, self.timeout(Step::Precommit)); - // Clear timeouts - self.timeouts = HashMap::new(); + self.block.round = + RoundData::::new(round, time.unwrap_or_else(|| self.block.end_time[&Round(round.0 - 1)])); + self.block.end_time.insert(round, self.block.round.end_time()); - self.round_propose() + // 14-21 + if Some(self.weights.proposer(self.block.number, self.block.round.round)) == + self.block.validator_id + { + let (round, block) = if let Some((round, block)) = &self.block.valid { + (Some(*round), block.clone()) + } else { + (None, self.block.proposal.clone()) + }; + self.broadcast(Data::Proposal(round, block)); + true + } else { + self.block.round.set_timeout(Step::Propose); + false + } } // 53-54 async fn reset(&mut self, end_round: Round, proposal: N::Block) { - // Wait for the next block interval - let round_end = self.end_time[&end_round]; + // Ensure we have the end time data for the last round + self.populate_end_time(end_round); + + // Sleep until this round ends + let round_end = self.block.end_time[&end_round]; sleep(round_end.instant().saturating_duration_since(Instant::now())).await; - self.validator_id = self.signer.validator_id().await; + // Only keep queued messages for this block + self.queue = self.queue.drain(..).filter(|msg| msg.number == self.block.number).collect(); - self.number.0 += 1; - self.personal_proposal = proposal; + // Create the new block + self.block = BlockData { + number: BlockNumber(self.block.number.0 + 1), + validator_id: self.signer.validator_id().await, + proposal, - self.queue = self.queue.drain(..).filter(|msg| msg.number == self.number).collect(); + log: MessageLog::new(self.weights.clone()), + slashes: HashSet::new(), + end_time: HashMap::new(), - self.log = MessageLog::new(self.weights.clone()); - self.slashes = HashSet::new(); - self.end_time = HashMap::new(); - self.start_time = round_end; + // This will be populated in the following round() call + round: RoundData::::new(Round(0), CanonicalInstant::new(0)), - self.locked = None; - self.valid = None; + locked: None, + valid: None, + }; - self.round(Round(0)); + // Start the first round + self.round(Round(0), Some(round_end)); } async fn reset_by_commit(&mut self, commit: Commit, proposal: N::Block) { - let mut round = None; - // If our start time is >= the commit's end time, it's from a previous round - if self.start_time.canonical() >= commit.end_time { - for (round_i, end_time) in &self.end_time { - if end_time.canonical() == commit.end_time { - round = Some(*round_i); - break; - } + let mut round = self.block.round.round; + // If this commit is for a round we don't have, jump up to it + while self.block.end_time[&round].canonical() < commit.end_time { + round.0 += 1; + self.populate_end_time(round); + } + // If this commit is for a prior round, find it + while self.block.end_time[&round].canonical() > commit.end_time { + if round.0 == 0 { + panic!("commit isn't for this machine's next block"); } - } else { - // Increment rounds until we find the round - while { - self.round(Round(self.round.0 + 1)); - // Use < to prevent an infinite loop - self.end_time[&self.round].canonical() < commit.end_time - } {} - round = - Some(self.round).filter(|_| self.end_time[&self.round].canonical() == commit.end_time); + round.0 -= 1; } + debug_assert_eq!(self.block.end_time[&round].canonical(), commit.end_time); - self.reset(round.expect("commit wasn't for the machine's next block"), proposal).await; + self.reset(round, proposal).await; } async fn slash(&mut self, validator: N::ValidatorId) { - if !self.slashes.contains(&validator) { - self.slashes.insert(validator); + if !self.block.slashes.contains(&validator) { + self.block.slashes.insert(validator); self.network.slash(validator).await; } } @@ -312,61 +302,42 @@ impl TendermintMachine { validators, weights: weights.clone(), - validator_id, - - number: BlockNumber(last.0 .0 + 1), - personal_proposal: proposal, - queue: VecDeque::new(), msg_recv, step_recv, - log: MessageLog::new(weights), - slashes: HashSet::new(), - end_time: HashMap::new(), - round: Round(0), - // The end time of the last block is the start time for this one - // The Commit explicitly contains the end time, so loading the last commit will provide - // this. The only exception is for the genesis block, which doesn't have a commit - // Using the genesis time in place will cause this block to be created immediately - // after it, without the standard amount of separation (so their times will be - // equivalent or minimally offset) - // For callers wishing to avoid this, they should pass (0, GENESIS + N::block_time()) - start_time: CanonicalInstant::new(last.1), - step: Step::Propose, - - locked: None, - valid: None, - - timeouts: HashMap::new(), + block: BlockData { + number: BlockNumber(last.0 .0 + 1), + validator_id, + proposal, + + log: MessageLog::new(weights), + slashes: HashSet::new(), + end_time: HashMap::new(), + + // This will be populated in the following round() call + round: RoundData::::new(Round(0), CanonicalInstant::new(0)), + + locked: None, + valid: None, + }, }; - machine.round(Round(0)); + + // The end time of the last block is the start time for this one + // The Commit explicitly contains the end time, so loading the last commit will provide + // this. The only exception is for the genesis block, which doesn't have a commit + // Using the genesis time in place will cause this block to be created immediately + // after it, without the standard amount of separation (so their times will be + // equivalent or minimally offset) + // For callers wishing to avoid this, they should pass (0, GENESIS + N::block_time()) + machine.round(Round(0), Some(CanonicalInstant::new(last.1))); machine }, } } pub async fn run(mut self) { - self.round(Round(0)); - loop { - // Create futures for the various timeouts - let timeout_future = |step| { - let timeout = self.timeouts.get(&step).copied(); - (async move { - if let Some(timeout) = timeout { - sleep(timeout.saturating_duration_since(Instant::now())).await; - } else { - future::pending::<()>().await; - } - }) - .fuse() - }; - let propose_timeout = timeout_future(Step::Propose); - let prevote_timeout = timeout_future(Step::Prevote); - let precommit_timeout = timeout_future(Step::Precommit); - futures::pin_mut!(propose_timeout, prevote_timeout, precommit_timeout); - // Also create a future for if the queue has a message // Does not pop_front as if another message has higher priority, its future will be handled // instead in this loop, and the popped value would be dropped with the next iteration @@ -391,31 +362,28 @@ impl TendermintMachine { }, // Handle any timeouts - _ = &mut propose_timeout => { + step = self.block.round.timeout_future().fuse() => { // Remove the timeout so it doesn't persist, always being the selected future due to bias - // While this does enable the below get_entry calls to enter timeouts again, they'll - // never attempt to add a timeout after this timeout has expired - self.timeouts.remove(&Step::Propose); - if self.step == Step::Propose { - // Slash the validator for not proposing when they should've - self.slash(self.weights.proposer(self.number, self.round)).await; - self.broadcast(Data::Prevote(None)); - } - None - }, - _ = &mut prevote_timeout => { - self.timeouts.remove(&Step::Prevote); - if self.step == Step::Prevote { - self.broadcast(Data::Precommit(None)); + // While this does enable the timeout to be entered again, the timeout setting code will + // never attempt to add a timeout after its timeout has expired + self.block.round.timeouts.remove(&step); + // Only run if it's still the step in question + if self.block.round.step == step { + match step { + Step::Propose => { + // Slash the validator for not proposing when they should've + self.slash(self.weights.proposer(self.block.number, self.block.round.round)).await; + self.broadcast(Data::Prevote(None)); + }, + Step::Prevote => self.broadcast(Data::Precommit(None)), + Step::Precommit => { + self.round(Round(self.block.round.round.0 + 1), None); + continue; + } + } } None }, - _ = &mut precommit_timeout => { - // Technically unnecessary since round() will clear the timeouts - self.timeouts.remove(&Step::Precommit); - self.round(Round(self.round.0.wrapping_add(1))); - continue; - }, // Handle any received messages msg = self.msg_recv.next() => { @@ -440,6 +408,7 @@ impl TendermintMachine { let mut validators = vec![]; let mut sigs = vec![]; for (v, sig) in self + .block .log .precommitted .iter() @@ -450,7 +419,7 @@ impl TendermintMachine { } let commit = Commit { - end_time: self.end_time[&msg.round].canonical(), + end_time: self.block.end_time[&msg.round].canonical(), validators, signature: N::SignatureScheme::aggregate(&sigs), }; @@ -484,7 +453,7 @@ impl TendermintMachine { // Only perform this verification if we already have the end_time // Else, there's a DoS where we receive a precommit for some round infinitely in the future // which forces to calculate every end time - if let Some(end_time) = self.end_time.get(&round) { + if let Some(end_time) = self.block.end_time.get(&round) { if !self.validators.verify(sender, &commit_msg(end_time.canonical(), id.as_ref()), sig) { Err(TendermintError::Malicious(sender))?; } @@ -497,7 +466,7 @@ impl TendermintMachine { &mut self, msg: Message::Signature>, ) -> Result, TendermintError> { - if msg.number != self.number { + if msg.number != self.block.number { Err(TendermintError::Temporal)?; } @@ -511,7 +480,7 @@ impl TendermintMachine { Err(TendermintError::Malicious(msg.sender))?; }; - if !self.log.log(msg.clone())? { + if !self.block.log.log(msg.clone())? { return Ok(None); } @@ -520,13 +489,14 @@ impl TendermintMachine { // Run the finalizer to see if it applies // 49-52 if matches!(msg.data, Data::Proposal(..)) || matches!(msg.data, Data::Precommit(_)) { - let proposer = self.weights.proposer(self.number, msg.round); + let proposer = self.weights.proposer(self.block.number, msg.round); // Get the proposal - if let Some(Data::Proposal(_, block)) = self.log.get(msg.round, proposer, Step::Propose) { + if let Some(Data::Proposal(_, block)) = self.block.log.get(msg.round, proposer, Step::Propose) + { // Check if it has gotten a sufficient amount of precommits // Use a junk signature since message equality disregards the signature - if self.log.has_consensus( + if self.block.log.has_consensus( msg.round, Data::Precommit(Some((block.id(), self.signer.sign(&[]).await))), ) { @@ -537,15 +507,15 @@ impl TendermintMachine { // Else, check if we need to jump ahead #[allow(clippy::comparison_chain)] - if msg.round.0 < self.round.0 { + if msg.round.0 < self.block.round.round.0 { // Prior round, disregard if not finalizing return Ok(None); - } else if msg.round.0 > self.round.0 { + } else if msg.round.0 > self.block.round.round.0 { // 55-56 // Jump, enabling processing by the below code - if self.log.round_participation(msg.round) > self.weights.fault_thresold() { + if self.block.log.round_participation(msg.round) > self.weights.fault_thresold() { // If this round already has precommit messages, verify their signatures - let round_msgs = self.log.log[&msg.round].clone(); + let round_msgs = self.block.log.log[&msg.round].clone(); for (validator, msgs) in &round_msgs { if let Some(data) = msgs.get(&Step::Precommit) { if self.verify_precommit_signature(*validator, msg.round, data).is_err() { @@ -555,7 +525,7 @@ impl TendermintMachine { } // If we're the proposer, return now so we re-run processing with our proposal // If we continue now, it'd just be wasted ops - if self.round(msg.round) { + if self.round(msg.round, None) { return Ok(None); } } else { @@ -567,12 +537,12 @@ impl TendermintMachine { // The paper executes these checks when the step is prevote. Making sure this message warrants // rerunning these checks is a sane optimization since message instances is a full iteration // of the round map - if (self.step == Step::Prevote) && matches!(msg.data, Data::Prevote(_)) { - let (participation, weight) = self.log.message_instances(self.round, Data::Prevote(None)); + if (self.block.round.step == Step::Prevote) && matches!(msg.data, Data::Prevote(_)) { + let (participation, weight) = + self.block.log.message_instances(self.block.round.round, Data::Prevote(None)); // 34-35 if participation >= self.weights.threshold() { - let timeout = self.timeout(Step::Prevote); - self.timeouts.entry(Step::Prevote).or_insert_with(|| timeout.instant()); + self.block.round.set_timeout(Step::Prevote); } // 44-46 @@ -584,16 +554,17 @@ impl TendermintMachine { // 47-48 if matches!(msg.data, Data::Precommit(_)) && - self.log.has_participation(self.round, Step::Precommit) + self.block.log.has_participation(self.block.round.round, Step::Precommit) { - let timeout = self.timeout(Step::Precommit); - self.timeouts.entry(Step::Precommit).or_insert_with(|| timeout.instant()); + self.block.round.set_timeout(Step::Precommit); } - let proposer = self.weights.proposer(self.number, self.round); - if let Some(Data::Proposal(vr, block)) = self.log.get(self.round, proposer, Step::Propose) { + let proposer = self.weights.proposer(self.block.number, self.block.round.round); + if let Some(Data::Proposal(vr, block)) = + self.block.log.get(self.block.round.round, proposer, Step::Propose) + { // 22-33 - if self.step == Step::Propose { + if self.block.round.step == Step::Propose { // Delay error handling (triggering a slash) until after we vote. let (valid, err) = match self.network.validate(block).await { Ok(_) => (true, Ok(None)), @@ -607,19 +578,19 @@ impl TendermintMachine { // 23 and 29. If it's some, both are satisfied if they're for the same ID. If it's some // with different IDs, the function on 22 rejects yet the function on 28 has one other // condition - let locked = self.locked.as_ref().map(|(_, id)| id == &block.id()).unwrap_or(true); + let locked = self.block.locked.as_ref().map(|(_, id)| id == &block.id()).unwrap_or(true); let mut vote = raw_vote.filter(|_| locked); if let Some(vr) = vr { // Malformed message - if vr.0 >= self.round.0 { + if vr.0 >= self.block.round.round.0 { Err(TendermintError::Malicious(msg.sender))?; } - if self.log.has_consensus(*vr, Data::Prevote(Some(block.id()))) { + if self.block.log.has_consensus(*vr, Data::Prevote(Some(block.id()))) { // Allow differing locked values if the proposal has a newer valid round // This is the other condition described above - if let Some((locked_round, _)) = self.locked.as_ref() { + if let Some((locked_round, _)) = self.block.locked.as_ref() { vote = vote.or_else(|| raw_vote.filter(|_| locked_round.0 <= vr.0)); } @@ -630,27 +601,36 @@ impl TendermintMachine { self.broadcast(Data::Prevote(vote)); return err; } - } else if self.valid.as_ref().map(|(round, _)| round != &self.round).unwrap_or(true) { + } else if self + .block + .valid + .as_ref() + .map(|(round, _)| round != &self.block.round.round) + .unwrap_or(true) + { // 36-43 // The run once condition is implemented above. Sinve valid will always be set, it not // being set, or only being set historically, means this has yet to be run - if self.log.has_consensus(self.round, Data::Prevote(Some(block.id()))) { + if self.block.log.has_consensus(self.block.round.round, Data::Prevote(Some(block.id()))) { match self.network.validate(block).await { Ok(_) => (), Err(BlockError::Temporal) => (), Err(BlockError::Fatal) => Err(TendermintError::Malicious(proposer))?, }; - self.valid = Some((self.round, block.clone())); - if self.step == Step::Prevote { - self.locked = Some((self.round, block.id())); + self.block.valid = Some((self.block.round.round, block.clone())); + if self.block.round.step == Step::Prevote { + self.block.locked = Some((self.block.round.round, block.id())); self.broadcast(Data::Precommit(Some(( block.id(), self .signer - .sign(&commit_msg(self.end_time[&self.round].canonical(), block.id().as_ref())) + .sign(&commit_msg( + self.block.end_time[&self.block.round.round].canonical(), + block.id().as_ref(), + )) .await, )))); return Ok(None); diff --git a/substrate/tendermint/machine/src/round.rs b/substrate/tendermint/machine/src/round.rs new file mode 100644 index 000000000..2763e5f75 --- /dev/null +++ b/substrate/tendermint/machine/src/round.rs @@ -0,0 +1,82 @@ +use std::{ + marker::PhantomData, + time::{Duration, Instant}, + collections::HashMap, +}; + +use futures::{FutureExt, future}; +use tokio::time::sleep; + +use crate::{ + time::CanonicalInstant, + Step, + ext::{Round, Network}, +}; + +pub(crate) struct RoundData { + _network: PhantomData, + pub(crate) round: Round, + pub(crate) start_time: CanonicalInstant, + pub(crate) step: Step, + pub(crate) timeouts: HashMap, +} + +impl RoundData { + pub(crate) fn new(round: Round, start_time: CanonicalInstant) -> Self { + RoundData { + _network: PhantomData, + round, + start_time, + step: Step::Propose, + timeouts: HashMap::new(), + } + } + + fn timeout(&self, step: Step) -> CanonicalInstant { + let adjusted_block = N::BLOCK_PROCESSING_TIME * (self.round.0 + 1); + let adjusted_latency = N::LATENCY_TIME * (self.round.0 + 1); + let offset = Duration::from_secs( + (match step { + Step::Propose => adjusted_block + adjusted_latency, + Step::Prevote => adjusted_block + (2 * adjusted_latency), + Step::Precommit => adjusted_block + (3 * adjusted_latency), + }) + .into(), + ); + self.start_time + offset + } + + pub(crate) fn end_time(&self) -> CanonicalInstant { + self.timeout(Step::Precommit) + } + + pub(crate) fn set_timeout(&mut self, step: Step) { + let timeout = self.timeout(step).instant(); + self.timeouts.entry(step).or_insert(timeout); + } + + pub(crate) async fn timeout_future(&self) -> Step { + let timeout_future = |step| { + let timeout = self.timeouts.get(&step).copied(); + (async move { + if let Some(timeout) = timeout { + sleep(timeout.saturating_duration_since(Instant::now())).await; + } else { + future::pending::<()>().await; + } + step + }) + .fuse() + }; + let propose_timeout = timeout_future(Step::Propose); + let prevote_timeout = timeout_future(Step::Prevote); + let precommit_timeout = timeout_future(Step::Precommit); + futures::pin_mut!(propose_timeout, prevote_timeout, precommit_timeout); + + futures::select_biased! { + step = propose_timeout => step, + step = prevote_timeout => step, + step = precommit_timeout => step, + } + } +} From 2f3bb8874472cfd8e81d4b7594291ea379cd9c67 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Nov 2022 10:52:39 -0500 Subject: [PATCH 137/186] Rename Round to RoundNumber --- substrate/tendermint/machine/src/ext.rs | 6 +- substrate/tendermint/machine/src/lib.rs | 72 ++++++++++--------- .../tendermint/machine/src/message_log.rs | 14 ++-- substrate/tendermint/machine/src/round.rs | 12 ++-- substrate/tendermint/machine/tests/ext.rs | 2 +- 5 files changed, 54 insertions(+), 52 deletions(-) diff --git a/substrate/tendermint/machine/src/ext.rs b/substrate/tendermint/machine/src/ext.rs index 270a3b12e..ba411e6f9 100644 --- a/substrate/tendermint/machine/src/ext.rs +++ b/substrate/tendermint/machine/src/ext.rs @@ -31,7 +31,7 @@ impl Signature for pub struct BlockNumber(pub u64); /// A struct containing a round number, wrapped to have a distinct type. #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)] -pub struct Round(pub u32); +pub struct RoundNumber(pub u32); /// A signer for a validator. #[async_trait] @@ -147,7 +147,7 @@ pub trait Weights: Send + Sync { } /// Weighted round robin function. - fn proposer(&self, number: BlockNumber, round: Round) -> Self::ValidatorId; + fn proposer(&self, number: BlockNumber, round: RoundNumber) -> Self::ValidatorId; } impl Weights for Arc { @@ -161,7 +161,7 @@ impl Weights for Arc { self.as_ref().weight(validator) } - fn proposer(&self, number: BlockNumber, round: Round) -> Self::ValidatorId { + fn proposer(&self, number: BlockNumber, round: RoundNumber) -> Self::ValidatorId { self.as_ref().proposer(number, round) } } diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 46386dc92..0a28c0ee2 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -41,7 +41,7 @@ enum Step { #[derive(Clone, Debug, Encode, Decode)] enum Data { - Proposal(Option, B), + Proposal(Option, B), Prevote(Option), Precommit(Option<(B::Id, S)>), } @@ -73,7 +73,7 @@ struct Message { sender: V, number: BlockNumber, - round: Round, + round: RoundNumber, data: Data, } @@ -113,12 +113,12 @@ struct BlockData { log: MessageLog, slashes: HashSet, - end_time: HashMap, + end_time: HashMap, round: RoundData, - locked: Option<(Round, ::Id)>, - valid: Option<(Round, N::Block)>, + locked: Option<(RoundNumber, ::Id)>, + valid: Option<(RoundNumber, N::Block)>, } /// A machine executing the Tendermint protocol. @@ -171,33 +171,35 @@ impl TendermintMachine { self.queue.push_back(Message { sender: *validator_id, number: self.block.number, - round: self.block.round.round, + round: self.block.round.number, data, }); } } - fn populate_end_time(&mut self, round: Round) { - for r in (self.block.round.round.0 + 1) .. round.0 { + fn populate_end_time(&mut self, round: RoundNumber) { + for r in (self.block.round.number.0 + 1) .. round.0 { self.block.end_time.insert( - Round(r), - RoundData::::new(Round(r), self.block.end_time[&Round(r - 1)]).end_time(), + RoundNumber(r), + RoundData::::new(RoundNumber(r), self.block.end_time[&RoundNumber(r - 1)]).end_time(), ); } } // Start a new round. Returns true if we were the proposer - fn round(&mut self, round: Round, time: Option) -> bool { + fn round(&mut self, round: RoundNumber, time: Option) -> bool { // If skipping rounds, populate end_time self.populate_end_time(round); // 11-13 - self.block.round = - RoundData::::new(round, time.unwrap_or_else(|| self.block.end_time[&Round(round.0 - 1)])); + self.block.round = RoundData::::new( + round, + time.unwrap_or_else(|| self.block.end_time[&RoundNumber(round.0 - 1)]), + ); self.block.end_time.insert(round, self.block.round.end_time()); // 14-21 - if Some(self.weights.proposer(self.block.number, self.block.round.round)) == + if Some(self.weights.proposer(self.block.number, self.block.round.number)) == self.block.validator_id { let (round, block) = if let Some((round, block)) = &self.block.valid { @@ -214,7 +216,7 @@ impl TendermintMachine { } // 53-54 - async fn reset(&mut self, end_round: Round, proposal: N::Block) { + async fn reset(&mut self, end_round: RoundNumber, proposal: N::Block) { // Ensure we have the end time data for the last round self.populate_end_time(end_round); @@ -236,18 +238,18 @@ impl TendermintMachine { end_time: HashMap::new(), // This will be populated in the following round() call - round: RoundData::::new(Round(0), CanonicalInstant::new(0)), + round: RoundData::::new(RoundNumber(0), CanonicalInstant::new(0)), locked: None, valid: None, }; // Start the first round - self.round(Round(0), Some(round_end)); + self.round(RoundNumber(0), Some(round_end)); } async fn reset_by_commit(&mut self, commit: Commit, proposal: N::Block) { - let mut round = self.block.round.round; + let mut round = self.block.round.number; // If this commit is for a round we don't have, jump up to it while self.block.end_time[&round].canonical() < commit.end_time { round.0 += 1; @@ -316,7 +318,7 @@ impl TendermintMachine { end_time: HashMap::new(), // This will be populated in the following round() call - round: RoundData::::new(Round(0), CanonicalInstant::new(0)), + round: RoundData::::new(RoundNumber(0), CanonicalInstant::new(0)), locked: None, valid: None, @@ -330,7 +332,7 @@ impl TendermintMachine { // after it, without the standard amount of separation (so their times will be // equivalent or minimally offset) // For callers wishing to avoid this, they should pass (0, GENESIS + N::block_time()) - machine.round(Round(0), Some(CanonicalInstant::new(last.1))); + machine.round(RoundNumber(0), Some(CanonicalInstant::new(last.1))); machine }, } @@ -372,12 +374,12 @@ impl TendermintMachine { match step { Step::Propose => { // Slash the validator for not proposing when they should've - self.slash(self.weights.proposer(self.block.number, self.block.round.round)).await; + self.slash(self.weights.proposer(self.block.number, self.block.round.number)).await; self.broadcast(Data::Prevote(None)); }, Step::Prevote => self.broadcast(Data::Precommit(None)), Step::Precommit => { - self.round(Round(self.block.round.round.0 + 1), None); + self.round(RoundNumber(self.block.round.number.0 + 1), None); continue; } } @@ -445,7 +447,7 @@ impl TendermintMachine { fn verify_precommit_signature( &self, sender: N::ValidatorId, - round: Round, + round: RoundNumber, data: &Data::Signature>, ) -> Result<(), TendermintError> { if let Data::Precommit(Some((id, sig))) = data { @@ -507,10 +509,10 @@ impl TendermintMachine { // Else, check if we need to jump ahead #[allow(clippy::comparison_chain)] - if msg.round.0 < self.block.round.round.0 { + if msg.round.0 < self.block.round.number.0 { // Prior round, disregard if not finalizing return Ok(None); - } else if msg.round.0 > self.block.round.round.0 { + } else if msg.round.0 > self.block.round.number.0 { // 55-56 // Jump, enabling processing by the below code if self.block.log.round_participation(msg.round) > self.weights.fault_thresold() { @@ -539,7 +541,7 @@ impl TendermintMachine { // of the round map if (self.block.round.step == Step::Prevote) && matches!(msg.data, Data::Prevote(_)) { let (participation, weight) = - self.block.log.message_instances(self.block.round.round, Data::Prevote(None)); + self.block.log.message_instances(self.block.round.number, Data::Prevote(None)); // 34-35 if participation >= self.weights.threshold() { self.block.round.set_timeout(Step::Prevote); @@ -554,14 +556,14 @@ impl TendermintMachine { // 47-48 if matches!(msg.data, Data::Precommit(_)) && - self.block.log.has_participation(self.block.round.round, Step::Precommit) + self.block.log.has_participation(self.block.round.number, Step::Precommit) { self.block.round.set_timeout(Step::Precommit); } - let proposer = self.weights.proposer(self.block.number, self.block.round.round); + let proposer = self.weights.proposer(self.block.number, self.block.round.number); if let Some(Data::Proposal(vr, block)) = - self.block.log.get(self.block.round.round, proposer, Step::Propose) + self.block.log.get(self.block.round.number, proposer, Step::Propose) { // 22-33 if self.block.round.step == Step::Propose { @@ -583,7 +585,7 @@ impl TendermintMachine { if let Some(vr) = vr { // Malformed message - if vr.0 >= self.block.round.round.0 { + if vr.0 >= self.block.round.number.0 { Err(TendermintError::Malicious(msg.sender))?; } @@ -605,7 +607,7 @@ impl TendermintMachine { .block .valid .as_ref() - .map(|(round, _)| round != &self.block.round.round) + .map(|(round, _)| round != &self.block.round.number) .unwrap_or(true) { // 36-43 @@ -613,22 +615,22 @@ impl TendermintMachine { // The run once condition is implemented above. Sinve valid will always be set, it not // being set, or only being set historically, means this has yet to be run - if self.block.log.has_consensus(self.block.round.round, Data::Prevote(Some(block.id()))) { + if self.block.log.has_consensus(self.block.round.number, Data::Prevote(Some(block.id()))) { match self.network.validate(block).await { Ok(_) => (), Err(BlockError::Temporal) => (), Err(BlockError::Fatal) => Err(TendermintError::Malicious(proposer))?, }; - self.block.valid = Some((self.block.round.round, block.clone())); + self.block.valid = Some((self.block.round.number, block.clone())); if self.block.round.step == Step::Prevote { - self.block.locked = Some((self.block.round.round, block.id())); + self.block.locked = Some((self.block.round.number, block.id())); self.broadcast(Data::Precommit(Some(( block.id(), self .signer .sign(&commit_msg( - self.block.end_time[&self.block.round.round].canonical(), + self.block.end_time[&self.block.round.number].canonical(), block.id().as_ref(), )) .await, diff --git a/substrate/tendermint/machine/src/message_log.rs b/substrate/tendermint/machine/src/message_log.rs index 793ad7a25..e9877130c 100644 --- a/substrate/tendermint/machine/src/message_log.rs +++ b/substrate/tendermint/machine/src/message_log.rs @@ -1,6 +1,6 @@ use std::{sync::Arc, collections::HashMap}; -use crate::{ext::*, Round, Step, Data, Message, TendermintError}; +use crate::{ext::*, RoundNumber, Step, Data, Message, TendermintError}; pub(crate) struct MessageLog { weights: Arc, @@ -9,7 +9,7 @@ pub(crate) struct MessageLog { (::Id, ::Signature), >, pub(crate) log: HashMap< - Round, + RoundNumber, HashMap< N::ValidatorId, HashMap::Signature>>, @@ -57,7 +57,7 @@ impl MessageLog { // the data. pub(crate) fn message_instances( &self, - round: Round, + round: RoundNumber, data: Data::Signature>, ) -> (u64, u64) { let mut participating = 0; @@ -75,7 +75,7 @@ impl MessageLog { } // Get the participation in a given round - pub(crate) fn round_participation(&self, round: Round) -> u64 { + pub(crate) fn round_participation(&self, round: RoundNumber) -> u64 { let mut weight = 0; if let Some(round) = self.log.get(&round) { for participant in round.keys() { @@ -86,7 +86,7 @@ impl MessageLog { } // Check if a supermajority of nodes have participated on a specific step - pub(crate) fn has_participation(&self, round: Round, step: Step) -> bool { + pub(crate) fn has_participation(&self, round: RoundNumber, step: Step) -> bool { let mut participating = 0; for (participant, msgs) in &self.log[&round] { if msgs.get(&step).is_some() { @@ -99,7 +99,7 @@ impl MessageLog { // Check if consensus has been reached on a specific piece of data pub(crate) fn has_consensus( &self, - round: Round, + round: RoundNumber, data: Data::Signature>, ) -> bool { let (_, weight) = self.message_instances(round, data); @@ -108,7 +108,7 @@ impl MessageLog { pub(crate) fn get( &self, - round: Round, + round: RoundNumber, sender: N::ValidatorId, step: Step, ) -> Option<&Data::Signature>> { diff --git a/substrate/tendermint/machine/src/round.rs b/substrate/tendermint/machine/src/round.rs index 2763e5f75..3224b9a36 100644 --- a/substrate/tendermint/machine/src/round.rs +++ b/substrate/tendermint/machine/src/round.rs @@ -10,22 +10,22 @@ use tokio::time::sleep; use crate::{ time::CanonicalInstant, Step, - ext::{Round, Network}, + ext::{RoundNumber, Network}, }; pub(crate) struct RoundData { _network: PhantomData, - pub(crate) round: Round, + pub(crate) number: RoundNumber, pub(crate) start_time: CanonicalInstant, pub(crate) step: Step, pub(crate) timeouts: HashMap, } impl RoundData { - pub(crate) fn new(round: Round, start_time: CanonicalInstant) -> Self { + pub(crate) fn new(number: RoundNumber, start_time: CanonicalInstant) -> Self { RoundData { _network: PhantomData, - round, + number, start_time, step: Step::Propose, timeouts: HashMap::new(), @@ -33,8 +33,8 @@ impl RoundData { } fn timeout(&self, step: Step) -> CanonicalInstant { - let adjusted_block = N::BLOCK_PROCESSING_TIME * (self.round.0 + 1); - let adjusted_latency = N::LATENCY_TIME * (self.round.0 + 1); + let adjusted_block = N::BLOCK_PROCESSING_TIME * (self.number.0 + 1); + let adjusted_latency = N::LATENCY_TIME * (self.number.0 + 1); let offset = Duration::from_secs( (match step { Step::Propose => adjusted_block + adjusted_latency, diff --git a/substrate/tendermint/machine/tests/ext.rs b/substrate/tendermint/machine/tests/ext.rs index d94a2f3be..69b8e55fc 100644 --- a/substrate/tendermint/machine/tests/ext.rs +++ b/substrate/tendermint/machine/tests/ext.rs @@ -77,7 +77,7 @@ impl Weights for TestWeights { [1; 4][usize::try_from(id).unwrap()] } - fn proposer(&self, number: BlockNumber, round: Round) -> TestValidatorId { + fn proposer(&self, number: BlockNumber, round: RoundNumber) -> TestValidatorId { TestValidatorId::try_from((number.0 + u64::from(round.0)) % 4).unwrap() } } From b7b57ee6dc4300e514eb817e29f96f97781b20dd Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Nov 2022 11:42:40 -0500 Subject: [PATCH 138/186] Move BlockData to a new file --- substrate/tendermint/machine/src/block.rs | 23 +++++++++++++++++++++++ substrate/tendermint/machine/src/lib.rs | 22 +++++----------------- 2 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 substrate/tendermint/machine/src/block.rs diff --git a/substrate/tendermint/machine/src/block.rs b/substrate/tendermint/machine/src/block.rs new file mode 100644 index 000000000..9c100ccc9 --- /dev/null +++ b/substrate/tendermint/machine/src/block.rs @@ -0,0 +1,23 @@ +use std::collections::{HashSet, HashMap}; + +use crate::{ + time::CanonicalInstant, + ext::{RoundNumber, BlockNumber, Block, Network}, + round::RoundData, + message_log::MessageLog, +}; + +pub(crate) struct BlockData { + pub(crate) number: BlockNumber, + pub(crate) validator_id: Option, + pub(crate) proposal: N::Block, + + pub(crate) log: MessageLog, + pub(crate) slashes: HashSet, + pub(crate) end_time: HashMap, + + pub(crate) round: RoundData, + + pub(crate) locked: Option<(RoundNumber, ::Id)>, + pub(crate) valid: Option<(RoundNumber, N::Block)>, +} diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 0a28c0ee2..ab9967248 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -16,10 +16,13 @@ use futures::{ use tokio::time::sleep; mod time; -use time::*; +use time::{sys_time, CanonicalInstant}; mod round; -use round::*; +use round::RoundData; + +mod block; +use block::BlockData; mod message_log; use message_log::MessageLog; @@ -106,21 +109,6 @@ enum TendermintError { Temporal, } -struct BlockData { - number: BlockNumber, - validator_id: Option, - proposal: N::Block, - - log: MessageLog, - slashes: HashSet, - end_time: HashMap, - - round: RoundData, - - locked: Option<(RoundNumber, ::Id)>, - valid: Option<(RoundNumber, N::Block)>, -} - /// A machine executing the Tendermint protocol. pub struct TendermintMachine { network: N, From 850878330ea590a66839ce793aafd474c223bca3 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Nov 2022 11:45:09 -0500 Subject: [PATCH 139/186] Move Round to an Option due to the pseudo-uninitialized state we create Before the addition of RoundData, we always created the round, and on .round(0), simply created it again. With RoundData, and the changes to the time code, we used round 0, time 0, the latter being incorrect yet not an issue due to lack of misuse. Now, if we do misuse it, it'll panic. --- substrate/tendermint/machine/src/block.rs | 12 +++- substrate/tendermint/machine/src/lib.rs | 77 ++++++++++++----------- 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/substrate/tendermint/machine/src/block.rs b/substrate/tendermint/machine/src/block.rs index 9c100ccc9..1a08ff4ab 100644 --- a/substrate/tendermint/machine/src/block.rs +++ b/substrate/tendermint/machine/src/block.rs @@ -16,8 +16,18 @@ pub(crate) struct BlockData { pub(crate) slashes: HashSet, pub(crate) end_time: HashMap, - pub(crate) round: RoundData, + pub(crate) round: Option>, pub(crate) locked: Option<(RoundNumber, ::Id)>, pub(crate) valid: Option<(RoundNumber, N::Block)>, } + +impl BlockData { + pub(crate) fn round(&self) -> &RoundData { + self.round.as_ref().unwrap() + } + + pub(crate) fn round_mut(&mut self) -> &mut RoundData { + self.round.as_mut().unwrap() + } +} diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index ab9967248..132de8043 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -153,20 +153,20 @@ impl TendermintMachine { &mut self, data: Data::Signature>, ) { - if let Some(validator_id) = &self.block.validator_id { + if let Some(validator_id) = self.block.validator_id { // 27, 33, 41, 46, 60, 64 - self.block.round.step = data.step(); + self.block.round_mut().step = data.step(); self.queue.push_back(Message { - sender: *validator_id, + sender: validator_id, number: self.block.number, - round: self.block.round.number, + round: self.block.round().number, data, }); } } fn populate_end_time(&mut self, round: RoundNumber) { - for r in (self.block.round.number.0 + 1) .. round.0 { + for r in (self.block.round().number.0 + 1) .. round.0 { self.block.end_time.insert( RoundNumber(r), RoundData::::new(RoundNumber(r), self.block.end_time[&RoundNumber(r - 1)]).end_time(), @@ -177,17 +177,19 @@ impl TendermintMachine { // Start a new round. Returns true if we were the proposer fn round(&mut self, round: RoundNumber, time: Option) -> bool { // If skipping rounds, populate end_time - self.populate_end_time(round); + if round.0 != 0 { + self.populate_end_time(round); + } // 11-13 - self.block.round = RoundData::::new( + self.block.round = Some(RoundData::::new( round, time.unwrap_or_else(|| self.block.end_time[&RoundNumber(round.0 - 1)]), - ); - self.block.end_time.insert(round, self.block.round.end_time()); + )); + self.block.end_time.insert(round, self.block.round().end_time()); // 14-21 - if Some(self.weights.proposer(self.block.number, self.block.round.number)) == + if Some(self.weights.proposer(self.block.number, self.block.round().number)) == self.block.validator_id { let (round, block) = if let Some((round, block)) = &self.block.valid { @@ -198,7 +200,7 @@ impl TendermintMachine { self.broadcast(Data::Proposal(round, block)); true } else { - self.block.round.set_timeout(Step::Propose); + self.block.round_mut().set_timeout(Step::Propose); false } } @@ -226,7 +228,7 @@ impl TendermintMachine { end_time: HashMap::new(), // This will be populated in the following round() call - round: RoundData::::new(RoundNumber(0), CanonicalInstant::new(0)), + round: None, locked: None, valid: None, @@ -237,7 +239,7 @@ impl TendermintMachine { } async fn reset_by_commit(&mut self, commit: Commit, proposal: N::Block) { - let mut round = self.block.round.number; + let mut round = self.block.round().number; // If this commit is for a round we don't have, jump up to it while self.block.end_time[&round].canonical() < commit.end_time { round.0 += 1; @@ -306,7 +308,7 @@ impl TendermintMachine { end_time: HashMap::new(), // This will be populated in the following round() call - round: RoundData::::new(RoundNumber(0), CanonicalInstant::new(0)), + round: None, locked: None, valid: None, @@ -352,22 +354,24 @@ impl TendermintMachine { }, // Handle any timeouts - step = self.block.round.timeout_future().fuse() => { + step = self.block.round().timeout_future().fuse() => { // Remove the timeout so it doesn't persist, always being the selected future due to bias // While this does enable the timeout to be entered again, the timeout setting code will // never attempt to add a timeout after its timeout has expired - self.block.round.timeouts.remove(&step); + self.block.round_mut().timeouts.remove(&step); // Only run if it's still the step in question - if self.block.round.step == step { + if self.block.round().step == step { match step { Step::Propose => { // Slash the validator for not proposing when they should've - self.slash(self.weights.proposer(self.block.number, self.block.round.number)).await; + self.slash( + self.weights.proposer(self.block.number, self.block.round().number) + ).await; self.broadcast(Data::Prevote(None)); }, Step::Prevote => self.broadcast(Data::Precommit(None)), Step::Precommit => { - self.round(RoundNumber(self.block.round.number.0 + 1), None); + self.round(RoundNumber(self.block.round().number.0 + 1), None); continue; } } @@ -497,10 +501,10 @@ impl TendermintMachine { // Else, check if we need to jump ahead #[allow(clippy::comparison_chain)] - if msg.round.0 < self.block.round.number.0 { + if msg.round.0 < self.block.round().number.0 { // Prior round, disregard if not finalizing return Ok(None); - } else if msg.round.0 > self.block.round.number.0 { + } else if msg.round.0 > self.block.round().number.0 { // 55-56 // Jump, enabling processing by the below code if self.block.log.round_participation(msg.round) > self.weights.fault_thresold() { @@ -527,12 +531,12 @@ impl TendermintMachine { // The paper executes these checks when the step is prevote. Making sure this message warrants // rerunning these checks is a sane optimization since message instances is a full iteration // of the round map - if (self.block.round.step == Step::Prevote) && matches!(msg.data, Data::Prevote(_)) { + if (self.block.round().step == Step::Prevote) && matches!(msg.data, Data::Prevote(_)) { let (participation, weight) = - self.block.log.message_instances(self.block.round.number, Data::Prevote(None)); + self.block.log.message_instances(self.block.round().number, Data::Prevote(None)); // 34-35 if participation >= self.weights.threshold() { - self.block.round.set_timeout(Step::Prevote); + self.block.round_mut().set_timeout(Step::Prevote); } // 44-46 @@ -544,17 +548,17 @@ impl TendermintMachine { // 47-48 if matches!(msg.data, Data::Precommit(_)) && - self.block.log.has_participation(self.block.round.number, Step::Precommit) + self.block.log.has_participation(self.block.round().number, Step::Precommit) { - self.block.round.set_timeout(Step::Precommit); + self.block.round_mut().set_timeout(Step::Precommit); } - let proposer = self.weights.proposer(self.block.number, self.block.round.number); + let proposer = self.weights.proposer(self.block.number, self.block.round().number); if let Some(Data::Proposal(vr, block)) = - self.block.log.get(self.block.round.number, proposer, Step::Propose) + self.block.log.get(self.block.round().number, proposer, Step::Propose) { // 22-33 - if self.block.round.step == Step::Propose { + if self.block.round().step == Step::Propose { // Delay error handling (triggering a slash) until after we vote. let (valid, err) = match self.network.validate(block).await { Ok(_) => (true, Ok(None)), @@ -573,7 +577,7 @@ impl TendermintMachine { if let Some(vr) = vr { // Malformed message - if vr.0 >= self.block.round.number.0 { + if vr.0 >= self.block.round().number.0 { Err(TendermintError::Malicious(msg.sender))?; } @@ -595,7 +599,7 @@ impl TendermintMachine { .block .valid .as_ref() - .map(|(round, _)| round != &self.block.round.number) + .map(|(round, _)| round != &self.block.round().number) .unwrap_or(true) { // 36-43 @@ -603,22 +607,23 @@ impl TendermintMachine { // The run once condition is implemented above. Sinve valid will always be set, it not // being set, or only being set historically, means this has yet to be run - if self.block.log.has_consensus(self.block.round.number, Data::Prevote(Some(block.id()))) { + if self.block.log.has_consensus(self.block.round().number, Data::Prevote(Some(block.id()))) + { match self.network.validate(block).await { Ok(_) => (), Err(BlockError::Temporal) => (), Err(BlockError::Fatal) => Err(TendermintError::Malicious(proposer))?, }; - self.block.valid = Some((self.block.round.number, block.clone())); - if self.block.round.step == Step::Prevote { - self.block.locked = Some((self.block.round.number, block.id())); + self.block.valid = Some((self.block.round().number, block.clone())); + if self.block.round().step == Step::Prevote { + self.block.locked = Some((self.block.round().number, block.id())); self.broadcast(Data::Precommit(Some(( block.id(), self .signer .sign(&commit_msg( - self.block.end_time[&self.block.round.number].canonical(), + self.block.end_time[&self.block.round().number].canonical(), block.id().as_ref(), )) .await, From 2de4ab8c9d913cfcbcca43bfe9ca1a17e6f2b593 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Nov 2022 11:46:32 -0500 Subject: [PATCH 140/186] Clear the Queue instead of draining and filtering There shouldn't ever be a message which passes the filter under the current design. --- substrate/tendermint/machine/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 132de8043..024333185 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -214,8 +214,8 @@ impl TendermintMachine { let round_end = self.block.end_time[&end_round]; sleep(round_end.instant().saturating_duration_since(Instant::now())).await; - // Only keep queued messages for this block - self.queue = self.queue.drain(..).filter(|msg| msg.number == self.block.number).collect(); + // Clear our outbound message queue + self.queue = VecDeque::new(); // Create the new block self.block = BlockData { @@ -339,6 +339,7 @@ impl TendermintMachine { if let Some((broadcast, msg)) = futures::select_biased! { // Handle a new height occuring externally (an external sync loop) + // Has the highest priority as it makes all other futures here irrelevant msg = self.step_recv.next() => { if let Some((commit, proposal)) = msg { self.reset_by_commit(commit, proposal).await; From 4ba469e653c98f618d91426be0d71264ff087197 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Nov 2022 11:52:55 -0500 Subject: [PATCH 141/186] BlockData::new --- substrate/tendermint/machine/src/block.rs | 28 +++++++++++++++- substrate/tendermint/machine/src/lib.rs | 40 +++++------------------ 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/substrate/tendermint/machine/src/block.rs b/substrate/tendermint/machine/src/block.rs index 1a08ff4ab..a6b7b1908 100644 --- a/substrate/tendermint/machine/src/block.rs +++ b/substrate/tendermint/machine/src/block.rs @@ -1,4 +1,7 @@ -use std::collections::{HashSet, HashMap}; +use std::{ + sync::Arc, + collections::{HashSet, HashMap}, +}; use crate::{ time::CanonicalInstant, @@ -23,6 +26,29 @@ pub(crate) struct BlockData { } impl BlockData { + pub(crate) fn new( + weights: Arc, + number: BlockNumber, + validator_id: Option, + proposal: N::Block, + ) -> BlockData { + BlockData { + number, + validator_id, + proposal, + + log: MessageLog::new(weights), + slashes: HashSet::new(), + end_time: HashMap::new(), + + // The caller of BlockData::new is expected to be populated after by the caller + round: None, + + locked: None, + valid: None, + } + } + pub(crate) fn round(&self) -> &RoundData { self.round.as_ref().unwrap() } diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 024333185..cdd3e90a3 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -3,7 +3,7 @@ use core::fmt::Debug; use std::{ sync::Arc, time::{SystemTime, Instant, Duration}, - collections::{VecDeque, HashSet, HashMap}, + collections::VecDeque, }; use parity_scale_codec::{Encode, Decode}; @@ -24,8 +24,7 @@ use round::RoundData; mod block; use block::BlockData; -mod message_log; -use message_log::MessageLog; +pub(crate) mod message_log; /// Traits and types of the external network being integrated with to provide consensus over. pub mod ext; @@ -218,21 +217,12 @@ impl TendermintMachine { self.queue = VecDeque::new(); // Create the new block - self.block = BlockData { - number: BlockNumber(self.block.number.0 + 1), - validator_id: self.signer.validator_id().await, + self.block = BlockData::new( + self.weights.clone(), + BlockNumber(self.block.number.0 + 1), + self.signer.validator_id().await, proposal, - - log: MessageLog::new(self.weights.clone()), - slashes: HashSet::new(), - end_time: HashMap::new(), - - // This will be populated in the following round() call - round: None, - - locked: None, - valid: None, - }; + ); // Start the first round self.round(RoundNumber(0), Some(round_end)); @@ -298,21 +288,7 @@ impl TendermintMachine { msg_recv, step_recv, - block: BlockData { - number: BlockNumber(last.0 .0 + 1), - validator_id, - proposal, - - log: MessageLog::new(weights), - slashes: HashSet::new(), - end_time: HashMap::new(), - - // This will be populated in the following round() call - round: None, - - locked: None, - valid: None, - }, + block: BlockData::new(weights, BlockNumber(last.0 .0 + 1), validator_id, proposal), }; // The end time of the last block is the start time for this one From c13e0c75aeaafa24caef0b3bed36e936e11a3b00 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 13 Nov 2022 18:11:09 -0500 Subject: [PATCH 142/186] Move more code into block.rs Introduces type-aliases to obtain Data/Message/SignedMessage solely from a Network object. Fixes a bug regarding stepping when you're not an active validator. --- substrate/tendermint/machine/src/block.rs | 68 ++++++++++++++ substrate/tendermint/machine/src/ext.rs | 11 +-- substrate/tendermint/machine/src/lib.rs | 89 ++++++------------- .../tendermint/machine/src/message_log.rs | 26 ++---- substrate/tendermint/machine/tests/ext.rs | 4 +- 5 files changed, 106 insertions(+), 92 deletions(-) diff --git a/substrate/tendermint/machine/src/block.rs b/substrate/tendermint/machine/src/block.rs index a6b7b1908..3e7dc6a5d 100644 --- a/substrate/tendermint/machine/src/block.rs +++ b/substrate/tendermint/machine/src/block.rs @@ -8,6 +8,7 @@ use crate::{ ext::{RoundNumber, BlockNumber, Block, Network}, round::RoundData, message_log::MessageLog, + Step, Data, DataFor, Message, MessageFor, }; pub(crate) struct BlockData { @@ -56,4 +57,71 @@ impl BlockData { pub(crate) fn round_mut(&mut self) -> &mut RoundData { self.round.as_mut().unwrap() } + + pub(crate) fn populate_end_time(&mut self, round: RoundNumber) { + for r in (self.round().number.0 + 1) .. round.0 { + self.end_time.insert( + RoundNumber(r), + RoundData::::new(RoundNumber(r), self.end_time[&RoundNumber(r - 1)]).end_time(), + ); + } + } + + // Start a new round. Optionally takes in the time for when this is the first round, and the time + // isn't simply the time of the prior round (yet rather the prior block). Returns the proposal + // data, if we are the proposer. + pub(crate) fn new_round( + &mut self, + round: RoundNumber, + proposer: N::ValidatorId, + time: Option, + ) -> Option> { + debug_assert_eq!(round.0 == 0, time.is_some()); + + // If skipping rounds, populate end_time + if round.0 != 0 { + self.populate_end_time(round); + } + + // 11-13 + self.round = Some(RoundData::::new( + round, + time.unwrap_or_else(|| self.end_time[&RoundNumber(round.0 - 1)]), + )); + self.end_time.insert(round, self.round().end_time()); + + // 14-21 + if Some(proposer) == self.validator_id { + let (round, block) = if let Some((round, block)) = &self.valid { + (Some(*round), block.clone()) + } else { + (None, self.proposal.clone()) + }; + Some(Data::Proposal(round, block)) + } else { + self.round_mut().set_timeout(Step::Propose); + None + } + } + + // Transform Data into an actual Message, using the contextual data from this block + pub(crate) fn message(&mut self, data: DataFor) -> Option> { + debug_assert_eq!( + self.round().step, + match data.step() { + Step::Propose | Step::Prevote => Step::Propose, + Step::Precommit => Step::Prevote, + }, + ); + // 27, 33, 41, 46, 60, 64 + self.round_mut().step = data.step(); + + // Only return a message to if we're actually a current validator + self.validator_id.map(|validator_id| Message { + sender: validator_id, + number: self.number, + round: self.round().number, + data, + }) + } } diff --git a/substrate/tendermint/machine/src/ext.rs b/substrate/tendermint/machine/src/ext.rs index ba411e6f9..b7295f2cb 100644 --- a/substrate/tendermint/machine/src/ext.rs +++ b/substrate/tendermint/machine/src/ext.rs @@ -6,7 +6,7 @@ use thiserror::Error; use parity_scale_codec::{Encode, Decode}; -use crate::{SignedMessage, commit_msg}; +use crate::{SignedMessageFor, commit_msg}; /// An alias for a series of traits required for a type to be usable as a validator ID, /// automatically implemented for all types satisfying those traits. @@ -249,14 +249,7 @@ pub trait Network: Send + Sync { /// established, this will double-authenticate. Switching to unauthenticated channels in a system /// already providing authenticated channels is not recommended as this is a minor, temporal /// inefficiency while downgrading channels may have wider implications. - async fn broadcast( - &mut self, - msg: SignedMessage< - Self::ValidatorId, - Self::Block, - ::Signature, - >, - ); + async fn broadcast(&mut self, msg: SignedMessageFor); /// Trigger a slash for the validator in question who was definitively malicious. /// The exact process of triggering a slash is undefined and left to the network as a whole. diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index cdd3e90a3..876b11ec5 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -19,7 +19,6 @@ mod time; use time::{sys_time, CanonicalInstant}; mod round; -use round::RoundData; mod block; use block::BlockData; @@ -108,6 +107,21 @@ enum TendermintError { Temporal, } +// Type aliases to abstract over generic hell +pub(crate) type DataFor = + Data<::Block, <::SignatureScheme as SignatureScheme>::Signature>; +pub(crate) type MessageFor = Message< + ::ValidatorId, + ::Block, + <::SignatureScheme as SignatureScheme>::Signature, +>; +/// Type alias to the SignedMessage type for a given Network +pub type SignedMessageFor = SignedMessage< + ::ValidatorId, + ::Block, + <::SignatureScheme as SignatureScheme>::Signature, +>; + /// A machine executing the Tendermint protocol. pub struct TendermintMachine { network: N, @@ -115,11 +129,8 @@ pub struct TendermintMachine { validators: N::SignatureScheme, weights: Arc, - queue: - VecDeque::Signature>>, - msg_recv: mpsc::UnboundedReceiver< - SignedMessage::Signature>, - >, + queue: VecDeque>, + msg_recv: mpsc::UnboundedReceiver>, step_recv: mpsc::UnboundedReceiver<(Commit, N::Block)>, block: BlockData, @@ -128,13 +139,7 @@ pub struct TendermintMachine { pub type StepSender = mpsc::UnboundedSender<(Commit<::SignatureScheme>, ::Block)>; -pub type MessageSender = mpsc::UnboundedSender< - SignedMessage< - ::ValidatorId, - ::Block, - <::SignatureScheme as SignatureScheme>::Signature, - >, ->; +pub type MessageSender = mpsc::UnboundedSender>; /// A Tendermint machine and its channel to receive messages from the gossip layer over. pub struct TendermintHandle { @@ -148,58 +153,20 @@ pub struct TendermintHandle { } impl TendermintMachine { - fn broadcast( - &mut self, - data: Data::Signature>, - ) { - if let Some(validator_id) = self.block.validator_id { - // 27, 33, 41, 46, 60, 64 - self.block.round_mut().step = data.step(); - self.queue.push_back(Message { - sender: validator_id, - number: self.block.number, - round: self.block.round().number, - data, - }); - } - } - - fn populate_end_time(&mut self, round: RoundNumber) { - for r in (self.block.round().number.0 + 1) .. round.0 { - self.block.end_time.insert( - RoundNumber(r), - RoundData::::new(RoundNumber(r), self.block.end_time[&RoundNumber(r - 1)]).end_time(), - ); + fn broadcast(&mut self, data: DataFor) { + if let Some(msg) = self.block.message(data) { + self.queue.push_back(msg); } } // Start a new round. Returns true if we were the proposer fn round(&mut self, round: RoundNumber, time: Option) -> bool { - // If skipping rounds, populate end_time - if round.0 != 0 { - self.populate_end_time(round); - } - - // 11-13 - self.block.round = Some(RoundData::::new( - round, - time.unwrap_or_else(|| self.block.end_time[&RoundNumber(round.0 - 1)]), - )); - self.block.end_time.insert(round, self.block.round().end_time()); - - // 14-21 - if Some(self.weights.proposer(self.block.number, self.block.round().number)) == - self.block.validator_id + if let Some(data) = + self.block.new_round(round, self.weights.proposer(self.block.number, round), time) { - let (round, block) = if let Some((round, block)) = &self.block.valid { - (Some(*round), block.clone()) - } else { - (None, self.block.proposal.clone()) - }; - self.broadcast(Data::Proposal(round, block)); + self.broadcast(data); true } else { - self.block.round_mut().set_timeout(Step::Propose); false } } @@ -207,7 +174,7 @@ impl TendermintMachine { // 53-54 async fn reset(&mut self, end_round: RoundNumber, proposal: N::Block) { // Ensure we have the end time data for the last round - self.populate_end_time(end_round); + self.block.populate_end_time(end_round); // Sleep until this round ends let round_end = self.block.end_time[&end_round]; @@ -233,7 +200,7 @@ impl TendermintMachine { // If this commit is for a round we don't have, jump up to it while self.block.end_time[&round].canonical() < commit.end_time { round.0 += 1; - self.populate_end_time(round); + self.block.populate_end_time(round); } // If this commit is for a prior round, find it while self.block.end_time[&round].canonical() > commit.end_time { @@ -417,7 +384,7 @@ impl TendermintMachine { &self, sender: N::ValidatorId, round: RoundNumber, - data: &Data::Signature>, + data: &DataFor, ) -> Result<(), TendermintError> { if let Data::Precommit(Some((id, sig))) = data { // Also verify the end_time of the commit @@ -435,7 +402,7 @@ impl TendermintMachine { async fn message( &mut self, - msg: Message::Signature>, + msg: MessageFor, ) -> Result, TendermintError> { if msg.number != self.block.number { Err(TendermintError::Temporal)?; diff --git a/substrate/tendermint/machine/src/message_log.rs b/substrate/tendermint/machine/src/message_log.rs index e9877130c..0592160d2 100644 --- a/substrate/tendermint/machine/src/message_log.rs +++ b/substrate/tendermint/machine/src/message_log.rs @@ -1,6 +1,6 @@ use std::{sync::Arc, collections::HashMap}; -use crate::{ext::*, RoundNumber, Step, Data, Message, TendermintError}; +use crate::{ext::*, RoundNumber, Step, Data, DataFor, MessageFor, TendermintError}; pub(crate) struct MessageLog { weights: Arc, @@ -8,13 +8,7 @@ pub(crate) struct MessageLog { N::ValidatorId, (::Id, ::Signature), >, - pub(crate) log: HashMap< - RoundNumber, - HashMap< - N::ValidatorId, - HashMap::Signature>>, - >, - >, + pub(crate) log: HashMap>>>, } impl MessageLog { @@ -25,7 +19,7 @@ impl MessageLog { // Returns true if it's a new message pub(crate) fn log( &mut self, - msg: Message::Signature>, + msg: MessageFor, ) -> Result> { let round = self.log.entry(msg.round).or_insert_with(HashMap::new); let msgs = round.entry(msg.sender).or_insert_with(HashMap::new); @@ -55,11 +49,7 @@ impl MessageLog { // For a given round, return the participating weight for this step, and the weight agreeing with // the data. - pub(crate) fn message_instances( - &self, - round: RoundNumber, - data: Data::Signature>, - ) -> (u64, u64) { + pub(crate) fn message_instances(&self, round: RoundNumber, data: DataFor) -> (u64, u64) { let mut participating = 0; let mut weight = 0; for (participant, msgs) in &self.log[&round] { @@ -97,11 +87,7 @@ impl MessageLog { } // Check if consensus has been reached on a specific piece of data - pub(crate) fn has_consensus( - &self, - round: RoundNumber, - data: Data::Signature>, - ) -> bool { + pub(crate) fn has_consensus(&self, round: RoundNumber, data: DataFor) -> bool { let (_, weight) = self.message_instances(round, data); weight >= self.weights.threshold() } @@ -111,7 +97,7 @@ impl MessageLog { round: RoundNumber, sender: N::ValidatorId, step: Step, - ) -> Option<&Data::Signature>> { + ) -> Option<&DataFor> { self.log.get(&round).and_then(|round| round.get(&sender).and_then(|msgs| msgs.get(&step))) } } diff --git a/substrate/tendermint/machine/tests/ext.rs b/substrate/tendermint/machine/tests/ext.rs index 69b8e55fc..086c96d8e 100644 --- a/substrate/tendermint/machine/tests/ext.rs +++ b/substrate/tendermint/machine/tests/ext.rs @@ -11,7 +11,7 @@ use futures::SinkExt; use tokio::{sync::RwLock, time::sleep}; use tendermint_machine::{ - ext::*, SignedMessage, StepSender, MessageSender, TendermintMachine, TendermintHandle, + ext::*, SignedMessageFor, StepSender, MessageSender, TendermintMachine, TendermintHandle, }; type TestValidatorId = u16; @@ -120,7 +120,7 @@ impl Network for TestNetwork { TestWeights } - async fn broadcast(&mut self, msg: SignedMessage) { + async fn broadcast(&mut self, msg: SignedMessageFor) { for (messages, _) in self.1.write().await.iter_mut() { messages.send(msg.clone()).await.unwrap(); } From b7502a7f674af7fedf526c478a3278f34bdfc1f1 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 13 Nov 2022 18:11:58 -0500 Subject: [PATCH 143/186] Have verify_precommit_signature return if it verified the signature Also fixes a bug where invalid precommit signatures were left standing and therefore contributing to commits. --- substrate/tendermint/machine/src/lib.rs | 31 +++++++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 876b11ec5..43056f9fe 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -380,13 +380,16 @@ impl TendermintMachine { } } + // Returns Ok(true) if this was a Precommit which had its signature validated + // Returns Ok(false) if the signature wasn't validated yet + // Returns Err if the signature was invalid fn verify_precommit_signature( &self, sender: N::ValidatorId, round: RoundNumber, data: &DataFor, - ) -> Result<(), TendermintError> { - if let Data::Precommit(Some((id, sig))) = data { + ) -> Result> { + Ok(if let Data::Precommit(Some((id, sig))) = data { // Also verify the end_time of the commit // Only perform this verification if we already have the end_time // Else, there's a DoS where we receive a precommit for some round infinitely in the future @@ -395,9 +398,13 @@ impl TendermintMachine { if !self.validators.verify(sender, &commit_msg(end_time.canonical(), id.as_ref()), sig) { Err(TendermintError::Malicious(sender))?; } + true + } else { + false } - } - Ok(()) + } else { + false + }) } async fn message( @@ -456,7 +463,21 @@ impl TendermintMachine { let round_msgs = self.block.log.log[&msg.round].clone(); for (validator, msgs) in &round_msgs { if let Some(data) = msgs.get(&Step::Precommit) { - if self.verify_precommit_signature(*validator, msg.round, data).is_err() { + if let Ok(res) = self.verify_precommit_signature(*validator, msg.round, data) { + // Ensure this actually verified the signature instead of believing it shouldn't yet + debug_assert!(res); + } else { + // Remove the message so it isn't counted towards forming a commit/included in one + // This won't remove the fact the precommitted for this block hash in the MessageLog + self + .block + .log + .log + .get_mut(&msg.round) + .unwrap() + .get_mut(validator) + .unwrap() + .remove(&Step::Precommit); self.slash(*validator).await; } } From 0b8181b912892cb48f37bae4413709f50553a4fe Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 13 Nov 2022 18:15:19 -0500 Subject: [PATCH 144/186] Remove the precommit signature hash It cached signatures per-block. Precommit signatures are bound to each round. This would lead to forming invalid commits when a commit should be formed. Under debug, the machine would catch that and panic. On release, it'd have everyone who wasn't a validator fail to continue syncing. --- substrate/tendermint/machine/src/lib.rs | 18 +++++++++--------- .../tendermint/machine/src/message_log.rs | 11 ++++------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 43056f9fe..166dbd6cd 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -345,15 +345,15 @@ impl TendermintMachine { Ok(Some(block)) => { let mut validators = vec![]; let mut sigs = vec![]; - for (v, sig) in self - .block - .log - .precommitted - .iter() - .filter_map(|(k, (id, sig))| Some((*k, sig.clone())).filter(|_| id == &block.id())) - { - validators.push(v); - sigs.push(sig); + // Get all precommits for this round + for (validator, msgs) in &self.block.log.log[&msg.round] { + if let Some(Data::Precommit(Some((id, sig)))) = msgs.get(&Step::Precommit) { + // If this precommit was for this block, include it + if id == &block.id() { + validators.push(*validator); + sigs.push(sig.clone()); + } + } } let commit = Commit { diff --git a/substrate/tendermint/machine/src/message_log.rs b/substrate/tendermint/machine/src/message_log.rs index 0592160d2..cb64676e4 100644 --- a/substrate/tendermint/machine/src/message_log.rs +++ b/substrate/tendermint/machine/src/message_log.rs @@ -4,10 +4,7 @@ use crate::{ext::*, RoundNumber, Step, Data, DataFor, MessageFor, TendermintErro pub(crate) struct MessageLog { weights: Arc, - pub(crate) precommitted: HashMap< - N::ValidatorId, - (::Id, ::Signature), - >, + precommitted: HashMap::Id>, pub(crate) log: HashMap>>>, } @@ -34,13 +31,13 @@ impl MessageLog { } // If they already precommitted to a distinct hash, error - if let Data::Precommit(Some((hash, sig))) = &msg.data { - if let Some((prev, _)) = self.precommitted.get(&msg.sender) { + if let Data::Precommit(Some((hash, _))) = &msg.data { + if let Some(prev) = self.precommitted.get(&msg.sender) { if hash != prev { Err(TendermintError::Malicious(msg.sender))?; } } - self.precommitted.insert(msg.sender, (*hash, sig.clone())); + self.precommitted.insert(msg.sender, *hash); } msgs.insert(step, msg.data); From 48b4b685ca5781fb04292b6c8d04b31547aa423e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 13 Nov 2022 18:33:26 -0500 Subject: [PATCH 145/186] Slight doc changes Also flattens the message handling function by replacing an if containing all following code in the function with an early return for the else case. --- substrate/tendermint/machine/src/lib.rs | 147 +++++++++++++----------- 1 file changed, 78 insertions(+), 69 deletions(-) diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 166dbd6cd..1c64637a4 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -381,7 +381,7 @@ impl TendermintMachine { } // Returns Ok(true) if this was a Precommit which had its signature validated - // Returns Ok(false) if the signature wasn't validated yet + // Returns Ok(false) if it wasn't a Precommit or the signature wasn't validated yet // Returns Err if the signature was invalid fn verify_precommit_signature( &self, @@ -469,6 +469,8 @@ impl TendermintMachine { } else { // Remove the message so it isn't counted towards forming a commit/included in one // This won't remove the fact the precommitted for this block hash in the MessageLog + // TODO: Don't even log these in the first place until we jump, preventing needing + // to do this in the first place self .block .log @@ -518,83 +520,90 @@ impl TendermintMachine { self.block.round_mut().set_timeout(Step::Precommit); } + // All further operations require actually having the proposal in question let proposer = self.weights.proposer(self.block.number, self.block.round().number); - if let Some(Data::Proposal(vr, block)) = + let (vr, block) = if let Some(Data::Proposal(vr, block)) = self.block.log.get(self.block.round().number, proposer, Step::Propose) { - // 22-33 - if self.block.round().step == Step::Propose { - // Delay error handling (triggering a slash) until after we vote. - let (valid, err) = match self.network.validate(block).await { - Ok(_) => (true, Ok(None)), - Err(BlockError::Temporal) => (false, Ok(None)), - Err(BlockError::Fatal) => (false, Err(TendermintError::Malicious(proposer))), - }; - // Create a raw vote which only requires block validity as a basis for the actual vote. - let raw_vote = Some(block.id()).filter(|_| valid); - - // If locked is none, it has a round of -1 according to the protocol. That satisfies - // 23 and 29. If it's some, both are satisfied if they're for the same ID. If it's some - // with different IDs, the function on 22 rejects yet the function on 28 has one other - // condition - let locked = self.block.locked.as_ref().map(|(_, id)| id == &block.id()).unwrap_or(true); - let mut vote = raw_vote.filter(|_| locked); - - if let Some(vr) = vr { - // Malformed message - if vr.0 >= self.block.round().number.0 { - Err(TendermintError::Malicious(msg.sender))?; - } + (vr, block) + } else { + return Ok(None); + }; - if self.block.log.has_consensus(*vr, Data::Prevote(Some(block.id()))) { - // Allow differing locked values if the proposal has a newer valid round - // This is the other condition described above - if let Some((locked_round, _)) = self.block.locked.as_ref() { - vote = vote.or_else(|| raw_vote.filter(|_| locked_round.0 <= vr.0)); - } + // 22-33 + if self.block.round().step == Step::Propose { + // Delay error handling (triggering a slash) until after we vote. + let (valid, err) = match self.network.validate(block).await { + Ok(_) => (true, Ok(None)), + Err(BlockError::Temporal) => (false, Ok(None)), + Err(BlockError::Fatal) => (false, Err(TendermintError::Malicious(proposer))), + }; + // Create a raw vote which only requires block validity as a basis for the actual vote. + let raw_vote = Some(block.id()).filter(|_| valid); + + // If locked is none, it has a round of -1 according to the protocol. That satisfies + // 23 and 29. If it's some, both are satisfied if they're for the same ID. If it's some + // with different IDs, the function on 22 rejects yet the function on 28 has one other + // condition + let locked = self.block.locked.as_ref().map(|(_, id)| id == &block.id()).unwrap_or(true); + let mut vote = raw_vote.filter(|_| locked); + + if let Some(vr) = vr { + // Malformed message + if vr.0 >= self.block.round().number.0 { + Err(TendermintError::Malicious(msg.sender))?; + } - self.broadcast(Data::Prevote(vote)); - return err; + if self.block.log.has_consensus(*vr, Data::Prevote(Some(block.id()))) { + // Allow differing locked values if the proposal has a newer valid round + // This is the other condition described above + if let Some((locked_round, _)) = self.block.locked.as_ref() { + vote = vote.or_else(|| raw_vote.filter(|_| locked_round.0 <= vr.0)); } - } else { + self.broadcast(Data::Prevote(vote)); return err; } - } else if self - .block - .valid - .as_ref() - .map(|(round, _)| round != &self.block.round().number) - .unwrap_or(true) - { - // 36-43 - - // The run once condition is implemented above. Sinve valid will always be set, it not - // being set, or only being set historically, means this has yet to be run - - if self.block.log.has_consensus(self.block.round().number, Data::Prevote(Some(block.id()))) - { - match self.network.validate(block).await { - Ok(_) => (), - Err(BlockError::Temporal) => (), - Err(BlockError::Fatal) => Err(TendermintError::Malicious(proposer))?, - }; - - self.block.valid = Some((self.block.round().number, block.clone())); - if self.block.round().step == Step::Prevote { - self.block.locked = Some((self.block.round().number, block.id())); - self.broadcast(Data::Precommit(Some(( - block.id(), - self - .signer - .sign(&commit_msg( - self.block.end_time[&self.block.round().number].canonical(), - block.id().as_ref(), - )) - .await, - )))); - return Ok(None); - } + } else { + self.broadcast(Data::Prevote(vote)); + return err; + } + + return Ok(None); + } + + if self + .block + .valid + .as_ref() + .map(|(round, _)| round != &self.block.round().number) + .unwrap_or(true) + { + // 36-43 + + // The run once condition is implemented above. Since valid will always be set by this, it + // not being set, or only being set historically, means this has yet to be run + + if self.block.log.has_consensus(self.block.round().number, Data::Prevote(Some(block.id()))) { + match self.network.validate(block).await { + Ok(_) => (), + Err(BlockError::Temporal) => (), + Err(BlockError::Fatal) => Err(TendermintError::Malicious(proposer))?, + }; + + self.block.valid = Some((self.block.round().number, block.clone())); + if self.block.round().step == Step::Prevote { + self.block.locked = Some((self.block.round().number, block.id())); + self.broadcast(Data::Precommit(Some(( + block.id(), + self + .signer + .sign(&commit_msg( + self.block.end_time[&self.block.round().number].canonical(), + block.id().as_ref(), + )) + .await, + )))); } } } From 33e52ae79a8b3a4c46155c583db531528322df96 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 13 Nov 2022 19:45:16 -0500 Subject: [PATCH 146/186] Always produce notifications for finalized blocks via origin overrides --- .../tendermint/client/src/authority/mod.rs | 5 +-- .../tendermint/client/src/block_import.rs | 40 ++++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 8490b2f1d..0ea6be9a8 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -310,10 +310,7 @@ impl Network for TendermintAuthority { *self.import.importing_block.write().unwrap() = Some(hash); queue_write.as_mut().unwrap().import_blocks( - // We do not want this block, which hasn't been confirmed, to be broadcast over the net - // Substrate will generate notifications unless it's Genesis, which this isn't, InitialSync, - // which changes telemtry behavior, or File, which is... close enough - BlockOrigin::File, + BlockOrigin::ConsensusBroadcast, // TODO: Use BlockOrigin::Own when it's our block vec![IncomingBlock { hash, header: Some(header), diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index dc285e66a..2b439cb47 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use sp_api::BlockId; use sp_runtime::traits::{Header, Block}; use sp_blockchain::{BlockStatus, HeaderBackend, Backend as BlockchainBackend}; -use sp_consensus::{Error, CacheKeyId, SelectChain}; +use sp_consensus::{Error, CacheKeyId, BlockOrigin, SelectChain}; use sc_consensus::{BlockCheckParams, BlockImportParams, ImportResult, BlockImport, Verifier}; @@ -86,6 +86,44 @@ where &mut self, mut block: BlockImportParams, ) -> Result<(BlockImportParams, Option)>>), String> { + block.origin = match block.origin { + BlockOrigin::Genesis => BlockOrigin::Genesis, + BlockOrigin::NetworkBroadcast => BlockOrigin::NetworkBroadcast, + + // Re-map NetworkInitialSync to NetworkBroadcast so it still triggers notifications + // Tendermint will listen to the finality stream. If we sync a block we're running a machine + // for, it'll force the machine to move ahead. We can only do that if there actually are + // notifications + // + // Then Serai also runs data indexing code based on block addition, so ensuring it always + // emits events ensures we always perform our necessary indexing (albeit with a race + // condition since Substrate will eventually prune the block's state, potentially before + // indexing finishes when syncing) + // + // The alternative to this would be editing Substrate directly, which would be a lot less + // fragile, manually triggering the notifications (which may be possible with code intended + // for testing), writing our own notification system, or implementing lock_import_and_run + // on our end, letting us directly set the notifications, so we're not beholden to when + // Substrate decides to call notify_finalized + // + // TODO: Call lock_import_and_run on our end, which already may be needed for safety reasons + BlockOrigin::NetworkInitialSync => BlockOrigin::NetworkBroadcast, + // Also re-map File so bootstraps also trigger notifications, enabling safely using + // bootstraps + BlockOrigin::File => BlockOrigin::NetworkBroadcast, + + // We do not want this block, which hasn't been confirmed, to be broadcast over the net + // Substrate will generate notifications unless it's Genesis, which this isn't, InitialSync, + // which changes telemetry behavior, or File, which is... close enough + // + // Even if we do manually implement lock_import_and_run, Substrate will still override + // our notifications if it believes it should provide notifications. That means we *still* + // have to keep this patch, with all its fragility, unless we edit Substrate or move the + // the entire block import flow under Serai + BlockOrigin::ConsensusBroadcast => BlockOrigin::File, + BlockOrigin::Own => BlockOrigin::File, + }; + if self.check_already_in_chain(block.header.hash()) { return Ok((block, None)); } From 2408fd8500c775d6d39bfbef66bfcd66b384b138 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 13 Nov 2022 20:18:47 -0500 Subject: [PATCH 147/186] Correct weird formatting --- .../tendermint/client/src/authority/mod.rs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 0ea6be9a8..042342627 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -229,17 +229,17 @@ impl TendermintAuthority { // Received a message msg = recv.next() => { if let Some(msg) = msg { - messages.send(match SignedMessage::decode(&mut msg.message.as_ref()) { - Ok(msg) => msg, - Err(e) => { - // This is guaranteed to be valid thanks to to the gossip validator, assuming - // that pipeline is correct. That's why this doesn't panic - error!(target: "tendermint", "Couldn't decode valid message: {}", e); - continue; - } - }) - .await - .unwrap() + messages.send( + match SignedMessage::decode(&mut msg.message.as_ref()) { + Ok(msg) => msg, + Err(e) => { + // This is guaranteed to be valid thanks to to the gossip validator, assuming + // that pipeline is correct. This doesn't panic as a hedge + error!(target: "tendermint", "Couldn't decode valid message: {}", e); + continue; + } + } + ).await.unwrap() } else { break; } From 138866f64df84ea56454c248be0c1f2dae886c06 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 13 Nov 2022 20:19:19 -0500 Subject: [PATCH 148/186] Update to the latest tendermint-machine --- substrate/tendermint/client/src/authority/mod.rs | 2 +- substrate/tendermint/client/src/validators.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 042342627..e4972fef8 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -176,7 +176,7 @@ impl TendermintAuthority { let (gossip_tx, mut gossip_rx) = mpsc::unbounded(); // Create the Tendermint machine - let TendermintHandle { mut messages, machine } = { + let TendermintHandle { mut step, mut messages, machine } = { // Set this struct as active *self.import.providers.write().await = Some(providers); self.active = Some(ActiveAuthority { diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 084546383..c02a61f03 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -15,7 +15,7 @@ use sp_api::{BlockId, ProvideRuntimeApi}; use sc_client_api::HeaderBackend; -use tendermint_machine::ext::{BlockNumber, Round, Weights, Signer, SignatureScheme}; +use tendermint_machine::ext::{BlockNumber, RoundNumber, Weights, Signer, SignatureScheme}; use sp_tendermint::TendermintApi; @@ -186,7 +186,7 @@ impl Weights for TendermintValidators { } // TODO - fn proposer(&self, number: BlockNumber, round: Round) -> u16 { + fn proposer(&self, number: BlockNumber, round: RoundNumber) -> u16 { u16::try_from( (number.0 + u64::from(round.0)) % u64::try_from(self.0.read().unwrap().lookup.len()).unwrap(), ) From 8c51bc011d03c8d54ded05011e7f4d1a01e9f873 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 13 Nov 2022 20:26:01 -0500 Subject: [PATCH 149/186] Manually step the Tendermint machine when we synced a block over the network --- .../tendermint/client/src/authority/mod.rs | 99 +++++++++++++------ substrate/tendermint/client/src/lib.rs | 4 +- 2 files changed, 71 insertions(+), 32 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index e4972fef8..31c2c597f 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -10,6 +10,7 @@ use log::{warn, error}; use futures::{ SinkExt, StreamExt, + lock::Mutex, channel::mpsc::{self, UnboundedSender}, }; @@ -26,7 +27,7 @@ use sp_consensus::{Error, BlockOrigin, Proposer, Environment}; use sc_consensus::import_queue::IncomingBlock; use sc_service::ImportQueue; -use sc_client_api::{BlockBackend, Finalizer}; +use sc_client_api::{BlockBackend, Finalizer, BlockchainEvents}; use sc_network::{ProtocolName, NetworkBlock}; use sc_network_gossip::GossipEngine; @@ -64,7 +65,7 @@ struct ActiveAuthority { >, // Block producer - env: T::Environment, + env: Arc>, announce: T::Network, } @@ -74,6 +75,35 @@ pub struct TendermintAuthority { active: Option>, } +async fn get_proposal( + env: &Arc>, + import: &TendermintImport, + header: &::Header, + stub: bool +) -> T::Block { + let proposer = + env.lock().await.init(header).await.expect("Failed to create a proposer for the new block"); + + proposer + .propose( + import.inherent_data(*header.parent_hash()).await, + Digest::default(), + if stub { + Duration::ZERO + } else { + // The first processing time is to build the block. + // The second is for it to be downloaded (assumes a block won't take longer to download + // than it'll take to process) + // The third is for it to actually be processed + Duration::from_secs((T::BLOCK_PROCESSING_TIME_IN_SECONDS / 3).into()) + }, + Some(T::PROPOSED_BLOCK_SIZE_LIMIT), + ) + .await + .expect("Failed to crate a new block proposal") + .block +} + impl TendermintAuthority { /// Create a new TendermintAuthority. pub fn new(import: TendermintImport) -> Self { @@ -114,32 +144,8 @@ impl TendermintAuthority { ) } - pub(crate) async fn get_proposal(&mut self, header: &::Header) -> T::Block { - let parent = *header.parent_hash(); - - let proposer = self - .active - .as_mut() - .unwrap() - .env - .init(header) - .await - .expect("Failed to create a proposer for the new block"); - - proposer - .propose( - self.import.inherent_data(parent).await, - Digest::default(), - // The first processing time is to build the block. - // The second is for it to be downloaded (assumes a block won't take longer to download - // than it'll take to process) - // The third is for it to actually be processed - Duration::from_secs((T::BLOCK_PROCESSING_TIME_IN_SECONDS / 3).into()), - Some(T::PROPOSED_BLOCK_SIZE_LIMIT), - ) - .await - .expect("Failed to crate a new block proposal") - .block + async fn get_proposal(&mut self, header: &::Header) -> T::Block { + get_proposal(&self.active.as_mut().unwrap().env, &self.import, header, false).await } /// Act as a network authority, proposing and voting on blocks. This should be spawned on a task @@ -175,6 +181,12 @@ impl TendermintAuthority { let (new_number_tx, mut new_number_rx) = mpsc::unbounded(); let (gossip_tx, mut gossip_rx) = mpsc::unbounded(); + // Clone the import object + let import = self.import.clone(); + + // Move the env into an Arc + let env = Arc::new(Mutex::new(env)); + // Create the Tendermint machine let TendermintHandle { mut step, mut messages, machine } = { // Set this struct as active @@ -185,7 +197,7 @@ impl TendermintAuthority { new_number: new_number_tx, gossip: gossip_tx, - env, + env: env.clone(), announce: network, }); @@ -201,16 +213,41 @@ impl TendermintAuthority { // Start receiving messages about the Tendermint process for this block let mut recv = gossip.messages_for(TendermintGossip::::topic(new_number)); + // Get finality events from Substrate + let mut finality = import.client.finality_notification_stream(); + loop { futures::select_biased! { // GossipEngine closed down _ = gossip => break, + // Synced a block from the network + notif = finality.next() => { + if let Some(notif) = notif { + let justifications = import.client.justifications(notif.hash).unwrap().unwrap(); + step.send(( + Commit::decode(&mut justifications.get(CONSENSUS_ID).unwrap().as_ref()).unwrap(), + // This will fail if syncing occurs radically faster than machine stepping takes + // TODO: Set true when initial syncing + get_proposal(&env, &import, ¬if.header, false).await + )).await.unwrap(); + + let new_number = match (*notif.header.number()).try_into() { + Ok(number) => number, + Err(_) => panic!("BlockNumber exceeded u64"), + }; + *number.write().unwrap() = new_number; + recv = gossip.messages_for(TendermintGossip::::topic(new_number)) + } else { + break; + } + }, + // Machine reached a new height new_number = new_number_rx.next() => { if let Some(new_number) = new_number { *number.write().unwrap() = new_number; - recv = gossip.messages_for(TendermintGossip::::topic(new_number)); + recv = gossip.messages_for(TendermintGossip::::topic(new_number)) } else { break; } @@ -239,7 +276,7 @@ impl TendermintAuthority { continue; } } - ).await.unwrap() + ).await.unwrap(); } else { break; } diff --git a/substrate/tendermint/client/src/lib.rs b/substrate/tendermint/client/src/lib.rs index 4ebf6e475..90c50244d 100644 --- a/substrate/tendermint/client/src/lib.rs +++ b/substrate/tendermint/client/src/lib.rs @@ -7,7 +7,7 @@ use sp_blockchain::HeaderBackend; use sp_api::{StateBackend, StateBackendFor, TransactionFor, ApiExt, ProvideRuntimeApi}; use sp_consensus::{Error, Environment}; -use sc_client_api::{BlockBackend, Backend, Finalizer}; +use sc_client_api::{BlockBackend, Backend, Finalizer, BlockchainEvents}; use sc_block_builder::BlockBuilderApi; use sc_consensus::{BlockImport, BasicQueue}; @@ -81,6 +81,7 @@ pub trait TendermintClient: Send + Sync + 'static { + BlockBackend + BlockImport + Finalizer + + BlockchainEvents + ProvideRuntimeApi + 'static; } @@ -100,6 +101,7 @@ pub trait TendermintClientMinimal: Send + Sync + 'static { + BlockBackend + BlockImport> + Finalizer + + BlockchainEvents + ProvideRuntimeApi + 'static; } From 0490157e418947aaaded20fb9299b9915ea39fce Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 13 Nov 2022 20:31:00 -0500 Subject: [PATCH 150/186] Ignore finality notifications for old blocks --- substrate/tendermint/client/src/authority/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 31c2c597f..31a773595 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -224,6 +224,14 @@ impl TendermintAuthority { // Synced a block from the network notif = finality.next() => { if let Some(notif) = notif { + let new_number = match (*notif.header.number()).try_into() { + Ok(number) => number, + Err(_) => panic!("BlockNumber exceeded u64"), + }; + if new_number <= *number.read().unwrap() { + continue; + } + let justifications = import.client.justifications(notif.hash).unwrap().unwrap(); step.send(( Commit::decode(&mut justifications.get(CONSENSUS_ID).unwrap().as_ref()).unwrap(), @@ -232,10 +240,6 @@ impl TendermintAuthority { get_proposal(&env, &import, ¬if.header, false).await )).await.unwrap(); - let new_number = match (*notif.header.number()).try_into() { - Ok(number) => number, - Err(_) => panic!("BlockNumber exceeded u64"), - }; *number.write().unwrap() = new_number; recv = gossip.messages_for(TendermintGossip::::topic(new_number)) } else { From 457d11a62eed06e1a727362545e62f76a789b4e5 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 13 Nov 2022 20:31:13 -0500 Subject: [PATCH 151/186] Remove a TODO resolved in 8c51bc011d03c8d54ded05011e7f4d1a01e9f873 --- substrate/tendermint/client/src/block_import.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index 2b439cb47..9edf5be07 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -70,9 +70,6 @@ where self.check(&mut block).await?; self.client.import_block(block, new_cache).await.map_err(Into::into) - - // TODO: If we're a validator who just successfully synced a block, recreate the tendermint - // machine with the new height } } From 7248a414d05f12ad0bb101580150a9fd63581a55 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 13 Nov 2022 20:47:15 -0500 Subject: [PATCH 152/186] Add a TODO comment to slash Enables searching for the case-sensitive phrase and finding it. --- substrate/tendermint/client/src/authority/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 31a773595..95c1ebf18 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -326,6 +326,7 @@ impl Network for TendermintAuthority { } async fn slash(&mut self, _validator: u16) { + // TODO todo!() } From 9dc8f5c14a0ca5570f97685dd061f1911ed131b6 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 13 Nov 2022 22:17:12 -0500 Subject: [PATCH 153/186] cargo fmt --- substrate/tendermint/client/src/authority/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 95c1ebf18..4b0596153 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -79,7 +79,7 @@ async fn get_proposal( env: &Arc>, import: &TendermintImport, header: &::Header, - stub: bool + stub: bool, ) -> T::Block { let proposer = env.lock().await.init(header).await.expect("Failed to create a proposer for the new block"); From 707a177d527e89da2aea6fafccbf2bb5ecb602a5 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 14 Nov 2022 16:38:46 -0500 Subject: [PATCH 154/186] Use a tmp DB for Serai in Docker --- deploy/serai/scripts/entry-dev.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/serai/scripts/entry-dev.sh b/deploy/serai/scripts/entry-dev.sh index 56be45954..7ac28349e 100755 --- a/deploy/serai/scripts/entry-dev.sh +++ b/deploy/serai/scripts/entry-dev.sh @@ -1,6 +1,6 @@ #!/bin/bash if [[ -z $VALIDATOR ]]; then - serai-node --chain $CHAIN --name $NAME + serai-node --tmp --chain $CHAIN --name $NAME else - serai-node --chain $CHAIN --$NAME + serai-node --tmp --chain $CHAIN --$NAME fi From 7d42c45024b0534f2822e1675553ac93af30e6aa Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 14 Nov 2022 19:00:54 -0500 Subject: [PATCH 155/186] Remove panic on slash As we move towards protonet, this can happen (if a node goes offline), yet it happening brings down the entire net right now. --- substrate/tendermint/client/src/authority/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 4b0596153..33a8d2de7 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -327,7 +327,6 @@ impl Network for TendermintAuthority { async fn slash(&mut self, _validator: u16) { // TODO - todo!() } // The Tendermint machine will call add_block for any block which is committed to, regardless of From 06c57a1b03efe2b9b551f4606df66f684b9b4d5a Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 14 Nov 2022 19:02:32 -0500 Subject: [PATCH 156/186] Add log::error on slash --- substrate/tendermint/client/src/authority/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 33a8d2de7..95decf9e9 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -325,8 +325,9 @@ impl Network for TendermintAuthority { } } - async fn slash(&mut self, _validator: u16) { + async fn slash(&mut self, validator: u16) { // TODO + error!("slashing {}, if this is a local network, this shouldn't happen", validator); } // The Tendermint machine will call add_block for any block which is committed to, regardless of From d2e0b58cab51782008b5d37afb03c8268e3a7c7b Mon Sep 17 00:00:00 2001 From: vrx00 Date: Thu, 17 Nov 2022 00:29:33 +0100 Subject: [PATCH 157/186] created shared volume between containers --- deploy/docker-compose.yml | 14 +++++++++++++- deploy/genesis-service/Dockerfile | 5 +++++ deploy/genesis-service/entry-dev.sh | 6 ++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 deploy/genesis-service/Dockerfile create mode 100644 deploy/genesis-service/entry-dev.sh diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index d7ce91b92..fbf828085 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -22,9 +22,18 @@ volumes: serai-dave: serai-eve: serai-ferdie: - + genesis-service-volume: services: + genesis-service: + image: serai-genesis-service:dev + build: + context: ./genesis-service/ + dockerfile: Dockerfile + entrypoint: /entry-dev.sh + volumes: + - genesis-service-volume:/temp + _serai: &serai_defaults restart: unless-stopped @@ -39,6 +48,9 @@ services: entrypoint: /scripts/entry-dev.sh volumes: - "./serai/scripts:/scripts" + - genesis-service-volume:/temp + depends_on: + - genesis-service serai-base: <<: *serai_defaults diff --git a/deploy/genesis-service/Dockerfile b/deploy/genesis-service/Dockerfile new file mode 100644 index 000000000..014642d9f --- /dev/null +++ b/deploy/genesis-service/Dockerfile @@ -0,0 +1,5 @@ +FROM alpine + +COPY entry-dev.sh / + +ENTRYPOINT ["entry-dev.sh"] diff --git a/deploy/genesis-service/entry-dev.sh b/deploy/genesis-service/entry-dev.sh new file mode 100644 index 000000000..2058df41d --- /dev/null +++ b/deploy/genesis-service/entry-dev.sh @@ -0,0 +1,6 @@ +#!/bin/sh +date +%s > /genesis +genesis=`cat /genesis` +echo "Genesis : $genesis" + +tail -f /dev/null From 8c65e1bd79c1bfd27c016e7235957bc56e2020e5 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 16 Nov 2022 21:18:26 -0500 Subject: [PATCH 158/186] Complete the sh scripts --- deploy/genesis-service/entry-dev.sh | 7 ++++--- deploy/serai/scripts/entry-dev.sh | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) mode change 100644 => 100755 deploy/genesis-service/entry-dev.sh diff --git a/deploy/genesis-service/entry-dev.sh b/deploy/genesis-service/entry-dev.sh old mode 100644 new mode 100755 index 2058df41d..dd2415ee3 --- a/deploy/genesis-service/entry-dev.sh +++ b/deploy/genesis-service/entry-dev.sh @@ -1,6 +1,7 @@ #!/bin/sh -date +%s > /genesis -genesis=`cat /genesis` -echo "Genesis : $genesis" + +date +%s > /temp/genesis +GENESIS=$(cat /temp/genesis) +echo "Genesis : $GENESIS" tail -f /dev/null diff --git a/deploy/serai/scripts/entry-dev.sh b/deploy/serai/scripts/entry-dev.sh index 7ac28349e..2947351c8 100755 --- a/deploy/serai/scripts/entry-dev.sh +++ b/deploy/serai/scripts/entry-dev.sh @@ -1,4 +1,6 @@ #!/bin/bash + +export GENESIS=$(cat /temp/genesis) if [[ -z $VALIDATOR ]]; then serai-node --tmp --chain $CHAIN --name $NAME else From 426bacac35420280119650a650ed4f8c634b2361 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 16 Nov 2022 21:21:40 -0500 Subject: [PATCH 159/186] Pass in the genesis time to Substrate --- substrate/node/src/service.rs | 17 +++++++++++++++-- .../tendermint/client/src/authority/mod.rs | 18 ++++++++++-------- substrate/tendermint/client/src/tendermint.rs | 2 +- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index abb3fcdab..3dbcdf571 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -1,4 +1,10 @@ -use std::{boxed::Box, sync::Arc, error::Error}; +use std::{ + error::Error, + boxed::Box, + sync::Arc, + time::{UNIX_EPOCH, Duration}, + str::FromStr, +}; use sp_runtime::traits::{Block as BlockTrait}; use sp_inherents::CreateInherentDataProviders; @@ -241,7 +247,14 @@ pub async fn new_full(mut config: Configuration) -> Result { /// Tendermint Authority. Participates in the block proposal and voting process. pub struct TendermintAuthority { + genesis: Option, import: TendermintImport, active: Option>, } @@ -106,18 +107,19 @@ async fn get_proposal( impl TendermintAuthority { /// Create a new TendermintAuthority. - pub fn new(import: TendermintImport) -> Self { - Self { import, active: None } + pub fn new(genesis: Option, import: TendermintImport) -> Self { + Self { + genesis: genesis.map(|genesis| { + genesis.duration_since(UNIX_EPOCH).unwrap().as_secs() + u64::from(Self::block_time()) + }), + import, + active: None, + } } fn get_last(&self) -> (::Hash, (BlockNumber, u64)) { let info = self.import.client.info(); - // TODO: Genesis start time + BLOCK_TIME - let mut fake_genesis = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); - // Round up to the nearest 5s increment - fake_genesis += 5 - (fake_genesis % 5); - ( info.finalized_hash, ( @@ -139,7 +141,7 @@ impl TendermintAuthority { .as_ref(), ) .map(|commit| commit.end_time) - .unwrap_or(fake_genesis), + .unwrap_or_else(|_| self.genesis.unwrap()), ), ) } diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index c70ce118e..a02eed204 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -157,7 +157,7 @@ impl TendermintImport { let commit: Commit> = Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; - if !TendermintAuthority::new(self.clone()).verify_commit(hash, &commit) { + if !TendermintAuthority::new(None, self.clone()).verify_commit(hash, &commit) { Err(Error::InvalidJustification)?; } Ok(()) From 1aefa3f06a3493b98d8a537369a07103b50d8561 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 16 Nov 2022 21:21:56 -0500 Subject: [PATCH 160/186] Correct block announcements They were announced, yet not marked best. --- substrate/tendermint/client/src/authority/mod.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index b19ff5293..b0756fd98 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -402,18 +402,22 @@ impl Network for TendermintAuthority { // Clear any blocks for the previous height we were willing to recheck *self.import.recheck.write().unwrap() = HashSet::new(); - let number: u64 = match (*block.header().number()).try_into() { + let raw_number = *block.header().number(); + let number: u64 = match raw_number.try_into() { Ok(number) => number, Err(_) => panic!("BlockNumber exceeded u64"), }; - if self.active.as_mut().unwrap().new_number.unbounded_send(number + 1).is_err() { + + let active = self.active.as_mut().unwrap(); + if active.new_number.unbounded_send(number + 1).is_err() { warn!( target: "tendermint", "Attempted to send a new number to the gossip handler except it's closed. {}", "Is the node shutting down?" ); } - self.active.as_ref().unwrap().announce.announce_block(hash, None); + active.announce.announce_block(hash, None); + active.announce.new_best_block_imported(hash, raw_number); self.get_proposal(block.header()).await } From 14fc181d10affc1946f1332252009a132b4669e2 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 16 Nov 2022 21:22:30 -0500 Subject: [PATCH 161/186] Correct pupulate_end_time It was used as inclusive yet didn't work inclusively. --- substrate/tendermint/machine/src/block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/tendermint/machine/src/block.rs b/substrate/tendermint/machine/src/block.rs index 3e7dc6a5d..bbf901f7a 100644 --- a/substrate/tendermint/machine/src/block.rs +++ b/substrate/tendermint/machine/src/block.rs @@ -59,7 +59,7 @@ impl BlockData { } pub(crate) fn populate_end_time(&mut self, round: RoundNumber) { - for r in (self.round().number.0 + 1) .. round.0 { + for r in (self.round().number.0 + 1) ..= round.0 { self.end_time.insert( RoundNumber(r), RoundData::::new(RoundNumber(r), self.end_time[&RoundNumber(r - 1)]).end_time(), From b077dc7727caec55be8122fa6ea5fce172204c50 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 16 Nov 2022 21:56:02 -0500 Subject: [PATCH 162/186] Correct gossip channel jumping when a block is synced via Substrate --- substrate/tendermint/client/src/authority/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index b0756fd98..e500ff7cb 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -226,7 +226,7 @@ impl TendermintAuthority { // Synced a block from the network notif = finality.next() => { if let Some(notif) = notif { - let new_number = match (*notif.header.number()).try_into() { + let mut new_number = match (*notif.header.number()).try_into() { Ok(number) => number, Err(_) => panic!("BlockNumber exceeded u64"), }; @@ -242,6 +242,7 @@ impl TendermintAuthority { get_proposal(&env, &import, ¬if.header, false).await )).await.unwrap(); + new_number += 1; *number.write().unwrap() = new_number; recv = gossip.messages_for(TendermintGossip::::topic(new_number)) } else { From 82da7ebea75cdac3a41e21f93ae3d8e4441e9328 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 16 Nov 2022 21:56:30 -0500 Subject: [PATCH 163/186] Use a looser check in import_future This triggered so it needs to be accordingly relaxed. --- substrate/tendermint/client/src/authority/import_future.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/tendermint/client/src/authority/import_future.rs b/substrate/tendermint/client/src/authority/import_future.rs index 9239bc29e..c8eda9ee0 100644 --- a/substrate/tendermint/client/src/authority/import_future.rs +++ b/substrate/tendermint/client/src/authority/import_future.rs @@ -28,7 +28,7 @@ impl Link for ValidateLink { B::Hash, )>, ) { - assert_eq!(imported, 1); + assert!(imported <= 1); assert_eq!(count, 1); self.0 = Some(( results[0].1, From 26ad7c1d5995b2c2fcaa53691ccb1ddfffa2970b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 16 Nov 2022 23:47:25 -0500 Subject: [PATCH 164/186] Correct race conditions between add_block and step Also corrects a <= to <. --- .../tendermint/client/src/authority/mod.rs | 92 ++++++++++++------- substrate/tendermint/machine/src/lib.rs | 15 ++- 2 files changed, 71 insertions(+), 36 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index e500ff7cb..c0b4db547 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -58,7 +58,8 @@ struct ActiveAuthority { signer: TendermintSigner, // Notification channel for when we start a new number - new_number: UnboundedSender, + new_number: Arc>, + new_number_event: UnboundedSender<()>, // Outgoing message queue, placed here as the GossipEngine itself can't be gossip: UnboundedSender< SignedMessage as SignatureScheme>::Signature>, @@ -196,7 +197,8 @@ impl TendermintAuthority { self.active = Some(ActiveAuthority { signer: TendermintSigner(keys, self.import.validators.clone()), - new_number: new_number_tx, + new_number: number.clone(), + new_number_event: new_number_tx, gossip: gossip_tx, env: env.clone(), @@ -226,25 +228,32 @@ impl TendermintAuthority { // Synced a block from the network notif = finality.next() => { if let Some(notif) = notif { - let mut new_number = match (*notif.header.number()).try_into() { + let this_number = match (*notif.header.number()).try_into() { Ok(number) => number, Err(_) => panic!("BlockNumber exceeded u64"), }; - if new_number <= *number.read().unwrap() { - continue; + + // There's a race condition between the machine add_block and this + // Both wait for a write lock on this number and don't release it until after updating + // it accordingly + { + let mut number = number.write().unwrap(); + if this_number < *number { + continue; + } + let new_number = this_number + 1; + *number = new_number; + recv = gossip.messages_for(TendermintGossip::::topic(new_number)); } let justifications = import.client.justifications(notif.hash).unwrap().unwrap(); step.send(( + BlockNumber(this_number), Commit::decode(&mut justifications.get(CONSENSUS_ID).unwrap().as_ref()).unwrap(), // This will fail if syncing occurs radically faster than machine stepping takes // TODO: Set true when initial syncing get_proposal(&env, &import, ¬if.header, false).await )).await.unwrap(); - - new_number += 1; - *number.write().unwrap() = new_number; - recv = gossip.messages_for(TendermintGossip::::topic(new_number)) } else { break; } @@ -252,9 +261,8 @@ impl TendermintAuthority { // Machine reached a new height new_number = new_number_rx.next() => { - if let Some(new_number) = new_number { - *number.write().unwrap() = new_number; - recv = gossip.messages_for(TendermintGossip::::topic(new_number)) + if new_number.is_some() { + recv = gossip.messages_for(TendermintGossip::::topic(*number.read().unwrap())); } else { break; } @@ -351,6 +359,11 @@ impl Network for TendermintAuthority { let parent = *header.parent_hash(); let number = *header.number(); + // Can happen when we sync a block while also acting as a validator + if number <= self.import.client.info().best_number { + Err(BlockError::Temporal)?; + } + let mut queue_write = self.import.queue.write().await; *self.import.importing_block.write().unwrap() = Some(hash); @@ -393,32 +406,47 @@ impl Network for TendermintAuthority { let justification = (CONSENSUS_ID, commit.encode()); debug_assert!(self.import.verify_justification(hash, &justification).is_ok()); - self - .import - .client - .finalize_block(hash, Some(justification), true) - .map_err(|_| Error::InvalidJustification) - .unwrap(); - - // Clear any blocks for the previous height we were willing to recheck - *self.import.recheck.write().unwrap() = HashSet::new(); - let raw_number = *block.header().number(); - let number: u64 = match raw_number.try_into() { + let this_number: u64 = match raw_number.try_into() { Ok(number) => number, Err(_) => panic!("BlockNumber exceeded u64"), }; + let new_number = this_number + 1; - let active = self.active.as_mut().unwrap(); - if active.new_number.unbounded_send(number + 1).is_err() { - warn!( - target: "tendermint", - "Attempted to send a new number to the gossip handler except it's closed. {}", - "Is the node shutting down?" - ); + { + let active = self.active.as_mut().unwrap(); + // Acquire the write lock + let mut number = active.new_number.write().unwrap(); + + // This block's number may not be the block we're working on if Substrate synced it before + // the machine synced the necessary precommits + if this_number == *number { + // If we are the party responsible for handling this block, finalize it + self + .import + .client + .finalize_block(hash, Some(justification), true) + .map_err(|_| Error::InvalidJustification) + .unwrap(); + + // Tell the loop there's a new number + *number = new_number; + if active.new_number_event.unbounded_send(()).is_err() { + warn!( + target: "tendermint", + "Attempted to send a new number to the gossip handler except it's closed. {}", + "Is the node shutting down?" + ); + } + } + + // Clear any blocks for the previous height we were willing to recheck + *self.import.recheck.write().unwrap() = HashSet::new(); + + // Announce the block to the network so new clients can sync properly + active.announce.announce_block(hash, None); + active.announce.new_best_block_imported(hash, raw_number); } - active.announce.announce_block(hash, None); - active.announce.new_best_block_imported(hash, raw_number); self.get_proposal(block.header()).await } diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index 1c64637a4..bb38b216f 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -131,13 +131,16 @@ pub struct TendermintMachine { queue: VecDeque>, msg_recv: mpsc::UnboundedReceiver>, - step_recv: mpsc::UnboundedReceiver<(Commit, N::Block)>, + step_recv: mpsc::UnboundedReceiver<(BlockNumber, Commit, N::Block)>, block: BlockData, } -pub type StepSender = - mpsc::UnboundedSender<(Commit<::SignatureScheme>, ::Block)>; +pub type StepSender = mpsc::UnboundedSender<( + BlockNumber, + Commit<::SignatureScheme>, + ::Block, +)>; pub type MessageSender = mpsc::UnboundedSender>; @@ -284,7 +287,11 @@ impl TendermintMachine { // Handle a new height occuring externally (an external sync loop) // Has the highest priority as it makes all other futures here irrelevant msg = self.step_recv.next() => { - if let Some((commit, proposal)) = msg { + if let Some((block_number, commit, proposal)) = msg { + // Commit is for a block we've already moved past + if block_number != self.block.number { + continue; + } self.reset_by_commit(commit, proposal).await; None } else { From 6e992a341ad90fb057cbe9165e3ed4f0645d41f5 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 17 Nov 2022 01:17:48 -0500 Subject: [PATCH 165/186] Update cargo deny --- deny.toml | 5 ++++- substrate/node/Cargo.toml | 2 +- substrate/tendermint/client/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/deny.toml b/deny.toml index cea6b938c..0909db187 100644 --- a/deny.toml +++ b/deny.toml @@ -45,8 +45,11 @@ exceptions = [ { allow = ["AGPL-3.0"], name = "serai-extension" }, { allow = ["AGPL-3.0"], name = "serai-multisig" }, + { allow = ["AGPL-3.0"], name = "sp-tendermint" }, + { allow = ["AGPL-3.0"], name = "pallet-tendermint" }, + { allow = ["AGPL-3.0"], name = "sc-tendermint" }, + { allow = ["AGPL-3.0"], name = "serai-runtime" }, - { allow = ["AGPL-3.0"], name = "serai-consensus" }, { allow = ["AGPL-3.0"], name = "serai-node" }, ] diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index 50394c7fe..22dcbbcfd 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -59,7 +59,7 @@ pallet-transaction-payment-rpc = { git = "https://github.com/serai-dex/substrate sp-tendermint = { path = "../tendermint/primitives" } pallet-tendermint = { path = "../tendermint/pallet", default-features = false } serai-runtime = { path = "../runtime" } -sc_tendermint = { path = "../tendermint/client" } +sc-tendermint = { path = "../tendermint/client" } [build-dependencies] substrate-build-script-utils = { git = "https://github.com/serai-dex/substrate.git" } diff --git a/substrate/tendermint/client/Cargo.toml b/substrate/tendermint/client/Cargo.toml index f20b22798..0698f6337 100644 --- a/substrate/tendermint/client/Cargo.toml +++ b/substrate/tendermint/client/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sc_tendermint" +name = "sc-tendermint" version = "0.1.0" description = "Tendermint client for Substrate" license = "AGPL-3.0-only" From 88aabde6a300b07c5806ff1f25e46476cb8adc04 Mon Sep 17 00:00:00 2001 From: TheArchitect108 Date: Thu, 17 Nov 2022 09:28:37 -0600 Subject: [PATCH 166/186] rename genesis-service to genesis --- deploy/docker-compose.yml | 14 +++++++------- deploy/{genesis-service => genesis}/Dockerfile | 0 deploy/{genesis-service => genesis}/entry-dev.sh | 0 3 files changed, 7 insertions(+), 7 deletions(-) rename deploy/{genesis-service => genesis}/Dockerfile (100%) rename deploy/{genesis-service => genesis}/entry-dev.sh (100%) mode change 100755 => 100644 diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index fbf828085..23cae9521 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -22,17 +22,17 @@ volumes: serai-dave: serai-eve: serai-ferdie: - genesis-service-volume: + genesis-volume: services: - genesis-service: - image: serai-genesis-service:dev + genesis: + image: genesis:dev build: - context: ./genesis-service/ + context: ./genesis/ dockerfile: Dockerfile entrypoint: /entry-dev.sh volumes: - - genesis-service-volume:/temp + - genesis-volume:/temp _serai: &serai_defaults @@ -48,9 +48,9 @@ services: entrypoint: /scripts/entry-dev.sh volumes: - "./serai/scripts:/scripts" - - genesis-service-volume:/temp + - genesis-volume:/temp depends_on: - - genesis-service + - genesis serai-base: <<: *serai_defaults diff --git a/deploy/genesis-service/Dockerfile b/deploy/genesis/Dockerfile similarity index 100% rename from deploy/genesis-service/Dockerfile rename to deploy/genesis/Dockerfile diff --git a/deploy/genesis-service/entry-dev.sh b/deploy/genesis/entry-dev.sh old mode 100755 new mode 100644 similarity index 100% rename from deploy/genesis-service/entry-dev.sh rename to deploy/genesis/entry-dev.sh From c7e97d9536c0e958c0e854bfef51f95c69ef7ce3 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 18 Nov 2022 01:09:53 -0500 Subject: [PATCH 167/186] Update Cargo.lock --- Cargo.lock | 66 +++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e373e6ea..dcd59cdbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7252,6 +7252,38 @@ dependencies = [ "wasm-timer", ] +[[package]] +name = "sc-tendermint" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures", + "hex", + "log", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-executor", + "sc-network", + "sc-network-common", + "sc-network-gossip", + "sc-service", + "sc-transaction-pool", + "sp-api", + "sp-application-crypto", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-inherents", + "sp-keystore", + "sp-runtime", + "sp-staking", + "sp-tendermint", + "substrate-prometheus-endpoint", + "tendermint-machine", + "tokio", +] + [[package]] name = "sc-tracing" version = "4.0.0-dev" @@ -7348,38 +7380,6 @@ dependencies = [ "prometheus", ] -[[package]] -name = "sc_tendermint" -version = "0.1.0" -dependencies = [ - "async-trait", - "futures", - "hex", - "log", - "sc-block-builder", - "sc-client-api", - "sc-consensus", - "sc-executor", - "sc-network", - "sc-network-common", - "sc-network-gossip", - "sc-service", - "sc-transaction-pool", - "sp-api", - "sp-application-crypto", - "sp-blockchain", - "sp-consensus", - "sp-core", - "sp-inherents", - "sp-keystore", - "sp-runtime", - "sp-staking", - "sp-tendermint", - "substrate-prometheus-endpoint", - "tendermint-machine", - "tokio", -] - [[package]] name = "scale-info" version = "2.3.0" @@ -7636,9 +7636,9 @@ dependencies = [ "sc-rpc-api", "sc-service", "sc-telemetry", + "sc-tendermint", "sc-transaction-pool", "sc-transaction-pool-api", - "sc_tendermint", "serai-runtime", "sp-api", "sp-application-crypto", From cbe79eb06548c3cb0d35df07f5a56a8dcfd67de7 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 19 Nov 2022 23:39:12 -0500 Subject: [PATCH 168/186] Correct runtime Cargo.toml whitespace --- substrate/runtime/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index 39ce6bb4a..bfa4b1727 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -81,10 +81,10 @@ std = [ "pallet-balances/std", "pallet-transaction-payment/std", - "pallet-contracts/std", - "pallet-contracts-primitives/std", + "pallet-contracts/std", + "pallet-contracts-primitives/std", - "pallet-session/std", + "pallet-session/std", "pallet-tendermint/std", "frame-system-rpc-runtime-api/std", From 10aac4a2dd608a5b5410a637ca5e43ca3abefd97 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 20 Nov 2022 00:15:14 -0500 Subject: [PATCH 169/186] Correct typo --- substrate/tendermint/client/src/tendermint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index a02eed204..17d6f14c0 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -164,7 +164,7 @@ impl TendermintImport { } // Verifies the justifications aren't malformed, not that the block is justified - // Errors if justifications is neither empty nor a sinlge Tendermint justification + // Errors if justifications is neither empty nor a single Tendermint justification // If the block does have a justification, finalized will be set to true fn verify_justifications( &self, From 9b51eafc5fb038348a3e67baa365ba7930252b0a Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 20 Nov 2022 19:13:40 -0500 Subject: [PATCH 170/186] Document recheck --- substrate/tendermint/client/src/tendermint.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 17d6f14c0..29f17be34 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -34,6 +34,12 @@ pub struct TendermintImport { pub(crate) providers: Arc>>, pub(crate) importing_block: Arc::Hash>>>, + + // A set of blocks which we're willing to recheck + // We reject blocks with invalid inherents, yet inherents can be fatally flawed or solely + // perceived as flawed + // If we solely perceive them as flawed, we mark them as eligible for being checked again. Then, + // if they're proposed again, we see if our perception has changed pub(crate) recheck: Arc::Hash>>>, pub(crate) client: Arc, From 9dfa22de238d82573cedada8b205e75e39f6211f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 20 Nov 2022 19:39:38 -0500 Subject: [PATCH 171/186] Misc lints --- .../tendermint/client/src/authority/mod.rs | 18 +++++++-------- substrate/tendermint/machine/src/block.rs | 2 +- substrate/tendermint/machine/src/ext.rs | 6 ++--- substrate/tendermint/machine/src/lib.rs | 22 ++++++++++--------- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index c0b4db547..15b48f2d8 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -181,8 +181,8 @@ impl TendermintAuthority { // This should only have a single value, yet a bounded channel with a capacity of 1 would cause // a firm bound. It's not worth having a backlog crash the node since we aren't constrained - let (new_number_tx, mut new_number_rx) = mpsc::unbounded(); - let (gossip_tx, mut gossip_rx) = mpsc::unbounded(); + let (new_number_send, mut new_number_recv) = mpsc::unbounded(); + let (gossip_send, mut gossip_recv) = mpsc::unbounded(); // Clone the import object let import = self.import.clone(); @@ -198,8 +198,8 @@ impl TendermintAuthority { signer: TendermintSigner(keys, self.import.validators.clone()), new_number: number.clone(), - new_number_event: new_number_tx, - gossip: gossip_tx, + new_number_event: new_number_send, + gossip: gossip_send, env: env.clone(), announce: network, @@ -259,8 +259,8 @@ impl TendermintAuthority { } }, - // Machine reached a new height - new_number = new_number_rx.next() => { + // Machine reached a new block + new_number = new_number_recv.next() => { if new_number.is_some() { recv = gossip.messages_for(TendermintGossip::::topic(*number.read().unwrap())); } else { @@ -269,7 +269,7 @@ impl TendermintAuthority { }, // Message to broadcast - msg = gossip_rx.next() => { + msg = gossip_recv.next() => { if let Some(msg) = msg { let topic = TendermintGossip::::topic(msg.number().0); gossip.gossip_message(topic, msg.encode(), false); @@ -375,7 +375,7 @@ impl Network for TendermintAuthority { body: Some(body), indexed_body: None, justifications: None, - origin: None, + origin: None, // TODO allow_missing_state: false, skip_execution: false, import_existing: self.import.recheck.read().unwrap().contains(&hash), @@ -440,7 +440,7 @@ impl Network for TendermintAuthority { } } - // Clear any blocks for the previous height we were willing to recheck + // Clear any blocks for the previous slot which we were willing to recheck *self.import.recheck.write().unwrap() = HashSet::new(); // Announce the block to the network so new clients can sync properly diff --git a/substrate/tendermint/machine/src/block.rs b/substrate/tendermint/machine/src/block.rs index bbf901f7a..4c5487c19 100644 --- a/substrate/tendermint/machine/src/block.rs +++ b/substrate/tendermint/machine/src/block.rs @@ -119,7 +119,7 @@ impl BlockData { // Only return a message to if we're actually a current validator self.validator_id.map(|validator_id| Message { sender: validator_id, - number: self.number, + block: self.number, round: self.round().number, data, }) diff --git a/substrate/tendermint/machine/src/ext.rs b/substrate/tendermint/machine/src/ext.rs index b7295f2cb..d14e770ee 100644 --- a/substrate/tendermint/machine/src/ext.rs +++ b/substrate/tendermint/machine/src/ext.rs @@ -147,7 +147,7 @@ pub trait Weights: Send + Sync { } /// Weighted round robin function. - fn proposer(&self, number: BlockNumber, round: RoundNumber) -> Self::ValidatorId; + fn proposer(&self, block: BlockNumber, round: RoundNumber) -> Self::ValidatorId; } impl Weights for Arc { @@ -161,8 +161,8 @@ impl Weights for Arc { self.as_ref().weight(validator) } - fn proposer(&self, number: BlockNumber, round: RoundNumber) -> Self::ValidatorId { - self.as_ref().proposer(number, round) + fn proposer(&self, block: BlockNumber, round: RoundNumber) -> Self::ValidatorId { + self.as_ref().proposer(block, round) } } diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index bb38b216f..ff6e2ee02 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -50,10 +50,12 @@ enum Data { impl PartialEq for Data { fn eq(&self, other: &Data) -> bool { match (self, other) { - (Data::Proposal(r, b), Data::Proposal(r2, b2)) => (r == r2) && (b == b2), - (Data::Prevote(i), Data::Prevote(i2)) => i == i2, + (Data::Proposal(valid_round, block), Data::Proposal(valid_round2, block2)) => { + (valid_round == valid_round2) && (block == block2) + } + (Data::Prevote(id), Data::Prevote(id2)) => id == id2, (Data::Precommit(None), Data::Precommit(None)) => true, - (Data::Precommit(Some((i, _))), Data::Precommit(Some((i2, _)))) => i == i2, + (Data::Precommit(Some((id, _))), Data::Precommit(Some((id2, _)))) => id == id2, _ => false, } } @@ -73,7 +75,7 @@ impl Data { struct Message { sender: V, - number: BlockNumber, + block: BlockNumber, round: RoundNumber, data: Data, @@ -88,8 +90,8 @@ pub struct SignedMessage { impl SignedMessage { /// Number of the block this message is attempting to add to the chain. - pub fn number(&self) -> BlockNumber { - self.msg.number + pub fn block(&self) -> BlockNumber { + self.msg.block } #[must_use] @@ -146,7 +148,7 @@ pub type MessageSender = mpsc::UnboundedSender>; /// A Tendermint machine and its channel to receive messages from the gossip layer over. pub struct TendermintHandle { - /// Channel to trigger the machine to move to the next height. + /// Channel to trigger the machine to move to the next block. /// Takes in the the previous block's commit, along with the new proposal. pub step: StepSender, /// Channel to send messages received from the P2P layer. @@ -284,7 +286,7 @@ impl TendermintMachine { if self.queue.is_empty() { Fuse::terminated() } else { future::ready(()).fuse() }; if let Some((broadcast, msg)) = futures::select_biased! { - // Handle a new height occuring externally (an external sync loop) + // Handle a new block occuring externally (an external sync loop) // Has the highest priority as it makes all other futures here irrelevant msg = self.step_recv.next() => { if let Some((block_number, commit, proposal)) = msg { @@ -418,7 +420,7 @@ impl TendermintMachine { &mut self, msg: MessageFor, ) -> Result, TendermintError> { - if msg.number != self.block.number { + if msg.block != self.block.number { Err(TendermintError::Temporal)?; } @@ -427,7 +429,7 @@ impl TendermintMachine { // Only let the proposer propose if matches!(msg.data, Data::Proposal(..)) && - (msg.sender != self.weights.proposer(msg.number, msg.round)) + (msg.sender != self.weights.proposer(msg.block, msg.round)) { Err(TendermintError::Malicious(msg.sender))?; }; From bfe7546064739fa6f489581e6e37a309bdfc6c1b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 20 Nov 2022 22:07:30 -0500 Subject: [PATCH 172/186] Fix prev commit --- substrate/tendermint/client/src/authority/gossip.rs | 4 ++-- substrate/tendermint/client/src/authority/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/substrate/tendermint/client/src/authority/gossip.rs b/substrate/tendermint/client/src/authority/gossip.rs index e7317031a..42e465662 100644 --- a/substrate/tendermint/client/src/authority/gossip.rs +++ b/substrate/tendermint/client/src/authority/gossip.rs @@ -45,7 +45,7 @@ impl Validator for TendermintGossip { Err(_) => return ValidationResult::Discard, }; - if msg.number().0 < *self.number.read().unwrap() { + if msg.block().0 < *self.number.read().unwrap() { return ValidationResult::Discard; } @@ -55,7 +55,7 @@ impl Validator for TendermintGossip { return ValidationResult::Discard; } - ValidationResult::ProcessAndKeep(Self::topic(msg.number().0)) + ValidationResult::ProcessAndKeep(Self::topic(msg.block().0)) } fn message_expired<'a>( diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 15b48f2d8..8e4c97f75 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -271,7 +271,7 @@ impl TendermintAuthority { // Message to broadcast msg = gossip_recv.next() => { if let Some(msg) = msg { - let topic = TendermintGossip::::topic(msg.number().0); + let topic = TendermintGossip::::topic(msg.block().0); gossip.gossip_message(topic, msg.encode(), false); } else { break; From cd9b9c899110af48670ecd0b775055f47da1b49e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 21 Nov 2022 05:21:38 -0500 Subject: [PATCH 173/186] Resolve low-hanging review comments --- substrate/node/src/service.rs | 6 +- .../tendermint/client/src/authority/mod.rs | 59 ++++++++----------- .../tendermint/client/src/block_import.rs | 6 +- substrate/tendermint/client/src/validators.rs | 9 +-- substrate/tendermint/machine/src/lib.rs | 24 ++++---- substrate/tendermint/machine/tests/ext.rs | 3 +- substrate/tendermint/pallet/src/lib.rs | 2 + 7 files changed, 46 insertions(+), 63 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 3dbcdf571..76b7a54d4 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -183,10 +183,8 @@ pub async fn new_full(mut config: Configuration) -> Result TendermintAuthority { } } - fn get_last(&self) -> (::Hash, (BlockNumber, u64)) { - let info = self.import.client.info(); - - ( - info.finalized_hash, - ( - // Header::Number: TryInto doesn't implement Debug and can't be unwrapped - match info.finalized_number.try_into() { - Ok(best) => BlockNumber(best), - Err(_) => panic!("BlockNumber exceeded u64"), - }, - // Get the last time by grabbing the last block's justification and reading the time from - // that - Commit::>::decode( - &mut self - .import - .client - .justifications(info.finalized_hash) - .unwrap() - .map(|justifications| justifications.get(CONSENSUS_ID).cloned().unwrap()) - .unwrap_or_default() - .as_ref(), - ) - .map(|commit| commit.end_time) - .unwrap_or_else(|_| self.genesis.unwrap()), - ), - ) - } - async fn get_proposal(&mut self, header: &::Header) -> T::Block { get_proposal(&self.active.as_mut().unwrap().env, &self.import, header, false).await } @@ -164,8 +135,30 @@ impl TendermintAuthority { network: T::Network, registry: Option<&Registry>, ) { - let (best_hash, last) = self.get_last(); - let new_number = last.0 .0 + 1; + let info = self.import.client.info(); + + // Header::Number: TryInto doesn't implement Debug and can't be unwrapped + let last_block: u64 = match info.finalized_number.try_into() { + Ok(best) => best, + Err(_) => panic!("BlockNumber exceeded u64"), + }; + let last_hash = info.finalized_hash; + + // Get the last block's time by grabbing its commit and reading the time from that + let last_time = Commit::>::decode( + &mut self + .import + .client + .justifications(last_hash) + .unwrap() + .map(|justifications| justifications.get(CONSENSUS_ID).cloned().unwrap()) + .unwrap_or_default() + .as_ref(), + ) + .map(|commit| commit.end_time) + .unwrap_or_else(|_| self.genesis.unwrap()); + + let new_number = last_block + 1; // Shared references between us and the Tendermint machine (and its actions via its Network // trait) @@ -206,11 +199,11 @@ impl TendermintAuthority { }); let proposal = self - .get_proposal(&self.import.client.header(BlockId::Hash(best_hash)).unwrap().unwrap()) + .get_proposal(&self.import.client.header(BlockId::Hash(last_hash)).unwrap().unwrap()) .await; // We no longer need self, so let TendermintMachine become its owner - TendermintMachine::new(self, last, proposal).await + TendermintMachine::new(self, BlockNumber(last_block), last_time, proposal).await }; spawner.spawn_essential("machine", Some("tendermint"), Box::pin(machine.run())); diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index 9edf5be07..f679fba4b 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -21,12 +21,8 @@ impl TendermintImport { // justifications // This can be triggered if the validators add a block, without justifications, yet the p2p // process then broadcasts it with its justifications - if (self.client.status(id).unwrap() == BlockStatus::InChain) && + (self.client.status(id).unwrap() == BlockStatus::InChain) && self.client.justifications(hash).unwrap().is_some() - { - return true; - } - false } } diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index c02a61f03..2b38753fb 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -59,13 +59,8 @@ impl Refresh { // If the session has changed, re-create the struct with the data on it fn refresh(&self) { let session = self._refresh.read().unwrap().session; - if session != - self - .client - .runtime_api() - .current_session(&BlockId::Hash(self.client.info().finalized_hash)) - .unwrap() - { + let current_block = BlockId::Hash(self.client.info().finalized_hash); + if session != self.client.runtime_api().current_session(¤t_block).unwrap() { *self._refresh.write().unwrap() = TendermintValidatorsStruct::from_module::(&self.client); } } diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index ff6e2ee02..a9d6bced2 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -232,7 +232,8 @@ impl TendermintMachine { #[allow(clippy::new_ret_no_self)] pub async fn new( network: N, - last: (BlockNumber, u64), + last_block: BlockNumber, + last_time: u64, proposal: N::Block, ) -> TendermintHandle { let (msg_send, msg_recv) = mpsc::unbounded(); @@ -241,9 +242,9 @@ impl TendermintMachine { step: step_send, messages: msg_send, machine: { - let last_time = sys_time(last.1); + let sys_time = sys_time(last_time); // If the last block hasn't ended yet, sleep until it has - sleep(last_time.duration_since(SystemTime::now()).unwrap_or(Duration::ZERO)).await; + sleep(sys_time.duration_since(SystemTime::now()).unwrap_or(Duration::ZERO)).await; let signer = network.signer(); let validators = network.signature_scheme(); @@ -260,7 +261,7 @@ impl TendermintMachine { msg_recv, step_recv, - block: BlockData::new(weights, BlockNumber(last.0 .0 + 1), validator_id, proposal), + block: BlockData::new(weights, BlockNumber(last_block.0 + 1), validator_id, proposal), }; // The end time of the last block is the start time for this one @@ -270,7 +271,7 @@ impl TendermintMachine { // after it, without the standard amount of separation (so their times will be // equivalent or minimally offset) // For callers wishing to avoid this, they should pass (0, GENESIS + N::block_time()) - machine.round(RoundNumber(0), Some(CanonicalInstant::new(last.1))); + machine.round(RoundNumber(0), Some(CanonicalInstant::new(last_time))); machine }, } @@ -398,22 +399,19 @@ impl TendermintMachine { round: RoundNumber, data: &DataFor, ) -> Result> { - Ok(if let Data::Precommit(Some((id, sig))) = data { + if let Data::Precommit(Some((id, sig))) = data { // Also verify the end_time of the commit // Only perform this verification if we already have the end_time // Else, there's a DoS where we receive a precommit for some round infinitely in the future - // which forces to calculate every end time + // which forces us to calculate every end time if let Some(end_time) = self.block.end_time.get(&round) { if !self.validators.verify(sender, &commit_msg(end_time.canonical(), id.as_ref()), sig) { Err(TendermintError::Malicious(sender))?; } - true - } else { - false + return Ok(true); } - } else { - false - }) + } + Ok(false) } async fn message( diff --git a/substrate/tendermint/machine/tests/ext.rs b/substrate/tendermint/machine/tests/ext.rs index 086c96d8e..fc6df5816 100644 --- a/substrate/tendermint/machine/tests/ext.rs +++ b/substrate/tendermint/machine/tests/ext.rs @@ -156,7 +156,8 @@ impl TestNetwork { let i = u16::try_from(i).unwrap(); let TendermintHandle { messages, machine, step } = TendermintMachine::new( TestNetwork(i, arc.clone()), - (BlockNumber(1), (SystemTime::now().duration_since(UNIX_EPOCH)).unwrap().as_secs()), + BlockNumber(1), + SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), TestBlock { id: 1u32.to_le_bytes(), valid: Ok(()) }, ) .await; diff --git a/substrate/tendermint/pallet/src/lib.rs b/substrate/tendermint/pallet/src/lib.rs index f8b92db85..54d86fe18 100644 --- a/substrate/tendermint/pallet/src/lib.rs +++ b/substrate/tendermint/pallet/src/lib.rs @@ -43,6 +43,7 @@ pub mod pallet { impl OneSessionHandler for Pallet { type Key = crypto::Public; + // TODO fn on_genesis_session<'a, I: 'a>(_validators: I) where I: Iterator, @@ -66,6 +67,7 @@ pub mod pallet { ); } + // TODO fn on_disabled(_validator_index: u32) {} } } From 92760c087d37ed8ebd421356a42ce1a383a87001 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 22 Nov 2022 01:07:50 -0500 Subject: [PATCH 174/186] Mark genesis/entry-dev.sh as executable --- deploy/genesis/entry-dev.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 deploy/genesis/entry-dev.sh diff --git a/deploy/genesis/entry-dev.sh b/deploy/genesis/entry-dev.sh old mode 100644 new mode 100755 From b042a2a34429d1489f64b23ec7d7f42ded29c556 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 23 Nov 2022 08:01:28 -0500 Subject: [PATCH 175/186] Prevent a commit from including the same signature multiple times Yanks tendermint-machine 0.1.0 accordingly. --- substrate/tendermint/machine/src/ext.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/substrate/tendermint/machine/src/ext.rs b/substrate/tendermint/machine/src/ext.rs index d14e770ee..daa684c37 100644 --- a/substrate/tendermint/machine/src/ext.rs +++ b/substrate/tendermint/machine/src/ext.rs @@ -1,5 +1,5 @@ use core::{hash::Hash, fmt::Debug}; -use std::sync::Arc; +use std::{sync::Arc, collections::HashSet}; use async_trait::async_trait; use thiserror::Error; @@ -233,6 +233,10 @@ pub trait Network: Send + Sync { id: ::Id, commit: &Commit, ) -> bool { + if commit.validators.iter().collect::>().len() != commit.validators.len() { + return false; + } + if !self.signature_scheme().verify_aggregate( &commit.validators, &commit_msg(commit.end_time, id.as_ref()), From 14ce9ef020c4253f8e98ef64d6af24efc867d115 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 23 Nov 2022 08:02:04 -0500 Subject: [PATCH 176/186] Update to latest nightly clippy --- substrate/tendermint/machine/src/time.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/substrate/tendermint/machine/src/time.rs b/substrate/tendermint/machine/src/time.rs index b6bd1c183..3973b1474 100644 --- a/substrate/tendermint/machine/src/time.rs +++ b/substrate/tendermint/machine/src/time.rs @@ -21,7 +21,8 @@ impl CanonicalInstant { // If the time is in the future, this will be off by that much time let elapsed = sys_now.duration_since(sys_time(time)).unwrap_or(Duration::ZERO); - let synced_instant = instant_now - elapsed; + // Except for the fact this panics here + let synced_instant = instant_now.checked_sub(elapsed).unwrap(); CanonicalInstant { time, instant: synced_instant } } From 3d20afd2bc37bf2aeb983e3f0b8ff7a3ee20e24a Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 24 Nov 2022 01:19:47 -0500 Subject: [PATCH 177/186] Improve documentation --- .../tendermint/client/src/authority/mod.rs | 8 +++++++- substrate/tendermint/client/src/validators.rs | 2 +- substrate/tendermint/machine/README.md | 14 +++----------- substrate/tendermint/machine/src/block.rs | 18 +++++++++++++++++- substrate/tendermint/machine/src/lib.rs | 7 +++++++ substrate/tendermint/machine/src/round.rs | 1 + 6 files changed, 36 insertions(+), 14 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index bdbe14712..0cf36940c 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -77,6 +77,12 @@ pub struct TendermintAuthority { active: Option>, } +// Get a block to propose after the specified header +// If stub is true, no time will be spent adding transactions to it (beyond what's required), +// making it as minimal as possible (a stub) +// This is so we can create proposals when syncing, respecting tendermint-machine's API boundaries, +// without spending the entire block processing time trying to include transactions (since we know +// our proposal is meaningless and we'll just be syncing a new block anyways) async fn get_proposal( env: &Arc>, import: &TendermintImport, @@ -93,7 +99,7 @@ async fn get_proposal( if stub { Duration::ZERO } else { - // The first processing time is to build the block. + // The first processing time is to build the block // The second is for it to be downloaded (assumes a block won't take longer to download // than it'll take to process) // The third is for it to actually be processed diff --git a/substrate/tendermint/client/src/validators.rs b/substrate/tendermint/client/src/validators.rs index 2b38753fb..d60f179ec 100644 --- a/substrate/tendermint/client/src/validators.rs +++ b/substrate/tendermint/client/src/validators.rs @@ -180,7 +180,7 @@ impl Weights for TendermintValidators { self.0.read().unwrap().weights[usize::try_from(id).unwrap()] } - // TODO + // TODO: https://github.com/serai-dex/serai/issues/159 fn proposer(&self, number: BlockNumber, round: RoundNumber) -> u16 { u16::try_from( (number.0 + u64::from(round.0)) % u64::try_from(self.0.read().unwrap().lookup.len()).unwrap(), diff --git a/substrate/tendermint/machine/README.md b/substrate/tendermint/machine/README.md index 0135adbb0..ac497bb9d 100644 --- a/substrate/tendermint/machine/README.md +++ b/substrate/tendermint/machine/README.md @@ -35,17 +35,9 @@ implementation of the [academic protocol](https://arxiv.org/pdf/1807.04938.pdf). ### Paper The [paper](https://arxiv.org/abs/1807.04938) describes the algorithm with -pseudocode on page 6. This pseudocode is written as a series of conditions for -advancement. This is extremely archaic, as its a fraction of the actually -required code. This is due to its hand-waving away of data tracking, lack of -comments (beyond the entire rest of the paper, of course), and lack of -specification regarding faulty nodes. - -While the "hand-waving" is both legitimate and expected, as it's not the paper's -job to describe a full message processing loop nor efficient variable handling, -it does leave behind ambiguities and annoyances, not to mention an overall -structure which cannot be directly translated. This section is meant to be a -description of it as used for translation. +pseudocode on page 6. This pseudocode isn't directly implementable, nor does it +specify faulty behavior. Instead, it's solely a series of conditions which +trigger events in order to successfully achieve consensus. The included pseudocode segments can be minimally described as follows: diff --git a/substrate/tendermint/machine/src/block.rs b/substrate/tendermint/machine/src/block.rs index 4c5487c19..92923ae9d 100644 --- a/substrate/tendermint/machine/src/block.rs +++ b/substrate/tendermint/machine/src/block.rs @@ -18,6 +18,10 @@ pub(crate) struct BlockData { pub(crate) log: MessageLog, pub(crate) slashes: HashSet, + // We track the end times of each round for two reasons: + // 1) Knowing the start time of the next round + // 2) Validating precommits, which include the end time of the round which produced it + // This HashMap contains the end time of the round we're currently in and every round prior pub(crate) end_time: HashMap, pub(crate) round: Option>, @@ -58,7 +62,12 @@ impl BlockData { self.round.as_mut().unwrap() } + // Populate the end time up to the specified round + // This is generally used when moving to the next round, where this will only populate one time, + // yet is also used when jumping rounds (when 33% of the validators are on a round ahead of us) pub(crate) fn populate_end_time(&mut self, round: RoundNumber) { + // Starts from the current round since we only start the current round once we have have all + // the prior time data for r in (self.round().number.0 + 1) ..= round.0 { self.end_time.insert( RoundNumber(r), @@ -78,7 +87,12 @@ impl BlockData { ) -> Option> { debug_assert_eq!(round.0 == 0, time.is_some()); - // If skipping rounds, populate end_time + // If this is the first round, we don't have a prior round's end time to use as the start + // We use the passed in time instead + // If this isn't the first round, ensure we have the prior round's end time by populating the + // map with all rounds till this round + // This can happen we jump from round x to round x+n, where n != 1 + // The paper says to do so whenever you observe a sufficient amount of peers on a higher round if round.0 != 0 { self.populate_end_time(round); } @@ -113,6 +127,8 @@ impl BlockData { Step::Precommit => Step::Prevote, }, ); + // Tendermint always sets the round's step to whatever it just broadcasted + // Consolidate all of those here to ensure they aren't missed by an oversight // 27, 33, 41, 46, 60, 64 self.round_mut().step = data.step(); diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index a9d6bced2..ac1070ff4 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -158,8 +158,15 @@ pub struct TendermintHandle { } impl TendermintMachine { + // Broadcast the given piece of data + // Tendermint messages always specify their block/round, yet Tendermint only ever broadcasts for + // the current block/round. Accordingly, instead of manually fetching those at every call-site, + // this function can simply pass the data to the block which can contextualize it fn broadcast(&mut self, data: DataFor) { if let Some(msg) = self.block.message(data) { + // Push it on to the queue. This is done so we only handle one message at a time, and so we + // can handle our own message before broadcasting it. That way, we fail before before + // becoming malicious self.queue.push_back(msg); } } diff --git a/substrate/tendermint/machine/src/round.rs b/substrate/tendermint/machine/src/round.rs index 3224b9a36..18cc3c55e 100644 --- a/substrate/tendermint/machine/src/round.rs +++ b/substrate/tendermint/machine/src/round.rs @@ -55,6 +55,7 @@ impl RoundData { self.timeouts.entry(step).or_insert(timeout); } + // Poll all set timeouts, returning the Step whose timeout has just expired pub(crate) async fn timeout_future(&self) -> Step { let timeout_future = |step| { let timeout = self.timeouts.get(&step).copied(); From 1d45e0397bae8f6359faa079cfa1f267acef80d6 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 24 Nov 2022 02:53:56 -0500 Subject: [PATCH 178/186] Use clearer variable names --- .../tendermint/client/src/authority/mod.rs | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 0cf36940c..04e2d6158 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -57,9 +57,10 @@ use import_future::ImportFuture; struct ActiveAuthority { signer: TendermintSigner, - // Notification channel for when we start a new number - new_number: Arc>, - new_number_event: UnboundedSender<()>, + // The number of the Block we're working on producing + block_in_progress: Arc>, + // Notification channel for when we start on a new block + new_block_event: UnboundedSender<()>, // Outgoing message queue, placed here as the GossipEngine itself can't be gossip: UnboundedSender< SignedMessage as SignatureScheme>::Signature>, @@ -164,23 +165,23 @@ impl TendermintAuthority { .map(|commit| commit.end_time) .unwrap_or_else(|_| self.genesis.unwrap()); - let new_number = last_block + 1; + let next_block = last_block + 1; // Shared references between us and the Tendermint machine (and its actions via its Network // trait) - let number = Arc::new(RwLock::new(new_number)); + let block_in_progress = Arc::new(RwLock::new(next_block)); // Create the gossip network let mut gossip = GossipEngine::new( network.clone(), protocol, - Arc::new(TendermintGossip::new(number.clone(), self.import.validators.clone())), + Arc::new(TendermintGossip::new(block_in_progress.clone(), self.import.validators.clone())), registry, ); // This should only have a single value, yet a bounded channel with a capacity of 1 would cause // a firm bound. It's not worth having a backlog crash the node since we aren't constrained - let (new_number_send, mut new_number_recv) = mpsc::unbounded(); + let (new_block_event_send, mut new_block_event_recv) = mpsc::unbounded(); let (gossip_send, mut gossip_recv) = mpsc::unbounded(); // Clone the import object @@ -196,8 +197,8 @@ impl TendermintAuthority { self.active = Some(ActiveAuthority { signer: TendermintSigner(keys, self.import.validators.clone()), - new_number: number.clone(), - new_number_event: new_number_send, + block_in_progress: block_in_progress.clone(), + new_block_event: new_block_event_send, gossip: gossip_send, env: env.clone(), @@ -214,7 +215,7 @@ impl TendermintAuthority { spawner.spawn_essential("machine", Some("tendermint"), Box::pin(machine.run())); // Start receiving messages about the Tendermint process for this block - let mut recv = gossip.messages_for(TendermintGossip::::topic(new_number)); + let mut recv = gossip.messages_for(TendermintGossip::::topic(next_block)); // Get finality events from Substrate let mut finality = import.client.finality_notification_stream(); @@ -227,27 +228,27 @@ impl TendermintAuthority { // Synced a block from the network notif = finality.next() => { if let Some(notif) = notif { - let this_number = match (*notif.header.number()).try_into() { + let number = match (*notif.header.number()).try_into() { Ok(number) => number, Err(_) => panic!("BlockNumber exceeded u64"), }; // There's a race condition between the machine add_block and this - // Both wait for a write lock on this number and don't release it until after updating - // it accordingly + // Both wait for a write lock on this ref and don't release it until after updating it + // accordingly { - let mut number = number.write().unwrap(); - if this_number < *number { + let mut block_in_progress = block_in_progress.write().unwrap(); + if number < *block_in_progress { continue; } - let new_number = this_number + 1; - *number = new_number; - recv = gossip.messages_for(TendermintGossip::::topic(new_number)); + let next_block = number + 1; + *block_in_progress = next_block; + recv = gossip.messages_for(TendermintGossip::::topic(next_block)); } let justifications = import.client.justifications(notif.hash).unwrap().unwrap(); step.send(( - BlockNumber(this_number), + BlockNumber(number), Commit::decode(&mut justifications.get(CONSENSUS_ID).unwrap().as_ref()).unwrap(), // This will fail if syncing occurs radically faster than machine stepping takes // TODO: Set true when initial syncing @@ -258,10 +259,12 @@ impl TendermintAuthority { } }, - // Machine reached a new block - new_number = new_number_recv.next() => { - if new_number.is_some() { - recv = gossip.messages_for(TendermintGossip::::topic(*number.read().unwrap())); + // Machine accomplished a new block + new_block = new_block_event_recv.next() => { + if new_block.is_some() { + recv = gossip.messages_for( + TendermintGossip::::topic(*block_in_progress.read().unwrap()) + ); } else { break; } @@ -406,20 +409,19 @@ impl Network for TendermintAuthority { debug_assert!(self.import.verify_justification(hash, &justification).is_ok()); let raw_number = *block.header().number(); - let this_number: u64 = match raw_number.try_into() { + let number: u64 = match raw_number.try_into() { Ok(number) => number, Err(_) => panic!("BlockNumber exceeded u64"), }; - let new_number = this_number + 1; { let active = self.active.as_mut().unwrap(); // Acquire the write lock - let mut number = active.new_number.write().unwrap(); + let mut block_in_progress = active.block_in_progress.write().unwrap(); - // This block's number may not be the block we're working on if Substrate synced it before + // This block's number may not be the block we were working on if Substrate synced it before // the machine synced the necessary precommits - if this_number == *number { + if number == *block_in_progress { // If we are the party responsible for handling this block, finalize it self .import @@ -428,9 +430,9 @@ impl Network for TendermintAuthority { .map_err(|_| Error::InvalidJustification) .unwrap(); - // Tell the loop there's a new number - *number = new_number; - if active.new_number_event.unbounded_send(()).is_err() { + // Tell the loop we received a block and to move to the next + *block_in_progress = number + 1; + if active.new_block_event.unbounded_send(()).is_err() { warn!( target: "tendermint", "Attempted to send a new number to the gossip handler except it's closed. {}", From 32129420c0079036a352c8c68d7ac87cd9694f1b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 24 Nov 2022 03:40:11 -0500 Subject: [PATCH 179/186] Add log statements --- Cargo.lock | 1 + .../tendermint/client/src/authority/mod.rs | 47 +++++++++++++++---- substrate/tendermint/machine/Cargo.toml | 2 + substrate/tendermint/machine/src/lib.rs | 21 +++++++-- .../tendermint/machine/src/message_log.rs | 10 +++- 5 files changed, 66 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f08645b44..76ffe271b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8923,6 +8923,7 @@ version = "0.1.0" dependencies = [ "async-trait", "futures", + "log", "parity-scale-codec", "sp-runtime", "thiserror", diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 04e2d6158..cc1b483da 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -6,7 +6,7 @@ use std::{ use async_trait::async_trait; -use log::{warn, error}; +use log::{debug, warn, error}; use futures::{ SinkExt, StreamExt, @@ -182,7 +182,7 @@ impl TendermintAuthority { // This should only have a single value, yet a bounded channel with a capacity of 1 would cause // a firm bound. It's not worth having a backlog crash the node since we aren't constrained let (new_block_event_send, mut new_block_event_recv) = mpsc::unbounded(); - let (gossip_send, mut gossip_recv) = mpsc::unbounded(); + let (msg_send, mut msg_recv) = mpsc::unbounded(); // Clone the import object let import = self.import.clone(); @@ -199,7 +199,7 @@ impl TendermintAuthority { block_in_progress: block_in_progress.clone(), new_block_event: new_block_event_send, - gossip: gossip_send, + gossip: msg_send, env: env.clone(), announce: network, @@ -215,7 +215,7 @@ impl TendermintAuthority { spawner.spawn_essential("machine", Some("tendermint"), Box::pin(machine.run())); // Start receiving messages about the Tendermint process for this block - let mut recv = gossip.messages_for(TendermintGossip::::topic(next_block)); + let mut gossip_recv = gossip.messages_for(TendermintGossip::::topic(next_block)); // Get finality events from Substrate let mut finality = import.client.finality_notification_stream(); @@ -223,7 +223,14 @@ impl TendermintAuthority { loop { futures::select_biased! { // GossipEngine closed down - _ = gossip => break, + _ = gossip => { + debug!( + target: "tendermint", + "GossipEngine shut down. {}", + "Is the node shutting down?" + ); + break + }, // Synced a block from the network notif = finality.next() => { @@ -243,7 +250,7 @@ impl TendermintAuthority { } let next_block = number + 1; *block_in_progress = next_block; - recv = gossip.messages_for(TendermintGossip::::topic(next_block)); + gossip_recv = gossip.messages_for(TendermintGossip::::topic(next_block)); } let justifications = import.client.justifications(notif.hash).unwrap().unwrap(); @@ -255,6 +262,11 @@ impl TendermintAuthority { get_proposal(&env, &import, ¬if.header, false).await )).await.unwrap(); } else { + debug!( + target: "tendermint", + "Finality notification stream closed down. {}", + "Is the node shutting down?" + ); break; } }, @@ -262,26 +274,36 @@ impl TendermintAuthority { // Machine accomplished a new block new_block = new_block_event_recv.next() => { if new_block.is_some() { - recv = gossip.messages_for( + gossip_recv = gossip.messages_for( TendermintGossip::::topic(*block_in_progress.read().unwrap()) ); } else { + debug!( + target: "tendermint", + "Block notification stream shut down. {}", + "Is the node shutting down?" + ); break; } }, // Message to broadcast - msg = gossip_recv.next() => { + msg = msg_recv.next() => { if let Some(msg) = msg { let topic = TendermintGossip::::topic(msg.block().0); gossip.gossip_message(topic, msg.encode(), false); } else { + debug!( + target: "tendermint", + "Machine's message channel shut down. {}", + "Is the node shutting down?" + ); break; } }, // Received a message - msg = recv.next() => { + msg = gossip_recv.next() => { if let Some(msg) = msg { messages.send( match SignedMessage::decode(&mut msg.message.as_ref()) { @@ -295,6 +317,11 @@ impl TendermintAuthority { } ).await.unwrap(); } else { + debug!( + target: "tendermint", + "Gossip channel shut down. {}", + "Is the node shutting down?" + ); break; } } @@ -439,6 +466,8 @@ impl Network for TendermintAuthority { "Is the node shutting down?" ); } + } else { + debug!(target: "tendermint", "Machine produced a commit after we synced it"); } // Clear any blocks for the previous slot which we were willing to recheck diff --git a/substrate/tendermint/machine/Cargo.toml b/substrate/tendermint/machine/Cargo.toml index 32b6add68..e918a88f1 100644 --- a/substrate/tendermint/machine/Cargo.toml +++ b/substrate/tendermint/machine/Cargo.toml @@ -11,6 +11,8 @@ edition = "2021" async-trait = "0.1" thiserror = "1" +log = "0.4" + parity-scale-codec = { version = "3.2", features = ["derive"] } futures = "0.3" diff --git a/substrate/tendermint/machine/src/lib.rs b/substrate/tendermint/machine/src/lib.rs index ac1070ff4..487f4cbf3 100644 --- a/substrate/tendermint/machine/src/lib.rs +++ b/substrate/tendermint/machine/src/lib.rs @@ -6,6 +6,8 @@ use std::{ collections::VecDeque, }; +use log::debug; + use parity_scale_codec::{Encode, Decode}; use futures::{ @@ -228,6 +230,7 @@ impl TendermintMachine { async fn slash(&mut self, validator: N::ValidatorId) { if !self.block.slashes.contains(&validator) { + debug!(target: "tendermint", "Slashing validator {:?}", validator); self.block.slashes.insert(validator); self.network.slash(validator).await; } @@ -325,6 +328,7 @@ impl TendermintMachine { match step { Step::Propose => { // Slash the validator for not proposing when they should've + debug!(target: "tendermint", "Validator didn't propose when they should have"); self.slash( self.weights.proposer(self.block.number, self.block.round().number) ).await; @@ -383,9 +387,7 @@ impl TendermintMachine { let proposal = self.network.add_block(block, commit).await; self.reset(msg.round, proposal).await; } - Err(TendermintError::Malicious(validator)) => { - self.slash(validator).await; - } + Err(TendermintError::Malicious(validator)) => self.slash(validator).await, Err(TendermintError::Temporal) => (), } @@ -413,6 +415,7 @@ impl TendermintMachine { // which forces us to calculate every end time if let Some(end_time) = self.block.end_time.get(&round) { if !self.validators.verify(sender, &commit_msg(end_time.canonical(), id.as_ref()), sig) { + debug!(target: "tendermint", "Validator produced an invalid commit signature"); Err(TendermintError::Malicious(sender))?; } return Ok(true); @@ -436,6 +439,7 @@ impl TendermintMachine { if matches!(msg.data, Data::Proposal(..)) && (msg.sender != self.weights.proposer(msg.block, msg.round)) { + debug!(target: "tendermint", "Validator who wasn't the proposer proposed"); Err(TendermintError::Malicious(msg.sender))?; }; @@ -550,7 +554,10 @@ impl TendermintMachine { let (valid, err) = match self.network.validate(block).await { Ok(_) => (true, Ok(None)), Err(BlockError::Temporal) => (false, Ok(None)), - Err(BlockError::Fatal) => (false, Err(TendermintError::Malicious(proposer))), + Err(BlockError::Fatal) => (false, { + debug!(target: "tendermint", "Validator proposed a fatally invalid block"); + Err(TendermintError::Malicious(proposer)) + }), }; // Create a raw vote which only requires block validity as a basis for the actual vote. let raw_vote = Some(block.id()).filter(|_| valid); @@ -565,6 +572,7 @@ impl TendermintMachine { if let Some(vr) = vr { // Malformed message if vr.0 >= self.block.round().number.0 { + debug!(target: "tendermint", "Validator claimed a round from the future was valid"); Err(TendermintError::Malicious(msg.sender))?; } @@ -602,7 +610,10 @@ impl TendermintMachine { match self.network.validate(block).await { Ok(_) => (), Err(BlockError::Temporal) => (), - Err(BlockError::Fatal) => Err(TendermintError::Malicious(proposer))?, + Err(BlockError::Fatal) => { + debug!(target: "tendermint", "Validator proposed a fatally invalid block"); + Err(TendermintError::Malicious(proposer))? + } }; self.block.valid = Some((self.block.round().number, block.clone())); diff --git a/substrate/tendermint/machine/src/message_log.rs b/substrate/tendermint/machine/src/message_log.rs index cb64676e4..e914f6940 100644 --- a/substrate/tendermint/machine/src/message_log.rs +++ b/substrate/tendermint/machine/src/message_log.rs @@ -1,11 +1,14 @@ use std::{sync::Arc, collections::HashMap}; +use log::debug; + use crate::{ext::*, RoundNumber, Step, Data, DataFor, MessageFor, TendermintError}; +type RoundLog = HashMap<::ValidatorId, HashMap>>; pub(crate) struct MessageLog { weights: Arc, precommitted: HashMap::Id>, - pub(crate) log: HashMap>>>, + pub(crate) log: HashMap>, } impl MessageLog { @@ -25,6 +28,10 @@ impl MessageLog { let step = msg.data.step(); if let Some(existing) = msgs.get(&step) { if existing != &msg.data { + debug!( + target: "tendermint", + "Validator sent multiple messages for the same block + round + step" + ); Err(TendermintError::Malicious(msg.sender))?; } return Ok(false); @@ -34,6 +41,7 @@ impl MessageLog { if let Data::Precommit(Some((hash, _))) = &msg.data { if let Some(prev) = self.precommitted.get(&msg.sender) { if hash != prev { + debug!(target: "tendermint", "Validator precommitted to multiple blocks"); Err(TendermintError::Malicious(msg.sender))?; } } From 849c358fe299eaed7ce179c4a525eb583eea8d35 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 24 Nov 2022 03:45:01 -0500 Subject: [PATCH 180/186] Pair more log statements --- substrate/tendermint/client/src/authority/mod.rs | 1 + substrate/tendermint/client/src/tendermint.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index cc1b483da..097cd7d49 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -390,6 +390,7 @@ impl Network for TendermintAuthority { // Can happen when we sync a block while also acting as a validator if number <= self.import.client.info().best_number { + debug!(target: "tendermint", "Machine proposed a block for a slot we've already synced"); Err(BlockError::Temporal)?; } diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 29f17be34..506e5508d 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -3,7 +3,7 @@ use std::{ collections::HashSet, }; -use log::warn; +use log::{debug, warn}; use tokio::sync::RwLock as AsyncRwLock; @@ -119,6 +119,7 @@ impl TendermintImport { } else if err.fatal_error() { Err(Error::Other(BlockError::Fatal.into())) } else { + debug!(target: "tendermint", "Proposed block has temporally wrong inherents"); self.recheck.write().unwrap().insert(hash); Err(Error::Other(BlockError::Temporal.into())) } From d75792467291ea1a82e94c2bb6625f1e291038c3 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 25 Nov 2022 20:18:15 -0500 Subject: [PATCH 181/186] Clean TendermintAuthority::authority as possible Merges it into new. It has way too many arguments, yet there's no clear path at consolidation there, unfortunately. Additionally provides better scoping within itself. --- substrate/node/src/service.rs | 10 +- .../tendermint/client/src/authority/mod.rs | 156 +++++++++--------- substrate/tendermint/client/src/tendermint.rs | 3 +- 3 files changed, 86 insertions(+), 83 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 76b7a54d4..54fd22c0a 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -246,14 +246,10 @@ pub async fn new_full(mut config: Configuration) -> Result { /// Tendermint Authority. Participates in the block proposal and voting process. pub struct TendermintAuthority { - genesis: Option, import: TendermintImport, active: Option>, } @@ -114,27 +113,22 @@ async fn get_proposal( } impl TendermintAuthority { - /// Create a new TendermintAuthority. - pub fn new(genesis: Option, import: TendermintImport) -> Self { - Self { - genesis: genesis.map(|genesis| { - genesis.duration_since(UNIX_EPOCH).unwrap().as_secs() + u64::from(Self::block_time()) - }), - import, - active: None, - } + // Authority which is capable of verifying commits + pub(crate) fn stub(import: TendermintImport) -> Self { + Self { import, active: None } } async fn get_proposal(&mut self, header: &::Header) -> T::Block { get_proposal(&self.active.as_mut().unwrap().env, &self.import, header, false).await } - /// Act as a network authority, proposing and voting on blocks. This should be spawned on a task - /// as it will not return until the P2P stack shuts down. - #[allow(clippy::too_many_arguments)] - pub async fn authority( - mut self, + /// Create and run a new Tendermint Authority, proposing and voting on blocks. + /// This should be spawned on a task as it will not return until the P2P stack shuts down. + #[allow(clippy::too_many_arguments, clippy::new_ret_no_self)] + pub async fn new( + genesis: SystemTime, protocol: ProtocolName, + import: TendermintImport, keys: Arc, providers: T::CIDP, spawner: impl SpawnEssentialNamed, @@ -142,80 +136,92 @@ impl TendermintAuthority { network: T::Network, registry: Option<&Registry>, ) { - let info = self.import.client.info(); - - // Header::Number: TryInto doesn't implement Debug and can't be unwrapped - let last_block: u64 = match info.finalized_number.try_into() { - Ok(best) => best, - Err(_) => panic!("BlockNumber exceeded u64"), - }; - let last_hash = info.finalized_hash; - - // Get the last block's time by grabbing its commit and reading the time from that - let last_time = Commit::>::decode( - &mut self - .import - .client - .justifications(last_hash) - .unwrap() - .map(|justifications| justifications.get(CONSENSUS_ID).cloned().unwrap()) - .unwrap_or_default() - .as_ref(), - ) - .map(|commit| commit.end_time) - .unwrap_or_else(|_| self.genesis.unwrap()); - - let next_block = last_block + 1; - - // Shared references between us and the Tendermint machine (and its actions via its Network - // trait) - let block_in_progress = Arc::new(RwLock::new(next_block)); - - // Create the gossip network - let mut gossip = GossipEngine::new( - network.clone(), - protocol, - Arc::new(TendermintGossip::new(block_in_progress.clone(), self.import.validators.clone())), - registry, - ); - // This should only have a single value, yet a bounded channel with a capacity of 1 would cause // a firm bound. It's not worth having a backlog crash the node since we aren't constrained let (new_block_event_send, mut new_block_event_recv) = mpsc::unbounded(); let (msg_send, mut msg_recv) = mpsc::unbounded(); - // Clone the import object - let import = self.import.clone(); - // Move the env into an Arc let env = Arc::new(Mutex::new(env)); - // Create the Tendermint machine - let TendermintHandle { mut step, mut messages, machine } = { - // Set this struct as active - *self.import.providers.write().await = Some(providers); - self.active = Some(ActiveAuthority { - signer: TendermintSigner(keys, self.import.validators.clone()), - - block_in_progress: block_in_progress.clone(), - new_block_event: new_block_event_send, - gossip: msg_send, - - env: env.clone(), - announce: network, - }); - - let proposal = self - .get_proposal(&self.import.client.header(BlockId::Hash(last_hash)).unwrap().unwrap()) + // Scoped so the temporary variables used here don't leak + let (block_in_progress, TendermintHandle { mut step, mut messages, machine }) = { + // Get the info necessary to spawn the machine + let info = import.client.info(); + + // Header::Number: TryInto doesn't implement Debug and can't be unwrapped + let last_block: u64 = match info.finalized_number.try_into() { + Ok(best) => best, + Err(_) => panic!("BlockNumber exceeded u64"), + }; + let last_hash = info.finalized_hash; + + let last_time = { + // Convert into a Unix timestamp + let genesis = genesis.duration_since(UNIX_EPOCH).unwrap().as_secs(); + + // Get the last block's time by grabbing its commit and reading the time from that + Commit::>::decode( + &mut import + .client + .justifications(last_hash) + .unwrap() + .map(|justifications| justifications.get(CONSENSUS_ID).cloned().unwrap()) + .unwrap_or_default() + .as_ref(), + ) + .map(|commit| commit.end_time) + // The commit provides the time its block ended at + // The genesis time is when the network starts + // Accordingly, the end of the genesis block is a block time after the genesis time + .unwrap_or_else(|_| genesis + u64::from(Self::block_time())) + }; + + let next_block = last_block + 1; + // Shared references between us and the Tendermint machine (and its actions via its Network + // trait) + let block_in_progress = Arc::new(RwLock::new(next_block)); + + // Write the providers into the import so it can verify inherents + *import.providers.write().await = Some(providers); + + let mut authority = Self { + import: import.clone(), + active: Some(ActiveAuthority { + signer: TendermintSigner(keys, import.validators.clone()), + + block_in_progress: block_in_progress.clone(), + new_block_event: new_block_event_send, + gossip: msg_send, + + env: env.clone(), + announce: network.clone(), + }), + }; + + // Get our first proposal + let proposal = authority + .get_proposal(&import.client.header(BlockId::Hash(last_hash)).unwrap().unwrap()) .await; - // We no longer need self, so let TendermintMachine become its owner - TendermintMachine::new(self, BlockNumber(last_block), last_time, proposal).await + ( + block_in_progress, + TendermintMachine::new(authority, BlockNumber(last_block), last_time, proposal).await, + ) }; spawner.spawn_essential("machine", Some("tendermint"), Box::pin(machine.run())); + // Create the gossip network + let mut gossip = GossipEngine::new( + network, + protocol, + Arc::new(TendermintGossip::new(block_in_progress.clone(), import.validators.clone())), + registry, + ); + // Start receiving messages about the Tendermint process for this block - let mut gossip_recv = gossip.messages_for(TendermintGossip::::topic(next_block)); + let mut gossip_recv = + gossip.messages_for(TendermintGossip::::topic(*block_in_progress.read().unwrap())); // Get finality events from Substrate let mut finality = import.client.finality_notification_stream(); diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 506e5508d..20e78c108 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -164,7 +164,8 @@ impl TendermintImport { let commit: Commit> = Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; - if !TendermintAuthority::new(None, self.clone()).verify_commit(hash, &commit) { + // Create a stubbed TendermintAuthority so we can verify the commit + if !TendermintAuthority::stub(self.clone()).verify_commit(hash, &commit) { Err(Error::InvalidJustification)?; } Ok(()) From 61b00b38b9df7dacd65d982f0cc049b958dbc893 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 26 Nov 2022 01:17:05 -0500 Subject: [PATCH 182/186] Fix #158 Doesn't use lock_import_and_run for reasons commented (lack of async). --- .../tendermint/client/src/authority/mod.rs | 82 ++++++++++--------- .../tendermint/client/src/block_import.rs | 16 ++-- substrate/tendermint/client/src/tendermint.rs | 13 ++- 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index 4feccdbac..ef2032fe2 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -118,8 +118,8 @@ impl TendermintAuthority { Self { import, active: None } } - async fn get_proposal(&mut self, header: &::Header) -> T::Block { - get_proposal(&self.active.as_mut().unwrap().env, &self.import, header, false).await + async fn get_proposal(&self, header: &::Header) -> T::Block { + get_proposal(&self.active.as_ref().unwrap().env, &self.import, header, false).await } /// Create and run a new Tendermint Authority, proposing and voting on blocks. @@ -185,7 +185,7 @@ impl TendermintAuthority { // Write the providers into the import so it can verify inherents *import.providers.write().await = Some(providers); - let mut authority = Self { + let authority = Self { import: import.clone(), active: Some(ActiveAuthority { signer: TendermintSigner(keys, import.validators.clone()), @@ -438,53 +438,55 @@ impl Network for TendermintAuthority { block: T::Block, commit: Commit>, ) -> T::Block { - let hash = block.hash(); - let justification = (CONSENSUS_ID, commit.encode()); - debug_assert!(self.import.verify_justification(hash, &justification).is_ok()); - - let raw_number = *block.header().number(); - let number: u64 = match raw_number.try_into() { - Ok(number) => number, - Err(_) => panic!("BlockNumber exceeded u64"), - }; + // Prevent import_block from being called while we run + let _guard = self.import.sync_lock.lock().await; + + // Check if we already imported this externally + if self.import.client.justifications(block.hash()).unwrap().is_some() { + debug!(target: "tendermint", "Machine produced a commit after we already synced it"); + } else { + let hash = block.hash(); + let justification = (CONSENSUS_ID, commit.encode()); + debug_assert!(self.import.verify_justification(hash, &justification).is_ok()); + + let raw_number = *block.header().number(); + let number: u64 = match raw_number.try_into() { + Ok(number) => number, + Err(_) => panic!("BlockNumber exceeded u64"), + }; - { let active = self.active.as_mut().unwrap(); - // Acquire the write lock let mut block_in_progress = active.block_in_progress.write().unwrap(); - - // This block's number may not be the block we were working on if Substrate synced it before - // the machine synced the necessary precommits - if number == *block_in_progress { - // If we are the party responsible for handling this block, finalize it - self - .import - .client - .finalize_block(hash, Some(justification), true) - .map_err(|_| Error::InvalidJustification) - .unwrap(); - - // Tell the loop we received a block and to move to the next - *block_in_progress = number + 1; - if active.new_block_event.unbounded_send(()).is_err() { - warn!( - target: "tendermint", - "Attempted to send a new number to the gossip handler except it's closed. {}", - "Is the node shutting down?" - ); - } - } else { - debug!(target: "tendermint", "Machine produced a commit after we synced it"); + // This will hold true unless we received, and handled, a notification for the block before + // its justification was made available + debug_assert_eq!(number, *block_in_progress); + + // Finalize the block + self + .import + .client + .finalize_block(hash, Some(justification), true) + .map_err(|_| Error::InvalidJustification) + .unwrap(); + + // Tell the loop we received a block and to move to the next + *block_in_progress = number + 1; + if active.new_block_event.unbounded_send(()).is_err() { + warn!( + target: "tendermint", + "Attempted to send a new number to the gossip handler except it's closed. {}", + "Is the node shutting down?" + ); } - // Clear any blocks for the previous slot which we were willing to recheck - *self.import.recheck.write().unwrap() = HashSet::new(); - // Announce the block to the network so new clients can sync properly active.announce.announce_block(hash, None); active.announce.new_best_block_imported(hash, raw_number); } + // Clear any blocks for the previous slot which we were willing to recheck + *self.import.recheck.write().unwrap() = HashSet::new(); + self.get_proposal(block.header()).await } } diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index f679fba4b..dba1992b0 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -60,6 +60,9 @@ where mut block: BlockImportParams, new_cache: HashMap>, ) -> Result { + // Don't allow multiple blocks to be imported at once + let _guard = self.sync_lock.lock().await; + if self.check_already_in_chain(block.header.hash()) { return Ok(ImportResult::AlreadyInChain); } @@ -99,20 +102,17 @@ where // on our end, letting us directly set the notifications, so we're not beholden to when // Substrate decides to call notify_finalized // - // TODO: Call lock_import_and_run on our end, which already may be needed for safety reasons + // lock_import_and_run unfortunately doesn't allow async code and generally isn't feasible to + // work with though. We also couldn't use it to prevent Substrate from creating + // notifications, so it only solves half the problem. We'd *still* have to keep this patch, + // with all its fragility, unless we edit Substrate or move the entire block import flow here BlockOrigin::NetworkInitialSync => BlockOrigin::NetworkBroadcast, - // Also re-map File so bootstraps also trigger notifications, enabling safely using - // bootstraps + // Also re-map File so bootstraps also trigger notifications, enabling using bootstraps BlockOrigin::File => BlockOrigin::NetworkBroadcast, // We do not want this block, which hasn't been confirmed, to be broadcast over the net // Substrate will generate notifications unless it's Genesis, which this isn't, InitialSync, // which changes telemetry behavior, or File, which is... close enough - // - // Even if we do manually implement lock_import_and_run, Substrate will still override - // our notifications if it believes it should provide notifications. That means we *still* - // have to keep this patch, with all its fragility, unless we edit Substrate or move the - // the entire block import flow under Serai BlockOrigin::ConsensusBroadcast => BlockOrigin::File, BlockOrigin::Own => BlockOrigin::File, }; diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index 20e78c108..c3630e4c0 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -5,7 +5,7 @@ use std::{ use log::{debug, warn}; -use tokio::sync::RwLock as AsyncRwLock; +use tokio::sync::{Mutex, RwLock as AsyncRwLock}; use sp_core::Decode; use sp_runtime::{ @@ -30,6 +30,9 @@ use crate::{ /// Tendermint import handler. pub struct TendermintImport { + // Lock ensuring only one block is imported at a time + pub(crate) sync_lock: Arc>, + pub(crate) validators: TendermintValidators, pub(crate) providers: Arc>>, @@ -50,6 +53,8 @@ pub struct TendermintImport { impl Clone for TendermintImport { fn clone(&self) -> Self { TendermintImport { + sync_lock: self.sync_lock.clone(), + validators: self.validators.clone(), providers: self.providers.clone(), @@ -65,6 +70,8 @@ impl Clone for TendermintImport { impl TendermintImport { pub(crate) fn new(client: Arc) -> TendermintImport { TendermintImport { + sync_lock: Arc::new(Mutex::new(())), + validators: TendermintValidators::new(client.clone()), providers: Arc::new(AsyncRwLock::new(None)), @@ -102,7 +109,7 @@ impl TendermintImport { } async fn check_inherents( - &mut self, + &self, hash: ::Hash, block: T::Block, ) -> Result<(), Error> { @@ -193,7 +200,7 @@ impl TendermintImport { } pub(crate) async fn check( - &mut self, + &self, block: &mut BlockImportParams, ) -> Result<(), Error> { if block.finalized { From 8c2cbffec1fb5790d0445a0e5bb36c90ce6789ee Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 27 Nov 2022 01:00:16 -0500 Subject: [PATCH 183/186] Rename guard to lock --- substrate/tendermint/client/src/authority/mod.rs | 2 +- substrate/tendermint/client/src/block_import.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index ef2032fe2..d19f529c9 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -439,7 +439,7 @@ impl Network for TendermintAuthority { commit: Commit>, ) -> T::Block { // Prevent import_block from being called while we run - let _guard = self.import.sync_lock.lock().await; + let _lock = self.import.sync_lock.lock().await; // Check if we already imported this externally if self.import.client.justifications(block.hash()).unwrap().is_some() { diff --git a/substrate/tendermint/client/src/block_import.rs b/substrate/tendermint/client/src/block_import.rs index dba1992b0..4282f6697 100644 --- a/substrate/tendermint/client/src/block_import.rs +++ b/substrate/tendermint/client/src/block_import.rs @@ -61,7 +61,7 @@ where new_cache: HashMap>, ) -> Result { // Don't allow multiple blocks to be imported at once - let _guard = self.sync_lock.lock().await; + let _lock = self.sync_lock.lock().await; if self.check_already_in_chain(block.header.hash()) { return Ok(ImportResult::AlreadyInChain); From 3ea8becc57303ef9780edd98a8eda0549df0f9d8 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 29 Nov 2022 08:59:03 -0500 Subject: [PATCH 184/186] Have the devnet use the current time as the genesis Possible since it's only a single node, not requiring synchronization. --- substrate/node/src/service.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 54fd22c0a..6179c0e24 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -2,7 +2,7 @@ use std::{ error::Error, boxed::Box, sync::Arc, - time::{UNIX_EPOCH, Duration}, + time::{UNIX_EPOCH, SystemTime, Duration}, str::FromStr, }; @@ -226,6 +226,12 @@ pub async fn new_full(mut config: Configuration) -> Result Result Date: Fri, 2 Dec 2022 09:36:44 -0500 Subject: [PATCH 185/186] Fix gossiping I really don't know what side effect this avoids and I can't say I care at this point. --- .../tendermint/client/src/authority/mod.rs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index d19f529c9..aa7223447 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -145,7 +145,7 @@ impl TendermintAuthority { let env = Arc::new(Mutex::new(env)); // Scoped so the temporary variables used here don't leak - let (block_in_progress, TendermintHandle { mut step, mut messages, machine }) = { + let (block_in_progress, mut gossip, TendermintHandle { mut step, mut messages, machine }) = { // Get the info necessary to spawn the machine let info = import.client.info(); @@ -204,21 +204,23 @@ impl TendermintAuthority { .get_proposal(&import.client.header(BlockId::Hash(last_hash)).unwrap().unwrap()) .await; + // Create the gossip network + // This has to be spawning the machine, else gossip fails for some reason + let gossip = GossipEngine::new( + network, + protocol, + Arc::new(TendermintGossip::new(block_in_progress.clone(), import.validators.clone())), + registry, + ); + ( block_in_progress, + gossip, TendermintMachine::new(authority, BlockNumber(last_block), last_time, proposal).await, ) }; spawner.spawn_essential("machine", Some("tendermint"), Box::pin(machine.run())); - // Create the gossip network - let mut gossip = GossipEngine::new( - network, - protocol, - Arc::new(TendermintGossip::new(block_in_progress.clone(), import.validators.clone())), - registry, - ); - // Start receiving messages about the Tendermint process for this block let mut gossip_recv = gossip.messages_for(TendermintGossip::::topic(*block_in_progress.read().unwrap())); From e220163dca3b1b4799c2442c6dbbb463b9ecc1bd Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 2 Dec 2022 09:53:13 -0500 Subject: [PATCH 186/186] Misc lints --- deploy/genesis/entry-dev.sh | 2 +- .../tendermint/client/src/authority/mod.rs | 2 +- substrate/tendermint/client/src/tendermint.rs | 18 +++++++++++------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/deploy/genesis/entry-dev.sh b/deploy/genesis/entry-dev.sh index dd2415ee3..46ff6d60d 100755 --- a/deploy/genesis/entry-dev.sh +++ b/deploy/genesis/entry-dev.sh @@ -2,6 +2,6 @@ date +%s > /temp/genesis GENESIS=$(cat /temp/genesis) -echo "Genesis : $GENESIS" +echo "Genesis: $GENESIS" tail -f /dev/null diff --git a/substrate/tendermint/client/src/authority/mod.rs b/substrate/tendermint/client/src/authority/mod.rs index aa7223447..778f50d4f 100644 --- a/substrate/tendermint/client/src/authority/mod.rs +++ b/substrate/tendermint/client/src/authority/mod.rs @@ -237,7 +237,7 @@ impl TendermintAuthority { "GossipEngine shut down. {}", "Is the node shutting down?" ); - break + break; }, // Synced a block from the network diff --git a/substrate/tendermint/client/src/tendermint.rs b/substrate/tendermint/client/src/tendermint.rs index c3630e4c0..6e1b6f9e8 100644 --- a/substrate/tendermint/client/src/tendermint.rs +++ b/substrate/tendermint/client/src/tendermint.rs @@ -24,10 +24,15 @@ use sc_block_builder::BlockBuilderApi; use tendermint_machine::ext::{BlockError, Commit, Network}; use crate::{ - CONSENSUS_ID, TendermintValidator, validators::TendermintValidators, TendermintImportQueue, - authority::TendermintAuthority, + CONSENSUS_ID, TendermintClient, TendermintValidator, validators::TendermintValidators, + TendermintImportQueue, authority::TendermintAuthority, }; +type InstantiatedTendermintImportQueue = TendermintImportQueue< + ::Block, + ::BackendTransaction, +>; + /// Tendermint import handler. pub struct TendermintImport { // Lock ensuring only one block is imported at a time @@ -46,8 +51,7 @@ pub struct TendermintImport { pub(crate) recheck: Arc::Hash>>>, pub(crate) client: Arc, - pub(crate) queue: - Arc>>>, + pub(crate) queue: Arc>>>, } impl Clone for TendermintImport { @@ -223,9 +227,9 @@ impl TendermintImport { if !block.finalized { let hash = block.header.hash(); self.verify_origin(hash)?; - if let Some(body) = block.body.clone() { - self.check_inherents(hash, T::Block::new(block.header.clone(), body)).await?; - } + self + .check_inherents(hash, T::Block::new(block.header.clone(), block.body.clone().unwrap())) + .await?; } // Additionally check these fields are empty