lotus/node/impl/full/eth_utils.go
Steven Allen 9b4df6a4d0
fix: eth: use the correct state-tree when resolving addresses (#11387)
We need to always use the state-tree from the tipset _after_ the message
executed. If we use any other state-tree, we might not find the address
we're trying to resolve.

This change also has some implication for pending messages: there's no
guarantee we'll be able to generate a 0x-style address for a pending
native message. So, instead of trying, I've removed support for pending
native messages from the Eth API. Messages from EthAccounts will still
work, and native messages will still show up in blocks/traces, they just
won't show up as "pending". Which should affect exactly nobody.

I'm also taking this opportunity to cleanup some edge-cases:

1. Pass contexts where appropriate.
2. Remove all state access from `ethTxHashFromSignedMessage`.

Part of #11355
2023-11-17 18:20:31 +01:00

751 lines
23 KiB
Go

package full
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
builtintypes "github.com/filecoin-project/go-state-types/builtin"
"github.com/filecoin-project/go-state-types/builtin/v10/eam"
"github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/go-state-types/exitcode"
"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/state"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
"github.com/filecoin-project/lotus/chain/vm"
)
func getTipsetByBlockNumber(ctx context.Context, chain *store.ChainStore, blkParam string, strict bool) (*types.TipSet, error) {
if blkParam == "earliest" {
return nil, fmt.Errorf("block param \"earliest\" is not supported")
}
head := chain.GetHeaviestTipSet()
switch blkParam {
case "pending":
return head, nil
case "latest":
parent, err := chain.GetTipSetFromKey(ctx, head.Parents())
if err != nil {
return nil, fmt.Errorf("cannot get parent tipset")
}
return parent, nil
default:
var num ethtypes.EthUint64
err := num.UnmarshalJSON([]byte(`"` + blkParam + `"`))
if err != nil {
return nil, fmt.Errorf("cannot parse block number: %v", err)
}
if abi.ChainEpoch(num) > head.Height()-1 {
return nil, fmt.Errorf("requested a future epoch (beyond 'latest')")
}
ts, err := chain.GetTipsetByHeight(ctx, abi.ChainEpoch(num), head, true)
if err != nil {
return nil, fmt.Errorf("cannot get tipset at height: %v", num)
}
if strict && ts.Height() != abi.ChainEpoch(num) {
return nil, ErrNullRound
}
return ts, nil
}
}
func getTipsetByEthBlockNumberOrHash(ctx context.Context, chain *store.ChainStore, blkParam ethtypes.EthBlockNumberOrHash) (*types.TipSet, error) {
head := chain.GetHeaviestTipSet()
predefined := blkParam.PredefinedBlock
if predefined != nil {
if *predefined == "earliest" {
return nil, fmt.Errorf("block param \"earliest\" is not supported")
} else if *predefined == "pending" {
return head, nil
} else if *predefined == "latest" {
parent, err := chain.GetTipSetFromKey(ctx, head.Parents())
if err != nil {
return nil, fmt.Errorf("cannot get parent tipset")
}
return parent, nil
} else {
return nil, fmt.Errorf("unknown predefined block %s", *predefined)
}
}
if blkParam.BlockNumber != nil {
height := abi.ChainEpoch(*blkParam.BlockNumber)
if height > head.Height()-1 {
return nil, fmt.Errorf("requested a future epoch (beyond 'latest')")
}
ts, err := chain.GetTipsetByHeight(ctx, height, head, true)
if err != nil {
return nil, fmt.Errorf("cannot get tipset at height: %v", height)
}
return ts, nil
}
if blkParam.BlockHash != nil {
ts, err := chain.GetTipSetByCid(ctx, blkParam.BlockHash.ToCid())
if err != nil {
return nil, fmt.Errorf("cannot get tipset by hash: %v", err)
}
// verify that the tipset is in the canonical chain
if blkParam.RequireCanonical {
// walk up the current chain (our head) until we reach ts.Height()
walkTs, err := chain.GetTipsetByHeight(ctx, ts.Height(), head, true)
if err != nil {
return nil, fmt.Errorf("cannot get tipset at height: %v", ts.Height())
}
// verify that it equals the expected tipset
if !walkTs.Equals(ts) {
return nil, fmt.Errorf("tipset is not canonical")
}
}
return ts, nil
}
return nil, errors.New("invalid block param")
}
func ethCallToFilecoinMessage(ctx context.Context, tx ethtypes.EthCall) (*types.Message, error) {
var from address.Address
if tx.From == nil || *tx.From == (ethtypes.EthAddress{}) {
// Send from the filecoin "system" address.
var err error
from, err = (ethtypes.EthAddress{}).ToFilecoinAddress()
if err != nil {
return nil, fmt.Errorf("failed to construct the ethereum system address: %w", err)
}
} else {
// The from address must be translatable to an f4 address.
var err error
from, err = tx.From.ToFilecoinAddress()
if err != nil {
return nil, fmt.Errorf("failed to translate sender address (%s): %w", tx.From.String(), err)
}
if p := from.Protocol(); p != address.Delegated {
return nil, fmt.Errorf("expected a class 4 address, got: %d: %w", p, err)
}
}
var params []byte
if len(tx.Data) > 0 {
initcode := abi.CborBytes(tx.Data)
params2, err := actors.SerializeParams(&initcode)
if err != nil {
return nil, fmt.Errorf("failed to serialize params: %w", err)
}
params = params2
}
var to address.Address
var method abi.MethodNum
if tx.To == nil {
// this is a contract creation
to = builtintypes.EthereumAddressManagerActorAddr
method = builtintypes.MethodsEAM.CreateExternal
} else {
addr, err := tx.To.ToFilecoinAddress()
if err != nil {
return nil, xerrors.Errorf("cannot get Filecoin address: %w", err)
}
to = addr
method = builtintypes.MethodsEVM.InvokeContract
}
return &types.Message{
From: from,
To: to,
Value: big.Int(tx.Value),
Method: method,
Params: params,
GasLimit: build.BlockGasLimit,
GasFeeCap: big.Zero(),
GasPremium: big.Zero(),
}, nil
}
func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTxInfo bool, cs *store.ChainStore, sa StateAPI) (ethtypes.EthBlock, error) {
parentKeyCid, err := ts.Parents().Cid()
if err != nil {
return ethtypes.EthBlock{}, err
}
parentBlkHash, err := ethtypes.EthHashFromCid(parentKeyCid)
if err != nil {
return ethtypes.EthBlock{}, err
}
bn := ethtypes.EthUint64(ts.Height())
tsk := ts.Key()
blkCid, err := tsk.Cid()
if err != nil {
return ethtypes.EthBlock{}, err
}
blkHash, err := ethtypes.EthHashFromCid(blkCid)
if err != nil {
return ethtypes.EthBlock{}, err
}
stRoot, msgs, rcpts, err := executeTipset(ctx, ts, cs, sa)
if err != nil {
return ethtypes.EthBlock{}, xerrors.Errorf("failed to retrieve messages and receipts: %w", err)
}
st, err := sa.StateManager.StateTree(stRoot)
if err != nil {
return ethtypes.EthBlock{}, xerrors.Errorf("failed to load state-tree root %q: %w", stRoot, err)
}
block := ethtypes.NewEthBlock(len(msgs) > 0)
gasUsed := int64(0)
for i, msg := range msgs {
rcpt := rcpts[i]
ti := ethtypes.EthUint64(i)
gasUsed += rcpt.GasUsed
var smsg *types.SignedMessage
switch msg := msg.(type) {
case *types.SignedMessage:
smsg = msg
case *types.Message:
smsg = &types.SignedMessage{
Message: *msg,
Signature: crypto.Signature{
Type: crypto.SigTypeBLS,
},
}
default:
return ethtypes.EthBlock{}, xerrors.Errorf("failed to get signed msg %s: %w", msg.Cid(), err)
}
tx, err := newEthTxFromSignedMessage(smsg, st)
if err != nil {
return ethtypes.EthBlock{}, xerrors.Errorf("failed to convert msg to ethTx: %w", err)
}
tx.ChainID = ethtypes.EthUint64(build.Eip155ChainId)
tx.BlockHash = &blkHash
tx.BlockNumber = &bn
tx.TransactionIndex = &ti
if fullTxInfo {
block.Transactions = append(block.Transactions, tx)
} else {
block.Transactions = append(block.Transactions, tx.Hash.String())
}
}
block.Hash = blkHash
block.Number = bn
block.ParentHash = parentBlkHash
block.Timestamp = ethtypes.EthUint64(ts.Blocks()[0].Timestamp)
block.BaseFeePerGas = ethtypes.EthBigInt{Int: ts.Blocks()[0].ParentBaseFee.Int}
block.GasUsed = ethtypes.EthUint64(gasUsed)
return block, nil
}
func executeTipset(ctx context.Context, ts *types.TipSet, cs *store.ChainStore, sa StateAPI) (cid.Cid, []types.ChainMsg, []types.MessageReceipt, error) {
msgs, err := cs.MessagesForTipset(ctx, ts)
if err != nil {
return cid.Undef, nil, nil, xerrors.Errorf("error loading messages for tipset: %v: %w", ts, err)
}
stRoot, rcptRoot, err := sa.StateManager.TipSetState(ctx, ts)
if err != nil {
return cid.Undef, nil, nil, xerrors.Errorf("failed to compute state: %w", err)
}
rcpts, err := cs.ReadReceipts(ctx, rcptRoot)
if err != nil {
return cid.Undef, nil, nil, xerrors.Errorf("error loading receipts for tipset: %v: %w", ts, err)
}
if len(msgs) != len(rcpts) {
return cid.Undef, nil, nil, xerrors.Errorf("receipts and message array lengths didn't match for tipset: %v: %w", ts, err)
}
return stRoot, msgs, rcpts, nil
}
const errorFunctionSelector = "\x08\xc3\x79\xa0" // Error(string)
const panicFunctionSelector = "\x4e\x48\x7b\x71" // Panic(uint256)
// Eth ABI (solidity) panic codes.
var panicErrorCodes map[uint64]string = map[uint64]string{
0x00: "Panic()",
0x01: "Assert()",
0x11: "ArithmeticOverflow()",
0x12: "DivideByZero()",
0x21: "InvalidEnumVariant()",
0x22: "InvalidStorageArray()",
0x31: "PopEmptyArray()",
0x32: "ArrayIndexOutOfBounds()",
0x41: "OutOfMemory()",
0x51: "CalledUninitializedFunction()",
}
// Parse an ABI encoded revert reason. This reason should be encoded as if it were the parameters to
// an `Error(string)` function call.
//
// See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
func parseEthRevert(ret []byte) string {
if len(ret) == 0 {
return "none"
}
var cbytes abi.CborBytes
if err := cbytes.UnmarshalCBOR(bytes.NewReader(ret)); err != nil {
return "ERROR: revert reason is not cbor encoded bytes"
}
if len(cbytes) == 0 {
return "none"
}
// If it's not long enough to contain an ABI encoded response, return immediately.
if len(cbytes) < 4+32 {
return ethtypes.EthBytes(cbytes).String()
}
switch string(cbytes[:4]) {
case panicFunctionSelector:
cbytes := cbytes[4 : 4+32]
// Read the and check the code.
code, err := ethtypes.EthUint64FromBytes(cbytes)
if err != nil {
// If it's too big, just return the raw value.
codeInt := big.PositiveFromUnsignedBytes(cbytes)
return fmt.Sprintf("Panic(%s)", ethtypes.EthBigInt(codeInt).String())
}
if s, ok := panicErrorCodes[uint64(code)]; ok {
return s
}
return fmt.Sprintf("Panic(0x%x)", code)
case errorFunctionSelector:
cbytes := cbytes[4:]
cbytesLen := ethtypes.EthUint64(len(cbytes))
// Read the and check the offset.
offset, err := ethtypes.EthUint64FromBytes(cbytes[:32])
if err != nil {
break
}
if cbytesLen < offset {
break
}
// Read and check the length.
if cbytesLen-offset < 32 {
break
}
start := offset + 32
length, err := ethtypes.EthUint64FromBytes(cbytes[offset : offset+32])
if err != nil {
break
}
if cbytesLen-start < length {
break
}
// Slice the error message.
return fmt.Sprintf("Error(%s)", cbytes[start:start+length])
}
return ethtypes.EthBytes(cbytes).String()
}
// 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 delegated 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 lookupEthAddress(addr address.Address, st *state.StateTree) (ethtypes.EthAddress, error) {
// BLOCK A: We are trying to get an actual Ethereum address from an f410 address.
// Attempt to convert directly, if it's an f4 address.
ethAddr, err := ethtypes.EthAddressFromFilecoinAddress(addr)
if err == nil && !ethAddr.IsMaskedID() {
return ethAddr, nil
}
// Lookup on the target actor and try to get an f410 address.
if actor, err := st.GetActor(addr); err != nil {
return ethtypes.EthAddress{}, err
} else if actor.Address != nil {
if ethAddr, err := ethtypes.EthAddressFromFilecoinAddress(*actor.Address); err == nil && !ethAddr.IsMaskedID() {
return ethAddr, nil
}
}
// BLOCK B: We gave up on getting an actual Ethereum address and are falling back to a Masked ID address.
// Check if we already have an ID addr, and use it if possible.
if err == nil && ethAddr.IsMaskedID() {
return ethAddr, nil
}
// Otherwise, resolve the ID addr.
idAddr, err := st.LookupID(addr)
if err != nil {
return ethtypes.EthAddress{}, err
}
return ethtypes.EthAddressFromFilecoinAddress(idAddr)
}
func parseEthTopics(topics ethtypes.EthTopicSpec) (map[string][][]byte, error) {
keys := map[string][][]byte{}
for idx, vals := range topics {
if len(vals) == 0 {
continue
}
// Ethereum topics are emitted using `LOG{0..4}` opcodes resulting in topics1..4
key := fmt.Sprintf("t%d", idx+1)
for _, v := range vals {
v := v // copy the ethhash to avoid repeatedly referencing the same one.
keys[key] = append(keys[key], v[:])
}
}
return keys, nil
}
func ethTxHashFromMessageCid(ctx context.Context, c cid.Cid, sa StateAPI) (ethtypes.EthHash, error) {
smsg, err := sa.Chain.GetSignedMessage(ctx, c)
if err == nil {
// This is an Eth Tx, Secp message, Or BLS message in the mpool
return ethTxHashFromSignedMessage(smsg)
}
_, err = sa.Chain.GetMessage(ctx, c)
if err == nil {
// This is a BLS message
return ethtypes.EthHashFromCid(c)
}
return ethtypes.EmptyEthHash, nil
}
func ethTxHashFromSignedMessage(smsg *types.SignedMessage) (ethtypes.EthHash, error) {
if smsg.Signature.Type == crypto.SigTypeDelegated {
tx, err := ethtypes.EthTxFromSignedEthMessage(smsg)
if err != nil {
return ethtypes.EthHash{}, xerrors.Errorf("failed to convert from signed message: %w", err)
}
return tx.TxHash()
} else if smsg.Signature.Type == crypto.SigTypeSecp256k1 {
return ethtypes.EthHashFromCid(smsg.Cid())
} else { // BLS message
return ethtypes.EthHashFromCid(smsg.Message.Cid())
}
}
func newEthTxFromSignedMessage(smsg *types.SignedMessage, st *state.StateTree) (ethtypes.EthTx, error) {
var tx ethtypes.EthTx
var err error
// This is an eth tx
if smsg.Signature.Type == crypto.SigTypeDelegated {
tx, err = ethtypes.EthTxFromSignedEthMessage(smsg)
if err != nil {
return ethtypes.EthTx{}, xerrors.Errorf("failed to convert from signed message: %w", err)
}
tx.Hash, err = tx.TxHash()
if err != nil {
return ethtypes.EthTx{}, xerrors.Errorf("failed to calculate hash for ethTx: %w", err)
}
} else if smsg.Signature.Type == crypto.SigTypeSecp256k1 { // Secp Filecoin Message
tx = ethTxFromNativeMessage(smsg.VMMessage(), st)
tx.Hash, err = ethtypes.EthHashFromCid(smsg.Cid())
if err != nil {
return tx, err
}
} else { // BLS Filecoin message
tx = ethTxFromNativeMessage(smsg.VMMessage(), st)
tx.Hash, err = ethtypes.EthHashFromCid(smsg.Message.Cid())
if err != nil {
return tx, err
}
}
return tx, nil
}
// ethTxFromNativeMessage does NOT populate:
// - BlockHash
// - BlockNumber
// - TransactionIndex
// - Hash
func ethTxFromNativeMessage(msg *types.Message, st *state.StateTree) ethtypes.EthTx {
// We don't care if we error here, conversion is best effort for non-eth transactions
from, _ := lookupEthAddress(msg.From, st)
to, _ := lookupEthAddress(msg.To, st)
return ethtypes.EthTx{
To: &to,
From: from,
Nonce: ethtypes.EthUint64(msg.Nonce),
ChainID: ethtypes.EthUint64(build.Eip155ChainId),
Value: ethtypes.EthBigInt(msg.Value),
Type: ethtypes.Eip1559TxType,
Gas: ethtypes.EthUint64(msg.GasLimit),
MaxFeePerGas: ethtypes.EthBigInt(msg.GasFeeCap),
MaxPriorityFeePerGas: ethtypes.EthBigInt(msg.GasPremium),
AccessList: []ethtypes.EthHash{},
}
}
func getSignedMessage(ctx context.Context, cs *store.ChainStore, msgCid cid.Cid) (*types.SignedMessage, error) {
smsg, err := cs.GetSignedMessage(ctx, msgCid)
if err != nil {
// We couldn't find the signed message, it might be a BLS message, so search for a regular message.
msg, err := cs.GetMessage(ctx, msgCid)
if err != nil {
return nil, xerrors.Errorf("failed to find msg %s: %w", msgCid, err)
}
smsg = &types.SignedMessage{
Message: *msg,
Signature: crypto.Signature{
Type: crypto.SigTypeBLS,
},
}
}
return smsg, nil
}
// newEthTxFromMessageLookup creates an ethereum transaction from filecoin message lookup. If a negative txIdx is passed
// into the function, it looks up the transaction index of the message in the tipset, otherwise it uses the txIdx passed into the
// function
func newEthTxFromMessageLookup(ctx context.Context, msgLookup *api.MsgLookup, txIdx int, cs *store.ChainStore, sa StateAPI) (ethtypes.EthTx, error) {
ts, err := cs.LoadTipSet(ctx, msgLookup.TipSet)
if err != nil {
return ethtypes.EthTx{}, err
}
// This tx is located in the parent tipset
parentTs, err := cs.LoadTipSet(ctx, ts.Parents())
if err != nil {
return ethtypes.EthTx{}, err
}
parentTsCid, err := parentTs.Key().Cid()
if err != nil {
return ethtypes.EthTx{}, err
}
// lookup the transactionIndex
if txIdx < 0 {
msgs, err := cs.MessagesForTipset(ctx, parentTs)
if err != nil {
return ethtypes.EthTx{}, err
}
for i, msg := range msgs {
if msg.Cid() == msgLookup.Message {
txIdx = i
break
}
}
if txIdx < 0 {
return ethtypes.EthTx{}, fmt.Errorf("cannot find the msg in the tipset")
}
}
blkHash, err := ethtypes.EthHashFromCid(parentTsCid)
if err != nil {
return ethtypes.EthTx{}, err
}
smsg, err := getSignedMessage(ctx, cs, msgLookup.Message)
if err != nil {
return ethtypes.EthTx{}, xerrors.Errorf("failed to get signed msg: %w", err)
}
st, err := sa.StateManager.StateTree(ts.ParentState())
if err != nil {
return ethtypes.EthTx{}, xerrors.Errorf("failed to load message state tree: %w", err)
}
tx, err := newEthTxFromSignedMessage(smsg, st)
if err != nil {
return ethtypes.EthTx{}, err
}
var (
bn = ethtypes.EthUint64(parentTs.Height())
ti = ethtypes.EthUint64(txIdx)
)
tx.BlockHash = &blkHash
tx.BlockNumber = &bn
tx.TransactionIndex = &ti
return tx, nil
}
func newEthTxReceipt(ctx context.Context, tx ethtypes.EthTx, lookup *api.MsgLookup, events []types.Event, cs *store.ChainStore, sa StateAPI) (api.EthTxReceipt, error) {
var (
transactionIndex ethtypes.EthUint64
blockHash ethtypes.EthHash
blockNumber ethtypes.EthUint64
)
if tx.TransactionIndex != nil {
transactionIndex = *tx.TransactionIndex
}
if tx.BlockHash != nil {
blockHash = *tx.BlockHash
}
if tx.BlockNumber != nil {
blockNumber = *tx.BlockNumber
}
receipt := api.EthTxReceipt{
TransactionHash: tx.Hash,
From: tx.From,
To: tx.To,
TransactionIndex: transactionIndex,
BlockHash: blockHash,
BlockNumber: blockNumber,
Type: ethtypes.EthUint64(2),
Logs: []ethtypes.EthLog{}, // empty log array is compulsory when no logs, or libraries like ethers.js break
LogsBloom: ethtypes.EmptyEthBloom[:],
}
if lookup.Receipt.ExitCode.IsSuccess() {
receipt.Status = 1
} else {
receipt.Status = 0
}
receipt.GasUsed = ethtypes.EthUint64(lookup.Receipt.GasUsed)
// TODO: handle CumulativeGasUsed
receipt.CumulativeGasUsed = ethtypes.EmptyEthInt
// TODO: avoid loading the tipset twice (once here, once when we convert the message to a txn)
ts, err := cs.GetTipSetFromKey(ctx, lookup.TipSet)
if err != nil {
return api.EthTxReceipt{}, xerrors.Errorf("failed to lookup tipset %s when constructing the eth txn receipt: %w", lookup.TipSet, err)
}
st, err := sa.StateManager.StateTree(ts.ParentState())
if err != nil {
return api.EthTxReceipt{}, xerrors.Errorf("failed to load the state %s when constructing the eth txn receipt: %w", ts.ParentState(), err)
}
// The tx is located in the parent tipset
parentTs, err := cs.LoadTipSet(ctx, ts.Parents())
if err != nil {
return api.EthTxReceipt{}, xerrors.Errorf("failed to lookup tipset %s when constructing the eth txn receipt: %w", ts.Parents(), err)
}
baseFee := parentTs.Blocks()[0].ParentBaseFee
gasOutputs := vm.ComputeGasOutputs(lookup.Receipt.GasUsed, int64(tx.Gas), baseFee, big.Int(tx.MaxFeePerGas), big.Int(tx.MaxPriorityFeePerGas), true)
totalSpent := big.Sum(gasOutputs.BaseFeeBurn, gasOutputs.MinerTip, gasOutputs.OverEstimationBurn)
effectiveGasPrice := big.Zero()
if lookup.Receipt.GasUsed > 0 {
effectiveGasPrice = big.Div(totalSpent, big.NewInt(lookup.Receipt.GasUsed))
}
receipt.EffectiveGasPrice = ethtypes.EthBigInt(effectiveGasPrice)
if receipt.To == nil && lookup.Receipt.ExitCode.IsSuccess() {
// Create and Create2 return the same things.
var ret eam.CreateExternalReturn
if err := ret.UnmarshalCBOR(bytes.NewReader(lookup.Receipt.Return)); err != nil {
return api.EthTxReceipt{}, xerrors.Errorf("failed to parse contract creation result: %w", err)
}
addr := ethtypes.EthAddress(ret.EthAddress)
receipt.ContractAddress = &addr
}
if len(events) > 0 {
receipt.Logs = make([]ethtypes.EthLog, 0, len(events))
for i, evt := range events {
l := ethtypes.EthLog{
Removed: false,
LogIndex: ethtypes.EthUint64(i),
TransactionHash: tx.Hash,
TransactionIndex: transactionIndex,
BlockHash: blockHash,
BlockNumber: blockNumber,
}
data, topics, ok := ethLogFromEvent(evt.Entries)
if !ok {
// not an eth event.
continue
}
for _, topic := range topics {
log.Debug("LogsBloom set for ", topic)
ethtypes.EthBloomSet(receipt.LogsBloom, topic[:])
}
l.Data = data
l.Topics = topics
addr, err := address.NewIDAddress(uint64(evt.Emitter))
if err != nil {
return api.EthTxReceipt{}, xerrors.Errorf("failed to create ID address: %w", err)
}
l.Address, err = lookupEthAddress(addr, st)
if err != nil {
return api.EthTxReceipt{}, xerrors.Errorf("failed to resolve Ethereum address: %w", err)
}
ethtypes.EthBloomSet(receipt.LogsBloom, l.Address[:])
receipt.Logs = append(receipt.Logs, l)
}
}
return receipt, nil
}
func encodeFilecoinParamsAsABI(method abi.MethodNum, codec uint64, params []byte) []byte {
buf := []byte{0x86, 0x8e, 0x10, 0xc4} // Native method selector.
return append(buf, encodeAsABIHelper(uint64(method), codec, params)...)
}
func encodeFilecoinReturnAsABI(exitCode exitcode.ExitCode, codec uint64, data []byte) []byte {
return encodeAsABIHelper(uint64(exitCode), codec, data)
}
// Format 2 numbers followed by an arbitrary byte array as solidity ABI. Both our native
// inputs/outputs follow the same pattern, so we can reuse this code.
func encodeAsABIHelper(param1 uint64, param2 uint64, data []byte) []byte {
const EVM_WORD_SIZE = 32
// The first two params are "static" numbers. Then, we record the offset of the "data" arg,
// then, at that offset, we record the length of the data.
//
// In practice, this means we have 4 256-bit words back to back where the third arg (the
// offset) is _always_ '32*3'.
staticArgs := []uint64{param1, param2, EVM_WORD_SIZE * 3, uint64(len(data))}
// We always pad out to the next EVM "word" (32 bytes).
totalWords := len(staticArgs) + (len(data) / EVM_WORD_SIZE)
if len(data)%EVM_WORD_SIZE != 0 {
totalWords++
}
len := totalWords * EVM_WORD_SIZE
buf := make([]byte, len)
offset := 0
// Below, we use copy instead of "appending" to preserve all the zero padding.
for _, arg := range staticArgs {
// Write each "arg" into the last 8 bytes of each 32 byte word.
offset += EVM_WORD_SIZE
start := offset - 8
binary.BigEndian.PutUint64(buf[start:offset], arg)
}
// Finally, we copy in the data.
copy(buf[offset:], data)
return buf
}