Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support Homestead and EIP-155 Ethereum transactions #6363

Merged
merged 4 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions app/submodule/eth/eth_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,23 @@ func (a *ethAPI) EthGetTransactionByHashLimited(ctx context.Context, txHash *typ

for _, p := range pending {
if p.Cid() == c {
tx, err := newEthTxFromSignedMessage(ctx, p, a.chain)
// We only return pending eth-account messages because we can't guarantee
// that the from/to addresses of other messages are conversable to 0x-style
// addresses. So we just ignore them.
//
// This should be "fine" as anyone using an "Ethereum-centric" block
// explorer shouldn't care about seeing pending messages from native
// accounts.
ethtx, err := types.EthTransactionFromSignedFilecoinMessage(p)
if err != nil {
return nil, fmt.Errorf("could not convert Filecoin message into tx: %v", err)
return nil, fmt.Errorf("could not convert Filecoin message into tx: %w", err)
}

tx, err := ethtx.ToEthTx(p)
if err != nil {
return nil, fmt.Errorf("could not convert Eth transaction to EthTx: %w", err)
}

return &tx, nil
}
}
Expand Down Expand Up @@ -318,7 +331,7 @@ func (a *ethAPI) EthGetMessageCidByTransactionHash(ctx context.Context, txHash *
}

func (a *ethAPI) EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*types.EthHash, error) {
hash, err := ethTxHashFromMessageCid(ctx, cid, a.em.chainModule.MessageStore, a.chain)
hash, err := ethTxHashFromMessageCid(ctx, cid, a.em.chainModule.MessageStore)
if hash == types.EmptyEthHash {
// not found
return nil, nil
Expand Down Expand Up @@ -800,12 +813,12 @@ func (a *ethAPI) EthGasPrice(ctx context.Context) (types.EthBigInt, error) {
}

func (a *ethAPI) EthSendRawTransaction(ctx context.Context, rawTx types.EthBytes) (types.EthHash, error) {
txArgs, err := types.ParseEthTxArgs(rawTx)
txArgs, err := types.ParseEthTransaction(rawTx)
if err != nil {
return types.EmptyEthHash, err
}

smsg, err := txArgs.ToSignedMessage()
smsg, err := types.ToSignedFilecoinMessage(txArgs)
if err != nil {
return types.EmptyEthHash, err
}
Expand Down
20 changes: 10 additions & 10 deletions app/submodule/eth/eth_event_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func (e *ethEventAPI) EthGetLogs(ctx context.Context, filterSpec *types.EthFilte

_ = e.uninstallFilter(ctx, f)

return ethFilterResultFromEvents(ces, e.em.chainModule.MessageStore, e.ChainAPI)
return ethFilterResultFromEvents(ces, e.em.chainModule.MessageStore)
}

func (e *ethEventAPI) EthGetFilterChanges(ctx context.Context, id types.EthFilterID) (*types.EthFilterResult, error) {
Expand All @@ -174,11 +174,11 @@ func (e *ethEventAPI) EthGetFilterChanges(ctx context.Context, id types.EthFilte

switch fc := f.(type) {
case filterEventCollector:
return ethFilterResultFromEvents(fc.TakeCollectedEvents(ctx), e.em.chainModule.MessageStore, e.ChainAPI)
return ethFilterResultFromEvents(fc.TakeCollectedEvents(ctx), e.em.chainModule.MessageStore)
case filterTipSetCollector:
return ethFilterResultFromTipSets(fc.TakeCollectedTipSets(ctx))
case filterMessageCollector:
return ethFilterResultFromMessages(fc.TakeCollectedMessages(ctx), e.ChainAPI)
return ethFilterResultFromMessages(fc.TakeCollectedMessages(ctx))
}

return nil, fmt.Errorf("unknown filter type")
Expand All @@ -196,7 +196,7 @@ func (e *ethEventAPI) EthGetFilterLogs(ctx context.Context, id types.EthFilterID

switch fc := f.(type) {
case filterEventCollector:
return ethFilterResultFromEvents(fc.TakeCollectedEvents(ctx), e.em.chainModule.MessageStore, e.ChainAPI)
return ethFilterResultFromEvents(fc.TakeCollectedEvents(ctx), e.em.chainModule.MessageStore)
}

return nil, fmt.Errorf("wrong filter type")
Expand Down Expand Up @@ -625,7 +625,7 @@ func ethLogFromEvent(entries []types.EventEntry) (data []byte, topics []types.Et
return data, topics, true
}

func ethFilterResultFromEvents(evs []*filter.CollectedEvent, ms *chain.MessageStore, ca v1.IChain) (*types.EthFilterResult, error) {
func ethFilterResultFromEvents(evs []*filter.CollectedEvent, ms *chain.MessageStore) (*types.EthFilterResult, error) {
res := &types.EthFilterResult{}
for _, ev := range evs {
log := types.EthLog{
Expand All @@ -649,7 +649,7 @@ func ethFilterResultFromEvents(evs []*filter.CollectedEvent, ms *chain.MessageSt
return nil, err
}

log.TransactionHash, err = ethTxHashFromMessageCid(context.TODO(), ev.MsgCid, ms, ca)
log.TransactionHash, err = ethTxHashFromMessageCid(context.TODO(), ev.MsgCid, ms)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -692,11 +692,11 @@ func ethFilterResultFromTipSets(tsks []types.TipSetKey) (*types.EthFilterResult,
return res, nil
}

func ethFilterResultFromMessages(cs []*types.SignedMessage, ca v1.IChain) (*types.EthFilterResult, error) {
func ethFilterResultFromMessages(cs []*types.SignedMessage) (*types.EthFilterResult, error) {
res := &types.EthFilterResult{}

for _, c := range cs {
hash, err := ethTxHashFromSignedMessage(context.TODO(), c, ca)
hash, err := ethTxHashFromSignedMessage(c)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -865,7 +865,7 @@ func (e *ethSubscription) start(ctx context.Context) {
case v := <-e.in:
switch vt := v.(type) {
case *filter.CollectedEvent:
evs, err := ethFilterResultFromEvents([]*filter.CollectedEvent{vt}, e.messageStore, e.chainAPI)
evs, err := ethFilterResultFromEvents([]*filter.CollectedEvent{vt}, e.messageStore)
if err != nil {
continue
}
Expand Down Expand Up @@ -896,7 +896,7 @@ func (e *ethSubscription) start(ctx context.Context) {
e.send(ctx, ethBlock)
e.lastSentTipset = &parentTipSetKey
case *types.SignedMessage: // mpool txid
evs, err := ethFilterResultFromMessages([]*types.SignedMessage{vt}, e.chainAPI)
evs, err := ethFilterResultFromMessages([]*types.SignedMessage{vt})
if err != nil {
continue
}
Expand Down
145 changes: 112 additions & 33 deletions app/submodule/eth/eth_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"

"github.com/ipfs/go-cid"
"github.com/multiformats/go-multicodec"

"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
Expand All @@ -26,6 +27,18 @@ import (
"github.com/filecoin-project/venus/venus-shared/types"
)

// The address used in messages to actors that have since been deleted.
//
// 0xff0000000000000000000000ffffffffffffffff
var revertedEthAddress types.EthAddress

func init() {
revertedEthAddress[0] = 0xff
for i := 20 - 8; i < 20; i++ {
revertedEthAddress[i] = 0xff
}
}

func getTipsetByBlockNumber(ctx context.Context, store *chain.Store, blkParam string, strict bool) (*types.TipSet, error) {
if blkParam == "earliest" {
return nil, fmt.Errorf("block param \"earliest\" is not supported")
Expand Down Expand Up @@ -231,7 +244,6 @@ func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTx
return types.EthBlock{}, fmt.Errorf("failed to convert msg to ethTx: %w", err)
}

tx.ChainID = types.EthUint64(types2.Eip155ChainID)
tx.BlockHash = &blkHash
tx.BlockNumber = &bn
tx.TransactionIndex = &ti
Expand Down Expand Up @@ -377,8 +389,10 @@ func lookupEthAddress(ctx context.Context, addr address.Address, ca v1.IChain) (
return types.EthAddress{}, err
}

// revive:disable:empty-block easier to grok when the cases are explicit

// Lookup on the target actor and try to get an f410 address.
if actor, err := ca.GetActor(ctx, idAddr); errors.Is(err, types.ErrActorNotFound) {
if actor, err := ca.StateGetActor(ctx, idAddr, types.EmptyTSK); errors.Is(err, types.ErrActorNotFound) {
// Not found -> use a masked ID address
} else if err != nil {
// Any other error -> fail.
Expand All @@ -394,28 +408,35 @@ func lookupEthAddress(ctx context.Context, addr address.Address, ca v1.IChain) (
return types.EthAddressFromFilecoinAddress(idAddr)
}

func ethTxHashFromMessageCid(ctx context.Context, c cid.Cid, ms *chain.MessageStore, ca v1.IChain) (types.EthHash, error) {
func ethTxHashFromMessageCid(ctx context.Context, c cid.Cid, ms *chain.MessageStore) (types.EthHash, error) {
smsg, err := ms.LoadSignedMessage(ctx, c)
if err == nil {
// This is an Eth Tx, Secp message, Or BLS message in the mpool
return ethTxHashFromSignedMessage(ctx, smsg, ca)
return ethTxHashFromSignedMessage(smsg)
}

_, err = ms.LoadMessage(ctx, c)
if err == nil {
// This is a BLS message
return types.EthHashFromCid(c)
}

return types.EthHashFromCid(c)
}

func ethTxHashFromSignedMessage(ctx context.Context, smsg *types.SignedMessage, ca v1.IChain) (types.EthHash, error) {
func ethTxHashFromSignedMessage(smsg *types.SignedMessage) (types.EthHash, error) {
if smsg.Signature.Type == crypto.SigTypeDelegated {
ethTx, err := newEthTxFromSignedMessage(ctx, smsg, ca)
tx, err := types.EthTransactionFromSignedFilecoinMessage(smsg)
if err != nil {
return types.EmptyEthHash, err
return types.EthHash{}, fmt.Errorf("failed to convert from signed message: %w", err)
}
return ethTx.Hash, nil

return tx.TxHash()
} else if smsg.Signature.Type == crypto.SigTypeSecp256k1 {
return types.EthHashFromCid(smsg.Cid())
} else { // BLS message
return types.EthHashFromCid(smsg.Message.Cid())
}
// else BLS message
return types.EthHashFromCid(smsg.Message.Cid())
}

func newEthTxFromSignedMessage(ctx context.Context, smsg *types.SignedMessage, ca v1.IChain) (types.EthTx, error) {
Expand All @@ -424,33 +445,31 @@ func newEthTxFromSignedMessage(ctx context.Context, smsg *types.SignedMessage, c

// This is an eth tx
if smsg.Signature.Type == crypto.SigTypeDelegated {
tx, err = types.EthTxFromSignedEthMessage(smsg)
ethTx, err := types.EthTransactionFromSignedFilecoinMessage(smsg)
if err != nil {
return types.EthTx{}, fmt.Errorf("failed to convert from signed message: %w", err)
}

tx.Hash, err = tx.TxHash()
tx, err = ethTx.ToEthTx(smsg)
if err != nil {
return types.EthTx{}, fmt.Errorf("failed to calculate hash for ethTx: %w", err)
return types.EthTx{}, fmt.Errorf("failed to convert from signed message: %w", err)
}

fromAddr, err := lookupEthAddress(ctx, smsg.Message.From, ca)
} else if smsg.Signature.Type == crypto.SigTypeSecp256k1 { // Secp Filecoin Message
tx, err = ethTxFromNativeMessage(ctx, smsg.VMMessage(), ca)
if err != nil {
return types.EthTx{}, fmt.Errorf("failed to resolve Ethereum address: %w", err)
return types.EthTx{}, err
}

tx.From = fromAddr
} else if smsg.Signature.Type == crypto.SigTypeSecp256k1 { // Secp Filecoin Message
tx = ethTxFromNativeMessage(ctx, smsg.VMMessage(), ca)
tx.Hash, err = types.EthHashFromCid(smsg.Cid())
if err != nil {
return tx, err
return types.EthTx{}, err
}
} else { // BLS Filecoin message
tx = ethTxFromNativeMessage(ctx, smsg.VMMessage(), ca)
tx, err = ethTxFromNativeMessage(ctx, smsg.VMMessage(), ca)
if err != nil {
return types.EthTx{}, err
}
tx.Hash, err = types.EthHashFromCid(smsg.Message.Cid())
if err != nil {
return tx, err
return types.EthTx{}, err
}
}

Expand All @@ -473,27 +492,76 @@ func parseEthTopics(topics types.EthTopicSpec) (map[string][][]byte, error) {
return keys, nil
}

// Convert a native message to an eth transaction.
//
// - The state-tree must be from after the message was applied (ideally the following tipset).
// - In some cases, the "to" address may be `0xff0000000000000000000000ffffffffffffffff`. This
// means that the "to" address has not been assigned in the passed state-tree and can only
// happen if the transaction reverted.
//
// ethTxFromNativeMessage does NOT populate:
// - BlockHash
// - BlockNumber
// - TransactionIndex
// - Hash
func ethTxFromNativeMessage(ctx context.Context, msg *types.Message, ca v1.IChain) types.EthTx {
// We don't care if we error here, conversion is best effort for non-eth transactions
from, _ := lookupEthAddress(ctx, msg.From, ca)
to, _ := lookupEthAddress(ctx, msg.To, ca)
return types.EthTx{
func ethTxFromNativeMessage(ctx context.Context, msg *types.Message, ca v1.IChain) (types.EthTx, error) {
// Lookup the from address. This must succeed.
from, err := lookupEthAddress(ctx, msg.From, ca)
if err != nil {
return types.EthTx{}, fmt.Errorf("failed to lookup sender address %s when converting a native message to an eth txn: %w", msg.From, err)
}
// Lookup the to address. If the recipient doesn't exist, we replace the address with a
// known sentinel address.
to, err := lookupEthAddress(ctx, msg.To, ca)
if err != nil {
if !errors.Is(err, types.ErrActorNotFound) {
return types.EthTx{}, fmt.Errorf("failed to lookup receiver address %s when converting a native message to an eth txn: %w", msg.To, err)
}
to = revertedEthAddress
}

// For empty, we use "0" as the codec. Otherwise, we use CBOR for message
// parameters.
var codec uint64
if len(msg.Params) > 0 {
codec = uint64(multicodec.Cbor)
}

maxFeePerGas := types.EthBigInt(msg.GasFeeCap)
maxPriorityFeePerGas := types.EthBigInt(msg.GasPremium)

// We decode as a native call first.
ethTx := types.EthTx{
To: &to,
From: from,
Input: encodeFilecoinParamsAsABI(msg.Method, codec, msg.Params),
Nonce: types.EthUint64(msg.Nonce),
ChainID: types.EthUint64(types2.Eip155ChainID),
Value: types.EthBigInt(msg.Value),
Type: types.Eip1559TxType,
Type: types.EIP1559TxType,
Gas: types.EthUint64(msg.GasLimit),
MaxFeePerGas: types.EthBigInt(msg.GasFeeCap),
MaxPriorityFeePerGas: types.EthBigInt(msg.GasPremium),
MaxFeePerGas: &maxFeePerGas,
MaxPriorityFeePerGas: &maxPriorityFeePerGas,
AccessList: []types.EthHash{},
}

// Then we try to see if it's "special". If we fail, we ignore the error and keep treating
// it as a native message. Unfortunately, the user is free to send garbage that may not
// properly decode.
if msg.Method == builtin.MethodsEVM.InvokeContract {
// try to decode it as a contract invocation first.
if inp, err := decodePayload(msg.Params, codec); err == nil {
ethTx.Input = []byte(inp)
}
} else if msg.To == builtin.EthereumAddressManagerActorAddr && msg.Method == builtin.MethodsEAM.CreateExternal {
// Then, try to decode it as a contract deployment from an EOA.
if inp, err := decodePayload(msg.Params, codec); err == nil {
ethTx.Input = []byte(inp)
ethTx.To = nil
}
}

return ethTx, nil
}

// newEthTxFromMessageLookup creates an ethereum transaction from filecoin message lookup. If a negative txIdx is passed
Expand Down Expand Up @@ -618,7 +686,18 @@ func newEthTxReceipt(ctx context.Context, tx types.EthTx, lookup *types.MsgLooku
}

baseFee := parentTS.Blocks()[0].ParentBaseFee
gasOutputs := gas.ComputeGasOutputs(lookup.Receipt.GasUsed, int64(tx.Gas), baseFee, big.Int(tx.MaxFeePerGas), big.Int(tx.MaxPriorityFeePerGas), true)

gasFeeCap, err := tx.GasFeeCap()
if err != nil {
return types.EthTxReceipt{}, fmt.Errorf("failed to get gas fee cap: %w", err)
}
gasPremium, err := tx.GasPremium()
if err != nil {
return types.EthTxReceipt{}, fmt.Errorf("failed to get gas premium: %w", err)
}

gasOutputs := gas.ComputeGasOutputs(lookup.Receipt.GasUsed, int64(tx.Gas), baseFee, big.Int(gasFeeCap),
big.Int(gasPremium), true)
totalSpent := big.Sum(gasOutputs.BaseFeeBurn, gasOutputs.MinerTip, gasOutputs.OverEstimationBurn)

effectiveGasPrice := big.Zero()
Expand Down
Loading
Loading