use lambda instead of a hash map to find spent outputs (#319)

* use lambda instead of a hash map to find spent outputs
* check for double use of an input
This commit is contained in:
Tamás Blummer 2019-08-23 18:49:31 +02:00 committed by GitHub
parent 8ff904c747
commit f01568c85a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 37 additions and 21 deletions

View File

@ -34,7 +34,7 @@ use consensus::{encode, Decodable, Encodable};
use hashes::{hash160, sha256, Hash};
#[cfg(feature="bitcoinconsensus")] use bitcoinconsensus;
#[cfg(feature="bitcoinconsensus")] use std::convert;
#[cfg(feature="bitcoinconsensus")] use hashes::sha256d;
#[cfg(feature="bitcoinconsensus")] use OutPoint;
use util::key::PublicKey;
@ -95,11 +95,8 @@ pub enum Error {
/// Error validating the script with bitcoinconsensus library
BitcoinConsensus(bitcoinconsensus::Error),
#[cfg(feature="bitcoinconsensus")]
/// Can not find the spent transaction
UnknownSpentTransaction(sha256d::Hash),
#[cfg(feature="bitcoinconsensus")]
/// The spent transaction does not have the referred output
WrongSpentOutputIndex(usize),
/// Can not find the spent output
UnknownSpentOutput(OutPoint),
#[cfg(feature="bitcoinconsensus")]
/// Can not serialize the spending transaction
SerializationError
@ -122,9 +119,7 @@ impl error::Error for Error {
#[cfg(feature="bitcoinconsensus")]
Error::BitcoinConsensus(ref _n) => "bitcoinconsensus verification failed",
#[cfg(feature="bitcoinconsensus")]
Error::UnknownSpentTransaction (ref _hash) => "unknown transaction referred in Transaction::verify()",
#[cfg(feature="bitcoinconsensus")]
Error::WrongSpentOutputIndex(ref _ix) => "unknown output index {} referred in Transaction::verify()",
Error::UnknownSpentOutput(ref _point) => "unknown spent output Transaction::verify()",
#[cfg(feature="bitcoinconsensus")]
Error::SerializationError => "can not serialize the spending transaction in Transaction::verify()",
}

View File

@ -26,7 +26,6 @@
use byteorder::{LittleEndian, WriteBytesExt};
use std::default::Default;
use std::{fmt, io};
#[cfg(feature="bitcoinconsensus")] use std::collections::HashMap;
use hashes::{self, sha256d, Hash};
use hashes::hex::FromHex;
@ -406,18 +405,16 @@ impl Transaction {
}
#[cfg(feature="bitcoinconsensus")]
/// Verify that this transaction is able to spend some outputs of spent transactions
pub fn verify(&self, spent: &HashMap<sha256d::Hash, Transaction>) -> Result<(), script::Error> {
/// Verify that this transaction is able to spend its inputs
/// The lambda spent should not return the same TxOut twice!
pub fn verify<S>(&self, mut spent: S) -> Result<(), script::Error>
where S: FnMut(&OutPoint) -> Option<TxOut> {
let tx = serialize(&*self);
for (idx, input) in self.input.iter().enumerate() {
if let Some(ref s) = spent.get(&input.previous_output.txid) {
if let Some(ref output) = s.output.get(input.previous_output.vout as usize) {
output.script_pubkey.verify(idx, output.value, tx.as_slice())?;
} else {
return Err(script::Error::WrongSpentOutputIndex(input.previous_output.vout as usize));
}
if let Some(output) = spent(&input.previous_output) {
output.script_pubkey.verify(idx, output.value, tx.as_slice())?;
} else {
return Err(script::Error::UnknownSpentTransaction(input.previous_output.txid));
return Err(script::Error::UnknownSpentOutput(input.previous_output.clone()));
}
}
Ok(())
@ -1130,12 +1127,36 @@ mod tests {
spent.insert(spent1.txid(), spent1);
spent.insert(spent2.txid(), spent2);
spent.insert(spent3.txid(), spent3);
let mut spent2 = spent.clone();
let mut spent3 = spent.clone();
spending.verify(&spent).unwrap();
spending.verify(|point: &OutPoint| {
if let Some(tx) = spent.remove(&point.txid) {
return tx.output.get(point.vout as usize).cloned();
}
None
}).unwrap();
// test that we fail with repeated use of same input
let mut double_spending = spending.clone();
let re_use = double_spending.input[0].clone();
double_spending.input.push (re_use);
assert!(double_spending.verify(|point: &OutPoint| {
if let Some(tx) = spent2.remove(&point.txid) {
return tx.output.get(point.vout as usize).cloned();
}
None
}).is_err());
// test that we get a failure if we corrupt a signature
spending.input[1].witness[0][10] = 42;
match spending.verify(&spent).err().unwrap() {
match spending.verify(|point: &OutPoint| {
if let Some(tx) = spent3.remove(&point.txid) {
return tx.output.get(point.vout as usize).cloned();
}
None
}).err().unwrap() {
script::Error::BitcoinConsensus(_) => {},
_ => panic!("Wrong error type"),
}