Checkpoint commit -- prefix-filtered address indexing works
This commit is contained in:
parent
4629472d69
commit
6250f4fd9c
|
@ -24,6 +24,7 @@
|
||||||
//! This module provides the structures and functions needed to support scripts.
|
//! This module provides the structures and functions needed to support scripts.
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
use std::hash;
|
||||||
use std::char::from_digit;
|
use std::char::from_digit;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use serialize::json;
|
use serialize::json;
|
||||||
|
@ -51,6 +52,14 @@ use util::thinvec::ThinVec;
|
||||||
/// A Bitcoin script
|
/// A Bitcoin script
|
||||||
pub struct Script(ThinVec<u8>);
|
pub struct Script(ThinVec<u8>);
|
||||||
|
|
||||||
|
impl<S: hash::Writer> hash::Hash<S> for Script {
|
||||||
|
#[inline]
|
||||||
|
fn hash(&self, state: &mut S) {
|
||||||
|
let &Script(ref raw) = self;
|
||||||
|
raw.as_slice().hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Ways that a script might fail. Not everything is split up as
|
/// Ways that a script might fail. Not everything is split up as
|
||||||
/// much as it could be; patches welcome if more detailed errors
|
/// much as it could be; patches welcome if more detailed errors
|
||||||
/// would help you.
|
/// would help you.
|
||||||
|
@ -1165,6 +1174,11 @@ impl AbstractStack {
|
||||||
self.stack.as_slice()
|
self.stack.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Immutable view of the current stack as a slice of bytes
|
||||||
|
pub fn slice_to<'a>(&'a self, n: uint) -> &'a [uint] {
|
||||||
|
self.stack.slice_to(n)
|
||||||
|
}
|
||||||
|
|
||||||
/// Mutable view of the current stack as a slice (to be compatible
|
/// Mutable view of the current stack as a slice (to be compatible
|
||||||
/// with the `stack_opcode!` macro
|
/// with the `stack_opcode!` macro
|
||||||
fn as_mut_slice<'a>(&'a mut self) -> &'a mut [uint] {
|
fn as_mut_slice<'a>(&'a mut self) -> &'a mut [uint] {
|
||||||
|
@ -1588,6 +1602,12 @@ impl Script {
|
||||||
/// Creates a new script from an existing vector
|
/// Creates a new script from an existing vector
|
||||||
pub fn from_vec(v: Vec<u8>) -> Script { Script(ThinVec::from_vec(v)) }
|
pub fn from_vec(v: Vec<u8>) -> Script { Script(ThinVec::from_vec(v)) }
|
||||||
|
|
||||||
|
/// The length in bytes of the script
|
||||||
|
pub fn len(&self) -> uint {
|
||||||
|
let &Script(ref raw) = self;
|
||||||
|
raw.len()
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds instructions to push an integer onto the stack. Integers are
|
/// Adds instructions to push an integer onto the stack. Integers are
|
||||||
/// encoded as little-endian signed-magnitude numbers, but there are
|
/// encoded as little-endian signed-magnitude numbers, but there are
|
||||||
/// dedicated opcodes to push some small integers.
|
/// dedicated opcodes to push some small integers.
|
||||||
|
@ -1654,6 +1674,24 @@ impl Script {
|
||||||
raw.as_slice()
|
raw.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a view into the script as a slice
|
||||||
|
pub fn slice_to(&self, n: uint) -> &[u8] {
|
||||||
|
let &Script(ref raw) = self;
|
||||||
|
raw.slice_to(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a view into the script as a slice
|
||||||
|
pub fn slice_from(&self, n: uint) -> &[u8] {
|
||||||
|
let &Script(ref raw) = self;
|
||||||
|
raw.slice_from(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a view into the script as a slice
|
||||||
|
pub fn slice(&self, s: uint, e: uint) -> &[u8] {
|
||||||
|
let &Script(ref raw) = self;
|
||||||
|
raw.slice(s, e)
|
||||||
|
}
|
||||||
|
|
||||||
/// Trace a script
|
/// Trace a script
|
||||||
pub fn trace<'a>(&'a self, stack: &mut Vec<MaybeOwned<'a>>,
|
pub fn trace<'a>(&'a self, stack: &mut Vec<MaybeOwned<'a>>,
|
||||||
input_context: Option<(&Transaction, uint)>)
|
input_context: Option<(&Transaction, uint)>)
|
||||||
|
|
|
@ -31,6 +31,8 @@ use blockdata::script::{mod, Script, ScriptError, ScriptTrace, read_scriptbool};
|
||||||
use blockdata::utxoset::UtxoSet;
|
use blockdata::utxoset::UtxoSet;
|
||||||
use network::encodable::ConsensusEncodable;
|
use network::encodable::ConsensusEncodable;
|
||||||
use network::serialize::BitcoinHash;
|
use network::serialize::BitcoinHash;
|
||||||
|
use network::constants::Network;
|
||||||
|
use wallet::address::Address;
|
||||||
|
|
||||||
/// A transaction input, which defines old coins to be consumed
|
/// A transaction input, which defines old coins to be consumed
|
||||||
#[deriving(Clone, PartialEq, Eq, Show)]
|
#[deriving(Clone, PartialEq, Eq, Show)]
|
||||||
|
@ -65,6 +67,26 @@ impl Default for TxOut {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A classification for script pubkeys
|
||||||
|
pub enum ScriptPubkeyTemplate {
|
||||||
|
/// A pay-to-address output
|
||||||
|
PayToPubkeyHash(Address),
|
||||||
|
/// Another kind of output
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxOut {
|
||||||
|
pub fn classify(&self, network: Network) -> ScriptPubkeyTemplate {
|
||||||
|
if self.script_pubkey.len() == 25 &&
|
||||||
|
self.script_pubkey.slice_to(3) == &[0x76, 0xa9, 0x14] &&
|
||||||
|
self.script_pubkey.slice_from(23) == &[0x88, 0xac] {
|
||||||
|
PayToPubkeyHash(Address::from_slice(network, self.script_pubkey.slice(3, 23)))
|
||||||
|
} else {
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A Bitcoin transaction, which describes an authenticated movement of coins
|
/// A Bitcoin transaction, which describes an authenticated movement of coins
|
||||||
#[deriving(Clone, PartialEq, Eq, Show)]
|
#[deriving(Clone, PartialEq, Eq, Show)]
|
||||||
pub struct Transaction {
|
pub struct Transaction {
|
||||||
|
|
|
@ -20,9 +20,11 @@
|
||||||
|
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::hashmap::Entries;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::os::num_cpus;
|
use std::os::num_cpus;
|
||||||
use std::sync::Future;
|
use std::sync::Future;
|
||||||
|
use std::num::Zero;
|
||||||
|
|
||||||
use blockdata::transaction::{Transaction, TxOut};
|
use blockdata::transaction::{Transaction, TxOut};
|
||||||
use blockdata::transaction::{TransactionError, InputNotFound};
|
use blockdata::transaction::{TransactionError, InputNotFound};
|
||||||
|
@ -61,6 +63,39 @@ pub enum UtxoSetError {
|
||||||
/// Vector of outputs; None indicates a nonexistent or already spent output
|
/// Vector of outputs; None indicates a nonexistent or already spent output
|
||||||
type UtxoNode = ThinVec<Option<TxOut>>;
|
type UtxoNode = ThinVec<Option<TxOut>>;
|
||||||
|
|
||||||
|
/// An iterator over UTXOs
|
||||||
|
pub struct UtxoIterator<'a> {
|
||||||
|
tx_iter: Entries<'a, Uint128, UtxoNode>,
|
||||||
|
current_key: Uint128,
|
||||||
|
current: Option<&'a UtxoNode>,
|
||||||
|
tx_index: uint
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator<(Uint128, uint, &'a TxOut)> for UtxoIterator<'a> {
|
||||||
|
fn next(&mut self) -> Option<(Uint128, uint, &'a TxOut)> {
|
||||||
|
while self.current.is_some() {
|
||||||
|
let current = self.current.unwrap();
|
||||||
|
while self.tx_index < current.len() {
|
||||||
|
self.tx_index += 1;
|
||||||
|
if unsafe { current.get(self.tx_index - 1) }.is_some() {
|
||||||
|
return Some((self.current_key,
|
||||||
|
self.tx_index,
|
||||||
|
unsafe { current.get(self.tx_index - 1) }.as_ref().unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match self.tx_iter.next() {
|
||||||
|
Some((&x, y)) => {
|
||||||
|
self.tx_index = 0;
|
||||||
|
self.current_key = x;
|
||||||
|
self.current = Some(y);
|
||||||
|
}
|
||||||
|
None => { self.current = None; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The UTXO set
|
/// The UTXO set
|
||||||
pub struct UtxoSet {
|
pub struct UtxoSet {
|
||||||
table: HashMap<Uint128, UtxoNode, DumbHasher>,
|
table: HashMap<Uint128, UtxoNode, DumbHasher>,
|
||||||
|
@ -366,6 +401,26 @@ impl UtxoSet {
|
||||||
pub fn n_pruned(&self) -> uint {
|
pub fn n_pruned(&self) -> uint {
|
||||||
self.n_pruned as uint
|
self.n_pruned as uint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an iterator over all UTXOs
|
||||||
|
pub fn iter<'a>(&'a self) -> UtxoIterator<'a> {
|
||||||
|
let mut iter = self.table.iter();
|
||||||
|
let first = iter.next();
|
||||||
|
match first {
|
||||||
|
Some((&key, val)) => UtxoIterator {
|
||||||
|
current_key: key,
|
||||||
|
current: Some(val),
|
||||||
|
tx_iter: iter,
|
||||||
|
tx_index: 0
|
||||||
|
},
|
||||||
|
None => UtxoIterator {
|
||||||
|
current_key: Zero::zero(),
|
||||||
|
current: None,
|
||||||
|
tx_iter: iter,
|
||||||
|
tx_index: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -103,7 +103,13 @@ impl<T> ThinVec<T> {
|
||||||
self.as_slice().slice_from(index)
|
self.as_slice().slice_from(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a slice starting from `s` ending in `e`
|
/// Returns a slice ending just before `index`
|
||||||
|
#[inline]
|
||||||
|
pub fn slice_to<'a>(&'a self, index: uint) -> &'a [T] {
|
||||||
|
self.as_slice().slice_to(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a slice starting from `s` ending just before `e`
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn slice<'a>(&'a self, s: uint, e: uint) -> &'a [T] {
|
pub fn slice<'a>(&'a self, s: uint, e: uint) -> &'a [T] {
|
||||||
self.as_slice().slice(s, e)
|
self.as_slice().slice(s, e)
|
||||||
|
|
|
@ -29,6 +29,7 @@ macro_rules! construct_uint(
|
||||||
/// Little-endian large integer type
|
/// Little-endian large integer type
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct $name(pub [u64, ..$n_words]);
|
pub struct $name(pub [u64, ..$n_words]);
|
||||||
|
impl_array_newtype!($name, u64, $n_words)
|
||||||
|
|
||||||
impl $name {
|
impl $name {
|
||||||
/// Conversion to u32
|
/// Conversion to u32
|
||||||
|
@ -289,18 +290,6 @@ macro_rules! construct_uint(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for $name {
|
|
||||||
fn eq(&self, other: &$name) -> bool {
|
|
||||||
let &$name(ref arr1) = self;
|
|
||||||
let &$name(ref arr2) = other;
|
|
||||||
for i in range(0, $n_words) {
|
|
||||||
if arr1[i] != arr2[i] { return false; }
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Eq for $name {}
|
|
||||||
|
|
||||||
impl Ord for $name {
|
impl Ord for $name {
|
||||||
fn cmp(&self, other: &$name) -> Ordering {
|
fn cmp(&self, other: &$name) -> Ordering {
|
||||||
let &$name(ref me) = self;
|
let &$name(ref me) = self;
|
||||||
|
|
|
@ -36,7 +36,24 @@ pub struct Address {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Address {
|
impl Address {
|
||||||
|
/// Creates an address the raw 20 bytes of a hash
|
||||||
|
#[inline]
|
||||||
|
pub fn from_slice(network: Network, data: &[u8]) -> Address {
|
||||||
|
Address {
|
||||||
|
network: network,
|
||||||
|
hash: Ripemd160Hash::from_slice(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a byteslice view of the `Address` --- note that no network information
|
||||||
|
/// is contained in this.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_slice<'a>(&'a self) -> &'a [u8] {
|
||||||
|
self.hash.as_slice()
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates an address from a public key
|
/// Creates an address from a public key
|
||||||
|
#[inline]
|
||||||
pub fn from_key(network: Network, pk: &PublicKey) -> Address {
|
pub fn from_key(network: Network, pk: &PublicKey) -> Address {
|
||||||
let mut sha = Sha256::new();
|
let mut sha = Sha256::new();
|
||||||
let mut out = [0, ..32];
|
let mut out = [0, ..32];
|
||||||
|
@ -49,6 +66,7 @@ impl Address {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a script pubkey spending to this address
|
/// Generates a script pubkey spending to this address
|
||||||
|
#[inline]
|
||||||
pub fn script_pubkey(&self) -> Script {
|
pub fn script_pubkey(&self) -> Script {
|
||||||
let mut script = Script::new();
|
let mut script = Script::new();
|
||||||
script.push_opcode(all::OP_DUP);
|
script.push_opcode(all::OP_DUP);
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Rust Bitcoin Library
|
||||||
|
// Written in 2014 by
|
||||||
|
// Andrew Poelstra <apoelstra@wpsoftware.net>
|
||||||
|
// To the extent possible under law, the author(s) have dedicated all
|
||||||
|
// copyright and related and neighboring rights to this software to
|
||||||
|
// the public domain worldwide. This software is distributed without
|
||||||
|
// any warranty.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
// along with this software.
|
||||||
|
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
//! # Address Index
|
||||||
|
//!
|
||||||
|
//! Maintains an index from addresses to unspent outputs. It reduces size by
|
||||||
|
//! checking that the first byte of HMAC(wallet key, address outscript) is
|
||||||
|
//! zero, so that the index will be 1/256th the size of the utxoset in RAM.
|
||||||
|
//!
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use blockdata::utxoset::UtxoSet;
|
||||||
|
use blockdata::script::Script;
|
||||||
|
use wallet::wallet::Wallet;
|
||||||
|
use util::uint::Uint128;
|
||||||
|
|
||||||
|
/// An address index
|
||||||
|
#[deriving(Clone, PartialEq, Eq, Show)]
|
||||||
|
pub struct AddressIndex {
|
||||||
|
index: HashMap<Script, (Uint128, uint)>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddressIndex {
|
||||||
|
/// Creates a new address index from a wallet (which provides an authenticated
|
||||||
|
/// hash function for prefix filtering) and UTXO set (which is what gets filtered).
|
||||||
|
pub fn new(utxo_set: &UtxoSet, wallet: &Wallet) -> AddressIndex {
|
||||||
|
let mut ret = AddressIndex {
|
||||||
|
index: HashMap::with_capacity(utxo_set.n_utxos() / 256)
|
||||||
|
};
|
||||||
|
for (key, idx, txo) in utxo_set.iter() {
|
||||||
|
if wallet.might_be_mine(txo) {
|
||||||
|
ret.index.insert(txo.script_pubkey.clone(), (key, idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
//! Wallet, keys and addresses
|
//! Wallet, keys and addresses
|
||||||
|
|
||||||
pub mod address;
|
pub mod address;
|
||||||
|
pub mod address_index;
|
||||||
pub mod bip32;
|
pub mod bip32;
|
||||||
pub mod wallet;
|
pub mod wallet;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,10 @@
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
use std::io::extensions::u64_from_be_bytes;
|
||||||
|
use collections::hash::sip::hash_with_keys;
|
||||||
|
|
||||||
|
use blockdata::transaction::{PayToPubkeyHash, TxOut};
|
||||||
use network::constants::Network;
|
use network::constants::Network;
|
||||||
use wallet::bip32::{mod, ChildNumber, ExtendedPrivKey, Normal, Hardened};
|
use wallet::bip32::{mod, ChildNumber, ExtendedPrivKey, Normal, Hardened};
|
||||||
|
|
||||||
|
@ -56,7 +59,8 @@ pub struct Wallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Wallet {
|
impl Wallet {
|
||||||
/// Creates a new wallet from a seed
|
/// Creates a new wallet from a BIP32 seed
|
||||||
|
#[inline]
|
||||||
pub fn from_seed(network: Network, seed: &[u8]) -> Result<Wallet, bip32::Error> {
|
pub fn from_seed(network: Network, seed: &[u8]) -> Result<Wallet, bip32::Error> {
|
||||||
let mut accounts = HashMap::new();
|
let mut accounts = HashMap::new();
|
||||||
accounts.insert(String::new(), Default::default());
|
accounts.insert(String::new(), Default::default());
|
||||||
|
@ -82,6 +86,30 @@ impl Wallet {
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the network of the wallet
|
||||||
|
#[inline]
|
||||||
|
pub fn network(&self) -> Network {
|
||||||
|
self.master.network
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a key suitable for keying hash functions for DoS protection
|
||||||
|
#[inline]
|
||||||
|
pub fn siphash_key(&self) -> (u64, u64) {
|
||||||
|
let ck_slice = self.master.chain_code.as_slice();
|
||||||
|
(u64_from_be_bytes(ck_slice, 0, 8),
|
||||||
|
u64_from_be_bytes(ck_slice, 8, 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A filter used for creating a small address index
|
||||||
|
#[inline]
|
||||||
|
pub fn might_be_mine(&self, out: &TxOut) -> bool {
|
||||||
|
let (k1, k2) = self.siphash_key();
|
||||||
|
match out.classify(self.network()) {
|
||||||
|
PayToPubkeyHash(addr) => hash_with_keys(k1, k2, &addr.as_slice()) & 0xFF == 0,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue