From 51038f5810f65d56da96771aee52f86200be65fa Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 18 Jul 2014 14:38:35 -0700 Subject: [PATCH] Add alternate network support to `Blockchain`, `UtxoSet`, `Socket` Still need to do alternate diffchange rules.. --- src/blockdata/blockchain.rs | 25 ++++++++--- src/blockdata/constants.rs | 85 +++++++++++++++++++++++++------------ src/blockdata/utxoset.rs | 10 +++-- src/lib.rs | 1 - src/network/constants.rs | 41 +++++++++++++++++- src/network/listener.rs | 7 +-- src/network/serialize.rs | 3 -- src/network/socket.rs | 4 +- 8 files changed, 128 insertions(+), 48 deletions(-) diff --git a/src/blockdata/blockchain.rs b/src/blockdata/blockchain.rs index 3fffbf43..9848b3bc 100644 --- a/src/blockdata/blockchain.rs +++ b/src/blockdata/blockchain.rs @@ -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 { /// The blockchain pub struct Blockchain { + network: Network, tree: BlockTree, best_tip: Rc, best_hash: Sha256dHash, @@ -143,6 +145,7 @@ pub struct Blockchain { impl Serializable for Blockchain { fn serialize(&self) -> Vec { 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>(mut iter: I) -> IoResult { + 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 = 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()); } } diff --git a/src/blockdata/constants.rs b/src/blockdata/constants.rs index 6c679da2..39377519 100644 --- a/src/blockdata/constants.rs +++ b/src/blockdata/constants.rs @@ -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::(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::>(), - 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::>(), - 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()); } } diff --git a/src/blockdata/utxoset.rs b/src/blockdata/utxoset.rs index 0ae050d4..866c0e7d 100644 --- a/src/blockdata/utxoset.rs +++ b/src/blockdata/utxoset.rs @@ -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(); diff --git a/src/lib.rs b/src/lib.rs index 40ef6f9a..a1b53862 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,4 +62,3 @@ pub mod network; pub mod blockdata; pub mod util; - diff --git a/src/network/constants.rs b/src/network/constants.rs index ea6e7d45..c93e441e 100644 --- a/src/network/constants.rs +++ b/src/network/constants.rs @@ -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 { + magic(*self).serialize() + } + + fn deserialize>(iter: I) -> IoResult { + let magic: u32 = try!(prepend_err("magic", Serializable::deserialize(iter))); + match magic { + 0xD9B4BEF9 => Ok(Bitcoin), + 0x0709110B => Ok(BitcoinTestnet), + _ => Err(standard_error(InvalidInput)) + } + } +} + diff --git a/src/network/listener.rs b/src/network/listener.rs index d5747e75..910b01d1 100644 --- a/src/network/listener.rs +++ b/src/network/listener.rs @@ -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, 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)) diff --git a/src/network/serialize.rs b/src/network/serialize.rs index 4766001b..09b0b2fa 100644 --- a/src/network/serialize.rs +++ b/src/network/serialize.rs @@ -76,9 +76,6 @@ impl<'a> Iterator 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); diff --git a/src/network/socket.rs b/src/network/socket.rs index 20a0e728..be28f0cf 100644 --- a/src/network/socket.rs +++ b/src/network/socket.rs @@ -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) } }