From dde76d520c508dfd0e2982cdf7a3c8dd072f33bf Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Mon, 18 Feb 2019 21:52:48 -0500 Subject: [PATCH] Separate out code and tests for calculating a burn distribution -- a Vec of BurnSamplePoints. This allows us to take a block's worth of block commits and user burns, and figure out the total burns behind each candidate block. This code also takes the integer range [0, 2**256 - 1) and partitions it into disjoint ranges representing sets of possible sortition hashes whose sizes are proportional to how much burn support there is behind each block. --- src/chainstate/burn/distribution.rs | 827 ++++++++++++++++++++++++++++ 1 file changed, 827 insertions(+) create mode 100644 src/chainstate/burn/distribution.rs diff --git a/src/chainstate/burn/distribution.rs b/src/chainstate/burn/distribution.rs new file mode 100644 index 000000000..43dfdd2f1 --- /dev/null +++ b/src/chainstate/burn/distribution.rs @@ -0,0 +1,827 @@ +/* + copyright: (c) 2013-2018 by Blockstack PBC, a public benefit corporation. + + This file is part of Blockstack. + + Blockstack is free software. You may redistribute or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License or + (at your option) any later version. + + Blockstack is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY, including without the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Blockstack. If not, see . +*/ + +use std::collections::BTreeMap; + +use chainstate::burn::operations::BlockstackOperationType; +use chainstate::burn::operations::leader_key_register::LeaderKeyRegisterOp; +use chainstate::burn::operations::leader_block_commit::LeaderBlockCommitOp; +use chainstate::burn::operations::user_burn_support::UserBurnSupportOp; + +use burnchains::Address; +use burnchains::PublicKey; +use burnchains::Burnchain; + +use util::hash::Hash160; +use util::uint::Uint256; +use util::uint::Uint512; +use util::uint::BitArray; +use util::vrf::ECVRF_public_key_to_hex; + +use util::log; + +#[derive(Debug, Clone, PartialEq)] +pub struct BurnSamplePoint +where + A: Address, + K: PublicKey +{ + pub burns: u128, + pub range_start: Uint256, + pub range_end: Uint256, + pub candidate: LeaderBlockCommitOp, + pub key: LeaderKeyRegisterOp, + pub user_burns: Vec> +} + +impl BurnSamplePoint +where + A: Address, + K: PublicKey +{ + + /// Make a burn distribution -- a list of (burn total, block candidate) pairs -- from a block's + /// block commits, leader keys, and user support burns. + /// + /// All operations need to be from the same block height, or this method panics. + /// + /// Returns the distribution, which consumes the given lists of operations. + pub fn make_distribution(block_candidates: Vec>, consumed_leader_keys: Vec>, user_burns: Vec>) -> Vec> { + // trivial case + if block_candidates.len() == 0 { + return vec![]; + } + + BurnSamplePoint::ops_sanity_checks(&block_candidates, &consumed_leader_keys, &user_burns); + + // map each leader key's position in the blockchain to its index in consumed_leader_keys. + // The DB will ensure that there are no duplicates. + let mut key_index : BTreeMap<(u64, u32), usize> = BTreeMap::new(); + for i in 0..consumed_leader_keys.len() { + let key_loc = (consumed_leader_keys[i].block_number, consumed_leader_keys[i].vtxindex); + assert!(key_index.get(&key_loc).is_none()); + + key_index.insert(key_loc, i); + } + + // consume block candidates and leader keys to produce the list of burn sample points + let mut burn_sample : Vec> = block_candidates + .into_iter() + .map(|bc| { + let key_idx_opt = key_index.get(&(bc.block_number - (bc.key_block_backptr as u64), bc.key_vtxindex as u32)); + if key_idx_opt.is_none() { + // should never happen -- the DB only accepts a leader block commitment if it + // matches a corresponding VRF public key + panic!("No leader key for block commitment {} (at {},{}) -- points to ({},{})", + &bc.txid.to_hex(), bc.block_number, bc.vtxindex, bc.block_number - (bc.key_block_backptr as u64), bc.key_vtxindex); + } + let key_idx = key_idx_opt.unwrap(); + + BurnSamplePoint { + burns: bc.burn_fee as u128, // Initial burn weight is the block commitment's burn + range_start: Uint256::zero(), // To be filled in + range_end: Uint256::zero(), // To be filled in + candidate: bc, + key: consumed_leader_keys[*key_idx].clone(), + user_burns: vec![] + } + }) + .collect(); + + // assign user burns to the burn sample points + BurnSamplePoint::apply_user_burns(&mut burn_sample, user_burns); + + // calculate burn ranges + BurnSamplePoint::make_sortition_ranges(&mut burn_sample); + burn_sample + } + + // sanity checks for making a burn distribution + fn ops_sanity_checks(block_candidates: &Vec>, consumed_leader_keys: &Vec>, user_burns: &Vec>) -> () { + // sanity checks + if block_candidates.len() != consumed_leader_keys.len() { + panic!("FATAL ERROR: {} block candidates != {} leader keys", block_candidates.len(), consumed_leader_keys.len()); + } + + let block_height = block_candidates[0].block_number; + for i in 1..block_candidates.len() { + if block_candidates[i].block_number != block_height { + panic!("FATAL ERROR: block commit {} is at ({},{}) not {}", &block_candidates[i].txid.to_hex(), block_candidates[i].block_number, block_candidates[i].vtxindex, block_height); + } + } + + for i in 0..user_burns.len() { + if user_burns[i].block_number != block_height { + panic!("FATAL ERROR: user burn {} is at ({},{}) not {}", &user_burns[i].txid.to_hex(), user_burns[i].block_number, user_burns[i].vtxindex, block_height); + } + } + + return; + } + + /// Move a list of user burns into their burn sample points. + fn apply_user_burns(burn_sample: &mut Vec>, user_burns: Vec>) -> () { + // map each burn sample's (VRF public key, block hash 160) pair to its index. + // The pair will be unique, since the DB requires each VRF key to be unique. + // Hovever, the block hash 160 is not guaranteed to be unique -- two different + // leaders can burn for the same block. + let mut burn_index : BTreeMap<(String, Hash160), usize> = BTreeMap::new(); + for i in 0..burn_sample.len() { + let block_header_hash_160 = Hash160::from_sha256(&burn_sample[i].candidate.block_header_hash.as_bytes()); + let vrf_hex = ECVRF_public_key_to_hex(&burn_sample[i].key.public_key); + assert!(burn_index.get(&(vrf_hex.clone(), block_header_hash_160)).is_none()); + + burn_index.insert((vrf_hex, block_header_hash_160), i); + } + + // match user burns to the leaders + for user_burn in user_burns { + let block_idx_opt = burn_index.get(&(ECVRF_public_key_to_hex(&user_burn.public_key), user_burn.block_header_hash_160)); + match block_idx_opt { + Some(ref_block_idx) => { + // user burn matches a (key, block) pair committed to in this block + let idx = *ref_block_idx; + burn_sample[idx].burns += (user_burn.burn_fee as u128); + burn_sample[idx].user_burns.push(user_burn); + }, + None => { + info!("User burn {} ({},{}) of {} for key={}, block={} has no matching block commit", + &user_burn.txid.to_hex(), user_burn.block_number, user_burn.vtxindex, user_burn.burn_fee, + ECVRF_public_key_to_hex(&user_burn.public_key), &user_burn.block_header_hash_160.to_hex()); + continue; + } + }; + } + } + + /// Calculate the ranges between 0 and 2**256 - 1 over which each point in the burn sample + /// applies, so we can later select which block to use. + fn make_sortition_ranges(burn_sample: &mut Vec>) -> () { + if burn_sample.len() == 0 { + // empty sample + return; + } + if burn_sample.len() == 1 { + // sample that covers the whole range + burn_sample[0].range_start = Uint256::zero(); + burn_sample[0].range_end = Uint256::max(); + return; + } + + // total burns for valid blocks? + // NOTE: this can't overflow -- there's no way we get that many (u64) burns + let total_burns_u128: u128 = burn_sample + .iter() + .fold(0u128, |mut total_burns, sample| { + total_burns = total_burns + (sample.burns as u128); + total_burns + }); + + let total_burns = Uint512::from_u128(total_burns_u128); + + // determine range start/end for each sample. + // Use fixed-point math on an unsigned 512-bit number -- + // * the upper 256 bits are the integer + // * the lower 256 bits are the fraction + // These range fields correspond to ranges in the 32-byte hash space + let mut burn_acc = Uint512::from_u128(burn_sample[0].burns); + + burn_sample[0].range_start = Uint256::zero(); + burn_sample[0].range_end = ((Uint512::from_uint256(&Uint256::max()) * burn_acc) / total_burns).to_uint256(); + for i in 1..burn_sample.len() { + burn_sample[i].range_start = burn_sample[i-1].range_end; + + burn_acc = burn_acc + Uint512::from_u128(burn_sample[i].burns); + burn_sample[i].range_end = ((Uint512::from_uint256(&Uint256::max()) * burn_acc) / total_burns).to_uint256(); + } + + for i in 0..burn_sample.len() { + test_debug!("Range for block {}: {} / {}: {} - {}", burn_sample[i].candidate.block_header_hash.to_hex(), burn_sample[i].burns, total_burns_u128, burn_sample[i].range_start, burn_sample[i].range_end); + } + } + + /// Calculate the total amount of crypto destroyed in this burn distribution. + /// Returns None if there was an overflow. + pub fn get_total_burns(burn_dist: &Vec>) -> Option { + let block_burn_total_u128 : u128 = burn_dist + .iter() + .fold(0u128, |mut burns_so_far, sample_point| { + burns_so_far += sample_point.burns; + burns_so_far + }); + + // check overflow + if block_burn_total_u128 >= 0xffffffffffffffff { + error!("Excessive burn size {}", block_burn_total_u128); + return None; + } + let block_burn_total = block_burn_total_u128 as u64; + Some(block_burn_total) + } +} + +#[cfg(test)] +mod tests { + use super::BurnSamplePoint; + + use std::marker::PhantomData; + + use burnchains::Address; + use burnchains::PublicKey; + use burnchains::Burnchain; + use burnchains::BurnchainTxInput; + use burnchains::BurnchainInputType; + + use chainstate::burn::operations::leader_key_register::LeaderKeyRegisterOp; + use chainstate::burn::operations::leader_block_commit::LeaderBlockCommitOp; + use chainstate::burn::operations::user_burn_support::UserBurnSupportOp; + use chainstate::burn::operations::leader_block_commit::OPCODE as LeaderBlockCommitOpcode; + use chainstate::burn::operations::leader_key_register::OPCODE as LeaderKeyRegisterOpcode; + use chainstate::burn::operations::user_burn_support::OPCODE as UserBurnSupportOpcode; + + use burnchains::bitcoin::address::BitcoinAddress; + use burnchains::bitcoin::keys::BitcoinPublicKey; + use burnchains::bitcoin::BitcoinNetworkType; + + use burnchains::{Txid, BurnchainHeaderHash}; + use chainstate::burn::{ConsensusHash, VRFSeed, BlockHeaderHash}; + use util::hash::hex_bytes; + use ed25519_dalek::PublicKey as VRFPublicKey; + + use util::hash::Hash160; + use util::uint::Uint256; + use util::uint::Uint512; + use util::uint::BitArray; + use util::vrf::ECVRF_public_key_to_hex; + + use util::log; + + struct BurnDistFixture { + consumed_leader_keys: Vec>, + block_commits: Vec>, + user_burns: Vec>, + res: Vec> + } + + #[test] + fn make_burn_distribution() { + + let first_burn_hash = BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); + + let leader_key_1 : LeaderKeyRegisterOp = LeaderKeyRegisterOp { + consensus_hash: ConsensusHash::from_bytes(&hex_bytes("2222222222222222222222222222222222222222").unwrap()).unwrap(), + public_key: VRFPublicKey::from_bytes(&hex_bytes("a366b51292bef4edd64063d9145c617fec373bceb0758e98cd72becd84d54c7a").unwrap()).unwrap(), + memo: vec![01, 02, 03, 04, 05], + address: BitcoinAddress::from_scriptpubkey(BitcoinNetworkType::Testnet, &hex_bytes("76a9140be3e286a15ea85882761618e366586b5574100d88ac").unwrap()).unwrap(), + + op: LeaderKeyRegisterOpcode, + txid: Txid::from_bytes_be(&hex_bytes("1bfa831b5fc56c858198acb8e77e5863c1e9d8ac26d49ddb914e24d8d4083562").unwrap()).unwrap(), + vtxindex: 456, + block_number: 123, + burn_header_hash: BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000001").unwrap(), + + _phantom: PhantomData + }; + + let leader_key_2 : LeaderKeyRegisterOp = LeaderKeyRegisterOp { + consensus_hash: ConsensusHash::from_bytes(&hex_bytes("3333333333333333333333333333333333333333").unwrap()).unwrap(), + public_key: VRFPublicKey::from_bytes(&hex_bytes("bb519494643f79f1dea0350e6fb9a1da88dfdb6137117fc2523824a8aa44fe1c").unwrap()).unwrap(), + memo: vec![01, 02, 03, 04, 05], + address: BitcoinAddress::from_scriptpubkey(BitcoinNetworkType::Testnet, &hex_bytes("76a91432b6c66189da32bd0a9f00ee4927f569957d71aa88ac").unwrap()).unwrap(), + + op: LeaderKeyRegisterOpcode, + txid: Txid::from_bytes_be(&hex_bytes("9410df84e2b440055c33acb075a0687752df63fe8fe84aeec61abe469f0448c7").unwrap()).unwrap(), + vtxindex: 457, + block_number: 122, + burn_header_hash: BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000002").unwrap(), + + _phantom: PhantomData + }; + + let leader_key_3 : LeaderKeyRegisterOp = LeaderKeyRegisterOp { + consensus_hash: ConsensusHash::from_bytes(&hex_bytes("3333333333333333333333333333333333333333").unwrap()).unwrap(), + public_key: VRFPublicKey::from_bytes(&hex_bytes("de8af7037e522e65d2fe2d63fb1b764bfea829df78b84444338379df13144a02").unwrap()).unwrap(), + memo: vec![01, 02, 03, 04, 05], + address: BitcoinAddress::from_scriptpubkey(BitcoinNetworkType::Testnet, &hex_bytes("76a91432b6c66189da32bd0a9f00ee4927f569957d71aa88ac").unwrap()).unwrap(), + + op: LeaderKeyRegisterOpcode, + txid: Txid::from_bytes_be(&hex_bytes("eb54704f71d4a2d1128d60ffccced547054b52250ada6f3e7356165714f44d4c").unwrap()).unwrap(), + vtxindex: 10, + block_number: 121, + burn_header_hash: BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000012").unwrap(), + + _phantom: PhantomData + }; + + let user_burn_noblock : UserBurnSupportOp = UserBurnSupportOp { + consensus_hash: ConsensusHash::from_bytes(&hex_bytes("4444444444444444444444444444444444444444").unwrap()).unwrap(), + public_key: VRFPublicKey::from_bytes(&hex_bytes("a366b51292bef4edd64063d9145c617fec373bceb0758e98cd72becd84d54c7a").unwrap()).unwrap(), + block_header_hash_160: Hash160::from_bytes(&hex_bytes("3333333333333333333333333333333333333333").unwrap()).unwrap(), + memo: vec![0x01, 0x02, 0x03, 0x04, 0x05], + burn_fee: 12345, + + op: UserBurnSupportOpcode, + txid: Txid::from_bytes_be(&hex_bytes("1d5cbdd276495b07f0e0bf0181fa57c175b217bc35531b078d62fc20986c716c").unwrap()).unwrap(), + vtxindex: 12, + block_number: 124, + burn_header_hash: BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000004").unwrap(), + + _phantom_a: PhantomData, + _phantom_k: PhantomData + }; + + let user_burn_1 : UserBurnSupportOp = UserBurnSupportOp { + consensus_hash: ConsensusHash::from_bytes(&hex_bytes("4444444444444444444444444444444444444444").unwrap()).unwrap(), + public_key: VRFPublicKey::from_bytes(&hex_bytes("a366b51292bef4edd64063d9145c617fec373bceb0758e98cd72becd84d54c7a").unwrap()).unwrap(), + block_header_hash_160: Hash160::from_bytes(&hex_bytes("7150f635054b87df566a970b21e07030d6444bf2").unwrap()).unwrap(), // 22222....2222 + memo: vec![0x01, 0x02, 0x03, 0x04, 0x05], + burn_fee: 10000, + + op: UserBurnSupportOpcode, + txid: Txid::from_bytes_be(&hex_bytes("1d5cbdd276495b07f0e0bf0181fa57c175b217bc35531b078d62fc20986c716c").unwrap()).unwrap(), + vtxindex: 13, + block_number: 124, + burn_header_hash: BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000004").unwrap(), + + _phantom_a: PhantomData, + _phantom_k: PhantomData + }; + + let user_burn_1_2 : UserBurnSupportOp = UserBurnSupportOp { + consensus_hash: ConsensusHash::from_bytes(&hex_bytes("4444444444444444444444444444444444444444").unwrap()).unwrap(), + public_key: VRFPublicKey::from_bytes(&hex_bytes("a366b51292bef4edd64063d9145c617fec373bceb0758e98cd72becd84d54c7a").unwrap()).unwrap(), + block_header_hash_160: Hash160::from_bytes(&hex_bytes("7150f635054b87df566a970b21e07030d6444bf2").unwrap()).unwrap(), // 22222....2222 + memo: vec![0x01, 0x02, 0x03, 0x04, 0x05], + burn_fee: 30000, + + op: UserBurnSupportOpcode, + txid: Txid::from_bytes_be(&hex_bytes("1d5cbdd276495b07f0e0bf0181fa57c175b217bc35531b078d62fc20986c716c").unwrap()).unwrap(), + vtxindex: 14, + block_number: 124, + burn_header_hash: BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000004").unwrap(), + + _phantom_a: PhantomData, + _phantom_k: PhantomData + }; + + let user_burn_2 : UserBurnSupportOp = UserBurnSupportOp { + consensus_hash: ConsensusHash::from_bytes(&hex_bytes("4444444444444444444444444444444444444444").unwrap()).unwrap(), + public_key: VRFPublicKey::from_bytes(&hex_bytes("bb519494643f79f1dea0350e6fb9a1da88dfdb6137117fc2523824a8aa44fe1c").unwrap()).unwrap(), + block_header_hash_160: Hash160::from_bytes(&hex_bytes("037a1e860899a4fa823c18b66f6264d20236ec58").unwrap()).unwrap(), // 22222....2223 + memo: vec![0x01, 0x02, 0x03, 0x04, 0x05], + burn_fee: 20000, + + op: UserBurnSupportOpcode, + txid: Txid::from_bytes_be(&hex_bytes("1d5cbdd276495b07f0e0bf0181fa57c175b217bc35531b078d62fc20986c716d").unwrap()).unwrap(), + vtxindex: 14, + block_number: 124, + burn_header_hash: BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000004").unwrap(), + + _phantom_a: PhantomData, + _phantom_k: PhantomData + }; + + let user_burn_2_2 : UserBurnSupportOp = UserBurnSupportOp { + consensus_hash: ConsensusHash::from_bytes(&hex_bytes("4444444444444444444444444444444444444444").unwrap()).unwrap(), + public_key: VRFPublicKey::from_bytes(&hex_bytes("bb519494643f79f1dea0350e6fb9a1da88dfdb6137117fc2523824a8aa44fe1c").unwrap()).unwrap(), + block_header_hash_160: Hash160::from_bytes(&hex_bytes("037a1e860899a4fa823c18b66f6264d20236ec58").unwrap()).unwrap(), // 22222....2223 + memo: vec![0x01, 0x02, 0x03, 0x04, 0x05], + burn_fee: 40000, + + op: UserBurnSupportOpcode, + txid: Txid::from_bytes_be(&hex_bytes("1d5cbdd276495b07f0e0bf0181fa57c175b217bc35531b078d62fc20986c716c").unwrap()).unwrap(), + vtxindex: 15, + block_number: 124, + burn_header_hash: BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000004").unwrap(), + + _phantom_a: PhantomData, + _phantom_k: PhantomData + }; + + let user_burn_nokey : UserBurnSupportOp = UserBurnSupportOp { + consensus_hash: ConsensusHash::from_bytes(&hex_bytes("4444444444444444444444444444444444444444").unwrap()).unwrap(), + public_key: VRFPublicKey::from_bytes(&hex_bytes("3f3338db51f2b1f6ac0cf6177179a24ee130c04ef2f9849a64a216969ab60e70").unwrap()).unwrap(), + block_header_hash_160: Hash160::from_bytes(&hex_bytes("037a1e860899a4fa823c18b66f6264d20236ec58").unwrap()).unwrap(), + memo: vec![0x01, 0x02, 0x03, 0x04, 0x05], + burn_fee: 12345, + + op: UserBurnSupportOpcode, + txid: Txid::from_bytes_be(&hex_bytes("1d5cbdd276495b07f0e0bf0181fa57c175b217bc35531b078d62fc20986c716e").unwrap()).unwrap(), + vtxindex: 15, + block_number: 124, + burn_header_hash: BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000004").unwrap(), + + _phantom_a: PhantomData, + _phantom_k: PhantomData + }; + + let block_commit_1 : LeaderBlockCommitOp = LeaderBlockCommitOp { + block_header_hash: BlockHeaderHash::from_bytes(&hex_bytes("2222222222222222222222222222222222222222222222222222222222222222").unwrap()).unwrap(), + new_seed: VRFSeed::from_bytes(&hex_bytes("3333333333333333333333333333333333333333333333333333333333333333").unwrap()).unwrap(), + parent_block_backptr: 123, + parent_vtxindex: 456, + key_block_backptr: 1, + key_vtxindex: 456, + epoch_num: 50, + memo: vec![0x80], + + burn_fee: 12345, + input: BurnchainTxInput { + keys: vec![ + BitcoinPublicKey::from_hex("02d8015134d9db8178ac93acbc43170a2f20febba5087a5b0437058765ad5133d0").unwrap(), + ], + num_required: 1, + in_type: BurnchainInputType::BitcoinInput, + }, + + op: 91, // '[' in ascii + txid: Txid::from_bytes_be(&hex_bytes("3c07a0a93360bc85047bbaadd49e30c8af770f73a37e10fec400174d2e5f27cf").unwrap()).unwrap(), + vtxindex: 444, + block_number: 124, + burn_header_hash: BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000004").unwrap(), + + _phantom: PhantomData + }; + + let block_commit_2 : LeaderBlockCommitOp = LeaderBlockCommitOp { + block_header_hash: BlockHeaderHash::from_bytes(&hex_bytes("2222222222222222222222222222222222222222222222222222222222222223").unwrap()).unwrap(), + new_seed: VRFSeed::from_bytes(&hex_bytes("3333333333333333333333333333333333333333333333333333333333333334").unwrap()).unwrap(), + parent_block_backptr: 123, + parent_vtxindex: 111, + key_block_backptr: 2, + key_vtxindex: 457, + epoch_num: 50, + memo: vec![0x80], + + burn_fee: 12345, + input: BurnchainTxInput { + keys: vec![ + BitcoinPublicKey::from_hex("02d8015134d9db8178ac93acbc43170a2f20febba5087a5b0437058765ad5133d0").unwrap(), + ], + num_required: 1, + in_type: BurnchainInputType::BitcoinInput, + }, + + op: 91, // '[' in ascii + txid: Txid::from_bytes_be(&hex_bytes("3c07a0a93360bc85047bbaadd49e30c8af770f73a37e10fec400174d2e5f27d0").unwrap()).unwrap(), + vtxindex: 445, + block_number: 124, + burn_header_hash: BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000004").unwrap(), + + _phantom: PhantomData + }; + + let block_commit_3 : LeaderBlockCommitOp = LeaderBlockCommitOp { + block_header_hash: BlockHeaderHash::from_bytes(&hex_bytes("2222222222222222222222222222222222222222222222222222222222222224").unwrap()).unwrap(), + new_seed: VRFSeed::from_bytes(&hex_bytes("3333333333333333333333333333333333333333333333333333333333333335").unwrap()).unwrap(), + parent_block_backptr: 123, + parent_vtxindex: 111, + key_block_backptr: 3, + key_vtxindex: 10, + epoch_num: 50, + memo: vec![0x80], + + burn_fee: 23456, + input: BurnchainTxInput { + keys: vec![ + BitcoinPublicKey::from_hex("02d8015134d9db8178ac93acbc43170a2f20febba5087a5b0437058765ad5133d0").unwrap(), + ], + num_required: 1, + in_type: BurnchainInputType::BitcoinInput, + }, + + op: 91, // '[' in ascii + txid: Txid::from_bytes_be(&hex_bytes("301dc687a9f06a1ae87a013f27133e9cec0843c2983567be73e185827c7c13de").unwrap()).unwrap(), + vtxindex: 445, + block_number: 124, + burn_header_hash: BurnchainHeaderHash::from_hex("0000000000000000000000000000000000000000000000000000000000000004").unwrap(), + + _phantom: PhantomData + }; + + /* + You can generate the burn sample ranges with this Python script: + #!/usr/bin/python + + import sys + + a = eval(sys.argv[1]) + b = eval(sys.argv[2]) + + s = '{:0128x}'.format((a * (2**256 - 1)) / b).decode('hex')[::-1]; + l = ['0x{:016x}'.format(int(s[(8*i):(8*(i+1))][::-1].encode('hex'),16)) for i in range(0,(256/8/8))] + + print float(a) / b + print '{:0128x}'.format((a * (2**256 - 1)) / b) + print '[' + ', '.join(l) + ']' + */ + + let fixtures : Vec = vec![ + BurnDistFixture { + consumed_leader_keys: vec![], + block_commits: vec![], + user_burns: vec![], + res: vec![], + }, + BurnDistFixture { + consumed_leader_keys: vec![ + leader_key_1.clone(), + ], + block_commits: vec![ + block_commit_1.clone(), + ], + user_burns: vec![], + res: vec![ + BurnSamplePoint { + burns: block_commit_1.burn_fee.into(), + range_start: Uint256::zero(), + range_end: Uint256::max(), + candidate: block_commit_1.clone(), + key: leader_key_1.clone(), + user_burns: vec![] + } + ] + }, + BurnDistFixture { + consumed_leader_keys: vec![ + leader_key_1.clone(), + leader_key_2.clone(), + ], + block_commits: vec![ + block_commit_1.clone(), + block_commit_2.clone(), + ], + user_burns: vec![], + res: vec![ + BurnSamplePoint { + burns: block_commit_1.burn_fee.into(), + range_start: Uint256::zero(), + range_end: Uint256([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff]), + candidate: block_commit_1.clone(), + key: leader_key_1.clone(), + user_burns: vec![], + }, + BurnSamplePoint { + burns: block_commit_2.burn_fee.into(), + range_start: Uint256([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff]), + range_end: Uint256::max(), + candidate: block_commit_2.clone(), + key: leader_key_2.clone(), + user_burns: vec![], + }, + ] + }, + BurnDistFixture { + consumed_leader_keys: vec![ + leader_key_1.clone(), + leader_key_2.clone(), + ], + block_commits: vec![ + block_commit_1.clone(), + block_commit_2.clone(), + ], + user_burns: vec![ + user_burn_noblock.clone(), + ], + res: vec![ + BurnSamplePoint { + burns: block_commit_1.burn_fee.into(), + range_start: Uint256::zero(), + range_end: Uint256([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff]), + candidate: block_commit_1.clone(), + key: leader_key_1.clone(), + user_burns: vec![], + }, + BurnSamplePoint { + burns: block_commit_2.burn_fee.into(), + range_start: Uint256([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff]), + range_end: Uint256::max(), + candidate: block_commit_2.clone(), + key: leader_key_2.clone(), + user_burns: vec![], + }, + ] + }, + BurnDistFixture { + consumed_leader_keys: vec![ + leader_key_1.clone(), + leader_key_2.clone(), + ], + block_commits: vec![ + block_commit_1.clone(), + block_commit_2.clone(), + ], + user_burns: vec![ + user_burn_nokey.clone(), + ], + res: vec![ + BurnSamplePoint { + burns: block_commit_1.burn_fee.into(), + range_start: Uint256::zero(), + range_end: Uint256([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff]), + candidate: block_commit_1.clone(), + key: leader_key_1.clone(), + user_burns: vec![], + }, + BurnSamplePoint { + burns: block_commit_2.burn_fee.into(), + range_start: Uint256([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff]), + range_end: Uint256::max(), + candidate: block_commit_2.clone(), + key: leader_key_2.clone(), + user_burns: vec![], + }, + ] + }, + BurnDistFixture { + consumed_leader_keys: vec![ + leader_key_1.clone(), + leader_key_2.clone(), + ], + block_commits: vec![ + block_commit_1.clone(), + block_commit_2.clone(), + ], + user_burns: vec![ + user_burn_nokey.clone(), + user_burn_noblock.clone(), + user_burn_1.clone(), + ], + res: vec![ + BurnSamplePoint { + burns: (block_commit_1.burn_fee + user_burn_1.burn_fee).into(), + range_start: Uint256::zero(), + range_end: Uint256([0x441d393138e5a796, 0xbada4a3d4046d839, 0xa24749933957018c, 0xa4e5f328cf38744d]), + candidate: block_commit_1.clone(), + key: leader_key_1.clone(), + user_burns: vec![ + user_burn_1.clone(), + ], + }, + BurnSamplePoint { + burns: block_commit_2.burn_fee.into(), + range_start: Uint256([0x441d393138e5a796, 0xbada4a3d4046d839, 0xa24749933957018c, 0xa4e5f328cf38744d]), + range_end: Uint256::max(), + candidate: block_commit_2.clone(), + key: leader_key_2.clone(), + user_burns: vec![], + }, + ] + }, + BurnDistFixture { + consumed_leader_keys: vec![ + leader_key_1.clone(), + leader_key_2.clone(), + ], + block_commits: vec![ + block_commit_1.clone(), + block_commit_2.clone(), + ], + user_burns: vec![ + user_burn_nokey.clone(), + user_burn_noblock.clone(), + user_burn_1.clone(), + user_burn_2.clone(), + ], + res: vec![ + BurnSamplePoint { + burns: (block_commit_1.burn_fee + user_burn_1.burn_fee).into(), + range_start: Uint256::zero(), + range_end: Uint256([0x65db6527a5c06ed7, 0xfbf9725ae754dd80, 0xeafb8d991cf9964d, 0x6898693a2f1713b4]), + candidate: block_commit_1.clone(), + key: leader_key_1.clone(), + user_burns: vec![ + user_burn_1.clone(), + ], + }, + BurnSamplePoint { + burns: (block_commit_2.burn_fee + user_burn_2.burn_fee).into(), + range_start: Uint256([0x65db6527a5c06ed7, 0xfbf9725ae754dd80, 0xeafb8d991cf9964d, 0x6898693a2f1713b4]), + range_end: Uint256::max(), + candidate: block_commit_2.clone(), + key: leader_key_2.clone(), + user_burns: vec![ + user_burn_2.clone() + ], + }, + ] + }, + BurnDistFixture { + consumed_leader_keys: vec![ + leader_key_1.clone(), + leader_key_2.clone(), + ], + block_commits: vec![ + block_commit_1.clone(), + block_commit_2.clone(), + ], + user_burns: vec![ + user_burn_nokey.clone(), + user_burn_noblock.clone(), + user_burn_2_2.clone(), + user_burn_1_2.clone(), + user_burn_1.clone(), + user_burn_2.clone(), + ], + res: vec![ + BurnSamplePoint { + burns: (block_commit_1.burn_fee + user_burn_1.burn_fee + user_burn_1_2.burn_fee).into(), + range_start: Uint256::zero(), + range_end: Uint256([0xbc9e168afe8ad47e, 0xbbb6d3eb8d1be6c9, 0x45a410039d0a7dc5, 0x6b7815d84b0f9fc0]), + candidate: block_commit_1.clone(), + key: leader_key_1.clone(), + user_burns: vec![ + user_burn_1_2.clone(), + user_burn_1.clone(), + ], + }, + BurnSamplePoint { + burns: (block_commit_2.burn_fee + user_burn_2.burn_fee + user_burn_2_2.burn_fee).into(), + range_start: Uint256([0xbc9e168afe8ad47e, 0xbbb6d3eb8d1be6c9, 0x45a410039d0a7dc5, 0x6b7815d84b0f9fc0]), + range_end: Uint256::max(), + candidate: block_commit_2.clone(), + key: leader_key_2.clone(), + user_burns: vec![ + user_burn_2_2.clone(), + user_burn_2.clone() + ], + }, + ] + }, + BurnDistFixture { + consumed_leader_keys: vec![ + leader_key_1.clone(), + leader_key_2.clone(), + leader_key_3.clone(), + ], + block_commits: vec![ + block_commit_1.clone(), + block_commit_2.clone(), + block_commit_3.clone(), + ], + user_burns: vec![ + user_burn_nokey.clone(), + user_burn_noblock.clone(), + user_burn_2_2.clone(), + user_burn_1_2.clone(), + user_burn_1.clone(), + user_burn_2.clone(), + ], + res: vec![ + BurnSamplePoint { + burns: (block_commit_1.burn_fee + user_burn_1.burn_fee + user_burn_1_2.burn_fee).into(), + range_start: Uint256::zero(), + range_end: Uint256([0xcb48ed15c5086a5c, 0x6b29682cfbe4089c, 0x4a30e732285c18c9, 0x5a7416b691bddbad]), + candidate: block_commit_1.clone(), + key: leader_key_1.clone(), + user_burns: vec![ + user_burn_1_2.clone(), + user_burn_1.clone(), + ], + }, + BurnSamplePoint { + burns: (block_commit_2.burn_fee + user_burn_2.burn_fee + user_burn_2_2.burn_fee).into(), + range_start: Uint256([0xcb48ed15c5086a5c, 0x6b29682cfbe4089c, 0x4a30e732285c18c9, 0x5a7416b691bddbad]), + range_end: Uint256([0xa224e0451efa00f5, 0xa57394a7b38d5b1c, 0x6bfdbf24cdb0b617, 0xd777aa6d9e769e59]), + candidate: block_commit_2.clone(), + key: leader_key_2.clone(), + user_burns: vec![ + user_burn_2_2.clone(), + user_burn_2.clone() + ], + }, + BurnSamplePoint { + burns: (block_commit_3.burn_fee).into(), + range_start: Uint256([0xa224e0451efa00f5, 0xa57394a7b38d5b1c, 0x6bfdbf24cdb0b617, 0xd777aa6d9e769e59]), + range_end: Uint256::max(), + candidate: block_commit_3.clone(), + key: leader_key_3.clone(), + user_burns: vec![] + }, + ] + }, + ]; + + for i in 0..fixtures.len() { + let f = &fixtures[i]; + let dist = BurnSamplePoint::make_distribution(f.block_commits.iter().cloned().collect(), f.consumed_leader_keys.iter().cloned().collect(), f.user_burns.iter().cloned().collect()); + assert_eq!(dist, f.res); + } + } +}