Nakamoto v1 #3892
Replies: 11 comments 14 replies
-
Thanks for all of your hard work on this! I've compiled some questions (a batch for each section of the Specification) that I will post below. The intention is to just illicit a dialog. Hopefully, this can play a small part in scoping out more concrete deliverables / milestones. 😅 |
Beta Was this translation helpful? Give feedback.
-
Chain Structure:
|
Beta Was this translation helpful? Give feedback.
-
Chain Synchronization:
|
Beta Was this translation helpful? Give feedback.
-
Block Structure:
|
Beta Was this translation helpful? Give feedback.
-
Block Storage:
|
Beta Was this translation helpful? Give feedback.
-
Tenure Changes:
|
Beta Was this translation helpful? Give feedback.
-
Dynamic Runtime Budgets:
|
Beta Was this translation helpful? Give feedback.
-
New Block Validation Rules:
|
Beta Was this translation helpful? Give feedback.
-
Block Reward Distribution and MEV:
|
Beta Was this translation helpful? Give feedback.
-
Bitcoin Fork Recovery:
|
Beta Was this translation helpful? Give feedback.
-
Helium: Mockamoto v1
|
Beta Was this translation helpful? Give feedback.
-
Nakamoto v1
Abstract
This document describes a moderate set of modifications to Stacks 2.4 that would implement the user-facing functionality promised by Nakamoto. This proposal’s complexity lies somewhere between the “aggressive” Nakamoto proposal which came out of the August hackathon, and the “conservative” Nakamoto proposed by Jude in late August. We estimate that this proposal would take between 50%-60% of the time required by the aggressive proposal.
Introduction
The Nakamoto release will add the following user-facing changes to the Stacks blockchain.
This priority ranking is the conclusion drawn from many discussions between core devs and Stacks builders. A fast blockchain with Bitcoin finality helps all Stacks projects – especially Stacks DeFi projects including sBTC – and thus should be the core devs’ top priority.
To do this, the system must confirm transactions fast enough that the risk of a price-change to the assets being traded is minimized. Moreover, it must ensure that it is at least as difficult to reorg the Stacks blockchain as it is to reorg Bitcoin. Finally, the system must resist Bitcoin miner MEV, which today enables Bitcoin miners to avoid competing with Stacks miners and win Stacks blocks for a de minimis PoX payment.
To achieve fast block times, this proposal advocates for decoupling Stacks tenure changes from Bitcoin block arrivals. A miner may create many Stacks blocks per Bitcoin block, and the next miner must confirm all of them. There are no more microblocks or Bitcoin-anchored blocks; instead, a new block type will be created.
To speed implementation, this proposal advocates for retaining Stacks miners as block producers, which execute a cryptographic sortition every Bitcoin block to decide which competing miner becomes the sole block producer until the next tenure change (triggered by a Bitcoin block arrival). The implementation would retain the
SortitionDB
system in the current Stacks node, with a few modifications described in this document. This differs from the original Nakamoto proposal, which called for a network of block producers to collectively mine blocks for a 10-Bitcoin-block tenure, and thus would have required substantial additional implementation work to achieve the same ends that this proposal describes.To ensure that no forks arise, this proposal advocates for extending the StacksChainState system in order to ensure miners always know the canonical Stacks chain tip, and thus can be prevented from ever building atop any other block. This is achieved by decoupling Stacks tenure changes from Bitcoin block arrivals. In the proposed system, a cryptographic sortition only selects a new miner; it does not give them the power to orphan confirmed transactions as it does today in Stacks. Instead, a cryptographic sortition causes the Stackers to carry out a tenure change by (a) agreeing on a “last-signed” block from the current producer, and (b) agreeing to only sign blocks from the new producer which descend from this last-signed block. Thus, Stackers police miner behavior – Stackers prevent miners from mining forks during their tenure, and ensure that they begin their tenures by building atop the canonical chain tip.
To ensure Bitcoin finality, Stacks miners commit to the indexed block hash (a
StacksBlockId
) of the first block produced by the last Stacks miner in their block-commit transactions on Bitcoin. This anchors the Stacks chain history to Bitcoin up to the start of the previous miner’s tenure, and it anchored causally-dependent Bitcoin state to Stacks. This ensures that the history of tenure changes up to (but excluding) the most recent change is written to Bitcoin, and thus as difficult to erase as any other Bitcoin transaction. It further ensures that a node with an up-to-date copy of the Stacks chain state can identify which Stacks blocks are affected by a Bitcoin reorg, so it can begin recovering the affected Stacks transactions.This proposal advocates for a Stacker-driven approach to Bitcoin reorg recovery. If a Bitcoin reorg happens, Stackers identify the sequence of now-orphaned Stacks transactions that miners must replay in order to preserve them in the Stacks blockchain across the fork. This can be implemented separately because it is not consensus-critical, and can be deployed incrementally because it only requires sufficient numbers of Stackers to cooperate.
Finally, this proposal advocates for adopting a Bitcoin MEV solution proposed by Jesse Soslow. These solutions have already been vetted for application with the current Stacks mining system, and are easily extended to work in Nakamoto v1.
The bulk of the work in this proposal will be in replacing Stacks blocks and microblocks with Nakamoto Stacks blocks. However, this proposal calls for a design that minimizes the amount of change that would need to be made in
StacksChainState
andPeerNetwork
in order to keep relevant and reapply as many lessons learned from the Stacks 2.x system as possible. These lessons learned have accrued in the codebase over the past few years, and are based on real-world operational experiences. By keeping the overall data schemas and processing algorithms as close to the current system as possible, this proposal minimizes the risks of shipping too much untested code and shipping too late.Specification
Chain Structure
As with the system today, Bitcoin finality is achieved through a
block-commit
transaction on Bitcoin. A Stacks miner sends one of these transactions in order to submit their candidacy to become the next block producer after the next tenure change. This proposal advocates for keepingblock-commit
transactions’ syntax, but calls for altering its semantics in one key way: theblock_header_hash
field is no longer the hash of a proposed Stacks block (i.e. itsBlockHeaderHash
), but instead is the index block hash (i.e.StacksBlockId
) of the previous miner’s first-ever produced block (Figure 1).Figure 1: Overview of the relationship between Bitcoin blocks (and sortitions), Stacks blocks, and the inventory bitmaps exchanged by Stacks nodes. Each winning block-commit’s
BlockHeaderHash
field no longer refers to a new Stacks block to be appended, but instead contains the hash of the very first Stacks block in the previous tenure. These tenure start blocks each contain aTenureChange
transaction (not shown), which, among other things, identifies the number of Stacks blocks produced since the last valid start block (numbers in dark orange circles).The reason for this change is to both preserve Bitcoin finality and to facilitate initial block downloads without significantly altering the inventory state synchronization and block downloader state machines. Bitcoin finality is preserved because at every Bitcoin block N+1, the state of the Stacks chain as of the start of tenure N is written to Bitcoin. Even if at a future date all of the former Stackers’ signing keys were compromised, they would be unable to rewrite Stacks history for tenure N without rewriting Bitcoin history back to sortition N+1.
Chain Synchronization
This chain structure is similar enough to the current system that the inter-node synchronization procedure remains roughly the same as it is today, meaning all the lessons learned in building out inter-node synchronization still mostly apply. At a high-level, nodes would do the following when they have all of the Stacks chain state up to reward cycle R:
Download and process all sortitions in reward cycle R+1. This happens largely the same way here as it does today – the node downloads the Bitcoin blocks, identifies the valid block-commits within them, and runs sortition on each Bitcoin block’s block-commits to choose a winner. It does this on a reward-cycle by reward-cycle basis, since it must first process the PoX anchor block for the next reward cycle before it can validate the next reward cycle’s block-commits.
For each sortition N+1, go and fetch the start block of tenure N if sortition N+1 has a valid block-commit and the inventory bit for tenure N is 1. This requires minimal changes to the block inventory and block downloader state-machines. Each neighbor node serves the node an inventory bitmap of all tenure start blocks they have available, which enables the node to identify neighbors that have the blocks they need. Unlike today, only the inventory bitmap of tenure start blocks is needed; there is no longer a need for a PoX anchor block bitmap nor a microblock bitmap.
For each start block of tenure N, identify the number of blocks between this start block and the last prior tenure committed to by a winning block-commit (note that this may not always be tenure N-1, per figure 1). This information is identified by a special
TenureChange
transaction that must be included in each tenure start block (see next section). So, the act of fetching the tenure-start blocks in step 2 is the act of obtaining theseTenureChange
transactions.Download and validate the continuity of each block sequence between consecutive block commits. Now that the node knows the number of blocks between two consecutive winning block-commits, as well as the hashes of the first and last block in this sequence, the node can do this in a bound amount of space and time. There is no risk of a malicious node serving an endless stream of well-formed but invalid blocks to a booting-up node, because the booting-up node knows exactly how many blocks to expect and knows what hashes they must have.
Concurrently processes newly-downloaded blocks in reward cycle R+1 to build up its tenure of the blockchain.
Repeat once the PoX anchor block for R+2 has been downloaded and processed
This bootup procedure is amenable to starting Nakamoto from Stacks 2.x, as long as it happens on a reward cycle boundary.
When the node has synchronized to the latest reward cycle, it would run this algorithm to discover new tenures within the current reward cycle until it reaches the chain tip. Once it has processed all blocks as of the last sortition, it continues to keep pace with the current miner by receiving new blocks broadcasted through the peer network by Stackers once they accept the next Stacks block.
Block Structure
Nakamoto Stacks blocks will have a different wire format than Stacks blocks and microblocks today. In particular, the header will contain:
StacksBlockId
of the parent Stacks blockAs before, the Stacks blockchain will continue to calculate a consensus hash for each sortition. The
StacksBlockId
of a Stacks block would simply be the SHA512/256 of the block’s associated sortition’s consensus hash and the hash of the block header.Absent from this header is the VRF proof. Instead, this information will be put into the Nakamoto
Coinbase
transaction.Block Storage
The Nakamoto system is expected to produce a Stacks block on average once every five seconds. This is 120x the rate of the current system. Because of the high volume of blocks, the block storage system in Stacks will need to be upgraded to store blocks as rows in a sqlite database instead of files, lest the host run out of i-nodes.
The block data will be stored in a wholly separate database from the chainstate, so that storing newly-downloaded blocks and loading them up again for processing never results in lock-contention arising from block-processing. This is because block-processing opens a transaction for the duration of the processing time, which would prevent concurrent storage of downloaded blocks if they lived in the same database.
The primary key of each Stacks block row will be its
StacksBlockId
. Each row would need the following indexed columns. Note that this schema is subject to change if something better is thought of.StacksBlockId
of its parentStacksBlockId
of the tenure-start block.StacksBlockId
of the highest tenure-start block for which there is a valid block-commit. For example, all blocks in tenures N+1 and N+2 have theStacksBlockId
of the first block in tenure N+1 as this column’s value.In addition, the following non-indexed columns will be needed:
Given this data, it will be possible to efficiently do the following:
StacksBlockId
. This is needed for step 2 of the chain synchronization algorithm.StreamCursor
variant that can incrementally load up all Stacks blocks between the tenure start blocks any tenures N and N + k (k > 0). This is needed for step 4 in the chain synchronization algorithm.Tenure Changes
As mentioned earlier, the reason why continuing to rely on microblocks is not good enough for our goals in Nakamoto is because in the current system (and in Conservative Nakamoto), microblocks may be orphaned. This is fundamentally unavoidable because the current Stacks miner can continue to produce microblocks well after the next miner produces a Stacks block. This happens due to two data races. First, Stacks miners race the current miner to confirm their microblock stream – the current miner produces microblocks on top of the stream that the next miner confirms via a Bitcoin transaction. Second, the Bitcoin and Stacks peer network block propagation races the current miner to tell it to stop making microblocks – the current miner can continue to produce microblocks well after the next Stacks block is announced (which would not confirm them).
The reason this happens is because system _tenure change_s in Stacks today are triggered by a new Stacks block being discovered, which may not confirm all of the previous miner’s produced microblocks. This violates the promise Nakamoto makes to users, which is that their transactions, once confirmed, remain confirmed forever so long as Bitcoin does not fork.
This is a problem that would need to be addressed in all proposed Nakamoto designs. This design calls for a Bitcoin block arrival to trigger a tenure change, and calls for Stackers to execute the tenure change (Figure 2).
Figure 2: Tenure change overview. When a new Bitcoin block arrives, Stackers begin the process of deciding the last block they will sign from miner N. When they reach quorum, they announce this data in a FROST-signed specially-crafted data payload in their StackerDB message. This information serves as a record of the tenure change, and must be incorporated in miner N+1’s tenure-start block. In other words, miner N+1 cannot begin producing Stacks blocks until Stackers inform it of block X – the block from miner N that it must build atop. Stacks-on-Bitcoin transactions are applied by miner N+1 for all Bitcoin blocks in sortitions N and earlier when its tenure begins.
A Stacks miner produces blocks until Stackers declare that they will no longer sign their blocks, or until they run out of execution budget – whichever comes first. In the former case, Stackers will encode the tenure change as a novel Stacks transaction, called a
TenureChange
transaction, which encodes the following data:StacksBlockId
of the last block from miner N that they will have processed (this is “X” in figure 2)When produced, the
TenureChange
transaction will be stored to the Stacker’s StackerDB instance. Miners N and N+1 will both monitor this StackerDB to observe its arrival. Once miner N sees it, it ceases producing blocks. Once miner N+1 sees it, it begins its tenure by doing the following:TenureChange
transaction as its first transaction.If miner N does not see the
TenureChange
transaction in the StackerDB, then it will keep producing blocks. However, Stackers will not sign them, so as far as the rest of the network is concerned, these blocks never materialized. If miner N+1 does not see theTenureChange
transaction, it does not start mining; a delay in obtaining theTenureChange
transaction can lead to a period of chain inactivity. This is mitigated by the fact that the miner knows the set of Stackers’ reachable Stacks nodes in advance (as part of StackerDB), so it can directly query them if the data does not arrive at its node in a timely fashion.The Nakamoto Stacks blockchain has exactly one
TenureChange
transaction for each and every tenure, even if the tenure is empty.Empty Tenures
Sometimes, a tenure can be empty. The miner and Stackers may fail to sign any Stacks block. This can happen for banal reasons, such as (but not limited to):
In all cases, the tenure would be empty (Figure 3).
Figure 3: Tenures N+1 and N+2 are empty. Stackers recover from this by requiring the next non-empty tenure to include the empty tenures’
TenureChange
transactions, to prove that these tenures were in fact empty and no blocks were signed for them. Because the tenures are empty, the inventory bitmap would mark them as 0’s – there’s no block data to fetch.To recover from this, Stackers would still produce
TenureChange
transactions for the empty tenures, and require the start block of the first non-empty tenure to include them. In other words, Stackers always produce aTenureChange
transaction for each tenure; it just might not be included by the tenure’s sortition-selected miner.Stacker tenure Changes
A miner tenure change happens every Bitcoin block. In addition, there are Stacker tenure changes, which happen once every 2100 Bitcoin blocks (one reward cycle). In a Stacker tenure change, a new set of Stackers are selected by the PoX anchor block (see SIP-007).
Because Stacks no longer forks, the PoX anchor block is always known 100 Bitcoin blocks prior to the start of the next reward cycle: it is simply the the start block of the 2000th tenure in the current reward cycle.
The PoX anchor block identifies the next Stackers. They have 100 Bitcoin blocks to prepare for signing Stacks blocks. Within this amount of time, the new Stackers would complete a FROST DKG for signing blocks (among other things, such as the sBTC peg wallet hand-off). The PoX contract will require Stackers to register their block-signing keys when they stack or delegate-stack STX, so the entire network knows enough information to validate their FROST Schnorr signatures on blocks.
PoX Failure
In the event that PoX does not activate, the chain halts. If there are no stackers, then block production cannot happen.
Changes to PoX
To support tenure changes, the
.pox-4
contract would be altered to enable the following:stack-stx
and optionallydelegate-stx
.delegate-stx
, Stackers may instead elect for the delegate to supply the signing key.pox-4
implements the StackerDB trait. Each Stacker receives a number of slots equal to the number of reward slots, into which they will write their FROST cryptographic state. This enables Stackers to both perform DKG as well as signing rounds.Dynamic Runtime Budgets
To support fast blocks, it will be necessary to ship Nakamoto v1 with a means of ensuring that no matter how long it takes for the next tenure change to execute, the current miner will be able to keep producing blocks at a fixed cadence.
Previous Nakamoto proposals advocated for using a verifiable delay function (VDF) for allowing block producers to prove that they have waited for the expected tenure window in order to earn additional runtime budget allocations. However, there is a simpler way to do this in Nakamoto v1: Stackers themselves can simply vote to grant the current block producer additional tenure runtime budgets.
To achieve this, Stackers each keep track of the elapsed wall-clock time since the start of the tenure. Once the expected tenure time has passed (e.g. 10 minutes), they vote to grant the miner an additional tenure execution budget. This is achieved by having Stackers collectively produce a
TenureExtension
Stacks transaction, which the miner would include into their block stream.When a node validates a block stream today, it keeps track of how much runtime budget the transactions have consumed since the tenure’s start. In Nakamoto, if it sees a valid
TenureExtension
transaction, then the tenure’s so-far-consumed execution budget would simply reset.New Block Validation Rules
Under Nakamoto, a block is valid if and only if the following are true:
TenureChange
transactions for any tenures not represented by Bitcoin block-commits, and they are present in order by sortition.TenureChange
transaction is aCoinbase
.TenureExtension
transactions mined in this tenure.PoisonMicroblock
transaction variant is not supported anymoreBlock Reward Distribution and MEV
The Nakamoto system will use the Assumed Total Commitment with Carryforward (ATC-C) MEV mitigation strategy described in this document to allocate block rewards to miners. Unlike Stacks today, there is no 40/60 fee split between two consecutive miners. Each miner nominally receives the entire coinbase and transaction fees before the MEV mitigation is applied.
In the ATC-C algorithm, Nakamoto would use the document’s recommended assumed total commitment function: the median total PoX spend across all miners for the past 5 Bitcoin blocks. It would additionally use the document’s recommended carryforward function for missed sortitions’ coinbases: the coinbase for a Bitcoin block without a sortition would be available to winning miners across the next five tenures. That is, if a miner whose tenure begins during the next five tenure-changes manages to produce a Stacks block with a
Coinbase
, then they receive a 20% of the coinbase that was lost.The reason ATC (and ATC-C) were not considered as viable anti-MEV strategies before is because a decrease in the PoX total spend can lead to a Bitcoin block with no sortition. This is a deliberate design choice in ATC-C, because it has the effect of lowering the expected return of MEV mining. In ATC-C, the probability of a miner winning a sortition is equal to (i.e. no longer proportional to) the miner’s BTC spend, divided by the maximum of either the assumed total commit (median total BTC spend in the last 5 blocks) or the total BTC spend in this Bitcoin block. This means that the sum of each miners’ winning probabilities is not guaranteed to be 1. The system deals with this by creating a virtual “null” miner that participates in each sortition, such that its probability of the null miner winning is 1 - sum(probabilities-of-all-other-miners). If the null miner wins, then the sortition is treated as empty.
While the existence of a null miner was a liveness concern in Stacks today, it is not a concern in Nakamoto. If the null miner wins tenure N, then the last non-null miner continues to produce blocks in tenure N. They receive transaction fees, but no coinbase for N.
Bitcoin Fork Recovery
The Bitcoin chain can fork. This is a problem for Nakamoto, because Stacks transactions can be causally dependent on the now-orphaned Bitcoin state. For example, any Stacks transaction that uses
(get-burn-block-info?)
may have a different execution outcome if evaluated after the Bitcoin block state from which it was mined no longer exists.Earlier proposals had called for re-mining all Stacks transactions in the same order in which they originally appeared in the chain history, and treating the failure of re-mined but now invalid transactions as runtime errors (meaning, including them in a block will not invalidate the block). While this could be made to work, it would be very time-consuming for questionable benefit. Namely, the system would need to recognize transactions that would invalidate a block if included (e.g. they cannot pay their transaction fee, or they exceed the block’s runtime budget), and continue to process them as runtime errors if and only if Stackers indicated that this particular transaction was included to recover from a Bitcoin fork. To achieve this, the Nakamoto chain would need to require miners and Stackers to prove that the state which made these transactions previously-valid actually existed, but on an orphaned Bitcoin block.
While this could be done, we do not believe that we have time to do so. Instead, this proposal calls for dropping any previously-mined but now-invalid Stacks transactions from the Stacks chain history, but re-mining the set of Stacks transactions which remain valid across the Bitcoin fork in the same order in which they were previously mined. That is, transactions that were not causally dependent on lost Bitcoin state would remain confirmed on Stacks, in the same (relative) order in which they were previously mined.
This guarantee is much simpler to implement, and can even be deferred until after the Nakamoto v1 hard fork. To do so, Stackers would first observe that a Bitcoin fork has occurred, and vote on which Bitcoin block(s) were orphaned (even if it means sending the orphaned data to each other, since not all Stacks nodes may have seen it). Once Stackers agree on the sequence of orphaned Bitcoin blocks, they identify which Stacks blocks would be affected. From there, they each replay the affected Stacks blocks’ transactions in the same order, and in doing so, identify which transactions are now invalid and which ones remain valid. Once they have this subsequence of still-valid transactions, they advertise it to miners, and only sign off on Stacks blocks that include a prefix of this subsequence that has not yet been re-mined (ignoring
Coinbase
orTenureChange
transactions). This way, the Stacks miners are compelled to replay the still-valid Stacks transactions in their same relative order, thereby meeting this guarantee.Importantly, this transaction replay feature is directed exclusively by Stacker and miner policy logic. It is not consensus-critical, and in fact cannot be because not all miners or Stackers may have even seen the orphaned Bitcoin state (which precludes them from independently identifying replay transactions; they must instead work together to do so off-chain). Therefore, this feature’s implementation can be deferred until after the Nakamoto v1 hard fork. Shipping the implementation itself is not a hard fork either; it can instead be incrementally rolled out.
Implementation Milestones
Helium: Mockamoto v1
Goal: bring the Nakamoto node online.
This milestone would see the following functionality implemented:
StacksBlockId
of the last tenure’s first block.TenureChange
transaction is synthesized by the miner, but is present in the block stream.Neon: Mockamoto v2
Goal: Bring the Nakamoto stacker daemon online
This milestone would see the following additional functionality implemented:
TenureChange
transactions, which it shares with the miner via StackerDB.Argon: Mockamoto v3
Goal: Bring the Nakamoto network state machines online
This milestone would see the following additional functionality implemented:
TenureChange
transactions to execute tenure changes.Krypton: Mockamoto v4
Goal: Bring the Stacker network’s FROST DKG online
This milestone would see the following additional functionality implemented:
TenureChange
transactions.TenureExtension
transactions, which are picked up by the current miner.Xenon: Public Testnet
Goal: Bring sBTC online
This milestone would see the following additional functionality implemented:
Appendix: Why Conservative Nakamoto was Not Good Enough
The key deficiency in Conservative Nakamoto is its continued reliance on the existing microblocks system for achieving low transaction confirmation latency. While this proposal did take steps to ensure that miners would always need to produce microblocks at a steady cadence, it did not properly address two key problems:
(at-block)
to work in microblocks. Because processing a microblock does not produce a new trie in the Stacks MARF, it is currently impossible to use(at-block)
on a microblock. This imposes severe and confusing constraints on the ability for smart contracts to recent historic chain state – they can only do so at Stacks blocks (i.e. at cryptographic sortitions).Beta Was this translation helpful? Give feedback.
All reactions