Merge pull request #167 from ethcore/verification

Verification additional checks
This commit is contained in:
Marek Kotewicz 2016-11-22 11:41:27 +01:00 committed by GitHub
commit 4291b09302
4 changed files with 81 additions and 13 deletions

View File

@ -359,6 +359,33 @@ impl Script {
Ok(result)
}
pub fn sigop_count_p2sh(&self, input_ref: &Script) -> Result<usize, Error> {
if !self.is_pay_to_script_hash() { return self.sigop_count(true); }
let mut script_data: Option<&[u8]> = None;
// we need last command
for next in input_ref.iter() {
let instruction = match next {
Err(_) => return Ok(0),
Ok(i) => i,
};
if instruction.opcode as u8 > Opcode::OP_16 as u8 {
return Ok(0);
}
script_data = instruction.data;
}
match script_data {
Some(slc) => {
let nested_script: Script = slc.to_vec().into();
nested_script.sigop_count(true)
},
None => Ok(0),
}
}
}
pub struct Instructions<'a> {

View File

@ -10,6 +10,8 @@ const COINBASE_MATURITY: u32 = 100; // 2 hours
const MAX_BLOCK_SIGOPS: usize = 20000;
const MAX_BLOCK_SIZE: usize = 1000000;
const BIP16_TIME: u32 = 1333238400;
pub struct ChainVerifier {
store: db::SharedStore,
verify_p2sh: bool,
@ -110,7 +112,11 @@ 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,
sequence: usize,
) -> Result<usize, TransactionError> {
use script::{
TransactionInputSigner,
TransactionSignatureChecker,
@ -119,6 +125,20 @@ impl ChainVerifier {
verify_script,
};
let mut sigops = utils::transaction_sigops(transaction)
.map_err(|e| TransactionError::SignatureMallformed(e.to_string()))?;
if sequence == 0 { return Ok(sigops); }
// must not be coinbase (sequence = 0 is returned above)
if transaction.is_coinbase() { return Err(TransactionError::MisplacedCoinbase(sequence)); }
if sigops >= MAX_BLOCK_SIGOPS { return Err(TransactionError::Sigops(sigops)); }
// strict pay-to-script-hash signature operations count toward block
// signature operations limit is enforced with BIP16
let is_strict_p2sh = block.header().time >= BIP16_TIME;
for (input_index, input) in transaction.inputs().iter().enumerate() {
let store_parent_transaction = self.store.transaction(&input.previous_output.hash);
let parent_transaction = store_parent_transaction
@ -140,6 +160,12 @@ impl ChainVerifier {
let input: Script = input.script_sig().to_vec().into();
let output: Script = paired_output.script_pubkey.to_vec().into();
if is_strict_p2sh && output.is_pay_to_script_hash() {
sigops += utils::p2sh_sigops(&output, &input);
if sigops >= MAX_BLOCK_SIGOPS { return Err(TransactionError::SigopsP2SH(sigops)); }
}
let flags = VerificationFlags::default()
.verify_p2sh(self.verify_p2sh)
.verify_clocktimeverify(self.verify_clocktimeverify);
@ -156,7 +182,7 @@ impl ChainVerifier {
}
}
Ok(())
Ok(sigops)
}
fn verify_block(&self, block: &chain::Block) -> VerificationResult {
@ -201,20 +227,20 @@ impl ChainVerifier {
return Err(Error::CoinbaseSignatureLength(coinbase_script_len));
}
// verify transactions (except coinbase)
let mut block_sigops = utils::transaction_sigops(&block.transactions()[0])
.map_err(|e| Error::Transaction(1, TransactionError::SignatureMallformed(e.to_string())))?;
for (idx, transaction) in block.transactions().iter().enumerate().skip(1) {
block_sigops += utils::transaction_sigops(transaction)
.map_err(|e| Error::Transaction(idx, TransactionError::SignatureMallformed(e.to_string())))?;
// transaction verification including number of signature operations checking
let mut block_sigops = 0;
for (idx, transaction) in block.transactions().iter().enumerate() {
block_sigops += try!(
self.verify_transaction(
block,
transaction,
idx,
).map_err(|e| Error::Transaction(idx, e))
);
if block_sigops > MAX_BLOCK_SIGOPS {
return Err(Error::MaximumSigops);
}
try!(self.verify_transaction(block, transaction).map_err(|e| Error::Transaction(idx, e)));
}
// todo: pre-process projected block number once verification is parallel!
@ -253,7 +279,7 @@ impl ContinueVerify for ChainVerifier {
fn continue_verify(&self, block: &chain::Block, state: usize) -> VerificationResult {
// verify transactions (except coinbase)
for (idx, transaction) in block.transactions().iter().enumerate().skip(state - 1) {
try!(self.verify_transaction(block, transaction).map_err(|e| Error::Transaction(idx, e)));
try!(self.verify_transaction(block, transaction, idx).map_err(|e| Error::Transaction(idx, e)));
}
let _parent = match self.store.block(BlockRef::Hash(block.header().previous_header_hash.clone())) {

View File

@ -73,6 +73,12 @@ pub enum TransactionError {
Overspend,
/// Signature script can't be properly parsed
SignatureMallformed(String),
/// Too many signature operations
Sigops(usize),
/// Too many signature operations once p2sh operations included
SigopsP2SH(usize),
/// Coinbase transaction is found at position that is not 0
MisplacedCoinbase(usize),
}
#[derive(PartialEq, Debug)]

View File

@ -4,7 +4,11 @@ use byteorder::{BigEndian, ByteOrder};
use chain;
use script::{self, Script};
const MAX_NBITS: u32 = 0x207fffff;
pub fn check_nbits(hash: &H256, n_bits: u32) -> bool {
if n_bits > MAX_NBITS { return false; }
let hash_bytes: &[u8] = &**hash;
let mut nb = [0u8; 4];
@ -73,6 +77,11 @@ pub fn transaction_sigops(transaction: &chain::Transaction) -> Result<usize, scr
Ok(result)
}
pub fn p2sh_sigops(output: &Script, input_ref: &Script) -> usize {
// todo: not always skip malformed output?
output.sigop_count_p2sh(input_ref).unwrap_or(0)
}
#[cfg(test)]
mod tests {