stacks-network / stacks-core

The Stacks blockchain implementation
https://docs.stacks.co
GNU General Public License v3.0
3k stars 660 forks source link

Signer: Tracking unfortunately timed first block in tenure vs. bitcoin blocks #4844

Open kantai opened 1 month ago

kantai commented 1 month ago

One of the issues that signers need to resolve is an issue of poorly timed tenure and bitcoin blocks.

Basically, if the current tenure's first block is produced and signed near when the next bitcoin block is produced, the subsequent miner's block commit cannot commit to that tenure. Signers, in good behavior, should allow the subsequent miner to continue producing blocks (reorging the prior miner's blocks)-- this encourages miners to try to get their first tenure blocks produced as quickly as possible to avoid this situation.

Note: this shouldn't be a 3.0-must. It does not require consensus changes, and in these circumstances, if the signers don't implement this feature, the chain would continue to operate.

kantai commented 5 days ago

Important note here: this logic can't apply to would-be PoX anchor blocks. If the signer set approves a block which would become an anchor block, they must force subsequent miners to build off of it.

jcnelson commented 5 days ago

Important note here: this logic can't apply to would-be PoX anchor blocks. If the signer set approves a block which would become an anchor block, they must force subsequent miners to build off of it.

I'm not sure this is necessary after #4930 merges. There can be multiple PoX anchor blocks formed in the prepare phase, but signers must eventually converge on a single chain history that confirms a single one of them. The node no longer cares if there are multiple forks.

kantai commented 5 days ago

I'm not sure this is necessary after https://github.com/stacks-network/stacks-core/pull/4930 merges. There can be multiple PoX anchor blocks formed in the prepare phase, but signers must eventually converge on a single chain history that confirms a single one of them. The node no longer cares if there are multiple forks.

But the node has to load the PoX anchor block data when evaluating sortitions. What anchor data is loaded? I think even in #4930, sortition evaluation does not use a stacks tip to load this information, its instead stored in the sortition db.

jcnelson commented 5 days ago

No, #4390 uses the stacks tip now in load_nakamoto_reward_set(). The test battery in #4390 create multiple PoX anchor blocks as part of the new systemic block malleablization in TestPeer and make_nakamoto_peers_from_invs()

kantai commented 5 days ago

Okay -- so the behavior is that the canonical tip at the time of the first block in a reward cycle is used to index into the prepare phase tenures (this is what it looks like from the call to evaluate_sortition() from the nakamoto coordinator)?

jcnelson commented 5 days ago

Right -- in #4390, the PoX reward set is chosen by whatever happens to be the canonical Stacks tip at the start of the reward phase. Assuming that at least 30% of signers are honest, there can never be two distinct reward sets for a given reward cycle. This is noted in the code snippet that precedes the call to evaluate_sortition():

                // we're at the end of the prepare phase, so we'd better have obtained the reward
                // cycle info of we must block.
                // NOTE(safety): the reason it's safe to use the local best stacks tip here is
                // because as long as at least 30% of the signers are honest, there's no way there
                // can be two or more distinct reward sets calculated for a reward cycle.  Due to
                // signature malleability, there can be multiple unconfirmed siblings at a given
                // height H, but at height H+1, exactly one of those siblings will be canonical,
                // and will remain canonical with respect to its tenure's Bitcoin fork forever.
                // Here, we're loading a reward set calculated between H and H+99 from H+100, where
                // H is the start of the prepare phase.  So if we get any reward set from our
                // canonical tip, it's guaranteed to be the canonical one.
                let canonical_sortition_tip = self.canonical_sortition_tip.clone().unwrap_or(
                    // should be unreachable
                    SortitionDB::get_canonical_burn_chain_tip(&self.sortition_db.conn())?
                        .sortition_id,
                );

                let Some(local_best_nakamoto_tip) = self
                    .sortition_db
                    .index_handle(&canonical_sortition_tip)
                    .get_nakamoto_tip_block_id()?
                else {
                    debug!("No Nakamoto blocks processed yet, so no reward cycle known for this next reward cycle");
                    return Ok(false);
                };

                let reward_cycle_info = self.get_nakamoto_reward_cycle_info(
                    header.block_height,
                    &local_best_nakamoto_tip,
                )?;

If you'd like, I can make this safety guarantee stronger by additionally requiring that the PoX reward set is affirmed by miners' block-commits.

kantai commented 5 days ago

No -- I think this is fine. The signer sets would need to really go off the rails for this to become a problem.