transaction & block sigops amount check
This commit is contained in:
parent
f4fa69e383
commit
5fcba908f4
|
@ -7,6 +7,7 @@ dependencies = [
|
|||
"db 0.1.0",
|
||||
"ethcore-devtools 1.3.0",
|
||||
"linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"primitives 0.1.0",
|
||||
"script 0.1.0",
|
||||
|
@ -204,6 +205,7 @@ dependencies = [
|
|||
name = "ethcore-devtools"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ authors = ["Ethcore <admin@ethcore.io>"]
|
|||
|
||||
[dependencies]
|
||||
rand = "0.3"
|
||||
log = "0.3"
|
||||
|
||||
[features]
|
||||
|
||||
|
|
|
@ -18,11 +18,14 @@
|
|||
|
||||
|
||||
extern crate rand;
|
||||
extern crate log;
|
||||
|
||||
mod random_path;
|
||||
mod test_socket;
|
||||
mod stop_guard;
|
||||
mod test_logger;
|
||||
|
||||
pub use random_path::*;
|
||||
pub use test_socket::*;
|
||||
pub use stop_guard::*;
|
||||
pub use test_logger::init as test_logger;
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
use log::{self, Log, LogRecord, LogLevel, LogMetadata, SetLoggerError, LogLevelFilter};
|
||||
|
||||
struct TestLogger;
|
||||
|
||||
impl Log for TestLogger {
|
||||
fn enabled(&self, metadata: &LogMetadata) -> bool {
|
||||
metadata.level() <= LogLevel::Info
|
||||
}
|
||||
|
||||
fn log(&self, record: &LogRecord) {
|
||||
if self.enabled(record.metadata()) {
|
||||
println!("{} - {}", record.level(), record.args());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() -> Result<(), SetLoggerError> {
|
||||
log::set_logger(|max_log_level| {
|
||||
max_log_level.set(LogLevelFilter::Info);
|
||||
Box::new(TestLogger)
|
||||
})
|
||||
}
|
|
@ -331,6 +331,11 @@ impl<F> TransactionInputBuilder<F> where F: Invoke<chain::TransactionInput> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn signature_bytes(mut self, sig: Bytes) -> Self {
|
||||
self.signature = sig;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn hash(mut self, hash: H256) -> Self {
|
||||
let mut output = self.output.unwrap_or(chain::OutPoint { hash: hash.clone(), index: 0 });
|
||||
output.hash = hash;
|
||||
|
@ -387,6 +392,11 @@ impl<F> TransactionOutputBuilder<F> where F: Invoke<chain::TransactionOutput> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn signature_bytes(mut self, sig: Bytes) -> Self {
|
||||
self.signature = sig;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> F::Result {
|
||||
self.callback.invoke(
|
||||
chain::TransactionOutput {
|
||||
|
|
|
@ -14,6 +14,7 @@ test-data = { path = "../test-data" }
|
|||
byteorder = "0.5"
|
||||
time = "0.1"
|
||||
script = { path = "../script" }
|
||||
log = "0.3"
|
||||
|
||||
[dependencies.db]
|
||||
path = "../db"
|
||||
|
|
|
@ -9,6 +9,7 @@ use utils;
|
|||
|
||||
const BLOCK_MAX_FUTURE: i64 = 2 * 60 * 60; // 2 hours
|
||||
const COINBASE_MATURITY: u32 = 100; // 2 hours
|
||||
const MAX_BLOCK_SIGOPS: usize = 20000;
|
||||
|
||||
pub struct ChainVerifier {
|
||||
store: Arc<db::Store>,
|
||||
|
@ -93,7 +94,7 @@ impl ChainVerifier {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_transaction(&self, block: &chain::Block, transaction: &chain::Transaction) -> Result<(), TransactionError> {
|
||||
fn verify_transaction(&self, block: &chain::Block, transaction: &chain::Transaction) -> Result<usize, TransactionError> {
|
||||
use script::{
|
||||
TransactionInputSigner,
|
||||
TransactionSignatureChecker,
|
||||
|
@ -102,6 +103,7 @@ impl ChainVerifier {
|
|||
verify_script,
|
||||
};
|
||||
|
||||
let mut sigops: usize = 0;
|
||||
for (input_index, input) in transaction.inputs().iter().enumerate() {
|
||||
let store_parent_transaction = self.store.transaction(&input.previous_output.hash);
|
||||
let parent_transaction = match store_parent_transaction {
|
||||
|
@ -117,7 +119,6 @@ impl ChainVerifier {
|
|||
return Err(TransactionError::Input(input_index));
|
||||
}
|
||||
|
||||
if self.skip_sig { continue; }
|
||||
// signature verification
|
||||
let signer: TransactionInputSigner = transaction.clone().into();
|
||||
let paired_output = &parent_transaction.outputs[input.previous_output.index as usize];
|
||||
|
@ -127,16 +128,29 @@ impl ChainVerifier {
|
|||
};
|
||||
let input: Script = input.script_sig().to_vec().into();
|
||||
let output: Script = paired_output.script_pubkey.to_vec().into();
|
||||
|
||||
sigops +=
|
||||
try!(output.sigop_count().map_err(|e| TransactionError::ReferenceSignatureMallformed(format!("{}", e)))) +
|
||||
try!(input.sigop_count().map_err(|e| TransactionError::SignatureMallformed(format!("{}", e))));
|
||||
|
||||
if sigops > MAX_BLOCK_SIGOPS {
|
||||
// already overflown
|
||||
return Err(TransactionError::SigopsAmount);
|
||||
}
|
||||
|
||||
let flags = VerificationFlags::default().verify_p2sh(true);
|
||||
|
||||
if let Err(e) = verify_script(&input, &output, &flags, &checker) {
|
||||
// for tests only, skips as late as possible
|
||||
if self.skip_sig { continue; }
|
||||
|
||||
if let Err(e) = verify_script(&input, &output, &flags, &checker) {
|
||||
println!("transaction signature verification failure: {:?}", e);
|
||||
// todo: log error here
|
||||
return Err(TransactionError::Signature(input_index))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(sigops)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,10 +184,18 @@ impl Verify for ChainVerifier {
|
|||
}
|
||||
|
||||
// verify transactions (except coinbase)
|
||||
let mut block_sigops = 0;
|
||||
for (idx, transaction) in block.transactions().iter().skip(1).enumerate() {
|
||||
try!(self.verify_transaction(block, transaction).map_err(|e| Error::Transaction(idx+1, e)));
|
||||
let tx_sigops = try!(self.verify_transaction(block, transaction).map_err(|e| Error::Transaction(idx+1, e)));
|
||||
block_sigops += tx_sigops;
|
||||
|
||||
if block_sigops >= MAX_BLOCK_SIGOPS {
|
||||
return Err(Error::MaximumSigops);
|
||||
}
|
||||
}
|
||||
|
||||
trace!(target: "verification", "Block {} total sigops: {}", &hash, &block_sigops);
|
||||
|
||||
// todo: pre-process projected block number once verification is parallel!
|
||||
match self.store.accepted_location(block.header()) {
|
||||
None => {
|
||||
|
@ -220,6 +242,7 @@ mod tests {
|
|||
use std::sync::Arc;
|
||||
use devtools::RandomTempPath;
|
||||
use chain::RepresentH256;
|
||||
use script;
|
||||
|
||||
#[test]
|
||||
fn verify_orphan() {
|
||||
|
@ -454,6 +477,97 @@ mod tests {
|
|||
assert_eq!(expected, verifier.verify(&block))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sigops_overflow_tx() {
|
||||
let path = RandomTempPath::create_dir();
|
||||
let storage = Storage::new(path.as_path()).unwrap();
|
||||
|
||||
let genesis = test_data::block_builder()
|
||||
.transaction()
|
||||
.coinbase()
|
||||
.build()
|
||||
.transaction()
|
||||
.output().value(50).build()
|
||||
.build()
|
||||
.merkled_header().build()
|
||||
.build();
|
||||
|
||||
storage.insert_block(&genesis).unwrap();
|
||||
let reference_tx = genesis.transactions()[1].hash();
|
||||
|
||||
let mut builder = script::Builder::default();
|
||||
for _ in 0..21000 {
|
||||
builder = builder.push_opcode(script::Opcode::OP_CHECKSIG)
|
||||
}
|
||||
|
||||
let block = test_data::block_builder()
|
||||
.transaction().coinbase().build()
|
||||
.transaction()
|
||||
.input()
|
||||
.hash(reference_tx)
|
||||
.signature_bytes(builder.into_script().to_bytes())
|
||||
.build()
|
||||
.build()
|
||||
.merkled_header().parent(genesis.hash()).build()
|
||||
.build();
|
||||
|
||||
let verifier = ChainVerifier::new(Arc::new(storage)).pow_skip();
|
||||
|
||||
let expected = Err(Error::Transaction(1, TransactionError::SigopsAmount));
|
||||
assert_eq!(expected, verifier.verify(&block));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sigops_overflow_block() {
|
||||
let path = RandomTempPath::create_dir();
|
||||
let storage = Storage::new(path.as_path()).unwrap();
|
||||
|
||||
let genesis = test_data::block_builder()
|
||||
.transaction()
|
||||
.coinbase()
|
||||
.build()
|
||||
.transaction()
|
||||
.output().value(50).build()
|
||||
.build()
|
||||
.merkled_header().build()
|
||||
.build();
|
||||
|
||||
storage.insert_block(&genesis).unwrap();
|
||||
let reference_tx = genesis.transactions()[1].hash();
|
||||
|
||||
let mut builder_tx1 = script::Builder::default();
|
||||
for _ in 0..11000 {
|
||||
builder_tx1 = builder_tx1.push_opcode(script::Opcode::OP_CHECKSIG)
|
||||
}
|
||||
|
||||
let mut builder_tx2 = script::Builder::default();
|
||||
for _ in 0..11000 {
|
||||
builder_tx2 = builder_tx2.push_opcode(script::Opcode::OP_CHECKSIG)
|
||||
}
|
||||
|
||||
let block = test_data::block_builder()
|
||||
.transaction().coinbase().build()
|
||||
.transaction()
|
||||
.input()
|
||||
.hash(reference_tx.clone())
|
||||
.signature_bytes(builder_tx1.into_script().to_bytes())
|
||||
.build()
|
||||
.build()
|
||||
.transaction()
|
||||
.input()
|
||||
.hash(reference_tx)
|
||||
.signature_bytes(builder_tx2.into_script().to_bytes())
|
||||
.build()
|
||||
.build()
|
||||
.merkled_header().parent(genesis.hash()).build()
|
||||
.build();
|
||||
|
||||
let verifier = ChainVerifier::new(Arc::new(storage)).pow_skip().signatures_skip();
|
||||
|
||||
let expected = Err(Error::MaximumSigops);
|
||||
assert_eq!(expected, verifier.verify(&block));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coinbase_overspend() {
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ extern crate linked_hash_map;
|
|||
extern crate byteorder;
|
||||
extern crate time;
|
||||
extern crate script;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate ethcore_devtools as devtools;
|
||||
|
@ -45,6 +47,9 @@ pub enum Error {
|
|||
MerkleRoot,
|
||||
/// Coinbase spends too much
|
||||
CoinbaseOverspend { expected_max: u64, actual: u64 },
|
||||
/// Maximum sigops operations exceeded - will not provide how much it was in total
|
||||
/// since it stops counting once `MAX_BLOCK_SIGOPS` is reached
|
||||
MaximumSigops,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -62,6 +67,12 @@ pub enum TransactionError {
|
|||
UnknownReference(H256),
|
||||
/// Spends more than claims
|
||||
Overspend,
|
||||
/// Signature script can't be properly parsed
|
||||
SignatureMallformed(String),
|
||||
/// Signature script in referenced transaction can't be properly parsed
|
||||
ReferenceSignatureMallformed(String),
|
||||
/// Too many sigops
|
||||
SigopsAmount,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
|
|
Loading…
Reference in New Issue