From d28a27e553779636c7ddfe3e62963976a8b7668b Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Wed, 23 Sep 2020 16:15:20 -0500 Subject: [PATCH] fix: use cur burn block to fetch reward cycle info --- src/chainstate/coordinator/mod.rs | 21 +-- src/chainstate/coordinator/tests.rs | 4 +- src/chainstate/stacks/boot/mod.rs | 116 ++++++++------ src/chainstate/stacks/boot/pox-mainnet.clar | 24 +++ src/chainstate/stacks/boot/pox-testnet.clar | 25 +++ src/chainstate/stacks/boot/pox.clar | 65 ++++---- src/net/mod.rs | 4 +- testnet/stacks-node/src/neon_node.rs | 2 +- .../src/tests/neon_integrations.rs | 143 +++++++++++++++++- 9 files changed, 298 insertions(+), 106 deletions(-) create mode 100644 src/chainstate/stacks/boot/pox-mainnet.clar create mode 100644 src/chainstate/stacks/boot/pox-testnet.clar diff --git a/src/chainstate/coordinator/mod.rs b/src/chainstate/coordinator/mod.rs index e80bce2f09..6797fdad0f 100644 --- a/src/chainstate/coordinator/mod.rs +++ b/src/chainstate/coordinator/mod.rs @@ -142,30 +142,21 @@ impl From for Error { } pub trait RewardSetProvider { - fn get_reward_set(&self, chainstate: &mut StacksChainState, + fn get_reward_set(&self, current_burn_height: u64, chainstate: &mut StacksChainState, burnchain: &Burnchain, sortdb: &SortitionDB, block_id: &StacksBlockId) -> Result, Error>; } -pub struct OnChainRewardSetProvider { - -} +pub struct OnChainRewardSetProvider (); impl RewardSetProvider for OnChainRewardSetProvider { - fn get_reward_set(&self, chainstate: &mut StacksChainState, + fn get_reward_set(&self, current_burn_height: u64, chainstate: &mut StacksChainState, burnchain: &Burnchain, sortdb: &SortitionDB, block_id: &StacksBlockId) -> Result, Error> { - let res = chainstate.get_reward_addresses(burnchain, sortdb, block_id)?; + let res = chainstate.get_reward_addresses(burnchain, sortdb, current_burn_height, block_id)?; let addresses = res.iter().map(|a| a.0).collect::>(); Ok(addresses) } } -impl OnChainRewardSetProvider { - - pub fn new() -> OnChainRewardSetProvider { - OnChainRewardSetProvider {} - } -} - impl <'a, T: BlockEventDispatcher> ChainsCoordinator <'a, T, ArcCounterCoordinatorNotices, OnChainRewardSetProvider> { pub fn run(chain_state_path: &str, burnchain: Burnchain, stacks_mainnet: bool, stacks_chain_id: u32, initial_balances: Option>, @@ -196,7 +187,7 @@ impl <'a, T: BlockEventDispatcher> ChainsCoordinator <'a, T, ArcCounterCoordinat burnchain, dispatcher: Some(dispatcher), notifier: arc_notices, - reward_set_provider: OnChainRewardSetProvider::new(), + reward_set_provider: OnChainRewardSetProvider(), }; loop { @@ -284,7 +275,7 @@ pub fn get_reward_cycle_info( let anchor_status = if anchor_block_known { let block_id = StacksBlockHeader::make_index_block_hash(&consensus_hash, &stacks_block_hash); let reward_set = provider.get_reward_set( - chain_state, burnchain, sort_db, &block_id)?; + burn_height, chain_state, burnchain, sort_db, &block_id)?; PoxAnchorBlockStatus::SelectedAndKnown(stacks_block_hash, reward_set) } else { PoxAnchorBlockStatus::SelectedAndUnknown(stacks_block_hash) diff --git a/src/chainstate/coordinator/tests.rs b/src/chainstate/coordinator/tests.rs index 1a8fb4bf3d..db30786677 100644 --- a/src/chainstate/coordinator/tests.rs +++ b/src/chainstate/coordinator/tests.rs @@ -165,13 +165,13 @@ impl BlockEventDispatcher for NullEventDispatcher { } pub fn make_coordinator<'a>(path: &str) -> ChainsCoordinator<'a, NullEventDispatcher, (), OnChainRewardSetProvider> { - ChainsCoordinator::test_new(&get_burnchain(path), path, OnChainRewardSetProvider::new()) + ChainsCoordinator::test_new(&get_burnchain(path), path, OnChainRewardSetProvider()) } struct StubbedRewardSetProvider(Vec); impl RewardSetProvider for StubbedRewardSetProvider { - fn get_reward_set(&self, chainstate: &mut StacksChainState, + fn get_reward_set(&self, _current_burn_height: u64, chainstate: &mut StacksChainState, burnchain: &Burnchain, sortdb: &SortitionDB, block_id: &StacksBlockId) -> Result, chainstate::coordinator::Error> { Ok(self.0.clone()) } diff --git a/src/chainstate/stacks/boot/mod.rs b/src/chainstate/stacks/boot/mod.rs index a6393f1363..7ba18cdf9a 100644 --- a/src/chainstate/stacks/boot/mod.rs +++ b/src/chainstate/stacks/boot/mod.rs @@ -100,15 +100,8 @@ impl StacksChainState { } /// Determine which reward cycle this particular block lives in. - pub fn get_reward_cycle(&mut self, burnchain: &Burnchain, block_id: &StacksBlockId) -> Result { - let parent_block_id = StacksChainState::get_parent_block_id(self.headers_db(), block_id)? - .ok_or(Error::PoxNoRewardCycle)?; - - let parent_header_info = StacksChainState::get_stacks_block_header_info_by_index_block_hash(self.headers_db(), &parent_block_id)? - .ok_or(Error::PoxNoRewardCycle)?; - - // NOTE: the parent's burn block height is what's exposed as burn-block-height in the VM - Ok((((parent_header_info.burn_header_height as u64) - burnchain.first_block_height) / burnchain.pox_constants.reward_cycle_length as u64) as u128) + pub fn get_reward_cycle(&mut self, burnchain: &Burnchain, burn_block_height: u64) -> u128 { + ((burn_block_height - burnchain.first_block_height) / burnchain.pox_constants.reward_cycle_length as u64) as u128 } /// Determine the minimum amount of STX per reward address required to stack in the _next_ @@ -132,10 +125,9 @@ impl StacksChainState { .map(|value| value.expect_bool()) } - /// List all PoX addresses and amount of uSTX stacked, at a particular block. /// Each address will have at least (get-stacking-minimum) tokens. - pub fn get_reward_addresses(&mut self, burnchain: &Burnchain, sortdb: &SortitionDB, block_id: &StacksBlockId) -> Result, Error> { - let reward_cycle = self.get_reward_cycle(burnchain, block_id)?; + pub fn get_reward_addresses(&mut self, burnchain: &Burnchain, sortdb: &SortitionDB, current_burn_height: u64, block_id: &StacksBlockId) -> Result, Error> { + let reward_cycle = self.get_reward_cycle(burnchain, current_burn_height); if !self.is_pox_active(sortdb, block_id, reward_cycle)? { debug!("PoX was voted disabled in block {} (reward cycle {})", block_id, reward_cycle); return Ok(vec![]); @@ -560,6 +552,11 @@ pub mod test { tx_signer.get_tx().unwrap() } + fn get_reward_addresses_with_par_tip(state: &mut StacksChainState, burnchain: &Burnchain, sortdb: &SortitionDB, block_id: &StacksBlockId) -> Result, Error> { + let burn_block_height = get_par_burn_block_height(state, block_id); + state.get_reward_addresses(burnchain, sortdb, burn_block_height, block_id) + } + fn get_parent_tip(parent_opt: &Option<&StacksBlock>, chainstate: &StacksChainState, sortdb: &SortitionDB) -> StacksHeaderInfo { let tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).unwrap(); let parent_tip = match parent_opt { @@ -769,6 +766,16 @@ pub mod test { } } } + + fn get_par_burn_block_height(state: &mut StacksChainState, block_id: &StacksBlockId) -> u64 { + let parent_block_id = StacksChainState::get_parent_block_id(state.headers_db(), block_id) + .unwrap().unwrap(); + + let parent_header_info = StacksChainState::get_stacks_block_header_info_by_index_block_hash(state.headers_db(), &parent_block_id) + .unwrap().unwrap(); + + parent_header_info.burn_header_height as u64 + } #[test] fn test_pox_lockup_single_tx_sender() { @@ -831,25 +838,27 @@ pub mod test { assert_eq!(min_ustx, total_liquid_ustx / 20000); // no reward addresses - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); assert_eq!(reward_addrs.len(), 0); // record the first reward cycle when Alice's tokens get stacked - alice_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + let tip_burn_block_height = get_par_burn_block_height(peer.chainstate(), &tip_index_block); + alice_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); eprintln!("\nalice reward cycle: {}\ncur reward cycle: {}\n", alice_reward_cycle, cur_reward_cycle); } else { // Alice's address is locked as of the next reward cycle - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + let tip_burn_block_height = get_par_burn_block_height(peer.chainstate(), &tip_index_block); + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); // Alice has locked up STX no matter what let alice_balance = get_balance(&mut peer, &key_to_stacks_addr(&alice).into()); assert_eq!(alice_balance, 0); let min_ustx = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_stacking_minimum(sortdb, &tip_index_block)).unwrap(); - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); let total_stacked = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_total_ustx_stacked(sortdb, &tip_index_block, cur_reward_cycle)).unwrap(); eprintln!("\ntenure: {}\nreward cycle: {}\nmin-uSTX: {}\naddrs: {:?}\ntotal_liquid_ustx: {}\ntotal-stacked: {}\n", tenure_id, cur_reward_cycle, min_ustx, &reward_addrs, total_liquid_ustx, total_stacked); @@ -951,24 +960,26 @@ pub mod test { assert_eq!(min_ustx, total_liquid_ustx / 20000); // no reward addresses - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); assert_eq!(reward_addrs.len(), 0); // record the first reward cycle when Alice's tokens get stacked - alice_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + let tip_burn_block_height = get_par_burn_block_height(peer.chainstate(), &tip_index_block); + alice_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); eprintln!("\nalice reward cycle: {}\ncur reward cycle: {}\n", alice_reward_cycle, cur_reward_cycle); } else { - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + let tip_burn_block_height = get_par_burn_block_height(peer.chainstate(), &tip_index_block); + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); // Alice's tokens got sent to the contract, so her balance is 0 let alice_balance = get_balance(&mut peer, &key_to_stacks_addr(&alice).into()); assert_eq!(alice_balance, 0); let min_ustx = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_stacking_minimum(sortdb, &tip_index_block)).unwrap(); - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); let total_stacked = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_total_ustx_stacked(sortdb, &tip_index_block, cur_reward_cycle)).unwrap(); eprintln!("\ntenure: {}\nreward cycle: {}\nmin-uSTX: {}\naddrs: {:?}\ntotal_liquid_ustx: {}\ntotal-stacked: {}\n", tenure_id, cur_reward_cycle, min_ustx, &reward_addrs, total_liquid_ustx, total_stacked); @@ -1111,18 +1122,20 @@ pub mod test { assert_eq!(min_ustx, total_liquid_ustx / 20000); // no reward addresses - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); assert_eq!(reward_addrs.len(), 0); // record the first reward cycle when Alice's tokens get stacked - first_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + let tip_burn_block_height = get_par_burn_block_height(peer.chainstate(), &tip_index_block); + first_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); eprintln!("\nalice reward cycle: {}\ncur reward cycle: {}\n", first_reward_cycle, cur_reward_cycle); } else { // Alice's and Bob's addresses are locked as of the next reward cycle - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + let tip_burn_block_height = get_par_burn_block_height(peer.chainstate(), &tip_index_block); + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); // Alice and Bob have locked up STX no matter what let alice_balance = get_balance(&mut peer, &key_to_stacks_addr(&alice).into()); @@ -1132,7 +1145,7 @@ pub mod test { assert_eq!(bob_balance, 1024 * 1000000 - (4 * 1024 * 1000000) / 5); let min_ustx = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_stacking_minimum(sortdb, &tip_index_block)).unwrap(); - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); eprintln!("\nreward cycle: {}\nmin-uSTX: {}\naddrs: {:?}\ntotal_liquid_ustx: {}\n", cur_reward_cycle, min_ustx, &reward_addrs, total_liquid_ustx); @@ -1291,12 +1304,14 @@ pub mod test { if tenure_id <= 1 { // no reward addresses - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); assert_eq!(reward_addrs.len(), 0); // record the first reward cycle when Alice's tokens get stacked - first_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + let tip_burn_block_height = get_par_burn_block_height(peer.chainstate(), &tip_index_block); + + first_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); eprintln!("\nalice reward cycle: {}\ncur reward cycle: {}\n", first_reward_cycle, cur_reward_cycle); } @@ -1372,23 +1387,25 @@ pub mod test { assert_eq!(min_ustx, total_liquid_ustx / 20000); // no reward addresses - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); assert_eq!(reward_addrs.len(), 0); // record the first reward cycle when Alice's tokens get stacked - alice_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + let tip_burn_block_height = get_par_burn_block_height(peer.chainstate(), &tip_index_block); + alice_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); eprintln!("\nalice reward cycle: {}\ncur reward cycle: {}\n", alice_reward_cycle, cur_reward_cycle); } else { // Alice's address is locked as of the next reward cycle - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + let tip_burn_block_height = get_par_burn_block_height(peer.chainstate(), &tip_index_block); + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); let alice_balance = get_balance(&mut peer, &key_to_stacks_addr(&alice).into()); let min_ustx = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_stacking_minimum(sortdb, &tip_index_block)).unwrap(); - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); let total_stacked = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_total_ustx_stacked(sortdb, &tip_index_block, cur_reward_cycle)).unwrap(); eprintln!("\ntenure: {}\nreward cycle: {}\nmin-uSTX: {}\naddrs: {:?}\ntotal_liquid_ustx: {}\ntotal-stacked: {}\n", tenure_id, cur_reward_cycle, min_ustx, &reward_addrs, total_liquid_ustx, total_stacked); @@ -1523,12 +1540,13 @@ pub mod test { let total_liquid_ustx = get_liquid_ustx(&mut peer); let tip_index_block = StacksBlockHeader::make_index_block_hash(&consensus_hash, &stacks_block.block_hash()); - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + let tip_burn_block_height = get_par_burn_block_height(peer.chainstate(), &tip_index_block); + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); let alice_balance = get_balance(&mut peer, &key_to_stacks_addr(&alice).into()); let charlie_balance = get_balance(&mut peer, &make_contract_id(&key_to_stacks_addr(&bob), "do-lockup").into()); - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); let min_ustx = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_stacking_minimum(sortdb, &tip_index_block)).unwrap(); let total_stacked = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_total_ustx_stacked(sortdb, &tip_index_block, cur_reward_cycle)).unwrap(); @@ -1550,7 +1568,7 @@ pub mod test { assert_eq!(reward_addrs.len(), 0); // record the first reward cycle when Alice's tokens get stacked - first_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + first_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); eprintln!("\nfirst reward cycle: {}\ncur reward cycle: {}\n", first_reward_cycle, cur_reward_cycle); assert!(first_reward_cycle > cur_reward_cycle); @@ -1574,7 +1592,7 @@ pub mod test { assert_eq!(reward_addrs.len(), 0); // record the first reward cycle when Alice's tokens get stacked - second_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + second_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); assert!(second_reward_cycle > cur_reward_cycle); eprintln!("\nsecond reward cycle: {}\ncur reward cycle: {}\n", second_reward_cycle, cur_reward_cycle); } @@ -1848,7 +1866,9 @@ pub mod test { let total_liquid_ustx = get_liquid_ustx(&mut peer); let tip_index_block = StacksBlockHeader::make_index_block_hash(&consensus_hash, &stacks_block.block_hash()); - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + let tip_burn_block_height = get_par_burn_block_height(peer.chainstate(), &tip_index_block); + + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); let stacker_addrs : Vec = vec![ key_to_stacks_addr(&alice).into(), @@ -1912,7 +1932,7 @@ pub mod test { ]; let min_ustx = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_stacking_minimum(sortdb, &tip_index_block)).unwrap(); - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); let total_stacked = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_total_ustx_stacked(sortdb, &tip_index_block, cur_reward_cycle)).unwrap(); eprintln!("\ntenure: {}\nreward cycle: {}\nmin-uSTX: {}\naddrs: {:?}\ntotal_liquid_ustx: {}\ntotal-stacked: {}\n", tenure_id, cur_reward_cycle, min_ustx, &reward_addrs, total_liquid_ustx, total_stacked); @@ -1929,11 +1949,11 @@ pub mod test { assert_eq!(min_ustx, total_liquid_ustx / 20000); // no reward addresses - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); assert_eq!(reward_addrs.len(), 0); // record the first reward cycle when Alice's tokens get stacked - reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); eprintln!("first reward cycle: {}\ncur reward cycle: {}\n", reward_cycle, cur_reward_cycle); assert!(reward_cycle > cur_reward_cycle); @@ -2126,11 +2146,13 @@ pub mod test { let total_liquid_ustx = get_liquid_ustx(&mut peer); let tip_index_block = StacksBlockHeader::make_index_block_hash(&consensus_hash, &stacks_block.block_hash()); - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + let tip_burn_block_height = get_par_burn_block_height(peer.chainstate(), &tip_index_block); + + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); let alice_balance = get_balance(&mut peer, &key_to_stacks_addr(&alice).into()); let min_ustx = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_stacking_minimum(sortdb, &tip_index_block)).unwrap(); - let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_reward_addresses(&burnchain, sortdb, &tip_index_block)).unwrap(); + let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| get_reward_addresses_with_par_tip(chainstate, &burnchain, sortdb, &tip_index_block)).unwrap(); let total_stacked = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_total_ustx_stacked(sortdb, &tip_index_block, cur_reward_cycle)).unwrap(); let total_stacked_next = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| chainstate.get_total_ustx_stacked(sortdb, &tip_index_block, cur_reward_cycle + 1)).unwrap(); @@ -2155,8 +2177,8 @@ pub mod test { assert_eq!(reward_addrs.len(), 0); // record the first reward cycle when Alice's tokens get stacked - alice_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); - let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, &tip_index_block).unwrap(); + alice_reward_cycle = 1 + peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); + let cur_reward_cycle = peer.chainstate().get_reward_cycle(&burnchain, tip_burn_block_height); eprintln!("\nalice reward cycle: {}\ncur reward cycle: {}\n", alice_reward_cycle, cur_reward_cycle); } diff --git a/src/chainstate/stacks/boot/pox-mainnet.clar b/src/chainstate/stacks/boot/pox-mainnet.clar new file mode 100644 index 0000000000..fc24b31aa7 --- /dev/null +++ b/src/chainstate/stacks/boot/pox-mainnet.clar @@ -0,0 +1,24 @@ +;; PoX mainnet constants +;; Min/max number of reward cycles uSTX can be locked for +(define-constant MIN-POX-REWARD-CYCLES u1) +(define-constant MAX-POX-REWARD-CYCLES u12) + +;; Default length of the PoX registration window, in burnchain blocks. +(define-constant REGISTRATION-WINDOW-LENGTH u250) + +;; Default length of the PoX reward cycle, in burnchain blocks. +(define-constant REWARD-CYCLE-LENGTH u1000) + +;; Valid values for burnchain address versions. +;; TODO: These correspond to address hash modes in Stacks 2.0, +;; but they should just be Bitcoin version bytes: we don't +;; need to constrain PoX recipient addresses the way that +;; we constrain other kinds of Bitcoin addresses +(define-constant ADDRESS-VERSION-P2PKH 0x00) +(define-constant ADDRESS-VERSION-P2SH 0x01) +(define-constant ADDRESS-VERSION-P2WPKH 0x02) +(define-constant ADDRESS-VERSION-P2WSH 0x03) + +;; Stacking thresholds +(define-constant STACKING-THRESHOLD-25 u20000) +(define-constant STACKING-THRESHOLD-100 u5000) diff --git a/src/chainstate/stacks/boot/pox-testnet.clar b/src/chainstate/stacks/boot/pox-testnet.clar new file mode 100644 index 0000000000..f8c7aeb81a --- /dev/null +++ b/src/chainstate/stacks/boot/pox-testnet.clar @@ -0,0 +1,25 @@ +;; PoX testnet constants +;; Min/max number of reward cycles uSTX can be locked for +(define-constant MIN-POX-REWARD-CYCLES u1) +(define-constant MAX-POX-REWARD-CYCLES u12) + +;; Default length of the PoX registration window, in burnchain blocks. +(define-constant REGISTRATION-WINDOW-LENGTH u30) + +;; Default length of the PoX reward cycle, in burnchain blocks. +(define-constant REWARD-CYCLE-LENGTH u120) + + +;; Valid values for burnchain address versions. +;; TODO: These correspond to address hash modes in Stacks 2.0, +;; but they should just be Bitcoin version bytes: we don't +;; need to constrain PoX recipient addresses the way that +;; we constrain other kinds of Bitcoin addresses +(define-constant ADDRESS-VERSION-P2PKH 0x00) +(define-constant ADDRESS-VERSION-P2SH 0x01) +(define-constant ADDRESS-VERSION-P2WPKH 0x02) +(define-constant ADDRESS-VERSION-P2WSH 0x03) + +;; Stacking thresholds +(define-constant STACKING-THRESHOLD-25 u480) +(define-constant STACKING-THRESHOLD-100 u120) diff --git a/src/chainstate/stacks/boot/pox.clar b/src/chainstate/stacks/boot/pox.clar index b8709bd23c..609a550fa7 100644 --- a/src/chainstate/stacks/boot/pox.clar +++ b/src/chainstate/stacks/boot/pox.clar @@ -229,36 +229,30 @@ (num-cycles uint) (amount-ustx uint) (i uint)))) - (let ( - (reward-cycle (+ (get first-reward-cycle params) (get i params))) - (i (get i params)) - ) + (let ((reward-cycle (+ (get first-reward-cycle params) (get i params))) + (i (get i params))) { pox-addr: (get pox-addr params), first-reward-cycle: (get first-reward-cycle params), num-cycles: (get num-cycles params), amount-ustx: (get amount-ustx params), i: (if (< i (get num-cycles params)) - (let ( - (total-ustx (get-total-ustx-stacked reward-cycle)) - ) - ;; record how many uSTX this pox-addr will stack for in the given reward cycle - (append-reward-cycle-pox-addr + (let ((total-ustx (get-total-ustx-stacked reward-cycle))) + ;; record how many uSTX this pox-addr will stack for in the given reward cycle + (append-reward-cycle-pox-addr (get pox-addr params) reward-cycle (get amount-ustx params)) - ;; update running total - (map-set reward-cycle-total-stacked - { reward-cycle: reward-cycle } - { total-ustx: (+ (get amount-ustx params) total-ustx) } - ) - - ;; updated _this_ reward cycle - (+ i u1)) + ;; update running total + (map-set reward-cycle-total-stacked + { reward-cycle: reward-cycle } + { total-ustx: (+ (get amount-ustx params) total-ustx) }) + + ;; updated _this_ reward cycle + (+ i u1)) (+ i u0)) - }) -) + })) ;; Add a PoX address to a given sequence of reward cycle lists. ;; A PoX address can be added to at most 12 consecutive cycles. @@ -341,33 +335,28 @@ (define-read-only (can-stacks-stx (amount-ustx uint) (pox-addr (tuple (version (buff 1)) (hashbytes (buff 20)))) (lock-period uint)) - (let ( - ;; this stacker's first reward cycle is the _next_ reward cycle - (first-reward-cycle (+ u1 (current-pox-reward-cycle))) - ) - ;; amount must be valid - (asserts! (> amount-ustx u0) + ;; this stacker's first reward cycle is the _next_ reward cycle + (let ((first-reward-cycle (+ u1 (current-pox-reward-cycle)))) + ;; amount must be valid + (asserts! (> amount-ustx u0) (err ERR_STACKING_INVALID_AMOUNT)) - ;; tx-sender principal must not have rejected in this upcoming reward cycle - (asserts! (is-none (get-pox-rejection tx-sender first-reward-cycle)) + ;; tx-sender principal must not have rejected in this upcoming reward cycle + (asserts! (is-none (get-pox-rejection tx-sender first-reward-cycle)) (err ERR_STACKING_ALREADY_REJECTED)) - ;; tx-sender principal must not be stacking - (asserts! (is-none (get-stacker-info tx-sender)) + ;; tx-sender principal must not be stacking + (asserts! (is-none (get-stacker-info tx-sender)) (err ERR_STACKING_ALREADY_STACKED)) - ;; the Stacker must have sufficient unlocked funds - (asserts! (>= (stx-get-balance tx-sender) amount-ustx) + ;; the Stacker must have sufficient unlocked funds + (asserts! (>= (stx-get-balance tx-sender) amount-ustx) (err ERR_STACKING_INSUFFICIENT_FUNDS)) - (ok { - amount-ustx: amount-ustx, - pox-addr: pox-addr, - first-reward-cycle: first-reward-cycle, - lock-period: lock-period - })) -) + (ok { amount-ustx: amount-ustx, + pox-addr: pox-addr, + first-reward-cycle: first-reward-cycle, + lock-period: lock-period }))) ;; Lock up some uSTX for stacking! Note that the given amount here is in micro-STX (uSTX). ;; The STX will be locked for the given number of reward cycles (lock-period). diff --git a/src/net/mod.rs b/src/net/mod.rs index b35d2d3c56..19b6aa1745 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -2010,7 +2010,7 @@ pub mod test { }, ExecutionCost::max_value()).unwrap(); - let mut coord = ChainsCoordinator::test_new(&burnchain, &test_path, OnChainRewardSetProvider::new()); + let mut coord = ChainsCoordinator::test_new(&burnchain, &test_path, OnChainRewardSetProvider()); coord.handle_new_burnchain_block().unwrap(); let mut stacks_node = TestStacksNode::from_chainstate(chainstate); @@ -2479,7 +2479,7 @@ pub mod test { let leader_key_op = stacks_node.add_key_register(&mut burn_block, &mut self.miner); // patch in reward set info - match get_next_recipients(&last_sortition_block, &mut stacks_node.chainstate, &mut sortdb, &self.config.burnchain, &OnChainRewardSetProvider::new()) { + match get_next_recipients(&last_sortition_block, &mut stacks_node.chainstate, &mut sortdb, &self.config.burnchain, &OnChainRewardSetProvider()) { Ok(recipients) => { block_commit_op.commit_outs = match recipients { Some(info) => vec![info.recipient.0], diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 174ce5ea7d..45f14600ec 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -774,7 +774,7 @@ impl InitializedNeonNode { // let's figure out the recipient set! let recipients = match get_next_recipients(&burn_block, chain_state, burn_db, - burnchain, &OnChainRewardSetProvider::new()) { + burnchain, &OnChainRewardSetProvider()) { Ok(x) => x, Err(e) => { error!("Failure fetching recipient set: {:?}", e); diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index 3ab127da47..640a5a6b84 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -1,12 +1,16 @@ use super::{make_stacks_transfer_mblock_only, SK_1, ADDR_4, to_addr, make_microblock, + make_contract_call, make_contract_publish, make_contract_publish_microblock_only}; use stacks::burnchains::{ Address, PublicKey }; +use stacks::util::secp256k1::Secp256k1PublicKey; use stacks::chainstate::stacks::{ StacksTransaction, StacksPrivateKey, StacksPublicKey, StacksAddress, db::StacksChainState, StacksBlock, StacksBlockHeader }; use stacks::chainstate::burn::ConsensusHash; use stacks::net::StacksMessageCodec; -use stacks::vm::types::PrincipalData; +use stacks::vm::types::{PrincipalData}; use stacks::vm::costs::ExecutionCost; +use stacks::vm::Value; +use stacks::vm::execute; use crate::{ neon, Config, Keychain, config::InitialBalance, BitcoinRegtestController, BurnchainController, @@ -27,6 +31,8 @@ fn neon_integration_test_conf() -> (Config, StacksAddress) { let keychain = Keychain::default(conf.node.seed.clone()); conf.node.miner = true; + conf.node.wait_time_for_microblocks = 500; + conf.burnchain.mode = "neon".into(); conf.burnchain.username = Some("neon-tester".into()); conf.burnchain.password = Some("neon-tester-pass".into()); @@ -559,3 +565,138 @@ fn size_check_integration_test() { channel.stop_chains_coordinator(); } + + +#[test] +#[ignore] +fn pox_integration_test() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return + } + + let spender_sk = StacksPrivateKey::new(); + let spender_addr: PrincipalData = to_addr(&spender_sk).into(); + + let pox_pubkey = Secp256k1PublicKey::from_hex("02f006a09b59979e2cb8449f58076152af6b124aa29b948a3714b8d5f15aa94ede").unwrap(); + let pox_pubkey_hash = bytes_to_hex(&Hash160::from_data(&pox_pubkey.to_bytes()).to_bytes().to_vec()); + + let (mut conf, miner_account) = neon_integration_test_conf(); + + let total_bal = 10_000_000_000; + let stacked_bal = 1_000_000_000; + + conf.initial_balances.push(InitialBalance { + address: spender_addr.clone(), + amount: total_bal, + }); + + let mut btcd_controller = BitcoinCoreController::new(conf.clone()); + btcd_controller.start_bitcoind().map_err(|_e| ()).expect("Failed starting bitcoind"); + + let mut btc_regtest_controller = BitcoinRegtestController::new(conf.clone(), None); + let http_origin = format!("http://{}", &conf.node.rpc_bind); + + btc_regtest_controller.bootstrap_chain(201); + + eprintln!("Chain bootstrapped..."); + + let mut run_loop = neon::RunLoop::new(conf); + let blocks_processed = run_loop.get_blocks_processed_arc(); + let client = reqwest::blocking::Client::new(); + let channel = run_loop.get_coordinator_channel().unwrap(); + + thread::spawn(move || { + run_loop.start(0) + }); + + // give the run loop some time to start up! + wait_for_runloop(&blocks_processed); + + // first block wakes up the run loop + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + // first block will hold our VRF registration + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + // second block will be the first mined Stacks block + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + // let's query the miner's account nonce: + + eprintln!("Miner account: {}", miner_account); + + let path = format!("{}/v2/accounts/{}?proof=0", + &http_origin, &miner_account); + eprintln!("Test: GET {}", path); + let res = client.get(&path).send().unwrap().json::().unwrap(); + assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 0); + assert_eq!(res.nonce, 1); + + // and our potential spenders: + + let path = format!("{}/v2/accounts/{}?proof=0", + &http_origin, spender_addr); + let res = client.get(&path).send().unwrap().json::().unwrap(); + assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), total_bal as u128); + assert_eq!(res.nonce, 0); + + let tx = make_contract_call(&spender_sk, 0, 243, &StacksAddress::from_string("ST000000000000000000002AMW42H").unwrap(), + "pox", "stack-stx", &[Value::UInt(stacked_bal), + execute(&format!("{{ hashbytes: 0x{}, version: 0x00 }}", pox_pubkey_hash)).unwrap().unwrap(), + Value::UInt(3)]); + + // okay, let's push that stacking transaction! + let path = format!("{}/v2/transactions", &http_origin); + let res = client.post(&path) + .header("Content-Type", "application/octet-stream") + .body(tx.clone()) + .send() + .unwrap(); + eprintln!("{:#?}", res); + if res.status().is_success() { + let res: String = res + .json() + .unwrap(); + assert_eq!(res, StacksTransaction::consensus_deserialize(&mut &tx[..]).unwrap().txid().to_string()); + } else { + eprintln!("{}", res.text().unwrap()); + panic!(""); + } + + // now let's mine a couple blocks, and then check the sender's nonce. + // at the end of mining three blocks, there should be _one_ transaction from the microblock + // only set that got mined (since the block before this one was empty, a microblock can + // be added), + // and _two_ transactions from the two anchor blocks that got mined (and processed) + // + // this one wakes up our node, so that it'll mine a microblock _and_ an anchor block. + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + // this one will contain the sortition from above anchor block, + // which *should* have also confirmed the microblock. + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + // let's figure out how many micro-only and anchor-only txs got accepted + // by examining our account nonces: + let path = format!("{}/v2/accounts/{}?proof=0", + &http_origin, spender_addr); + let res = client.get(&path).send().unwrap().json::().unwrap(); + if res.nonce != 1 { + assert_eq!(res.nonce, 1, "Spender address nonce should be 1"); + } + + // now let's mine until the next reward cycle starts ... + for _i in 0..35 { + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + } + + // we should have received a Bitcoin commitment + let utxos = btc_regtest_controller.get_utxos( + &pox_pubkey, 1).expect("Should have been able to retrieve UTXOs for PoX recipient"); + + eprintln!("Got UTXOs: {}", utxos.len()); + assert!(utxos.len() > 0, "Should have received an output during PoX"); + + channel.stop_chains_coordinator(); +}