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
1 change: 0 additions & 1 deletion doc/src/design/ir.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,6 @@ containing the correctly serialized transactions `v15` and `v30`.
| `AddWitness` | Adds an item to the witness stack. |
| `EndWitnessStack`| Finishes building the witness stack. |
| **Compact Block** | **Construct a compact block.**|
| `BuildCompactBlock` | Builds a compact block. |
| **Blocktxn building** | **Construct a BIP152 blocktxn message**|
| `BeginBuildBlockTxn` | Begins building a blocktxn message after sending a compact block. |
| `AddTxToBlockTxn` | Adds a transaction to the blocktxn message. |
Expand Down
63 changes: 63 additions & 0 deletions fuzzamoto-ir/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ impl ProgramBuilder {

Ok(())
}

pub fn append_program_without_threshold(
&mut self,
program: Program,
Expand Down Expand Up @@ -354,6 +355,68 @@ impl ProgramBuilder {
}
}

/// Given a Block variable index, return the `ConstBlockTransactions` variable index and the
/// list of `ConstTx` variable indices that were added to that block. Both are guaranteed to
/// belong to the same block, making them safe to pass to `AddPrefillTx`.
#[must_use]
pub fn get_block_vars(&self, block_var_index: usize) -> Option<(usize, Vec<usize>)> {
// Walk instructions tracking the running variable index to find the BuildBlock
// instruction whose Block output sits at block_var_index.
// BuildBlock outputs: Header (offset 0), Block (offset 1), CoinbaseTx (offset 2).
let mut var_count = 0;
for instruction in &self.instructions {
if matches!(instruction.operation, Operation::BuildBlock) {
// offset 1 is the Block variable
if var_count + 1 == block_var_index {
// inputs[4] is the ConstBlockTransactions variable
let block_transactions_idx = instruction.inputs[4];

// Find the MutBlockTransactions that was closed into block_transactions_idx
// by tracing the EndBlockTransactions instruction.
let tx_var_indices =
self.get_tx_var_indices_for_block_transactions(block_transactions_idx);

return Some((block_transactions_idx, tx_var_indices));
}
}
var_count +=
instruction.operation.num_outputs() + instruction.operation.num_inner_outputs();
}
None
}

/// Given a `ConstBlockTransactions` variable index, return the `ConstTx` variable indices that
/// were added to it via `AddTx` instructions.
#[must_use]
fn get_tx_var_indices_for_block_transactions(
&self,
block_transactions_idx: usize,
) -> Vec<usize> {
// Find the EndBlockTransactions instruction whose output is block_transactions_idx,
// then collect all AddTx instructions that fed into the same MutBlockTransactions.
let mut var_count = 0;
for instruction in &self.instructions {
if matches!(instruction.operation, Operation::EndBlockTransactions)
&& var_count == block_transactions_idx
{
// inputs[0] is the MutBlockTransactions being closed
let mut_block_transactions_idx = instruction.inputs[0];
return self
.instructions
.iter()
.filter(|i| {
matches!(i.operation, Operation::AddTx)
&& i.inputs[0] == mut_block_transactions_idx
})
.map(|i| i.inputs[1]) // inputs[1] is the ConstTx var index
.collect();
}
var_count +=
instruction.operation.num_outputs() + instruction.operation.num_inner_outputs();
}
Vec::new()
}

/// Get the nearest (searched in reverse) available (in the current scope) variable of a given
/// type
#[must_use]
Expand Down
139 changes: 99 additions & 40 deletions fuzzamoto-ir/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ pub type ConnectionId = usize;

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct CompiledMetadata {
// Map from blockhash to (block variable index, list of transaction variable indices)
block_tx_var_map: HashMap<bitcoin::BlockHash, (usize, usize, Vec<usize>)>,
// Map from blockhash to (header_var, block_var, block_transactions_var, tx_var_indices)
block_tx_var_map: HashMap<bitcoin::BlockHash, (usize, usize, usize, Vec<usize>)>,
// Map from connection ids to connection variable indices.
connection_map: HashMap<ConnectionId, VariableIndex>,
// List of instruction indices that correspond to actions in the compiled program (does not include probe operation)
Expand Down Expand Up @@ -112,15 +112,28 @@ impl CompiledMetadata {
}
}

// Get the block variable index and list of transaction variable indices for a given block hash
/// Get the header var index, block var index, `block_transactions` var index, and list of
/// transaction variable indices for a given block hash.
#[must_use]
pub fn block_variables(
&self,
block_hash: &bitcoin::BlockHash,
) -> Option<(usize, usize, &[usize])> {
) -> Option<(usize, usize, usize, &[usize])> {
self.block_tx_var_map.get(block_hash).map(
|(header_var, block_var, block_txs_var, tx_vars)| {
(*header_var, *block_var, *block_txs_var, tx_vars.as_slice())
},
)
}

/// Look up a block by its block variable index, returning the `block_transactions` var index
/// and the list of transaction variable indices that belong to it.
#[must_use]
pub fn block_vars_by_block_index(&self, block_var_index: usize) -> Option<(usize, &[usize])> {
self.block_tx_var_map
.get(block_hash)
.map(|(header_var, block_var, tx_vars)| (*header_var, *block_var, tx_vars.as_slice()))
.values()
.find(|(_, block_var, _, _)| *block_var == block_var_index)
.map(|(_, _, block_txs_var, tx_vars)| (*block_txs_var, tx_vars.as_slice()))
}

// Get the list of instruction indices that correspond to actions in the compiled program
Expand Down Expand Up @@ -276,6 +289,11 @@ struct BlockTransactions {
var_indices: Vec<usize>,
}

#[derive(Clone, Debug)]
struct PrefillTransactions {
indices: Vec<usize>,
}

#[derive(Clone, Debug)]
struct AddrList {
entries: Vec<(u32, Address)>,
Expand Down Expand Up @@ -364,10 +382,6 @@ impl Compiler {
self.handle_build_taproot_tree(instruction)?;
}

Operation::BuildCompactBlock => {
self.handle_compact_block_building_operations(instruction)?;
}

Operation::BeginBlockTransactions
| Operation::AddTx
| Operation::EndBlockTransactions
Expand Down Expand Up @@ -459,6 +473,13 @@ impl Compiler {
self.handle_new_connection_operations(instruction)?;
}

Operation::BeginPrefillTransactions
| Operation::AddPrefillTx
| Operation::EndPrefillTransactions
| Operation::BuildCompactBlockWithPrefill => {
self.handle_prefill_building_operations(instruction)?;
}

Operation::SendRawMessage
| Operation::SendTxNoWit
| Operation::SendTx
Expand Down Expand Up @@ -729,11 +750,7 @@ impl Compiler {
let octets: [u8; 16] = payload.as_slice().try_into().expect("length checked");
AddrV2::Cjdns(Ipv6Addr::from(octets))
}
AddrNetwork::Yggdrasil => {
// `bitcoin::p2p::address::AddrV2` has no Yggdrasil variant; preserve the
// network ID via the generic `Unknown` encoding.
AddrV2::Unknown(AddrNetwork::Yggdrasil.id(), payload.clone())
}
AddrNetwork::Yggdrasil => AddrV2::Unknown(AddrNetwork::Yggdrasil.id(), payload.clone()),
AddrNetwork::Unknown(id) => AddrV2::Unknown(id, payload.clone()),
};

Expand Down Expand Up @@ -837,30 +854,6 @@ impl Compiler {
Ok(())
}

fn handle_compact_block_building_operations(
&mut self,
instruction: &Instruction,
) -> Result<(), CompilerError> {
match &instruction.operation {
Operation::BuildCompactBlock => {
let block = self.get_input::<bitcoin::Block>(&instruction.inputs, 0)?;
let nonce = self.get_input::<u64>(&instruction.inputs, 1)?;

// TODO: put other txs than coinbase tx
let prefill = &[];
let header_and_shortids = HeaderAndShortIds::from_block(block, *nonce, 2, prefill)
.expect("from_block should never fail");
self.append_variable(CmpctBlock {
compact_block: header_and_shortids,
});
}
_ => unreachable!(
"Non-compactblock-building operation passed to handle_compact_block_building_operations"
),
}
Ok(())
}

fn handle_taproot_conversions(
&mut self,
instruction: &Instruction,
Expand Down Expand Up @@ -1785,6 +1778,65 @@ impl Compiler {
}
}

fn handle_prefill_building_operations(
&mut self,
instruction: &Instruction,
) -> Result<(), CompilerError> {
match &instruction.operation {
Operation::BeginPrefillTransactions => {
self.append_variable(PrefillTransactions {
indices: Vec::new(),
});
}
Operation::AddPrefillTx => {
let block_txs = self
.get_input::<BlockTransactions>(&instruction.inputs, 1)?
.clone();
let tx_to_prefill = self.get_input::<Tx>(&instruction.inputs, 2)?;
let tx_to_prefill_id = tx_to_prefill.id;

// Find the position of this tx within the block's transaction list.
// If the tx is not part of this block, skip it silently.
let Some(position) = block_txs.txs.iter().position(|t| t.id == tx_to_prefill_id)
else {
return Ok(());
};

let prefill_var =
self.get_input_mut::<PrefillTransactions>(&instruction.inputs, 0)?;
if !prefill_var.indices.contains(&position) {
prefill_var.indices.push(position);
}
}
Operation::EndPrefillTransactions => {
let prefill_var = self
.get_input::<PrefillTransactions>(&instruction.inputs, 0)?
.clone();
self.append_variable(prefill_var);
}
Operation::BuildCompactBlockWithPrefill => {
let block = self.get_input::<bitcoin::Block>(&instruction.inputs, 0)?;
let nonce = self.get_input::<u64>(&instruction.inputs, 1)?;
let prefill_var = self.get_input::<PrefillTransactions>(&instruction.inputs, 2)?;

let mut prefill = prefill_var.indices.clone();
// Filter out-of-bounds indices defensively, same as existing BuildCompactBlock.
prefill.retain(|&i| i > 0 && i < block.txdata.len());
prefill.sort_unstable();
prefill.dedup();

let header_and_shortids = HeaderAndShortIds::from_block(block, *nonce, 2, &prefill)
.expect("from_block should never fail");

self.append_variable(CmpctBlock {
compact_block: header_and_shortids,
});
}
_ => unreachable!("Non-prefill operation passed to handle_prefill_building_operations"),
}
Ok(())
}

fn handle_new_connection_operations(
&mut self,
instruction: &Instruction,
Expand Down Expand Up @@ -1919,6 +1971,10 @@ impl Compiler {
.get_input::<BlockTransactions>(&instruction.inputs, 4)?
.clone();

// Capture the ConstBlockTransactions variable index before we start
// appending new variables, so we can store it in the metadata.
let block_transactions_var_index = instruction.inputs[4];

coinbase_tx_var.tx.tx.input[0].script_sig = ScriptBuf::builder()
.push_int(i64::from(header_var.height + 1))
.push_int(0xFFFF_FFFF)
Expand Down Expand Up @@ -1991,7 +2047,9 @@ impl Compiler {
version: block.header.version.to_consensus(),
});

// Record the block variable index and transaction variable indices in metadata
// Record the block variable index, block_transactions variable index, and
// transaction variable indices in metadata so generators can look them up
// by block variable index later.
let block_hash = block.header.block_hash();
let block_var_index = self.variables.len();
self.append_variable(block);
Expand All @@ -2002,6 +2060,7 @@ impl Compiler {
.or_insert((
header_var_index,
block_var_index,
block_transactions_var_index,
block_transactions_var.var_indices.clone(),
));

Expand Down
Loading