Checkpoint commit -- prefix-filtered address indexing works

This commit is contained in:
Andrew Poelstra 2014-09-01 21:37:00 -05:00
parent 4629472d69
commit 6250f4fd9c
9 changed files with 222 additions and 14 deletions

View File

@ -24,6 +24,7 @@
//! This module provides the structures and functions needed to support scripts.
//!
use std::hash;
use std::char::from_digit;
use std::default::Default;
use serialize::json;
@ -51,6 +52,14 @@ use util::thinvec::ThinVec;
/// A Bitcoin script
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
/// much as it could be; patches welcome if more detailed errors
/// would help you.
@ -1165,6 +1174,11 @@ impl AbstractStack {
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
/// with the `stack_opcode!` macro
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
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
/// encoded as little-endian signed-magnitude numbers, but there are
/// dedicated opcodes to push some small integers.
@ -1654,6 +1674,24 @@ impl Script {
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
pub fn trace<'a>(&'a self, stack: &mut Vec<MaybeOwned<'a>>,
input_context: Option<(&Transaction, uint)>)

View File

@ -31,6 +31,8 @@ use blockdata::script::{mod, Script, ScriptError, ScriptTrace, read_scriptbool};
use blockdata::utxoset::UtxoSet;
use network::encodable::ConsensusEncodable;
use network::serialize::BitcoinHash;
use network::constants::Network;
use wallet::address::Address;
/// A transaction input, which defines old coins to be consumed
#[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
#[deriving(Clone, PartialEq, Eq, Show)]
pub struct Transaction {

View File

@ -20,9 +20,11 @@
use std::cmp;
use std::collections::HashMap;
use std::collections::hashmap::Entries;
use std::mem;
use std::os::num_cpus;
use std::sync::Future;
use std::num::Zero;
use blockdata::transaction::{Transaction, TxOut};
use blockdata::transaction::{TransactionError, InputNotFound};
@ -61,6 +63,39 @@ pub enum UtxoSetError {
/// Vector of outputs; None indicates a nonexistent or already spent output
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
pub struct UtxoSet {
table: HashMap<Uint128, UtxoNode, DumbHasher>,
@ -366,6 +401,26 @@ impl UtxoSet {
pub fn n_pruned(&self) -> 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)]

View File

@ -103,7 +103,13 @@ impl<T> ThinVec<T> {
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]
pub fn slice<'a>(&'a self, s: uint, e: uint) -> &'a [T] {
self.as_slice().slice(s, e)

View File

@ -29,6 +29,7 @@ macro_rules! construct_uint(
/// Little-endian large integer type
#[repr(C)]
pub struct $name(pub [u64, ..$n_words]);
impl_array_newtype!($name, u64, $n_words)
impl $name {
/// 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 {
fn cmp(&self, other: &$name) -> Ordering {
let &$name(ref me) = self;

View File

@ -36,7 +36,24 @@ pub struct 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
#[inline]
pub fn from_key(network: Network, pk: &PublicKey) -> Address {
let mut sha = Sha256::new();
let mut out = [0, ..32];
@ -49,6 +66,7 @@ impl Address {
}
/// Generates a script pubkey spending to this address
#[inline]
pub fn script_pubkey(&self) -> Script {
let mut script = Script::new();
script.push_opcode(all::OP_DUP);

View File

@ -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
}
}

View File

@ -17,6 +17,7 @@
//! Wallet, keys and addresses
pub mod address;
pub mod address_index;
pub mod bip32;
pub mod wallet;

View File

@ -18,7 +18,10 @@
use std::collections::HashMap;
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 wallet::bip32::{mod, ChildNumber, ExtendedPrivKey, Normal, Hardened};
@ -56,7 +59,8 @@ pub struct 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> {
let mut accounts = HashMap::new();
accounts.insert(String::new(), Default::default());
@ -82,6 +86,30 @@ impl Wallet {
});
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
}
}
}