diff --git a/chain/actors/builtin/evm/actor.go.template b/chain/actors/builtin/evm/actor.go.template index aa23b5f11..62da06867 100644 --- a/chain/actors/builtin/evm/actor.go.template +++ b/chain/actors/builtin/evm/actor.go.template @@ -48,6 +48,7 @@ type State interface { cbor.Marshaler Nonce() (uint64, error) + IsAlive() (bool, error) GetState() interface{} GetBytecode() ([]byte, error) diff --git a/chain/actors/builtin/evm/evm.go b/chain/actors/builtin/evm/evm.go index f55a2177d..f214cdc13 100644 --- a/chain/actors/builtin/evm/evm.go +++ b/chain/actors/builtin/evm/evm.go @@ -48,6 +48,7 @@ type State interface { cbor.Marshaler Nonce() (uint64, error) + IsAlive() (bool, error) GetState() interface{} GetBytecode() ([]byte, error) diff --git a/chain/actors/builtin/evm/state.go.template b/chain/actors/builtin/evm/state.go.template index eb55a8463..f193733d1 100644 --- a/chain/actors/builtin/evm/state.go.template +++ b/chain/actors/builtin/evm/state.go.template @@ -4,6 +4,8 @@ import ( "github.com/ipfs/go-cid" "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/go-state-types/abi" evm{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}evm" ) @@ -40,6 +42,10 @@ func (s *state{{.v}}) Nonce() (uint64, error) { return s.State.Nonce, nil } +func (s *state{{.v}}) IsAlive() (bool, error) { + return s.State.Tombstone == nil, nil +} + func (s *state{{.v}}) GetState() interface{} { return &s.State } diff --git a/chain/actors/builtin/evm/v10.go b/chain/actors/builtin/evm/v10.go index 77a09037b..d467aa187 100644 --- a/chain/actors/builtin/evm/v10.go +++ b/chain/actors/builtin/evm/v10.go @@ -41,6 +41,10 @@ func (s *state10) Nonce() (uint64, error) { return s.State.Nonce, nil } +func (s *state10) IsAlive() (bool, error) { + return s.State.Tombstone == nil, nil +} + func (s *state10) GetState() interface{} { return &s.State } diff --git a/itests/eth_bytecode_test.go b/itests/eth_bytecode_test.go index 4d03a0f6d..a8a75e83f 100644 --- a/itests/eth_bytecode_test.go +++ b/itests/eth_bytecode_test.go @@ -10,15 +10,16 @@ import ( "github.com/stretchr/testify/require" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/itests/kit" ) -// TestGetCode ensures that GetCode returns the correct results for: +// TestGetCodeAndNonce ensures that GetCode and GetTransactionCount return the correct results for: // 1. Placeholders. // 2. Non-existent actors. // 3. Normal EVM actors. // 4. Self-destructed EVM actors. -func TestGetCode(t *testing.T) { +func TestGetCodeAndNonce(t *testing.T) { kit.QuietMiningLogs() blockTime := 100 * time.Millisecond @@ -28,7 +29,7 @@ func TestGetCode(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - // Accounts should have empty code. + // Accounts should have empty code, empty nonce. { // A random eth address should have no code. _, ethAddr, filAddr := client.EVM().NewAccount() @@ -36,6 +37,11 @@ func TestGetCode(t *testing.T) { require.NoError(t, err) require.Empty(t, bytecode) + // Nonce should also be zero + nonce, err := client.EVM().EthGetTransactionCount(ctx, ethAddr, "latest") + require.NoError(t, err) + require.Zero(t, nonce) + // send some funds to the account. kit.SendFunds(ctx, t, client, filAddr, types.FromFil(10)) @@ -43,6 +49,11 @@ func TestGetCode(t *testing.T) { bytecode, err = client.EVM().EthGetCode(ctx, ethAddr, "latest") require.NoError(t, err) require.Empty(t, bytecode) + + // Nonce should still be zero. + nonce, err = client.EVM().EthGetTransactionCount(ctx, ethAddr, "latest") + require.NoError(t, err) + require.Zero(t, nonce) } // Check contract code. @@ -61,6 +72,11 @@ func TestGetCode(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, bytecode) + // Nonce should be one. + nonce, err := client.EVM().EthGetTransactionCount(ctx, contractAddr, "latest") + require.NoError(t, err) + require.Equal(t, ethtypes.EthUint64(1), nonce) + // Destroy it. _, _, err = client.EVM().InvokeContractByFuncName(ctx, client.DefaultKey.Address, contractFilAddr, "destroy()", nil) require.NoError(t, err) @@ -69,6 +85,11 @@ func TestGetCode(t *testing.T) { bytecode, err = client.EVM().EthGetCode(ctx, contractAddr, "latest") require.NoError(t, err) require.Empty(t, bytecode) + + // Nonce should go back to zero + nonce, err = client.EVM().EthGetTransactionCount(ctx, contractAddr, "latest") + require.NoError(t, err) + require.Zero(t, nonce) } } diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 2f50d873e..69b891987 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -31,6 +31,7 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors" builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin" + builtinevm "github.com/filecoin-project/lotus/chain/actors/builtin/evm" "github.com/filecoin-project/lotus/chain/ethhashlookup" "github.com/filecoin-project/lotus/chain/events/filter" "github.com/filecoin-project/lotus/chain/messagepool" @@ -367,6 +368,26 @@ func (a *EthModule) EthGetTransactionCount(ctx context.Context, sender ethtypes. return ethtypes.EthUint64(0), xerrors.Errorf("cannot parse block param: %s", blkParam) } + // First, handle the case where the "sender" is an EVM actor. + if actor, err := a.StateManager.LoadActor(ctx, addr, ts); err != nil { + if xerrors.Is(err, types.ErrActorNotFound) { + return 0, nil + } + return 0, xerrors.Errorf("failed to lookup contract %s: %w", sender, err) + } else if builtinactors.IsEvmActor(actor.Code) { + evmState, err := builtinevm.Load(a.Chain.ActorStore(ctx), actor) + if err != nil { + return 0, xerrors.Errorf("failed to load evm state: %w", err) + } + if alive, err := evmState.IsAlive(); err != nil { + return 0, err + } else if !alive { + return 0, nil + } + nonce, err := evmState.Nonce() + return ethtypes.EthUint64(nonce), err + } + nonce, err := a.Mpool.GetNonce(ctx, addr, ts.Key()) if err != nil { return ethtypes.EthUint64(0), nil