diff --git a/api/eth_transactions.go b/api/eth_transactions.go index 73aabcf51fc..78dbe1a99eb 100644 --- a/api/eth_transactions.go +++ b/api/eth_transactions.go @@ -81,7 +81,7 @@ func NewEthTxArgsFromMessage(msg *types.Message) (EthTxArgs, error) { } } if isCreate { - addr, err := EthAddressFromFilecoinIDAddress(msg.To) + addr, err := EthAddressFromFilecoinAddress(msg.To) if err != nil { return EthTxArgs{}, nil } diff --git a/api/eth_types.go b/api/eth_types.go index 2a104b6e4ad..b12817e060e 100644 --- a/api/eth_types.go +++ b/api/eth_types.go @@ -12,6 +12,7 @@ import ( "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" + "github.com/multiformats/go-varint" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" @@ -289,15 +290,41 @@ func (ea EthAddress) ToFilecoinAddress() (address.Address, error) { return addr, nil } -func EthAddressFromFilecoinIDAddress(addr address.Address) (EthAddress, error) { - id, err := address.IDFromAddress(addr) - if err != nil { - return EthAddress{}, err +func TryEthAddressFromFilecoinAddress(addr address.Address, allowId bool) (EthAddress, bool, error) { + switch addr.Protocol() { + case address.ID: + if !allowId { + return EthAddress{}, false, nil + } + id, err := address.IDFromAddress(addr) + if err != nil { + return EthAddress{}, false, err + } + var ethaddr EthAddress + ethaddr[0] = 0xff + binary.BigEndian.PutUint64(ethaddr[12:], id) + return ethaddr, true, nil + case address.Delegated: + payload := addr.Payload() + namespace, n, err := varint.FromUvarint(payload) + if err != nil { + return EthAddress{}, false, xerrors.Errorf("invalid delegated address namespace in: %s", addr) + } + payload = payload[n:] + if namespace == builtin.EthereumAddressManagerActorID { + addr, err := EthAddressFromBytes(payload) + return addr, err == nil, err + } + } + return EthAddress{}, false, nil +} + +func EthAddressFromFilecoinAddress(addr address.Address) (EthAddress, error) { + ethAddr, ok, err := TryEthAddressFromFilecoinAddress(addr, true) + if !ok && err == nil { + err = xerrors.Errorf("failed to convert filecoin address %s to an equivalent eth address", addr) } - var ethaddr EthAddress - ethaddr[0] = 0xff - binary.BigEndian.PutUint64(ethaddr[12:], id) - return ethaddr, nil + return ethAddr, err } func EthAddressFromHex(s string) (EthAddress, error) { diff --git a/api/eth_types_test.go b/api/eth_types_test.go index 2a5a8b867ec..46ce4f49a75 100644 --- a/api/eth_types_test.go +++ b/api/eth_types_test.go @@ -119,7 +119,7 @@ func TestParseEthAddr(t *testing.T) { addr, err := address.NewIDAddress(id) require.Nil(t, err) - eaddr, err := EthAddressFromFilecoinIDAddress(addr) + eaddr, err := EthAddressFromFilecoinAddress(addr) require.Nil(t, err) faddr, err := eaddr.ToFilecoinAddress() diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index f144a852975..8732574e72e 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -588,6 +588,50 @@ func (a *EthModule) ethBlockFromFilecoinTipSet(ctx context.Context, ts *types.Ti return block, nil } +// lookupEthAddress makes its best effort at finding the Ethereum address for a +// Filecoin address. It does the following: +// +// 1. If the supplied address is an f410 address, we return its payload as the EthAddress. +// 2. Otherwise (f0, f1, f2, f3), we look up the actor on the state tree. If it has a predictable address, we return it if it's f410 address. +// 3. Otherwise, we fall back to returning a masked ID Ethereum address. If the supplied address is an f0 address, we +// use that ID to form the masked ID address. +// 4. Otherwise, we fetch the actor's ID from the state tree and form the masked ID with it. +func (a *EthModule) lookupEthAddress(ctx context.Context, addr address.Address) (api.EthAddress, error) { + // Attempt to convert directly. + if ethAddr, ok, err := api.TryEthAddressFromFilecoinAddress(addr, false); err != nil { + return api.EthAddress{}, err + } else if ok { + return ethAddr, nil + } + + // Lookup on the target actor. + actor, err := a.StateAPI.StateGetActor(ctx, addr, types.EmptyTSK) + if err != nil { + return api.EthAddress{}, err + } + if actor.Address != nil { + if ethAddr, ok, err := api.TryEthAddressFromFilecoinAddress(*actor.Address, false); err != nil { + return api.EthAddress{}, err + } else if ok { + return ethAddr, nil + } + } + + // Check if we already have an ID addr, and use it if possible. + if ethAddr, ok, err := api.TryEthAddressFromFilecoinAddress(addr, true); err != nil { + return api.EthAddress{}, err + } else if ok { + return ethAddr, nil + } + + // Otherwise, resolve the ID addr. + idAddr, err := a.StateAPI.StateLookupID(ctx, addr, types.EmptyTSK) + if err != nil { + return api.EthAddress{}, err + } + return api.EthAddressFromFilecoinAddress(idAddr) +} + func (a *EthModule) ethTxFromFilecoinMessageLookup(ctx context.Context, msgLookup *api.MsgLookup) (api.EthTx, error) { if msgLookup == nil { return api.EthTx{}, fmt.Errorf("msg does not exist") @@ -613,22 +657,12 @@ func (a *EthModule) ethTxFromFilecoinMessageLookup(ctx context.Context, msgLooku return api.EthTx{}, err } - fromFilIdAddr, err := a.StateAPI.StateLookupID(ctx, msg.From, types.EmptyTSK) - if err != nil { - return api.EthTx{}, err - } - - fromEthAddr, err := api.EthAddressFromFilecoinIDAddress(fromFilIdAddr) - if err != nil { - return api.EthTx{}, err - } - - toFilAddr, err := a.StateAPI.StateLookupID(ctx, msg.To, types.EmptyTSK) + fromEthAddr, err := a.lookupEthAddress(ctx, msg.From) if err != nil { return api.EthTx{}, err } - toEthAddr, err := api.EthAddressFromFilecoinIDAddress(toFilAddr) + toEthAddr, err := a.lookupEthAddress(ctx, msg.To) if err != nil { return api.EthTx{}, err } @@ -636,7 +670,8 @@ func (a *EthModule) ethTxFromFilecoinMessageLookup(ctx context.Context, msgLooku toAddr := &toEthAddr input := msg.Params // Check to see if we need to decode as contract deployment. - if toFilAddr == builtintypes.EthereumAddressManagerActorAddr { + // We don't need to resolve the to address, because there's only one form (an ID). + if msg.To == builtintypes.EthereumAddressManagerActorAddr { switch msg.Method { case builtintypes.MethodsEAM.Create: toAddr = nil