few functions are more idiomatic, initial support for bip30
This commit is contained in:
parent
7f07b60a85
commit
9d0d251a37
|
@ -175,7 +175,7 @@ impl Storage {
|
|||
if let Some(accepted_tx) = accepted_txs.iter().next() {
|
||||
context.meta.insert(
|
||||
accepted_tx.hash(),
|
||||
TransactionMeta::new(number, accepted_tx.outputs.len()).coinbase()
|
||||
TransactionMeta::new_coinbase(number, accepted_tx.outputs.len())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -186,29 +186,27 @@ impl Storage {
|
|||
);
|
||||
|
||||
for input in &accepted_tx.inputs {
|
||||
if !match context.meta.get_mut(&input.previous_output.hash) {
|
||||
Some(ref mut meta) => {
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
match context.meta.entry(input.previous_output.hash.clone()) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
let meta = entry.get_mut();
|
||||
if meta.is_spent(input.previous_output.index as usize) {
|
||||
return Err(Error::double_spend(&input.previous_output.hash));
|
||||
}
|
||||
meta.denote_used(input.previous_output.index as usize);
|
||||
},
|
||||
Entry::Vacant(entry) => {
|
||||
let mut meta = self.transaction_meta(&input.previous_output.hash)
|
||||
.ok_or(Error::unknown_spending(&input.previous_output.hash))?;
|
||||
|
||||
if meta.is_spent(input.previous_output.index as usize) {
|
||||
return Err(Error::double_spend(&input.previous_output.hash));
|
||||
}
|
||||
|
||||
meta.denote_used(input.previous_output.index as usize);
|
||||
true
|
||||
entry.insert(meta);
|
||||
},
|
||||
None => false,
|
||||
} {
|
||||
let mut meta = self.transaction_meta(&input.previous_output.hash)
|
||||
.ok_or(Error::unknown_spending(&input.previous_output.hash))?;
|
||||
|
||||
if meta.is_spent(input.previous_output.index as usize) {
|
||||
return Err(Error::double_spend(&input.previous_output.hash));
|
||||
}
|
||||
|
||||
meta.denote_used(input.previous_output.index as usize);
|
||||
|
||||
context.meta.insert(
|
||||
input.previous_output.hash.clone(),
|
||||
meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,30 +236,24 @@ impl Storage {
|
|||
// remove meta
|
||||
context.db_transaction.delete(Some(COL_TRANSACTIONS_META), &**tx_hash);
|
||||
|
||||
// denote outputs used
|
||||
if tx_hash_num == 0 { continue; } // coinbase transaction does not have inputs
|
||||
// coinbase transaction does not have inputs
|
||||
if tx_hash_num == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// denote outputs as unused
|
||||
for input in &tx.inputs {
|
||||
if !match context.meta.get_mut(&input.previous_output.hash) {
|
||||
Some(ref mut meta) => {
|
||||
meta.denote_unused(input.previous_output.index as usize);
|
||||
true
|
||||
use std::collections::hash_map::Entry;
|
||||
match context.meta.entry(input.previous_output.hash.clone()) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().denote_unused(input.previous_output.index as usize);
|
||||
},
|
||||
Entry::Vacant(entry) => {
|
||||
let mut meta = self.transaction_meta(&input.previous_output.hash)
|
||||
.expect("No transaction metadata! Possible db corruption");
|
||||
meta.denote_unused(input.previous_output.index as usize);
|
||||
entry.insert(meta);
|
||||
},
|
||||
None => false,
|
||||
} {
|
||||
let mut meta =
|
||||
self.transaction_meta(&input.previous_output.hash)
|
||||
.unwrap_or_else(|| panic!(
|
||||
// decanonization should always have meta
|
||||
// because block could not have made canonical without writing meta
|
||||
"No transaction metadata for {}! Corrupted DB? Reindex?",
|
||||
&input.previous_output.hash
|
||||
));
|
||||
|
||||
meta.denote_unused(input.previous_output.index as usize);
|
||||
|
||||
context.meta.insert(
|
||||
input.previous_output.hash.clone(),
|
||||
meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ use byteorder::{LittleEndian, ByteOrder};
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct TransactionMeta {
|
||||
block_height: u32,
|
||||
// first bit is coinbase flag, others - one per output listed
|
||||
/// first bit indicate if transaction is a coinbase transaction
|
||||
/// next bits indicate if transaction has spend outputs
|
||||
bits: BitVec,
|
||||
}
|
||||
|
||||
|
@ -17,7 +18,7 @@ pub enum Error {
|
|||
}
|
||||
|
||||
impl TransactionMeta {
|
||||
/// new transaction description for indexing
|
||||
/// New transaction description for indexing
|
||||
pub fn new(block_height: u32, outputs: usize) -> Self {
|
||||
TransactionMeta {
|
||||
block_height: block_height,
|
||||
|
@ -25,22 +26,25 @@ impl TransactionMeta {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn coinbase(mut self) -> Self {
|
||||
self.bits.set(0, true);
|
||||
self
|
||||
/// New coinbase transaction
|
||||
pub fn new_coinbase(block_height: u32, outputs: usize) -> Self {
|
||||
let mut result = Self::new(block_height, outputs);
|
||||
result.bits.set(0, true);
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns true if it is a coinbase transaction
|
||||
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")
|
||||
.expect("One bit should always exists, since it is created as usize + 1; minimum value of usize is 0; 0 + 1 = 1; qed")
|
||||
}
|
||||
|
||||
/// denote particular output as used
|
||||
/// Denote particular output as used
|
||||
pub fn denote_used(&mut self, index: usize) {
|
||||
self.bits.set(index + 1 , true);
|
||||
}
|
||||
|
||||
/// denote particular output as not used
|
||||
/// Denote particular output as not used
|
||||
pub fn denote_unused(&mut self, index: usize) {
|
||||
self.bits.set(index + 1, false);
|
||||
}
|
||||
|
@ -61,8 +65,34 @@ impl TransactionMeta {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u32 { self.block_height }
|
||||
pub fn height(&self) -> u32 {
|
||||
self.block_height
|
||||
}
|
||||
|
||||
pub fn is_spent(&self, idx: usize) -> bool { self.bits.get(idx + 1).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")
|
||||
}
|
||||
|
||||
pub fn is_fully_spent(&self) -> bool {
|
||||
// skip coinbase bit, the rest needs to true
|
||||
self.bits.iter().skip(1).all(|x| x)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::TransactionMeta;
|
||||
|
||||
#[test]
|
||||
fn test_is_fully_spent() {
|
||||
let t = TransactionMeta::new(0, 0);
|
||||
assert!(t.is_fully_spent());
|
||||
|
||||
let mut t = TransactionMeta::new(0, 1);
|
||||
assert!(!t.is_fully_spent());
|
||||
t.denote_used(0);
|
||||
assert!(t.is_fully_spent());
|
||||
t.denote_unused(0);
|
||||
assert!(!t.is_fully_spent());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,16 @@ impl ChainVerifier {
|
|||
|
||||
let coinbase_spends = block.transactions()[0].total_spends();
|
||||
|
||||
// bip30
|
||||
// TODO: add exceptions
|
||||
for (tx_index, tx) in block.transactions.iter().enumerate() {
|
||||
if let Some(meta) = self.store.transaction_meta(&tx.hash()) {
|
||||
if !meta.is_fully_spent() {
|
||||
return Err(Error::Transaction(tx_index, TransactionError::BIP30));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut total_unspent = 0u64;
|
||||
for (tx_index, tx) in block.transactions().iter().enumerate().skip(1) {
|
||||
|
||||
|
|
|
@ -84,6 +84,8 @@ pub enum TransactionError {
|
|||
SigopsP2SH(usize),
|
||||
/// Coinbase transaction is found at position that is not 0
|
||||
MisplacedCoinbase(usize),
|
||||
/// Not fully spent transaction with the same hash already exists.
|
||||
BIP30,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
|
|
Loading…
Reference in New Issue