diff --git a/.circleci/config.yml b/.circleci/config.yml index 02b4f3fad..ac68da329 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -686,6 +686,11 @@ workflows: - build suite: itest-eth_block_hash target: "./itests/eth_block_hash_test.go" + - test: + name: test-itest-eth_bytecode + suite: itest-eth_bytecode + target: "./itests/eth_bytecode_test.go" + - test: name: test-itest-eth_config requires: diff --git a/go.mod b/go.mod index 8c27e89f3..720f2f727 100644 --- a/go.mod +++ b/go.mod @@ -108,7 +108,7 @@ require ( github.com/ipld/go-car v0.4.0 github.com/ipld/go-car/v2 v2.5.0 github.com/ipld/go-codec-dagpb v1.5.0 - github.com/ipld/go-ipld-prime v0.18.0 + github.com/ipld/go-ipld-prime v0.20.0 github.com/ipld/go-ipld-selector-text-lite v0.0.1 github.com/ipni/index-provider v0.10.0 github.com/kelseyhightower/envconfig v1.4.0 @@ -294,7 +294,7 @@ require ( github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect - github.com/multiformats/go-multicodec v0.6.0 // indirect + github.com/multiformats/go-multicodec v0.8.0 // indirect github.com/multiformats/go-multistream v0.3.3 // indirect github.com/nikkolasg/hexjson v0.0.0-20181101101858-78e39397e00c // indirect github.com/nkovacs/streamquote v1.0.0 // indirect diff --git a/go.sum b/go.sum index beeaab040..2ade44d4e 100644 --- a/go.sum +++ b/go.sum @@ -399,8 +399,8 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -898,8 +898,8 @@ github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHt github.com/ipld/go-ipld-prime v0.14.0/go.mod h1:9ASQLwUFLptCov6lIYc70GRB4V7UTyLD0IJtrDJe6ZM= github.com/ipld/go-ipld-prime v0.16.0/go.mod h1:axSCuOCBPqrH+gvXr2w9uAOulJqBPhHPT2PjoiiU1qA= github.com/ipld/go-ipld-prime v0.17.0/go.mod h1:aYcKm5TIvGfY8P3QBKz/2gKcLxzJ1zDaD+o0bOowhgs= -github.com/ipld/go-ipld-prime v0.18.0 h1:xUk7NUBSWHEXdjiOu2sLXouFJOMs0yoYzeI5RAqhYQo= -github.com/ipld/go-ipld-prime v0.18.0/go.mod h1:735yXW548CKrLwVCYXzqx90p5deRJMVVxM9eJ4Qe+qE= +github.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g= +github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M= github.com/ipld/go-ipld-prime-proto v0.0.0-20191113031812-e32bd156a1e5/go.mod h1:gcvzoEDBjwycpXt3LBE061wT9f46szXGHAmj9uoP6fU= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73 h1:TsyATB2ZRRQGTwafJdgEUQkmjOExRV0DNokcihZxbnQ= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73/go.mod h1:2PJ0JgxyB08t0b2WKrcuqI3di0V+5n6RS/LTUJhkoxY= @@ -1000,8 +1000,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= @@ -1526,8 +1526,8 @@ github.com/multiformats/go-multicodec v0.3.1-0.20210902112759-1539a079fd61/go.mo github.com/multiformats/go-multicodec v0.3.1-0.20211210143421-a526f306ed2c/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= github.com/multiformats/go-multicodec v0.5.0/go.mod h1:DiY2HFaEp5EhEXb/iYzVAunmyX/aSFMxq2KMKfWEues= -github.com/multiformats/go-multicodec v0.6.0 h1:KhH2kSuCARyuJraYMFxrNO3DqIaYhOdS039kbhgVwpE= -github.com/multiformats/go-multicodec v0.6.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= +github.com/multiformats/go-multicodec v0.8.0 h1:evBmgkbSQux+Ds2IgfhkO38Dl2GDtRW8/Rp6YiSHX/Q= +github.com/multiformats/go-multicodec v0.8.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= @@ -1711,7 +1711,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= @@ -1854,8 +1854,8 @@ github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= -github.com/warpfork/go-testmark v0.10.0 h1:E86YlUMYfwIacEsQGlnTvjk1IgYkyTGjPhF0RnwTCmw= github.com/warpfork/go-testmark v0.10.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= +github.com/warpfork/go-testmark v0.11.0 h1:J6LnV8KpceDvo7spaNU4+DauH2n1x+6RaO2rJrmpQ9U= github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20190328234359-8b3e70f8e830/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a h1:G++j5e0OC488te356JvdhaM8YS6nMsjLAYF7JxCv07w= diff --git a/itests/eth_bytecode_test.go b/itests/eth_bytecode_test.go new file mode 100644 index 000000000..4d03a0f6d --- /dev/null +++ b/itests/eth_bytecode_test.go @@ -0,0 +1,74 @@ +package itests + +import ( + "context" + "encoding/hex" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit" +) + +// TestGetCode ensures that GetCode returns the correct results for: +// 1. Placeholders. +// 2. Non-existent actors. +// 3. Normal EVM actors. +// 4. Self-destructed EVM actors. +func TestGetCode(t *testing.T) { + kit.QuietMiningLogs() + + blockTime := 100 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // Accounts should have empty code. + { + // A random eth address should have no code. + _, ethAddr, filAddr := client.EVM().NewAccount() + bytecode, err := client.EVM().EthGetCode(ctx, ethAddr, "latest") + require.NoError(t, err) + require.Empty(t, bytecode) + + // send some funds to the account. + kit.SendFunds(ctx, t, client, filAddr, types.FromFil(10)) + + // The code should still be empty, target is now a placeholder. + bytecode, err = client.EVM().EthGetCode(ctx, ethAddr, "latest") + require.NoError(t, err) + require.Empty(t, bytecode) + } + + // Check contract code. + { + // install a contract + contractHex, err := os.ReadFile("./contracts/SelfDestruct.hex") + require.NoError(t, err) + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + createReturn := client.EVM().DeployContract(ctx, client.DefaultKey.Address, contract) + contractAddr := createReturn.EthAddress + contractFilAddr := *createReturn.RobustAddress + + // The newly deployed contract should not be empty. + bytecode, err := client.EVM().EthGetCode(ctx, contractAddr, "latest") + require.NoError(t, err) + require.NotEmpty(t, bytecode) + + // Destroy it. + _, _, err = client.EVM().InvokeContractByFuncName(ctx, client.DefaultKey.Address, contractFilAddr, "destroy()", nil) + require.NoError(t, err) + + // The code should be empty again. + bytecode, err = client.EVM().EthGetCode(ctx, contractAddr, "latest") + require.NoError(t, err) + require.Empty(t, bytecode) + } + +} diff --git a/itests/kit/evm.go b/itests/kit/evm.go index 216a7f1f3..3cbfcd5f2 100644 --- a/itests/kit/evm.go +++ b/itests/kit/evm.go @@ -64,7 +64,7 @@ func (e *EVM) DeployContractWithValue(ctx context.Context, sender address.Addres require.NoError(err) e.t.Log("waiting for message to execute") - wait, err := e.StateWaitMsg(ctx, smsg.Cid(), 0, 0, false) + wait, err := e.StateWaitMsg(ctx, smsg.Cid(), 3, 0, false) require.NoError(err) require.True(wait.Receipt.ExitCode.IsSuccess(), "contract installation failed") @@ -128,7 +128,7 @@ func (e *EVM) InvokeSolidity(ctx context.Context, sender address.Address, target } e.t.Log("waiting for message to execute") - wait, err := e.StateWaitMsg(ctx, smsg.Cid(), 0, 0, false) + wait, err := e.StateWaitMsg(ctx, smsg.Cid(), 3, 0, false) if err != nil { return nil, err } diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 8401d7d51..9576dee40 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -426,22 +426,6 @@ func (a *EthModule) EthGetCode(ctx context.Context, ethAddr ethtypes.EthAddress, return nil, xerrors.Errorf("cannot get Filecoin address: %w", err) } - // use the system actor as the caller - from, err := address.NewIDAddress(0) - if err != nil { - return nil, fmt.Errorf("failed to construct system sender address: %w", err) - } - msg := &types.Message{ - From: from, - To: to, - Value: big.Zero(), - Method: builtintypes.MethodsEVM.GetBytecode, - Params: nil, - GasLimit: build.BlockGasLimit, - GasFeeCap: big.Zero(), - GasPremium: big.Zero(), - } - ts, err := a.parseBlkParam(ctx, blkParam) if err != nil { return nil, xerrors.Errorf("cannot parse block param: %s", blkParam) @@ -452,6 +436,31 @@ func (a *EthModule) EthGetCode(ctx context.Context, ethAddr ethtypes.EthAddress, return nil, xerrors.Errorf("block param must not specify genesis block") } + actor, err := a.StateManager.LoadActor(ctx, to, ts) + if err != nil { + if xerrors.Is(err, types.ErrActorNotFound) { + return nil, nil + } + return nil, xerrors.Errorf("failed to lookup contract %s: %w", ethAddr, err) + } + + // Not a contract. We could try to distinguish between accounts and "native" contracts here, + // but it's not worth it. + if !builtinactors.IsEvmActor(actor.Code) { + return nil, nil + } + + msg := &types.Message{ + From: builtinactors.SystemActorAddr, + To: to, + Value: big.Zero(), + Method: builtintypes.MethodsEVM.GetBytecode, + Params: nil, + GasLimit: build.BlockGasLimit, + GasFeeCap: big.Zero(), + GasPremium: big.Zero(), + } + // Try calling until we find a height with no migration. var res *api.InvocResult for { @@ -466,9 +475,7 @@ func (a *EthModule) EthGetCode(ctx context.Context, ethAddr ethtypes.EthAddress, } if err != nil { - // if the call resulted in error, this is not an EVM smart contract; - // return no bytecode. - return nil, nil + return nil, xerrors.Errorf("failed to call GetBytecode: %w", err) } if res.MsgRct == nil { @@ -476,15 +483,20 @@ func (a *EthModule) EthGetCode(ctx context.Context, ethAddr ethtypes.EthAddress, } if res.MsgRct.ExitCode.IsError() { - return nil, xerrors.Errorf("message execution failed: exit %s, reason: %s", res.MsgRct.ExitCode, res.Error) + return nil, xerrors.Errorf("GetBytecode failed: %s", res.Error) } - var bytecodeCid cbg.CborCid - if err := bytecodeCid.UnmarshalCBOR(bytes.NewReader(res.MsgRct.Return)); err != nil { + var getBytecodeReturn evm.GetBytecodeReturn + if err := getBytecodeReturn.UnmarshalCBOR(bytes.NewReader(res.MsgRct.Return)); err != nil { return nil, fmt.Errorf("failed to decode EVM bytecode CID: %w", err) } - blk, err := a.Chain.StateBlockstore().Get(ctx, cid.Cid(bytecodeCid)) + // The contract has selfdestructed, so the code is "empty". + if getBytecodeReturn.Cid == nil { + return nil, nil + } + + blk, err := a.Chain.StateBlockstore().Get(ctx, *getBytecodeReturn.Cid) if err != nil { return nil, fmt.Errorf("failed to get EVM bytecode: %w", err) }