From 4703a38c77f2773af5852226dcbefadd78fd44fc Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Sat, 9 Mar 2019 18:51:39 -0500 Subject: [PATCH] fix sortition algorithm to decrement burn quota only once it has been met over several blocks (#931) --- src/chainstate/burn/sortition.rs | 59 ++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/chainstate/burn/sortition.rs b/src/chainstate/burn/sortition.rs index 2d27ee17a..0d61fe6c2 100644 --- a/src/chainstate/burn/sortition.rs +++ b/src/chainstate/burn/sortition.rs @@ -32,8 +32,8 @@ use chainstate::burn::{ BurnchainHeaderHash }; +use chainstate::Error as db_error; use chainstate::burn::db::burndb::BurnDB; -use chainstate::burn::db::Error as db_error; use chainstate::burn::operations::BlockstackOperationType; use chainstate::burn::operations::leader_key_register::LeaderKeyRegisterOp; use chainstate::burn::operations::leader_block_commit::LeaderBlockCommitOp; @@ -185,7 +185,8 @@ impl BlockSnapshot { /// All of this is rolled into the BlockSnapshot struct. /// /// Call this *after* you store all of the block's transactions to the burn db. - pub fn make_snapshot<'a, A, K>(tx: &mut Transaction<'a>, burnchain: &Burnchain, first_block_height: u64, block_height: u64, block_hash: &BurnchainHeaderHash, burn_dist: &Vec>) -> Result + pub fn make_snapshot<'a, A, K>(tx: &mut Transaction<'a>, burnchain: &Burnchain, first_block_height: u64, + block_height: u64, block_hash: &BurnchainHeaderHash, parent_block_hash: &BurnchainHeaderHash, burn_dist: &Vec>) -> Result where A: Address, K: PublicKey @@ -204,43 +205,51 @@ impl BlockSnapshot { let last_burn_quota; let last_sortition_hash; let last_burn_total; + let last_sortition_burn_total; + let last_sortition; let next_sortition; + let next_sortition_burn_total; let next_burn_quota; let winning_block_txid; let winning_block_burn_hash; let last_block_snapshot_opt = BurnDB::::get_block_snapshot(tx, block_height - 1)?; - match last_block_snapshot_opt { Some(prev_snapshot) => { last_burn_total = prev_snapshot.total_burn; + last_sortition_burn_total = prev_snapshot.sortition_burn; last_burn_quota = prev_snapshot.burn_quota; last_sortition_hash = prev_snapshot.sortition_hash; + last_sortition = prev_snapshot.sortition; }, - None => { + None => { + // initial block snapshot last_burn_total = 0; + last_sortition_burn_total = 0; last_burn_quota = 0; last_sortition_hash = SortitionHash::initial(); + last_sortition = true; } }; - + + let sortition_burn_total = BlockSnapshot::burn_total_checked(block_burn_total, last_sortition_burn_total); let next_sortition_hash = last_sortition_hash.mix_burn_header(block_hash); // did we burn enough? - // TODO do we have to be sticklers about the burn quota? can't we just leave it alone if it's "close enough" to the last burn quota? - if block_burn_total >= last_burn_quota { + if sortition_burn_total >= last_burn_quota { // can do a sortition in this block // Try to pick a next block. let winning_block_opt : Option> = BlockSnapshot::select_winning_block(tx, block_height, &next_sortition_hash, burn_dist)?; match winning_block_opt { None => { - // we burned enough for a sortition, but no winner was picked + // we burned enough for a sortition, but no winner was picked. + // can happen if the burn quota is 0. next_sortition = false; winning_block_txid = Txid::from_bytes(&[0u8; 32]).unwrap(); winning_block_burn_hash = BurnchainHeaderHash::from_bytes(&[0u8; 32]).unwrap(); - info!("SORTITION: Burn quota met ({}), but NO BLOCK CHOSEN", block_burn_total); + info!("SORTITION({}): Burn quota met ({}), but NO BLOCK CHOSEN", block_height, block_burn_total); }, Some(winning_block) => { // we burned enough for a sortition, and a winner was picked! @@ -248,22 +257,40 @@ impl BlockSnapshot { winning_block_txid = winning_block.txid.clone(); winning_block_burn_hash = winning_block.burn_header_hash.clone(); - info!("SORTITION: Burn quota met ({}). WINNER is {} (at {})", block_burn_total, &winning_block.block_header_hash.to_hex(), &winning_block_txid.to_hex()); + info!("SORTITION({}): Burn quota met ({}). WINNER BLOCK is {}", block_height, block_burn_total, &winning_block.block_header_hash.to_hex()); } }; - // either way, we burned enough. Increaes the quota for the next block. - next_burn_quota = BlockSnapshot::burn_quota_inc_checked(last_burn_quota, burnchain.burn_quota.inc); + // either way, we burned enough that we can alter the burn quota. + // -- we INCREMENT if the burn quota was met in the _last_ block + // -- we either did a sortition last block, OR + // -- no blocks were committed (so no sortition) and the burn quota was 0 + // -- we DECREMENT otherwise + if last_sortition || (last_sortition_burn_total == 0 && last_burn_quota == 0) { + debug!("SORTITION({}): burned enough for sortition last block, so QUOTA INCREMENT", block_height); + next_burn_quota = BlockSnapshot::burn_quota_inc_checked(last_burn_quota, burnchain.burn_quota.inc); + } + else { + debug!("SORTITION({}): did no burn enough for sortition last block, so QUOTA DECREASE", block_height); + next_burn_quota = BlockSnapshot::burn_quota_dec_checked(last_burn_quota, burnchain.burn_quota.dec_num, burnchain.burn_quota.dec_den); + } + + // we will have burned 0 towards the next sortition. + next_sortition_burn_total = 0; } else { - // cannot do a sortition in this block. Decrease the quota. + // cannot do a sortition in this block, but don't change the burn quota -- it will only + // be decremented once we meet it! Instead, keep a running total of how much we burned + // since the last sortition, and use that in the next epoch to see if we can adjust the + // burn quota. next_sortition = false; - next_burn_quota = BlockSnapshot::burn_quota_dec_checked(last_burn_quota, burnchain.burn_quota.dec_num, burnchain.burn_quota.dec_den); + next_burn_quota = last_burn_quota; + next_sortition_burn_total = sortition_burn_total; winning_block_txid = Txid::from_bytes(&[0u8; 32]).unwrap(); winning_block_burn_hash = BurnchainHeaderHash::from_bytes(&[0u8; 32]).unwrap(); - info!("SORTITION: Burn quota NOT MET ({} < {})", block_burn_total, last_burn_quota); + info!("SORTITION({}): Burn quota not met ({} < {}), so NO QUOTA CHANGE", block_height, block_burn_total, last_burn_quota); } let next_burn_total = BlockSnapshot::burn_total_checked(last_burn_total, block_burn_total); @@ -274,9 +301,11 @@ impl BlockSnapshot { Ok(BlockSnapshot { block_height: block_height, burn_header_hash: block_hash.clone(), + parent_burn_header_hash: parent_block_hash.clone(), consensus_hash: next_ch, ops_hash: next_ops_hash, total_burn: next_burn_total, + sortition_burn: next_sortition_burn_total, burn_quota: next_burn_quota, sortition: next_sortition, sortition_hash: next_sortition_hash,