From b36598029ad58173bf3cfe2bbd389437a6943f8a Mon Sep 17 00:00:00 2001 From: dustinxie Date: Mon, 4 Nov 2024 20:33:38 -0800 Subject: [PATCH 1/2] [facotry] add factoryWithHeight for archive mode --- action/protocol/managers.go | 11 ++ api/coreservice.go | 8 +- api/serverV2_integrity_test.go | 1 + blockchain/integrity/integrity_test.go | 17 +-- state/factory/factory.go | 84 +++-------- state/factory/factory_test.go | 12 +- state/factory/factory_withheight.go | 89 ++++++++++++ state/factory/historyfactory.go | 49 ------- state/factory/statedb.go | 5 + .../mock_chainmanager/mock_chainmanager.go | 132 ++++++++++++++++++ test/mock/mock_factory/mock_factory.go | 54 ++----- 11 files changed, 288 insertions(+), 174 deletions(-) create mode 100644 state/factory/factory_withheight.go delete mode 100644 state/factory/historyfactory.go diff --git a/action/protocol/managers.go b/action/protocol/managers.go index 60defe5ef3..81b87cd33a 100644 --- a/action/protocol/managers.go +++ b/action/protocol/managers.go @@ -1,11 +1,14 @@ package protocol import ( + "context" "math/big" "github.com/iotexproject/go-pkgs/hash" + "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" + "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/state" ) @@ -86,6 +89,14 @@ type ( Dock } + // ArchiveStateSimulator defines an interface to read state and + // simulate-run actions on a given height + ArchiveStateSimulator interface { + StateReader + SimulateExecution(context.Context, address.Address, action.Envelope, ...SimulateOption) ([]byte, *action.Receipt, error) + ReadContractStorage(context.Context, address.Address, []byte) ([]byte, error) + } + // Dock defines an interface for protocol to read/write their private data in StateReader/Manager // data are stored as interface{}, user needs to type-assert on their own upon Unload() Dock interface { diff --git a/api/coreservice.go b/api/coreservice.go index 97f8ec2f82..c5d3a95b54 100644 --- a/api/coreservice.go +++ b/api/coreservice.go @@ -949,7 +949,7 @@ func (core *coreService) readState(ctx context.Context, p protocol.Protocol, hei if height != "" { inputHeight, err := strconv.ParseUint(height, 0, 64) if err != nil { - return nil, uint64(0), err + return nil, 0, err } rp := rolldpos.FindProtocol(core.registry) if rp != nil { @@ -961,7 +961,11 @@ func (core *coreService) readState(ctx context.Context, p protocol.Protocol, hei } if inputHeight < tipHeight { // old data, wrap to history state reader - d, h, err := p.ReadState(ctx, factory.NewHistoryStateReader(core.sf, inputHeight), methodName, arguments...) + sfAtHeight, err := core.sf.AtHeight(inputHeight) + if err != nil { + return nil, 0, err + } + d, h, err := p.ReadState(ctx, sfAtHeight, methodName, arguments...) if err == nil { key.Height = strconv.FormatUint(h, 10) core.readCache.Put(key.Hash(), d) diff --git a/api/serverV2_integrity_test.go b/api/serverV2_integrity_test.go index 7727eeb8ea..0d05ed952e 100644 --- a/api/serverV2_integrity_test.go +++ b/api/serverV2_integrity_test.go @@ -287,6 +287,7 @@ func addActsToActPool(ctx context.Context, ap actpool.ActPool) error { func setupChain(cfg testConfig) (blockchain.Blockchain, blockdao.BlockDAO, blockindex.Indexer, blockindex.BloomFilterIndexer, factory.Factory, actpool.ActPool, *protocol.Registry, string, error) { cfg.chain.ProducerPrivKey = hex.EncodeToString(identityset.PrivateKey(0).Bytes()) + cfg.chain.EnableArchiveMode = true registry := protocol.NewRegistry() factoryCfg := factory.GenerateConfig(cfg.chain, cfg.genesis) sf, err := factory.NewFactory(factoryCfg, db.NewMemKVStore(), factory.RegistryOption(registry)) diff --git a/blockchain/integrity/integrity_test.go b/blockchain/integrity/integrity_test.go index 6c5c0205c5..dd0b9fd069 100644 --- a/blockchain/integrity/integrity_test.go +++ b/blockchain/integrity/integrity_test.go @@ -2355,14 +2355,14 @@ func testHistoryForAccount(t *testing.T, statetx bool) { // check history account's balance if statetx { - _, err = accountutil.AccountState(ctx, factory.NewHistoryStateReader(sf, bc.TipHeight()-1), a) - require.Equal(factory.ErrNotSupported, errors.Cause(err)) - _, err = accountutil.AccountState(ctx, factory.NewHistoryStateReader(sf, bc.TipHeight()-1), b) + _, err = sf.AtHeight(0) require.Equal(factory.ErrNotSupported, errors.Cause(err)) } else { - AccountA, err = accountutil.AccountState(ctx, factory.NewHistoryStateReader(sf, bc.TipHeight()-1), a) + sr, err := sf.AtHeight(bc.TipHeight() - 1) + require.NoError(err) + AccountA, err = accountutil.AccountState(ctx, sr, a) require.NoError(err) - AccountB, err = accountutil.AccountState(ctx, factory.NewHistoryStateReader(sf, bc.TipHeight()-1), b) + AccountB, err = accountutil.AccountState(ctx, sr, b) require.NoError(err) require.Equal(big.NewInt(100), AccountA.Balance) require.Equal(big.NewInt(100), AccountB.Balance) @@ -2403,10 +2403,11 @@ func testHistoryForContract(t *testing.T, statetx bool) { // check the the original balance again if statetx { - _, err = accountutil.AccountState(ctx, factory.NewHistoryStateReader(sf, bc.TipHeight()-1), contractAddr) - require.True(errors.Cause(err) == factory.ErrNotSupported) + _, err = sf.AtHeight(bc.TipHeight() - 1) + require.Equal(factory.ErrNotSupported, errors.Cause(err)) } else { - sr := factory.NewHistoryStateReader(sf, bc.TipHeight()-1) + sr, err := sf.AtHeight(bc.TipHeight() - 1) + require.NoError(err) account, err = accountutil.AccountState(ctx, sr, contractAddr) require.NoError(err) balance = BalanceOfContract(contract, genesisAccount, sr, t, account.Root) diff --git a/state/factory/factory.go b/state/factory/factory.go index f5dcca0527..67878d01c8 100644 --- a/state/factory/factory.go +++ b/state/factory/factory.go @@ -90,8 +90,7 @@ type ( ReadContractStorage(context.Context, address.Address, []byte) ([]byte, error) PutBlock(context.Context, *block.Block) error DeleteTipBlock(context.Context, *block.Block) error - StateAtHeight(uint64, interface{}, ...protocol.StateOption) error - StatesAtHeight(uint64, ...protocol.StateOption) (state.Iterator, error) + AtHeight(uint64) (protocol.ArchiveStateSimulator, error) // returns a factory at the given height } // factory implements StateFactory interface, tracks changes to account/contract and batch-commits to DB @@ -346,6 +345,21 @@ func (sf *factory) Validate(ctx context.Context, blk *block.Block) error { return nil } +func (sf *factory) AtHeight(h uint64) (protocol.ArchiveStateSimulator, error) { + sf.mutex.RLock() + defer sf.mutex.RUnlock() + if h == sf.currentChainHeight { + return sf, nil + } + if !sf.saveHistory { + return nil, errors.Wrap(ErrNoArchiveData, "archive mode not enabled") + } + return &factoryWithHeight{ + factory: sf, + height: h, + }, nil +} + // NewBlockBuilder returns block builder which hasn't been signed yet func (sf *factory) NewBlockBuilder( ctx context.Context, @@ -485,52 +499,6 @@ func (sf *factory) DeleteTipBlock(_ context.Context, _ *block.Block) error { return errors.Wrap(ErrNotSupported, "cannot delete tip block from factory") } -// StateAtHeight returns a confirmed state at height -- archive mode -func (sf *factory) StateAtHeight(height uint64, s interface{}, opts ...protocol.StateOption) error { - sf.mutex.RLock() - defer sf.mutex.RUnlock() - cfg, err := processOptions(opts...) - if err != nil { - return err - } - if cfg.Keys != nil { - return errors.Wrap(ErrNotSupported, "Read state with keys option has not been implemented yet") - } - if height > sf.currentChainHeight { - return errors.Errorf("query height %d is higher than tip height %d", height, sf.currentChainHeight) - } - return sf.stateAtHeight(height, cfg.Namespace, cfg.Key, s) -} - -// StatesAtHeight returns a set states in the state factory at height -- archive mode -func (sf *factory) StatesAtHeight(height uint64, opts ...protocol.StateOption) (state.Iterator, error) { - sf.mutex.RLock() - defer sf.mutex.RUnlock() - if height > sf.currentChainHeight { - return nil, errors.Errorf("query height %d is higher than tip height %d", height, sf.currentChainHeight) - } - cfg, err := processOptions(opts...) - if err != nil { - return nil, err - } - if cfg.Keys != nil { - return nil, errors.Wrap(ErrNotSupported, "Read states with keys option has not been implemented yet") - } - tlt, err := newTwoLayerTrie(ArchiveTrieNamespace, sf.dao, fmt.Sprintf("%s-%d", ArchiveTrieRootKey, height), false) - if err != nil { - return nil, errors.Wrapf(err, "failed to generate trie for %d", height) - } - if err := tlt.Start(context.Background()); err != nil { - return nil, err - } - defer tlt.Stop(context.Background()) - keys, values, err := readStatesFromTLT(tlt, cfg.Namespace, cfg.Keys) - if err != nil { - return nil, err - } - return state.NewIterator(keys, values) -} - // State returns a confirmed state in the state factory func (sf *factory) State(s interface{}, opts ...protocol.StateOption) (uint64, error) { sf.mutex.RLock() @@ -602,26 +570,6 @@ func legacyKeyLen() int { return 20 } -func (sf *factory) stateAtHeight(height uint64, ns string, key []byte, s interface{}) error { - if !sf.saveHistory { - return ErrNoArchiveData - } - tlt, err := newTwoLayerTrie(ArchiveTrieNamespace, sf.dao, fmt.Sprintf("%s-%d", ArchiveTrieRootKey, height), false) - if err != nil { - return errors.Wrapf(err, "failed to generate trie for %d", height) - } - if err := tlt.Start(context.Background()); err != nil { - return err - } - defer tlt.Stop(context.Background()) - - value, err := readStateFromTLT(tlt, ns, key) - if err != nil { - return err - } - return state.Deserialize(s, value) -} - func (sf *factory) createGenesisStates(ctx context.Context) error { ws, err := sf.newWorkingSet(ctx, 0) if err != nil { diff --git a/state/factory/factory_test.go b/state/factory/factory_test.go index ae065537d1..d4e11d4820 100644 --- a/state/factory/factory_test.go +++ b/state/factory/factory_test.go @@ -568,20 +568,16 @@ func testHistoryState(sf Factory, t *testing.T, statetx, archive bool) { // check archive data if statetx { // statetx not support archive mode - _, err = accountutil.AccountState(ctx, NewHistoryStateReader(sf, 0), a) - require.Equal(t, ErrNotSupported, errors.Cause(err)) - _, err = accountutil.AccountState(ctx, NewHistoryStateReader(sf, 0), b) + _, err = sf.AtHeight(0) require.Equal(t, ErrNotSupported, errors.Cause(err)) } else { + sr, err := sf.AtHeight(0) if !archive { - _, err = accountutil.AccountState(ctx, NewHistoryStateReader(sf, 0), a) - require.Equal(t, ErrNoArchiveData, errors.Cause(err)) - _, err = accountutil.AccountState(ctx, NewHistoryStateReader(sf, 0), b) require.Equal(t, ErrNoArchiveData, errors.Cause(err)) } else { - accountA, err = accountutil.AccountState(ctx, NewHistoryStateReader(sf, 0), a) + accountA, err = accountutil.AccountState(ctx, sr, a) require.NoError(t, err) - accountB, err = accountutil.AccountState(ctx, NewHistoryStateReader(sf, 0), b) + accountB, err = accountutil.AccountState(ctx, sr, b) require.NoError(t, err) require.Equal(t, big.NewInt(100), accountA.Balance) require.Equal(t, big.NewInt(0), accountB.Balance) diff --git a/state/factory/factory_withheight.go b/state/factory/factory_withheight.go new file mode 100644 index 0000000000..da78761b12 --- /dev/null +++ b/state/factory/factory_withheight.go @@ -0,0 +1,89 @@ +// Copyright (c) 2020 IoTeX Foundation +// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability +// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. +// This source code is governed by Apache License 2.0 that can be found in the LICENSE file. + +package factory + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/state" +) + +type factoryWithHeight struct { + *factory + height uint64 +} + +func (sf *factoryWithHeight) State(s interface{}, opts ...protocol.StateOption) (uint64, error) { + sf.mutex.RLock() + defer sf.mutex.RUnlock() + cfg, err := processOptions(opts...) + if err != nil { + return 0, err + } + if cfg.Keys != nil { + return 0, errors.Wrap(ErrNotSupported, "Read state with keys option has not been implemented yet") + } + if sf.height > sf.currentChainHeight { + return 0, errors.Errorf("query height %d is higher than tip height %d", sf.height, sf.currentChainHeight) + } + return sf.height, sf.stateAtHeight(sf.height, cfg.Namespace, cfg.Key, s) +} + +func (sf *factoryWithHeight) stateAtHeight(height uint64, ns string, key []byte, s interface{}) error { + if !sf.saveHistory { + return ErrNoArchiveData + } + tlt, err := newTwoLayerTrie(ArchiveTrieNamespace, sf.dao, fmt.Sprintf("%s-%d", ArchiveTrieRootKey, height), false) + if err != nil { + return errors.Wrapf(err, "failed to generate trie for %d", height) + } + if err := tlt.Start(context.Background()); err != nil { + return err + } + defer tlt.Stop(context.Background()) + + value, err := readStateFromTLT(tlt, ns, key) + if err != nil { + return err + } + return state.Deserialize(s, value) +} + +func (sf *factoryWithHeight) States(opts ...protocol.StateOption) (uint64, state.Iterator, error) { + sf.mutex.RLock() + defer sf.mutex.RUnlock() + if sf.height > sf.currentChainHeight { + return 0, nil, errors.Errorf("query height %d is higher than tip height %d", sf.height, sf.currentChainHeight) + } + cfg, err := processOptions(opts...) + if err != nil { + return 0, nil, err + } + if cfg.Keys != nil { + return 0, nil, errors.Wrap(ErrNotSupported, "Read states with keys option has not been implemented yet") + } + tlt, err := newTwoLayerTrie(ArchiveTrieNamespace, sf.dao, fmt.Sprintf("%s-%d", ArchiveTrieRootKey, sf.height), false) + if err != nil { + return 0, nil, errors.Wrapf(err, "failed to generate trie for %d", sf.height) + } + if err := tlt.Start(context.Background()); err != nil { + return 0, nil, err + } + defer tlt.Stop(context.Background()) + keys, values, err := readStatesFromTLT(tlt, cfg.Namespace, cfg.Keys) + if err != nil { + return 0, nil, err + } + iter, err := state.NewIterator(keys, values) + if err != nil { + return 0, nil, err + } + return sf.height, iter, err +} diff --git a/state/factory/historyfactory.go b/state/factory/historyfactory.go deleted file mode 100644 index d2e8c6ad26..0000000000 --- a/state/factory/historyfactory.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2020 IoTeX Foundation -// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability -// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. -// This source code is governed by Apache License 2.0 that can be found in the LICENSE file. - -package factory - -import ( - "github.com/iotexproject/iotex-core/v2/action/protocol" - "github.com/iotexproject/iotex-core/v2/state" -) - -// historyStateReader implements state reader interface, wrap factory with archive height -type historyStateReader struct { - height uint64 - sf Factory -} - -// NewHistoryStateReader creates new history state reader by given state factory and height -func NewHistoryStateReader(sf Factory, h uint64) protocol.StateReader { - return &historyStateReader{ - sf: sf, - height: h, - } -} - -// Height returns archive height -func (hReader *historyStateReader) Height() (uint64, error) { - return hReader.height, nil -} - -// State returns history state in the archive mode state factory -func (hReader *historyStateReader) State(s interface{}, opts ...protocol.StateOption) (uint64, error) { - return hReader.height, hReader.sf.StateAtHeight(hReader.height, s, opts...) -} - -// States returns history states in the archive mode state factory -func (hReader *historyStateReader) States(opts ...protocol.StateOption) (uint64, state.Iterator, error) { - iterator, err := hReader.sf.StatesAtHeight(hReader.height, opts...) - if err != nil { - return 0, nil, err - } - return hReader.height, iterator, nil -} - -// ReadView reads the view -func (hReader *historyStateReader) ReadView(name string) (interface{}, error) { - return hReader.sf.ReadView(name) -} diff --git a/state/factory/statedb.go b/state/factory/statedb.go index 9cf0c62c14..cc1b3a4594 100644 --- a/state/factory/statedb.go +++ b/state/factory/statedb.go @@ -357,6 +357,11 @@ func (sdb *stateDB) DeleteTipBlock(_ context.Context, _ *block.Block) error { return errors.Wrap(ErrNotSupported, "cannot delete tip block from state db") } +func (sdb *stateDB) AtHeight(h uint64) (protocol.ArchiveStateSimulator, error) { + // TODO: implement state db at height + return nil, ErrNotSupported +} + // State returns a confirmed state in the state factory func (sdb *stateDB) State(s interface{}, opts ...protocol.StateOption) (uint64, error) { cfg, err := processOptions(opts...) diff --git a/test/mock/mock_chainmanager/mock_chainmanager.go b/test/mock/mock_chainmanager/mock_chainmanager.go index 29cdce5ac2..e81f09af4a 100644 --- a/test/mock/mock_chainmanager/mock_chainmanager.go +++ b/test/mock/mock_chainmanager/mock_chainmanager.go @@ -5,9 +5,12 @@ package mock_chainmanager import ( + context "context" reflect "reflect" gomock "github.com/golang/mock/gomock" + address "github.com/iotexproject/iotex-address/address" + action "github.com/iotexproject/iotex-core/v2/action" protocol "github.com/iotexproject/iotex-core/v2/action/protocol" state "github.com/iotexproject/iotex-core/v2/state" ) @@ -333,6 +336,135 @@ func (mr *MockStateManagerMockRecorder) WriteView(arg0, arg1 interface{}) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteView", reflect.TypeOf((*MockStateManager)(nil).WriteView), arg0, arg1) } +// MockArchiveStateSimulator is a mock of ArchiveStateSimulator interface. +type MockArchiveStateSimulator struct { + ctrl *gomock.Controller + recorder *MockArchiveStateSimulatorMockRecorder +} + +// MockArchiveStateSimulatorMockRecorder is the mock recorder for MockArchiveStateSimulator. +type MockArchiveStateSimulatorMockRecorder struct { + mock *MockArchiveStateSimulator +} + +// NewMockArchiveStateSimulator creates a new mock instance. +func NewMockArchiveStateSimulator(ctrl *gomock.Controller) *MockArchiveStateSimulator { + mock := &MockArchiveStateSimulator{ctrl: ctrl} + mock.recorder = &MockArchiveStateSimulatorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockArchiveStateSimulator) EXPECT() *MockArchiveStateSimulatorMockRecorder { + return m.recorder +} + +// Height mocks base method. +func (m *MockArchiveStateSimulator) Height() (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Height") + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Height indicates an expected call of Height. +func (mr *MockArchiveStateSimulatorMockRecorder) Height() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Height", reflect.TypeOf((*MockArchiveStateSimulator)(nil).Height)) +} + +// ReadContractStorage mocks base method. +func (m *MockArchiveStateSimulator) ReadContractStorage(arg0 context.Context, arg1 address.Address, arg2 []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadContractStorage", arg0, arg1, arg2) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReadContractStorage indicates an expected call of ReadContractStorage. +func (mr *MockArchiveStateSimulatorMockRecorder) ReadContractStorage(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadContractStorage", reflect.TypeOf((*MockArchiveStateSimulator)(nil).ReadContractStorage), arg0, arg1, arg2) +} + +// ReadView mocks base method. +func (m *MockArchiveStateSimulator) ReadView(arg0 string) (interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadView", arg0) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReadView indicates an expected call of ReadView. +func (mr *MockArchiveStateSimulatorMockRecorder) ReadView(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadView", reflect.TypeOf((*MockArchiveStateSimulator)(nil).ReadView), arg0) +} + +// SimulateExecution mocks base method. +func (m *MockArchiveStateSimulator) SimulateExecution(arg0 context.Context, arg1 address.Address, arg2 action.Envelope, arg3 ...protocol.SimulateOption) ([]byte, *action.Receipt, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SimulateExecution", varargs...) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(*action.Receipt) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// SimulateExecution indicates an expected call of SimulateExecution. +func (mr *MockArchiveStateSimulatorMockRecorder) SimulateExecution(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SimulateExecution", reflect.TypeOf((*MockArchiveStateSimulator)(nil).SimulateExecution), varargs...) +} + +// State mocks base method. +func (m *MockArchiveStateSimulator) State(arg0 interface{}, arg1 ...protocol.StateOption) (uint64, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "State", varargs...) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// State indicates an expected call of State. +func (mr *MockArchiveStateSimulatorMockRecorder) State(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "State", reflect.TypeOf((*MockArchiveStateSimulator)(nil).State), varargs...) +} + +// States mocks base method. +func (m *MockArchiveStateSimulator) States(arg0 ...protocol.StateOption) (uint64, state.Iterator, error) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "States", varargs...) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(state.Iterator) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// States indicates an expected call of States. +func (mr *MockArchiveStateSimulatorMockRecorder) States(arg0 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "States", reflect.TypeOf((*MockArchiveStateSimulator)(nil).States), arg0...) +} + // MockDock is a mock of Dock interface. type MockDock struct { ctrl *gomock.Controller diff --git a/test/mock/mock_factory/mock_factory.go b/test/mock/mock_factory/mock_factory.go index c96ca3e24a..8f6d281a15 100644 --- a/test/mock/mock_factory/mock_factory.go +++ b/test/mock/mock_factory/mock_factory.go @@ -40,6 +40,21 @@ func (m *MockFactory) EXPECT() *MockFactoryMockRecorder { return m.recorder } +// AtHeight mocks base method. +func (m *MockFactory) AtHeight(arg0 uint64) (protocol.ArchiveStateSimulator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AtHeight", arg0) + ret0, _ := ret[0].(protocol.ArchiveStateSimulator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AtHeight indicates an expected call of AtHeight. +func (mr *MockFactoryMockRecorder) AtHeight(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AtHeight", reflect.TypeOf((*MockFactory)(nil).AtHeight), arg0) +} + // DeleteTipBlock mocks base method. func (m *MockFactory) DeleteTipBlock(arg0 context.Context, arg1 *block.Block) error { m.ctrl.T.Helper() @@ -197,25 +212,6 @@ func (mr *MockFactoryMockRecorder) State(arg0 interface{}, arg1 ...interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "State", reflect.TypeOf((*MockFactory)(nil).State), varargs...) } -// StateAtHeight mocks base method. -func (m *MockFactory) StateAtHeight(arg0 uint64, arg1 interface{}, arg2 ...protocol.StateOption) error { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "StateAtHeight", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// StateAtHeight indicates an expected call of StateAtHeight. -func (mr *MockFactoryMockRecorder) StateAtHeight(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateAtHeight", reflect.TypeOf((*MockFactory)(nil).StateAtHeight), varargs...) -} - // States mocks base method. func (m *MockFactory) States(arg0 ...protocol.StateOption) (uint64, state.Iterator, error) { m.ctrl.T.Helper() @@ -236,26 +232,6 @@ func (mr *MockFactoryMockRecorder) States(arg0 ...interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "States", reflect.TypeOf((*MockFactory)(nil).States), arg0...) } -// StatesAtHeight mocks base method. -func (m *MockFactory) StatesAtHeight(arg0 uint64, arg1 ...protocol.StateOption) (state.Iterator, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "StatesAtHeight", varargs...) - ret0, _ := ret[0].(state.Iterator) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// StatesAtHeight indicates an expected call of StatesAtHeight. -func (mr *MockFactoryMockRecorder) StatesAtHeight(arg0 interface{}, arg1 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatesAtHeight", reflect.TypeOf((*MockFactory)(nil).StatesAtHeight), varargs...) -} - // Stop mocks base method. func (m *MockFactory) Stop(arg0 context.Context) error { m.ctrl.T.Helper() From 614708676575e028731c984b66b917f3f5ac9cac Mon Sep 17 00:00:00 2001 From: dustinxie Date: Tue, 5 Nov 2024 10:30:59 -0800 Subject: [PATCH 2/2] address comment --- state/factory/factory.go | 16 +++--------- state/factory/factory_test.go | 2 ++ state/factory/factory_withheight.go | 40 +++++++++++++++++++---------- state/factory/util.go | 21 +++++++++++++++ 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/state/factory/factory.go b/state/factory/factory.go index 67878d01c8..83bd6a87b4 100644 --- a/state/factory/factory.go +++ b/state/factory/factory.go @@ -348,6 +348,9 @@ func (sf *factory) Validate(ctx context.Context, blk *block.Block) error { func (sf *factory) AtHeight(h uint64) (protocol.ArchiveStateSimulator, error) { sf.mutex.RLock() defer sf.mutex.RUnlock() + if h > sf.currentChainHeight { + return nil, errors.Errorf("query height %d is higher than tip height %d", h, sf.currentChainHeight) + } if h == sf.currentChainHeight { return sf, nil } @@ -413,16 +416,7 @@ func (sf *factory) SimulateExecution( if err != nil { return nil, nil, errors.Wrap(err, "failed to obtain working set from state factory") } - cfg := &protocol.SimulateOptionConfig{} - for _, opt := range opts { - opt(cfg) - } - if cfg.PreOpt != nil { - if err := cfg.PreOpt(ws); err != nil { - return nil, nil, err - } - } - return evm.SimulateExecution(ctx, ws, caller, elp) + return simulateExecution(ctx, ws, caller, elp, opts...) } // ReadContractStorage reads contract's storage @@ -438,9 +432,7 @@ func (sf *factory) ReadContractStorage(ctx context.Context, contract address.Add // PutBlock persists all changes in RunActions() into the DB func (sf *factory) PutBlock(ctx context.Context, blk *block.Block) error { - sf.mutex.Lock() timer := sf.timerFactory.NewTimer("Commit") - sf.mutex.Unlock() defer timer.End() producer := blk.PublicKey().Address() if producer == nil { diff --git a/state/factory/factory_test.go b/state/factory/factory_test.go index d4e11d4820..44bba4fed1 100644 --- a/state/factory/factory_test.go +++ b/state/factory/factory_test.go @@ -571,6 +571,8 @@ func testHistoryState(sf Factory, t *testing.T, statetx, archive bool) { _, err = sf.AtHeight(0) require.Equal(t, ErrNotSupported, errors.Cause(err)) } else { + _, err = sf.AtHeight(10) + require.Contains(t, err.Error(), "query height 10 is higher than tip height 1") sr, err := sf.AtHeight(0) if !archive { require.Equal(t, ErrNoArchiveData, errors.Cause(err)) diff --git a/state/factory/factory_withheight.go b/state/factory/factory_withheight.go index da78761b12..2692b1c1a7 100644 --- a/state/factory/factory_withheight.go +++ b/state/factory/factory_withheight.go @@ -11,7 +11,11 @@ import ( "github.com/pkg/errors" + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/execution/evm" + "github.com/iotexproject/iotex-core/v2/pkg/tracer" "github.com/iotexproject/iotex-core/v2/state" ) @@ -21,8 +25,6 @@ type factoryWithHeight struct { } func (sf *factoryWithHeight) State(s interface{}, opts ...protocol.StateOption) (uint64, error) { - sf.mutex.RLock() - defer sf.mutex.RUnlock() cfg, err := processOptions(opts...) if err != nil { return 0, err @@ -30,16 +32,10 @@ func (sf *factoryWithHeight) State(s interface{}, opts ...protocol.StateOption) if cfg.Keys != nil { return 0, errors.Wrap(ErrNotSupported, "Read state with keys option has not been implemented yet") } - if sf.height > sf.currentChainHeight { - return 0, errors.Errorf("query height %d is higher than tip height %d", sf.height, sf.currentChainHeight) - } return sf.height, sf.stateAtHeight(sf.height, cfg.Namespace, cfg.Key, s) } func (sf *factoryWithHeight) stateAtHeight(height uint64, ns string, key []byte, s interface{}) error { - if !sf.saveHistory { - return ErrNoArchiveData - } tlt, err := newTwoLayerTrie(ArchiveTrieNamespace, sf.dao, fmt.Sprintf("%s-%d", ArchiveTrieRootKey, height), false) if err != nil { return errors.Wrapf(err, "failed to generate trie for %d", height) @@ -57,11 +53,6 @@ func (sf *factoryWithHeight) stateAtHeight(height uint64, ns string, key []byte, } func (sf *factoryWithHeight) States(opts ...protocol.StateOption) (uint64, state.Iterator, error) { - sf.mutex.RLock() - defer sf.mutex.RUnlock() - if sf.height > sf.currentChainHeight { - return 0, nil, errors.Errorf("query height %d is higher than tip height %d", sf.height, sf.currentChainHeight) - } cfg, err := processOptions(opts...) if err != nil { return 0, nil, err @@ -87,3 +78,26 @@ func (sf *factoryWithHeight) States(opts ...protocol.StateOption) (uint64, state } return sf.height, iter, err } + +func (sf *factoryWithHeight) SimulateExecution( + ctx context.Context, + caller address.Address, + elp action.Envelope, + opts ...protocol.SimulateOption, +) ([]byte, *action.Receipt, error) { + ctx, span := tracer.NewSpan(ctx, "factoryWithHeight.SimulateExecution") + defer span.End() + ws, err := sf.newWorkingSet(ctx, sf.height) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to obtain working set from state factory") + } + return simulateExecution(ctx, ws, caller, elp) +} + +func (sf *factoryWithHeight) ReadContractStorage(ctx context.Context, contract address.Address, key []byte) ([]byte, error) { + ws, err := sf.newWorkingSet(ctx, sf.height) + if err != nil { + return nil, errors.Wrap(err, "failed to generate working set from state factory") + } + return evm.ReadContractStorage(ctx, ws, contract, key) +} diff --git a/state/factory/util.go b/state/factory/util.go index 9e25bff81c..d955a4f931 100644 --- a/state/factory/util.go +++ b/state/factory/util.go @@ -11,10 +11,12 @@ import ( "github.com/iotexproject/go-pkgs/bloom" "github.com/iotexproject/go-pkgs/crypto" "github.com/iotexproject/go-pkgs/hash" + "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/execution/evm" "github.com/iotexproject/iotex-core/v2/blockchain/block" "github.com/iotexproject/iotex-core/v2/blockchain/genesis" "github.com/iotexproject/iotex-core/v2/db" @@ -223,3 +225,22 @@ func newTwoLayerTrie(ns string, dao db.KVStore, rootKey string, create bool) (tr } return mptrie.NewTwoLayerTrie(dbForTrie, rootKey), nil } + +func simulateExecution( + ctx context.Context, + ws *workingSet, + caller address.Address, + elp action.Envelope, + opts ...protocol.SimulateOption, +) ([]byte, *action.Receipt, error) { + cfg := &protocol.SimulateOptionConfig{} + for _, opt := range opts { + opt(cfg) + } + if cfg.PreOpt != nil { + if err := cfg.PreOpt(ws); err != nil { + return nil, nil, err + } + } + return evm.SimulateExecution(ctx, ws, caller, elp) +}