commit
23b1c804e3
|
@ -398,6 +398,7 @@ name = "network"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chain 0.1.0",
|
||||
"primitives 0.1.0",
|
||||
"serialization 0.1.0",
|
||||
]
|
||||
|
||||
|
|
|
@ -70,6 +70,10 @@ impl Block {
|
|||
pub fn hash(&self) -> H256 {
|
||||
self.block_header.hash()
|
||||
}
|
||||
|
||||
pub fn is_final(&self, height: u32) -> bool {
|
||||
self.transactions.iter().all(|t| t.is_final(height, self.block_header.time))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -23,7 +23,8 @@ pub use self::merkle_root::merkle_node_hash;
|
|||
pub use self::transaction::{
|
||||
Transaction, TransactionInput, TransactionOutput, OutPoint,
|
||||
SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_FINAL,
|
||||
SEQUENCE_LOCKTIME_TYPE_FLAG, SEQUENCE_LOCKTIME_MASK
|
||||
SEQUENCE_LOCKTIME_TYPE_FLAG, SEQUENCE_LOCKTIME_MASK,
|
||||
LOCKTIME_THRESHOLD
|
||||
};
|
||||
|
||||
pub type ShortTransactionID = hash::H48;
|
||||
|
|
|
@ -30,6 +30,9 @@ pub const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = (1 << 22);
|
|||
// applied to extract that lock-time from the sequence field.
|
||||
pub const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000ffff;
|
||||
|
||||
/// Threshold for `nLockTime`: below this value it is interpreted as block number,
|
||||
/// otherwise as UNIX timestamp.
|
||||
pub const LOCKTIME_THRESHOLD: u32 = 500000000; // Tue Nov 5 00:53:20 1985 UTC
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
pub struct OutPoint {
|
||||
|
@ -66,10 +69,6 @@ impl OutPoint {
|
|||
&self.hash
|
||||
}
|
||||
|
||||
pub fn index(&self) -> u32 {
|
||||
self.index
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.hash.is_zero() && self.index == u32::max_value()
|
||||
}
|
||||
|
@ -82,6 +81,12 @@ pub struct TransactionInput {
|
|||
pub sequence: u32,
|
||||
}
|
||||
|
||||
impl TransactionInput {
|
||||
pub fn is_final(&self) -> bool {
|
||||
self.sequence == SEQUENCE_FINAL
|
||||
}
|
||||
}
|
||||
|
||||
impl Serializable for TransactionInput {
|
||||
fn serialize(&self, stream: &mut Stream) {
|
||||
stream
|
||||
|
@ -116,20 +121,6 @@ impl HeapSizeOf for TransactionInput {
|
|||
}
|
||||
}
|
||||
|
||||
impl TransactionInput {
|
||||
pub fn previous_output(&self) -> &OutPoint {
|
||||
&self.previous_output
|
||||
}
|
||||
|
||||
pub fn script_sig(&self) -> &[u8] {
|
||||
&self.script_sig
|
||||
}
|
||||
|
||||
pub fn sequence(&self) -> u32 {
|
||||
self.sequence
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct TransactionOutput {
|
||||
pub value: u64,
|
||||
|
@ -176,16 +167,6 @@ impl HeapSizeOf for TransactionOutput {
|
|||
}
|
||||
}
|
||||
|
||||
impl TransactionOutput {
|
||||
pub fn value(&self) -> u64 {
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn script_pubkey(&self) -> &[u8] {
|
||||
&self.script_pubkey
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default, Clone)]
|
||||
pub struct Transaction {
|
||||
pub version: i32,
|
||||
|
@ -254,6 +235,24 @@ impl Transaction {
|
|||
self.inputs.len() == 1 && self.inputs[0].previous_output.is_null()
|
||||
}
|
||||
|
||||
pub fn is_final(&self, block_height: u32, block_time: u32) -> bool {
|
||||
if self.lock_time == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let max_lock_time = if self.lock_time < LOCKTIME_THRESHOLD {
|
||||
block_height
|
||||
} else {
|
||||
block_time
|
||||
};
|
||||
|
||||
if self.lock_time < max_lock_time {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.inputs.iter().all(TransactionInput::is_final)
|
||||
}
|
||||
|
||||
pub fn total_spends(&self) -> u64 {
|
||||
self.outputs
|
||||
.iter()
|
||||
|
|
|
@ -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,17 +186,17 @@ 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);
|
||||
true
|
||||
},
|
||||
None => false,
|
||||
} {
|
||||
Entry::Vacant(entry) => {
|
||||
let mut meta = self.transaction_meta(&input.previous_output.hash)
|
||||
.ok_or(Error::unknown_spending(&input.previous_output.hash))?;
|
||||
|
||||
|
@ -205,10 +205,8 @@ impl Storage {
|
|||
}
|
||||
|
||||
meta.denote_used(input.previous_output.index as usize);
|
||||
|
||||
context.meta.insert(
|
||||
input.previous_output.hash.clone(),
|
||||
meta);
|
||||
entry.insert(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);
|
||||
},
|
||||
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
|
||||
));
|
||||
|
||||
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);
|
||||
|
||||
context.meta.insert(
|
||||
input.previous_output.hash.clone(),
|
||||
meta);
|
||||
entry.insert(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")
|
||||
}
|
||||
|
||||
/// 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 is_spent(&self, idx: usize) -> bool { self.bits.get(idx + 1).expect("Index should be verified by the caller") }
|
||||
|
||||
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_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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,3 +6,4 @@ authors = ["debris <marek.kotewicz@gmail.com>"]
|
|||
[dependencies]
|
||||
serialization = { path = "../serialization" }
|
||||
chain = { path = "../chain" }
|
||||
primitives = { path = "../primitives" }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use hash::H256;
|
||||
use super::Magic;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -28,6 +29,11 @@ impl ConsensusParams {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_bip30_exception(&self, hash: &H256, height: u32) -> bool {
|
||||
(height == 91842 && hash == &H256::from_reversed_str("0x00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")) ||
|
||||
(height == 91880 && hash == &H256::from_reversed_str("0x00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
extern crate chain;
|
||||
extern crate primitives;
|
||||
extern crate serialization as ser;
|
||||
|
||||
mod consensus;
|
||||
mod magic;
|
||||
|
||||
pub use primitives::hash;
|
||||
|
||||
pub use consensus::ConsensusParams;
|
||||
pub use magic::Magic;
|
||||
|
||||
|
|
|
@ -16,10 +16,6 @@ pub const MAX_PUBKEYS_PER_MULTISIG: usize = 20;
|
|||
/// Maximum script length in bytes
|
||||
pub const MAX_SCRIPT_SIZE: usize = 10000;
|
||||
|
||||
/// Threshold for `nLockTime`: below this value it is interpreted as block number,
|
||||
/// otherwise as UNIX timestamp.
|
||||
pub const LOCKTIME_THRESHOLD: u32 = 500000000; // Tue Nov 5 00:53:20 1985 UTC
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum ScriptType {
|
||||
NonStandard,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use keys::{Public, Signature};
|
||||
use chain::{
|
||||
SEQUENCE_FINAL, SEQUENCE_LOCKTIME_DISABLE_FLAG,
|
||||
self, SEQUENCE_FINAL, SEQUENCE_LOCKTIME_DISABLE_FLAG,
|
||||
SEQUENCE_LOCKTIME_MASK, SEQUENCE_LOCKTIME_TYPE_FLAG
|
||||
};
|
||||
use {script, SignatureVersion, Script, TransactionInputSigner, Num};
|
||||
use {SignatureVersion, Script, TransactionInputSigner, Num};
|
||||
|
||||
pub trait SignatureChecker {
|
||||
fn check_signature(
|
||||
|
@ -64,8 +64,8 @@ impl SignatureChecker for TransactionSignatureChecker {
|
|||
// the nLockTime in the transaction.
|
||||
let lock_time_u32: u32 = lock_time.into();
|
||||
if !(
|
||||
(self.signer.lock_time < script::LOCKTIME_THRESHOLD && lock_time_u32 < script::LOCKTIME_THRESHOLD) ||
|
||||
(self.signer.lock_time >= script::LOCKTIME_THRESHOLD && lock_time_u32 >= script::LOCKTIME_THRESHOLD)
|
||||
(self.signer.lock_time < chain::LOCKTIME_THRESHOLD && lock_time_u32 < chain::LOCKTIME_THRESHOLD) ||
|
||||
(self.signer.lock_time >= chain::LOCKTIME_THRESHOLD && lock_time_u32 >= chain::LOCKTIME_THRESHOLD)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -57,6 +57,13 @@ impl ChainVerifier {
|
|||
}
|
||||
|
||||
fn ordered_verify(&self, block: &chain::Block, at_height: u32) -> Result<(), Error> {
|
||||
if !block.is_final(at_height) {
|
||||
return Err(Error::NonFinalBlock);
|
||||
}
|
||||
|
||||
let block_hash = block.hash();
|
||||
let consensus_params = self.network.consensus_params();
|
||||
|
||||
// check that difficulty matches the adjusted level
|
||||
if let Some(work) = self.work_required(block, at_height) {
|
||||
if !self.skip_pow && work != block.header().nbits {
|
||||
|
@ -68,6 +75,15 @@ impl ChainVerifier {
|
|||
|
||||
let coinbase_spends = block.transactions()[0].total_spends();
|
||||
|
||||
// bip30
|
||||
for (tx_index, tx) in block.transactions.iter().enumerate() {
|
||||
if let Some(meta) = self.store.transaction_meta(&tx.hash()) {
|
||||
if !meta.is_fully_spent() && !consensus_params.is_bip30_exception(&block_hash, at_height) {
|
||||
return Err(Error::Transaction(tx_index, TransactionError::UnspentTransactionWithTheSameHash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut total_unspent = 0u64;
|
||||
for (tx_index, tx) in block.transactions().iter().enumerate().skip(1) {
|
||||
|
||||
|
@ -168,7 +184,7 @@ impl ChainVerifier {
|
|||
signer: signer,
|
||||
input_index: input_index,
|
||||
};
|
||||
let input: Script = input.script_sig().to_vec().into();
|
||||
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() {
|
||||
|
@ -240,7 +256,7 @@ impl ChainVerifier {
|
|||
// check that coinbase has a valid signature
|
||||
let coinbase = &block.transactions()[0];
|
||||
// is_coinbase() = true above guarantees that there is at least one input
|
||||
let coinbase_script_len = coinbase.inputs[0].script_sig().len();
|
||||
let coinbase_script_len = coinbase.inputs[0].script_sig.len();
|
||||
if coinbase_script_len < 2 || coinbase_script_len > 100 {
|
||||
return Err(Error::CoinbaseSignatureLength(coinbase_script_len));
|
||||
}
|
||||
|
@ -455,6 +471,7 @@ mod tests {
|
|||
let genesis = test_data::block_builder()
|
||||
.transaction()
|
||||
.coinbase()
|
||||
.output().value(1).build()
|
||||
.build()
|
||||
.transaction()
|
||||
.output().value(50).build()
|
||||
|
@ -488,6 +505,7 @@ mod tests {
|
|||
let genesis = test_data::block_builder()
|
||||
.transaction()
|
||||
.coinbase()
|
||||
.output().value(1).build()
|
||||
.build()
|
||||
.transaction()
|
||||
.output().value(50).build()
|
||||
|
@ -525,6 +543,7 @@ mod tests {
|
|||
let genesis = test_data::block_builder()
|
||||
.transaction()
|
||||
.coinbase()
|
||||
.output().value(1).build()
|
||||
.build()
|
||||
.transaction()
|
||||
.output().value(50).build()
|
||||
|
|
|
@ -59,6 +59,8 @@ pub enum Error {
|
|||
CoinbaseSignatureLength(usize),
|
||||
/// Block size is invalid
|
||||
Size(usize),
|
||||
/// Block transactions are not final.
|
||||
NonFinalBlock,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -84,6 +86,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.
|
||||
UnspentTransactionWithTheSameHash,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
|
|
|
@ -121,7 +121,7 @@ pub fn transaction_sigops(transaction: &chain::Transaction) -> Result<usize, scr
|
|||
if transaction.is_coinbase() { return Ok(result); }
|
||||
|
||||
for input in &transaction.inputs {
|
||||
let input_script: Script = input.script_sig().to_vec().into();
|
||||
let input_script: Script = input.script_sig.to_vec().into();
|
||||
result += try!(input_script.sigop_count(false));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue