From a5557264e7ec21bd6dce41388e56f0729cb798a2 Mon Sep 17 00:00:00 2001 From: evlekht Date: Tue, 29 Aug 2023 21:36:56 +0400 Subject: [PATCH] fix --- vms/platformvm/camino_vm_test.go | 1058 ++++++----------- .../dac/camino_change_base_fee_proposal.go | 32 +- .../camino_change_base_fee_proposal_test.go | 97 +- vms/platformvm/dac/camino_vote.go | 8 + vms/platformvm/state/camino.go | 30 +- vms/platformvm/state/camino_diff.go | 41 +- vms/platformvm/state/camino_diff_test.go | 132 +- vms/platformvm/state/camino_proposal.go | 86 +- vms/platformvm/state/camino_proposal_test.go | 219 ++-- vms/platformvm/txs/builder/camino_builder.go | 20 +- .../txs/camino_finish_proposals_tx.go | 21 +- .../txs/camino_finish_proposals_tx_test.go | 24 +- vms/platformvm/txs/executor/camino_dac.go | 8 +- .../txs/executor/camino_tx_executor.go | 39 +- .../txs/executor/camino_tx_executor_test.go | 138 ++- vms/platformvm/utxo/camino_locked.go | 3 - 16 files changed, 861 insertions(+), 1095 deletions(-) diff --git a/vms/platformvm/camino_vm_test.go b/vms/platformvm/camino_vm_test.go index c7e21e389857..be6e57712ef3 100644 --- a/vms/platformvm/camino_vm_test.go +++ b/vms/platformvm/camino_vm_test.go @@ -12,7 +12,6 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/formatting/address" @@ -20,7 +19,6 @@ import ( "github.com/ava-labs/avalanchego/utils/nodeid" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/avax" - "github.com/ava-labs/avalanchego/vms/components/multisig" "github.com/ava-labs/avalanchego/vms/platformvm/api" "github.com/ava-labs/avalanchego/vms/platformvm/blocks" "github.com/ava-labs/avalanchego/vms/platformvm/dac" @@ -532,747 +530,198 @@ func TestDepositsAutoUnlock(t *testing.T) { require.ErrorIs(err, database.ErrNotFound) } -func TestProposalsExpiration(t *testing.T) { - require := require.New(t) - +func TestProposals(t *testing.T) { proposerKey, proposerAddr, _ := generateKeyAndOwner(t) proposerAddrStr, err := address.FormatBech32(constants.NetworkIDToHRP[testNetworkID], proposerAddr.Bytes()) - require.NoError(err) + require.NoError(t, err) caminoPreFundedKey0AddrStr, err := address.FormatBech32(constants.NetworkIDToHRP[testNetworkID], caminoPreFundedKeys[0].Address().Bytes()) - require.NoError(err) + require.NoError(t, err) defaultConfig := defaultCaminoConfig(true) proposalBondAmount := defaultConfig.CaminoConfig.DACProposalBondAmount - initialBaseFee := defaultTxFee - newBaseFee := defaultTxFee + 7 - proposerInitialBalance := proposalBondAmount*3 + initialBaseFee*6 + newBaseFee*2 - - // Prepare vm - vm := newCaminoVM(api.Camino{ - VerifyNodeSignature: true, - LockModeBondDeposit: true, - InitialAdmin: caminoPreFundedKeys[0].Address(), - }, []api.UTXO{ - { - Amount: json.Uint64(proposerInitialBalance), - Address: proposerAddrStr, - }, - { - Amount: json.Uint64(defaultTxFee), - Address: caminoPreFundedKey0AddrStr, - }, - }, &defaultConfig.BanffTime) - vm.ctx.Lock.Lock() - defer func() { require.NoError(vm.Shutdown(context.Background())) }() //nolint:lint - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance, // total - 0, 0, 0, proposerInitialBalance, // unlocked - ) - - // Give proposer address role to make proposals - addrStateTx, err := vm.txBuilder.NewAddressStateTx( - proposerAddr, - false, - txs.AddressStateBitCaminoProposer, - []*secp256k1.PrivateKey{caminoPreFundedKeys[0]}, - nil, - ) - require.NoError(err) - blk := buildAndAcceptBlock(t, vm, addrStateTx) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), addrStateTx.ID()) - - // Add proposal1 - currentChainTime := vm.state.GetTimestamp() - ins, outs, signers, _, err := vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - proposalBondAmount, - initialBaseFee, - locked.StateBonded, - nil, nil, 0, - ) - require.NoError(err) - proposal1 := &txs.ProposalWrapper{Proposal: &dac.BaseFeeProposal{ - Start: uint64(currentChainTime.Add(100 * time.Second).Unix()), - End: uint64(currentChainTime.Add(200 * time.Second).Unix()), - Options: []uint64{newBaseFee + 10, newBaseFee, newBaseFee + 20}, - }} - proposalBytes1, err := txs.Codec.Marshal(txs.Version, proposal1) - require.NoError(err) - proposalTx1, err := txs.NewSigned(&txs.AddProposalTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - ProposalPayload: proposalBytes1, - ProposerAddress: proposerAddr, - ProposerAuth: &secp256k1fx.Input{SigIndices: []uint32{0}}, - }, txs.Codec, append(signers, []*secp256k1.PrivateKey{proposerKey})) - require.NoError(err) - blk = buildAndAcceptBlock(t, vm, proposalTx1) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), proposalTx1.ID()) - proposalState1, err := vm.state.GetProposal(proposalTx1.ID()) - require.NoError(err) - nextProposalIDsToExpire, nexExpirationTime, err := vm.state.GetNextToExpireProposalIDsAndTime(nil) - require.NoError(err) - require.Equal([]ids.ID{proposalTx1.ID()}, nextProposalIDsToExpire) - require.Equal(proposalState1.EndTime(), nexExpirationTime) - proposalIDsToFinish, err := vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Empty(proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee, // total - proposalBondAmount, // bonded - 0, 0, proposerInitialBalance-proposalBondAmount-initialBaseFee, // unlocked - ) - - // Add proposal2 - ins, outs, signers, _, err = vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - proposalBondAmount, - initialBaseFee, - locked.StateBonded, - nil, nil, 0, - ) - require.NoError(err) - proposal2 := &txs.ProposalWrapper{Proposal: &dac.BaseFeeProposal{ - Start: uint64(proposal1.StartTime().Unix()), // starts when proposal1 starts - End: uint64(proposalState1.EndTime().Unix()) + 50, // ends after proposal1 - Options: []uint64{newBaseFee + 100, newBaseFee + 200}, - }} - proposalBytes2, err := txs.Codec.Marshal(txs.Version, proposal2) - require.NoError(err) - proposalTx2, err := txs.NewSigned(&txs.AddProposalTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - ProposalPayload: proposalBytes2, - ProposerAddress: proposerAddr, - ProposerAuth: &secp256k1fx.Input{SigIndices: []uint32{0}}, - }, txs.Codec, append(signers, []*secp256k1.PrivateKey{proposerKey})) - require.NoError(err) - blk = buildAndAcceptBlock(t, vm, proposalTx2) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), proposalTx2.ID()) - proposalState2, err := vm.state.GetProposal(proposalTx2.ID()) - require.NoError(err) - nextProposalIDsToExpire, nexExpirationTime, err = vm.state.GetNextToExpireProposalIDsAndTime(nil) - require.NoError(err) - require.Equal([]ids.ID{proposalTx1.ID()}, nextProposalIDsToExpire) - require.Equal(proposalState1.EndTime(), nexExpirationTime) - proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Empty(proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*2, // total - proposalBondAmount*2, // bonded - 0, 0, proposerInitialBalance-proposalBondAmount*2-initialBaseFee*2, // unlocked - ) - - // Add proposal3 - ins, outs, signers, _, err = vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - proposalBondAmount, - initialBaseFee, - locked.StateBonded, - nil, nil, 0, - ) - require.NoError(err) - proposal3 := &txs.ProposalWrapper{Proposal: &dac.BaseFeeProposal{ - Start: uint64(proposal2.StartTime().Unix()), // starts when proposal1 and proposal2 start - End: uint64(proposalState2.EndTime().Unix()), // ends after proposal1, when proposal2 ends - Options: []uint64{newBaseFee + 150, newBaseFee + 250}, - }} - proposalBytes3, err := txs.Codec.Marshal(txs.Version, proposal3) - require.NoError(err) - proposalTx3, err := txs.NewSigned(&txs.AddProposalTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - ProposalPayload: proposalBytes3, - ProposerAddress: proposerAddr, - ProposerAuth: &secp256k1fx.Input{SigIndices: []uint32{0}}, - }, txs.Codec, append(signers, []*secp256k1.PrivateKey{proposerKey})) - require.NoError(err) - blk = buildAndAcceptBlock(t, vm, proposalTx3) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), proposalTx3.ID()) - _, err = vm.state.GetProposal(proposalTx3.ID()) - require.NoError(err) - nextProposalIDsToExpire, nexExpirationTime, err = vm.state.GetNextToExpireProposalIDsAndTime(nil) - require.NoError(err) - require.Equal([]ids.ID{proposalTx1.ID()}, nextProposalIDsToExpire) - require.Equal(proposalState1.EndTime(), nexExpirationTime) - proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Empty(proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*3, // total - proposalBondAmount*3, // bonded - 0, 0, proposerInitialBalance-proposalBondAmount*3-initialBaseFee*3, // unlocked - ) - - // Fast-forward clock to time a bit forward, but still before proposals start - // Verify that we can't vote on proposal1 yet - ins, outs, signers, _, err = vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - 0, - initialBaseFee, - locked.StateUnlocked, - nil, nil, 0, - ) - require.NoError(err) - voteBytes1, err := txs.Codec.Marshal(txs.Version, &txs.VoteWrapper{Vote: &dac.SimpleVote{OptionIndex: 1}}) - require.NoError(err) - addVoteTx1, err := txs.NewSigned(&txs.AddVoteTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - ProposalID: proposalTx1.ID(), - VotePayload: voteBytes1, - VoterAddress: caminoPreFundedKeys[0].Address(), - VoterAuth: &secp256k1fx.Input{SigIndices: []uint32{0}}, - }, txs.Codec, append(signers, []*secp256k1.PrivateKey{caminoPreFundedKeys[0]})) - require.NoError(err) - vm.clock.Set(proposal1.StartTime().Add(-time.Second)) - _, err = vm.Builder.BuildBlock(context.Background()) - require.Error(err) + newFee := (defaultTxFee + 7) * 10 - // Fast-forward clock to time when proposals start - // Vote on proposal1 - vm.clock.Set(proposal1.StartTime()) - blk = buildAndAcceptBlock(t, vm, addVoteTx1) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), addVoteTx1.ID()) - proposalState1, err = vm.state.GetProposal(proposalTx1.ID()) - require.NoError(err) - baseFeeProposal, ok := proposalState1.(*dac.BaseFeeProposalState) - require.True(ok) - require.EqualValues(0, baseFeeProposal.Options[0].Weight) - require.EqualValues(1, baseFeeProposal.Options[1].Weight) // voted option - require.EqualValues(0, baseFeeProposal.Options[2].Weight) - proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Empty(proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*4, // total - proposalBondAmount*3, // bonded - 0, 0, proposerInitialBalance-proposalBondAmount*3-initialBaseFee*4, // unlocked - ) - - // Vote on proposal2 - ins, outs, signers, _, err = vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - 0, - initialBaseFee, - locked.StateUnlocked, - nil, nil, 0, - ) - require.NoError(err) - voteBytes21, err := txs.Codec.Marshal(txs.Version, &txs.VoteWrapper{Vote: &dac.SimpleVote{OptionIndex: 0}}) - require.NoError(err) - addVoteTx21, err := txs.NewSigned(&txs.AddVoteTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - ProposalID: proposalTx2.ID(), - VotePayload: voteBytes21, - VoterAddress: caminoPreFundedKeys[0].Address(), - VoterAuth: &secp256k1fx.Input{SigIndices: []uint32{0}}, - }, txs.Codec, append(signers, []*secp256k1.PrivateKey{caminoPreFundedKeys[0]})) - require.NoError(err) - blk = buildAndAcceptBlock(t, vm, addVoteTx21) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), addVoteTx21.ID()) - proposalState2, err = vm.state.GetProposal(proposalTx2.ID()) - require.NoError(err) - baseFeeProposal, ok = proposalState2.(*dac.BaseFeeProposalState) - require.True(ok) - require.EqualValues(1, baseFeeProposal.Options[0].Weight) // voted option - require.EqualValues(0, baseFeeProposal.Options[1].Weight) - proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Empty(proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*5, // total - proposalBondAmount*3, // bonded - 0, 0, proposerInitialBalance-proposalBondAmount*3-initialBaseFee*5, // unlocked - ) - - // Vote on proposal2, but on different option to keep it ambiguous - ins, outs, signers, _, err = vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - 0, - initialBaseFee, - locked.StateUnlocked, - nil, nil, 0, - ) - require.NoError(err) - voteBytes22, err := txs.Codec.Marshal(txs.Version, &txs.VoteWrapper{Vote: &dac.SimpleVote{OptionIndex: 1}}) - require.NoError(err) - addVoteTx22, err := txs.NewSigned(&txs.AddVoteTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - ProposalID: proposalTx2.ID(), - VotePayload: voteBytes22, - VoterAddress: caminoPreFundedKeys[1].Address(), - VoterAuth: &secp256k1fx.Input{SigIndices: []uint32{0}}, - }, txs.Codec, append(signers, []*secp256k1.PrivateKey{caminoPreFundedKeys[1]})) - require.NoError(err) - blk = buildAndAcceptBlock(t, vm, addVoteTx22) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), addVoteTx22.ID()) - proposalState2, err = vm.state.GetProposal(proposalTx2.ID()) - require.NoError(err) - baseFeeProposal, ok = proposalState2.(*dac.BaseFeeProposalState) - require.True(ok) - require.EqualValues(1, baseFeeProposal.Options[0].Weight) // voted option - require.EqualValues(1, baseFeeProposal.Options[1].Weight) // voted option - proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Empty(proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*6, // total - proposalBondAmount*3, // bonded - 0, 0, proposerInitialBalance-proposalBondAmount*3-initialBaseFee*6, // unlocked - ) - - // Fast-forward clock to time when proposal is still active - // Verify that we can't build block yet - vm.clock.Set(proposalState1.EndTime().Add(-time.Second)) - _, err = vm.Builder.BuildBlock(context.Background()) - require.Error(err) - - // Fast-forward clock to time when proposal1 is expired - // Verify that proposal1 is executed and removed from state - vm.clock.Set(proposalState1.EndTime()) - blk = buildAndAcceptBlock(t, vm, nil) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), blk.Txs()[0].ID()) - // proposals 1 is unambiguous and should be removed from state with execution - _, err = vm.state.GetProposal(proposalTx1.ID()) - require.ErrorIs(err, database.ErrNotFound) - baseFee, err := vm.state.GetBaseFee() - require.NoError(err) - require.Equal(newBaseFee, baseFee) - nextProposalIDsToExpire, nexExpirationTime, err = vm.state.GetNextToExpireProposalIDsAndTime(nil) - require.NoError(err) - expectedNextProposalIDsToExpire := []ids.ID{proposalTx2.ID(), proposalTx3.ID()} - utils.Sort(expectedNextProposalIDsToExpire) - require.Equal(expectedNextProposalIDsToExpire, nextProposalIDsToExpire) - require.Equal(proposalState2.EndTime(), nexExpirationTime) - proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Empty(proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*6, // total - proposalBondAmount*2, // bonded - 0, 0, proposerInitialBalance-proposalBondAmount*2-initialBaseFee*6, // unlocked - ) + type vote struct { + option uint32 + success bool // proposal is successful after this vote + } - // Create arbitrary tx to verify that it will use new base fee - ins, outs, signers, _, err = vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - 0, - newBaseFee, - locked.StateUnlocked, - nil, nil, 0, - ) - require.NoError(err) - feeTestingTx, err := txs.NewSigned(&txs.MultisigAliasTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - MultisigAlias: multisig.Alias{ - Owners: &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{proposerAddr}, + tests := map[string]struct { + feeOptions []uint64 + winningOption uint32 + earlyFinish bool + votes []vote // no more than 5 votes, cause we have only 5 validators + }{ + "Early success: 1|3 votes": { + feeOptions: []uint64{1, newFee}, + winningOption: 1, + earlyFinish: true, + votes: []vote{ + {option: 1}, + {option: 1}, + {option: 0, success: true}, + {option: 1, success: true}, }, }, - Auth: &secp256k1fx.Input{}, - }, txs.Codec, signers) - require.NoError(err) - blk = buildAndAcceptBlock(t, vm, feeTestingTx) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), feeTestingTx.ID()) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*6-newBaseFee, // total - proposalBondAmount*2, // bonded - 0, 0, proposerInitialBalance-proposalBondAmount*2-initialBaseFee*6-newBaseFee, // unlocked - ) - - // Fast-forward clock to time when proposal2-3 are expired - vm.clock.Set(proposalState2.EndTime()) - blk = buildAndAcceptBlock(t, vm, nil) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), blk.Txs()[0].ID()) - // proposals 2 and 3 are ambiguous and should be removed from state without execution - baseFee, err = vm.state.GetBaseFee() - require.NoError(err) - require.Equal(newBaseFee, baseFee) - _, _, err = vm.state.GetNextToExpireProposalIDsAndTime(nil) - require.ErrorIs(err, database.ErrNotFound) - proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Empty(proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*6-newBaseFee, // total - 0, 0, 0, proposerInitialBalance-initialBaseFee*6-newBaseFee, // unlocked - ) - - // Create arbitrary tx to verify that it still uses base fee from proposalTx1 - ins, outs, signers, _, err = vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - 0, - newBaseFee, - locked.StateUnlocked, - nil, nil, 0, - ) - require.NoError(err) - feeTestingTx, err = txs.NewSigned(&txs.MultisigAliasTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - MultisigAlias: multisig.Alias{ - Owners: &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{proposerAddr}, + "Early fail: 2|2|1 votes, not reaching mostVoted threshold and being unambiguous": { + feeOptions: []uint64{1, 2, 3}, + earlyFinish: true, + votes: []vote{ + {option: 0}, + {option: 0}, + {option: 1, success: true}, + {option: 1}, + {option: 2}, }, }, - Auth: &secp256k1fx.Input{}, - }, txs.Codec, signers) - require.NoError(err) - blk = buildAndAcceptBlock(t, vm, feeTestingTx) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), feeTestingTx.ID()) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*6-newBaseFee*2, // total - 0, 0, 0, proposerInitialBalance-initialBaseFee*6-newBaseFee*2, // unlocked - ) -} - -func TestProposalsThresholdExecution(t *testing.T) { - require := require.New(t) - - proposerKey, proposerAddr, _ := generateKeyAndOwner(t) - proposerAddrStr, err := address.FormatBech32(constants.NetworkIDToHRP[testNetworkID], proposerAddr.Bytes()) - require.NoError(err) - caminoPreFundedKey0AddrStr, err := address.FormatBech32(constants.NetworkIDToHRP[testNetworkID], caminoPreFundedKeys[0].Address().Bytes()) - require.NoError(err) - - defaultConfig := defaultCaminoConfig(true) - proposalBondAmount := defaultConfig.CaminoConfig.DACProposalBondAmount - initialBaseFee := defaultTxFee - newBaseFee := defaultTxFee + 7 - proposerInitialBalance := proposalBondAmount + initialBaseFee*4 + newBaseFee - - // Prepare vm - vm := newCaminoVM(api.Camino{ - VerifyNodeSignature: true, - LockModeBondDeposit: true, - InitialAdmin: caminoPreFundedKeys[0].Address(), - }, []api.UTXO{ - { - Amount: json.Uint64(proposerInitialBalance), - Address: proposerAddrStr, + "Success: 0|2|1 votes": { + feeOptions: []uint64{1, newFee, 17}, + winningOption: 1, + votes: []vote{ + {option: 1}, + {option: 1}, + {option: 2, success: true}, + }, }, - { - Amount: json.Uint64(defaultTxFee), - Address: caminoPreFundedKey0AddrStr, + "Fail: 0 votes": { + feeOptions: []uint64{1}, + votes: []vote{}, }, - }, &defaultConfig.BanffTime) - vm.ctx.Lock.Lock() - defer func() { require.NoError(vm.Shutdown(context.Background())) }() //nolint:lint - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance, // total - 0, 0, 0, proposerInitialBalance, // unlocked - ) - - // Give proposer address role to make proposals - addrStateTx, err := vm.txBuilder.NewAddressStateTx( - proposerAddr, - false, - txs.AddressStateBitCaminoProposer, - []*secp256k1.PrivateKey{caminoPreFundedKeys[0]}, - nil, - ) - require.NoError(err) - blk := buildAndAcceptBlock(t, vm, addrStateTx) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), addrStateTx.ID()) - - // Add proposal - currentChainTime := vm.state.GetTimestamp() - ins, outs, signers, _, err := vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - proposalBondAmount, - initialBaseFee, - locked.StateBonded, - nil, nil, 0, - ) - require.NoError(err) - proposal := &txs.ProposalWrapper{Proposal: &dac.BaseFeeProposal{ - Start: uint64(currentChainTime.Add(100 * time.Second).Unix()), - End: uint64(currentChainTime.Add(200 * time.Second).Unix()), - Options: []uint64{newBaseFee}, - }} - proposalBytes1, err := txs.Codec.Marshal(txs.Version, proposal) - require.NoError(err) - proposalTx, err := txs.NewSigned(&txs.AddProposalTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - ProposalPayload: proposalBytes1, - ProposerAddress: proposerAddr, - ProposerAuth: &secp256k1fx.Input{SigIndices: []uint32{0}}, - }, txs.Codec, append(signers, []*secp256k1.PrivateKey{proposerKey})) - require.NoError(err) - blk = buildAndAcceptBlock(t, vm, proposalTx) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), proposalTx.ID()) - proposalState, err := vm.state.GetProposal(proposalTx.ID()) - require.NoError(err) - nextProposalIDsToExpire, nexExpirationTime, err := vm.state.GetNextToExpireProposalIDsAndTime(nil) - require.NoError(err) - require.Equal([]ids.ID{proposalTx.ID()}, nextProposalIDsToExpire) - require.Equal(proposalState.EndTime(), nexExpirationTime) - proposalIDsToFinish, err := vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Empty(proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee, // total - proposalBondAmount, // bonded - 0, 0, proposerInitialBalance-proposalBondAmount-initialBaseFee, // unlocked - ) - - // Fast-forward clock to time when proposals start, so we can vote - vm.clock.Set(proposal.StartTime()) - voteBytes, err := txs.Codec.Marshal(txs.Version, &txs.VoteWrapper{Vote: &dac.SimpleVote{OptionIndex: 0}}) - require.NoError(err) - - // Vote on proposal by validator0 - ins, outs, signers, _, err = vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - 0, - initialBaseFee, - locked.StateUnlocked, - nil, nil, 0, - ) - require.NoError(err) - addVoteTx0, err := txs.NewSigned(&txs.AddVoteTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - ProposalID: proposalTx.ID(), - VotePayload: voteBytes, - VoterAddress: caminoPreFundedKeys[0].Address(), - VoterAuth: &secp256k1fx.Input{SigIndices: []uint32{0}}, - }, txs.Codec, append(signers, []*secp256k1.PrivateKey{caminoPreFundedKeys[0]})) - require.NoError(err) - blk = buildAndAcceptBlock(t, vm, addVoteTx0) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), addVoteTx0.ID()) - proposalState, err = vm.state.GetProposal(proposalTx.ID()) - require.NoError(err) - baseFeeProposal, ok := proposalState.(*dac.BaseFeeProposalState) - require.True(ok) - require.EqualValues(1, baseFeeProposal.Options[0].Weight) - nextProposalIDsToExpire, nexExpirationTime, err = vm.state.GetNextToExpireProposalIDsAndTime(nil) - require.NoError(err) - require.Equal([]ids.ID{proposalTx.ID()}, nextProposalIDsToExpire) - require.Equal(proposalState.EndTime(), nexExpirationTime) - proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Empty(proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*2, // total - proposalBondAmount, // bonded - 0, 0, proposerInitialBalance-proposalBondAmount-initialBaseFee*2, // unlocked - ) - - // Vote on proposal by validator1 - ins, outs, signers, _, err = vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - 0, - initialBaseFee, - locked.StateUnlocked, - nil, nil, 0, - ) - require.NoError(err) - addVoteTx1, err := txs.NewSigned(&txs.AddVoteTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - ProposalID: proposalTx.ID(), - VotePayload: voteBytes, - VoterAddress: caminoPreFundedKeys[1].Address(), - VoterAuth: &secp256k1fx.Input{SigIndices: []uint32{0}}, - }, txs.Codec, append(signers, []*secp256k1.PrivateKey{caminoPreFundedKeys[1]})) - require.NoError(err) - blk = buildAndAcceptBlock(t, vm, addVoteTx1) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), addVoteTx1.ID()) - proposalState, err = vm.state.GetProposal(proposalTx.ID()) - require.NoError(err) - baseFeeProposal, ok = proposalState.(*dac.BaseFeeProposalState) - require.True(ok) - require.EqualValues(2, baseFeeProposal.Options[0].Weight) - require.EqualValues(2, baseFeeProposal.EarlySuccessThreshold) - nextProposalIDsToExpire, nexExpirationTime, err = vm.state.GetNextToExpireProposalIDsAndTime(nil) - require.NoError(err) - require.Equal([]ids.ID{proposalTx.ID()}, nextProposalIDsToExpire) - require.Equal(proposalState.EndTime(), nexExpirationTime) - proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Empty(proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*3, // total - proposalBondAmount, // bonded - 0, 0, proposerInitialBalance-proposalBondAmount-initialBaseFee*3, // unlocked - ) + "Fail: 2|1|1 votes, not reaching mostVoted threshold": { + feeOptions: []uint64{1, 2, 3}, + votes: []vote{ + {option: 0}, + {option: 0}, + {option: 1, success: true}, + {option: 2}, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + require := require.New(t) + balance := proposalBondAmount + defaultTxFee*(uint64(len(tt.votes))+1) + newFee + + // Prepare vm + vm := newCaminoVM(api.Camino{ + VerifyNodeSignature: true, + LockModeBondDeposit: true, + InitialAdmin: caminoPreFundedKeys[0].Address(), + }, []api.UTXO{ + { + Amount: json.Uint64(balance), + Address: proposerAddrStr, + }, + { + Amount: json.Uint64(defaultTxFee), + Address: caminoPreFundedKey0AddrStr, + }, + }, &defaultConfig.BanffTime) + vm.ctx.Lock.Lock() + defer func() { require.NoError(vm.Shutdown(context.Background())) }() //nolint:lint + checkBalance(t, vm.state, proposerAddr, + balance, // total + 0, 0, 0, balance, // unlocked + ) + + fee := defaultTxFee + burnedAmt := uint64(0) + + // Give proposer address role to make proposals + addrStateTx, err := vm.txBuilder.NewAddressStateTx( + proposerAddr, + false, + txs.AddressStateBitCaminoProposer, + []*secp256k1.PrivateKey{caminoPreFundedKeys[0]}, + nil, + ) + require.NoError(err) + blk := buildAndAcceptBlock(t, vm, addrStateTx) + require.Len(blk.Txs(), 1) + checkTx(t, vm, blk.ID(), addrStateTx.ID()) + + // Add proposal + chainTime := vm.state.GetTimestamp() + proposalTx := buildBaseFeeProposalTx(t, vm, proposerKey, proposalBondAmount, fee, + proposerKey, tt.feeOptions, chainTime.Add(100*time.Second), chainTime.Add(200*time.Second)) + proposalState, nextProposalIDsToExpire, nexExpirationTime, proposalIDsToFinish := makeProposalWithTx(t, vm, proposalTx) + require.EqualValues(5, proposalState.TotalAllowedVoters) // all 5 validators must vote + require.Equal([]ids.ID{proposalTx.ID()}, nextProposalIDsToExpire) // we have only one proposal + require.Equal(proposalState.EndTime(), nexExpirationTime) + require.Empty(proposalIDsToFinish) // no early-finished proposals + burnedAmt += fee + checkBalance(t, vm.state, proposerAddr, + balance-burnedAmt, // total + proposalBondAmount, // bonded + 0, 0, balance-proposalBondAmount-burnedAmt, // unlocked + ) + + // Fast-forward clock to time a bit forward, but still before proposals start + // Try to vote on proposal, expect to fail + vm.clock.Set(proposalState.StartTime().Add(-time.Second)) + addVoteTx := buildSimpleVoteTx(t, vm, proposerKey, fee, proposalTx.ID(), caminoPreFundedKeys[0], 0) + require.Error(vm.Builder.AddUnverifiedTx(addVoteTx)) + vm.clock.Set(proposalState.StartTime()) + + optionWeights := make([]uint32, len(proposalState.Options)) + for i, vote := range tt.votes { + optionWeights[vote.option]++ + voteTx := buildSimpleVoteTx(t, vm, proposerKey, fee, proposalTx.ID(), caminoPreFundedKeys[i], vote.option) + proposalIDsToFinish, proposalState = voteOnBaseFeeWithTx(t, vm, voteTx, proposalTx.ID(), optionWeights) + if tt.earlyFinish && i == len(tt.votes)-1 { + require.Equal([]ids.ID{proposalTx.ID()}, proposalIDsToFinish) // proposal has finished early + } else { + require.Empty(proposalIDsToFinish) // no early-finished proposals + } + nextProposalIDsToExpire, nexExpirationTime, err := vm.state.GetNextToExpireProposalIDsAndTime(nil) + require.NoError(err) + require.Equal([]ids.ID{proposalTx.ID()}, nextProposalIDsToExpire) + require.Equal(proposalState.EndTime(), nexExpirationTime) + require.Equal(tt.earlyFinish && i == len(tt.votes)-1, proposalState.CanBeFinished()) + require.Equal(vote.success, proposalState.IsSuccessful()) + burnedAmt += fee + checkBalance(t, vm.state, proposerAddr, + balance-burnedAmt, // total + proposalBondAmount, // bonded + 0, 0, balance-proposalBondAmount-burnedAmt, // unlocked + ) + } - // Vote on proposal by validator2 - ins, outs, signers, _, err = vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - 0, - initialBaseFee, - locked.StateUnlocked, - nil, nil, 0, - ) - require.NoError(err) - addVoteTx2, err := txs.NewSigned(&txs.AddVoteTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - ProposalID: proposalTx.ID(), - VotePayload: voteBytes, - VoterAddress: caminoPreFundedKeys[2].Address(), - VoterAuth: &secp256k1fx.Input{SigIndices: []uint32{0}}, - }, txs.Codec, append(signers, []*secp256k1.PrivateKey{caminoPreFundedKeys[2]})) - require.NoError(err) - blk = buildAndAcceptBlock(t, vm, addVoteTx2) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), addVoteTx2.ID()) - proposalState, err = vm.state.GetProposal(proposalTx.ID()) - require.NoError(err) - baseFeeProposal, ok = proposalState.(*dac.BaseFeeProposalState) - require.True(ok) - require.EqualValues(3, baseFeeProposal.Options[0].Weight) - nextProposalIDsToExpire, nexExpirationTime, err = vm.state.GetNextToExpireProposalIDsAndTime(nil) - require.NoError(err) - require.Equal([]ids.ID{proposalTx.ID()}, nextProposalIDsToExpire) - require.Equal(proposalState.EndTime(), nexExpirationTime) - proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Equal([]ids.ID{proposalTx.ID()}, proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*4, // total - proposalBondAmount, // bonded - 0, 0, proposerInitialBalance-proposalBondAmount-initialBaseFee*4, // unlocked - ) + if !tt.earlyFinish { // no early finish + vm.clock.Set(proposalState.EndTime()) + } - // Proposal votes threshold is reached, so its automatically executed - blk = buildAndAcceptBlock(t, vm, nil) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), blk.Txs()[0].ID()) - // proposals 1 is unambiguous and should be removed from state with execution - _, err = vm.state.GetProposal(proposalTx.ID()) - require.ErrorIs(err, database.ErrNotFound) - baseFee, err := vm.state.GetBaseFee() - require.NoError(err) - require.Equal(newBaseFee, baseFee) - _, _, err = vm.state.GetNextToExpireProposalIDsAndTime(nil) - require.ErrorIs(err, database.ErrNotFound) - proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() - require.NoError(err) - require.Empty(proposalIDsToFinish) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*4, // total - 0, 0, 0, proposerInitialBalance-initialBaseFee*4, // unlocked - ) + blk = buildAndAcceptBlock(t, vm, nil) + require.Len(blk.Txs(), 1) + checkTx(t, vm, blk.ID(), blk.Txs()[0].ID()) + _, err = vm.state.GetProposal(proposalTx.ID()) + require.ErrorIs(err, database.ErrNotFound) + _, _, err = vm.state.GetNextToExpireProposalIDsAndTime(nil) + require.ErrorIs(err, database.ErrNotFound) + proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() + require.NoError(err) + require.Empty(proposalIDsToFinish) + checkBalance(t, vm.state, proposerAddr, + balance-burnedAmt, // total + 0, 0, 0, balance-burnedAmt, // unlocked + ) + + if len(tt.votes) != 0 && tt.votes[len(tt.votes)-1].success { // last vote + fee = tt.feeOptions[tt.winningOption] + baseFee, err := vm.state.GetBaseFee() + require.NoError(err) + require.Equal(fee, baseFee) // fee has changed + } - // Create arbitrary tx to verify that it will use new base fee - ins, outs, signers, _, err = vm.txBuilder.Lock( - vm.state, - []*secp256k1.PrivateKey{proposerKey}, - 0, - newBaseFee, - locked.StateUnlocked, - nil, nil, 0, - ) - require.NoError(err) - feeTestingTx, err := txs.NewSigned(&txs.MultisigAliasTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: vm.ctx.NetworkID, - BlockchainID: vm.ctx.ChainID, - Ins: ins, - Outs: outs, - }}, - MultisigAlias: multisig.Alias{ - Owners: &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{proposerAddr}, - }, - }, - Auth: &secp256k1fx.Input{}, - }, txs.Codec, signers) - require.NoError(err) - blk = buildAndAcceptBlock(t, vm, feeTestingTx) - require.Len(blk.Txs(), 1) - checkTx(t, vm, blk.ID(), feeTestingTx.ID()) - checkBalance(t, vm.state, proposerAddr, - proposerInitialBalance-initialBaseFee*4-newBaseFee, // total - 0, 0, 0, proposerInitialBalance-initialBaseFee*4-newBaseFee, // unlocked - ) + // Create arbitrary tx to verify which fee is used + buildAndAcceptBaseTx(t, vm, proposerKey, fee) + burnedAmt += fee + checkBalance(t, vm.state, proposerAddr, + balance-burnedAmt, // total + 0, 0, 0, balance-burnedAmt, // unlocked + ) + }) + } } func buildAndAcceptBlock(t *testing.T, vm *VM, tx *txs.Tx) blocks.Block { @@ -1353,3 +802,160 @@ func checkTx(t *testing.T, vm *VM, blkID, txID ids.ID) { require.NoError(t, err) require.Equal(t, status.Committed, txStatus) } + +func buildBaseFeeProposalTx( + t *testing.T, + vm *VM, + fundsKey *secp256k1.PrivateKey, + amountToBond uint64, + amountToBurn uint64, + proposerKey *secp256k1.PrivateKey, + options []uint64, + startTime time.Time, + endTime time.Time, +) *txs.Tx { + t.Helper() + ins, outs, signers, _, err := vm.txBuilder.Lock( + vm.state, + []*secp256k1.PrivateKey{fundsKey}, + amountToBond, + amountToBurn, + locked.StateBonded, + nil, nil, 0, + ) + require.NoError(t, err) + proposal := &txs.ProposalWrapper{Proposal: &dac.BaseFeeProposal{ + Start: uint64(startTime.Unix()), + End: uint64(endTime.Unix()), + Options: options, + }} + proposalBytes, err := txs.Codec.Marshal(txs.Version, proposal) + require.NoError(t, err) + proposalTx, err := txs.NewSigned(&txs.AddProposalTx{ + BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ + NetworkID: vm.ctx.NetworkID, + BlockchainID: vm.ctx.ChainID, + Ins: ins, + Outs: outs, + }}, + ProposalPayload: proposalBytes, + ProposerAddress: proposerKey.Address(), + ProposerAuth: &secp256k1fx.Input{SigIndices: []uint32{0}}, + }, txs.Codec, append(signers, []*secp256k1.PrivateKey{proposerKey})) + require.NoError(t, err) + return proposalTx +} + +func makeProposalWithTx( + t *testing.T, + vm *VM, + tx *txs.Tx, +) ( + proposal *dac.BaseFeeProposalState, + nextProposalIDsToExpire []ids.ID, + nexExpirationTime time.Time, + proposalIDsToFinish []ids.ID, +) { + t.Helper() + blk := buildAndAcceptBlock(t, vm, tx) + require.Len(t, blk.Txs(), 1) + checkTx(t, vm, blk.ID(), tx.ID()) + proposalState, err := vm.state.GetProposal(tx.ID()) + require.NoError(t, err) + baseFeeProposalState, ok := proposalState.(*dac.BaseFeeProposalState) + require.True(t, ok) + nextProposalIDsToExpire, nexExpirationTime, err = vm.state.GetNextToExpireProposalIDsAndTime(nil) + require.NoError(t, err) + proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() + require.NoError(t, err) + return baseFeeProposalState, nextProposalIDsToExpire, nexExpirationTime, proposalIDsToFinish +} + +func buildSimpleVoteTx( + t *testing.T, + vm *VM, + fundsKey *secp256k1.PrivateKey, + amountToBurn uint64, + proposalID ids.ID, + voterKey *secp256k1.PrivateKey, + votedOption uint32, +) *txs.Tx { + t.Helper() + ins, outs, signers, _, err := vm.txBuilder.Lock( + vm.state, + []*secp256k1.PrivateKey{fundsKey}, + 0, + amountToBurn, + locked.StateUnlocked, + nil, nil, 0, + ) + require.NoError(t, err) + voteBytes, err := txs.Codec.Marshal(txs.Version, &txs.VoteWrapper{Vote: &dac.SimpleVote{OptionIndex: votedOption}}) + require.NoError(t, err) + addVoteTx, err := txs.NewSigned(&txs.AddVoteTx{ + BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ + NetworkID: vm.ctx.NetworkID, + BlockchainID: vm.ctx.ChainID, + Ins: ins, + Outs: outs, + }}, + ProposalID: proposalID, + VotePayload: voteBytes, + VoterAddress: voterKey.Address(), + VoterAuth: &secp256k1fx.Input{SigIndices: []uint32{0}}, + }, txs.Codec, append(signers, []*secp256k1.PrivateKey{voterKey})) + require.NoError(t, err) + return addVoteTx +} + +func voteOnBaseFeeWithTx( + t *testing.T, + vm *VM, + tx *txs.Tx, + proposalID ids.ID, + expectedVoteWeights []uint32, +) (proposalIDsToFinish []ids.ID, baseFeeProposalState *dac.BaseFeeProposalState) { + t.Helper() + blk := buildAndAcceptBlock(t, vm, tx) + require.Len(t, blk.Txs(), 1) + checkTx(t, vm, blk.ID(), tx.ID()) + proposalState, err := vm.state.GetProposal(proposalID) + require.NoError(t, err) + baseFeeProposalState, ok := proposalState.(*dac.BaseFeeProposalState) + require.True(t, ok) + require.Len(t, baseFeeProposalState.Options, len(expectedVoteWeights)) + for i := range baseFeeProposalState.Options { + require.Equal(t, expectedVoteWeights[i], baseFeeProposalState.Options[i].Weight) + } + proposalIDsToFinish, err = vm.state.GetProposalIDsToFinish() + require.NoError(t, err) + return proposalIDsToFinish, baseFeeProposalState +} + +func buildAndAcceptBaseTx( + t *testing.T, + vm *VM, + fundsKey *secp256k1.PrivateKey, + amountToBurn uint64, +) { + t.Helper() + ins, outs, signers, _, err := vm.txBuilder.Lock( + vm.state, + []*secp256k1.PrivateKey{fundsKey}, + 0, + amountToBurn, + locked.StateUnlocked, + nil, nil, 0, + ) + require.NoError(t, err) + feeTestingTx, err := txs.NewSigned(&txs.BaseTx{BaseTx: avax.BaseTx{ + NetworkID: vm.ctx.NetworkID, + BlockchainID: vm.ctx.ChainID, + Ins: ins, + Outs: outs, + }}, txs.Codec, signers) + require.NoError(t, err) + blk := buildAndAcceptBlock(t, vm, feeTestingTx) + require.Len(t, blk.Txs(), 1) + checkTx(t, vm, blk.ID(), feeTestingTx.ID()) +} diff --git a/vms/platformvm/dac/camino_change_base_fee_proposal.go b/vms/platformvm/dac/camino_change_base_fee_proposal.go index 09a530bc9af3..411092dae9a6 100644 --- a/vms/platformvm/dac/camino_change_base_fee_proposal.go +++ b/vms/platformvm/dac/camino_change_base_fee_proposal.go @@ -58,11 +58,10 @@ func (p *BaseFeeProposal) CreateProposalState(allowedVoters []ids.ShortID) Propo SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: make([]SimpleVoteOption[uint64], len(p.Options)), }, - Start: p.Start, - End: p.End, - AllowedVoters: allowedVoters, - EarlySuccessThreshold: uint32(len(allowedVoters) / 2), - FinishThreshold: uint32(len(allowedVoters)), + Start: p.Start, + End: p.End, + AllowedVoters: allowedVoters, + TotalAllowedVoters: uint32(len(allowedVoters)), } for i := range p.Options { stateProposal.Options[i].Value = p.Options[i] @@ -77,11 +76,10 @@ func (p *BaseFeeProposal) Visit(visitor VerifierVisitor) error { type BaseFeeProposalState struct { SimpleVoteOptions[uint64] `serialize:"true"` - Start uint64 `serialize:"true"` - End uint64 `serialize:"true"` - AllowedVoters []ids.ShortID `serialize:"true"` - EarlySuccessThreshold uint32 `serialize:"true"` - FinishThreshold uint32 `serialize:"true"` + Start uint64 `serialize:"true"` + End uint64 `serialize:"true"` + AllowedVoters []ids.ShortID `serialize:"true"` + TotalAllowedVoters uint32 `serialize:"true"` } func (p *BaseFeeProposalState) StartTime() time.Time { @@ -99,16 +97,14 @@ func (p *BaseFeeProposalState) IsActiveAt(time time.Time) bool { func (p *BaseFeeProposalState) CanBeFinished() bool { mostVotedWeight, _, unambiguous := p.GetMostVoted() - voted := uint32(0) - for i := range p.Options { - voted += p.Options[i].Weight - } - return voted == p.FinishThreshold || (unambiguous && mostVotedWeight > p.EarlySuccessThreshold) + voted := p.Voted() + return voted == p.TotalAllowedVoters || unambiguous && mostVotedWeight > p.TotalAllowedVoters/2 } func (p *BaseFeeProposalState) IsSuccessful() bool { - _, _, unambiguous := p.GetMostVoted() - return unambiguous + mostVotedWeight, _, unambiguous := p.GetMostVoted() + voted := p.Voted() + return unambiguous && voted > p.TotalAllowedVoters/2 && mostVotedWeight > voted/2 } func (p *BaseFeeProposalState) Outcome() any { @@ -149,7 +145,7 @@ func (p *BaseFeeProposalState) AddVote(voterAddress ids.ShortID, voteIntf Vote) SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: make([]SimpleVoteOption[uint64], len(p.Options)), }, - EarlySuccessThreshold: p.EarlySuccessThreshold, + TotalAllowedVoters: p.TotalAllowedVoters, } // we can't use the same slice, cause we need to change its elements copy(updatedProposal.AllowedVoters, p.AllowedVoters[:voterAddrPos]) diff --git a/vms/platformvm/dac/camino_change_base_fee_proposal_test.go b/vms/platformvm/dac/camino_change_base_fee_proposal_test.go index 545559df2de8..061a89da38a0 100644 --- a/vms/platformvm/dac/camino_change_base_fee_proposal_test.go +++ b/vms/platformvm/dac/camino_change_base_fee_proposal_test.go @@ -35,8 +35,7 @@ func TestBaseFeeProposalCreateProposalState(t *testing.T) { {Value: 7}, }, }, - EarlySuccessThreshold: 2, - FinishThreshold: 4, + TotalAllowedVoters: 4, }, expectedProposal: &BaseFeeProposal{ Start: 100, @@ -62,8 +61,7 @@ func TestBaseFeeProposalCreateProposalState(t *testing.T) { {Value: 7}, }, }, - EarlySuccessThreshold: 2, - FinishThreshold: 5, + TotalAllowedVoters: 5, }, expectedProposal: &BaseFeeProposal{ Start: 100, @@ -96,9 +94,10 @@ func TestBaseFeeProposalStateAddVote(t *testing.T) { }{ "Wrong vote type": { proposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{voterAddr1}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{voterAddr1}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{ {Value: 10, Weight: 2}, // 0 @@ -113,9 +112,10 @@ func TestBaseFeeProposalStateAddVote(t *testing.T) { voterAddr: voterAddr1, vote: &DummyVote{}, // not *SimpleVote expectedOriginalProposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{voterAddr1}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{voterAddr1}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{ {Value: 10, Weight: 2}, // 0 @@ -131,9 +131,10 @@ func TestBaseFeeProposalStateAddVote(t *testing.T) { }, "Wrong vote option index": { proposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{voterAddr1}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{voterAddr1}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{ {Value: 10, Weight: 2}, // 0 @@ -148,9 +149,10 @@ func TestBaseFeeProposalStateAddVote(t *testing.T) { voterAddr: ids.ShortID{3}, vote: &SimpleVote{OptionIndex: 3}, expectedOriginalProposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{voterAddr1}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{voterAddr1}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{ {Value: 10, Weight: 2}, // 0 @@ -183,9 +185,10 @@ func TestBaseFeeProposalStateAddVote(t *testing.T) { }, "OK: adding vote to not voted option": { proposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{voterAddr1}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{voterAddr1}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{ {Value: 10, Weight: 2}, // 0 @@ -200,9 +203,10 @@ func TestBaseFeeProposalStateAddVote(t *testing.T) { voterAddr: voterAddr1, vote: &SimpleVote{OptionIndex: 1}, expectedUpdatedProposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{ {Value: 10, Weight: 2}, // 0 @@ -212,9 +216,10 @@ func TestBaseFeeProposalStateAddVote(t *testing.T) { }, }, expectedOriginalProposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{voterAddr1}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{voterAddr1}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{ {Value: 10, Weight: 2}, // 0 @@ -229,9 +234,10 @@ func TestBaseFeeProposalStateAddVote(t *testing.T) { }, "OK: adding vote to already voted option": { proposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{voterAddr1}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{voterAddr1}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{ {Value: 10, Weight: 2}, // 0 @@ -246,9 +252,10 @@ func TestBaseFeeProposalStateAddVote(t *testing.T) { voterAddr: voterAddr1, vote: &SimpleVote{OptionIndex: 2}, expectedUpdatedProposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{ {Value: 10, Weight: 2}, // 0 @@ -258,9 +265,10 @@ func TestBaseFeeProposalStateAddVote(t *testing.T) { }, }, expectedOriginalProposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{voterAddr1}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{voterAddr1}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{ {Value: 10, Weight: 2}, // 0 @@ -275,9 +283,10 @@ func TestBaseFeeProposalStateAddVote(t *testing.T) { }, "OK: voter addr in the middle of allowedVoters array": { proposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{voterAddr1, voterAddr2, voterAddr3}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{voterAddr1, voterAddr2, voterAddr3}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{{Value: 1}}, }, @@ -285,17 +294,19 @@ func TestBaseFeeProposalStateAddVote(t *testing.T) { voterAddr: voterAddr2, vote: &SimpleVote{OptionIndex: 0}, expectedUpdatedProposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{voterAddr1, voterAddr3}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{voterAddr1, voterAddr3}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{{Value: 1, Weight: 1}}, }, }, expectedOriginalProposal: &BaseFeeProposalState{ - Start: 100, - End: 101, - AllowedVoters: []ids.ShortID{voterAddr1, voterAddr2, voterAddr3}, + Start: 100, + End: 101, + TotalAllowedVoters: 555, + AllowedVoters: []ids.ShortID{voterAddr1, voterAddr2, voterAddr3}, SimpleVoteOptions: SimpleVoteOptions[uint64]{ Options: []SimpleVoteOption[uint64]{{Value: 1}}, }, diff --git a/vms/platformvm/dac/camino_vote.go b/vms/platformvm/dac/camino_vote.go index a2bade21f06c..e4d8435bec93 100644 --- a/vms/platformvm/dac/camino_vote.go +++ b/vms/platformvm/dac/camino_vote.go @@ -95,6 +95,14 @@ func (p SimpleVoteOptions[T]) GetMostVoted() ( return p.mostVotedWeight, p.mostVotedOptionIndex, p.unambiguous } +func (p SimpleVoteOptions[T]) Voted() uint32 { + voted := uint32(0) + for i := range p.Options { + voted += p.Options[i].Weight + } + return voted +} + var _ Vote = (*DummyVote)(nil) type DummyVote struct { diff --git a/vms/platformvm/state/camino.go b/vms/platformvm/state/camino.go index dd34d56e189c..8c5227c0d15a 100644 --- a/vms/platformvm/state/camino.go +++ b/vms/platformvm/state/camino.go @@ -176,8 +176,7 @@ type caminoDiff struct { modifiedShortLinks map[ids.ID]*ids.ShortID modifiedClaimables map[ids.ID]*Claimable modifiedProposals map[ids.ID]*proposalDiff - addedProposalIDsToFinish []ids.ID - removedProposalIDsToFinish map[ids.ID]struct{} + modifiedProposalIDsToFinish map[ids.ID]bool modifiedNotDistributedValidatorReward *uint64 modifiedBaseFee *uint64 } @@ -231,19 +230,18 @@ type caminoState struct { proposalsDB database.Database proposalIDsByEndtimeDB database.Database proposalIDsToFinishDB database.Database - proposalIDsToFinishList linkeddb.LinkedDB // it is important to have them in order they were added } func newCaminoDiff() *caminoDiff { return &caminoDiff{ - modifiedAddressStates: make(map[ids.ShortID]txs.AddressState), - modifiedDepositOffers: make(map[ids.ID]*deposit.Offer), - modifiedDeposits: make(map[ids.ID]*depositDiff), - modifiedMultisigAliases: make(map[ids.ShortID]*multisig.AliasWithNonce), - modifiedShortLinks: make(map[ids.ID]*ids.ShortID), - modifiedClaimables: make(map[ids.ID]*Claimable), - modifiedProposals: make(map[ids.ID]*proposalDiff), - removedProposalIDsToFinish: make(map[ids.ID]struct{}), + modifiedAddressStates: make(map[ids.ShortID]txs.AddressState), + modifiedDepositOffers: make(map[ids.ID]*deposit.Offer), + modifiedDeposits: make(map[ids.ID]*depositDiff), + modifiedMultisigAliases: make(map[ids.ShortID]*multisig.AliasWithNonce), + modifiedShortLinks: make(map[ids.ID]*ids.ShortID), + modifiedClaimables: make(map[ids.ID]*Claimable), + modifiedProposals: make(map[ids.ID]*proposalDiff), + modifiedProposalIDsToFinish: make(map[ids.ID]bool), } } @@ -303,7 +301,6 @@ func newCaminoState(baseDB, validatorsDB database.Database, metricsReg prometheu } deferredValidatorsDB := prefixdb.New(deferredPrefix, validatorsDB) - proposalIDsToFinishDB := prefixdb.New(proposalIDsToFinishPrefix, baseDB) return &caminoState{ // Address State @@ -336,11 +333,10 @@ func newCaminoState(baseDB, validatorsDB database.Database, metricsReg prometheu deferredValidatorsDB: deferredValidatorsDB, deferredValidatorList: linkeddb.NewDefault(deferredValidatorsDB), - proposalsCache: proposalsCache, - proposalsDB: prefixdb.New(proposalsPrefix, baseDB), - proposalIDsByEndtimeDB: prefixdb.New(proposalIDsByEndtimePrefix, baseDB), - proposalIDsToFinishDB: proposalIDsToFinishDB, - proposalIDsToFinishList: linkeddb.NewDefault(proposalIDsToFinishDB), + proposalsCache: proposalsCache, + proposalsDB: prefixdb.New(proposalsPrefix, baseDB), + proposalIDsByEndtimeDB: prefixdb.New(proposalIDsByEndtimePrefix, baseDB), + proposalIDsToFinishDB: prefixdb.New(proposalIDsToFinishPrefix, baseDB), caminoDB: prefixdb.New(caminoPrefix, baseDB), caminoDiff: newCaminoDiff(), diff --git a/vms/platformvm/state/camino_diff.go b/vms/platformvm/state/camino_diff.go index 807d9cb2775f..9b507820da2e 100644 --- a/vms/platformvm/state/camino_diff.go +++ b/vms/platformvm/state/camino_diff.go @@ -420,7 +420,11 @@ func (d *diff) GetProposal(proposalID ids.ID) (dac.ProposalState, error) { } func (d *diff) AddProposalIDToFinish(proposalID ids.ID) { - d.caminoDiff.addedProposalIDsToFinish = append(d.caminoDiff.addedProposalIDsToFinish, proposalID) + d.caminoDiff.modifiedProposalIDsToFinish[proposalID] = true +} + +func (d *diff) RemoveProposalIDToFinish(proposalID ids.ID) { + d.caminoDiff.modifiedProposalIDsToFinish[proposalID] = false } func (d *diff) GetProposalIDsToFinish() ([]ids.ID, error) { @@ -434,23 +438,27 @@ func (d *diff) GetProposalIDsToFinish() ([]ids.ID, error) { return nil, err } - proposalIDsToFinish := make([]ids.ID, 0, len(parentProposalIDsToFinish)+len(d.caminoDiff.addedProposalIDsToFinish)-len(d.caminoDiff.removedProposalIDsToFinish)) + if len(d.caminoDiff.modifiedProposalIDsToFinish) == 0 { + return parentProposalIDsToFinish, nil + } + + uniqueProposalIDsToFinish := set.Set[ids.ID]{} + for _, proposalID := range parentProposalIDsToFinish { - if _, ok := d.caminoDiff.removedProposalIDsToFinish[proposalID]; !ok { - proposalIDsToFinish = append(proposalIDsToFinish, proposalID) + if notRemoved, ok := d.caminoDiff.modifiedProposalIDsToFinish[proposalID]; !ok || notRemoved { + uniqueProposalIDsToFinish.Add(proposalID) } } - for _, proposalID := range d.caminoDiff.addedProposalIDsToFinish { - if _, ok := d.caminoDiff.removedProposalIDsToFinish[proposalID]; !ok { - proposalIDsToFinish = append(proposalIDsToFinish, proposalID) + for proposalID, added := range d.caminoDiff.modifiedProposalIDsToFinish { + if added { + uniqueProposalIDsToFinish.Add(proposalID) } } - return proposalIDsToFinish, nil -} + proposalIDsToFinish := uniqueProposalIDsToFinish.List() + utils.Sort(proposalIDsToFinish) -func (d *diff) RemoveProposalIDToFinish(proposalID ids.ID) { - d.caminoDiff.removedProposalIDsToFinish[proposalID] = struct{}{} + return proposalIDsToFinish, nil } func (d *diff) GetNextProposalExpirationTime(removedProposalIDs set.Set[ids.ID]) (time.Time, error) { @@ -678,11 +686,12 @@ func (d *diff) ApplyCaminoState(baseState State) { } } - for _, proposalID := range d.caminoDiff.addedProposalIDsToFinish { - baseState.AddProposalIDToFinish(proposalID) - } - for proposalID := range d.caminoDiff.removedProposalIDsToFinish { - baseState.RemoveProposalIDToFinish(proposalID) + for proposalID, added := range d.caminoDiff.modifiedProposalIDsToFinish { + if added { + baseState.AddProposalIDToFinish(proposalID) + } else { + baseState.RemoveProposalIDToFinish(proposalID) + } } for _, validatorDiffs := range d.caminoDiff.deferredStakerDiffs.validatorDiffs { diff --git a/vms/platformvm/state/camino_diff_test.go b/vms/platformvm/state/camino_diff_test.go index d4c8d0b27c97..d20439687d53 100644 --- a/vms/platformvm/state/camino_diff_test.go +++ b/vms/platformvm/state/camino_diff_test.go @@ -3292,10 +3292,32 @@ func TestDiffAddProposalIDToFinish(t *testing.T) { "OK": { proposalID: proposalID3, diff: &diff{caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: true, + proposalID2: true, + }, }}, expectedDiff: &diff{caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2, proposalID3}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: true, + proposalID2: true, + proposalID3: true, + }, + }}, + }, + "OK: already exist": { + proposalID: proposalID2, + diff: &diff{caminoDiff: &caminoDiff{ + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: true, + proposalID2: true, + }, + }}, + expectedDiff: &diff{caminoDiff: &caminoDiff{ + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: true, + proposalID2: true, + }, }}, }, } @@ -3306,6 +3328,55 @@ func TestDiffAddProposalIDToFinish(t *testing.T) { }) } } +func TestDiffRemoveProposalIDToFinish(t *testing.T) { + proposalID1 := ids.ID{1} + proposalID2 := ids.ID{2} + proposalID3 := ids.ID{3} + + tests := map[string]struct { + diff *diff + proposalID ids.ID + expectedDiff *diff + }{ + "OK": { + proposalID: proposalID3, + diff: &diff{caminoDiff: &caminoDiff{ + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: false, + proposalID2: false, + }, + }}, + expectedDiff: &diff{caminoDiff: &caminoDiff{ + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: false, + proposalID2: false, + proposalID3: false, + }, + }}, + }, + "OK: not exist": { + proposalID: proposalID2, + diff: &diff{caminoDiff: &caminoDiff{ + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: false, + proposalID2: false, + }, + }}, + expectedDiff: &diff{caminoDiff: &caminoDiff{ + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: false, + proposalID2: false, + }, + }}, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + tt.diff.RemoveProposalIDToFinish(tt.proposalID) + require.Equal(t, tt.expectedDiff, tt.diff) + }) + } +} func TestDiffGetProposalIDsToFinish(t *testing.T) { parentStateID := ids.ID{1, 1} @@ -3313,6 +3384,8 @@ func TestDiffGetProposalIDsToFinish(t *testing.T) { proposalID2 := ids.ID{2} proposalID3 := ids.ID{3} proposalID4 := ids.ID{4} + proposalID5 := ids.ID{5} + proposalID6 := ids.ID{6} testErr := errors.New("test err") tests := map[string]struct { @@ -3357,7 +3430,7 @@ func TestDiffGetProposalIDsToFinish(t *testing.T) { caminoDiff: &caminoDiff{}, } }, - expectedProposalIDsToFinish: []ids.ID{}, + expectedProposalIDsToFinish: nil, }, "OK: only parent proposals to finish": { diff: func(c *gomock.Controller) *diff { @@ -3386,7 +3459,11 @@ func TestDiffGetProposalIDsToFinish(t *testing.T) { stateVersions: newMockStateVersions(c, parentStateID, parentState), parentID: parentStateID, caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: true, + proposalID2: false, + proposalID3: true, + }, }, } }, @@ -3395,21 +3472,30 @@ func TestDiffGetProposalIDsToFinish(t *testing.T) { stateVersions: actualDiff.stateVersions, parentID: actualDiff.parentID, caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: true, + proposalID2: false, + proposalID3: true, + }, }, } }, - expectedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2}, + expectedProposalIDsToFinish: []ids.ID{proposalID1, proposalID3}, }, "OK": { diff: func(c *gomock.Controller) *diff { parentState := NewMockChain(c) - parentState.EXPECT().GetProposalIDsToFinish().Return([]ids.ID{proposalID1, proposalID2}, nil) + parentState.EXPECT().GetProposalIDsToFinish().Return([]ids.ID{proposalID1, proposalID2, proposalID3}, nil) return &diff{ stateVersions: newMockStateVersions(c, parentStateID, parentState), parentID: parentStateID, caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID3, proposalID4}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID2: false, + proposalID4: true, + proposalID5: false, + proposalID6: true, + }, }, } }, @@ -3418,11 +3504,16 @@ func TestDiffGetProposalIDsToFinish(t *testing.T) { stateVersions: actualDiff.stateVersions, parentID: actualDiff.parentID, caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID3, proposalID4}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID2: false, + proposalID4: true, + proposalID5: false, + proposalID6: true, + }, }, } }, - expectedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2, proposalID3, proposalID4}, + expectedProposalIDsToFinish: []ids.ID{proposalID1, proposalID3, proposalID4, proposalID6}, }, } for name, tt := range tests { @@ -5084,8 +5175,9 @@ func TestDiffApplyCaminoState(t *testing.T) { }, }, removed: true}, }, - addedProposalIDsToFinish: []ids.ID{{17}, {18}, {19}}, - removedProposalIDsToFinish: map[ids.ID]struct{}{{20}: {}, {21}: {}}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + {17}: true, {18}: false, {19}: true, {20}: false, + }, modifiedNotDistributedValidatorReward: &reward, modifiedBaseFee: &baseFee, deferredStakerDiffs: diffStakers{}, @@ -5130,11 +5222,12 @@ func TestDiffApplyCaminoState(t *testing.T) { s.EXPECT().ModifyProposal(proposalID, proposalDiff.Proposal) } } - for _, proposalID := range d.caminoDiff.addedProposalIDsToFinish { - s.EXPECT().AddProposalIDToFinish(proposalID) - } - for proposalID := range d.caminoDiff.removedProposalIDsToFinish { - s.EXPECT().RemoveProposalIDToFinish(proposalID) + for proposalID, added := range d.caminoDiff.modifiedProposalIDsToFinish { + if added { + s.EXPECT().AddProposalIDToFinish(proposalID) + } else { + s.EXPECT().RemoveProposalIDToFinish(proposalID) + } } for _, validatorDiffs := range d.caminoDiff.deferredStakerDiffs.validatorDiffs { for _, validatorDiff := range validatorDiffs { @@ -5193,8 +5286,9 @@ func TestDiffApplyCaminoState(t *testing.T) { }, }, removed: true}, }, - addedProposalIDsToFinish: []ids.ID{{17}, {18}, {19}}, - removedProposalIDsToFinish: map[ids.ID]struct{}{{20}: {}, {21}: {}}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + {17}: true, {18}: false, {19}: true, {20}: false, + }, deferredStakerDiffs: diffStakers{}, modifiedNotDistributedValidatorReward: &reward, modifiedBaseFee: &baseFee, diff --git a/vms/platformvm/state/camino_proposal.go b/vms/platformvm/state/camino_proposal.go index d6d1d5edcfb2..b7792c03ec27 100644 --- a/vms/platformvm/state/camino_proposal.go +++ b/vms/platformvm/state/camino_proposal.go @@ -76,22 +76,32 @@ func (cs *caminoState) GetProposal(proposalID ids.ID) (dac.ProposalState, error) } func (cs *caminoState) AddProposalIDToFinish(proposalID ids.ID) { - cs.addedProposalIDsToFinish = append(cs.addedProposalIDsToFinish, proposalID) + cs.modifiedProposalIDsToFinish[proposalID] = true +} + +func (cs *caminoState) RemoveProposalIDToFinish(proposalID ids.ID) { + cs.modifiedProposalIDsToFinish[proposalID] = false } func (cs *caminoState) GetProposalIDsToFinish() ([]ids.ID, error) { - proposalIDsToFinish := make([]ids.ID, 0, len(cs.proposalIDsToFinish)+len(cs.addedProposalIDsToFinish)-len(cs.removedProposalIDsToFinish)) + if len(cs.modifiedProposalIDsToFinish) == 0 { + return cs.proposalIDsToFinish, nil + } + + uniqueProposalIDsToFinish := set.Set[ids.ID]{} for _, proposalID := range cs.proposalIDsToFinish { - if _, ok := cs.removedProposalIDsToFinish[proposalID]; !ok { - proposalIDsToFinish = append(proposalIDsToFinish, proposalID) + if notRemoved, ok := cs.modifiedProposalIDsToFinish[proposalID]; !ok || notRemoved { + uniqueProposalIDsToFinish.Add(proposalID) } } - for _, proposalID := range cs.addedProposalIDsToFinish { - if _, ok := cs.removedProposalIDsToFinish[proposalID]; !ok { - proposalIDsToFinish = append(proposalIDsToFinish, proposalID) + for proposalID, added := range cs.modifiedProposalIDsToFinish { + if added { + uniqueProposalIDsToFinish.Add(proposalID) } } + proposalIDsToFinish := uniqueProposalIDsToFinish.List() + utils.Sort(proposalIDsToFinish) return proposalIDsToFinish, nil } @@ -128,20 +138,13 @@ func (cs *caminoState) GetNextToExpireProposalIDsAndTime(removedProposalIDs set. return cs.getNextToExpireProposalIDsAndTimeFromDB(removedProposalIDs) } -// TODO@ only need to check for existing proposal of specific type -// TODO@ we don't allow 2 changeBaseFee proposals at the same time -// TODO@ maybe replace with singletone bool? func (cs *caminoState) GetProposalIterator() (ProposalsIterator, error) { return &proposalsIterator{ - dbIterator: cs.proposalsDB.NewIterator(), - modifiedProposals: cs.modifiedProposals, + dbIterator: cs.proposalsDB.NewIterator(), + caminoState: cs, }, nil } -func (cs *caminoState) RemoveProposalIDToFinish(proposalID ids.ID) { - cs.removedProposalIDsToFinish[proposalID] = struct{}{} -} - func (cs *caminoState) writeProposals() error { // checking if all current proposals were removed nextIDsIsEmpty := true @@ -228,30 +231,24 @@ func (cs *caminoState) writeProposals() error { // updating db of finished proposal ids - proposalIDsToFinish := make([]ids.ID, 0, len(cs.proposalIDsToFinish)+len(cs.addedProposalIDsToFinish)-len(cs.removedProposalIDsToFinish)) - for _, proposalID := range cs.proposalIDsToFinish { - if _, ok := cs.removedProposalIDsToFinish[proposalID]; !ok { - proposalIDsToFinish = append(proposalIDsToFinish, proposalID) - } + proposalIDsToFinish, err := cs.GetProposalIDsToFinish() + if err != nil { + return err // Won't happen cause err is always nil here. Just in case if we'll change that } - for _, proposalID := range cs.addedProposalIDsToFinish { - if _, ok := cs.removedProposalIDsToFinish[proposalID]; !ok { - if err := cs.proposalIDsToFinishList.Put(proposalID[:], nil); err != nil { + for proposalID, add := range cs.modifiedProposalIDsToFinish { + if add { + if err := cs.proposalIDsToFinishDB.Put(proposalID[:], nil); err != nil { + return err + } + } else { + if err := cs.proposalIDsToFinishDB.Delete(proposalID[:]); err != nil { return err } - proposalIDsToFinish = append(proposalIDsToFinish, proposalID) - } - } - - for proposalID := range cs.removedProposalIDsToFinish { - if err := cs.proposalIDsToFinishList.Delete(proposalID[:]); err != nil { - return err } - delete(cs.removedProposalIDsToFinish, proposalID) + delete(cs.modifiedProposalIDsToFinish, proposalID) } - cs.addedProposalIDsToFinish = nil cs.proposalIDsToFinish = proposalIDsToFinish return nil @@ -270,7 +267,7 @@ func (cs *caminoState) loadProposals() error { cs.proposalsNextExpirationTime = &proposalsNextExpirationTime // reading from db proposalIDs that are ready for execution - proposalsToFinishIterator := cs.proposalIDsToFinishList.NewIterator() + proposalsToFinishIterator := cs.proposalIDsToFinishDB.NewIterator() defer proposalsToFinishIterator.Release() for proposalsToFinishIterator.Next() { @@ -355,9 +352,9 @@ type ProposalsIterator interface { } type proposalsIterator struct { - dbIterator database.Iterator - modifiedProposals map[ids.ID]*proposalDiff - err error + caminoState *caminoState + dbIterator database.Iterator + err error } func (it *proposalsIterator) Next() bool { @@ -367,7 +364,7 @@ func (it *proposalsIterator) Next() bool { it.err = err return false } - if proposalDiff, ok := it.modifiedProposals[proposalID]; !ok || !proposalDiff.removed { + if proposalDiff, ok := it.caminoState.modifiedProposals[proposalID]; !ok || !proposalDiff.removed { return true } } @@ -379,14 +376,21 @@ func (it *proposalsIterator) Value() (dac.ProposalState, error) { if err != nil { // should never happen return nil, err } - if proposalDiff, ok := it.modifiedProposals[proposalID]; ok { + + if proposalDiff, ok := it.caminoState.modifiedProposals[proposalID]; ok { return proposalDiff.Proposal, nil } - proposal := &proposalStateWrapper{} // TODO@ get from cache ? + + if proposal, ok := it.caminoState.proposalsCache.Get(proposalID); ok { + return proposal, nil + } + + proposal := &proposalStateWrapper{} if _, err := txs.Codec.Unmarshal(it.dbIterator.Value(), proposal); err != nil { return nil, err } - return proposal, nil + + return proposal.ProposalState, nil } func (it *proposalsIterator) Error() error { diff --git a/vms/platformvm/state/camino_proposal_test.go b/vms/platformvm/state/camino_proposal_test.go index 17c533beb53b..38a18bc35043 100644 --- a/vms/platformvm/state/camino_proposal_test.go +++ b/vms/platformvm/state/camino_proposal_test.go @@ -10,7 +10,6 @@ import ( "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/database" - "github.com/ava-labs/avalanchego/database/linkeddb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/timer/mockable" @@ -30,7 +29,7 @@ func TestGetProposal(t *testing.T) { SimpleVoteOptions: dac.SimpleVoteOptions[uint64]{ Options: []dac.SimpleVoteOption[uint64]{{Value: 1234, Weight: 1}}, }, - EarlySuccessThreshold: 1, + TotalAllowedVoters: 5, }, } proposalBytes, err := blocks.GenesisCodec.Marshal(blocks.Version, wrapper) @@ -340,12 +339,38 @@ func TestAddProposalIDToFinish(t *testing.T) { proposalID: proposalID3, caminoState: &caminoState{ caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: true, + proposalID2: true, + }, + }, + }, + expectedCaminoState: &caminoState{ + caminoDiff: &caminoDiff{ + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: true, + proposalID2: true, + proposalID3: true, + }, + }, + }, + }, + "OK: already exist": { + proposalID: proposalID2, + caminoState: &caminoState{ + caminoDiff: &caminoDiff{ + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: true, + proposalID2: true, + }, }, }, expectedCaminoState: &caminoState{ caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2, proposalID3}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: true, + proposalID2: true, + }, }, }, }, @@ -358,11 +383,71 @@ func TestAddProposalIDToFinish(t *testing.T) { } } +func TestRemoveProposalIDToFinish(t *testing.T) { + proposalID1 := ids.ID{1} + proposalID2 := ids.ID{2} + proposalID3 := ids.ID{3} + + tests := map[string]struct { + caminoState *caminoState + proposalID ids.ID + expectedCaminoState *caminoState + }{ + "OK": { + proposalID: proposalID3, + caminoState: &caminoState{ + caminoDiff: &caminoDiff{ + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: false, + proposalID2: false, + }, + }, + }, + expectedCaminoState: &caminoState{ + caminoDiff: &caminoDiff{ + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: false, + proposalID2: false, + proposalID3: false, + }, + }, + }, + }, + "OK: not exist": { + proposalID: proposalID2, + caminoState: &caminoState{ + caminoDiff: &caminoDiff{ + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: false, + proposalID2: false, + }, + }, + }, + expectedCaminoState: &caminoState{ + caminoDiff: &caminoDiff{ + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: false, + proposalID2: false, + }, + }, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + tt.caminoState.RemoveProposalIDToFinish(tt.proposalID) + require.Equal(t, tt.expectedCaminoState, tt.caminoState) + }) + } +} + func TestGetProposalIDsToFinish(t *testing.T) { proposalID1 := ids.ID{1} proposalID2 := ids.ID{2} proposalID3 := ids.ID{3} proposalID4 := ids.ID{4} + proposalID5 := ids.ID{5} + proposalID6 := ids.ID{6} tests := map[string]struct { caminoState *caminoState @@ -373,7 +458,7 @@ func TestGetProposalIDsToFinish(t *testing.T) { "OK: no proposals to finish": { caminoState: &caminoState{caminoDiff: &caminoDiff{}}, expectedCaminoState: &caminoState{caminoDiff: &caminoDiff{}}, - expectedProposalIDsToFinish: []ids.ID{}, + expectedProposalIDsToFinish: nil, }, "OK: no new proposals to finish": { caminoState: &caminoState{ @@ -388,27 +473,43 @@ func TestGetProposalIDsToFinish(t *testing.T) { }, "OK: only new proposals to finish": { caminoState: &caminoState{caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: true, + proposalID2: true, + }, }}, expectedCaminoState: &caminoState{caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID1: true, + proposalID2: true, + }, }}, expectedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2}, }, "OK": { caminoState: &caminoState{ caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID3, proposalID4}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID2: false, + proposalID4: true, + proposalID5: false, + proposalID6: true, + }, }, - proposalIDsToFinish: []ids.ID{proposalID1, proposalID2}, + proposalIDsToFinish: []ids.ID{proposalID1, proposalID2, proposalID3}, }, expectedCaminoState: &caminoState{ caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID3, proposalID4}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID2: false, + proposalID4: true, + proposalID5: false, + proposalID6: true, + }, }, - proposalIDsToFinish: []ids.ID{proposalID1, proposalID2}, + proposalIDsToFinish: []ids.ID{proposalID1, proposalID2, proposalID3}, }, - expectedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2, proposalID3, proposalID4}, + expectedProposalIDsToFinish: []ids.ID{proposalID1, proposalID3, proposalID4, proposalID6}, }, } for name, tt := range tests { @@ -729,6 +830,9 @@ func TestWriteProposals(t *testing.T) { proposalID1 := ids.ID{1} proposalID2 := ids.ID{2} proposalID3 := ids.ID{3} + proposalID4 := ids.ID{4} + proposalID5 := ids.ID{5} + proposalID6 := ids.ID{6} proposalWrapper1 := &proposalStateWrapper{ProposalState: &dac.BaseFeeProposalState{ End: 10, @@ -829,11 +933,13 @@ func TestWriteProposals(t *testing.T) { }, expectedErr: testError, }, - "OK: add proposals-to-finish": { + "OK: add or remove proposals-to-finish": { caminoState: func(c *gomock.Controller) *caminoState { - proposalIDsToFinishList := linkeddb.NewMockLinkedDB(c) - proposalIDsToFinishList.EXPECT().Put(proposalID1[:], nil).Return(nil) - proposalIDsToFinishList.EXPECT().Put(proposalID2[:], nil).Return(nil) + proposalIDsToFinishDB := database.NewMockDatabase(c) + proposalIDsToFinishDB.EXPECT().Delete(proposalID2[:]).Return(nil) + proposalIDsToFinishDB.EXPECT().Put(proposalID4[:], nil).Return(nil) + proposalIDsToFinishDB.EXPECT().Delete(proposalID5[:]).Return(nil) + proposalIDsToFinishDB.EXPECT().Put(proposalID6[:], nil).Return(nil) proposalsIterator := database.NewMockIterator(c) proposalsIterator.EXPECT().Next().Return(false) @@ -844,19 +950,25 @@ func TestWriteProposals(t *testing.T) { proposalIDsByEndtimeDB.EXPECT().NewIterator().Return(proposalsIterator) return &caminoState{ - proposalIDsToFinishList: proposalIDsToFinishList, - proposalIDsByEndtimeDB: proposalIDsByEndtimeDB, + proposalIDsToFinishDB: proposalIDsToFinishDB, + proposalIDsByEndtimeDB: proposalIDsByEndtimeDB, caminoDiff: &caminoDiff{ - addedProposalIDsToFinish: []ids.ID{proposalID1, proposalID2}, + modifiedProposalIDsToFinish: map[ids.ID]bool{ + proposalID2: false, + proposalID4: true, + proposalID5: false, + proposalID6: true, + }, }, + proposalIDsToFinish: []ids.ID{proposalID1, proposalID2, proposalID3}, } }, expectedCaminoState: func(actualCaminoState *caminoState) *caminoState { return &caminoState{ - proposalIDsToFinishList: actualCaminoState.proposalIDsToFinishList, - proposalIDsByEndtimeDB: actualCaminoState.proposalIDsByEndtimeDB, - caminoDiff: &caminoDiff{}, - proposalIDsToFinish: []ids.ID{proposalID1, proposalID2}, + proposalIDsToFinishDB: actualCaminoState.proposalIDsToFinishDB, + proposalIDsByEndtimeDB: actualCaminoState.proposalIDsByEndtimeDB, + caminoDiff: &caminoDiff{modifiedProposalIDsToFinish: map[ids.ID]bool{}}, + proposalIDsToFinish: []ids.ID{proposalID1, proposalID3, proposalID4, proposalID6}, } }, }, @@ -894,7 +1006,6 @@ func TestWriteProposals(t *testing.T) { }, proposalsNextExpirationTime: &proposalEndtime, proposalsNextToExpireIDs: []ids.ID{proposalID1, proposalID2}, - proposalIDsToFinish: []ids.ID{}, } }, }, @@ -938,7 +1049,6 @@ func TestWriteProposals(t *testing.T) { }, proposalsNextExpirationTime: &proposalEndtime, proposalsNextToExpireIDs: []ids.ID{proposalID1}, - proposalIDsToFinish: []ids.ID{}, } }, }, @@ -990,19 +1100,19 @@ func TestLoadProposals(t *testing.T) { proposalsToFinishIterator.EXPECT().Error().Return(nil) proposalsToFinishIterator.EXPECT().Release() - proposalIDsToFinishList := linkeddb.NewMockLinkedDB(c) - proposalIDsToFinishList.EXPECT().NewIterator().Return(proposalsToFinishIterator) + proposalIDsToFinishDB := database.NewMockDatabase(c) + proposalIDsToFinishDB.EXPECT().NewIterator().Return(proposalsToFinishIterator) return &caminoState{ - proposalIDsByEndtimeDB: proposalIDsByEndtimeDB, - proposalIDsToFinishList: proposalIDsToFinishList, + proposalIDsByEndtimeDB: proposalIDsByEndtimeDB, + proposalIDsToFinishDB: proposalIDsToFinishDB, } }, expectedCaminoState: func(actualCaminoState *caminoState) *caminoState { nextTime := proposal1.EndTime() return &caminoState{ proposalIDsByEndtimeDB: actualCaminoState.proposalIDsByEndtimeDB, - proposalIDsToFinishList: actualCaminoState.proposalIDsToFinishList, + proposalIDsToFinishDB: actualCaminoState.proposalIDsToFinishDB, proposalsNextExpirationTime: &nextTime, proposalsNextToExpireIDs: []ids.ID{proposalID1, proposalID2}, proposalIDsToFinish: []ids.ID{proposalID2, proposalID4}, @@ -1037,52 +1147,3 @@ func TestLoadProposals(t *testing.T) { }) } } - -// Test that proposalIDsToFinish order is maintened with write/load -func TestProposalIDsToFinishOrder(t *testing.T) { - proposalID1 := ids.ID{1} - proposalID2 := ids.ID{2} - proposalID3 := ids.ID{3} - - proposalID4 := ids.ID{4} - proposalID5 := ids.ID{5} - proposalID6 := ids.ID{6} - - // initial state - state := newEmptyState(t) - caminoState, ok := state.caminoState.(*caminoState) - require.True(t, ok) - require.NoError(t, database.PutBool(caminoState.caminoDB, nodeSignatureKey, caminoState.verifyNodeSignature)) - require.NoError(t, database.PutBool(caminoState.caminoDB, depositBondModeKey, caminoState.lockModeBondDeposit)) - require.NoError(t, database.PutTimestamp(state.singletonDB, timestampKey, state.persistedTimestamp)) - require.NoError(t, database.PutUInt64(state.singletonDB, currentSupplyKey, state.persistedCurrentSupply)) - require.NoError(t, database.PutID(state.singletonDB, lastAcceptedKey, state.persistedLastAccepted)) - state.AddProposalIDToFinish(proposalID1) - state.AddProposalIDToFinish(proposalID2) - state.AddProposalIDToFinish(proposalID3) - require.NoError(t, state.Commit()) - proposalIDsToFinish, err := state.GetProposalIDsToFinish() - require.NoError(t, err) - require.Equal(t, []ids.ID{proposalID1, proposalID2, proposalID3}, proposalIDsToFinish) - - // adding new proposalIDs - state.AddProposalIDToFinish(proposalID4) - state.AddProposalIDToFinish(proposalID5) - state.AddProposalIDToFinish(proposalID6) - proposalIDsToFinish, err = state.GetProposalIDsToFinish() - require.NoError(t, err) - require.Equal(t, []ids.ID{proposalID1, proposalID2, proposalID3, proposalID4, proposalID5, proposalID6}, proposalIDsToFinish) - - // removing some proposalIDs from initial and from newly added - state.RemoveProposalIDToFinish(proposalID2) - state.RemoveProposalIDToFinish(proposalID4) - state.RemoveProposalIDToFinish(proposalID6) - proposalIDsToFinish, err = state.GetProposalIDsToFinish() - require.NoError(t, err) - require.Equal(t, []ids.ID{proposalID1, proposalID3, proposalID5}, proposalIDsToFinish) - - require.NoError(t, state.Commit()) - proposalIDsToFinish, err = state.GetProposalIDsToFinish() - require.NoError(t, err) - require.Equal(t, []ids.ID{proposalID1, proposalID3, proposalID5}, proposalIDsToFinish) -} diff --git a/vms/platformvm/txs/builder/camino_builder.go b/vms/platformvm/txs/builder/camino_builder.go index a47959f3caab..24c91e8fc7e4 100644 --- a/vms/platformvm/txs/builder/camino_builder.go +++ b/vms/platformvm/txs/builder/camino_builder.go @@ -701,21 +701,6 @@ func (b *caminoBuilder) FinishProposalsTx( return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err) } - expiredSuccessfulProposalIDs := []ids.ID{} - expiredFailedProposalIDs := []ids.ID{} - - for _, proposalID := range expiredProposalIDs { - proposal, err := b.state.GetProposal(proposalID) - if err != nil { - return nil, err - } - if proposal.IsSuccessful() { - expiredSuccessfulProposalIDs = append(expiredSuccessfulProposalIDs, proposalID) - } else { - expiredFailedProposalIDs = append(expiredFailedProposalIDs, proposalID) - } - } - utx := &txs.FinishProposalsTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.ctx.NetworkID, @@ -723,9 +708,8 @@ func (b *caminoBuilder) FinishProposalsTx( Ins: ins, Outs: outs, }}, - EarlyFinishedProposalIDs: earlyFinishedProposalIDs, - ExpiredSuccessfulProposalIDs: expiredSuccessfulProposalIDs, - ExpiredFailedProposalIDs: expiredFailedProposalIDs, + EarlyFinishedProposalIDs: earlyFinishedProposalIDs, + ExpiredProposalIDs: expiredProposalIDs, } tx, err := txs.NewSigned(utx, txs.Codec, nil) diff --git a/vms/platformvm/txs/camino_finish_proposals_tx.go b/vms/platformvm/txs/camino_finish_proposals_tx.go index 4acc1ecbf307..e4347155fe18 100644 --- a/vms/platformvm/txs/camino_finish_proposals_tx.go +++ b/vms/platformvm/txs/camino_finish_proposals_tx.go @@ -24,12 +24,10 @@ var ( type FinishProposalsTx struct { // Metadata, inputs and outputs BaseTx `serialize:"true"` - // Proposals that was finished early - they'll be removed and executed. + // Proposals that were finished early. EarlyFinishedProposalIDs []ids.ID `serialize:"true" json:"earlyFinishedProposalIDs"` - // Proposals that were expired, but still successful - they'll be removed and executed. - ExpiredSuccessfulProposalIDs []ids.ID `serialize:"true" json:"expiredSuccessfulProposalIDs"` - // Proposals that were expired - they'll be removed. - ExpiredFailedProposalIDs []ids.ID `serialize:"true" json:"expiredFailedProposalIDs"` + // Proposals that were expired. + ExpiredProposalIDs []ids.ID `serialize:"true" json:"expiredProposalIDs"` } // SyntacticVerify returns nil if [tx] is valid @@ -39,7 +37,7 @@ func (tx *FinishProposalsTx) SyntacticVerify(ctx *snow.Context) error { return ErrNilTx case tx.SyntacticallyVerified: // already passed syntactic verification return nil - case len(tx.EarlyFinishedProposalIDs) == 0 && len(tx.ExpiredSuccessfulProposalIDs) == 0 && len(tx.ExpiredFailedProposalIDs) == 0: + case len(tx.EarlyFinishedProposalIDs) == 0 && len(tx.ExpiredProposalIDs) == 0: return errNoFinishedProposals } @@ -47,7 +45,7 @@ func (tx *FinishProposalsTx) SyntacticVerify(ctx *snow.Context) error { return fmt.Errorf("failed to verify BaseTx: %w", err) } - uniqueProposals := set.NewSet[ids.ID](len(tx.EarlyFinishedProposalIDs) + len(tx.ExpiredSuccessfulProposalIDs) + len(tx.ExpiredFailedProposalIDs)) + uniqueProposals := set.NewSet[ids.ID](len(tx.EarlyFinishedProposalIDs) + len(tx.ExpiredProposalIDs)) for _, proposalID := range tx.EarlyFinishedProposalIDs { if uniqueProposals.Contains(proposalID) { return errNotUniqueProposalID @@ -55,14 +53,7 @@ func (tx *FinishProposalsTx) SyntacticVerify(ctx *snow.Context) error { uniqueProposals.Add(proposalID) } - for _, proposalID := range tx.ExpiredSuccessfulProposalIDs { - if uniqueProposals.Contains(proposalID) { - return errNotUniqueProposalID - } - uniqueProposals.Add(proposalID) - } - - for _, proposalID := range tx.ExpiredFailedProposalIDs { + for _, proposalID := range tx.ExpiredProposalIDs { if uniqueProposals.Contains(proposalID) { return errNotUniqueProposalID } diff --git a/vms/platformvm/txs/camino_finish_proposals_tx_test.go b/vms/platformvm/txs/camino_finish_proposals_tx_test.go index 94b3c9d7334b..571070a21ef7 100644 --- a/vms/platformvm/txs/camino_finish_proposals_tx_test.go +++ b/vms/platformvm/txs/camino_finish_proposals_tx_test.go @@ -21,8 +21,6 @@ func TestFinishProposalsTxSyntacticVerify(t *testing.T) { proposalID2 := ids.ID{2} proposalID3 := ids.ID{3} proposalID4 := ids.ID{4} - proposalID5 := ids.ID{5} - proposalID6 := ids.ID{6} baseTx := BaseTx{BaseTx: avax.BaseTx{ NetworkID: ctx.NetworkID, @@ -49,22 +47,21 @@ func TestFinishProposalsTxSyntacticVerify(t *testing.T) { }, expectedErr: errNotUniqueProposalID, }, - "Not unique proposals in ExpiredSuccessfulProposalIDs": { + "Not unique proposals in ExpiredProposalIDs": { tx: &FinishProposalsTx{ - BaseTx: baseTx, - ExpiredSuccessfulProposalIDs: []ids.ID{proposalID1, proposalID1}, + BaseTx: baseTx, + ExpiredProposalIDs: []ids.ID{proposalID1, proposalID1}, }, expectedErr: errNotUniqueProposalID, }, - "Not unique proposals in EarlyFinishedProposalIDs and ExpiredSuccessfulProposalIDs": { + "Not unique proposals in EarlyFinishedProposalIDs and ExpiredProposalIDs": { tx: &FinishProposalsTx{ - BaseTx: baseTx, - EarlyFinishedProposalIDs: []ids.ID{proposalID1}, - ExpiredSuccessfulProposalIDs: []ids.ID{proposalID1}, + BaseTx: baseTx, + EarlyFinishedProposalIDs: []ids.ID{proposalID1}, + ExpiredProposalIDs: []ids.ID{proposalID1}, }, expectedErr: errNotUniqueProposalID, }, - // TODO@ tests for ExpiredFailedProposalIDs "Stakable base tx input": { tx: &FinishProposalsTx{ BaseTx: BaseTx{BaseTx: avax.BaseTx{ @@ -93,10 +90,9 @@ func TestFinishProposalsTxSyntacticVerify(t *testing.T) { }, "OK": { tx: &FinishProposalsTx{ - BaseTx: baseTx, - EarlyFinishedProposalIDs: []ids.ID{proposalID1, proposalID2}, - ExpiredSuccessfulProposalIDs: []ids.ID{proposalID3, proposalID4}, - ExpiredFailedProposalIDs: []ids.ID{proposalID5, proposalID6}, + BaseTx: baseTx, + EarlyFinishedProposalIDs: []ids.ID{proposalID1, proposalID2}, + ExpiredProposalIDs: []ids.ID{proposalID3, proposalID4}, }, }, } diff --git a/vms/platformvm/txs/executor/camino_dac.go b/vms/platformvm/txs/executor/camino_dac.go index b0b6e450a606..b4e1c79b54f1 100644 --- a/vms/platformvm/txs/executor/camino_dac.go +++ b/vms/platformvm/txs/executor/camino_dac.go @@ -27,7 +27,11 @@ type proposalVerifier struct { addProposalTx *txs.AddProposalTx } -// executor calls should never error +// Executor calls should never error. +// We should always mind possible proposals conflict, when implementing proposal execution logic. +// Because when proposal is semantically verified, state doesn't know about changes +// that already existing proposals will bring into state on their execution. +// And proposal execution is a system tx, so it should always succeed. type proposalExecutor struct { state state.Chain fx fx.Fx @@ -60,10 +64,10 @@ func (e *proposalVerifier) BaseFeeProposal(*dac.BaseFeeProposal) error { } proposalsIterator, err := e.state.GetProposalIterator() - defer proposalsIterator.Release() if err != nil { return err } + defer proposalsIterator.Release() for proposalsIterator.Next() { proposal, err := proposalsIterator.Value() if err != nil { diff --git a/vms/platformvm/txs/executor/camino_tx_executor.go b/vms/platformvm/txs/executor/camino_tx_executor.go index 49479096ceb5..f0c44734ddcd 100644 --- a/vms/platformvm/txs/executor/camino_tx_executor.go +++ b/vms/platformvm/txs/executor/camino_tx_executor.go @@ -1817,8 +1817,7 @@ func (e *CaminoStandardTxExecutor) AddVoteTx(tx *txs.AddVoteTx) error { } if !proposal.IsActiveAt(chainTime) { - // should never happen, cause inactive proposals are removed from state - return errProposalInactive + return errProposalInactive // should never happen, cause inactive proposals are removed from state } // verify voter credential and address state (role) @@ -1883,6 +1882,7 @@ func (e *CaminoStandardTxExecutor) AddVoteTx(tx *txs.AddVoteTx) error { } e.State.ModifyProposal(tx.ProposalID, updatedProposal) + // If proposal became finishable, it cannot be reverted by future votes, even if they'll be accepted. if updatedProposal.CanBeFinished() { e.State.AddProposalIDToFinish(tx.ProposalID) } @@ -1917,8 +1917,7 @@ func (e *CaminoStandardTxExecutor) FinishProposalsTx(tx *txs.FinishProposalsTx) return errWrongCredentialsNumber } - lockTxIDs := append(tx.EarlyFinishedProposalIDs, tx.ExpiredFailedProposalIDs...) //nolint:gocritic - lockTxIDs = append(lockTxIDs, tx.ExpiredSuccessfulProposalIDs...) + lockTxIDs := append(tx.EarlyFinishedProposalIDs, tx.ExpiredProposalIDs...) //nolint:gocritic expectedIns, expectedOuts, err := e.FlowChecker.Unlock(e.State, lockTxIDs, locked.StateBonded) if err != nil { return err @@ -1940,43 +1939,33 @@ func (e *CaminoStandardTxExecutor) FinishProposalsTx(tx *txs.FinishProposalsTx) return err } - // ! @evlekht possible future critical issue: when adding new proposals, - // ! we can't foresee if proposals will conflict at the execution time - // ! suggestion: simply allow proposals to fail and finish them without execution if its the case - // ! possible issue: if allow to fail, then it could be hard to distinct - // ! case of corrupted state making proposal fail from conflicting proposal - // try to execute proposal - if err := proposal.Visit(e.proposalExecutor()); err != nil { - return err + if proposal.IsSuccessful() { + // try to execute proposal + if err := proposal.Visit(e.proposalExecutor()); err != nil { + return err + } } e.State.RemoveProposal(proposalID, proposal) e.State.RemoveProposalIDToFinish(proposalID) } - for _, proposalID := range tx.ExpiredSuccessfulProposalIDs { + for _, proposalID := range tx.ExpiredProposalIDs { proposal, err := e.State.GetProposal(proposalID) if err != nil { return err } - // ! @evlekht possible future critical issue: see ! comment above - // try to execute proposal - if err := proposal.Visit(e.proposalExecutor()); err != nil { - return err + if proposal.IsSuccessful() { + // try to execute proposal + if err := proposal.Visit(e.proposalExecutor()); err != nil { + return err + } } e.State.RemoveProposal(proposalID, proposal) } - for _, proposalID := range tx.ExpiredFailedProposalIDs { - proposal, err := e.State.GetProposal(proposalID) - if err != nil { - return err - } - e.State.RemoveProposal(proposalID, proposal) - } - avax.Consume(e.State, tx.Ins) avax.Produce(e.State, e.Tx.ID(), tx.Outs) diff --git a/vms/platformvm/txs/executor/camino_tx_executor_test.go b/vms/platformvm/txs/executor/camino_tx_executor_test.go index a79dada59325..590f7a756e4d 100644 --- a/vms/platformvm/txs/executor/camino_tx_executor_test.go +++ b/vms/platformvm/txs/executor/camino_tx_executor_test.go @@ -6133,10 +6133,11 @@ func TestCaminoStandardTxExecutorAddVoteTx(t *testing.T) { }, }} - proposalID := ids.ID{1, 2, 3, 4} + proposalID := ids.ID{1, 1, 1, 1} proposal := &dac.BaseFeeProposalState{ AllowedVoters: []ids.ShortID{voterAddr1, voterAddr3}, Start: 100, End: 102, + TotalAllowedVoters: 3, SimpleVoteOptions: dac.SimpleVoteOptions[uint64]{Options: []dac.SimpleVoteOption[uint64]{ {Value: 555}, {Value: 123, Weight: 1}, @@ -6529,66 +6530,82 @@ func TestCaminoStandardTxExecutorFinishProposalsTx(t *testing.T) { bondOwnerAddr1 := ids.ShortID{1} bondOwnerAddr2 := ids.ShortID{2} bondOwnerAddr3 := ids.ShortID{3} + bondOwnerAddr4 := ids.ShortID{3} voterAddr1 := ids.ShortID{4} voterAddr2 := ids.ShortID{5} bond1Owner := secp256k1fx.OutputOwners{Addrs: []ids.ShortID{bondOwnerAddr1}, Threshold: 1} bond2Owner := secp256k1fx.OutputOwners{Addrs: []ids.ShortID{bondOwnerAddr2}, Threshold: 1} bond3Owner := secp256k1fx.OutputOwners{Addrs: []ids.ShortID{bondOwnerAddr3}, Threshold: 1} + bond4Owner := secp256k1fx.OutputOwners{Addrs: []ids.ShortID{bondOwnerAddr4}, Threshold: 1} - successfulProposalID := ids.ID{1, 1} - successfulExpiredProposalID := ids.ID{2, 2} - unsuccessfulProposalID := ids.ID{3, 3} + successfulEarlyFinishedProposalID := ids.ID{1, 1} + failedEarlyFinishedProposalID := ids.ID{2, 2} + successfulExpiredProposalID := ids.ID{3, 3} + failedExpiredProposalID := ids.ID{4, 4} proposalBondAmt := uint64(100) - successfulProposalUTXO := generateTestUTXOWithIndex(ids.ID{1, 1, 1}, 0, ctx.AVAXAssetID, proposalBondAmt, bond1Owner, ids.Empty, locked.ThisTxID, true) - successfulExpiredProposalUTXO := generateTestUTXOWithIndex(ids.ID{2, 2, 2}, 0, ctx.AVAXAssetID, proposalBondAmt, bond2Owner, ids.Empty, locked.ThisTxID, true) - unsuccessfulProposalUTXO := generateTestUTXOWithIndex(ids.ID{3, 3, 3}, 0, ctx.AVAXAssetID, proposalBondAmt, bond3Owner, ids.Empty, locked.ThisTxID, true) + successfulEarlyFinishedProposalUTXO := generateTestUTXOWithIndex(ids.ID{1, 1, 1}, 0, ctx.AVAXAssetID, proposalBondAmt, bond1Owner, ids.Empty, locked.ThisTxID, true) + failedEarlyFinishedProposalUTXO := generateTestUTXOWithIndex(ids.ID{2, 2, 2}, 0, ctx.AVAXAssetID, proposalBondAmt, bond2Owner, ids.Empty, locked.ThisTxID, true) + successfulExpiredProposalUTXO := generateTestUTXOWithIndex(ids.ID{3, 3, 3}, 0, ctx.AVAXAssetID, proposalBondAmt, bond3Owner, ids.Empty, locked.ThisTxID, true) + failedExpiredProposalUTXO := generateTestUTXOWithIndex(ids.ID{4, 4, 4}, 0, ctx.AVAXAssetID, proposalBondAmt, bond4Owner, ids.Empty, locked.ThisTxID, true) baseTx := txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: ctx.NetworkID, BlockchainID: ctx.ChainID, Ins: generateInsFromUTXOsWithSigIndices([]*avax.UTXO{ - successfulProposalUTXO, successfulExpiredProposalUTXO, unsuccessfulProposalUTXO, + successfulEarlyFinishedProposalUTXO, failedEarlyFinishedProposalUTXO, + successfulExpiredProposalUTXO, failedExpiredProposalUTXO, }, []uint32{}), Outs: []*avax.TransferableOutput{ generateTestOut(ctx.AVAXAssetID, proposalBondAmt, bond1Owner, ids.Empty, ids.Empty), generateTestOut(ctx.AVAXAssetID, proposalBondAmt, bond2Owner, ids.Empty, ids.Empty), generateTestOut(ctx.AVAXAssetID, proposalBondAmt, bond3Owner, ids.Empty, ids.Empty), + generateTestOut(ctx.AVAXAssetID, proposalBondAmt, bond4Owner, ids.Empty, ids.Empty), }, }} mostVotedIndex := uint32(1) - successfulProposal := &dac.BaseFeeProposalState{ - AllowedVoters: []ids.ShortID{}, + successfulEarlyFinishedProposal := &dac.BaseFeeProposalState{ + AllowedVoters: []ids.ShortID{voterAddr1}, Start: 100, End: 102, SimpleVoteOptions: dac.SimpleVoteOptions[uint64]{Options: []dac.SimpleVoteOption[uint64]{ {Value: 555}, {Value: 123, Weight: 2}, {Value: 7}, }}, - EarlySuccessThreshold: 1, - FinishThreshold: 2, + TotalAllowedVoters: 3, } - successfulExpiredProposal := &dac.BaseFeeProposalState{ - AllowedVoters: []ids.ShortID{voterAddr2}, + + failedEarlyFinishedProposal := &dac.BaseFeeProposalState{ + AllowedVoters: []ids.ShortID{}, Start: 100, End: 102, SimpleVoteOptions: dac.SimpleVoteOptions[uint64]{Options: []dac.SimpleVoteOption[uint64]{ - {Value: 555}, + {Value: 555, Weight: 1}, {Value: 123, Weight: 1}, + {Value: 7, Weight: 1}, + }}, + TotalAllowedVoters: 3, + } + + successfulExpiredProposal := &dac.BaseFeeProposalState{ + AllowedVoters: []ids.ShortID{voterAddr1}, + Start: 100, End: 102, + SimpleVoteOptions: dac.SimpleVoteOptions[uint64]{Options: []dac.SimpleVoteOption[uint64]{ + {Value: 555, Weight: 1}, + {Value: 123, Weight: 2}, {Value: 7}, }}, - EarlySuccessThreshold: 1, - FinishThreshold: 2, + TotalAllowedVoters: 4, } - unsuccessfulProposal := &dac.BaseFeeProposalState{ + + failedExpiredProposal := &dac.BaseFeeProposalState{ AllowedVoters: []ids.ShortID{voterAddr1, voterAddr2}, Start: 100, End: 102, SimpleVoteOptions: dac.SimpleVoteOptions[uint64]{Options: []dac.SimpleVoteOption[uint64]{ - {Value: 555}, - {Value: 123}, + {Value: 555, Weight: 1}, + {Value: 123, Weight: 1}, {Value: 7}, }}, - EarlySuccessThreshold: 1, - FinishThreshold: 2, + TotalAllowedVoters: 4, } tests := map[string]struct { @@ -6607,7 +6624,8 @@ func TestCaminoStandardTxExecutorFinishProposalsTx(t *testing.T) { utx: func(cfg *config.Config) *txs.FinishProposalsTx { return &txs.FinishProposalsTx{ BaseTx: baseTx, - EarlyFinishedProposalIDs: []ids.ID{successfulProposalID}, + EarlyFinishedProposalIDs: []ids.ID{successfulEarlyFinishedProposalID, failedEarlyFinishedProposalID}, + ExpiredProposalIDs: []ids.ID{successfulExpiredProposalID, failedExpiredProposalID}, } }, expectedErr: errNotDACPhase, @@ -6622,7 +6640,8 @@ func TestCaminoStandardTxExecutorFinishProposalsTx(t *testing.T) { utx: func(cfg *config.Config) *txs.FinishProposalsTx { return &txs.FinishProposalsTx{ BaseTx: baseTx, - EarlyFinishedProposalIDs: []ids.ID{successfulProposalID}, + EarlyFinishedProposalIDs: []ids.ID{successfulEarlyFinishedProposalID, failedEarlyFinishedProposalID}, + ExpiredProposalIDs: []ids.ID{successfulExpiredProposalID, failedExpiredProposalID}, } }, signers: [][]*secp256k1.PrivateKey{{}}, @@ -6632,10 +6651,10 @@ func TestCaminoStandardTxExecutorFinishProposalsTx(t *testing.T) { state: func(t *testing.T, c *gomock.Controller, utx *txs.FinishProposalsTx, txID ids.ID, cfg *config.Config) *state.MockDiff { s := state.NewMockDiff(c) s.EXPECT().CaminoConfig().Return(caminoStateConf, nil) - expectUnlock(t, s, append(utx.EarlyFinishedProposalIDs, utx.ExpiredFailedProposalIDs...), []ids.ShortID{ + expectUnlock(t, s, append(utx.EarlyFinishedProposalIDs, utx.ExpiredProposalIDs...), []ids.ShortID{ bondOwnerAddr1, bondOwnerAddr3, }, []*avax.UTXO{ - successfulProposalUTXO, unsuccessfulProposalUTXO, + successfulEarlyFinishedProposalUTXO, successfulExpiredProposalUTXO, }, locked.StateBonded) s.EXPECT().GetTimestamp().Return(cfg.DACPhaseTime) return s @@ -6645,14 +6664,14 @@ func TestCaminoStandardTxExecutorFinishProposalsTx(t *testing.T) { BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: ctx.NetworkID, BlockchainID: ctx.ChainID, - Ins: []*avax.TransferableInput{generateTestInFromUTXO(successfulProposalUTXO, []uint32{})}, // missing 2nd input + Ins: []*avax.TransferableInput{generateTestInFromUTXO(successfulEarlyFinishedProposalUTXO, []uint32{})}, // missing 2nd input Outs: []*avax.TransferableOutput{ generateTestOut(ctx.AVAXAssetID, proposalBondAmt, bond1Owner, ids.Empty, ids.Empty), generateTestOut(ctx.AVAXAssetID, proposalBondAmt, bond3Owner, ids.Empty, ids.Empty), }, }}, - EarlyFinishedProposalIDs: []ids.ID{successfulProposalID}, - ExpiredFailedProposalIDs: []ids.ID{unsuccessfulProposalID}, + EarlyFinishedProposalIDs: []ids.ID{successfulEarlyFinishedProposalID}, + ExpiredProposalIDs: []ids.ID{successfulExpiredProposalID}, } }, expectedErr: errInvalidSystemTxBody, @@ -6662,10 +6681,10 @@ func TestCaminoStandardTxExecutorFinishProposalsTx(t *testing.T) { s := state.NewMockDiff(c) s.EXPECT().CaminoConfig().Return(caminoStateConf, nil) s.EXPECT().GetTimestamp().Return(cfg.DACPhaseTime) - expectUnlock(t, s, append(utx.EarlyFinishedProposalIDs, utx.ExpiredFailedProposalIDs...), []ids.ShortID{ + expectUnlock(t, s, append(utx.EarlyFinishedProposalIDs, utx.ExpiredProposalIDs...), []ids.ShortID{ bondOwnerAddr1, bondOwnerAddr3, }, []*avax.UTXO{ - successfulProposalUTXO, unsuccessfulProposalUTXO, + successfulEarlyFinishedProposalUTXO, successfulExpiredProposalUTXO, }, locked.StateBonded) return s }, @@ -6675,15 +6694,15 @@ func TestCaminoStandardTxExecutorFinishProposalsTx(t *testing.T) { NetworkID: ctx.NetworkID, BlockchainID: ctx.ChainID, Ins: generateInsFromUTXOsWithSigIndices([]*avax.UTXO{ - successfulProposalUTXO, unsuccessfulProposalUTXO, + successfulEarlyFinishedProposalUTXO, successfulExpiredProposalUTXO, }, []uint32{}), Outs: []*avax.TransferableOutput{ generateTestOut(ctx.AVAXAssetID, proposalBondAmt, bond1Owner, ids.Empty, ids.Empty), generateTestOut(ctx.AVAXAssetID, proposalBondAmt, bond1Owner, ids.Empty, ids.Empty), // different owner }, }}, - EarlyFinishedProposalIDs: []ids.ID{successfulProposalID}, - ExpiredFailedProposalIDs: []ids.ID{unsuccessfulProposalID}, + EarlyFinishedProposalIDs: []ids.ID{successfulEarlyFinishedProposalID}, + ExpiredProposalIDs: []ids.ID{successfulExpiredProposalID}, } }, expectedErr: errInvalidSystemTxBody, @@ -6694,22 +6713,20 @@ func TestCaminoStandardTxExecutorFinishProposalsTx(t *testing.T) { s.EXPECT().CaminoConfig().Return(caminoStateConf, nil) s.EXPECT().GetTimestamp().Return(cfg.DACPhaseTime) // utxo bondTxIDs should be from some other txs, that aren't proposals (e.g. addValidatorTx) - lockTxIDs := append(utx.EarlyFinishedProposalIDs, utx.ExpiredFailedProposalIDs...) //nolint:gocritic - lockTxIDs = append(lockTxIDs, utx.ExpiredSuccessfulProposalIDs...) - expectUnlock(t, s, lockTxIDs, []ids.ShortID{ - bondOwnerAddr1, bondOwnerAddr3, + expectUnlock(t, s, append(utx.EarlyFinishedProposalIDs, utx.ExpiredProposalIDs...), []ids.ShortID{ + bondOwnerAddr1, bondOwnerAddr2, bondOwnerAddr3, bondOwnerAddr4, }, []*avax.UTXO{ - successfulProposalUTXO, successfulExpiredProposalUTXO, unsuccessfulProposalUTXO, + successfulEarlyFinishedProposalUTXO, failedEarlyFinishedProposalUTXO, + successfulExpiredProposalUTXO, failedExpiredProposalUTXO, }, locked.StateBonded) - s.EXPECT().GetProposal(successfulProposalID).Return(nil, database.ErrNotFound) + s.EXPECT().GetProposal(successfulEarlyFinishedProposalID).Return(nil, database.ErrNotFound) return s }, utx: func(cfg *config.Config) *txs.FinishProposalsTx { return &txs.FinishProposalsTx{ - BaseTx: baseTx, - EarlyFinishedProposalIDs: []ids.ID{successfulProposalID}, - ExpiredSuccessfulProposalIDs: []ids.ID{successfulExpiredProposalID}, - ExpiredFailedProposalIDs: []ids.ID{unsuccessfulProposalID}, + BaseTx: baseTx, + EarlyFinishedProposalIDs: []ids.ID{successfulEarlyFinishedProposalID, failedEarlyFinishedProposalID}, + ExpiredProposalIDs: []ids.ID{successfulExpiredProposalID, failedExpiredProposalID}, } }, expectedErr: database.ErrNotFound, @@ -6719,25 +6736,29 @@ func TestCaminoStandardTxExecutorFinishProposalsTx(t *testing.T) { s := state.NewMockDiff(c) s.EXPECT().CaminoConfig().Return(caminoStateConf, nil) s.EXPECT().GetTimestamp().Return(cfg.DACPhaseTime) - lockTxIDs := append(utx.EarlyFinishedProposalIDs, utx.ExpiredFailedProposalIDs...) //nolint:gocritic - lockTxIDs = append(lockTxIDs, utx.ExpiredSuccessfulProposalIDs...) + lockTxIDs := append(utx.EarlyFinishedProposalIDs, utx.ExpiredProposalIDs...) //nolint:gocritic expectUnlock(t, s, lockTxIDs, []ids.ShortID{ - bondOwnerAddr1, bondOwnerAddr2, bondOwnerAddr3, + bondOwnerAddr1, bondOwnerAddr2, bondOwnerAddr3, bondOwnerAddr4, }, []*avax.UTXO{ - successfulProposalUTXO, successfulExpiredProposalUTXO, unsuccessfulProposalUTXO, + successfulEarlyFinishedProposalUTXO, failedEarlyFinishedProposalUTXO, + successfulExpiredProposalUTXO, failedExpiredProposalUTXO, }, locked.StateBonded) - s.EXPECT().GetProposal(successfulProposalID).Return(successfulProposal, nil) - s.EXPECT().SetBaseFee(successfulProposal.Options[mostVotedIndex].Value) // proposal executor - s.EXPECT().RemoveProposal(successfulProposalID, successfulProposal) - s.EXPECT().RemoveProposalIDToFinish(successfulProposalID) + s.EXPECT().GetProposal(successfulEarlyFinishedProposalID).Return(successfulEarlyFinishedProposal, nil) + s.EXPECT().SetBaseFee(successfulEarlyFinishedProposal.Options[mostVotedIndex].Value) // proposal executor + s.EXPECT().RemoveProposal(successfulEarlyFinishedProposalID, successfulEarlyFinishedProposal) + s.EXPECT().RemoveProposalIDToFinish(successfulEarlyFinishedProposalID) + + s.EXPECT().GetProposal(failedEarlyFinishedProposalID).Return(failedEarlyFinishedProposal, nil) + s.EXPECT().RemoveProposal(failedEarlyFinishedProposalID, failedEarlyFinishedProposal) + s.EXPECT().RemoveProposalIDToFinish(failedEarlyFinishedProposalID) s.EXPECT().GetProposal(successfulExpiredProposalID).Return(successfulExpiredProposal, nil) s.EXPECT().SetBaseFee(successfulExpiredProposal.Options[mostVotedIndex].Value) // proposal executor s.EXPECT().RemoveProposal(successfulExpiredProposalID, successfulExpiredProposal) - s.EXPECT().GetProposal(unsuccessfulProposalID).Return(unsuccessfulProposal, nil) - s.EXPECT().RemoveProposal(unsuccessfulProposalID, unsuccessfulProposal) + s.EXPECT().GetProposal(failedExpiredProposalID).Return(failedExpiredProposal, nil) + s.EXPECT().RemoveProposal(failedExpiredProposalID, failedExpiredProposal) expectConsumeUTXOs(t, s, utx.Ins) expectProduceUTXOs(t, s, utx.Outs, txID, 0) @@ -6745,10 +6766,9 @@ func TestCaminoStandardTxExecutorFinishProposalsTx(t *testing.T) { }, utx: func(cfg *config.Config) *txs.FinishProposalsTx { return &txs.FinishProposalsTx{ - BaseTx: baseTx, - EarlyFinishedProposalIDs: []ids.ID{successfulProposalID}, - ExpiredSuccessfulProposalIDs: []ids.ID{successfulExpiredProposalID}, - ExpiredFailedProposalIDs: []ids.ID{unsuccessfulProposalID}, + BaseTx: baseTx, + EarlyFinishedProposalIDs: []ids.ID{successfulEarlyFinishedProposalID, failedEarlyFinishedProposalID}, + ExpiredProposalIDs: []ids.ID{successfulExpiredProposalID, failedExpiredProposalID}, } }, }, @@ -6760,7 +6780,7 @@ func TestCaminoStandardTxExecutorFinishProposalsTx(t *testing.T) { env := newCaminoEnvironmentWithMocks(caminoGenesisConf, nil) defer func() { require.NoError(t, shutdownCaminoEnvironment(env)) }() - env.config.DACPhaseTime = successfulProposal.StartTime().Add(-1 * time.Second) + env.config.DACPhaseTime = successfulEarlyFinishedProposal.StartTime().Add(-1 * time.Second) utx := tt.utx(env.config) avax.SortTransferableInputsWithSigners(utx.Ins, tt.signers) diff --git a/vms/platformvm/utxo/camino_locked.go b/vms/platformvm/utxo/camino_locked.go index 59831de608f5..0fc556ae5450 100644 --- a/vms/platformvm/utxo/camino_locked.go +++ b/vms/platformvm/utxo/camino_locked.go @@ -1254,9 +1254,6 @@ func (*handler) isMultisigTransferOutput(utxoDB avax.UTXOReader, out verify.Stat return false } - // ! because currently there is no support for adding new msig aliases after genesis, - // ! we assume that state diffs won't contain any changes to msig aliases state - // ! that must be changed later state, ok := utxoDB.(state.CaminoDiff) if !ok { return false