Building the ChainState
The next step is building the ChainState
struct, which validates blocks and updates the ChainStore
.
Filename: pruned_utreexo/chain_state.rs
// Path: floresta-chain/src/pruned_utreexo/chain_state.rs
pub struct ChainState<PersistedState: ChainStore> {
inner: RwLock<ChainStateInner<PersistedState>>,
}
Note that the RwLock
that we use to wrap ChainStateInner
is not the one from the standard library but from the spin
crate, thus allowing no_std
.
std::sync::RwLock
relies on the OS to block and wake threads when the lock is available, whilespin::RwLock
uses a spinlock which does not require OS support for thread management, as the thread simply keeps running (and checking for lock availability) instead of sleeping.
The builder for ChainState
has this signature:
// Path: floresta-chain/src/pruned_utreexo/chain_state.rs
pub fn new(
chainstore: PersistedState,
network: Network,
assume_valid: AssumeValidArg,
) -> ChainState<PersistedState> {
The first argument is our ChainStore
implementation, the second one is a Network
enum, and thirdly the AssumeValidArg
enum.
Network
is an enum with four variants: Bitcoin
(mainchain), Testnet
, Regtest
and Signet
. It's declared in the lib.rs of floresta-chain
, along with conversions from and to bitcoin::network::Network
, an identical enum from the bitcoin
crate.
// Path: floresta-chain/src/lib.rs
// This is the only thing implemented in lib.rs
pub enum Network {
Bitcoin,
Testnet,
Regtest,
Signet,
}
// impl From<bitcoin::network::Network> for Network { ... }
// impl From<Network> for bitcoin::network::Network { ... }
The Assume-Valid Lore
The assume_valid
argument refers to a Bitcoin Core option that allows nodes during IBD to assume the validity of scripts (mainly signatures) up to a certain block.
Nodes with this option enabled will still choose the most PoW chain (the best tip), and will only skip script validation if the Assume-Valid
block is in that chain. Otherwise, if the Assume-Valid
block is not in the best chain, they will validate everything.
When users use the default
Assume-Valid
hash, hardcoded in the software, they aren't blindly trusting script validity. These hashes are reviewed through the same open-source process as other security-critical changes in Bitcoin Core, so the trust model is unchanged.
In Bitcoin Core, the hardcoded Assume-Valid
block hash is included in src/kernel/chainparams.cpp.
Filename: pruned_utreexo/chain_state.rs
// Path: floresta-chain/src/pruned_utreexo/chain_state.rs
pub enum AssumeValidArg {
Disabled,
Hardcoded,
UserInput(BlockHash),
}
Disabled
means the node verifies all scripts, Hardcoded
means the node uses the default block hash that has been hardcoded in the software (and validated by maintainers, developers and reviewers), and UserInput
means using a hash that the node runner provides, although the validity of the scripts up to this block should have been externally validated.
Genesis and Assume-Valid Blocks
The first part of the body of ChainState::new
(let's omit the impl
block from now on):
// Path: floresta-chain/src/pruned_utreexo/chain_state.rs
pub fn new(
chainstore: PersistedState,
network: Network,
assume_valid: AssumeValidArg,
) -> ChainState<PersistedState> {
let parameters = network.into();
let genesis = genesis_block(¶meters);
chainstore
.save_header(&super::chainstore::DiskBlockHeader::FullyValid(
genesis.header,
0,
))
.expect("Error while saving genesis");
chainstore
.update_block_index(0, genesis.block_hash())
.expect("Error updating index");
let assume_valid = ChainParams::get_assume_valid(network, assume_valid);
// ...
ChainState {
inner: RwLock::new(ChainStateInner {
chainstore,
acc: Stump::new(),
best_block: BestChain {
best_block: genesis.block_hash(),
depth: 0,
validation_index: genesis.block_hash(),
alternative_tips: Vec::new(),
assume_valid_index: 0,
},
broadcast_queue: Vec::new(),
subscribers: Vec::new(),
fee_estimation: (1_f64, 1_f64, 1_f64),
ibd: true,
consensus: Consensus { parameters },
assume_valid,
}),
}
}
First, we use the genesis_block
function from bitcoin
to retrieve the genesis block based on the specified parameters, which are determined by our Network
.
Then we save the genesis header into chainstore
, which of course is FullyValid
and has height 0. We also link the index 0 with the genesis block hash.
Finally, we get an Option<BlockHash>
by calling the ChainParams::get_assume_valid
function, which takes a Network
and an AssumeValidArg
.
Filename: pruned_utreexo/chainparams.rs
// Path: floresta-chain/src/pruned_utreexo/chainparams.rs
// Omitted: impl ChainParams {
pub fn get_assume_valid(network: Network, arg: AssumeValidArg) -> Option<BlockHash> {
match arg {
// No assume-valid hash
AssumeValidArg::Disabled => None,
// Use the user-provided hash
AssumeValidArg::UserInput(hash) => Some(hash),
// Fetch the hardcoded values, depending on the network
AssumeValidArg::Hardcoded => match network {
Network::Bitcoin => Some(bhash!(
"00000000000000000000569f4d863c27e667cbee8acc8da195e7e5551658e6e9"
)),
Network::Testnet => Some(bhash!(
"000000000000001142ad197bff16a1393290fca09e4ca904dd89e7ae98a90fcd"
)),
Network::Signet => Some(bhash!(
"0000003ed17b9c93954daab00d73ccbd0092074c4ebfc751c7458d58b827dfea"
)),
Network::Regtest => Some(bhash!(
"0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
)),
},
}
}
The final part of ChainState::new
just returns the instance of ChainState
with the ChainStateInner
initialized. We will see this initialization next.