mirror of
https://github.com/Cuprate/cuprate.git
synced 2024-12-23 03:59:31 +00:00
init consensus rules crate
This commit is contained in:
parent
59e7e0b4e8
commit
2f08978e67
9 changed files with 629 additions and 9 deletions
|
@ -2,7 +2,8 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
# "common",
|
"common",
|
||||||
|
"consensus",
|
||||||
#"cuprate",
|
#"cuprate",
|
||||||
# "database",
|
# "database",
|
||||||
"net/levin",
|
"net/levin",
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
pub mod hardforks;
|
//pub mod hardforks;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod pruning;
|
pub mod pruning;
|
||||||
|
|
||||||
pub use hardforks::HardForks;
|
//pub use hardforks::HardForks;
|
||||||
pub use network::Network;
|
pub use network::Network;
|
||||||
pub use pruning::{PruningError, PruningSeed};
|
pub use pruning::{PruningError, PruningSeed};
|
||||||
|
|
||||||
|
@ -12,3 +12,15 @@ pub const CRYPTONOTE_MAX_BLOCK_NUMBER: u64 = 500000000;
|
||||||
pub const CRYPTONOTE_PRUNING_LOG_STRIPES: u32 = 3;
|
pub const CRYPTONOTE_PRUNING_LOG_STRIPES: u32 = 3;
|
||||||
pub const CRYPTONOTE_PRUNING_STRIPE_SIZE: u64 = 4096;
|
pub const CRYPTONOTE_PRUNING_STRIPE_SIZE: u64 = 4096;
|
||||||
pub const CRYPTONOTE_PRUNING_TIP_BLOCKS: u64 = 5500;
|
pub const CRYPTONOTE_PRUNING_TIP_BLOCKS: u64 = 5500;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum BlockID {
|
||||||
|
Hash([u8; 32]),
|
||||||
|
Height(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for BlockID {
|
||||||
|
fn from(value: u64) -> Self {
|
||||||
|
BlockID::Height(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,17 +10,17 @@ const STAGENET_NETWORK_ID: [u8; 16] = [
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Network {
|
pub enum Network {
|
||||||
MainNet,
|
Mainnet,
|
||||||
TestNet,
|
Testnet,
|
||||||
StageNet,
|
Stagenet,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Network {
|
impl Network {
|
||||||
pub fn network_id(&self) -> [u8; 16] {
|
pub fn network_id(&self) -> [u8; 16] {
|
||||||
match self {
|
match self {
|
||||||
Network::MainNet => MAINNET_NETWORK_ID,
|
Network::Mainnet => MAINNET_NETWORK_ID,
|
||||||
Network::TestNet => TESTNET_NETWORK_ID,
|
Network::Testnet => TESTNET_NETWORK_ID,
|
||||||
Network::StageNet => STAGENET_NETWORK_ID,
|
Network::Stagenet => STAGENET_NETWORK_ID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
31
consensus/Cargo.toml
Normal file
31
consensus/Cargo.toml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
[package]
|
||||||
|
name = "monero-consensus"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "A crate implimenting all Moneros consensus rules."
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["Boog900"]
|
||||||
|
repository = "https://github.com/Cuprate/cuprate/tree/main/consensus"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["binaries"]
|
||||||
|
binaries = ["rpc", "dep:tokio", "dep:tracing-subscriber", "tower/retry", "tower/balance"]
|
||||||
|
rpc = ["dep:futures"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hex = "0.4"
|
||||||
|
thiserror = "1"
|
||||||
|
tower = {version = "0.4", features = ["util"]}
|
||||||
|
tracing = "0.1"
|
||||||
|
|
||||||
|
monero-serai = {git="https://github.com/Cuprate/serai.git", rev = "84b77b1"}
|
||||||
|
|
||||||
|
cuprate-common = {path = "../common"}
|
||||||
|
|
||||||
|
# used for rpc
|
||||||
|
futures = {version = "0.3", optional = true}
|
||||||
|
# used in binaries
|
||||||
|
tokio = { version = "1", features = ["rt-multi-thread", "macros"], optional = true }
|
||||||
|
tracing-subscriber = {version = "0.3", optional = true}
|
||||||
|
# here to help cargo to pick a version - remove me
|
||||||
|
syn = "2.0.29"
|
39
consensus/src/bin/scan_chain.rs
Normal file
39
consensus/src/bin/scan_chain.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#![cfg(feature = "binaries")]
|
||||||
|
|
||||||
|
use tower::{Service, ServiceExt};
|
||||||
|
use tracing::level_filters::LevelFilter;
|
||||||
|
|
||||||
|
use monero_consensus::hardforks::{HardFork, HardForkConfig, HardForks};
|
||||||
|
use monero_consensus::rpc::Rpc;
|
||||||
|
use monero_consensus::DatabaseRequest;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_max_level(LevelFilter::INFO)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let mut rpc = Rpc::new_http("http://xmr-node.cakewallet.com:18081".to_string());
|
||||||
|
|
||||||
|
let res = rpc
|
||||||
|
.ready()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.call(DatabaseRequest::ChainHeight)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("{:?}", res);
|
||||||
|
|
||||||
|
let mut hfs = HardForks::init(HardForkConfig::default(), &mut rpc)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("{:?}", hfs);
|
||||||
|
|
||||||
|
hfs.new_block(HardFork::V2, 1009827, &mut rpc).await;
|
||||||
|
println!("{:?}", hfs);
|
||||||
|
|
||||||
|
hfs.new_block(HardFork::V2, 1009828, &mut rpc).await;
|
||||||
|
println!("{:?}", hfs);
|
||||||
|
}
|
70
consensus/src/genesis.rs
Normal file
70
consensus/src/genesis.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/// This module contains the code to generate Monero's genesis blocks.
|
||||||
|
///
|
||||||
|
/// ref: consensus-doc#Genesis
|
||||||
|
use monero_serai::{
|
||||||
|
block::{Block, BlockHeader},
|
||||||
|
transaction::Transaction,
|
||||||
|
};
|
||||||
|
|
||||||
|
use cuprate_common::Network;
|
||||||
|
|
||||||
|
fn genesis_nonce(network: &Network) -> u32 {
|
||||||
|
match network {
|
||||||
|
Network::Mainnet => 10000,
|
||||||
|
Network::Testnet => 10001,
|
||||||
|
Network::Stagenet => 10002,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genesis_miner_tx(network: &Network) -> Transaction {
|
||||||
|
Transaction::read(&mut hex::decode(match network {
|
||||||
|
Network::Mainnet | Network::Testnet => "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1",
|
||||||
|
Network::Stagenet => "013c01ff0001ffffffffffff0302df5d56da0c7d643ddd1ce61901c7bdc5fb1738bfe39fbe69c28a3a7032729c0f2101168d0c4ca86fb55a4cf6a36d31431be1c53a3bd7411bb24e8832410289fa6f3b"
|
||||||
|
}).unwrap().as_slice()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the Monero genesis block.
|
||||||
|
///
|
||||||
|
/// ref: consensus-doc#Genesis
|
||||||
|
pub fn generate_genesis_block(network: &Network) -> Block {
|
||||||
|
Block {
|
||||||
|
header: BlockHeader {
|
||||||
|
major_version: 1,
|
||||||
|
minor_version: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
previous: [0; 32],
|
||||||
|
nonce: genesis_nonce(network),
|
||||||
|
},
|
||||||
|
miner_tx: genesis_miner_tx(network),
|
||||||
|
txs: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use cuprate_common::Network;
|
||||||
|
|
||||||
|
use super::generate_genesis_block;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_genesis_blocks() {
|
||||||
|
assert_eq!(
|
||||||
|
&generate_genesis_block(&Network::Mainnet).hash(),
|
||||||
|
hex::decode("418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3")
|
||||||
|
.unwrap()
|
||||||
|
.as_slice()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&generate_genesis_block(&Network::Testnet).hash(),
|
||||||
|
hex::decode("48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b")
|
||||||
|
.unwrap()
|
||||||
|
.as_slice()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&generate_genesis_block(&Network::Stagenet).hash(),
|
||||||
|
hex::decode("76ee3cc98646292206cd3e86f74d88b4dcc1d937088645e9b0cbca84b7ce74eb")
|
||||||
|
.unwrap()
|
||||||
|
.as_slice()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
342
consensus/src/hardforks.rs
Normal file
342
consensus/src/hardforks.rs
Normal file
|
@ -0,0 +1,342 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use monero_serai::block::BlockHeader;
|
||||||
|
use tower::ServiceExt;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use cuprate_common::{BlockID, Network};
|
||||||
|
|
||||||
|
use crate::{Database, DatabaseRequest, DatabaseResponse, Error};
|
||||||
|
|
||||||
|
//http://localhost:3000/consensus_rules/hardforks.html#window-size
|
||||||
|
const DEFAULT_WINDOW_SIZE: u64 = 10080; // supermajority window check length - a week
|
||||||
|
|
||||||
|
/// An identifier for every hard-fork Monero has had.
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum HardFork {
|
||||||
|
V1 = 1,
|
||||||
|
V2,
|
||||||
|
V3,
|
||||||
|
V4,
|
||||||
|
V5,
|
||||||
|
V6,
|
||||||
|
V7,
|
||||||
|
V8,
|
||||||
|
V9,
|
||||||
|
V10,
|
||||||
|
V11,
|
||||||
|
V12,
|
||||||
|
V13,
|
||||||
|
V14,
|
||||||
|
V15,
|
||||||
|
V16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HardFork {
|
||||||
|
/// Returns the hard-fork for a blocks `major_version` field.
|
||||||
|
///
|
||||||
|
/// http://**/consensus_rules/hardforks.html#blocks-version-and-vote
|
||||||
|
pub fn from_version(version: &u8) -> Result<HardFork, Error> {
|
||||||
|
Ok(match version {
|
||||||
|
1 => HardFork::V1,
|
||||||
|
2 => HardFork::V2,
|
||||||
|
3 => HardFork::V3,
|
||||||
|
4 => HardFork::V4,
|
||||||
|
5 => HardFork::V5,
|
||||||
|
6 => HardFork::V6,
|
||||||
|
7 => HardFork::V7,
|
||||||
|
8 => HardFork::V8,
|
||||||
|
9 => HardFork::V9,
|
||||||
|
10 => HardFork::V10,
|
||||||
|
11 => HardFork::V11,
|
||||||
|
12 => HardFork::V12,
|
||||||
|
13 => HardFork::V13,
|
||||||
|
14 => HardFork::V14,
|
||||||
|
15 => HardFork::V15,
|
||||||
|
16 => HardFork::V16,
|
||||||
|
_ => {
|
||||||
|
return Err(Error::InvalidHardForkVersion(
|
||||||
|
"Version is not a known hard fork",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the hard-fork for a blocks `minor_version` (vote) field.
|
||||||
|
///
|
||||||
|
/// http://**/consensus_rules/hardforks.html#blocks-version-and-vote
|
||||||
|
pub fn from_vote(vote: &u8) -> HardFork {
|
||||||
|
if *vote == 0 {
|
||||||
|
// A vote of 0 is interpreted as 1 as that's what Monero used to default to.
|
||||||
|
return HardFork::V1;
|
||||||
|
}
|
||||||
|
// This must default to the latest hard-fork!
|
||||||
|
Self::from_version(vote).unwrap_or(HardFork::V16)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next hard-fork.
|
||||||
|
pub fn next_fork(&self) -> Option<HardFork> {
|
||||||
|
match self {
|
||||||
|
HardFork::V1 => Some(HardFork::V2),
|
||||||
|
HardFork::V2 => Some(HardFork::V3),
|
||||||
|
HardFork::V3 => Some(HardFork::V4),
|
||||||
|
HardFork::V4 => Some(HardFork::V5),
|
||||||
|
HardFork::V5 => Some(HardFork::V6),
|
||||||
|
HardFork::V6 => Some(HardFork::V7),
|
||||||
|
HardFork::V7 => Some(HardFork::V8),
|
||||||
|
HardFork::V8 => Some(HardFork::V9),
|
||||||
|
HardFork::V9 => Some(HardFork::V10),
|
||||||
|
HardFork::V10 => Some(HardFork::V11),
|
||||||
|
HardFork::V11 => Some(HardFork::V12),
|
||||||
|
HardFork::V12 => Some(HardFork::V13),
|
||||||
|
HardFork::V13 => Some(HardFork::V14),
|
||||||
|
HardFork::V14 => Some(HardFork::V15),
|
||||||
|
HardFork::V15 => Some(HardFork::V16),
|
||||||
|
HardFork::V16 => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the threshold of this fork.
|
||||||
|
pub fn fork_threshold(&self, _: &Network) -> u64 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the votes needed for this fork.
|
||||||
|
pub fn votes_needed(&self, network: &Network, window: u64) -> u64 {
|
||||||
|
(self.fork_threshold(network) * window + 99) / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum height this fork will activate at
|
||||||
|
pub fn fork_height(&self, network: &Network) -> u64 {
|
||||||
|
match network {
|
||||||
|
Network::Mainnet => self.mainnet_fork_height(),
|
||||||
|
Network::Stagenet => self.stagenet_fork_height(),
|
||||||
|
Network::Testnet => self.testnet_fork_height(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stagenet_fork_height(&self) -> u64 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testnet_fork_height(&self) -> u64 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mainnet_fork_height(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
HardFork::V1 => 0, // Monero core has this as 1, which is strange
|
||||||
|
HardFork::V2 => 1009827,
|
||||||
|
HardFork::V3 => 1141317,
|
||||||
|
HardFork::V4 => 1220516,
|
||||||
|
HardFork::V5 => 1288616,
|
||||||
|
HardFork::V6 => 1400000,
|
||||||
|
HardFork::V7 => 1546000,
|
||||||
|
HardFork::V8 => 1685555,
|
||||||
|
HardFork::V9 => 1686275,
|
||||||
|
HardFork::V10 => 1788000,
|
||||||
|
HardFork::V11 => 1788720,
|
||||||
|
HardFork::V12 => 1978433,
|
||||||
|
HardFork::V13 => 2210000,
|
||||||
|
HardFork::V14 => 2210720,
|
||||||
|
HardFork::V15 => 2688888,
|
||||||
|
HardFork::V16 => 2689608,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct holding the current voting state of the blockchain.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct HFVotes {
|
||||||
|
votes: [u64; 16],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HFVotes {
|
||||||
|
/// Add votes for a hard-fork
|
||||||
|
pub fn add_votes_for_hf(&mut self, hf: &HardFork, votes: u64) {
|
||||||
|
self.votes[*hf as usize - 1] += votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a vote for a hard-fork.
|
||||||
|
pub fn add_vote_for_hf(&mut self, hf: &HardFork) {
|
||||||
|
self.add_votes_for_hf(hf, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a vote for a hard-fork.
|
||||||
|
pub fn remove_vote_for_hf(&mut self, hf: &HardFork) {
|
||||||
|
self.votes[*hf as usize - 1] -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the total votes for a hard-fork.
|
||||||
|
///
|
||||||
|
/// http://localhost:3000/consensus_rules/hardforks.html#accepting-a-fork
|
||||||
|
pub fn get_votes_for_hf(&self, hf: &HardFork) -> u64 {
|
||||||
|
self.votes[*hf as usize - 1..].iter().sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the total amount of votes being tracked
|
||||||
|
pub fn total_votes(&self) -> u64 {
|
||||||
|
self.votes.iter().sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration for hard-forks.
|
||||||
|
///
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HardForkConfig {
|
||||||
|
/// The network we are on.
|
||||||
|
network: Network,
|
||||||
|
/// The amount of votes we are taking into account to decide on a fork activation.
|
||||||
|
window: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HardForkConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
network: Network::Mainnet,
|
||||||
|
window: 3, //DEFAULT_WINDOW_SIZE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct that keeps track of the current hard-fork and current votes.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HardForks {
|
||||||
|
current_hardfork: HardFork,
|
||||||
|
next_hardfork: Option<HardFork>,
|
||||||
|
|
||||||
|
config: HardForkConfig,
|
||||||
|
votes: HFVotes,
|
||||||
|
|
||||||
|
last_height: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HardForks {
|
||||||
|
pub async fn init<D>(config: HardForkConfig, database: &mut D) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
D: Database,
|
||||||
|
{
|
||||||
|
let DatabaseResponse::ChainHeight(chain_height) = database
|
||||||
|
.ready()
|
||||||
|
.await?
|
||||||
|
.call(DatabaseRequest::ChainHeight)
|
||||||
|
.await? else {
|
||||||
|
panic!("Database sent incorrect response")
|
||||||
|
};
|
||||||
|
|
||||||
|
let block_heights = if chain_height > config.window {
|
||||||
|
chain_height - config.window..chain_height
|
||||||
|
} else {
|
||||||
|
0..chain_height
|
||||||
|
};
|
||||||
|
|
||||||
|
let votes = get_votes_in_range(database, block_heights).await?;
|
||||||
|
|
||||||
|
if chain_height > config.window {
|
||||||
|
assert_eq!(votes.total_votes(), config.window)
|
||||||
|
}
|
||||||
|
|
||||||
|
let latest_header = get_block_header(database, chain_height - 1).await?;
|
||||||
|
|
||||||
|
let current_hardfork = HardFork::from_version(&latest_header.major_version)
|
||||||
|
.expect("Invalid major version in stored block");
|
||||||
|
|
||||||
|
let next_hardfork = current_hardfork.next_fork();
|
||||||
|
|
||||||
|
let mut hfs = HardForks {
|
||||||
|
config,
|
||||||
|
current_hardfork,
|
||||||
|
next_hardfork,
|
||||||
|
votes,
|
||||||
|
last_height: chain_height - 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// chain_height = height + 1
|
||||||
|
hfs.check_set_new_hf(chain_height);
|
||||||
|
|
||||||
|
Ok(hfs)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_block_version_vote(&self, version: &HardFork, vote: &HardFork) -> bool {
|
||||||
|
&self.current_hardfork == version && vote >= &self.current_hardfork
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new_block<D: Database>(&mut self, vote: HardFork, height: u64, database: &mut D) {
|
||||||
|
assert_eq!(self.last_height + 1, height);
|
||||||
|
self.last_height += 1;
|
||||||
|
|
||||||
|
self.votes.add_vote_for_hf(&vote);
|
||||||
|
|
||||||
|
for offset in self.config.window..self.votes.total_votes() {
|
||||||
|
let header = get_block_header(database, height - offset)
|
||||||
|
.await
|
||||||
|
.expect("Error retrieving block we should have in database");
|
||||||
|
self.votes
|
||||||
|
.remove_vote_for_hf(&HardFork::from_vote(&header.minor_version));
|
||||||
|
}
|
||||||
|
|
||||||
|
if height > self.config.window {
|
||||||
|
assert_eq!(self.votes.total_votes(), self.config.window);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.check_set_new_hf(height + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_set_new_hf(&mut self, height: u64) {
|
||||||
|
while let Some(new_hf) = self.next_hardfork {
|
||||||
|
if height >= new_hf.fork_height(&self.config.network)
|
||||||
|
&& self.votes.get_votes_for_hf(&new_hf)
|
||||||
|
>= new_hf.votes_needed(&self.config.network, self.config.window)
|
||||||
|
{
|
||||||
|
self.set_hf(new_hf);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_hf(&mut self, new_hf: HardFork) {
|
||||||
|
self.next_hardfork = new_hf.next_fork();
|
||||||
|
self.current_hardfork = new_hf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(database))]
|
||||||
|
async fn get_votes_in_range<D: Database>(
|
||||||
|
database: &mut D,
|
||||||
|
block_heights: Range<u64>,
|
||||||
|
) -> Result<HFVotes, Error> {
|
||||||
|
let mut votes = HFVotes::default();
|
||||||
|
|
||||||
|
for height in block_heights {
|
||||||
|
let header = get_block_header(database, height).await?;
|
||||||
|
|
||||||
|
let vote = HardFork::from_vote(&header.minor_version);
|
||||||
|
|
||||||
|
tracing::info!("Block vote for height: {} = {:?}", height, vote);
|
||||||
|
|
||||||
|
votes.add_vote_for_hf(&HardFork::from_vote(&header.minor_version));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(votes)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_block_header<D: Database>(
|
||||||
|
database: &mut D,
|
||||||
|
block_id: impl Into<BlockID>,
|
||||||
|
) -> Result<BlockHeader, Error> {
|
||||||
|
let DatabaseResponse::BlockHeader(header) = database
|
||||||
|
.oneshot(DatabaseRequest::BlockHeader(block_id.into()))
|
||||||
|
.await? else {
|
||||||
|
panic!("Database sent incorrect response for block header request")
|
||||||
|
};
|
||||||
|
Ok(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_from_hf() {
|
||||||
|
let hf = HardFork::V1 as u8;
|
||||||
|
|
||||||
|
assert_eq!(hf, 1)
|
||||||
|
}
|
36
consensus/src/lib.rs
Normal file
36
consensus/src/lib.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use tower::ServiceExt;
|
||||||
|
|
||||||
|
pub mod genesis;
|
||||||
|
pub mod hardforks;
|
||||||
|
#[cfg(feature = "rpc")]
|
||||||
|
pub mod rpc;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Invalid hard fork version: {0}")]
|
||||||
|
InvalidHardForkVersion(&'static str),
|
||||||
|
#[error("Database error: {0}")]
|
||||||
|
Database(#[from] tower::BoxError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Database:
|
||||||
|
tower::Service<DatabaseRequest, Response = DatabaseResponse, Error = tower::BoxError>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: tower::Service<DatabaseRequest, Response = DatabaseResponse, Error = tower::BoxError>>
|
||||||
|
Database for T
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DatabaseRequest {
|
||||||
|
BlockHeader(cuprate_common::BlockID),
|
||||||
|
ChainHeight,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DatabaseResponse {
|
||||||
|
BlockHeader(monero_serai::block::BlockHeader),
|
||||||
|
ChainHeight(u64),
|
||||||
|
}
|
89
consensus/src/rpc.rs
Normal file
89
consensus/src/rpc.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use futures::lock::{OwnedMutexGuard, OwnedMutexLockFuture};
|
||||||
|
use futures::{FutureExt, TryFutureExt};
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use monero_serai::rpc::{HttpRpc, RpcConnection};
|
||||||
|
|
||||||
|
use cuprate_common::BlockID;
|
||||||
|
|
||||||
|
use crate::{DatabaseRequest, DatabaseResponse};
|
||||||
|
|
||||||
|
enum RpcState<R: RpcConnection> {
|
||||||
|
Locked,
|
||||||
|
Acquiring(OwnedMutexLockFuture<monero_serai::rpc::Rpc<R>>),
|
||||||
|
Acquired(OwnedMutexGuard<monero_serai::rpc::Rpc<R>>),
|
||||||
|
}
|
||||||
|
pub struct Rpc<R: RpcConnection>(
|
||||||
|
Arc<futures::lock::Mutex<monero_serai::rpc::Rpc<R>>>,
|
||||||
|
RpcState<R>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Rpc<HttpRpc> {
|
||||||
|
pub fn new_http(addr: String) -> Rpc<HttpRpc> {
|
||||||
|
let http_rpc = HttpRpc::new(addr).unwrap();
|
||||||
|
Rpc(
|
||||||
|
Arc::new(futures::lock::Mutex::new(http_rpc)),
|
||||||
|
RpcState::Locked,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: RpcConnection> Clone for Rpc<R> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Rpc(Arc::clone(&self.0), RpcState::Locked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: RpcConnection + Send + Sync + 'static> tower::Service<DatabaseRequest> for Rpc<R> {
|
||||||
|
type Response = DatabaseResponse;
|
||||||
|
type Error = tower::BoxError;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + 'static>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
loop {
|
||||||
|
match &mut self.1 {
|
||||||
|
RpcState::Locked => self.1 = RpcState::Acquiring(self.0.clone().lock_owned()),
|
||||||
|
RpcState::Acquiring(rpc) => {
|
||||||
|
self.1 = RpcState::Acquired(futures::ready!(rpc.poll_unpin(cx)))
|
||||||
|
}
|
||||||
|
RpcState::Acquired(_) => return Poll::Ready(Ok(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: DatabaseRequest) -> Self::Future {
|
||||||
|
let RpcState::Acquired(rpc) = std::mem::replace(&mut self.1, RpcState::Locked) else {
|
||||||
|
panic!("poll_ready was not called first!");
|
||||||
|
};
|
||||||
|
|
||||||
|
match req {
|
||||||
|
DatabaseRequest::ChainHeight => async move {
|
||||||
|
rpc.get_height()
|
||||||
|
.map_ok(|height| DatabaseResponse::ChainHeight(height.try_into().unwrap()))
|
||||||
|
.map_err(Into::into)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
.boxed(),
|
||||||
|
|
||||||
|
DatabaseRequest::BlockHeader(id) => match id {
|
||||||
|
BlockID::Hash(hash) => async move {
|
||||||
|
rpc.get_block(hash)
|
||||||
|
.map_ok(|block| DatabaseResponse::BlockHeader(block.header))
|
||||||
|
.map_err(Into::into)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
.boxed(),
|
||||||
|
BlockID::Height(height) => async move {
|
||||||
|
rpc.get_block_by_number(height.try_into().unwrap())
|
||||||
|
.map_ok(|block| DatabaseResponse::BlockHeader(block.header))
|
||||||
|
.map_err(Into::into)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
.boxed(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue