diff --git a/packages/chain/mempool/mempool.go b/packages/chain/mempool/mempool.go index d236e406c9..59f1afb10e 100644 --- a/packages/chain/mempool/mempool.go +++ b/packages/chain/mempool/mempool.go @@ -63,6 +63,7 @@ import ( "github.com/iotaledger/wasp/packages/util/pipe" "github.com/iotaledger/wasp/packages/vm/core/accounts" "github.com/iotaledger/wasp/packages/vm/core/blocklog" + "github.com/iotaledger/wasp/packages/vm/core/evm/evmimpl" "github.com/iotaledger/wasp/packages/vm/core/governance" ) @@ -452,46 +453,39 @@ func (mpi *mempoolImpl) distSyncRequestReceivedCB(request isc.Request) { } } +func (mpi *mempoolImpl) nonce(account isc.AgentID) uint64 { + accountsState := accounts.NewStateAccess(mpi.chainHeadState) + evmState := evmimpl.NewStateAccess(mpi.chainHeadState) + + if evmSender, ok := account.(*isc.EthereumAddressAgentID); ok { + return evmState.Nonce(evmSender.EthAddress()) + } + return accountsState.Nonce(account) +} + func (mpi *mempoolImpl) shouldAddOffledgerRequest(req isc.OffLedgerRequest) error { mpi.log.Debugf("trying to add to mempool, requestID: %s", req.ID().String()) if mpi.offLedgerPool.Has(isc.RequestRefFromRequest(req)) { return fmt.Errorf("already in mempool") } - if mpi.chainHeadState != nil { - requestID := req.ID() - // TODO check nonce instead - processed, err := blocklog.IsRequestProcessed(mpi.chainHeadState, requestID) - if err != nil { - panic(fmt.Errorf( - "cannot check if request.ID=%v is processed in the blocklog at state=%v: %w", - requestID, - mpi.chainHeadState, - err, - )) - } - if processed { - return fmt.Errorf("already processed") - } - accountsState := accounts.NewStateAccess(mpi.chainHeadState) - - if req.SenderAccount().Kind() == isc.AgentIDKindEthereumAddress { //nolint:revive // intentionally left empty - // TODO check ethereum nonce - } else { - accountNonce := accountsState.Nonce(req.SenderAccount()) - if req.Nonce() < accountNonce { - return fmt.Errorf("bad nonce, expected: %d", accountNonce) - } - } + if mpi.chainHeadState == nil { + return fmt.Errorf("chainHeadState is nil") + } - governanceState := governance.NewStateAccess(mpi.chainHeadState) - // check user has on-chain balance - if !accountsState.AccountExists(req.SenderAccount()) { - // make an exception for gov calls (sender is chan owner and target is gov contract) - chainOwner := governanceState.ChainOwnerID() - isGovRequest := req.SenderAccount().Equals(chainOwner) && req.CallTarget().Contract == governance.Contract.Hname() - if !isGovRequest { - return fmt.Errorf("no funds on chain") - } + accountNonce := mpi.nonce(req.SenderAccount()) + if req.Nonce() < accountNonce { + return fmt.Errorf("bad nonce, expected: %d", accountNonce) + } + + governanceState := governance.NewStateAccess(mpi.chainHeadState) + // check user has on-chain balance + accountsState := accounts.NewStateAccess(mpi.chainHeadState) + if !accountsState.AccountExists(req.SenderAccount()) { + // make an exception for gov calls (sender is chan owner and target is gov contract) + chainOwner := governanceState.ChainOwnerID() + isGovRequest := req.SenderAccount().Equals(chainOwner) && req.CallTarget().Contract == governance.Contract.Hname() + if !isGovRequest { + return fmt.Errorf("no funds on chain") } } return nil @@ -556,7 +550,6 @@ func (mpi *mempoolImpl) refsToPropose() []*isc.RequestRef { expectedAccountNonces := map[string]uint64{} // string is isc.AgentID.String() requestsNonces := map[string][]reqRefNonce{} // string is isc.AgentID.String() - accountsState := accounts.NewStateAccess(mpi.chainHeadState) mpi.offLedgerPool.Filter(func(request isc.OffLedgerRequest, ts time.Time) bool { ref := isc.RequestRefFromRequest(request) @@ -567,7 +560,7 @@ func (mpi *mempoolImpl) refsToPropose() []*isc.RequestRef { _, ok := expectedAccountNonces[senderKey] if !ok { // get the current state nonce so we can detect gaps with it - expectedAccountNonces[senderKey] = accountsState.Nonce(request.SenderAccount()) + expectedAccountNonces[senderKey] = mpi.nonce(request.SenderAccount()) } requestsNonces[senderKey] = append(requestsNonces[senderKey], reqRefNonce{ref: ref, nonce: request.Nonce()}) diff --git a/packages/solo/chain.go b/packages/solo/chain.go index 0359b393a9..4a95e5c86c 100644 --- a/packages/solo/chain.go +++ b/packages/solo/chain.go @@ -676,6 +676,11 @@ func (ch *Chain) LatestBlock() state.Block { } func (ch *Chain) Nonce(agentID isc.AgentID) uint64 { + if evmAgentID, ok := agentID.(*isc.EthereumAddressAgentID); ok { + nonce, err := ch.EVM().TransactionCount(evmAgentID.EthAddress(), nil) + require.NoError(ch.Env.T, err) + return nonce + } res, err := ch.CallView(accounts.Contract.Name, accounts.ViewGetAccountNonce.Name, accounts.ParamAgentID, agentID) require.NoError(ch.Env.T, err) return codec.MustDecodeUint64(res.Get(accounts.ParamAccountNonce)) diff --git a/packages/testutil/testdbhash/TestStorageContract.hex b/packages/testutil/testdbhash/TestStorageContract.hex index e2d8c7559d..409fa34a52 100644 --- a/packages/testutil/testdbhash/TestStorageContract.hex +++ b/packages/testutil/testdbhash/TestStorageContract.hex @@ -1 +1 @@ -0x574c6f091ebf2eb2110d557bb491976007d71882be47cb3baf5f81f6ea46243a +0xf3074fae5eb6e9c78e8a56b3a478c5140a419e812d789165dfefc66456c055b8 diff --git a/packages/vm/core/accounts/nonce.go b/packages/vm/core/accounts/nonce.go index 04b2a992af..9f97091da2 100644 --- a/packages/vm/core/accounts/nonce.go +++ b/packages/vm/core/accounts/nonce.go @@ -14,6 +14,9 @@ func nonceKey(callerAgentID isc.AgentID) kv.Key { // Nonce returns the "total request count" for an account (its the accountNonce that is expected in the next request) func accountNonce(state kv.KVStoreReader, callerAgentID isc.AgentID) uint64 { + if callerAgentID.Kind() == isc.AgentIDKindEthereumAddress { + panic("to get EVM nonce, call EVM contract") + } data := state.Get(nonceKey(callerAgentID)) if data == nil { return 0 @@ -22,11 +25,18 @@ func accountNonce(state kv.KVStoreReader, callerAgentID isc.AgentID) uint64 { } func IncrementNonce(state kv.KVStore, callerAgentID isc.AgentID) { + if callerAgentID.Kind() == isc.AgentIDKindEthereumAddress { + // don't update EVM nonces + return + } next := accountNonce(state, callerAgentID) state.Set(nonceKey(callerAgentID), codec.EncodeUint64(next)) } func CheckNonce(state kv.KVStoreReader, agentID isc.AgentID, nonce uint64) error { + if agentID.Kind() == isc.AgentIDKindEthereumAddress { + panic("to get EVM nonce, call EVM contract") + } expected := accountNonce(state, agentID) if nonce != expected { return fmt.Errorf("Invalid nonce, expected %d, got %d", expected, nonce) diff --git a/packages/vm/core/evm/emulator/statedb.go b/packages/vm/core/evm/emulator/statedb.go index 69ceed1013..b67e742ee8 100644 --- a/packages/vm/core/evm/emulator/statedb.go +++ b/packages/vm/core/evm/emulator/statedb.go @@ -97,8 +97,12 @@ func (s *StateDB) GetBalance(addr common.Address) *big.Int { return s.l2Balance.Get(addr) } +func GetNonce(s kv.KVStoreReader, addr common.Address) uint64 { + return codec.MustDecodeUint64(s.Get(accountNonceKey(addr)), 0) +} + func (s *StateDB) GetNonce(addr common.Address) uint64 { - return codec.MustDecodeUint64(s.kv.Get(accountNonceKey(addr)), 0) + return GetNonce(s.kv, addr) } func (s *StateDB) SetNonce(addr common.Address, n uint64) { diff --git a/packages/vm/core/evm/evmimpl/external.go b/packages/vm/core/evm/evmimpl/external.go new file mode 100644 index 0000000000..401f630604 --- /dev/null +++ b/packages/vm/core/evm/evmimpl/external.go @@ -0,0 +1,25 @@ +package evmimpl + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + + "github.com/iotaledger/wasp/packages/kv" + "github.com/iotaledger/wasp/packages/kv/subrealm" + "github.com/iotaledger/wasp/packages/vm/core/evm/emulator" +) + +func Nonce(state kv.KVStoreReader, addr common.Address) uint64 { + emulatorState := evmStateSubrealmR(state) + stateDBStore := subrealm.NewReadOnly(emulatorState, emulator.KeyStateDB) + return emulator.GetNonce(stateDBStore, addr) +} + +func CheckNonce(state kv.KVStore, addr common.Address, nonce uint64) error { + expected := Nonce(state, addr) + if nonce != expected { + return fmt.Errorf("Invalid nonce, expected %d, got %d", expected, nonce) + } + return nil +} diff --git a/packages/vm/core/evm/evmimpl/state.go b/packages/vm/core/evm/evmimpl/state.go index b0d92a3b12..1920e7a5a6 100644 --- a/packages/vm/core/evm/evmimpl/state.go +++ b/packages/vm/core/evm/evmimpl/state.go @@ -13,6 +13,10 @@ func evmStateSubrealm(state kv.KVStore) kv.KVStore { return subrealm.New(state, evm.KeyEVMState) } +func evmStateSubrealmR(state kv.KVStoreReader) kv.KVStoreReader { + return subrealm.NewReadOnly(state, evm.KeyEVMState) +} + func iscMagicSubrealm(state kv.KVStore) kv.KVStore { return subrealm.New(state, evm.KeyISCMagic) } diff --git a/packages/vm/core/evm/evmimpl/stateaccess.go b/packages/vm/core/evm/evmimpl/stateaccess.go new file mode 100644 index 0000000000..353ceaac28 --- /dev/null +++ b/packages/vm/core/evm/evmimpl/stateaccess.go @@ -0,0 +1,22 @@ +package evmimpl + +import ( + "github.com/ethereum/go-ethereum/common" + + "github.com/iotaledger/wasp/packages/kv" + "github.com/iotaledger/wasp/packages/kv/subrealm" + "github.com/iotaledger/wasp/packages/vm/core/evm" +) + +type StateAccess struct { + state kv.KVStoreReader +} + +func NewStateAccess(store kv.KVStoreReader) *StateAccess { + contractState := subrealm.NewReadOnly(store, kv.Key(evm.Contract.Hname().Bytes())) + return &StateAccess{state: contractState} +} + +func (sa *StateAccess) Nonce(addr common.Address) uint64 { + return Nonce(sa.state, addr) +} diff --git a/packages/vm/vmimpl/internal.go b/packages/vm/vmimpl/internal.go index af0aec5a95..69d9d07ccf 100644 --- a/packages/vm/vmimpl/internal.go +++ b/packages/vm/vmimpl/internal.go @@ -213,7 +213,7 @@ func (vmctx *vmContext) MustSaveEvent(hContract isc.Hname, topic string, payload vmctx.reqctx.requestEventIndex++ } -// updateOffLedgerRequestNonce updates stored nonce for off ledger requests +// updateOffLedgerRequestNonce updates stored nonce for ISC off ledger requests func (vmctx *vmContext) updateOffLedgerRequestNonce() { vmctx.callCore(accounts.Contract, func(s kv.KVStore) { accounts.IncrementNonce(s, vmctx.reqctx.req.SenderAccount()) diff --git a/packages/vm/vmimpl/skipreq.go b/packages/vm/vmimpl/skipreq.go index 57224eabed..c6cbe55ceb 100644 --- a/packages/vm/vmimpl/skipreq.go +++ b/packages/vm/vmimpl/skipreq.go @@ -10,6 +10,8 @@ import ( "github.com/iotaledger/wasp/packages/kv" "github.com/iotaledger/wasp/packages/vm/core/accounts" "github.com/iotaledger/wasp/packages/vm/core/blocklog" + "github.com/iotaledger/wasp/packages/vm/core/evm" + "github.com/iotaledger/wasp/packages/vm/core/evm/evmimpl" "github.com/iotaledger/wasp/packages/vm/core/governance" "github.com/iotaledger/wasp/packages/vm/vmexceptions" ) @@ -58,21 +60,26 @@ func (vmctx *vmContext) checkReasonRequestProcessed() error { // checkReasonToSkipOffLedger checks reasons to skip off ledger request func (vmctx *vmContext) checkReasonToSkipOffLedger() error { - // first checks if it is already in backlog - // TODO check nonce instead - if err := vmctx.checkReasonRequestProcessed(); err != nil { - return err + if vmctx.task.EstimateGasMode { + return nil } - - // skip ISC nonce check for EVM requests senderAccount := vmctx.reqctx.req.SenderAccount() - if senderAccount.Kind() == isc.AgentIDKindEthereumAddress { - return nil + reqNonce := vmctx.reqctx.req.(isc.OffLedgerRequest).Nonce() + var nonceErr error + + if evmAgentID, ok := senderAccount.(*isc.EthereumAddressAgentID); ok { + vmctx.callCore(evm.Contract, func(s kv.KVStore) { + nonceErr = evmimpl.CheckNonce(s, evmAgentID.EthAddress(), reqNonce) + }) + return nonceErr } - var nonceErr error vmctx.callCore(accounts.Contract, func(s kv.KVStore) { - nonceErr = accounts.CheckNonce(s, senderAccount, vmctx.reqctx.req.(isc.OffLedgerRequest).Nonce()) + nonceErr = accounts.CheckNonce( + s, + senderAccount, + reqNonce, + ) }) return nonceErr }