Skip to content

Commit

Permalink
Fast sync part 2 (#156)
Browse files Browse the repository at this point in the history
* boilerplate

* Cargo.lock

* Stub of block validation

* Block validation (diff by @dllud)

* Cargo.lock

* Complete implementation of block validation request

* Apply suggestions from code review

Co-authored-by: Boog900 <[email protected]>

* More suggestions

* Update consensus/fast-sync/src/fast_sync.rs

* Update consensus/fast-sync/src/fast_sync.rs

---------

Co-authored-by: Boog900 <[email protected]>
  • Loading branch information
jomuel and Boog900 authored Jun 14, 2024
1 parent f07d089 commit c837f2f
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 18 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions consensus/fast-sync/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ path = "src/create.rs"
[dependencies]
clap = { workspace = true, features = ["derive", "std"] }
cuprate-blockchain = { path = "../../storage/cuprate-blockchain" }
cuprate-consensus = { path = ".." }
cuprate-consensus-rules = { path = "../rules" }
cuprate-types = { path = "../../types" }
hex.workspace = true
hex-literal.workspace = true
monero-serai.workspace = true
rayon.workspace = true
sha3 = "0.10.8"
thiserror.workspace = true
tokio = { workspace = true, features = ["full"] }
tower.workspace = true

Expand Down
183 changes: 165 additions & 18 deletions consensus/fast-sync/src/fast_sync.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
use std::{
cmp,
collections::HashMap,
future::Future,
pin::Pin,
task::{Context, Poll},
};

#[allow(unused_imports)]
use hex_literal::hex;
use tower::Service;
use monero_serai::{
block::Block,
transaction::{Input, Transaction},
};
use tower::{Service, ServiceExt};

use cuprate_consensus::{
context::{BlockChainContextRequest, BlockChainContextResponse},
transactions::TransactionVerificationData,
};
use cuprate_consensus_rules::{miner_tx::MinerTxError, ConsensusError};
use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation};

use crate::{hash_of_hashes, BlockId, HashOfHashes};

#[cfg(not(test))]
static HASHES_OF_HASHES: &[HashOfHashes] = &include!("./data/hashes_of_hashes");

Expand All @@ -31,45 +44,86 @@ fn max_height() -> u64 {
(HASHES_OF_HASHES.len() * BATCH_SIZE) as u64
}

pub enum FastSyncRequest {
ValidateHashes {
start_height: u64,
block_ids: Vec<BlockId>,
},
}

#[derive(Debug, PartialEq)]
pub struct ValidBlockId(BlockId);

fn valid_block_ids(block_ids: &[BlockId]) -> Vec<ValidBlockId> {
block_ids.iter().map(|b| ValidBlockId(*b)).collect()
}

#[allow(clippy::large_enum_variant)]
pub enum FastSyncRequest {
ValidateHashes {
start_height: u64,
block_ids: Vec<BlockId>,
},
ValidateBlock {
block: Block,
txs: HashMap<[u8; 32], Transaction>,
token: ValidBlockId,
},
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug, PartialEq)]
pub enum FastSyncResponse {
ValidateHashes {
validated_hashes: Vec<ValidBlockId>,
unknown_hashes: Vec<BlockId>,
},
ValidateBlock(VerifiedBlockInformation),
}

#[derive(Debug, PartialEq)]
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum FastSyncError {
InvalidStartHeight, // start_height not a multiple of BATCH_SIZE
Mismatch, // hash does not match
NothingToDo, // no complete batch to check
OutOfRange, // start_height too high
#[error("Block does not match its expected hash")]
BlockHashMismatch,

#[error("Start height must be a multiple of the batch size")]
InvalidStartHeight,

#[error("Hash of hashes mismatch")]
Mismatch,

#[error("Given range too small for fast sync (less than one batch)")]
NothingToDo,

#[error("Start height too high for fast sync")]
OutOfRange,

#[error("Block does not have the expected height entry")]
BlockHeightMismatch,

#[error("Block does not contain the expected transaction list")]
TxsIncludedWithBlockIncorrect,

#[error(transparent)]
Consensus(#[from] ConsensusError),

#[error(transparent)]
MinerTx(#[from] MinerTxError),

#[error("Database error: {0}")]
DbErr(String),
}

impl From<tower::BoxError> for FastSyncError {
fn from(error: tower::BoxError) -> Self {
Self::DbErr(error.to_string())
}
}

#[allow(dead_code)]
pub struct FastSyncService<C> {
context_svc: C,
}

impl<C> FastSyncService<C>
where
C: Service<FastSyncRequest, Response = FastSyncResponse, Error = FastSyncError>
+ Clone
C: Service<
BlockChainContextRequest,
Response = BlockChainContextResponse,
Error = tower::BoxError,
> + Clone
+ Send
+ 'static,
{
Expand All @@ -81,8 +135,11 @@ where

impl<C> Service<FastSyncRequest> for FastSyncService<C>
where
C: Service<FastSyncRequest, Response = FastSyncResponse, Error = FastSyncError>
+ Clone
C: Service<
BlockChainContextRequest,
Response = BlockChainContextResponse,
Error = tower::BoxError,
> + Clone
+ Send
+ 'static,
C::Future: Send + 'static,
Expand All @@ -97,12 +154,17 @@ where
}

fn call(&mut self, req: FastSyncRequest) -> Self::Future {
let context_svc = self.context_svc.clone();

Box::pin(async move {
match req {
FastSyncRequest::ValidateHashes {
start_height,
block_ids,
} => validate_hashes(start_height, &block_ids).await,
FastSyncRequest::ValidateBlock { block, txs, token } => {
validate_block(context_svc, block, txs, token).await
}
}
})
}
Expand Down Expand Up @@ -149,6 +211,91 @@ async fn validate_hashes(
})
}

async fn validate_block<C>(
mut context_svc: C,
block: Block,
mut txs: HashMap<[u8; 32], Transaction>,
token: ValidBlockId,
) -> Result<FastSyncResponse, FastSyncError>
where
C: Service<
BlockChainContextRequest,
Response = BlockChainContextResponse,
Error = tower::BoxError,
> + Send
+ 'static,
C::Future: Send + 'static,
{
let BlockChainContextResponse::Context(checked_context) = context_svc
.ready()
.await?
.call(BlockChainContextRequest::GetContext)
.await?
else {
panic!("Context service returned wrong response!");
};

let block_chain_ctx = checked_context.unchecked_blockchain_context().clone();

let block_hash = block.hash();
if block_hash != token.0 {
return Err(FastSyncError::BlockHashMismatch);
}

let block_blob = block.serialize();

let Some(Input::Gen(height)) = block.miner_tx.prefix.inputs.first() else {
return Err(FastSyncError::MinerTx(MinerTxError::InputNotOfTypeGen));
};
if *height != block_chain_ctx.chain_height {
return Err(FastSyncError::BlockHeightMismatch);
}

let mut verified_txs = Vec::with_capacity(txs.len());
for tx in &block.txs {
let tx = txs
.remove(tx)
.ok_or(FastSyncError::TxsIncludedWithBlockIncorrect)?;

let data = TransactionVerificationData::new(tx)?;
verified_txs.push(VerifiedTransactionInformation {
tx_blob: data.tx_blob,
tx_weight: data.tx_weight,
fee: data.fee,
tx_hash: data.tx_hash,
tx: data.tx,
});
}

let total_fees = verified_txs.iter().map(|tx| tx.fee).sum::<u64>();
let total_outputs = block
.miner_tx
.prefix
.outputs
.iter()
.map(|output| output.amount.unwrap_or(0))
.sum::<u64>();

let generated_coins = total_outputs - total_fees;

let weight =
block.miner_tx.weight() + verified_txs.iter().map(|tx| tx.tx_weight).sum::<usize>();

Ok(FastSyncResponse::ValidateBlock(VerifiedBlockInformation {
block_blob,
txs: verified_txs,
block_hash,
pow_hash: [0u8; 32],
height: *height,
generated_coins,
weight,
long_term_weight: block_chain_ctx.next_block_long_term_weight(weight),
cumulative_difficulty: block_chain_ctx.cumulative_difficulty
+ block_chain_ctx.next_difficulty,
block,
}))
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit c837f2f

Please sign in to comment.