Skip to content

Commit

Permalink
Merge branch 'store-block-not-tip'
Browse files Browse the repository at this point in the history
Allows the storing of blocks that are not tips which, along with a
bigger sync state, increases reorganization tolerance to practically
infinity.

Blocks in block-batch responses are now supported by MMR membership
proofs against an anchor MMR such that all received blocks are proven to
belong to the canonical chain for a specified block. This specific block
has been authenticated through a successful sync challenge response,
making DOS attacks difficult. In other words: This construction makes it
difficult to fill up peers' disks with "garbage" blocks that will not
end up belonging to a canonical chain.
  • Loading branch information
Sword-Smith committed Jan 22, 2025
2 parents 5501468 + 0a083d8 commit 315ec44
Show file tree
Hide file tree
Showing 15 changed files with 1,253 additions and 430 deletions.
3 changes: 1 addition & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,7 @@ pub async fn initialize(cli_args: cli_args::Args) -> Result<i32> {

// Create handshake data which is used when connecting to outgoing peers specified in the
// CLI arguments
let syncing = false;
let networking_state = NetworkingState::new(peer_map, peer_databases, syncing);
let networking_state = NetworkingState::new(peer_map, peer_databases);

let light_state: LightState = LightState::from(latest_block.clone());
let blockchain_archival_state = BlockchainArchivalState {
Expand Down
361 changes: 295 additions & 66 deletions src/main_loop.rs

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions src/mine_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,9 @@ pub(crate) async fn mine(

let (guesser_tx, guesser_rx) = oneshot::channel::<NewBlockFound>();
let (composer_tx, composer_rx) = oneshot::channel::<(Block, Vec<ExpectedUtxo>)>();
let is_syncing = global_state_lock.lock(|s| s.net.syncing).await;
let is_syncing = global_state_lock
.lock(|s| s.net.sync_anchor.is_some())
.await;

let maybe_proposal = global_state_lock.lock_guard().await.block_proposal.clone();
let guess = cli_args.guess;
Expand Down Expand Up @@ -1577,7 +1579,7 @@ pub(crate) mod mine_loop_tests {

guess_worker(
block,
prev_block.header().clone(),
*prev_block.header(),
worker_task_tx,
composer_utxos,
sleepy_guessing,
Expand Down Expand Up @@ -1774,7 +1776,7 @@ pub(crate) mod mine_loop_tests {
let mut rng = thread_rng();
let mut counter = 0;
let mut successor_block = Block::new(
successor_header.clone(),
successor_header,
successor_body.clone(),
appendix,
BlockProof::Invalid,
Expand Down
2 changes: 1 addition & 1 deletion src/models/blockchain/block/block_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub(crate) const ADVANCE_DIFFICULTY_CORRECTION_FACTOR: usize = 4;

pub(crate) const BLOCK_HEADER_VERSION: BFieldElement = BFieldElement::new(0);

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, BFieldCodec, GetSize)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, BFieldCodec, GetSize)]
#[cfg_attr(any(test, feature = "arbitrary-impls"), derive(Arbitrary))]
pub struct BlockHeader {
pub version: BFieldElement,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ pub(crate) mod test {
(parent_header, parent_body, parent_appendix).prop_flat_map(
move |(header, body, appendix)| {
let parent_kernel = BlockKernel {
header: header.clone(),
header,
body: body.clone(),
appendix: appendix.clone(),
};
Expand Down
17 changes: 15 additions & 2 deletions src/models/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::net::SocketAddr;
use serde::Deserialize;
use serde::Serialize;
use tasm_lib::triton_vm::prelude::Digest;
use tasm_lib::twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator;

use super::blockchain::block::block_height::BlockHeight;
use super::blockchain::block::difficulty_control::ProofOfWork;
Expand Down Expand Up @@ -82,6 +83,10 @@ pub struct MainToPeerTaskBatchBlockRequest {
/// that the we would prefer to build on top off, if it belongs to the
/// canonical chain.
pub(crate) known_blocks: Vec<Digest>,

/// The block MMR accumulator relative to which incoming blocks are
/// authenticated.
pub(crate) anchor_mmr: MmrAccumulator,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
Expand Down Expand Up @@ -136,7 +141,15 @@ impl MainToPeerTask {
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum PeerTaskToMain {
NewBlocks(Vec<Block>),
AddPeerMaxBlockHeight((SocketAddr, BlockHeight, ProofOfWork)),
AddPeerMaxBlockHeight {
peer_address: SocketAddr,
claimed_height: BlockHeight,
claimed_cumulative_pow: ProofOfWork,

/// The MMR *after* adding the tip hash, so not the one contained in the
/// tip, but in its child.
claimed_block_mmra: MmrAccumulator,
},
RemovePeerMaxBlockHeight(SocketAddr),
PeerDiscoveryAnswer((Vec<(SocketAddr, u128)>, SocketAddr, u8)), // ([(peer_listen_address)], reported_by, distance)
Transaction(Box<PeerTaskToMainTransaction>),
Expand All @@ -154,7 +167,7 @@ impl PeerTaskToMain {
pub fn get_type(&self) -> String {
match self {
PeerTaskToMain::NewBlocks(_) => "new blocks",
PeerTaskToMain::AddPeerMaxBlockHeight(_) => "add peer max block height",
PeerTaskToMain::AddPeerMaxBlockHeight { .. } => "add peer max block height",
PeerTaskToMain::RemovePeerMaxBlockHeight(_) => "remove peer max block height",
PeerTaskToMain::PeerDiscoveryAnswer(_) => "peer discovery answer",
PeerTaskToMain::Transaction(_) => "transaction",
Expand Down
35 changes: 29 additions & 6 deletions src/models/peer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use serde::Deserialize;
use serde::Serialize;
use tasm_lib::twenty_first::prelude::Mmr;
use tasm_lib::twenty_first::prelude::MmrMembershipProof;
use tasm_lib::twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator;
use tracing::trace;
use transaction_notification::TransactionNotification;
use transfer_transaction::TransferTransaction;
Expand Down Expand Up @@ -155,8 +156,10 @@ pub enum NegativePeerSanction {
BatchBlocksInvalidStartHeight,
BatchBlocksUnknownRequest,
BatchBlocksRequestEmpty,
BatchBlocksRequestTooManyDigests,
InvalidTransaction,
UnconfirmableTransaction,
InvalidBlockMmrAuthentication,

InvalidTransferBlock,

Expand Down Expand Up @@ -221,6 +224,12 @@ impl Display for NegativePeerSanction {
NegativePeerSanction::TimedOutSyncChallengeResponse => {
"timed-out sync challenge response"
}
NegativePeerSanction::InvalidBlockMmrAuthentication => {
"invalid block mmr authentication"
}
NegativePeerSanction::BatchBlocksRequestTooManyDigests => {
"too many digests in batch block request"
}
};
write!(f, "{string}")
}
Expand Down Expand Up @@ -264,7 +273,7 @@ impl Sanction for NegativePeerSanction {
NegativePeerSanction::InvalidBlock(_) => -10,
NegativePeerSanction::DifferentGenesis => i32::MIN,
NegativePeerSanction::ForkResolutionError((_height, count, _digest)) => {
i32::from(count).saturating_mul(-3)
i32::from(count).saturating_mul(-1)
}
NegativePeerSanction::SynchronizationTimeout => -5,
NegativePeerSanction::FloodPeerListResponse => -2,
Expand All @@ -288,6 +297,8 @@ impl Sanction for NegativePeerSanction {
NegativePeerSanction::UnexpectedSyncChallengeResponse => -1,
NegativePeerSanction::InvalidTransferBlock => -50,
NegativePeerSanction::TimedOutSyncChallengeResponse => -50,
NegativePeerSanction::InvalidBlockMmrAuthentication => -4,
NegativePeerSanction::BatchBlocksRequestTooManyDigests => -50,
}
}
}
Expand Down Expand Up @@ -483,6 +494,16 @@ pub struct BlockRequestBatch {

/// Indicates the maximum allowed number of blocks in the response.
pub(crate) max_response_len: usize,

/// The block MMR accumulator of the tip of the chain which the node is
/// syncing towards. Its number of leafs is the block height the node is
/// syncing towards.
///
/// The receiver needs this value to know which MMR authentication paths to
/// attach to the blocks in the response. These paths allow the receiver of
/// a batch of blocks to verify that the received blocks are indeed
/// ancestors to a given tip.
pub(crate) anchor: MmrAccumulator,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
Expand All @@ -506,7 +527,7 @@ pub(crate) enum PeerMessage {
BlockRequestByHash(Digest),

BlockRequestBatch(BlockRequestBatch), // TODO: Consider restricting this in size
BlockResponseBatch(Vec<TransferBlock>), // TODO: Consider restricting this in size
BlockResponseBatch(Vec<(TransferBlock, MmrMembershipProof)>), // TODO: Consider restricting this in size
UnableToSatisfyBatchRequest,

SyncChallenge(SyncChallenge),
Expand Down Expand Up @@ -741,7 +762,7 @@ impl SyncChallengeResponse {
/// Determine whether the `SyncChallengeResponse` answers the given
/// `IssuedSyncChallenge`, and not some other one.
pub(crate) fn matches(&self, issued_challenge: IssuedSyncChallenge) -> bool {
let Ok(tip_predecessor) = Block::try_from(self.tip_parent.clone()) else {
let Ok(tip_parent) = Block::try_from(self.tip_parent.clone()) else {
return false;
};
let Ok(tip) = Block::try_from(self.tip.clone()) else {
Expand All @@ -754,7 +775,7 @@ impl SyncChallengeResponse {
.all(|((_, child), challenge_height)| child.header.height == *challenge_height)
&& issued_challenge.challenge.tip_digest == tip.hash()
&& issued_challenge.accumulated_pow == tip.header().cumulative_proof_of_work
&& tip.has_proof_of_work(tip_predecessor.header())
&& tip.has_proof_of_work(tip_parent.header())
}

/// Determine whether the proofs in `SyncChallengeResponse` are valid. Also
Expand All @@ -772,6 +793,8 @@ impl SyncChallengeResponse {
return false;
}

let mut mmra_anchor = tip.body().block_mmr_accumulator.to_owned();
mmra_anchor.append(tip.hash());
for ((parent, child), membership_proof) in
self.blocks.iter().zip(self.membership_proofs.iter())
{
Expand All @@ -781,8 +804,8 @@ impl SyncChallengeResponse {
if !membership_proof.verify(
child.header().height.into(),
child.hash(),
&tip.body().block_mmr_accumulator.peaks(),
tip.header().height.into(),
&mmra_anchor.peaks(),
mmra_anchor.num_leafs(),
) {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion src/models/peer/transfer_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl TryFrom<&Block> for TransferBlock {
}
};
Ok(Self {
header: block.kernel.header.clone(),
header: block.kernel.header,
body: block.kernel.body.clone(),
proof,
appendix: block.kernel.appendix.clone(),
Expand Down
Loading

0 comments on commit 315ec44

Please sign in to comment.