fix: eth: handle unresolvable addresses (#11433)

Correctly handle "unresolvable" to/from addresses in top-level messages in the Ethereum API. Specifically:

1. Fail if we can't resolve the from address. As far as I can tell, this should be impossible (the message statically couldn't have been included in the block if the sender didn't exist).
2. If we can't resolve the "to" address to an ID, use "max uint64" as the ID (`0xff0000000000000000000000ffffffffffffffff`). This will only happen if the transaction was reverted. It'll be a little confusing, but the alternative is to (a) use an empty address (will look like a contract creation, which is definitely wrong) or (b) use a random/hashed address which will likely be more confusing as it won't be "obviously weird".
This commit is contained in:
Steven Allen authored and GitHub committed 2023-11-29 16:12:18 +04:00
1 parent afa95681af
commit a34cc5e4e9
3 files changed
+185 -44

No files matched your search

+144 -36
View File
@@ -9,11 +9,14 @@ import (
"github.com/stretchr/testify/require"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
builtin2 "github.com/filecoin-project/go-state-types/builtin"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/go-state-types/manifest"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/store"
@@ -378,7 +381,54 @@ func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr eth
}, nil
}
func TestEthTxFromNativeAccount(t *testing.T) {
// Invoke a contract with empty input.
func TestEthTxFromNativeAccount_EmptyInput(t *testing.T) {
blockTime := 10 * 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()
secpAddr, err := address.NewSecp256k1Address([]byte("foobar"))
require.NoError(t, err)
msg := &types.Message{
From: client.DefaultKey.Address,
To: secpAddr,
Value: abi.TokenAmount(types.MustParseFIL("100")),
Method: builtin2.MethodsEVM.InvokeContract,
}
sMsg, err := client.MpoolPushMessage(ctx, msg, nil)
require.NoError(t, err)
client.WaitMsg(ctx, sMsg.Cid())
hash, err := client.EthGetTransactionHashByCid(ctx, sMsg.Cid())
require.NoError(t, err)
tx, err := client.EthGetTransactionByHash(ctx, hash)
require.NoError(t, err)
// Expect empty input params given that we "invoked" the contract (well, invoked ourselves).
require.Equal(t, ethtypes.EthBytes{}, tx.Input)
// Validate the to/from addresses.
toId, err := client.StateLookupID(ctx, msg.To, types.EmptyTSK)
require.NoError(t, err)
fromId, err := client.StateLookupID(ctx, msg.From, types.EmptyTSK)
require.NoError(t, err)
expectedTo, err := ethtypes.EthAddressFromFilecoinAddress(toId)
require.NoError(t, err)
expectedFrom, err := ethtypes.EthAddressFromFilecoinAddress(fromId)
require.NoError(t, err)
require.Equal(t, &expectedTo, tx.To)
require.Equal(t, expectedFrom, tx.From)
}
// Invoke a contract with non-empty input.
func TestEthTxFromNativeAccount_NonEmptyInput(t *testing.T) {
blockTime := 10 * time.Millisecond
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())
@@ -394,49 +444,48 @@ func TestEthTxFromNativeAccount(t *testing.T) {
Method: builtin2.MethodsEVM.InvokeContract,
}
// Send a message with no input.
var err error
input := abi.CborBytes([]byte{0x1, 0x2, 0x3, 0x4})
msg.Params, err = actors.SerializeParams(&input)
require.NoError(t, err)
sMsg, err := client.MpoolPushMessage(ctx, msg, nil)
require.NoError(t, err)
client.WaitMsg(ctx, sMsg.Cid())
hash, err := client.EthGetTransactionHashByCid(ctx, sMsg.Cid())
require.NoError(t, err)
tx, err := client.EthGetTransactionByHash(ctx, hash)
require.NoError(t, err)
// Expect empty input params given that we "invoked" the contract (well, invoked ourselves).
require.Equal(t, ethtypes.EthBytes{}, tx.Input)
// Send a message with some input.
input := abi.CborBytes([]byte{0x1, 0x2, 0x3, 0x4})
msg.Params, err = actors.SerializeParams(&input)
require.NoError(t, err)
sMsg, err = client.MpoolPushMessage(ctx, msg, nil)
require.NoError(t, err)
client.WaitMsg(ctx, sMsg.Cid())
hash, err = client.EthGetTransactionHashByCid(ctx, sMsg.Cid())
require.NoError(t, err)
tx, err = client.EthGetTransactionByHash(ctx, hash)
require.NoError(t, err)
// Expect the decoded input.
require.EqualValues(t, input, tx.Input)
}
// Invoke the contract, but with incorrectly encoded input. We expect this to be abi-encoded
// as if it were any other method call.
// Invoke a contract, but with incorrectly encoded input. We expect this to be abi-encoded as if it
// were any other method call.
func TestEthTxFromNativeAccount_BadInput(t *testing.T) {
blockTime := 10 * time.Millisecond
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())
msg.Params = input
require.NoError(t, err)
ens.InterconnectAll().BeginMining(blockTime)
sMsg, err = client.MpoolPushMessage(ctx, msg, nil)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
msg := &types.Message{
From: client.DefaultKey.Address,
To: client.DefaultKey.Address,
Value: abi.TokenAmount(types.MustParseFIL("100")),
Method: builtin2.MethodsEVM.InvokeContract,
Params: []byte{0x1, 0x2, 0x3, 0x4},
}
sMsg, err := client.MpoolPushMessage(ctx, msg, nil)
require.NoError(t, err)
client.WaitMsg(ctx, sMsg.Cid())
hash, err = client.EthGetTransactionHashByCid(ctx, sMsg.Cid())
hash, err := client.EthGetTransactionHashByCid(ctx, sMsg.Cid())
require.NoError(t, err)
tx, err = client.EthGetTransactionByHash(ctx, hash)
tx, err := client.EthGetTransactionByHash(ctx, hash)
require.NoError(t, err)
const expectedHex1 = "868e10c4" + // "handle filecoin method" function selector
@@ -451,24 +500,39 @@ func TestEthTxFromNativeAccount(t *testing.T) {
// Input: 1, 2, 3, 4
"0102030400000000000000000000000000000000000000000000000000000000"
input, err = hex.DecodeString(expectedHex1)
input, err := hex.DecodeString(expectedHex1)
require.NoError(t, err)
require.EqualValues(t, input, tx.Input)
// Invoke a random method with the same input. We expect the same result as above, but with
// a different method number.
}
msg.Method++
// Invoke a native method.
func TestEthTxFromNativeAccount_NativeMethod(t *testing.T) {
blockTime := 10 * time.Millisecond
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())
sMsg, err = client.MpoolPushMessage(ctx, msg, nil)
ens.InterconnectAll().BeginMining(blockTime)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
msg := &types.Message{
From: client.DefaultKey.Address,
To: client.DefaultKey.Address,
Value: abi.TokenAmount(types.MustParseFIL("100")),
Method: builtin2.MethodsEVM.InvokeContract + 1,
Params: []byte{0x1, 0x2, 0x3, 0x4},
}
sMsg, err := client.MpoolPushMessage(ctx, msg, nil)
require.NoError(t, err)
client.WaitMsg(ctx, sMsg.Cid())
hash, err = client.EthGetTransactionHashByCid(ctx, sMsg.Cid())
hash, err := client.EthGetTransactionHashByCid(ctx, sMsg.Cid())
require.NoError(t, err)
tx, err = client.EthGetTransactionByHash(ctx, hash)
tx, err := client.EthGetTransactionByHash(ctx, hash)
require.NoError(t, err)
const expectedHex2 = "868e10c4" + // "handle filecoin method" function selector
const expectedHex = "868e10c4" + // "handle filecoin method" function selector
// InvokeEVM+1
"00000000000000000000000000000000000000000000000000000000e525aa16" +
// CBOR multicodec (0x51)
@@ -479,7 +543,51 @@ func TestEthTxFromNativeAccount(t *testing.T) {
"0000000000000000000000000000000000000000000000000000000000000004" +
// Input: 1, 2, 3, 4
"0102030400000000000000000000000000000000000000000000000000000000"
input, err = hex.DecodeString(expectedHex2)
input, err := hex.DecodeString(expectedHex)
require.NoError(t, err)
require.EqualValues(t, input, tx.Input)
}
// Send to an invalid receiver. We're checking to make sure we correctly set `txn.To` to the special
// "reverted" eth addr.
func TestEthTxFromNativeAccount_InvalidReceiver(t *testing.T) {
blockTime := 10 * 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()
to, err := address.NewActorAddress([]byte("foobar"))
require.NoError(t, err)
msg := &types.Message{
From: client.DefaultKey.Address,
To: to,
Value: abi.TokenAmount(types.MustParseFIL("100")),
Method: builtin2.MethodsEVM.InvokeContract + 1,
Params: []byte{0x1, 0x2, 0x3, 0x4},
// We can't estimate gas for a failed message, so we hard-code these values.
GasLimit: 10_000_000,
GasFeeCap: abi.NewTokenAmount(10000),
}
// We expect the "to" address to be the special "reverted" eth address.
expectedTo, err := ethtypes.ParseEthAddress("ff0000000000000000000000ffffffffffffffff")
require.NoError(t, err)
sMsg, err := client.WalletSignMessage(ctx, client.DefaultKey.Address, msg)
require.NoError(t, err)
k, err := client.MpoolPush(ctx, sMsg)
require.NoError(t, err)
res, err := client.StateWaitMsg(ctx, k, 3, api.LookbackNoLimit, true)
require.NoError(t, err)
require.Equal(t, res.Receipt.ExitCode, exitcode.SysErrInvalidReceiver)
hash, err := client.EthGetTransactionHashByCid(ctx, k)
require.NoError(t, err)
tx, err := client.EthGetTransactionByHash(ctx, hash)
require.NoError(t, err)
require.EqualValues(t, &expectedTo, tx.To)
}