transaction & block sigops amount check

This commit is contained in:
NikVolf 2016-11-14 21:17:54 +03:00
parent f4fa69e383
commit 5fcba908f4
8 changed files with 169 additions and 5 deletions

2
Cargo.lock generated
View File

@ -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)",
]

View File

@ -8,6 +8,7 @@ authors = ["Ethcore <admin@ethcore.io>"]
[dependencies]
rand = "0.3"
log = "0.3"
[features]

View File

@ -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;

View File

@ -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)
})
}

View File

@ -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 {

View File

@ -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"

View File

@ -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() {

View File

@ -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)]