mirror of
https://github.com/Cuprate/cuprate.git
synced 2024-12-22 19:49:28 +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]
|
||||
|
||||
members = [
|
||||
# "common",
|
||||
"common",
|
||||
"consensus",
|
||||
#"cuprate",
|
||||
# "database",
|
||||
"net/levin",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
pub mod hardforks;
|
||||
//pub mod hardforks;
|
||||
pub mod network;
|
||||
pub mod pruning;
|
||||
|
||||
pub use hardforks::HardForks;
|
||||
//pub use hardforks::HardForks;
|
||||
pub use network::Network;
|
||||
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_STRIPE_SIZE: u64 = 4096;
|
||||
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)]
|
||||
pub enum Network {
|
||||
MainNet,
|
||||
TestNet,
|
||||
StageNet,
|
||||
Mainnet,
|
||||
Testnet,
|
||||
Stagenet,
|
||||
}
|
||||
|
||||
impl Network {
|
||||
pub fn network_id(&self) -> [u8; 16] {
|
||||
match self {
|
||||
Network::MainNet => MAINNET_NETWORK_ID,
|
||||
Network::TestNet => TESTNET_NETWORK_ID,
|
||||
Network::StageNet => STAGENET_NETWORK_ID,
|
||||
Network::Mainnet => MAINNET_NETWORK_ID,
|
||||
Network::Testnet => TESTNET_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