Fix BIP30 rewind handling; add unsafe annotations to ThinVec::reserve
This commit is contained in:
parent
fe9ca39736
commit
44dc29f013
|
@ -39,7 +39,7 @@ pub struct UtxoSet {
|
||||||
table: HashMap<Uint128, UtxoNode, DumbHasher>,
|
table: HashMap<Uint128, UtxoNode, DumbHasher>,
|
||||||
last_hash: Sha256dHash,
|
last_hash: Sha256dHash,
|
||||||
// A circular buffer of deleted utxos, grouped by block
|
// A circular buffer of deleted utxos, grouped by block
|
||||||
spent_txos: Vec<Vec<TxOut>>,
|
spent_txos: Vec<Vec<((Sha256dHash, u32), TxOut)>>,
|
||||||
// The last index into the above buffer that was assigned to
|
// The last index into the above buffer that was assigned to
|
||||||
spent_idx: u64,
|
spent_idx: u64,
|
||||||
n_utxos: u64
|
n_utxos: u64
|
||||||
|
@ -64,7 +64,7 @@ impl UtxoSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add all the UTXOs of a transaction to the set
|
/// Add all the UTXOs of a transaction to the set
|
||||||
fn add_utxos(&mut self, tx: &Transaction) -> bool {
|
fn add_utxos(&mut self, tx: &Transaction) -> Option<UtxoNode> {
|
||||||
let txid = tx.bitcoin_hash();
|
let txid = tx.bitcoin_hash();
|
||||||
// Locate node if it's already there
|
// Locate node if it's already there
|
||||||
let mut new_node = ThinVec::with_capacity(tx.output.len() as u32);
|
let mut new_node = ThinVec::with_capacity(tx.output.len() as u32);
|
||||||
|
@ -72,12 +72,13 @@ impl UtxoSet {
|
||||||
// Unsafe since we are not uninitializing the old data in the vector
|
// Unsafe since we are not uninitializing the old data in the vector
|
||||||
unsafe { new_node.init(vout as uint, Some(txo.clone())); }
|
unsafe { new_node.init(vout as uint, Some(txo.clone())); }
|
||||||
}
|
}
|
||||||
// TODO: insert/lookup should return a Result which we pass along
|
// Get the old value, if any (this is suprisingly possible, c.f. BIP30
|
||||||
if self.table.insert(txid.as_uint128(), new_node) {
|
// and the other comments in this file referring to it)
|
||||||
|
let ret = self.table.swap(txid.as_uint128(), new_node);
|
||||||
|
if ret.is_none() {
|
||||||
self.n_utxos += tx.output.len() as u64;
|
self.n_utxos += tx.output.len() as u64;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a UTXO from the set and return it
|
/// Remove a UTXO from the set and return it
|
||||||
|
@ -139,39 +140,52 @@ impl UtxoSet {
|
||||||
|
|
||||||
let mut skipped_genesis = false;
|
let mut skipped_genesis = false;
|
||||||
for tx in block.txdata.iter() {
|
for tx in block.txdata.iter() {
|
||||||
|
let txid = tx.bitcoin_hash();
|
||||||
// Put the removed utxos into the stxo cache. Note that the order that
|
// Put the removed utxos into the stxo cache. Note that the order that
|
||||||
// they are pushed onto the stxo cache -must- match the order of the
|
// they are pushed onto the stxo cache -must- match the order of the
|
||||||
// txos in the block so that rewind() will rewind them properly.
|
// txos in the block so that rewind() will rewind them properly.
|
||||||
if skipped_genesis {
|
if skipped_genesis {
|
||||||
self.spent_txos.get_mut(spent_idx).reserve_additional(tx.input.len());
|
self.spent_txos.get_mut(spent_idx).reserve_additional(tx.input.len());
|
||||||
for input in tx.input.iter() {
|
for (n, input) in tx.input.iter().enumerate() {
|
||||||
let taken = self.take_utxo(input.prev_hash, input.prev_index);
|
let taken = self.take_utxo(input.prev_hash, input.prev_index);
|
||||||
match taken {
|
match taken {
|
||||||
Some(txo) => { self.spent_txos.get_mut(spent_idx).push(txo); }
|
Some(txo) => { self.spent_txos.get_mut(spent_idx).push(((txid, n as u32), txo)); }
|
||||||
None => { self.rewind(block); }
|
None => { self.rewind(block); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
skipped_genesis = true;
|
skipped_genesis = true;
|
||||||
|
|
||||||
// Add outputs
|
let txid = tx.bitcoin_hash();
|
||||||
// This will fail in the case of a duplicate transaction. This can only
|
// Add outputs -- add_utxos returns the original transaction if this is a dupe.
|
||||||
// happen with coinbases, and in this case the block is invalid, -except-
|
// Note that this can only happen with coinbases, and in this case the block
|
||||||
// for two historic blocks which appeared in the blockchain before the
|
// is invalid, -except- for two historic blocks which appeared in the
|
||||||
// dupes were noticed. See bitcoind commit `ab91bf39` and BIP30.
|
// blockchain before the dupes were noticed.
|
||||||
// TODO: add a unit test for these blocks.
|
// See bitcoind commit `ab91bf39` and BIP30.
|
||||||
if !self.add_utxos(tx) {
|
match self.add_utxos(tx) {
|
||||||
let blockhash = block.header.bitcoin_hash().le_hex_string();
|
Some(mut replace) => {
|
||||||
if blockhash == "00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec".to_string() ||
|
let blockhash = block.header.bitcoin_hash().le_hex_string();
|
||||||
blockhash == "00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721".to_string() {
|
if blockhash == "00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec".to_string() ||
|
||||||
// For these specific blocks, overwrite the old UTXOs.
|
blockhash == "00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721".to_string() {
|
||||||
self.table.remove(&tx.bitcoin_hash().as_uint128());
|
// For these specific blocks, overwrite the old UTXOs.
|
||||||
self.add_utxos(tx);
|
// (Actually add_utxos() already did this, so we do nothing.)
|
||||||
} else {
|
} else {
|
||||||
// Otherwise fail the block
|
// Otherwise put the replaced txouts into the `deleted` cache
|
||||||
self.rewind(block);
|
// so that rewind will put them back.
|
||||||
return false;
|
self.spent_txos.get_mut(spent_idx).reserve_additional(replace.len());
|
||||||
|
for (n, input) in replace.mut_iter().enumerate() {
|
||||||
|
match input.take() {
|
||||||
|
Some(txo) => { self.spent_txos.get_mut(spent_idx).push(((txid, n as u32), txo)); }
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise fail the block
|
||||||
|
self.rewind(block);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Didn't replace anything? Good.
|
||||||
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we made it here, success!
|
// If we made it here, success!
|
||||||
|
@ -199,41 +213,47 @@ impl UtxoSet {
|
||||||
let txhash = tx.bitcoin_hash();
|
let txhash = tx.bitcoin_hash();
|
||||||
for n in range(0, tx.output.len()) {
|
for n in range(0, tx.output.len()) {
|
||||||
// Just bomb out the whole transaction
|
// Just bomb out the whole transaction
|
||||||
|
// TODO: this does not conform to BIP30: if a duplicate txid occurs,
|
||||||
|
// the block will be (rightly) rejected, causing it to be
|
||||||
|
// unwound. But when we get here, we can't see the duplicate,
|
||||||
|
// so we wind up deleting the old txid! This is very bad, and
|
||||||
|
// if it occurs, an affected user will have to recreate his
|
||||||
|
// whole UTXO index to get the original txid back.
|
||||||
self.take_utxo(txhash, n as u32);
|
self.take_utxo(txhash, n as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read deleted txouts -- note that we are trusting that these are
|
// Read deleted txouts
|
||||||
// in the same order in our cache as they were in the original block.
|
|
||||||
if skipped_genesis {
|
if skipped_genesis {
|
||||||
|
|
||||||
let mut extract_vec = vec![];
|
let mut extract_vec = vec![];
|
||||||
mem::swap(&mut extract_vec, self.spent_txos.get_mut(self.spent_idx as uint));
|
mem::swap(&mut extract_vec, self.spent_txos.get_mut(self.spent_idx as uint));
|
||||||
for (txo, inp) in extract_vec.move_iter().zip(tx.input.iter()) {
|
for ((txid, n), txo) in extract_vec.move_iter() {
|
||||||
// Remove the tx's utxo list and patch the txo into place
|
// Remove the tx's utxo list and patch the txo into place
|
||||||
let new_node =
|
let new_node =
|
||||||
match self.table.pop(&inp.prev_hash.as_uint128()) {
|
match self.table.pop(&txid.as_uint128()) {
|
||||||
Some(mut thinvec) => {
|
Some(mut thinvec) => {
|
||||||
let old_len = thinvec.len() as u32;
|
let old_len = thinvec.len() as u32;
|
||||||
if old_len < inp.prev_index + 1 {
|
if old_len < n + 1 {
|
||||||
thinvec.reserve(inp.prev_index + 1);
|
unsafe {
|
||||||
for i in range(old_len, inp.prev_index + 1) {
|
thinvec.reserve(n + 1);
|
||||||
unsafe { thinvec.init(i as uint, None); }
|
for i in range(old_len, n + 1) {
|
||||||
|
thinvec.init(i as uint, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unsafe { *thinvec.get_mut(inp.prev_index as uint) = Some(txo); }
|
unsafe { *thinvec.get_mut(n as uint) = Some(txo); }
|
||||||
thinvec
|
thinvec
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let mut thinvec = ThinVec::with_capacity(inp.prev_index + 1);
|
let mut thinvec = ThinVec::with_capacity(n + 1);
|
||||||
for i in range(0, inp.prev_index + 1) {
|
for i in range(0, n + 1) {
|
||||||
unsafe { thinvec.init(i as uint, None); }
|
unsafe { thinvec.init(i as uint, None); }
|
||||||
}
|
}
|
||||||
unsafe { *thinvec.get_mut(inp.prev_index as uint) = Some(txo); }
|
unsafe { *thinvec.get_mut(n as uint) = Some(txo); }
|
||||||
thinvec
|
thinvec
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Ram it back into the tree
|
// Ram it back into the tree
|
||||||
self.table.insert(inp.prev_hash.as_uint128(), new_node);
|
self.table.insert(txid.as_uint128(), new_node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
skipped_genesis = true;
|
skipped_genesis = true;
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
use alloc::heap::{allocate, reallocate, deallocate};
|
use alloc::heap::{allocate, reallocate, deallocate};
|
||||||
use std::raw::Slice;
|
use std::raw::Slice;
|
||||||
use std::slice::Items;
|
use std::slice::{Items, MutItems};
|
||||||
use std::{fmt, mem, ptr};
|
use std::{fmt, mem, ptr};
|
||||||
use std::u32;
|
use std::u32;
|
||||||
|
|
||||||
|
@ -57,6 +57,12 @@ impl<T> ThinVec<T> {
|
||||||
self.as_slice().iter()
|
self.as_slice().iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mutable iterator over elements of the vector
|
||||||
|
#[inline]
|
||||||
|
pub fn mut_iter<'a>(&'a mut self) -> MutItems<'a, T> {
|
||||||
|
self.as_mut_slice().mut_iter()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get vector as mutable slice
|
/// Get vector as mutable slice
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_mut_slice<'a>(&'a mut self) -> &'a mut [T] {
|
pub fn as_mut_slice<'a>(&'a mut self) -> &'a mut [T] {
|
||||||
|
@ -105,25 +111,23 @@ impl<T> ThinVec<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the length of the vector to the minimum of the current capacity and new capacity
|
/// Set the length of the vector to the minimum of the current capacity and new capacity
|
||||||
pub fn reserve(&mut self, new_cap: u32) {
|
pub unsafe fn reserve(&mut self, new_cap: u32) {
|
||||||
if new_cap > self.cap {
|
if new_cap > self.cap {
|
||||||
let new_size = (new_cap as uint).checked_mul(&mem::size_of::<T>())
|
let new_size = (new_cap as uint).checked_mul(&mem::size_of::<T>())
|
||||||
.expect("ThinVec::reserve: capacity overflow");
|
.expect("ThinVec::reserve: capacity overflow");
|
||||||
unsafe {
|
self.ptr =
|
||||||
self.ptr =
|
if self.cap == 0 {
|
||||||
if self.cap == 0 {
|
allocate(new_size, mem::min_align_of::<T>()) as *mut T
|
||||||
allocate(new_size, mem::min_align_of::<T>()) as *mut T
|
} else {
|
||||||
} else {
|
reallocate(self.ptr as *mut u8, new_size,
|
||||||
reallocate(self.ptr as *mut u8, new_size,
|
mem::min_align_of::<T>(), self.cap as uint) as *mut T
|
||||||
mem::min_align_of::<T>(), self.cap as uint) as *mut T
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
self.cap = new_cap;
|
self.cap = new_cap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Increase the length of the vector
|
/// Increase the length of the vector
|
||||||
pub fn reserve_additional(&mut self, extra: u32) {
|
pub unsafe fn reserve_additional(&mut self, extra: u32) {
|
||||||
let new_cap = self.cap.checked_add(&extra).expect("ThinVec::reserve_additional: length overflow");
|
let new_cap = self.cap.checked_add(&extra).expect("ThinVec::reserve_additional: length overflow");
|
||||||
self.reserve(new_cap);
|
self.reserve(new_cap);
|
||||||
}
|
}
|
||||||
|
@ -134,7 +138,7 @@ impl<T:Clone> ThinVec<T> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn push_all(&mut self, other: &[T]) {
|
pub fn push_all(&mut self, other: &[T]) {
|
||||||
let old_cap = self.cap as uint;
|
let old_cap = self.cap as uint;
|
||||||
self.reserve_additional(other.len() as u32);
|
unsafe { self.reserve_additional(other.len() as u32); }
|
||||||
// Copied from vec.rs, which claims this will be optimized to a memcpy
|
// Copied from vec.rs, which claims this will be optimized to a memcpy
|
||||||
// if T is Copy
|
// if T is Copy
|
||||||
for i in range(0, other.len()) {
|
for i in range(0, other.len()) {
|
||||||
|
@ -192,7 +196,7 @@ impl<T> Extendable<T> for ThinVec<T> {
|
||||||
fn extend<I: Iterator<T>>(&mut self, iter: I) {
|
fn extend<I: Iterator<T>>(&mut self, iter: I) {
|
||||||
let old_cap = self.cap;
|
let old_cap = self.cap;
|
||||||
let (lower, _) = iter.size_hint();
|
let (lower, _) = iter.size_hint();
|
||||||
self.reserve_additional(lower as u32);
|
unsafe { self.reserve_additional(lower as u32); }
|
||||||
for (n, elem) in iter.enumerate() {
|
for (n, elem) in iter.enumerate() {
|
||||||
if n < lower {
|
if n < lower {
|
||||||
unsafe { self.init(old_cap as uint + n, elem) };
|
unsafe { self.init(old_cap as uint + n, elem) };
|
||||||
|
|
Loading…
Reference in New Issue