From ebfdae6148847ab048584bc20a9338911ccb23ab Mon Sep 17 00:00:00 2001 From: Derek Leung Date: Wed, 18 Dec 2019 17:41:18 -0500 Subject: [PATCH 1/4] Allow upgrades to specify the delay before their execution. This replaces UpgradeWaitRounds with MinUpgradeWaitRounds and MaxUpgradeWaitRounds. Proposers specify an upgrade's delay given their own ApprovedUpgrades, encoding the proposed delay in NextProtocolSwitchOn. Verifiers check that the delay sits between MinUpgradeWaitRounds and MaxUpgradeWaitRounds (inclusive). --- config/config.go | 135 +++++++++++++++++++++----------------- data/bookkeeping/block.go | 31 ++++++--- 2 files changed, 97 insertions(+), 69 deletions(-) diff --git a/config/config.go b/config/config.go index b17bd36742..e5f3abc493 100644 --- a/config/config.go +++ b/config/config.go @@ -71,12 +71,19 @@ type ConsensusParams struct { // to be high enough to ensure that there are sufficient participants // after the upgrade. // - // There is a delay of UpgradeWaitRounds between approval of - // an upgrade and its deployment, to give clients time to notify users. - UpgradeVoteRounds uint64 - UpgradeThreshold uint64 - UpgradeWaitRounds uint64 - MaxVersionStringLen int + // A consensus protocol upgrade may specify the delay between its + // acceptance and its execution. This gives clients time to notify + // users. This delay is specified by the upgrade proposer and must + // be between MinUpgradeWaitRounds and MaxUpgradeWaitRounds (inclusive) + // in the old protocol's parameters. + // + // The maximum length of a consensus version string is + // MaxVersionStringLen. + UpgradeVoteRounds uint64 + UpgradeThreshold uint64 + MinUpgradeWaitRounds uint64 + MaxUpgradeWaitRounds uint64 + MaxVersionStringLen int // MaxTxnBytesPerBlock determines the maximum number of bytes // that transactions can take up in a block. Specifically, @@ -95,8 +102,9 @@ type ConsensusParams struct { MaxTxnLife uint64 // ApprovedUpgrades describes the upgrade proposals that this protocol - // implementation will vote for. - ApprovedUpgrades map[protocol.ConsensusVersion]bool + // implementation will vote for, along with their delay value + // (in rounds). + ApprovedUpgrades map[protocol.ConsensusVersion]uint64 // SupportGenesisHash indicates support for the GenesisHash // fields in transactions (and requires them in blocks). @@ -257,10 +265,11 @@ func initConsensusProtocols() { // Base consensus protocol version, v7. v7 := ConsensusParams{ - UpgradeVoteRounds: 10000, - UpgradeThreshold: 9000, - UpgradeWaitRounds: 10000, - MaxVersionStringLen: 64, + UpgradeVoteRounds: 10000, + UpgradeThreshold: 9000, + MinUpgradeWaitRounds: 10000, + MaxUpgradeWaitRounds: 10000, + MaxVersionStringLen: 64, MinBalance: 10000, MinTxnFee: 1000, @@ -274,7 +283,7 @@ func initConsensusProtocols() { RewardUnit: 1e6, RewardsRateRefreshInterval: 5e5, - ApprovedUpgrades: map[protocol.ConsensusVersion]bool{}, + ApprovedUpgrades: map[protocol.ConsensusVersion]uint64{}, NumProposers: 30, SoftCommitteeSize: 2500, @@ -300,7 +309,7 @@ func initConsensusProtocols() { MaxTxGroupSize: 1, } - v7.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v7.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusV7] = v7 // v8 uses parameters and a seed derivation policy (the "twin seeds") from Georgios' new analysis @@ -321,20 +330,20 @@ func initConsensusProtocols() { v8.DownCommitteeSize = 5000 v8.DownCommitteeThreshold = 3838 - v8.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v8.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusV8] = v8 // v7 can be upgraded to v8. - v7.ApprovedUpgrades[protocol.ConsensusV8] = true + v7.ApprovedUpgrades[protocol.ConsensusV8] = v7.MinUpgradeWaitRounds // v9 increases the minimum balance to 100,000 microAlgos. v9 := v8 v9.MinBalance = 100000 - v9.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v9.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusV9] = v9 // v8 can be upgraded to v9. - v8.ApprovedUpgrades[protocol.ConsensusV9] = true + v8.ApprovedUpgrades[protocol.ConsensusV9] = v8.MinUpgradeWaitRounds // v10 introduces fast partition recovery (and also raises NumProposers). v10 := v9 @@ -346,82 +355,82 @@ func initConsensusProtocols() { v10.RedoCommitteeThreshold = 1768 v10.DownCommitteeSize = 6000 v10.DownCommitteeThreshold = 4560 - v10.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v10.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusV10] = v10 // v9 can be upgraded to v10. - v9.ApprovedUpgrades[protocol.ConsensusV10] = true + v9.ApprovedUpgrades[protocol.ConsensusV10] = v9.MinUpgradeWaitRounds // v11 introduces SignedTxnInBlock. v11 := v10 v11.SupportSignedTxnInBlock = true v11.PaysetCommitFlat = true - v11.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v11.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusV11] = v11 // v10 can be upgraded to v11. - v10.ApprovedUpgrades[protocol.ConsensusV11] = true + v10.ApprovedUpgrades[protocol.ConsensusV11] = v10.MinUpgradeWaitRounds // v12 increases the maximum length of a version string. v12 := v11 v12.MaxVersionStringLen = 128 - v12.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v12.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusV12] = v12 // v11 can be upgraded to v12. - v11.ApprovedUpgrades[protocol.ConsensusV12] = true + v11.ApprovedUpgrades[protocol.ConsensusV12] = v11.MinUpgradeWaitRounds // v13 makes the consensus version a meaningful string. v13 := v12 - v13.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v13.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusV13] = v13 // v12 can be upgraded to v13. - v12.ApprovedUpgrades[protocol.ConsensusV13] = true + v12.ApprovedUpgrades[protocol.ConsensusV13] = v12.MinUpgradeWaitRounds // v14 introduces tracking of closing amounts in ApplyData, and enables // GenesisHash in transactions. v14 := v13 v14.ApplyData = true v14.SupportGenesisHash = true - v14.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v14.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusV14] = v14 // v13 can be upgraded to v14. - v13.ApprovedUpgrades[protocol.ConsensusV14] = true + v13.ApprovedUpgrades[protocol.ConsensusV14] = v13.MinUpgradeWaitRounds // v15 introduces tracking of reward distributions in ApplyData. v15 := v14 v15.RewardsInApplyData = true v15.ForceNonParticipatingFeeSink = true - v15.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v15.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusV15] = v15 // v14 can be upgraded to v15. - v14.ApprovedUpgrades[protocol.ConsensusV15] = true + v14.ApprovedUpgrades[protocol.ConsensusV15] = v14.MinUpgradeWaitRounds // v16 fixes domain separation in credentials. v16 := v15 v16.CredentialDomainSeparationEnabled = true v16.RequireGenesisHash = true - v16.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v16.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusV16] = v16 // v15 can be upgraded to v16. - v15.ApprovedUpgrades[protocol.ConsensusV16] = true + v15.ApprovedUpgrades[protocol.ConsensusV16] = v15.MinUpgradeWaitRounds // ConsensusV17 points to 'final' spec commit v17 := v16 - v17.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v17.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusV17] = v17 // v16 can be upgraded to v17. - v16.ApprovedUpgrades[protocol.ConsensusV17] = true + v16.ApprovedUpgrades[protocol.ConsensusV17] = v16.MinUpgradeWaitRounds // ConsensusV18 points to reward calculation spec commit v18 := v17 v18.PendingResidueRewards = true - v18.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v18.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} v18.TxnCounter = true v18.Asset = true v18.LogicSigVersion = 1 @@ -439,68 +448,71 @@ func initConsensusProtocols() { // ConsensusV19 is the official spec commit ( teal, assets, group tx ) v19 := v18 - v19.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v19.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusV19] = v19 // v18 can be upgraded to v19. - v18.ApprovedUpgrades[protocol.ConsensusV19] = true + v18.ApprovedUpgrades[protocol.ConsensusV19] = v18.MinUpgradeWaitRounds // v17 can be upgraded to v19. - v17.ApprovedUpgrades[protocol.ConsensusV19] = true + v17.ApprovedUpgrades[protocol.ConsensusV19] = v17.MinUpgradeWaitRounds // v20 points to adding the precision to the assets. v20 := v19 - v20.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + v20.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} v20.MaxAssetDecimals = 19 // we want to adjust the upgrade time to be roughly one week. // one week, in term of rounds would be: // 140651 = (7 * 24 * 60 * 60 / 4.3) // for the sake of future manual calculations, we'll round that down // a bit : - v20.UpgradeWaitRounds = 140000 + v20.MinUpgradeWaitRounds = 140000 + v20.MaxUpgradeWaitRounds = 140000 Consensus[protocol.ConsensusV20] = v20 // v19 can be upgraded to v20. - v19.ApprovedUpgrades[protocol.ConsensusV20] = true + v19.ApprovedUpgrades[protocol.ConsensusV20] = v19.MinUpgradeWaitRounds // ConsensusFuture is used to test features that are implemented // but not yet released in a production protocol version. vFuture := v20 - vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusFuture] = vFuture } func initConsensusTestProtocols() { // Various test protocol versions Consensus[protocol.ConsensusTest0] = ConsensusParams{ - UpgradeVoteRounds: 2, - UpgradeThreshold: 1, - UpgradeWaitRounds: 2, - MaxVersionStringLen: 64, + UpgradeVoteRounds: 2, + UpgradeThreshold: 1, + MinUpgradeWaitRounds: 2, + MaxUpgradeWaitRounds: 2, + MaxVersionStringLen: 64, MaxTxnBytesPerBlock: 1000000, DefaultKeyDilution: 10000, - ApprovedUpgrades: map[protocol.ConsensusVersion]bool{ - protocol.ConsensusTest1: true, + ApprovedUpgrades: map[protocol.ConsensusVersion]uint64{ + protocol.ConsensusTest1: 2, }, } Consensus[protocol.ConsensusTest1] = ConsensusParams{ - UpgradeVoteRounds: 10, - UpgradeThreshold: 8, - UpgradeWaitRounds: 10, - MaxVersionStringLen: 64, + UpgradeVoteRounds: 10, + UpgradeThreshold: 8, + MinUpgradeWaitRounds: 10, + MaxUpgradeWaitRounds: 10, + MaxVersionStringLen: 64, MaxTxnBytesPerBlock: 1000000, DefaultKeyDilution: 10000, - ApprovedUpgrades: map[protocol.ConsensusVersion]bool{}, + ApprovedUpgrades: map[protocol.ConsensusVersion]uint64{}, } testBigBlocks := Consensus[protocol.ConsensusCurrentVersion] testBigBlocks.MaxTxnBytesPerBlock = 100000000 - testBigBlocks.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + testBigBlocks.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusTestBigBlocks] = testBigBlocks rapidRecalcParams := Consensus[protocol.ConsensusCurrentVersion] @@ -508,14 +520,14 @@ func initConsensusTestProtocols() { //because rapidRecalcParams is based on ConsensusCurrentVersion, //it *shouldn't* have any ApprovedUpgrades //but explicitly mark "no approved upgrades" just in case - rapidRecalcParams.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + rapidRecalcParams.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} Consensus[protocol.ConsensusTestRapidRewardRecalculation] = rapidRecalcParams // Setting the testShorterLookback parameters derived from ConsensusCurrentVersion // Will result in MaxBalLookback = 32 // Used to run tests faster where past MaxBalLookback values are checked testShorterLookback := Consensus[protocol.ConsensusCurrentVersion] - testShorterLookback.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + testShorterLookback.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} // MaxBalLookback = 2 x SeedRefreshInterval x SeedLookback // ref. https://github.com/algorandfoundation/specs/blob/master/dev/abft.md @@ -532,12 +544,15 @@ func initConsensusTestFastUpgrade() { fastParams := params fastParams.UpgradeVoteRounds = 5 fastParams.UpgradeThreshold = 3 - fastParams.UpgradeWaitRounds = 5 + fastParams.MinUpgradeWaitRounds = 5 + fastParams.MaxUpgradeWaitRounds = 5 fastParams.MaxVersionStringLen += len(protocol.ConsensusTestFastUpgrade("")) - fastParams.ApprovedUpgrades = make(map[protocol.ConsensusVersion]bool) + fastParams.ApprovedUpgrades = make(map[protocol.ConsensusVersion]uint64) - for ver, flag := range params.ApprovedUpgrades { - fastParams.ApprovedUpgrades[protocol.ConsensusTestFastUpgrade(ver)] = flag + for ver, delay := range params.ApprovedUpgrades { + if delay > 0 { + fastParams.ApprovedUpgrades[protocol.ConsensusTestFastUpgrade(ver)] = 5 + } } fastUpgradeProtocols[protocol.ConsensusTestFastUpgrade(proto)] = fastParams diff --git a/data/bookkeeping/block.go b/data/bookkeeping/block.go index a2ad96cb54..e8e30de2fc 100644 --- a/data/bookkeeping/block.go +++ b/data/bookkeeping/block.go @@ -280,13 +280,13 @@ func (s RewardsState) NextRewardsState(nextRound basics.Round, nextProto config. return } -// computeUpgradeState determines the UpgradeState for a block at round thisR, +// computeUpgradeState determines the UpgradeState for a block at round r, // given the previous block's UpgradeState "s" and this block's UpgradeVote. // // This function returns an error if the input is not valid in prevState: that // is, if UpgradePropose shows up when there is already an active proposal, or // if UpgradeApprove shows up if there is no active proposal being voted on. -func (s UpgradeState) applyUpgradeVote(r basics.Round, vote UpgradeVote) (res UpgradeState, err error) { +func (s UpgradeState) applyUpgradeVote(r basics.Round, vote UpgradeVote, upgradeDelay uint64) (res UpgradeState, err error) { // Locate the config parameters for current protocol params, ok := config.Consensus[s.CurrentProtocol] if !ok { @@ -302,14 +302,19 @@ func (s UpgradeState) applyUpgradeVote(r basics.Round, vote UpgradeVote) (res Up } if len(vote.UpgradePropose) > params.MaxVersionStringLen { - err = fmt.Errorf("proposed protocol version %s too long", vote.UpgradePropose) + err = fmt.Errorf("computeUpgradeState: proposed protocol version %s too long", vote.UpgradePropose) + return + } + + if upgradeDelay > params.MaxUpgradeWaitRounds || upgradeDelay < params.MinUpgradeWaitRounds { + err = fmt.Errorf("computeUpgradeState: proposed upgrade wait rounds %d out of permissible range", upgradeDelay) return } s.NextProtocol = vote.UpgradePropose s.NextProtocolApprovals = 0 s.NextProtocolVoteBefore = r + basics.Round(params.UpgradeVoteRounds) - s.NextProtocolSwitchOn = r + basics.Round(params.UpgradeVoteRounds) + basics.Round(params.UpgradeWaitRounds) + s.NextProtocolSwitchOn = r + basics.Round(params.UpgradeVoteRounds) + basics.Round(upgradeDelay) } // Apply approval of existing protocol upgrade @@ -359,12 +364,14 @@ func ProcessUpgradeParams(prev BlockHeader) (uv UpgradeVote, us UpgradeState, er } // Decide on the votes for protocol upgrades + var upgradeDelay uint64 upgradeVote := UpgradeVote{} // If there is no upgrade proposal, see if we can make one if prev.NextProtocol == "" { for k, v := range prevParams.ApprovedUpgrades { - if v { + if v > 0 { + upgradeDelay = v upgradeVote.UpgradePropose = k upgradeVote.UpgradeApprove = true break @@ -372,17 +379,22 @@ func ProcessUpgradeParams(prev BlockHeader) (uv UpgradeVote, us UpgradeState, er } } + // Set the upgrade delay if we haven't already done so + if upgradeDelay == 0 { + upgradeDelay = uint64(prev.NextProtocolSwitchOn - prev.NextProtocolVoteBefore) + } + // If there is a proposal being voted on, see if we approve it round := prev.Round + 1 if round < prev.NextProtocolVoteBefore { - if prevParams.ApprovedUpgrades[prev.NextProtocol] { + if prev.NextProtocolVoteBefore+basics.Round(prevParams.ApprovedUpgrades[prev.NextProtocol]) == prev.NextProtocolSwitchOn { upgradeVote.UpgradeApprove = true } } - upgradeState, err := prev.UpgradeState.applyUpgradeVote(round, upgradeVote) + upgradeState, err := prev.UpgradeState.applyUpgradeVote(round, upgradeVote, upgradeDelay) if err != nil { - err = fmt.Errorf("constructed invalid upgrade vote %v for round %v in state %v: %v", upgradeVote, round, prev.UpgradeState, err) + err = fmt.Errorf("constructed invalid upgrade vote %v for round %v (delay %v) in state %v: %v", upgradeVote, round, upgradeDelay, prev.UpgradeState, err) return } @@ -448,7 +460,8 @@ func (bh BlockHeader) PreCheck(prev BlockHeader) error { } // check upgrade state - nextUpgradeState, err := prev.UpgradeState.applyUpgradeVote(round, bh.UpgradeVote) + nextUpgradeDelay := uint64(bh.UpgradeState.NextProtocolSwitchOn - bh.UpgradeState.NextProtocolVoteBefore) + nextUpgradeState, err := prev.UpgradeState.applyUpgradeVote(round, bh.UpgradeVote, nextUpgradeDelay) if err != nil { return err } From 3755258ac2475a07c22d6747b33073e7cc7ba588 Mon Sep 17 00:00:00 2001 From: Derek Leung Date: Thu, 19 Dec 2019 11:10:53 -0500 Subject: [PATCH 2/4] Fix log message. --- data/bookkeeping/block.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/bookkeeping/block.go b/data/bookkeeping/block.go index e8e30de2fc..e1ab7a37f5 100644 --- a/data/bookkeeping/block.go +++ b/data/bookkeeping/block.go @@ -280,7 +280,7 @@ func (s RewardsState) NextRewardsState(nextRound basics.Round, nextProto config. return } -// computeUpgradeState determines the UpgradeState for a block at round r, +// applyUpgradeVote determines the UpgradeState for a block at round r, // given the previous block's UpgradeState "s" and this block's UpgradeVote. // // This function returns an error if the input is not valid in prevState: that @@ -290,24 +290,24 @@ func (s UpgradeState) applyUpgradeVote(r basics.Round, vote UpgradeVote, upgrade // Locate the config parameters for current protocol params, ok := config.Consensus[s.CurrentProtocol] if !ok { - err = fmt.Errorf("computeUpgradeState: unsupported protocol %v", s.CurrentProtocol) + err = fmt.Errorf("applyUpgradeVote: unsupported protocol %v", s.CurrentProtocol) return } // Apply proposal of upgrade to new protocol if vote.UpgradePropose != "" { if s.NextProtocol != "" { - err = fmt.Errorf("computeUpgradeState: new proposal during existing proposal") + err = fmt.Errorf("applyUpgradeVote: new proposal during existing proposal") return } if len(vote.UpgradePropose) > params.MaxVersionStringLen { - err = fmt.Errorf("computeUpgradeState: proposed protocol version %s too long", vote.UpgradePropose) + err = fmt.Errorf("applyUpgradeVote: proposed protocol version %s too long", vote.UpgradePropose) return } if upgradeDelay > params.MaxUpgradeWaitRounds || upgradeDelay < params.MinUpgradeWaitRounds { - err = fmt.Errorf("computeUpgradeState: proposed upgrade wait rounds %d out of permissible range", upgradeDelay) + err = fmt.Errorf("applyUpgradeVote: proposed upgrade wait rounds %d out of permissible range", upgradeDelay) return } @@ -320,12 +320,12 @@ func (s UpgradeState) applyUpgradeVote(r basics.Round, vote UpgradeVote, upgrade // Apply approval of existing protocol upgrade if vote.UpgradeApprove { if s.NextProtocol == "" { - err = fmt.Errorf("computeUpgradeState: approval without an active proposal") + err = fmt.Errorf("applyUpgradeVote: approval without an active proposal") return } if r >= s.NextProtocolVoteBefore { - err = fmt.Errorf("computeUpgradeState: approval after vote deadline") + err = fmt.Errorf("applyUpgradeVote: approval after vote deadline") return } From c615ffd9b107be604653de5398179de83730e06f Mon Sep 17 00:00:00 2001 From: Derek Leung Date: Thu, 19 Dec 2019 11:33:15 -0500 Subject: [PATCH 3/4] Explicitly specify upgrade delay. --- config/config.go | 84 ++++++++++++++++------------------ data/bookkeeping/block.go | 52 ++++++++++++--------- data/bookkeeping/block_test.go | 6 +-- 3 files changed, 73 insertions(+), 69 deletions(-) diff --git a/config/config.go b/config/config.go index e5f3abc493..79e26386df 100644 --- a/config/config.go +++ b/config/config.go @@ -75,15 +75,17 @@ type ConsensusParams struct { // acceptance and its execution. This gives clients time to notify // users. This delay is specified by the upgrade proposer and must // be between MinUpgradeWaitRounds and MaxUpgradeWaitRounds (inclusive) - // in the old protocol's parameters. + // in the old protocol's parameters. If the delay is zero, it is + // equivalent to DefaultUpgradeWaitRounds. // // The maximum length of a consensus version string is // MaxVersionStringLen. - UpgradeVoteRounds uint64 - UpgradeThreshold uint64 - MinUpgradeWaitRounds uint64 - MaxUpgradeWaitRounds uint64 - MaxVersionStringLen int + UpgradeVoteRounds uint64 + UpgradeThreshold uint64 + DefaultUpgradeWaitRounds uint64 + MinUpgradeWaitRounds uint64 + MaxUpgradeWaitRounds uint64 + MaxVersionStringLen int // MaxTxnBytesPerBlock determines the maximum number of bytes // that transactions can take up in a block. Specifically, @@ -103,7 +105,8 @@ type ConsensusParams struct { // ApprovedUpgrades describes the upgrade proposals that this protocol // implementation will vote for, along with their delay value - // (in rounds). + // (in rounds). A delay value of zero is the same as a delay of + // DefaultUpgradeWaitRounds. ApprovedUpgrades map[protocol.ConsensusVersion]uint64 // SupportGenesisHash indicates support for the GenesisHash @@ -265,11 +268,10 @@ func initConsensusProtocols() { // Base consensus protocol version, v7. v7 := ConsensusParams{ - UpgradeVoteRounds: 10000, - UpgradeThreshold: 9000, - MinUpgradeWaitRounds: 10000, - MaxUpgradeWaitRounds: 10000, - MaxVersionStringLen: 64, + UpgradeVoteRounds: 10000, + UpgradeThreshold: 9000, + DefaultUpgradeWaitRounds: 10000, + MaxVersionStringLen: 64, MinBalance: 10000, MinTxnFee: 1000, @@ -334,7 +336,7 @@ func initConsensusProtocols() { Consensus[protocol.ConsensusV8] = v8 // v7 can be upgraded to v8. - v7.ApprovedUpgrades[protocol.ConsensusV8] = v7.MinUpgradeWaitRounds + v7.ApprovedUpgrades[protocol.ConsensusV8] = 0 // v9 increases the minimum balance to 100,000 microAlgos. v9 := v8 @@ -343,7 +345,7 @@ func initConsensusProtocols() { Consensus[protocol.ConsensusV9] = v9 // v8 can be upgraded to v9. - v8.ApprovedUpgrades[protocol.ConsensusV9] = v8.MinUpgradeWaitRounds + v8.ApprovedUpgrades[protocol.ConsensusV9] = 0 // v10 introduces fast partition recovery (and also raises NumProposers). v10 := v9 @@ -359,7 +361,7 @@ func initConsensusProtocols() { Consensus[protocol.ConsensusV10] = v10 // v9 can be upgraded to v10. - v9.ApprovedUpgrades[protocol.ConsensusV10] = v9.MinUpgradeWaitRounds + v9.ApprovedUpgrades[protocol.ConsensusV10] = 0 // v11 introduces SignedTxnInBlock. v11 := v10 @@ -369,7 +371,7 @@ func initConsensusProtocols() { Consensus[protocol.ConsensusV11] = v11 // v10 can be upgraded to v11. - v10.ApprovedUpgrades[protocol.ConsensusV11] = v10.MinUpgradeWaitRounds + v10.ApprovedUpgrades[protocol.ConsensusV11] = 0 // v12 increases the maximum length of a version string. v12 := v11 @@ -378,7 +380,7 @@ func initConsensusProtocols() { Consensus[protocol.ConsensusV12] = v12 // v11 can be upgraded to v12. - v11.ApprovedUpgrades[protocol.ConsensusV12] = v11.MinUpgradeWaitRounds + v11.ApprovedUpgrades[protocol.ConsensusV12] = 0 // v13 makes the consensus version a meaningful string. v13 := v12 @@ -386,7 +388,7 @@ func initConsensusProtocols() { Consensus[protocol.ConsensusV13] = v13 // v12 can be upgraded to v13. - v12.ApprovedUpgrades[protocol.ConsensusV13] = v12.MinUpgradeWaitRounds + v12.ApprovedUpgrades[protocol.ConsensusV13] = 0 // v14 introduces tracking of closing amounts in ApplyData, and enables // GenesisHash in transactions. @@ -397,7 +399,7 @@ func initConsensusProtocols() { Consensus[protocol.ConsensusV14] = v14 // v13 can be upgraded to v14. - v13.ApprovedUpgrades[protocol.ConsensusV14] = v13.MinUpgradeWaitRounds + v13.ApprovedUpgrades[protocol.ConsensusV14] = 0 // v15 introduces tracking of reward distributions in ApplyData. v15 := v14 @@ -407,7 +409,7 @@ func initConsensusProtocols() { Consensus[protocol.ConsensusV15] = v15 // v14 can be upgraded to v15. - v14.ApprovedUpgrades[protocol.ConsensusV15] = v14.MinUpgradeWaitRounds + v14.ApprovedUpgrades[protocol.ConsensusV15] = 0 // v16 fixes domain separation in credentials. v16 := v15 @@ -417,7 +419,7 @@ func initConsensusProtocols() { Consensus[protocol.ConsensusV16] = v16 // v15 can be upgraded to v16. - v15.ApprovedUpgrades[protocol.ConsensusV16] = v15.MinUpgradeWaitRounds + v15.ApprovedUpgrades[protocol.ConsensusV16] = 0 // ConsensusV17 points to 'final' spec commit v17 := v16 @@ -425,7 +427,7 @@ func initConsensusProtocols() { Consensus[protocol.ConsensusV17] = v17 // v16 can be upgraded to v17. - v16.ApprovedUpgrades[protocol.ConsensusV17] = v16.MinUpgradeWaitRounds + v16.ApprovedUpgrades[protocol.ConsensusV17] = 0 // ConsensusV18 points to reward calculation spec commit v18 := v17 @@ -453,9 +455,9 @@ func initConsensusProtocols() { Consensus[protocol.ConsensusV19] = v19 // v18 can be upgraded to v19. - v18.ApprovedUpgrades[protocol.ConsensusV19] = v18.MinUpgradeWaitRounds + v18.ApprovedUpgrades[protocol.ConsensusV19] = 0 // v17 can be upgraded to v19. - v17.ApprovedUpgrades[protocol.ConsensusV19] = v17.MinUpgradeWaitRounds + v17.ApprovedUpgrades[protocol.ConsensusV19] = 0 // v20 points to adding the precision to the assets. v20 := v19 @@ -466,12 +468,11 @@ func initConsensusProtocols() { // 140651 = (7 * 24 * 60 * 60 / 4.3) // for the sake of future manual calculations, we'll round that down // a bit : - v20.MinUpgradeWaitRounds = 140000 - v20.MaxUpgradeWaitRounds = 140000 + v20.DefaultUpgradeWaitRounds = 140000 Consensus[protocol.ConsensusV20] = v20 // v19 can be upgraded to v20. - v19.ApprovedUpgrades[protocol.ConsensusV20] = v19.MinUpgradeWaitRounds + v19.ApprovedUpgrades[protocol.ConsensusV20] = 0 // ConsensusFuture is used to test features that are implemented // but not yet released in a production protocol version. @@ -483,26 +484,24 @@ func initConsensusProtocols() { func initConsensusTestProtocols() { // Various test protocol versions Consensus[protocol.ConsensusTest0] = ConsensusParams{ - UpgradeVoteRounds: 2, - UpgradeThreshold: 1, - MinUpgradeWaitRounds: 2, - MaxUpgradeWaitRounds: 2, - MaxVersionStringLen: 64, + UpgradeVoteRounds: 2, + UpgradeThreshold: 1, + DefaultUpgradeWaitRounds: 2, + MaxVersionStringLen: 64, MaxTxnBytesPerBlock: 1000000, DefaultKeyDilution: 10000, ApprovedUpgrades: map[protocol.ConsensusVersion]uint64{ - protocol.ConsensusTest1: 2, + protocol.ConsensusTest1: 0, }, } Consensus[protocol.ConsensusTest1] = ConsensusParams{ - UpgradeVoteRounds: 10, - UpgradeThreshold: 8, - MinUpgradeWaitRounds: 10, - MaxUpgradeWaitRounds: 10, - MaxVersionStringLen: 64, + UpgradeVoteRounds: 10, + UpgradeThreshold: 8, + DefaultUpgradeWaitRounds: 10, + MaxVersionStringLen: 64, MaxTxnBytesPerBlock: 1000000, DefaultKeyDilution: 10000, @@ -544,15 +543,12 @@ func initConsensusTestFastUpgrade() { fastParams := params fastParams.UpgradeVoteRounds = 5 fastParams.UpgradeThreshold = 3 - fastParams.MinUpgradeWaitRounds = 5 - fastParams.MaxUpgradeWaitRounds = 5 + fastParams.DefaultUpgradeWaitRounds = 5 fastParams.MaxVersionStringLen += len(protocol.ConsensusTestFastUpgrade("")) fastParams.ApprovedUpgrades = make(map[protocol.ConsensusVersion]uint64) - for ver, delay := range params.ApprovedUpgrades { - if delay > 0 { - fastParams.ApprovedUpgrades[protocol.ConsensusTestFastUpgrade(ver)] = 5 - } + for ver := range params.ApprovedUpgrades { + fastParams.ApprovedUpgrades[protocol.ConsensusTestFastUpgrade(ver)] = 0 } fastUpgradeProtocols[protocol.ConsensusTestFastUpgrade(proto)] = fastParams diff --git a/data/bookkeeping/block.go b/data/bookkeeping/block.go index e1ab7a37f5..47d0e92426 100644 --- a/data/bookkeeping/block.go +++ b/data/bookkeeping/block.go @@ -97,9 +97,9 @@ type ( // // If enough votes are collected, the proposal is approved, and will // definitely take effect. The proposal lingers for some number of - // rounds (UpgradeWaitRounds) to give clients a chance to notify users - // about an approved upgrade, if the client doesn't support it, so the - // user has a chance to download updated client software. + // rounds to give clients a chance to notify users about an approved + // upgrade, if the client doesn't support it, so the user has a chance + // to download updated client software. // // Block proposers influence this upgrade machinery through two fields // in UpgradeVote: UpgradePropose, which proposes an upgrade to a new @@ -157,6 +157,9 @@ type ( // UpgradePropose indicates a proposed upgrade UpgradePropose protocol.ConsensusVersion `codec:"upgradeprop"` + // UpgradeDelay indicates the time between acceptance and execution + UpgradeDelay basics.Round `codec:"upgradedelay"` + // UpgradeApprove indicates a yes vote for the current proposal UpgradeApprove bool `codec:"upgradeyes"` } @@ -286,7 +289,7 @@ func (s RewardsState) NextRewardsState(nextRound basics.Round, nextProto config. // This function returns an error if the input is not valid in prevState: that // is, if UpgradePropose shows up when there is already an active proposal, or // if UpgradeApprove shows up if there is no active proposal being voted on. -func (s UpgradeState) applyUpgradeVote(r basics.Round, vote UpgradeVote, upgradeDelay uint64) (res UpgradeState, err error) { +func (s UpgradeState) applyUpgradeVote(r basics.Round, vote UpgradeVote) (res UpgradeState, err error) { // Locate the config parameters for current protocol params, ok := config.Consensus[s.CurrentProtocol] if !ok { @@ -306,15 +309,25 @@ func (s UpgradeState) applyUpgradeVote(r basics.Round, vote UpgradeVote, upgrade return } + upgradeDelay := uint64(vote.UpgradeDelay) if upgradeDelay > params.MaxUpgradeWaitRounds || upgradeDelay < params.MinUpgradeWaitRounds { err = fmt.Errorf("applyUpgradeVote: proposed upgrade wait rounds %d out of permissible range", upgradeDelay) return } + if upgradeDelay == 0 { + upgradeDelay = params.DefaultUpgradeWaitRounds + } + s.NextProtocol = vote.UpgradePropose s.NextProtocolApprovals = 0 s.NextProtocolVoteBefore = r + basics.Round(params.UpgradeVoteRounds) s.NextProtocolSwitchOn = r + basics.Round(params.UpgradeVoteRounds) + basics.Round(upgradeDelay) + } else { + if vote.UpgradeDelay != 0 { + err = fmt.Errorf("applyUpgradeVote: upgrade delay %d nonzero when not proposing", vote.UpgradeDelay) + return + } } // Apply approval of existing protocol upgrade @@ -364,37 +377,33 @@ func ProcessUpgradeParams(prev BlockHeader) (uv UpgradeVote, us UpgradeState, er } // Decide on the votes for protocol upgrades - var upgradeDelay uint64 upgradeVote := UpgradeVote{} // If there is no upgrade proposal, see if we can make one if prev.NextProtocol == "" { for k, v := range prevParams.ApprovedUpgrades { - if v > 0 { - upgradeDelay = v - upgradeVote.UpgradePropose = k - upgradeVote.UpgradeApprove = true - break - } + upgradeVote.UpgradePropose = k + upgradeVote.UpgradeDelay = basics.Round(v) + upgradeVote.UpgradeApprove = true + break } } - // Set the upgrade delay if we haven't already done so - if upgradeDelay == 0 { - upgradeDelay = uint64(prev.NextProtocolSwitchOn - prev.NextProtocolVoteBefore) - } - - // If there is a proposal being voted on, see if we approve it + // If there is a proposal being voted on, see if we approve it and its delay round := prev.Round + 1 if round < prev.NextProtocolVoteBefore { - if prev.NextProtocolVoteBefore+basics.Round(prevParams.ApprovedUpgrades[prev.NextProtocol]) == prev.NextProtocolSwitchOn { + expectedDelay, ok := prevParams.ApprovedUpgrades[prev.NextProtocol] + if expectedDelay == 0 { + expectedDelay = prevParams.DefaultUpgradeWaitRounds + } + if ok && prev.NextProtocolVoteBefore+basics.Round(expectedDelay) == prev.NextProtocolSwitchOn { upgradeVote.UpgradeApprove = true } } - upgradeState, err := prev.UpgradeState.applyUpgradeVote(round, upgradeVote, upgradeDelay) + upgradeState, err := prev.UpgradeState.applyUpgradeVote(round, upgradeVote) if err != nil { - err = fmt.Errorf("constructed invalid upgrade vote %v for round %v (delay %v) in state %v: %v", upgradeVote, round, upgradeDelay, prev.UpgradeState, err) + err = fmt.Errorf("constructed invalid upgrade vote %v for round %v in state %v: %v", upgradeVote, round, prev.UpgradeState, err) return } @@ -460,8 +469,7 @@ func (bh BlockHeader) PreCheck(prev BlockHeader) error { } // check upgrade state - nextUpgradeDelay := uint64(bh.UpgradeState.NextProtocolSwitchOn - bh.UpgradeState.NextProtocolVoteBefore) - nextUpgradeState, err := prev.UpgradeState.applyUpgradeVote(round, bh.UpgradeVote, nextUpgradeDelay) + nextUpgradeState, err := prev.UpgradeState.applyUpgradeVote(round, bh.UpgradeVote) if err != nil { return err } diff --git a/data/bookkeeping/block_test.go b/data/bookkeeping/block_test.go index eb2c0664e9..6fe6ed839b 100644 --- a/data/bookkeeping/block_test.go +++ b/data/bookkeeping/block_test.go @@ -38,13 +38,13 @@ var protoUnsupported = protocol.ConsensusVersion("TestUnsupported") func init() { params1 := config.Consensus[protocol.ConsensusCurrentVersion] - params1.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{ - proto2: true, + params1.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{ + proto2: 0, } config.Consensus[proto1] = params1 params2 := config.Consensus[protocol.ConsensusCurrentVersion] - params2.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{} + params2.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} config.Consensus[proto2] = params2 } From 38b30d73c32aa8dc43a7ab31215cd4292a7e5415 Mon Sep 17 00:00:00 2001 From: Derek Leung Date: Fri, 20 Dec 2019 09:41:37 -0500 Subject: [PATCH 4/4] Clarify documentation; add flexibility to approving upgrades. --- config/config.go | 6 ++++-- data/bookkeeping/block.go | 9 ++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/config/config.go b/config/config.go index 79e26386df..3381eee9cd 100644 --- a/config/config.go +++ b/config/config.go @@ -75,8 +75,10 @@ type ConsensusParams struct { // acceptance and its execution. This gives clients time to notify // users. This delay is specified by the upgrade proposer and must // be between MinUpgradeWaitRounds and MaxUpgradeWaitRounds (inclusive) - // in the old protocol's parameters. If the delay is zero, it is - // equivalent to DefaultUpgradeWaitRounds. + // in the old protocol's parameters. Note that these parameters refer + // to the representation of the delay in a block rather than the actual + // delay: if the specified delay is zero, it is equivalent to + // DefaultUpgradeWaitRounds. // // The maximum length of a consensus version string is // MaxVersionStringLen. diff --git a/data/bookkeeping/block.go b/data/bookkeeping/block.go index 47d0e92426..507e683deb 100644 --- a/data/bookkeeping/block.go +++ b/data/bookkeeping/block.go @@ -392,13 +392,8 @@ func ProcessUpgradeParams(prev BlockHeader) (uv UpgradeVote, us UpgradeState, er // If there is a proposal being voted on, see if we approve it and its delay round := prev.Round + 1 if round < prev.NextProtocolVoteBefore { - expectedDelay, ok := prevParams.ApprovedUpgrades[prev.NextProtocol] - if expectedDelay == 0 { - expectedDelay = prevParams.DefaultUpgradeWaitRounds - } - if ok && prev.NextProtocolVoteBefore+basics.Round(expectedDelay) == prev.NextProtocolSwitchOn { - upgradeVote.UpgradeApprove = true - } + _, ok := prevParams.ApprovedUpgrades[prev.NextProtocol] + upgradeVote.UpgradeApprove = ok } upgradeState, err := prev.UpgradeState.applyUpgradeVote(round, upgradeVote)