some verifications

This commit is contained in:
NikVolf 2016-11-10 19:19:47 +03:00
parent a77c986e11
commit 1a553e2caf
5 changed files with 90 additions and 9 deletions

View File

@ -250,6 +250,12 @@ impl Transaction {
if self.inputs.len() != 1 { return false; }
self.inputs[0].previous_output.hash.is_zero() && self.inputs[0].previous_output.index == 0xffffffff
}
pub fn total_spends(&self) -> u64 {
self.outputs
.iter()
.fold(0u64, |acc, out| acc + out.value)
}
}
#[cfg(test)]

View File

@ -147,8 +147,9 @@ impl Store for TestStorage {
Ok(())
}
fn transaction_meta(&self, _hash: &H256) -> Option<TransactionMeta> {
unimplemented!();
// just spawns new meta so far, use real store for proper tests
fn transaction_meta(&self, hash: &H256) -> Option<TransactionMeta> {
self.transaction(hash).map(|tx| TransactionMeta::new(0, tx.outputs.len()))
}
// supports only main chain in test storage

View File

@ -7,7 +7,8 @@ use byteorder::{LittleEndian, ByteOrder};
#[derive(Debug)]
pub struct TransactionMeta {
block_height: u32,
spent: BitVec,
// first bit is coinbase flag, others - one per output listed
bits: BitVec,
}
#[derive(Debug)]
@ -20,25 +21,34 @@ impl TransactionMeta {
pub fn new(block_height: u32, outputs: usize) -> Self {
TransactionMeta {
block_height: block_height,
spent: BitVec::from_elem(outputs, false),
bits: BitVec::from_elem(outputs + 1, false),
}
}
/// note that particular output has been used
pub fn note_used(&mut self, index: usize) {
self.spent.set(index, true);
self.bits.set(index + 1 , true);
}
pub fn coinbase(mut self) -> Self {
self.bits.set(0, true);
self
}
pub fn is_coinbase(&self) -> bool {
self.bits.get(0)
.expect("One bit should always exists, since it is created as usize + 1; minimum value of usize is 0; 0 + 1 = 1; qed")
}
/// note that particular output has been used
pub fn denote_used(&mut self, index: usize) {
self.spent.set(index, false);
self.bits.set(index + 1, false);
}
pub fn into_bytes(self) -> Vec<u8> {
let mut result = vec![0u8; 4];
LittleEndian::write_u32(&mut result[0..4], self.block_height);
result.extend(self.spent.to_bytes());
result.extend(self.bits.to_bytes());
result
}
@ -47,12 +57,12 @@ impl TransactionMeta {
Ok(TransactionMeta {
block_height: LittleEndian::read_u32(&bytes[0..4]),
spent: BitVec::from_bytes(&bytes[4..]),
bits: BitVec::from_bytes(&bytes[4..]),
})
}
pub fn height(&self) -> u32 { self.block_height }
pub fn is_spent(&self, idx: usize) -> bool { self.spent.get(idx).expect("Index should be verified by the caller") }
pub fn is_spent(&self, idx: usize) -> bool { self.bits.get(idx + 1).expect("Index should be verified by the caller") }
}

View File

@ -8,6 +8,7 @@ use super::{Verify, VerificationResult, Chain, Error, TransactionError, Continue
use utils;
const BLOCK_MAX_FUTURE: i64 = 2 * 60 * 60; // 2 hours
const COINBASE_MATURITY: u32 = 100; // 2 hours
pub struct ChainVerifier {
store: Arc<db::Store>,
@ -19,6 +20,63 @@ impl ChainVerifier {
}
fn ordered_verify(&self, block: &chain::Block, at_height: u32) -> Result<(), Error> {
let coinbase_spends = block.transactions()[0].total_spends();
let mut total_unspent = 0u64;
for (tx_index, tx) in block.transactions().iter().skip(1).enumerate() {
let mut total_claimed: u64 = 0;
for (_, input) in tx.inputs.iter().enumerate() {
// Coinbase maturity check
let previous_meta = try!(
self.store
.transaction_meta(&input.previous_output.hash)
.ok_or(
Error::Transaction(tx_index, TransactionError::UnknownReference(input.previous_output.hash.clone()))
)
);
if previous_meta.is_coinbase()
&& (at_height < COINBASE_MATURITY ||
at_height - COINBASE_MATURITY < previous_meta.height())
{
return Err(Error::Transaction(tx_index, TransactionError::Maturity));
}
let reference_tx = try!(
self.store.transaction(&input.previous_output.hash)
.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();
if total_claimed < total_spends {
return Err(Error::Transaction(tx_index, TransactionError::Overspend));
}
// total_claimed is greater than total_spends, checked above and returned otherwise, cannot overflow; qed
total_unspent += total_claimed - total_spends;
}
let expected_max = utils::block_reward_satoshi(at_height) + total_unspent;
if coinbase_spends > expected_max{
return Err(Error::CoinbaseOverspend { expected_max: expected_max, actual: coinbase_spends });
}
Ok(())
}

View File

@ -43,6 +43,8 @@ pub enum Error {
Difficulty,
/// Invalid merkle root
MerkleRoot,
/// Coinbase spends too much
CoinbaseOverspend { expected_max: u64, actual: u64 },
}
#[derive(Debug, PartialEq)]
@ -56,6 +58,10 @@ pub enum TransactionError {
Signature(usize),
/// Inconclusive (unknown parent transaction)
Inconclusive(H256),
/// Unknown previous transaction referenced
UnknownReference(H256),
/// Spends more than claims
Overspend,
}
#[derive(PartialEq, Debug)]