From fdd28c7d692a4abf83376dbf74510915170100d4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 23 Oct 2024 12:08:07 -0400 Subject: [PATCH] Add SoV Excess to P-chain state (#3482) --- .../block/executor/proposal_block_test.go | 2 ++ .../block/executor/standard_block_test.go | 2 ++ .../block/executor/verifier_test.go | 5 ++++ vms/platformvm/state/diff.go | 11 ++++++++ vms/platformvm/state/diff_test.go | 25 +++++++++++++++++ vms/platformvm/state/mock_chain.go | 26 ++++++++++++++++++ vms/platformvm/state/mock_diff.go | 26 ++++++++++++++++++ vms/platformvm/state/mock_state.go | 26 ++++++++++++++++++ vms/platformvm/state/state.go | 27 +++++++++++++++++++ vms/platformvm/state/state_test.go | 16 +++++++++++ 10 files changed, 166 insertions(+) diff --git a/vms/platformvm/block/executor/proposal_block_test.go b/vms/platformvm/block/executor/proposal_block_test.go index 4a4154b02e50..c0a597d77335 100644 --- a/vms/platformvm/block/executor/proposal_block_test.go +++ b/vms/platformvm/block/executor/proposal_block_test.go @@ -91,6 +91,7 @@ func TestApricotProposalBlockTimeVerification(t *testing.T) { // setup state to validate proposal block transaction onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() + onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() onParentAccept.EXPECT().GetCurrentStakerIterator().Return( @@ -162,6 +163,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { onParentAccept := state.NewMockDiff(ctrl) onParentAccept.EXPECT().GetTimestamp().Return(parentTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() + onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() onParentAccept.EXPECT().GetCurrentSupply(constants.PrimaryNetworkID).Return(uint64(1000), nil).AnyTimes() diff --git a/vms/platformvm/block/executor/standard_block_test.go b/vms/platformvm/block/executor/standard_block_test.go index 8e62937c9239..d9ad860d3d3b 100644 --- a/vms/platformvm/block/executor/standard_block_test.go +++ b/vms/platformvm/block/executor/standard_block_test.go @@ -59,6 +59,7 @@ func TestApricotStandardBlockTimeVerification(t *testing.T) { chainTime := env.clk.Time().Truncate(time.Second) onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() + onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() // wrong height @@ -134,6 +135,7 @@ func TestBanffStandardBlockTimeVerification(t *testing.T) { onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() + onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() txID := ids.GenerateTestID() diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index f57b8fb4ed58..a076616701f2 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -103,6 +103,7 @@ func TestVerifierVisitProposalBlock(t *testing.T) { // One call for each of onCommitState and onAbortState. parentOnAcceptState.EXPECT().GetTimestamp().Return(timestamp).Times(2) parentOnAcceptState.EXPECT().GetFeeState().Return(gas.State{}).Times(2) + parentOnAcceptState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(2) parentOnAcceptState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(2) backend := &backend{ @@ -335,6 +336,7 @@ func TestVerifierVisitStandardBlock(t *testing.T) { timestamp := time.Now() parentState.EXPECT().GetTimestamp().Return(timestamp).Times(1) parentState.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + parentState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) parentState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) mempool.EXPECT().Remove(apricotBlk.Txs()).Times(1) @@ -597,6 +599,7 @@ func TestBanffAbortBlockTimestampChecks(t *testing.T) { s.EXPECT().GetLastAccepted().Return(parentID).Times(3) s.EXPECT().GetTimestamp().Return(parentTime).Times(3) s.EXPECT().GetFeeState().Return(gas.State{}).Times(3) + s.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(3) s.EXPECT().GetAccruedFees().Return(uint64(0)).Times(3) onDecisionState, err := state.NewDiff(parentID, backend) @@ -695,6 +698,7 @@ func TestBanffCommitBlockTimestampChecks(t *testing.T) { s.EXPECT().GetLastAccepted().Return(parentID).Times(3) s.EXPECT().GetTimestamp().Return(parentTime).Times(3) s.EXPECT().GetFeeState().Return(gas.State{}).Times(3) + s.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(3) s.EXPECT().GetAccruedFees().Return(uint64(0)).Times(3) onDecisionState, err := state.NewDiff(parentID, backend) @@ -811,6 +815,7 @@ func TestVerifierVisitStandardBlockWithDuplicateInputs(t *testing.T) { parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) parentState.EXPECT().GetTimestamp().Return(timestamp).Times(1) parentState.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + parentState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) parentState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) parentStatelessBlk.EXPECT().Parent().Return(grandParentID).Times(1) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 9fe6a62363c0..da73854346ea 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -37,6 +37,7 @@ type diff struct { timestamp time.Time feeState gas.State + sovExcess gas.Gas accruedFees uint64 // Subnet ID --> supply of native asset of the subnet @@ -80,6 +81,7 @@ func NewDiff( stateVersions: stateVersions, timestamp: parentState.GetTimestamp(), feeState: parentState.GetFeeState(), + sovExcess: parentState.GetSoVExcess(), accruedFees: parentState.GetAccruedFees(), expiryDiff: newExpiryDiff(), subnetOwners: make(map[ids.ID]fx.Owner), @@ -117,6 +119,14 @@ func (d *diff) SetFeeState(feeState gas.State) { d.feeState = feeState } +func (d *diff) GetSoVExcess() gas.Gas { + return d.sovExcess +} + +func (d *diff) SetSoVExcess(excess gas.Gas) { + d.sovExcess = excess +} + func (d *diff) GetAccruedFees() uint64 { return d.accruedFees } @@ -482,6 +492,7 @@ func (d *diff) DeleteUTXO(utxoID ids.ID) { func (d *diff) Apply(baseState Chain) error { baseState.SetTimestamp(d.timestamp) baseState.SetFeeState(d.feeState) + baseState.SetSoVExcess(d.sovExcess) baseState.SetAccruedFees(d.accruedFees) for subnetID, supply := range d.currentSupply { baseState.SetCurrentSupply(subnetID, supply) diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index 3625986d7801..82e376c3b5f5 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -68,6 +68,24 @@ func TestDiffFeeState(t *testing.T) { assertChainsEqual(t, state, d) } +func TestDiffSoVExcess(t *testing.T) { + require := require.New(t) + + state := newTestState(t, memdb.New()) + + d, err := NewDiffOn(state) + require.NoError(err) + + initialExcess := state.GetSoVExcess() + newExcess := initialExcess + 1 + d.SetSoVExcess(newExcess) + require.Equal(newExcess, d.GetSoVExcess()) + require.Equal(initialExcess, state.GetSoVExcess()) + + require.NoError(d.Apply(state)) + assertChainsEqual(t, state, d) +} + func TestDiffAccruedFees(t *testing.T) { require := require.New(t) @@ -272,6 +290,7 @@ func TestDiffCurrentValidator(t *testing.T) { // Called in NewDiffOn state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) d, err := NewDiffOn(state) @@ -307,6 +326,7 @@ func TestDiffPendingValidator(t *testing.T) { // Called in NewDiffOn state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) d, err := NewDiffOn(state) @@ -348,6 +368,7 @@ func TestDiffCurrentDelegator(t *testing.T) { // Called in NewDiffOn state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) d, err := NewDiffOn(state) @@ -392,6 +413,7 @@ func TestDiffPendingDelegator(t *testing.T) { // Called in NewDiffOn state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) d, err := NewDiffOn(state) @@ -530,6 +552,7 @@ func TestDiffTx(t *testing.T) { // Called in NewDiffOn state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) d, err := NewDiffOn(state) @@ -628,6 +651,7 @@ func TestDiffUTXO(t *testing.T) { // Called in NewDiffOn state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) d, err := NewDiffOn(state) @@ -705,6 +729,7 @@ func assertChainsEqual(t *testing.T, expected, actual Chain) { require.Equal(expected.GetTimestamp(), actual.GetTimestamp()) require.Equal(expected.GetFeeState(), actual.GetFeeState()) + require.Equal(expected.GetSoVExcess(), actual.GetSoVExcess()) require.Equal(expected.GetAccruedFees(), actual.GetAccruedFees()) expectedCurrentSupply, err := expected.GetCurrentSupply(constants.PrimaryNetworkID) diff --git a/vms/platformvm/state/mock_chain.go b/vms/platformvm/state/mock_chain.go index 27daeae3a101..56c495924511 100644 --- a/vms/platformvm/state/mock_chain.go +++ b/vms/platformvm/state/mock_chain.go @@ -353,6 +353,20 @@ func (mr *MockChainMockRecorder) GetPendingValidator(subnetID, nodeID any) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockChain)(nil).GetPendingValidator), subnetID, nodeID) } +// GetSoVExcess mocks base method. +func (m *MockChain) GetSoVExcess() gas.Gas { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSoVExcess") + ret0, _ := ret[0].(gas.Gas) + return ret0 +} + +// GetSoVExcess indicates an expected call of GetSoVExcess. +func (mr *MockChainMockRecorder) GetSoVExcess() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSoVExcess", reflect.TypeOf((*MockChain)(nil).GetSoVExcess)) +} + // GetSubnetConversion mocks base method. func (m *MockChain) GetSubnetConversion(subnetID ids.ID) (SubnetConversion, error) { m.ctrl.T.Helper() @@ -572,6 +586,18 @@ func (mr *MockChainMockRecorder) SetFeeState(f any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFeeState", reflect.TypeOf((*MockChain)(nil).SetFeeState), f) } +// SetSoVExcess mocks base method. +func (m *MockChain) SetSoVExcess(e gas.Gas) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetSoVExcess", e) +} + +// SetSoVExcess indicates an expected call of SetSoVExcess. +func (mr *MockChainMockRecorder) SetSoVExcess(e any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSoVExcess", reflect.TypeOf((*MockChain)(nil).SetSoVExcess), e) +} + // SetSubnetConversion mocks base method. func (m *MockChain) SetSubnetConversion(subnetID ids.ID, c SubnetConversion) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/mock_diff.go b/vms/platformvm/state/mock_diff.go index 8732fc49b406..b8362386af96 100644 --- a/vms/platformvm/state/mock_diff.go +++ b/vms/platformvm/state/mock_diff.go @@ -367,6 +367,20 @@ func (mr *MockDiffMockRecorder) GetPendingValidator(subnetID, nodeID any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockDiff)(nil).GetPendingValidator), subnetID, nodeID) } +// GetSoVExcess mocks base method. +func (m *MockDiff) GetSoVExcess() gas.Gas { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSoVExcess") + ret0, _ := ret[0].(gas.Gas) + return ret0 +} + +// GetSoVExcess indicates an expected call of GetSoVExcess. +func (mr *MockDiffMockRecorder) GetSoVExcess() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSoVExcess", reflect.TypeOf((*MockDiff)(nil).GetSoVExcess)) +} + // GetSubnetConversion mocks base method. func (m *MockDiff) GetSubnetConversion(subnetID ids.ID) (SubnetConversion, error) { m.ctrl.T.Helper() @@ -586,6 +600,18 @@ func (mr *MockDiffMockRecorder) SetFeeState(f any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFeeState", reflect.TypeOf((*MockDiff)(nil).SetFeeState), f) } +// SetSoVExcess mocks base method. +func (m *MockDiff) SetSoVExcess(e gas.Gas) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetSoVExcess", e) +} + +// SetSoVExcess indicates an expected call of SetSoVExcess. +func (mr *MockDiffMockRecorder) SetSoVExcess(e any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSoVExcess", reflect.TypeOf((*MockDiff)(nil).SetSoVExcess), e) +} + // SetSubnetConversion mocks base method. func (m *MockDiff) SetSubnetConversion(subnetID ids.ID, c SubnetConversion) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index a17593982572..cb05f54fc6f7 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -527,6 +527,20 @@ func (mr *MockStateMockRecorder) GetRewardUTXOs(txID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRewardUTXOs", reflect.TypeOf((*MockState)(nil).GetRewardUTXOs), txID) } +// GetSoVExcess mocks base method. +func (m *MockState) GetSoVExcess() gas.Gas { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSoVExcess") + ret0, _ := ret[0].(gas.Gas) + return ret0 +} + +// GetSoVExcess indicates an expected call of GetSoVExcess. +func (mr *MockStateMockRecorder) GetSoVExcess() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSoVExcess", reflect.TypeOf((*MockState)(nil).GetSoVExcess)) +} + // GetStartTime mocks base method. func (m *MockState) GetStartTime(nodeID ids.NodeID) (time.Time, error) { m.ctrl.T.Helper() @@ -845,6 +859,18 @@ func (mr *MockStateMockRecorder) SetLastAccepted(blkID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLastAccepted", reflect.TypeOf((*MockState)(nil).SetLastAccepted), blkID) } +// SetSoVExcess mocks base method. +func (m *MockState) SetSoVExcess(e gas.Gas) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetSoVExcess", e) +} + +// SetSoVExcess indicates an expected call of SetSoVExcess. +func (mr *MockStateMockRecorder) SetSoVExcess(e any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSoVExcess", reflect.TypeOf((*MockState)(nil).SetSoVExcess), e) +} + // SetSubnetConversion mocks base method. func (m *MockState) SetSubnetConversion(subnetID ids.ID, c SubnetConversion) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 58c2056570bd..53b109c23249 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -87,6 +87,7 @@ var ( TimestampKey = []byte("timestamp") FeeStateKey = []byte("fee state") + SoVExcessKey = []byte("sov excess") AccruedFeesKey = []byte("accrued fees") CurrentSupplyKey = []byte("current supply") LastAcceptedKey = []byte("last accepted") @@ -110,6 +111,9 @@ type Chain interface { GetFeeState() gas.State SetFeeState(f gas.State) + GetSoVExcess() gas.Gas + SetSoVExcess(e gas.Gas) + GetAccruedFees() uint64 SetAccruedFees(f uint64) @@ -289,6 +293,7 @@ type stateBlk struct { * |-- blocksReindexedKey -> nil * |-- timestampKey -> timestamp * |-- feeStateKey -> feeState + * |-- sovExcessKey -> sovExcess * |-- accruedFeesKey -> accruedFees * |-- currentSupplyKey -> currentSupply * |-- lastAcceptedKey -> lastAccepted @@ -386,6 +391,7 @@ type state struct { // The persisted fields represent the current database value timestamp, persistedTimestamp time.Time feeState, persistedFeeState gas.State + sovExcess, persistedSOVExcess gas.Gas accruedFees, persistedAccruedFees uint64 currentSupply, persistedCurrentSupply uint64 // [lastAccepted] is the most recently accepted block. @@ -1091,6 +1097,14 @@ func (s *state) SetFeeState(feeState gas.State) { s.feeState = feeState } +func (s *state) GetSoVExcess() gas.Gas { + return s.sovExcess +} + +func (s *state) SetSoVExcess(e gas.Gas) { + s.sovExcess = e +} + func (s *state) GetAccruedFees() uint64 { return s.accruedFees } @@ -1391,6 +1405,13 @@ func (s *state) loadMetadata() error { s.persistedFeeState = feeState s.SetFeeState(feeState) + sovExcess, err := database.WithDefault(database.GetUInt64, s.singletonDB, SoVExcessKey, 0) + if err != nil { + return err + } + s.persistedSOVExcess = gas.Gas(sovExcess) + s.SetSoVExcess(gas.Gas(sovExcess)) + accruedFees, err := database.WithDefault(database.GetUInt64, s.singletonDB, AccruedFeesKey, 0) if err != nil { return err @@ -2439,6 +2460,12 @@ func (s *state) writeMetadata() error { } s.persistedFeeState = s.feeState } + if s.sovExcess != s.persistedSOVExcess { + if err := database.PutUInt64(s.singletonDB, SoVExcessKey, uint64(s.sovExcess)); err != nil { + return fmt.Errorf("failed to write sov excess: %w", err) + } + s.persistedSOVExcess = s.sovExcess + } if s.accruedFees != s.persistedAccruedFees { if err := database.PutUInt64(s.singletonDB, AccruedFeesKey, s.accruedFees); err != nil { return fmt.Errorf("failed to write accrued fees: %w", err) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index d29129500435..6204540bd615 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1474,6 +1474,22 @@ func TestStateFeeStateCommitAndLoad(t *testing.T) { require.Equal(expectedFeeState, s.GetFeeState()) } +// Verify that committing the state writes the sov excess to the database and +// that loading the state fetches the sov excess from the database. +func TestStateSoVExcessCommitAndLoad(t *testing.T) { + require := require.New(t) + + db := memdb.New() + s := newTestState(t, db) + + const expectedSoVExcess gas.Gas = 10 + s.SetSoVExcess(expectedSoVExcess) + require.NoError(s.Commit()) + + s = newTestState(t, db) + require.Equal(expectedSoVExcess, s.GetSoVExcess()) +} + // Verify that committing the state writes the accrued fees to the database and // that loading the state fetches the accrued fees from the database. func TestStateAccruedFeesCommitAndLoad(t *testing.T) {