Parallelize transaction verification in utxoset
We no longer confirm that chained transactions occur in the correct order in blocks, which is a minor consensus regression and should be dealt with in future.
This commit is contained in:
parent
cfe7d5eb26
commit
8e7d763310
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::sync::Future;
|
||||||
|
|
||||||
use blockdata::transaction::{Transaction, TxOut};
|
use blockdata::transaction::{Transaction, TxOut};
|
||||||
use blockdata::constants::genesis_block;
|
use blockdata::constants::genesis_block;
|
||||||
|
@ -128,9 +129,9 @@ impl UtxoSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to a UTXO in the set
|
/// Get a reference to a UTXO in the set
|
||||||
pub fn get_utxo<'a>(&'a mut self, txid: Sha256dHash, vout: u32) -> Option<&'a TxOut> {
|
pub fn get_utxo<'a>(&'a self, txid: Sha256dHash, vout: u32) -> Option<&'a TxOut> {
|
||||||
// Locate the UTXO, failing if not found
|
// Locate the UTXO, failing if not found
|
||||||
let node = match self.table.find_mut(&txid.into_uint128()) {
|
let node = match self.table.find(&txid.into_uint128()) {
|
||||||
Some(node) => node,
|
Some(node) => node,
|
||||||
None => return None
|
None => return None
|
||||||
};
|
};
|
||||||
|
@ -155,68 +156,11 @@ impl UtxoSet {
|
||||||
self.spent_idx = (self.spent_idx + 1) % self.spent_txos.len() as u64;
|
self.spent_idx = (self.spent_idx + 1) % self.spent_txos.len() as u64;
|
||||||
self.spent_txos.get_mut(spent_idx).clear();
|
self.spent_txos.get_mut(spent_idx).clear();
|
||||||
|
|
||||||
let mut skipped_genesis = false;
|
// Add all the utxos so that we can have chained transactions within the
|
||||||
|
// same block. (Note that Bitcoin requires chained transactions to be in
|
||||||
|
// the correct order, which we do not check, so we are minorly too permissive.
|
||||||
|
// TODO this is a consensus bug.)
|
||||||
for tx in block.txdata.iter() {
|
for tx in block.txdata.iter() {
|
||||||
let txid = tx.bitcoin_hash();
|
|
||||||
// Put the removed utxos into the stxo cache, in case we need to rewind
|
|
||||||
if skipped_genesis {
|
|
||||||
self.spent_txos.get_mut(spent_idx).reserve_additional(tx.input.len());
|
|
||||||
for (n, input) in tx.input.iter().enumerate() {
|
|
||||||
let taken = self.take_utxo(input.prev_hash, input.prev_index);
|
|
||||||
match taken {
|
|
||||||
Some(txo) => {
|
|
||||||
if validation >= ScriptValidation {
|
|
||||||
let mut stack = Vec::with_capacity(6);
|
|
||||||
match input.script_sig.evaluate(&mut stack, Some((tx, n))) {
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(e) => {
|
|
||||||
use serialize::json::ToJson;
|
|
||||||
println!("scriptsig error {}", e);
|
|
||||||
println!("txid was {}", tx.bitcoin_hash());
|
|
||||||
println!("tx was {}", tx.to_json().to_string());
|
|
||||||
println!("script ended with stack {}", stack);
|
|
||||||
self.rewind(block);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match txo.script_pubkey.evaluate(&mut stack, Some((tx, n))) {
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(e) => {
|
|
||||||
use serialize::json::ToJson;
|
|
||||||
println!("scriptpubkey error {}", e);
|
|
||||||
println!("txid was {}", tx.bitcoin_hash());
|
|
||||||
println!("tx was {}", tx.to_json().to_string());
|
|
||||||
println!("script ended with stack {}", stack);
|
|
||||||
self.rewind(block);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match stack.pop() {
|
|
||||||
Some(v) => {
|
|
||||||
if !read_scriptbool(v.as_slice()) {
|
|
||||||
use serialize::json::ToJson;
|
|
||||||
println!("txid was {}", tx.bitcoin_hash());
|
|
||||||
println!("tx was {}", tx.to_json().to_string());
|
|
||||||
println!("script ended with stack {}", stack);
|
|
||||||
self.rewind(block);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
println!("script ended with empty stack");
|
|
||||||
self.rewind(block);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.spent_txos.get_mut(spent_idx).push(((txid, n as u32), txo));
|
|
||||||
}
|
|
||||||
None => { if validation >= TxoValidation { self.rewind(block); return false; } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
skipped_genesis = true;
|
|
||||||
|
|
||||||
let txid = tx.bitcoin_hash();
|
let txid = tx.bitcoin_hash();
|
||||||
// Add outputs -- add_utxos returns the original transaction if this is a dupe.
|
// Add outputs -- add_utxos returns the original transaction if this is a dupe.
|
||||||
// Note that this can only happen with coinbases, and in this case the block
|
// Note that this can only happen with coinbases, and in this case the block
|
||||||
|
@ -249,6 +193,75 @@ impl UtxoSet {
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are validating scripts, do all that now in parallel
|
||||||
|
if validation >= ScriptValidation {
|
||||||
|
let mut future_vec = Vec::with_capacity(block.txdata.len() - 1);
|
||||||
|
// skip the genesis since we don't validate this script. (TODO this might
|
||||||
|
// be a consensus bug since we don't even check that the opcodes make sense.)
|
||||||
|
for tx in block.txdata.iter().skip(1) {
|
||||||
|
let s = self as *mut _ as *const UtxoSet;
|
||||||
|
let tx = tx as *const _;
|
||||||
|
future_vec.push(Future::spawn(proc() {
|
||||||
|
let tx = unsafe {&*tx};
|
||||||
|
for (n, input) in tx.input.iter().enumerate() {
|
||||||
|
let txo = unsafe { (*s).get_utxo(input.prev_hash, input.prev_index) };
|
||||||
|
match txo {
|
||||||
|
Some(txo) => {
|
||||||
|
let mut stack = Vec::with_capacity(6);
|
||||||
|
match input.script_sig.evaluate(&mut stack, Some((tx, n))) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("txid was {}", tx.bitcoin_hash());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match txo.script_pubkey.evaluate(&mut stack, Some((tx, n))) {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(e) => {
|
||||||
|
println!("txid was {}", tx.bitcoin_hash());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match stack.pop() {
|
||||||
|
Some(v) => {
|
||||||
|
if !read_scriptbool(v.as_slice()) {
|
||||||
|
use serialize::json::ToJson;
|
||||||
|
println!("txid was {}", tx.bitcoin_hash());
|
||||||
|
println!("tx was {}", tx.to_json().to_string());
|
||||||
|
println!("script ended with stack {}", stack);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => { return false; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => { return false; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if !future_vec.mut_iter().map(|f| f.get()).all(|x| x) { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut skipped_genesis = false;
|
||||||
|
for tx in block.txdata.iter() {
|
||||||
|
let txid = tx.bitcoin_hash();
|
||||||
|
// Put the removed utxos into the stxo cache, in case we need to rewind
|
||||||
|
if skipped_genesis {
|
||||||
|
self.spent_txos.get_mut(spent_idx).reserve_additional(tx.input.len());
|
||||||
|
for (n, input) in tx.input.iter().enumerate() {
|
||||||
|
let taken = self.take_utxo(input.prev_hash, input.prev_index);
|
||||||
|
match taken {
|
||||||
|
Some(txo) => { self.spent_txos.get_mut(spent_idx).push(((txid, n as u32), txo)); }
|
||||||
|
None => { if validation >= TxoValidation { self.rewind(block); return false; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
skipped_genesis = true;
|
||||||
|
|
||||||
|
}
|
||||||
// If we made it here, success!
|
// If we made it here, success!
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue