Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: PoW fraud proof #133

Merged
merged 6 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 63 additions & 42 deletions crates/floresta-chain/src/pruned_utreexo/chain_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ use bitcoin::consensus::deserialize_partial;
use bitcoin::consensus::Decodable;
use bitcoin::consensus::Encodable;
use bitcoin::hashes::sha256;
use bitcoin::hashes::Hash;
use bitcoin::p2p::utreexo::UtreexoBlock;
use bitcoin::Block;
use bitcoin::BlockHash;
use bitcoin::OutPoint;
Expand Down Expand Up @@ -766,16 +768,62 @@ impl<PersistedState: ChainStore> ChainState<PersistedState> {
Consensus::verify_block_transactions(inputs, &block.txdata, subsidy, verify_script, flags)?;
Ok(())
}

fn get_assumeutreexo_index(&self) -> (BlockHash, u32) {
let guard = read_lock!(self);
guard.consensus.parameters.assumeutreexo_index
}
}

impl<PersistedState: ChainStore> BlockchainInterface for ChainState<PersistedState> {
type Error = BlockchainError;

fn get_fork_point(&self, block: BlockHash) -> Result<BlockHash, Self::Error> {
let fork_point = self.find_fork_point(&self.get_block_header(&block)?)?;
Ok(fork_point.block_hash())
}

fn update_acc(
&self,
acc: Stump,
block: UtreexoBlock,
height: u32,
proof: Proof,
del_hashes: Vec<sha256::Hash>,
) -> Result<Stump, Self::Error> {
Consensus::update_acc(&acc, &block.block, height, proof, del_hashes)
}

fn get_chain_tips(&self) -> Result<Vec<BlockHash>, Self::Error> {
let inner = read_lock!(self);
let mut tips = Vec::new();

tips.push(inner.best_block.best_block);
tips.extend(inner.best_block.alternative_tips.iter());

Ok(tips)
}

fn validate_block(
&self,
block: &Block,
proof: Proof,
inputs: HashMap<OutPoint, TxOut>,
del_hashes: Vec<sha256::Hash>,
acc: Stump,
) -> Result<(), Self::Error> {
// verify the proof
let del_hashes = del_hashes
.iter()
.map(|hash| NodeHash::from(hash.as_byte_array()))
.collect::<Vec<_>>();

if !acc.verify(&proof, &del_hashes)? {
return Err(BlockValidationErrors::InvalidProof.into());
}

let height = self
.get_block_height(&block.block_hash())?
.ok_or(BlockchainError::BlockNotPresent)?;

self.validate_block(block, height, inputs)
}

fn get_block_locator_for_tip(&self, tip: BlockHash) -> Result<Vec<BlockHash>, BlockchainError> {
let mut hashes = Vec::new();
let height = self
Expand Down Expand Up @@ -815,6 +863,7 @@ impl<PersistedState: ChainStore> BlockchainInterface for ChainState<PersistedSta
fn is_in_idb(&self) -> bool {
self.inner.read().ibd
}

fn get_block_height(&self, hash: &BlockHash) -> Result<Option<u32>, Self::Error> {
self.get_disk_block_header(hash)
.map(|header| header.height())
Expand Down Expand Up @@ -925,49 +974,20 @@ impl<PersistedState: ChainStore> BlockchainInterface for ChainState<PersistedSta

Ok(height + chain_params.coinbase_maturity <= current_height)
}

fn get_unbroadcasted(&self) -> Vec<Transaction> {
let mut inner = write_lock!(self);
inner.broadcast_queue.drain(..).collect()
}
}
impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedState> {
fn mark_chain_as_valid(&self) -> Result<bool, BlockchainError> {
let (assume_utreexo_hash, assume_utreexo_height) = self.get_assumeutreexo_index();
let curr_validation_index = self.get_validation_index()?;

// We already ran once
if curr_validation_index >= assume_utreexo_height {
return Ok(true);
}

let mut assumed_hash = self.get_best_block()?.1;
// Walks the chain until finding our assumeutxo block.
// Since this block was passed in before starting florestad, this value should be
// lesser than or equal our current tip. If we don't find that block, it means the
// assumeutxo block was reorged out (or never was in the main chain). That's weird, but we
// should take precoution against it
while let Ok(header) = self.get_block_header(&assumed_hash) {
if header.block_hash() == assume_utreexo_hash {
break;
}
// We've reached genesis and didn't our block
if self.is_genesis(&header) {
break;
}
assumed_hash = self.get_ancestor(&header)?.block_hash();
}

// The assumeutreexo value passed is **not** in the main chain, start validaton from geneis
if assumed_hash != assume_utreexo_hash {
warn!("We are in a diffenrent chain than our default or provided assumeutreexo value. Restarting from genesis");

let mut guard = write_lock!(self);

guard.best_block.validation_index = assumed_hash; // Should be equal to genesis
guard.acc = Stump::new();
fn switch_chain(&self, new_tip: BlockHash) -> Result<(), BlockchainError> {
let new_tip = self.get_block_header(&new_tip)?;
self.reorg(new_tip)
}

return Ok(false);
}
fn mark_chain_as_valid(&self, acc: Stump) -> Result<bool, BlockchainError> {
let assumed_hash = self.get_best_block()?.1;

let mut curr_header = self.get_block_header(&assumed_hash)?;

Expand All @@ -985,7 +1005,6 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta
}

let mut guard = write_lock!(self);
let acc = guard.consensus.parameters.network_roots.clone();
guard.best_block.validation_index = assumed_hash;
info!("assuming chain with hash={assumed_hash}");
guard.acc = acc;
Expand Down Expand Up @@ -1016,10 +1035,12 @@ impl<PersistedState: ChainStore> UpdatableChainstate for ChainState<PersistedSta
);
Ok(())
}

fn toggle_ibd(&self, is_ibd: bool) {
let mut inner = write_lock!(self);
inner.ibd = is_ibd;
}

fn process_rescan_block(&self, block: &Block) -> Result<(), BlockchainError> {
let header = self.get_disk_block_header(&block.block_hash())?;
let height = header.height().expect("Recaning in an invalid tip");
Expand Down
34 changes: 29 additions & 5 deletions crates/floresta-chain/src/pruned_utreexo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use alloc::sync::Arc;

use bitcoin::block::Header as BlockHeader;
use bitcoin::hashes::sha256;
use bitcoin::p2p::utreexo::UtreexoBlock;
use bitcoin::Block;
use bitcoin::BlockHash;
use bitcoin::OutPoint;
Expand Down Expand Up @@ -76,21 +77,45 @@ pub trait BlockchainInterface {
fn get_rescan_index(&self) -> Option<u32>;
/// Returns the height of a block, given it's hash
fn get_block_height(&self, hash: &BlockHash) -> Result<Option<u32>, Self::Error>;
fn update_acc(
&self,
acc: Stump,
block: UtreexoBlock,
height: u32,
proof: Proof,
del_hashes: Vec<sha256::Hash>,
) -> Result<Stump, Self::Error>;

fn get_chain_tips(&self) -> Result<Vec<BlockHash>, Self::Error>;

fn validate_block(
&self,
block: &Block,
proof: Proof,
inputs: HashMap<OutPoint, TxOut>,
del_hashes: Vec<sha256::Hash>,
acc: Stump,
) -> Result<(), Self::Error>;

fn get_fork_point(&self, block: BlockHash) -> Result<BlockHash, Self::Error>;
}
/// [UpdatableChainstate] is a contract that a is expected from a chainstate
/// implementation, that wishes to be updated. Using those methods, a backend like the p2p-node,
/// can notify new blocks and transactions to a chainstate, allowing it to update it's state.
pub trait UpdatableChainstate {
/// This is one of the most important methods for a ChainState, it gets a block and some utreexo data,
/// validates this block and connects to our chain of blocks. This function is meant to
/// be atomic and prone of running in parallel.
/// This is one of the most important methods for a ChainState,
/// it gets a block and some utreexo data, validates this block and
/// connects to our chain of blocks. This function is meant to be atomic
/// and prone of running in parallel.
fn connect_block(
&self,
block: &Block,
proof: Proof,
inputs: HashMap<OutPoint, TxOut>,
del_hashes: Vec<sha256::Hash>,
) -> Result<u32, BlockchainError>;

fn switch_chain(&self, new_tip: BlockHash) -> Result<(), BlockchainError>;
/// Accepts a new header to our chain. This method is called before connect_block, and
/// makes some basic checks on a header and saves it on disk. We only accept a block as
/// valid after calling connect_block.
Expand Down Expand Up @@ -127,12 +152,11 @@ pub trait UpdatableChainstate {
final_height: u32,
acc: Stump,
) -> Result<PartialChainState, BlockchainError>;

/// Marks a chain as fully-valid
///
/// This mimics the behavour of checking every block before this block, and continues
/// from this point
fn mark_chain_as_valid(&self) -> Result<bool, BlockchainError>;
fn mark_chain_as_valid(&self, acc: Stump) -> Result<bool, BlockchainError>;
}

/// [ChainStore] is a trait defining how we interact with our chain database. This definitions
Expand Down
36 changes: 35 additions & 1 deletion crates/floresta-chain/src/pruned_utreexo/partial_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@ impl UpdatableChainstate for PartialChainState {
unimplemented!("partialChainState shouldn't be used to accept new headers")
}

fn switch_chain(&self, _new_tip: BlockHash) -> Result<(), BlockchainError> {
unimplemented!("partialChainState shouldn't be used to switch chains")
}

fn get_partial_chain(
&self,
_initial_height: u32,
Expand All @@ -337,7 +341,7 @@ impl UpdatableChainstate for PartialChainState {
unimplemented!("we don't do rescan")
}

fn mark_chain_as_valid(&self) -> Result<bool, BlockchainError> {
fn mark_chain_as_valid(&self, _acc: Stump) -> Result<bool, BlockchainError> {
unimplemented!("no need to mark as valid")
}
}
Expand Down Expand Up @@ -386,6 +390,36 @@ impl BlockchainInterface for PartialChainState {
unimplemented!("PartialChainState::get_block_header")
}

fn get_chain_tips(&self) -> Result<Vec<BlockHash>, Self::Error> {
unimplemented!("PartialChainState::get_chain_tips")
}

fn validate_block(
&self,
_block: &bitcoin::Block,
_proof: rustreexo::accumulator::proof::Proof,
_inputs: HashMap<bitcoin::OutPoint, bitcoin::TxOut>,
_del_hashes: Vec<bitcoin::hashes::sha256::Hash>,
_acc: Stump,
) -> Result<(), Self::Error> {
unimplemented!("PartialChainState::validate_block")
}

fn get_fork_point(&self, _block: BlockHash) -> Result<BlockHash, Self::Error> {
unimplemented!("PartialChainState::get_fork_point")
}

fn update_acc(
&self,
_acc: Stump,
_block: bitcoin::p2p::utreexo::UtreexoBlock,
_height: u32,
_proof: rustreexo::accumulator::proof::Proof,
_del_hashes: Vec<bitcoin::hashes::sha256::Hash>,
) -> Result<Stump, Self::Error> {
unimplemented!("PartialChainState::update_acc")
}

fn get_block_locator_for_tip(
&self,
_tip: BlockHash,
Expand Down
4 changes: 2 additions & 2 deletions crates/floresta-cli/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ pub trait JsonRPCClient: Sized {
/// Calls a method on the client
///
/// This should call the appropriated rpc method and return a parsed response or error.
fn call<T: serde::de::DeserializeOwned>(&self, method: &str, params: &[Value]) -> Result<T>
fn call<T>(&self, method: &str, params: &[Value]) -> Result<T>
where
T: for<'a> serde::de::Deserialize<'a> + Debug;
T: for<'a> serde::de::Deserialize<'a> + serde::de::DeserializeOwned + Debug;
}

impl<T: JsonRPCClient> FlorestaRPC for T {
Expand Down
2 changes: 1 addition & 1 deletion crates/floresta-wire/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub use p2p_wire::node_context;
pub use p2p_wire::node_interface;
#[cfg(not(target_arch = "wasm32"))]
pub use p2p_wire::running_node;

pub use p2p_wire::UtreexoNodeConfig;
/// NodeHooks is a trait that defines the hooks that a node can use to interact with the network
/// and the blockchain. Every time an event happens, the node will call the corresponding hook.
pub trait NodeHooks {
Expand Down
Loading
Loading