From a2a70e1fc199e465dd9c22906ca4b7148c340edf Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Fri, 20 Nov 2020 19:47:30 -0800 Subject: [PATCH] consensus: fix same-block utxo lookups The UTXO query system assumes that a transaction will only request information about UTXOs created in prior blocks. But transactions are allowed to spend UTXOs created by prior transactions in the same block. This doesn't fit with the existing query model, so instead of trying to change it, allow the script verifier to take an additional set of known UTXOs, and propagate this set from the block. --- Cargo.lock | 4 ++-- zebra-consensus/src/block.rs | 22 ++++++++++++++++++-- zebra-consensus/src/script.rs | 32 +++++++++++++++++------------- zebra-consensus/src/transaction.rs | 31 ++++++++++++++++++++++------- 4 files changed, 64 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 346da0458..398349c1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2744,7 +2744,7 @@ dependencies = [ [[package]] name = "tower" version = "0.4.0" -source = "git+https://github.com/tower-rs/tower?rev=5e1e07744820028877654c336a3b9fe057bf46f1#5e1e07744820028877654c336a3b9fe057bf46f1" +source = "git+https://github.com/tower-rs/tower?rev=d4d1c67c6a0e4213a52abcc2b9df6cc58276ee39#d4d1c67c6a0e4213a52abcc2b9df6cc58276ee39" dependencies = [ "futures-core", "futures-util", @@ -2789,7 +2789,7 @@ dependencies = [ [[package]] name = "tower-layer" version = "0.3.0" -source = "git+https://github.com/tower-rs/tower?rev=5e1e07744820028877654c336a3b9fe057bf46f1#5e1e07744820028877654c336a3b9fe057bf46f1" +source = "git+https://github.com/tower-rs/tower?rev=d4d1c67c6a0e4213a52abcc2b9df6cc58276ee39#d4d1c67c6a0e4213a52abcc2b9df6cc58276ee39" [[package]] name = "tower-service" diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index bcd3a7d6d..acdd56538 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -8,6 +8,7 @@ //! verification, where it may be accepted or rejected. use std::{ + collections::HashMap, future::Future, pin::Pin, sync::Arc, @@ -25,6 +26,7 @@ use zebra_chain::{ block::{self, Block}, parameters::Network, parameters::NetworkUpgrade, + transparent, work::equihash, }; use zebra_state as zs; @@ -165,13 +167,16 @@ where let mut async_checks = FuturesUnordered::new(); + let known_utxos = known_utxos(&block); for transaction in &block.transactions { - let req = transaction::Request::Block(transaction.clone()); let rsp = transaction_verifier .ready_and() .await .expect("transaction verifier is always ready") - .call(req); + .call(transaction::Request::Block { + transaction: transaction.clone(), + known_utxos: known_utxos.clone(), + }); async_checks.push(rsp); } tracing::trace!(len = async_checks.len(), "built async tx checks"); @@ -207,3 +212,16 @@ where .boxed() } } + +fn known_utxos(block: &Block) -> Arc> { + let mut map = HashMap::default(); + for transaction in &block.transactions { + let hash = transaction.hash(); + for (index, output) in transaction.outputs().iter().cloned().enumerate() { + let index = index as u32; + map.insert(transparent::OutPoint { hash, index }, output); + } + } + + Arc::new(map) +} diff --git a/zebra-consensus/src/script.rs b/zebra-consensus/src/script.rs index ee0876e70..36b986a16 100644 --- a/zebra-consensus/src/script.rs +++ b/zebra-consensus/src/script.rs @@ -1,4 +1,4 @@ -use std::{future::Future, pin::Pin, sync::Arc}; +use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc}; use tracing::Instrument; @@ -40,6 +40,7 @@ impl Verifier { pub struct Request { pub transaction: Arc, pub input_index: usize, + pub known_utxos: Arc>, } impl tower::Service for Verifier @@ -62,32 +63,35 @@ where fn call(&mut self, req: Request) -> Self::Future { use futures_util::FutureExt; - let input = &req.transaction.inputs()[req.input_index]; + let Request { + transaction, + input_index, + known_utxos, + } = req; + let input = &transaction.inputs()[input_index]; match input { transparent::Input::PrevOut { outpoint, .. } => { let outpoint = *outpoint; - let transaction = req.transaction; let branch_id = self.branch; - let input_index = req.input_index; let span = tracing::trace_span!("script", ?outpoint); - let output = + let query = span.in_scope(|| self.state.call(zebra_state::Request::AwaitUtxo(outpoint))); async move { tracing::trace!("awaiting outpoint lookup"); - let previous_output = match output.await? { - zebra_state::Response::Utxo(output) => output, - _ => unreachable!("AwaitUtxo always responds with Utxo"), + let output = if let Some(output) = known_utxos.get(&outpoint) { + tracing::trace!("UXTO in known_utxos, discarding query"); + output.clone() + } else if let zebra_state::Response::Utxo(output) = query.await? { + output + } else { + unreachable!("AwaitUtxo always responds with Utxo") }; - tracing::trace!(?previous_output, "got UTXO"); + tracing::trace!(?output, "got UTXO"); - zebra_script::is_valid( - transaction, - branch_id, - (input_index as u32, previous_output), - )?; + zebra_script::is_valid(transaction, branch_id, (input_index as u32, output))?; tracing::trace!("script verification succeeded"); Ok(()) diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index 39ca1a665..dd33d327b 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, future::Future, pin::Pin, sync::Arc, @@ -15,6 +16,7 @@ use tracing::Instrument; use zebra_chain::{ parameters::NetworkUpgrade, transaction::{self, HashType, Transaction}, + transparent, }; use zebra_state as zs; @@ -51,9 +53,17 @@ where #[allow(dead_code)] pub enum Request { /// Verify the supplied transaction as part of a block. - Block(Arc), + Block { + transaction: Arc, + /// Additional UTXOs which are known at the time of verification. + known_utxos: Arc>, + }, /// Verify the supplied transaction as part of the mempool. - Mempool(Arc), + Mempool { + transaction: Arc, + /// Additional UTXOs which are known at the time of verification. + known_utxos: Arc>, + }, } impl Service for Verifier @@ -73,17 +83,23 @@ where // TODO: break up each chunk into its own method fn call(&mut self, req: Request) -> Self::Future { let is_mempool = match req { - Request::Block(_) => false, - Request::Mempool(_) => true, + Request::Block { .. } => false, + Request::Mempool { .. } => true, }; if is_mempool { // XXX determine exactly which rules apply to mempool transactions unimplemented!(); } - let tx = match req { - Request::Block(tx) => tx, - Request::Mempool(tx) => tx, + let (tx, known_utxos) = match req { + Request::Block { + transaction, + known_utxos, + } => (transaction, known_utxos), + Request::Mempool { + transaction, + known_utxos, + } => (transaction, known_utxos), }; let mut redjubjub_verifier = crate::primitives::redjubjub::VERIFIER.clone(); @@ -119,6 +135,7 @@ where // feed all of the inputs to the script verifier for input_index in 0..inputs.len() { let rsp = script_verifier.ready_and().await?.call(script::Request { + known_utxos: known_utxos.clone(), transaction: tx.clone(), input_index, });