Add alternate network support to `Blockchain`, `UtxoSet`, `Socket`

Still need to do alternate diffchange rules..
This commit is contained in:
Andrew Poelstra 2014-07-18 14:38:35 -07:00
parent 8f826a959d
commit 51038f5810
8 changed files with 128 additions and 48 deletions

View File

@ -30,7 +30,8 @@ use std::kinds::marker;
use blockdata::block::{Block, BlockHeader};
use blockdata::transaction::Transaction;
use blockdata::constants::{DIFFCHANGE_INTERVAL, DIFFCHANGE_TIMESPAN, max_target};
use blockdata::constants::{DIFFCHANGE_INTERVAL, DIFFCHANGE_TIMESPAN, max_target, genesis_block};
use network::constants::Network;
use network::serialize::{Serializable, SerializeIter};
use util::BitArray;
use util::error::{BitcoinResult, BlockNotFound, DuplicateHash, PrevHashNotFound};
@ -134,6 +135,7 @@ impl Serializable for Rc<BlockchainNode> {
/// The blockchain
pub struct Blockchain {
network: Network,
tree: BlockTree,
best_tip: Rc<BlockchainNode>,
best_hash: Sha256dHash,
@ -143,6 +145,7 @@ pub struct Blockchain {
impl Serializable for Blockchain {
fn serialize(&self) -> Vec<u8> {
let mut ret = vec![];
ret.extend(self.network.serialize().move_iter());
ret.extend(self.tree.serialize().move_iter());
ret.extend(self.best_hash.serialize().move_iter());
ret.extend(self.genesis_hash.serialize().move_iter());
@ -152,7 +155,8 @@ impl Serializable for Blockchain {
fn serialize_iter<'a>(&'a self) -> SerializeIter<'a> {
SerializeIter {
data_iter: None,
sub_iter_iter: box vec![ &self.tree as &Serializable,
sub_iter_iter: box vec![ &self.network as &Serializable,
&self.tree as &Serializable,
&self.best_hash as &Serializable,
&self.genesis_hash as &Serializable ].move_iter(),
sub_iter: None,
@ -161,6 +165,7 @@ impl Serializable for Blockchain {
}
fn deserialize<I: Iterator<u8>>(mut iter: I) -> IoResult<Blockchain> {
let network: Network = try!(prepend_err("network", Serializable::deserialize(iter.by_ref())));
let tree: BlockTree = try!(prepend_err("tree", Serializable::deserialize(iter.by_ref())));
let best_hash: Sha256dHash = try!(prepend_err("best_hash", Serializable::deserialize(iter.by_ref())));
let genesis_hash: Sha256dHash = try!(prepend_err("genesis_hash", Serializable::deserialize(iter.by_ref())));
@ -201,6 +206,7 @@ impl Serializable for Blockchain {
} else {
// Return the chain
Ok(Blockchain {
network: network,
tree: tree,
best_tip: best.clone(),
best_hash: best_hash,
@ -367,7 +373,8 @@ fn satoshi_the_precision(n: &Uint256) -> Uint256 {
impl Blockchain {
/// Constructs a new blockchain
pub fn new(genesis: Block) -> Blockchain {
pub fn new(network: Network) -> Blockchain {
let genesis = genesis_block(network);
let genhash = genesis.header.hash();
let rc_gen = Rc::new(BlockchainNode {
total_work: Zero::zero(),
@ -379,6 +386,7 @@ impl Blockchain {
next: RefCell::new(None)
});
Blockchain {
network: network,
tree: {
let mut pat = PatriciaTree::new();
pat.insert(&genhash.as_uint256(), 256, rc_gen.clone());
@ -479,7 +487,7 @@ impl Blockchain {
target = target.mul_u32(timespan);
target = target / FromPrimitive::from_u64(DIFFCHANGE_TIMESPAN as u64).unwrap();
// Clamp below MAX_TARGET (difficulty 1)
let max = max_target();
let max = max_target(self.network);
if target > max { target = max };
// Compactify (make expressible in the 8+24 nBits float format
satoshi_the_precision(&target)
@ -601,12 +609,14 @@ mod tests {
use blockdata::blockchain::Blockchain;
use blockdata::constants::genesis_block;
use network::constants::Bitcoin;
use network::serialize::Serializable;
#[test]
fn blockchain_serialize_test() {
let empty_chain = Blockchain::new(genesis_block());
assert_eq!(empty_chain.best_tip.hash().serialize(), genesis_block().header.hash().serialize());
let empty_chain = Blockchain::new(Bitcoin);
assert_eq!(empty_chain.best_tip.hash().serialize(),
genesis_block(Bitcoin).header.hash().serialize());
let serial = empty_chain.serialize();
assert_eq!(serial, empty_chain.serialize_iter().collect());
@ -614,7 +624,8 @@ mod tests {
let deserial: IoResult<Blockchain> = Serializable::deserialize(serial.iter().map(|n| *n));
assert!(deserial.is_ok());
let read_chain = deserial.unwrap();
assert_eq!(read_chain.best_tip.hash().serialize(), genesis_block().header.hash().serialize());
assert_eq!(read_chain.best_tip.hash().serialize(),
genesis_block(Bitcoin).header.hash().serialize());
}
}

View File

@ -19,6 +19,8 @@
//! single transaction
//!
use network::constants::{Network, Bitcoin, BitcoinTestnet};
use std::num::from_u64;
use blockdata::opcodes;
@ -35,12 +37,12 @@ pub static DIFFCHANGE_INTERVAL: u32 = 2016;
pub static DIFFCHANGE_TIMESPAN: u32 = 14 * 24 * 3600;
/// In Bitcoind this is insanely described as ~((u256)0 >> 32)
pub fn max_target() -> Uint256 {
pub fn max_target(_: Network) -> Uint256 {
from_u64::<Uint256>(0xFFFF).unwrap() << 208u
}
/// Constructs and returns the coinbase (and only) transaction of the genesis block
pub fn genesis_tx() -> Transaction {
/// Constructs and returns the coinbase (and only) transaction of the Bitcoin genesis block
fn bitcoin_genesis_tx() -> Transaction {
// Base
let mut ret = Transaction {
version: 1,
@ -75,34 +77,51 @@ pub fn genesis_tx() -> Transaction {
}
/// Constructs and returns the genesis block
pub fn genesis_block() -> Block {
let txdata = vec![genesis_tx()];
let header = BlockHeader {
version: 1,
prev_blockhash: zero_hash(),
merkle_root: merkle_root(txdata.as_slice()),
time: 1231006505,
bits: 0x1d00ffff,
nonce: 2083236893
};
Block {
header: header,
txdata: txdata
pub fn genesis_block(network: Network) -> Block {
match network {
Bitcoin => {
let txdata = vec![bitcoin_genesis_tx()];
Block {
header: BlockHeader {
version: 1,
prev_blockhash: zero_hash(),
merkle_root: merkle_root(txdata.as_slice()),
time: 1231006505,
bits: 0x1d00ffff,
nonce: 2083236893
},
txdata: txdata
}
}
BitcoinTestnet => {
let txdata = vec![bitcoin_genesis_tx()];
Block {
header: BlockHeader {
version: 1,
prev_blockhash: zero_hash(),
merkle_root: merkle_root(txdata.as_slice()),
time: 1296688602,
bits: 0x1d00ffff,
nonce: 414098458
},
txdata: txdata
}
}
}
}
#[cfg(test)]
mod test {
use network::serialize::Serializable;
use blockdata::constants::{genesis_block, genesis_tx};
use network::constants::{Bitcoin, BitcoinTestnet};
use blockdata::constants::{genesis_block, bitcoin_genesis_tx};
use blockdata::constants::{MAX_SEQUENCE, COIN_VALUE};
use util::misc::hex_bytes;
use util::hash::zero_hash;
#[test]
fn genesis_first_transaction() {
let gen = genesis_tx();
fn bitcoin_genesis_first_transaction() {
let gen = bitcoin_genesis_tx();
assert_eq!(gen.version, 1);
assert_eq!(gen.input.len(), 1);
@ -123,18 +142,32 @@ mod test {
}
#[test]
fn genesis_full_block() {
let gen = genesis_block();
fn bitcoin_genesis_full_block() {
let gen = genesis_block(Bitcoin);
assert_eq!(gen.header.version, 1);
assert_eq!(gen.header.prev_blockhash.as_slice(), zero_hash().as_slice());
assert_eq!(gen.header.merkle_root.serialize().iter().rev().map(|n| *n).collect::<Vec<u8>>(),
hex_bytes("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b").unwrap());
assert_eq!(gen.header.merkle_root.le_hex_string(),
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b".to_string());
assert_eq!(gen.header.time, 1231006505);
assert_eq!(gen.header.bits, 0x1d00ffff);
assert_eq!(gen.header.nonce, 2083236893);
assert_eq!(gen.header.hash().serialize().iter().rev().map(|n| *n).collect::<Vec<u8>>(),
hex_bytes("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f").unwrap());
assert_eq!(gen.header.hash().le_hex_string(),
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f".to_string());
}
#[test]
fn testnet_genesis_full_block() {
let gen = genesis_block(BitcoinTestnet);
assert_eq!(gen.header.version, 1);
assert_eq!(gen.header.prev_blockhash.as_slice(), zero_hash().as_slice());
assert_eq!(gen.header.merkle_root.le_hex_string(),
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b".to_string());
assert_eq!(gen.header.time, 1296688602);
assert_eq!(gen.header.bits, 0x1d00ffff);
assert_eq!(gen.header.nonce, 414098458);
assert_eq!(gen.header.hash().le_hex_string(),
"000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943".to_string());
}
}

View File

@ -22,7 +22,9 @@ use std::io::IoResult;
use std::mem;
use blockdata::transaction::{Transaction, TxOut};
use blockdata::constants::genesis_block;
use blockdata::block::Block;
use network::constants::Network;
use network::serialize::{Serializable, SerializeIter};
use util::hash::Sha256dHash;
use util::uint::Uint128;
@ -51,14 +53,14 @@ impl_serializable!(UtxoSet, last_hash, n_utxos, spent_txos, spent_idx, tree)
impl UtxoSet {
/// Constructs a new UTXO set
pub fn new(genesis: Block, rewind_limit: uint) -> UtxoSet {
pub fn new(network: Network, rewind_limit: uint) -> UtxoSet {
// There is in fact a transaction in the genesis block, but the Bitcoin
// reference client does not add its sole output to the UTXO set. We
// must follow suit, otherwise we will accept a transaction spending it
// while the reference client won't, causing us to fork off the network.
UtxoSet {
tree: PatriciaTree::new(),
last_hash: genesis.header.hash(),
last_hash: genesis_block(network).header.hash(),
spent_txos: Vec::from_elem(rewind_limit, vec![]),
spent_idx: 0,
n_utxos: 0
@ -270,14 +272,14 @@ mod tests {
use std::io::IoResult;
use serialize::hex::FromHex;
use blockdata::constants::genesis_block;
use blockdata::block::Block;
use blockdata::utxoset::UtxoSet;
use network::constants::Bitcoin;
use network::serialize::Serializable;
#[test]
fn utxoset_serialize_test() {
let mut empty_set = UtxoSet::new(genesis_block(), 100);
let mut empty_set = UtxoSet::new(Bitcoin, 100);
let new_block: Block = Serializable::deserialize("010000004ddccd549d28f385ab457e98d1b11ce80bfea2c5ab93015ade4973e400000000bf4473e53794beae34e64fccc471dace6ae544180816f89591894e0f417a914cd74d6e49ffff001d323b3a7b0201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0804ffff001d026e04ffffffff0100f2052a0100000043410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac00000000010000000321f75f3139a013f50f315b23b0c9a2b6eac31e2bec98e5891c924664889942260000000049483045022100cb2c6b346a978ab8c61b18b5e9397755cbd17d6eb2fe0083ef32e067fa6c785a02206ce44e613f31d9a6b0517e46f3db1576e9812cc98d159bfdaf759a5014081b5c01ffffffff79cda0945903627c3da1f85fc95d0b8ee3e76ae0cfdc9a65d09744b1f8fc85430000000049483045022047957cdd957cfd0becd642f6b84d82f49b6cb4c51a91f49246908af7c3cfdf4a022100e96b46621f1bffcf5ea5982f88cef651e9354f5791602369bf5a82a6cd61a62501fffffffffe09f5fe3ffbf5ee97a54eb5e5069e9da6b4856ee86fc52938c2f979b0f38e82000000004847304402204165be9a4cbab8049e1af9723b96199bfd3e85f44c6b4c0177e3962686b26073022028f638da23fc003760861ad481ead4099312c60030d4cb57820ce4d33812a5ce01ffffffff01009d966b01000000434104ea1feff861b51fe3f5f8a3b12d0f4712db80e919548a80839fc47c6a21e66d957e9c5d8cd108c7a2d2324bad71f9904ac0ae7336507d785b17a2c115e427a32fac00000000".from_hex().unwrap().iter().map(|n| *n)).unwrap();

View File

@ -62,4 +62,3 @@ pub mod network;
pub mod blockdata;
pub mod util;

View File

@ -18,10 +18,47 @@
//! protocol, such as protocol versioning and magic header bytes.
//!
pub static MAGIC_BITCOIN: u32 = 0xD9B4BEF9;
pub static MAGIC_TESTNET: u32 = 0x0709110B;
use std::io::{IoResult, InvalidInput, standard_error};
use network::serialize::Serializable;
use util::misc::prepend_err;
/// The cryptocurrency to operate on
#[deriving(PartialEq, Eq, Clone, Show)]
pub enum Network {
/// Classic Bitcoin
Bitcoin,
/// Bitcoin's testnet
BitcoinTestnet,
}
pub static PROTOCOL_VERSION: u32 = 70001;
pub static SERVICES: u64 = 0;
pub static USER_AGENT: &'static str = "bitcoin-rust v0.1";
/// Return the network magic bytes, which should be encoded little-endian
/// at the start of every message
pub fn magic(network: Network) -> u32 {
match network {
Bitcoin => 0xD9B4BEF9,
BitcoinTestnet => 0x0709110B
// Note: any new entries here must be added to `deserialize` below
}
}
// This affects the representation of the `Network` in text files
impl Serializable for Network {
fn serialize(&self) -> Vec<u8> {
magic(*self).serialize()
}
fn deserialize<I: Iterator<u8>>(iter: I) -> IoResult<Network> {
let magic: u32 = try!(prepend_err("magic", Serializable::deserialize(iter)));
match magic {
0xD9B4BEF9 => Ok(Bitcoin),
0x0709110B => Ok(BitcoinTestnet),
_ => Err(standard_error(InvalidInput))
}
}
}

View File

@ -21,6 +21,7 @@
use std::io::{IoResult, standard_error, ConnectionFailed};
use std::io::timer;
use network::constants::Network;
use network::message::{NetworkMessage, Verack};
use network::socket::Socket;
@ -30,12 +31,12 @@ pub trait Listener {
fn peer<'a>(&'a self) -> &'a str;
/// Return the port we have connected to the peer on
fn port(&self) -> u16;
/// Return the network magic
fn magic(&self) -> u32;
/// Return the network this `Listener` is operating on
fn network(&self) -> Network;
/// Main listen loop
fn start(&self) -> IoResult<(Receiver<NetworkMessage>, Socket)> {
// Open socket
let mut ret_sock = Socket::new(self.magic());
let mut ret_sock = Socket::new(self.network());
match ret_sock.connect(self.peer(), self.port()) {
Ok(_) => {},
Err(_) => return Err(standard_error(ConnectionFailed))

View File

@ -76,9 +76,6 @@ impl<'a> Iterator<u8> for SerializeIter<'a> {
}
}
/// A string which must be encoded as 12 bytes, used in network message headers
#[deriving(PartialEq, Clone, Show)]
/// Data which must be preceded by a 4-byte checksum
pub struct CheckedData(pub Vec<u8>);

View File

@ -61,14 +61,14 @@ pub struct Socket {
impl Socket {
// TODO: we fix services to 0
/// Construct a new socket
pub fn new(magic: u32) -> Socket {
pub fn new(network: constants::Network) -> Socket {
let mut rng = task_rng();
Socket {
stream: None,
services: 0,
version_nonce: rng.gen(),
user_agent: String::from_str(constants::USER_AGENT),
magic: magic
magic: constants::magic(network)
}
}