Skip to content
Open
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
33 changes: 33 additions & 0 deletions app/rpc_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,39 @@ impl<const ENABLE_PRIVATE_API: bool> rpc_api::node::RpcServer
Ok(Some(block_hash))
}

async fn get_block_hash(
&self,
height: u32,
) -> RpcResult<Option<thunder::types::BlockHash>> {
self.app.node.try_get_block_hash(height).map_err(custom_err)
}

async fn get_block_index(
&self,
block_hash: thunder::types::BlockHash,
) -> RpcResult<rpc_api::node::GetBlockIndexResponse> {
let body = self.app.node.get_body(block_hash).map_err(custom_err)?;
let txs = body
.transactions
.iter()
.map(|tx| rpc_api::node::BlockIndexTx {
txid: tx.txid(),
size: tx.canonical_size(),
raw: const_hex::encode(tx.canonical_encoding()),
})
.collect();
let events = self
.app
.node
.get_block_index_events(block_hash)
.map_err(custom_err)?;
Ok(rpc_api::node::GetBlockIndexResponse {
txs,
deposits: events.deposits,
bundle_spends: events.bundle_spends,
})
}

async fn get_bmm_inclusions(
&self,
block_hash: thunder::types::BlockHash,
Expand Down
14 changes: 14 additions & 0 deletions cli/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ pub enum Command {
GetBlock {
block_hash: thunder::types::BlockHash,
},
/// Get the block hash at the specified height, if it exists
GetBlockHash { height: u32 },
/// Get everything about a block that its body does not carry
GetBlockIndex {
block_hash: thunder::types::BlockHash,
},
/// Assemble a block to blind merge mine, without requesting BMM for it
GetBlockTemplate,
/// Get mainchain blocks that commit to a specified block hash
Expand Down Expand Up @@ -249,6 +255,14 @@ where
let block_hash = rpc_client.get_best_sidechain_block_hash().await?;
serde_json::to_string_pretty(&block_hash)?
}
Command::GetBlockHash { height } => {
let block_hash = rpc_client.get_block_hash(height).await?;
serde_json::to_string_pretty(&block_hash)?
}
Command::GetBlockIndex { block_hash } => {
let block_index = rpc_client.get_block_index(block_hash).await?;
serde_json::to_string_pretty(&block_index)?
}
Command::GetBlockTemplate => {
let template = rpc_client.get_block_template().await?;
serde_json::to_string_pretty(&template)?
Expand Down
4 changes: 3 additions & 1 deletion lib/node/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use transitive::Transitive;

use crate::{
archive, mempool, net, state,
types::{AmountOverflowError, AmountUnderflowError, proto},
types::{AmountOverflowError, AmountUnderflowError, BlockHash, proto},
};

pub mod mainchain_task {
Expand Down Expand Up @@ -176,6 +176,8 @@ pub enum Error {
Net(#[from] Box<net::Error>),
#[error("net task error")]
NetTask(#[source] Box<net_task::Error>),
#[error("block {block_hash} is not in the current chain")]
NotInCurrentChain { block_hash: BlockHash },
#[error("peer info stream closed")]
PeerInfoRxClosed,
#[error("Receive mainchain task response cancelled")]
Expand Down
47 changes: 36 additions & 11 deletions lib/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::{
use bitcoin::amount::CheckedSum;
use fallible_iterator::{FallibleIterator, IteratorExt};
use futures::Stream;
use sneed::{DbError, Env, EnvError, RwTxnError};
use sneed::{DbError, Env, EnvError, RoTxn, RwTxnError};
use tokio::sync::Mutex;
use tonic::transport::Channel;

Expand All @@ -20,9 +20,10 @@ use crate::{
state::State,
types::{
Accumulator, Address, AmountOverflowError, AmountUnderflowError,
Authorized, AuthorizedTransaction, BlockHash, BmmResult, Body,
FilledTransaction, GetValue, Header, Network, OutPoint, OutPointKey,
Output, SpentOutput, Tip, Transaction, Txid, WithdrawalBundle,
Authorized, AuthorizedTransaction, BlockHash, BlockIndexEvents,
BmmResult, Body, FilledTransaction, GetValue, Header, Network,
OutPoint, OutPointKey, Output, SpentOutput, Tip, Transaction, Txid,
WithdrawalBundle,
net::{Peer, PeerAddress, ResolvedPeerAddress},
proto::{self, mainchain},
},
Expand Down Expand Up @@ -373,29 +374,53 @@ where
Ok(self.archive.get_header(&txn, block_hash)?)
}

/// Get the block hash at the specified height in the current chain,
/// if it exists
pub fn try_get_block_hash(
/// Get the coin movements that the block applied outside its body
pub fn get_block_index_events(
&self,
block_hash: BlockHash,
) -> Result<BlockIndexEvents, Error> {
let rotxn = self.env.read_txn().map_err(EnvError::from)?;
let height = self.archive.get_height(&rotxn, block_hash)?;
// The events are keyed by height, so a block off the current chain
// would read another block's events.
if self.try_get_block_hash_read(&rotxn, height)? != Some(block_hash) {
return Err(Error::NotInCurrentChain { block_hash });
}
let events = self.state.get_block_index_events(&rotxn, height)?;
Ok(events)
}

fn try_get_block_hash_read(
&self,
rotxn: &RoTxn,
height: u32,
) -> Result<Option<BlockHash>, Error> {
let rotxn = self.env.read_txn().map_err(EnvError::from)?;
let Some(tip) = self.state.try_get_tip(&rotxn)? else {
let Some(tip) = self.state.try_get_tip(rotxn)? else {
return Ok(None);
};
let Some(tip_height) = self.state.try_get_height(&rotxn)? else {
let Some(tip_height) = self.state.try_get_height(rotxn)? else {
return Ok(None);
};
if tip_height >= height {
self.archive
.ancestors(&rotxn, tip)
.ancestors(rotxn, tip)
.nth((tip_height - height) as usize)
.map_err(Error::from)
} else {
Ok(None)
}
}

/// Get the block hash at the specified height in the current chain,
/// if it exists
pub fn try_get_block_hash(
&self,
height: u32,
) -> Result<Option<BlockHash>, Error> {
let rotxn = self.env.read_txn().map_err(EnvError::from)?;
self.try_get_block_hash_read(&rotxn, height)
}

pub fn try_get_body(
&self,
block_hash: BlockHash,
Expand Down
94 changes: 85 additions & 9 deletions lib/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ use sneed::{
use crate::{
types::{
Accumulator, Address, AmountOverflowError, AmountUnderflowError,
Authorization, Authorized, AuthorizedTransaction, BlockHash, Body,
FilledTransaction, GetAddress, GetValue, Header, InPoint, M6id,
MerkleRoot, OutPoint, OutPointKey, Output, PointedOutput,
PointedOutputRef, SpentOutput, Transaction, UtreexoNodeHash,
UtreexoProof, VERSION, Verify, Version, WithdrawalBundle,
WithdrawalBundleStatus, proto::mainchain::TwoWayPegData,
Authorization, Authorized, AuthorizedTransaction, BlockHash,
BlockIndexEvents, Body, FilledTransaction, GetAddress, GetValue,
Header, InPoint, M6id, MerkleRoot, OutPoint, OutPointKey, Output,
PointedOutput, PointedOutputRef, SpentOutput, Transaction,
UtreexoNodeHash, UtreexoProof, VERSION, Verify, Version,
WithdrawalBundle, WithdrawalBundleStatus,
proto::mainchain::TwoWayPegData,
},
util::Watchable,
};
Expand Down Expand Up @@ -82,6 +83,10 @@ pub struct State {
SerdeBincode<M6id>,
SerdeBincode<(WithdrawalBundleInfo, RollBack<WithdrawalBundleStatus>)>,
>,
/// Coin movements that no block body carries, keyed by the height that
/// applied them
pub block_index_events:
DatabaseUnique<SerdeBincode<u32>, SerdeBincode<BlockIndexEvents>>,
/// deposit blocks and the height at which they were applied, keyed sequentially
pub deposit_blocks: DatabaseUnique<
SerdeBincode<u32>,
Expand All @@ -97,7 +102,7 @@ pub struct State {
}

impl State {
pub const NUM_DBS: u32 = 11;
pub const NUM_DBS: u32 = 12;

pub fn new<Tls>(env: &sneed::Env<Tls>) -> Result<Self, Error> {
let mut rwtxn = env.write_txn().map_err(EnvError::from)?;
Expand All @@ -124,6 +129,9 @@ impl State {
let withdrawal_bundles =
DatabaseUnique::create(env, &mut rwtxn, "withdrawal_bundles")
.map_err(EnvError::from)?;
let block_index_events =
DatabaseUnique::create(env, &mut rwtxn, "block_index_events")
.map_err(EnvError::from)?;
let deposit_blocks =
DatabaseUnique::create(env, &mut rwtxn, "deposit_blocks")
.map_err(EnvError::from)?;
Expand Down Expand Up @@ -156,13 +164,27 @@ impl State {
pending_withdrawal_bundle,
latest_failed_withdrawal_bundle,
withdrawal_bundles,
block_index_events,
deposit_blocks,
withdrawal_bundle_event_blocks,
utreexo_accumulator,
_version: version,
})
}

/// Coin movements that the block at this height applied outside its body.
pub fn get_block_index_events(
&self,
rotxn: &RoTxn,
height: u32,
) -> Result<BlockIndexEvents, Error> {
let events = self
.block_index_events
.try_get(rotxn, &height)?
.unwrap_or_default();
Ok(events)
}

pub fn try_get_tip(
&self,
rotxn: &RoTxn,
Expand Down Expand Up @@ -624,8 +646,9 @@ mod test {
use crate::{
state::State,
types::{
Address, FilledTransaction, InPoint, OutPoint, OutPointKey, Output,
OutputContent, PointedOutputRef, SpentOutput, Transaction, hash,
Address, BlockIndexEvents, FilledTransaction, InPoint, M6id,
OutPoint, OutPointKey, Output, OutputContent, PointedOutputRef,
SpentOutput, Transaction, hash,
},
};

Expand Down Expand Up @@ -784,4 +807,57 @@ mod test {
);
Ok(())
}

#[test]
fn block_index_events_round_trip() -> anyhow::Result<()> {
let (_temp_dir, env, state) = fresh_state("block-index-events")?;
let deposit_outpoint = |byte: u8| {
OutPoint::Deposit(bitcoin::OutPoint {
txid: bitcoin::Txid::from_byte_array([byte; 32]),
vout: 0,
})
};
let events = BlockIndexEvents {
deposits: vec![(
deposit_outpoint(1),
value_output(Address::ALL_ZEROS, 5000),
)],
bundle_spends: vec![(
deposit_outpoint(2),
M6id(bitcoin::Txid::from_byte_array([3; 32])),
)],
};
{
let mut rwtxn = env.write_txn()?;
state.block_index_events.put(&mut rwtxn, &7, &events)?;
rwtxn.commit()?;
}
{
let rotxn = env.read_txn()?;
anyhow::ensure!(state.get_block_index_events(&rotxn, 7)? == events);
// A height that moved nothing outside its body reads as empty.
anyhow::ensure!(
state.get_block_index_events(&rotxn, 8)?.is_empty()
);
}

// A disconnect drops the events, so a reorg leaves nothing behind for
// the block that takes the height.
{
let mut rwtxn = env.write_txn()?;
state.block_index_events.delete(&mut rwtxn, &7)?;
rwtxn.commit()?;
}
let rotxn = env.read_txn()?;
anyhow::ensure!(state.get_block_index_events(&rotxn, 7)?.is_empty());

// A height that moved nothing writes no row, so deleting it again is
// still safe.
{
let mut rwtxn = env.write_txn()?;
state.block_index_events.delete(&mut rwtxn, &8)?;
rwtxn.commit()?;
}
Ok(())
}
}
Loading
Loading