Merge pull request #204 from ethcore/sigops
regtests sigops fixes (now stuck at b82)
This commit is contained in:
commit
b6060f5560
|
@ -455,6 +455,17 @@ impl Opcode {
|
||||||
pub fn is_push_value(&self) -> bool {
|
pub fn is_push_value(&self) -> bool {
|
||||||
*self >= Opcode::OP_1NEGATE && *self <= Opcode::OP_16
|
*self >= Opcode::OP_1NEGATE && *self <= Opcode::OP_16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_within_op_n(&self) -> bool {
|
||||||
|
*self >= Opcode::OP_1 && *self <= Opcode::OP_16
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_op_n(&self) -> u8 {
|
||||||
|
assert!(self.is_within_op_n());
|
||||||
|
let value = *self as u8;
|
||||||
|
let op0 = Opcode::OP_1 as u8 - 1;
|
||||||
|
value - op0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -209,15 +209,15 @@ impl Script {
|
||||||
|
|
||||||
let slice = try!(self.take(position + 1, len));
|
let slice = try!(self.take(position + 1, len));
|
||||||
let n = try!(read_usize(slice, len));
|
let n = try!(read_usize(slice, len));
|
||||||
let bytes = try!(self.take_checked(position + 1 + len, n));
|
let bytes = try!(self.take(position + 1 + len, n));
|
||||||
Instruction {
|
Instruction {
|
||||||
opcode: opcode,
|
opcode: opcode,
|
||||||
step: len + n + 1,
|
step: len + n + 1,
|
||||||
data: Some(bytes),
|
data: Some(bytes),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
o if o >= Opcode::OP_0 && o <= Opcode::OP_PUSHBYTES_75 => {
|
o if o <= Opcode::OP_PUSHBYTES_75 => {
|
||||||
let bytes = try!(self.take_checked(position+ 1, opcode as usize));
|
let bytes = try!(self.take(position + 1, opcode as usize));
|
||||||
Instruction {
|
Instruction {
|
||||||
opcode: o,
|
opcode: o,
|
||||||
step: opcode as usize + 1,
|
step: opcode as usize + 1,
|
||||||
|
@ -243,15 +243,6 @@ impl Script {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn take_checked(&self, offset: usize, len: usize) -> Result<&[u8], Error> {
|
|
||||||
if len > MAX_SCRIPT_ELEMENT_SIZE {
|
|
||||||
Err(Error::ScriptSize)
|
|
||||||
} else {
|
|
||||||
self.take(offset, len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns Script without OP_CODESEPARATOR opcodes
|
/// Returns Script without OP_CODESEPARATOR opcodes
|
||||||
pub fn without_separators(&self) -> Script {
|
pub fn without_separators(&self) -> Script {
|
||||||
let mut pc = 0;
|
let mut pc = 0;
|
||||||
|
@ -318,78 +309,53 @@ impl Script {
|
||||||
Opcodes { position: 0, script: self }
|
Opcodes { position: 0, script: self }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sigop_count(&self, accurate: bool) -> Result<usize, Error> {
|
pub fn sigops_count(&self, serialized_script: bool) -> usize {
|
||||||
let mut last_opcode = Opcode::OP_0;
|
let mut last_opcode = Opcode::OP_0;
|
||||||
let mut result = 0;
|
let mut total = 0;
|
||||||
for opcode in self.opcodes() {
|
for opcode in self.opcodes() {
|
||||||
let opcode = try!(opcode);
|
let opcode = match opcode {
|
||||||
|
Ok(opcode) => opcode,
|
||||||
|
// If we push an invalid element, all previous CHECKSIGs are counted
|
||||||
|
_ => return total,
|
||||||
|
};
|
||||||
|
|
||||||
match opcode {
|
match opcode {
|
||||||
Opcode::OP_CHECKSIG | Opcode::OP_CHECKSIGVERIFY => { result += 1; },
|
Opcode::OP_CHECKSIG | Opcode::OP_CHECKSIGVERIFY => {
|
||||||
|
total += 1;
|
||||||
|
},
|
||||||
Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY => {
|
Opcode::OP_CHECKMULTISIG | Opcode::OP_CHECKMULTISIGVERIFY => {
|
||||||
if accurate {
|
if serialized_script && last_opcode.is_within_op_n() {
|
||||||
match last_opcode {
|
total += last_opcode.decode_op_n() as usize;
|
||||||
Opcode::OP_1 |
|
} else {
|
||||||
Opcode::OP_2 |
|
total += MAX_PUBKEYS_PER_MULTISIG;
|
||||||
Opcode::OP_3 |
|
|
||||||
Opcode::OP_4 |
|
|
||||||
Opcode::OP_5 |
|
|
||||||
Opcode::OP_6 |
|
|
||||||
Opcode::OP_7 |
|
|
||||||
Opcode::OP_8 |
|
|
||||||
Opcode::OP_9 |
|
|
||||||
Opcode::OP_10 |
|
|
||||||
Opcode::OP_11 |
|
|
||||||
Opcode::OP_12 |
|
|
||||||
Opcode::OP_13 |
|
|
||||||
Opcode::OP_14 |
|
|
||||||
Opcode::OP_15 |
|
|
||||||
Opcode::OP_16 => {
|
|
||||||
result += (last_opcode as u8 - (Opcode::OP_1 as u8 - 1)) as usize;
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
result += MAX_PUBKEYS_PER_MULTISIG;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
result += MAX_PUBKEYS_PER_MULTISIG;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => { }
|
_ => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
last_opcode = opcode;
|
last_opcode = opcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
total
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sigop_count_p2sh(&self, input_ref: &Script) -> Result<usize, Error> {
|
pub fn pay_to_script_hash_sigops(&self, prev_out: &Script) -> usize {
|
||||||
if !self.is_pay_to_script_hash() { return self.sigop_count(true); }
|
if !prev_out.is_pay_to_script_hash() {
|
||||||
|
return 0;
|
||||||
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 {
|
if self.data.is_empty() || !self.is_push_only() {
|
||||||
Some(slc) => {
|
return 0;
|
||||||
let nested_script: Script = slc.to_vec().into();
|
|
||||||
nested_script.sigop_count(true)
|
|
||||||
},
|
|
||||||
None => Ok(0),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let script: Script = self.iter().last()
|
||||||
|
.expect("self.data.is_empty() == false; qed")
|
||||||
|
.expect("self.data.is_push_only()")
|
||||||
|
.data.expect("self.data.is_push_only()")
|
||||||
|
.to_vec()
|
||||||
|
.into();
|
||||||
|
|
||||||
|
script.sigops_count(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,7 +458,7 @@ impl fmt::Display for Script {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use {Builder, Opcode};
|
use {Builder, Opcode};
|
||||||
use super::{Script, ScriptType};
|
use super::{Script, ScriptType, MAX_SCRIPT_ELEMENT_SIZE};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_pay_to_script_hash() {
|
fn test_is_pay_to_script_hash() {
|
||||||
|
@ -573,10 +539,39 @@ OP_ADD
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sigops_count() {
|
fn test_sigops_count() {
|
||||||
assert_eq!(1usize, Script::from("76a914aab76ba4877d696590d94ea3e02948b55294815188ac").sigop_count(false).unwrap());
|
assert_eq!(1usize, Script::from("76a914aab76ba4877d696590d94ea3e02948b55294815188ac").sigops_count(false));
|
||||||
assert_eq!(2usize, Script::from("522102004525da5546e7603eefad5ef971e82f7dad2272b34e6b3036ab1fe3d299c22f21037d7f2227e6c646707d1c61ecceb821794124363a2cf2c1d2a6f28cf01e5d6abe52ae").sigop_count(true).unwrap());
|
assert_eq!(2usize, Script::from("522102004525da5546e7603eefad5ef971e82f7dad2272b34e6b3036ab1fe3d299c22f21037d7f2227e6c646707d1c61ecceb821794124363a2cf2c1d2a6f28cf01e5d6abe52ae").sigops_count(true));
|
||||||
assert_eq!(20usize, Script::from("522102004525da5546e7603eefad5ef971e82f7dad2272b34e6b3036ab1fe3d299c22f21037d7f2227e6c646707d1c61ecceb821794124363a2cf2c1d2a6f28cf01e5d6abe52ae").sigop_count(false).unwrap());
|
assert_eq!(20usize, Script::from("522102004525da5546e7603eefad5ef971e82f7dad2272b34e6b3036ab1fe3d299c22f21037d7f2227e6c646707d1c61ecceb821794124363a2cf2c1d2a6f28cf01e5d6abe52ae").sigops_count(false));
|
||||||
assert_eq!(0usize, Script::from("a9146262b64aec1f4a4c1d21b32e9c2811dd2171fd7587").sigop_count(false).unwrap());
|
assert_eq!(0usize, Script::from("a9146262b64aec1f4a4c1d21b32e9c2811dd2171fd7587").sigops_count(false));
|
||||||
assert_eq!(1usize, Script::from("4104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac").sigop_count(false).unwrap());
|
assert_eq!(1usize, Script::from("4104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac").sigops_count(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sigops_count_b73() {
|
||||||
|
let max_block_sigops = 20000;
|
||||||
|
let block_sigops = 0;
|
||||||
|
let mut script = vec![Opcode::OP_CHECKSIG as u8; max_block_sigops - block_sigops + MAX_SCRIPT_ELEMENT_SIZE + 1 + 5 + 1];
|
||||||
|
script[max_block_sigops - block_sigops] = Opcode::OP_PUSHDATA4 as u8;
|
||||||
|
let overmax = MAX_SCRIPT_ELEMENT_SIZE + 1;
|
||||||
|
script[max_block_sigops - block_sigops + 1] = overmax as u8;
|
||||||
|
script[max_block_sigops - block_sigops + 2] = (overmax >> 8) as u8;
|
||||||
|
script[max_block_sigops - block_sigops + 3] = (overmax >> 16) as u8;
|
||||||
|
script[max_block_sigops - block_sigops + 4] = (overmax >> 24) as u8;
|
||||||
|
let script: Script = script.into();
|
||||||
|
assert_eq!(script.sigops_count(false), 20001);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sigops_count_b74() {
|
||||||
|
let max_block_sigops = 20000;
|
||||||
|
let block_sigops = 0;
|
||||||
|
let mut script = vec![Opcode::OP_CHECKSIG as u8; max_block_sigops - block_sigops + MAX_SCRIPT_ELEMENT_SIZE + 42];
|
||||||
|
script[max_block_sigops - block_sigops + 1] = Opcode::OP_PUSHDATA4 as u8;
|
||||||
|
script[max_block_sigops - block_sigops + 2] = 0xfe;
|
||||||
|
script[max_block_sigops - block_sigops + 3] = 0xff;
|
||||||
|
script[max_block_sigops - block_sigops + 4] = 0xff;
|
||||||
|
script[max_block_sigops - block_sigops + 5] = 0xff;
|
||||||
|
let script: Script = script.into();
|
||||||
|
assert_eq!(script.sigops_count(false), 20001);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use db::{self, BlockRef, BlockLocation};
|
use db::{self, BlockRef, BlockLocation};
|
||||||
use network::Magic;
|
use network::Magic;
|
||||||
|
use script::Script;
|
||||||
use super::{Verify, VerificationResult, Chain, Error, TransactionError, ContinueVerify};
|
use super::{Verify, VerificationResult, Chain, Error, TransactionError, ContinueVerify};
|
||||||
use {chain, utils};
|
use {chain, utils};
|
||||||
|
|
||||||
|
@ -11,8 +12,6 @@ const COINBASE_MATURITY: u32 = 100; // 2 hours
|
||||||
const MAX_BLOCK_SIGOPS: usize = 20000;
|
const MAX_BLOCK_SIGOPS: usize = 20000;
|
||||||
const MAX_BLOCK_SIZE: usize = 1000000;
|
const MAX_BLOCK_SIZE: usize = 1000000;
|
||||||
|
|
||||||
const BIP16_TIME: u32 = 1333238400;
|
|
||||||
|
|
||||||
pub struct ChainVerifier {
|
pub struct ChainVerifier {
|
||||||
store: db::SharedStore,
|
store: db::SharedStore,
|
||||||
verify_p2sh: bool,
|
verify_p2sh: bool,
|
||||||
|
@ -56,11 +55,61 @@ impl ChainVerifier {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns previous transaction output.
|
||||||
|
/// NOTE: This function expects all previous blocks to be already in database.
|
||||||
|
fn previous_transaction_output(&self, block: &chain::Block, prevout: &chain::OutPoint) -> Option<chain::TransactionOutput> {
|
||||||
|
self.store.transaction(&prevout.hash)
|
||||||
|
.as_ref()
|
||||||
|
.or_else(|| block.transactions.iter().find(|t| t.hash() == prevout.hash))
|
||||||
|
.and_then(|tx| tx.outputs.iter().nth(prevout.index as usize).cloned())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns number of transaction signature operations.
|
||||||
|
/// NOTE: This function expects all previous blocks to be already in database.
|
||||||
|
fn transaction_sigops(&self, block: &chain::Block, transaction: &chain::Transaction, bip16_active: bool) -> usize {
|
||||||
|
let output_sigops: usize = transaction.outputs.iter().map(|output| {
|
||||||
|
let output_script: Script = output.script_pubkey.clone().into();
|
||||||
|
output_script.sigops_count(false)
|
||||||
|
}).sum();
|
||||||
|
|
||||||
|
if transaction.is_coinbase() {
|
||||||
|
return output_sigops;
|
||||||
|
}
|
||||||
|
|
||||||
|
let input_sigops: usize = transaction.inputs.iter().map(|input| {
|
||||||
|
let input_script: Script = input.script_sig.clone().into();
|
||||||
|
let mut sigops = input_script.sigops_count(false);
|
||||||
|
if bip16_active {
|
||||||
|
let previous_output = self.previous_transaction_output(block, &input.previous_output)
|
||||||
|
.expect("missing tx, out of order verification or malformed db");
|
||||||
|
let prevout_script: Script = previous_output.script_pubkey.into();
|
||||||
|
sigops += input_script.pay_to_script_hash_sigops(&prevout_script);
|
||||||
|
}
|
||||||
|
sigops
|
||||||
|
}).sum();
|
||||||
|
|
||||||
|
input_sigops + output_sigops
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns number of block signature operations.
|
||||||
|
/// NOTE: This function expects all previous blocks to be already in database.
|
||||||
|
fn block_sigops(&self, block: &chain::Block) -> usize {
|
||||||
|
// strict pay-to-script-hash signature operations count toward block
|
||||||
|
// signature operations limit is enforced with BIP16
|
||||||
|
let bip16_active = block.block_header.time >= self.network.consensus_params().bip16_time;
|
||||||
|
block.transactions.iter().map(|tx| self.transaction_sigops(block, tx, bip16_active)).sum()
|
||||||
|
}
|
||||||
|
|
||||||
fn ordered_verify(&self, block: &chain::Block, at_height: u32) -> Result<(), Error> {
|
fn ordered_verify(&self, block: &chain::Block, at_height: u32) -> Result<(), Error> {
|
||||||
if !block.is_final(at_height) {
|
if !block.is_final(at_height) {
|
||||||
return Err(Error::NonFinalBlock);
|
return Err(Error::NonFinalBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// transaction verification including number of signature operations checking
|
||||||
|
if self.block_sigops(block) > MAX_BLOCK_SIGOPS {
|
||||||
|
return Err(Error::MaximumSigops);
|
||||||
|
}
|
||||||
|
|
||||||
let block_hash = block.hash();
|
let block_hash = block.hash();
|
||||||
let consensus_params = self.network.consensus_params();
|
let consensus_params = self.network.consensus_params();
|
||||||
|
|
||||||
|
@ -86,39 +135,22 @@ impl ChainVerifier {
|
||||||
|
|
||||||
let mut total_unspent = 0u64;
|
let mut total_unspent = 0u64;
|
||||||
for (tx_index, tx) in block.transactions().iter().enumerate().skip(1) {
|
for (tx_index, tx) in block.transactions().iter().enumerate().skip(1) {
|
||||||
|
|
||||||
let mut total_claimed: u64 = 0;
|
let mut total_claimed: u64 = 0;
|
||||||
|
|
||||||
for input in &tx.inputs {
|
for input in &tx.inputs {
|
||||||
|
|
||||||
// Coinbase maturity check
|
// Coinbase maturity check
|
||||||
if let Some(previous_meta) = self.store.transaction_meta(&input.previous_output.hash) {
|
if let Some(previous_meta) = self.store.transaction_meta(&input.previous_output.hash) {
|
||||||
// check if it exists only
|
// check if it exists only
|
||||||
// it will fail a little later if there is no transaction at all
|
// it will fail a little later if there is no transaction at all
|
||||||
if previous_meta.is_coinbase() &&
|
if previous_meta.is_coinbase() &&
|
||||||
(at_height < COINBASE_MATURITY || at_height - COINBASE_MATURITY < previous_meta.height())
|
(at_height < COINBASE_MATURITY || at_height - COINBASE_MATURITY < previous_meta.height()) {
|
||||||
{
|
|
||||||
return Err(Error::Transaction(tx_index, TransactionError::Maturity));
|
return Err(Error::Transaction(tx_index, TransactionError::Maturity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let reference_tx = try!(
|
let previous_output = self.previous_transaction_output(block, &input.previous_output)
|
||||||
|
.expect("missing tx, out of order verification or malformed db");
|
||||||
|
|
||||||
self.store.transaction(&input.previous_output.hash)
|
total_claimed += previous_output.value;
|
||||||
// todo: optimize block decomposition vec<transaction> -> hashmap<h256, transaction>
|
|
||||||
.or(block.transactions().iter().find(|tx| !tx.is_coinbase() && tx.hash() == input.previous_output.hash).cloned())
|
|
||||||
.ok_or(
|
|
||||||
Error::Transaction(tx_index, TransactionError::UnknownReference(input.previous_output.hash.clone()))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = try!(reference_tx.outputs.get(input.previous_output.index as usize)
|
|
||||||
.ok_or(
|
|
||||||
Error::Transaction(tx_index, TransactionError::Input(input.previous_output.index as usize))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
total_claimed += output.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let total_spends = tx.total_spends();
|
let total_spends = tx.total_spends();
|
||||||
|
@ -143,7 +175,7 @@ impl ChainVerifier {
|
||||||
block: &chain::Block,
|
block: &chain::Block,
|
||||||
transaction: &chain::Transaction,
|
transaction: &chain::Transaction,
|
||||||
sequence: usize,
|
sequence: usize,
|
||||||
) -> Result<usize, TransactionError> {
|
) -> Result<(), TransactionError> {
|
||||||
use script::{
|
use script::{
|
||||||
TransactionInputSigner,
|
TransactionInputSigner,
|
||||||
TransactionSignatureChecker,
|
TransactionSignatureChecker,
|
||||||
|
@ -152,46 +184,27 @@ impl ChainVerifier {
|
||||||
verify_script,
|
verify_script,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut sigops = utils::transaction_sigops(transaction)
|
if sequence == 0 {
|
||||||
.map_err(|e| TransactionError::SignatureMallformed(e.to_string()))?;
|
return Ok(());
|
||||||
|
}
|
||||||
if sequence == 0 { return Ok(sigops); }
|
|
||||||
|
|
||||||
// must not be coinbase (sequence = 0 is returned above)
|
// must not be coinbase (sequence = 0 is returned above)
|
||||||
if transaction.is_coinbase() { return Err(TransactionError::MisplacedCoinbase(sequence)); }
|
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() {
|
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
|
|
||||||
.as_ref()
|
|
||||||
.or_else(|| block.transactions.iter().find(|t| t.hash() == input.previous_output.hash))
|
|
||||||
.ok_or_else(|| TransactionError::Inconclusive(input.previous_output.hash.clone()))?;
|
|
||||||
|
|
||||||
if parent_transaction.outputs.len() <= input.previous_output.index as usize {
|
|
||||||
return Err(TransactionError::Input(input_index));
|
|
||||||
}
|
|
||||||
|
|
||||||
// signature verification
|
// signature verification
|
||||||
let signer: TransactionInputSigner = transaction.clone().into();
|
let signer: TransactionInputSigner = transaction.clone().into();
|
||||||
let paired_output = &parent_transaction.outputs[input.previous_output.index as usize];
|
let paired_output = match self.previous_transaction_output(block, &input.previous_output) {
|
||||||
|
Some(output) => output,
|
||||||
|
_ => return Err(TransactionError::Inconclusive(input.previous_output.hash.clone()))
|
||||||
|
};
|
||||||
|
|
||||||
let checker = TransactionSignatureChecker {
|
let checker = TransactionSignatureChecker {
|
||||||
signer: signer,
|
signer: signer,
|
||||||
input_index: input_index,
|
input_index: input_index,
|
||||||
};
|
};
|
||||||
let input: Script = input.script_sig.to_vec().into();
|
let input: Script = input.script_sig.clone().into();
|
||||||
let output: Script = paired_output.script_pubkey.to_vec().into();
|
let output: Script = paired_output.script_pubkey.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()
|
let flags = VerificationFlags::default()
|
||||||
.verify_p2sh(self.verify_p2sh)
|
.verify_p2sh(self.verify_p2sh)
|
||||||
|
@ -209,7 +222,7 @@ impl ChainVerifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(sigops)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_block(&self, block: &chain::Block) -> VerificationResult {
|
fn verify_block(&self, block: &chain::Block) -> VerificationResult {
|
||||||
|
@ -261,20 +274,8 @@ impl ChainVerifier {
|
||||||
return Err(Error::CoinbaseSignatureLength(coinbase_script_len));
|
return Err(Error::CoinbaseSignatureLength(coinbase_script_len));
|
||||||
}
|
}
|
||||||
|
|
||||||
// transaction verification including number of signature operations checking
|
|
||||||
let mut block_sigops = 0;
|
|
||||||
for (idx, transaction) in block.transactions().iter().enumerate() {
|
for (idx, transaction) in block.transactions().iter().enumerate() {
|
||||||
block_sigops += try!(
|
try!(self.verify_transaction(block, transaction, idx).map_err(|e| Error::Transaction(idx, e)))
|
||||||
self.verify_transaction(
|
|
||||||
block,
|
|
||||||
transaction,
|
|
||||||
idx,
|
|
||||||
).map_err(|e| Error::Transaction(idx, e))
|
|
||||||
);
|
|
||||||
|
|
||||||
if block_sigops > MAX_BLOCK_SIGOPS {
|
|
||||||
return Err(Error::MaximumSigops);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: pre-process projected block number once verification is parallel!
|
// todo: pre-process projected block number once verification is parallel!
|
||||||
|
|
|
@ -4,8 +4,6 @@ use std::cmp;
|
||||||
use hash::H256;
|
use hash::H256;
|
||||||
use uint::U256;
|
use uint::U256;
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use script::{self, Script};
|
|
||||||
use chain;
|
|
||||||
use compact::Compact;
|
use compact::Compact;
|
||||||
|
|
||||||
// Timespan constants
|
// Timespan constants
|
||||||
|
@ -109,30 +107,6 @@ pub fn block_reward_satoshi(block_height: u32) -> u64 {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transaction_sigops(transaction: &chain::Transaction) -> Result<usize, script::Error> {
|
|
||||||
let mut result = 0usize;
|
|
||||||
|
|
||||||
for output in &transaction.outputs {
|
|
||||||
let output_script: Script = output.script_pubkey.to_vec().into();
|
|
||||||
// todo: not always allow malformed output?
|
|
||||||
result += output_script.sigop_count(false).unwrap_or(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if transaction.is_coinbase() { return Ok(result); }
|
|
||||||
|
|
||||||
for input in &transaction.inputs {
|
|
||||||
let input_script: Script = input.script_sig.to_vec().into();
|
|
||||||
result += try!(input_script.sigop_count(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use network::Magic;
|
use network::Magic;
|
||||||
|
|
Loading…
Reference in New Issue