feat: ETH compatibility in Filecoin : Support Homestead and EIP-155 Ethereum transactions("legacy" transactions) in Filecoin (#11969)

* poc for eth legacy tx

* print statements

* finished

* tests work

* remove print statements

* Remove all print statements

* remove extraneous changes

* cleaned up code and interface

* run make jen

* dont duplicate signature

* go mod tidy and remove prints

* clean up tests

* test for conversion

* changes as per review

* more unit tests for legacy txns

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* address review comments from Rodd

* changes as per zen's 2nd review

* go mod tidy

* feat: ETH compatibility in Filecoin : Support EIP-155 Ethereum transactions in Filecoin (#11970)

* itests passing for 155 tx

* first working version for EIP-155 transactions

* green itest

* add docs

* tests

* remove print stmt

* remove print stmt

* validate signature

* changes as per zen's review

* correct signature verification

* gate tx by Network Version

* handle arajsek review

* fix imports order

* fix lint

* dont lock in mpool for network gating ETH messages

* sender can be an ID address

---------

Co-authored-by: Rod Vagg <rod@vagg.org>
This commit is contained in:
Aarsh Shah 2024-06-05 09:25:50 +04:00 committed by GitHub
parent 423d8a798f
commit c9c070727a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 2731 additions and 787 deletions

View File

@ -6543,6 +6543,7 @@
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"gasPrice": "0x0",
"accessList": [
"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"
],
@ -6598,6 +6599,10 @@
"title": "number",
"type": "number"
},
"gasPrice": {
"additionalProperties": false,
"type": "object"
},
"hash": {
"items": {
"description": "Number is a number",
@ -6738,6 +6743,7 @@
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"gasPrice": "0x0",
"accessList": [
"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"
],
@ -6793,6 +6799,10 @@
"title": "number",
"type": "number"
},
"gasPrice": {
"additionalProperties": false,
"type": "object"
},
"hash": {
"items": {
"description": "Number is a number",
@ -6925,6 +6935,7 @@
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"gasPrice": "0x0",
"accessList": [
"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"
],
@ -6980,6 +6991,10 @@
"title": "number",
"type": "number"
},
"gasPrice": {
"additionalProperties": false,
"type": "object"
},
"hash": {
"items": {
"description": "Number is a number",
@ -7129,6 +7144,7 @@
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"gasPrice": "0x0",
"accessList": [
"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"
],
@ -7184,6 +7200,10 @@
"title": "number",
"type": "number"
},
"gasPrice": {
"additionalProperties": false,
"type": "object"
},
"hash": {
"items": {
"description": "Number is a number",

View File

@ -3132,6 +3132,7 @@
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"gasPrice": "0x0",
"accessList": [
"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"
],
@ -3187,6 +3188,10 @@
"title": "number",
"type": "number"
},
"gasPrice": {
"additionalProperties": false,
"type": "object"
},
"hash": {
"items": {
"description": "Number is a number",
@ -3336,6 +3341,7 @@
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"gasPrice": "0x0",
"accessList": [
"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"
],
@ -3391,6 +3397,10 @@
"title": "number",
"type": "number"
},
"gasPrice": {
"additionalProperties": false,
"type": "object"
},
"hash": {
"items": {
"description": "Number is a number",

View File

@ -29,6 +29,7 @@ import (
"github.com/filecoin-project/lotus/chain/stmgr"
"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"
"github.com/filecoin-project/lotus/lib/async"
"github.com/filecoin-project/lotus/metrics"
@ -131,6 +132,18 @@ func CommonBlkChecks(ctx context.Context, sm *stmgr.StateManager, cs *store.Chai
}
}
func IsValidEthTxForSending(nv network.Version, smsg *types.SignedMessage) bool {
ethTx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(smsg)
if err != nil {
return false
}
if nv < network.Version23 && ethTx.Type() != ethtypes.EIP1559TxType {
return false
}
return true
}
func IsValidForSending(nv network.Version, act *types.Actor) bool {
// Before nv18 (Hygge), we only supported built-in account actors as senders.
//
@ -276,6 +289,10 @@ func checkBlockMessages(ctx context.Context, sm *stmgr.StateManager, cs *store.C
return xerrors.Errorf("block had invalid signed message at index %d: %w", i, err)
}
if m.Signature.Type == crypto.SigTypeDelegated && !IsValidEthTxForSending(nv, m) {
return xerrors.Errorf("network version should be atleast NV23 for sending legacy ETH transactions; but current network version is %d", nv)
}
if err := checkMsg(m); err != nil {
return xerrors.Errorf("block had invalid secpk message at index %d: %w", i, err)
}

View File

@ -18,34 +18,42 @@ import (
// must be recognized by the registered verifier for the signature type.
func AuthenticateMessage(msg *types.SignedMessage, signer address.Address) error {
var digest []byte
signatureType := msg.Signature.Type
signatureCopy := msg.Signature
typ := msg.Signature.Type
switch typ {
switch signatureType {
case crypto.SigTypeDelegated:
txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(&msg.Message)
signatureCopy.Data = make([]byte, len(msg.Signature.Data))
copy(signatureCopy.Data, msg.Signature.Data)
ethTx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(msg)
if err != nil {
return xerrors.Errorf("failed to reconstruct eth transaction: %w", err)
}
roundTripMsg, err := txArgs.ToUnsignedMessage(msg.Message.From)
if err != nil {
return xerrors.Errorf("failed to reconstruct filecoin msg: %w", err)
return xerrors.Errorf("failed to reconstruct Ethereum transaction: %w", err)
}
if !msg.Message.Equals(roundTripMsg) {
return xerrors.New("ethereum tx failed to roundtrip")
filecoinMsg, err := ethTx.ToUnsignedFilecoinMessage(msg.Message.From)
if err != nil {
return xerrors.Errorf("failed to reconstruct Filecoin message: %w", err)
}
rlpEncodedMsg, err := txArgs.ToRlpUnsignedMsg()
if !msg.Message.Equals(filecoinMsg) {
return xerrors.New("Ethereum transaction roundtrip mismatch")
}
rlpEncodedMsg, err := ethTx.ToRlpUnsignedMsg()
if err != nil {
return xerrors.Errorf("failed to repack eth rlp message: %w", err)
return xerrors.Errorf("failed to encode RLP message: %w", err)
}
digest = rlpEncodedMsg
signatureCopy.Data, err = ethTx.ToVerifiableSignature(signatureCopy.Data)
if err != nil {
return xerrors.Errorf("failed to verify signature: %w", err)
}
default:
digest = msg.Message.Cid().Bytes()
}
if err := sigs.Verify(&msg.Signature, signer, digest); err != nil {
return xerrors.Errorf("message %s has invalid signature (type %d): %w", msg.Cid(), typ, err)
if err := sigs.Verify(&signatureCopy, signer, digest); err != nil {
return xerrors.Errorf("invalid signature for message %s (type %d): %w", msg.Cid(), signatureType, err)
}
return nil
}

View File

@ -887,6 +887,11 @@ func (mp *MessagePool) addTs(ctx context.Context, m *types.SignedMessage, curTs
nv := mp.api.StateNetworkVersion(ctx, epoch)
// TODO: I'm not thrilled about depending on filcns here, but I prefer this to duplicating logic
if m.Signature.Type == crypto.SigTypeDelegated && !consensus.IsValidEthTxForSending(nv, m) {
return false, xerrors.Errorf("network version should be atleast NV23 for sending legacy ETH transactions; but current network version is %d", nv)
}
if !consensus.IsValidForSending(nv, senderAct) {
return false, xerrors.Errorf("sender actor %s is not a valid top-level sender", m.Message.From)
}

View File

@ -196,7 +196,7 @@ func (ms *MessageSigner) dstoreKey(addr address.Address) datastore.Key {
func SigningBytes(msg *types.Message, sigType address.Protocol) ([]byte, error) {
if sigType == address.Delegated {
txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(msg)
txArgs, err := ethtypes.Eth1559TxArgsFromUnsignedFilecoinMessage(msg)
if err != nil {
return nil, xerrors.Errorf("failed to reconstruct eth transaction: %w", err)
}

View File

@ -0,0 +1,342 @@
package ethtypes
import (
"fmt"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/big"
typescrypto "github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/types"
)
var _ EthTransaction = (*Eth1559TxArgs)(nil)
type Eth1559TxArgs struct {
ChainID int `json:"chainId"`
Nonce int `json:"nonce"`
To *EthAddress `json:"to"`
Value big.Int `json:"value"`
MaxFeePerGas big.Int `json:"maxFeePerGas"`
MaxPriorityFeePerGas big.Int `json:"maxPriorityFeePerGas"`
GasLimit int `json:"gasLimit"`
Input []byte `json:"input"`
V big.Int `json:"v"`
R big.Int `json:"r"`
S big.Int `json:"s"`
}
func (tx *Eth1559TxArgs) ToUnsignedFilecoinMessage(from address.Address) (*types.Message, error) {
if tx.ChainID != build.Eip155ChainId {
return nil, fmt.Errorf("invalid chain id: %d", tx.ChainID)
}
mi, err := getFilecoinMethodInfo(tx.To, tx.Input)
if err != nil {
return nil, xerrors.Errorf("failed to get method info: %w", err)
}
return &types.Message{
Version: 0,
To: mi.to,
From: from,
Nonce: uint64(tx.Nonce),
Value: tx.Value,
GasLimit: int64(tx.GasLimit),
GasFeeCap: tx.MaxFeePerGas,
GasPremium: tx.MaxPriorityFeePerGas,
Method: mi.method,
Params: mi.params,
}, nil
}
func (tx *Eth1559TxArgs) ToRlpUnsignedMsg() ([]byte, error) {
encoded, err := toRlpUnsignedMsg(tx)
if err != nil {
return nil, err
}
return append([]byte{EIP1559TxType}, encoded...), nil
}
func (tx *Eth1559TxArgs) TxHash() (EthHash, error) {
rlp, err := tx.ToRlpSignedMsg()
if err != nil {
return EmptyEthHash, err
}
return EthHashFromTxBytes(rlp), nil
}
func (tx *Eth1559TxArgs) ToRlpSignedMsg() ([]byte, error) {
encoded, err := toRlpSignedMsg(tx, tx.V, tx.R, tx.S)
if err != nil {
return nil, err
}
return append([]byte{EIP1559TxType}, encoded...), nil
}
func (tx *Eth1559TxArgs) Signature() (*typescrypto.Signature, error) {
r := tx.R.Int.Bytes()
s := tx.S.Int.Bytes()
v := tx.V.Int.Bytes()
sig := append([]byte{}, padLeadingZeros(r, 32)...)
sig = append(sig, padLeadingZeros(s, 32)...)
if len(v) == 0 {
sig = append(sig, 0)
} else {
sig = append(sig, v[0])
}
if len(sig) != 65 {
return nil, xerrors.Errorf("signature is not 65 bytes")
}
return &typescrypto.Signature{
Type: typescrypto.SigTypeDelegated, Data: sig,
}, nil
}
func (tx *Eth1559TxArgs) Sender() (address.Address, error) {
return sender(tx)
}
func (tx *Eth1559TxArgs) Type() int {
return EIP1559TxType
}
func (tx *Eth1559TxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) {
return sig, nil
}
func (tx *Eth1559TxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) {
from, err := EthAddressFromFilecoinAddress(smsg.Message.From)
if err != nil {
return EthTx{}, xerrors.Errorf("sender was not an eth account")
}
hash, err := tx.TxHash()
if err != nil {
return EthTx{}, err
}
gasFeeCap := EthBigInt(tx.MaxFeePerGas)
gasPremium := EthBigInt(tx.MaxPriorityFeePerGas)
ethTx := EthTx{
ChainID: EthUint64(build.Eip155ChainId),
Type: EIP1559TxType,
Nonce: EthUint64(tx.Nonce),
Hash: hash,
To: tx.To,
Value: EthBigInt(tx.Value),
Input: tx.Input,
Gas: EthUint64(tx.GasLimit),
MaxFeePerGas: &gasFeeCap,
MaxPriorityFeePerGas: &gasPremium,
From: from,
R: EthBigInt(tx.R),
S: EthBigInt(tx.S),
V: EthBigInt(tx.V),
}
return ethTx, nil
}
func (tx *Eth1559TxArgs) InitialiseSignature(sig typescrypto.Signature) error {
if sig.Type != typescrypto.SigTypeDelegated {
return xerrors.Errorf("RecoverSignature only supports Delegated signature")
}
if len(sig.Data) != EthEIP1559TxSignatureLen {
return xerrors.Errorf("signature should be 65 bytes long, but got %d bytes", len(sig.Data))
}
r_, err := parseBigInt(sig.Data[0:32])
if err != nil {
return xerrors.Errorf("cannot parse r into EthBigInt")
}
s_, err := parseBigInt(sig.Data[32:64])
if err != nil {
return xerrors.Errorf("cannot parse s into EthBigInt")
}
v_, err := parseBigInt([]byte{sig.Data[64]})
if err != nil {
return xerrors.Errorf("cannot parse v into EthBigInt")
}
tx.R = r_
tx.S = s_
tx.V = v_
return nil
}
func (tx *Eth1559TxArgs) packTxFields() ([]interface{}, error) {
chainId, err := formatInt(tx.ChainID)
if err != nil {
return nil, err
}
nonce, err := formatInt(tx.Nonce)
if err != nil {
return nil, err
}
maxPriorityFeePerGas, err := formatBigInt(tx.MaxPriorityFeePerGas)
if err != nil {
return nil, err
}
maxFeePerGas, err := formatBigInt(tx.MaxFeePerGas)
if err != nil {
return nil, err
}
gasLimit, err := formatInt(tx.GasLimit)
if err != nil {
return nil, err
}
value, err := formatBigInt(tx.Value)
if err != nil {
return nil, err
}
res := []interface{}{
chainId,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
gasLimit,
formatEthAddr(tx.To),
value,
tx.Input,
[]interface{}{}, // access list
}
return res, nil
}
func parseEip1559Tx(data []byte) (*Eth1559TxArgs, error) {
if data[0] != EIP1559TxType {
return nil, xerrors.Errorf("not an EIP-1559 transaction: first byte is not %d", EIP1559TxType)
}
d, err := DecodeRLP(data[1:])
if err != nil {
return nil, err
}
decoded, ok := d.([]interface{})
if !ok {
return nil, xerrors.Errorf("not an EIP-1559 transaction: decoded data is not a list")
}
if len(decoded) != 12 {
return nil, xerrors.Errorf("not an EIP-1559 transaction: should have 12 elements in the rlp list")
}
chainId, err := parseInt(decoded[0])
if err != nil {
return nil, err
}
nonce, err := parseInt(decoded[1])
if err != nil {
return nil, err
}
maxPriorityFeePerGas, err := parseBigInt(decoded[2])
if err != nil {
return nil, err
}
maxFeePerGas, err := parseBigInt(decoded[3])
if err != nil {
return nil, err
}
gasLimit, err := parseInt(decoded[4])
if err != nil {
return nil, err
}
to, err := parseEthAddr(decoded[5])
if err != nil {
return nil, err
}
value, err := parseBigInt(decoded[6])
if err != nil {
return nil, err
}
input, err := parseBytes(decoded[7])
if err != nil {
return nil, err
}
accessList, ok := decoded[8].([]interface{})
if !ok || (ok && len(accessList) != 0) {
return nil, xerrors.Errorf("access list should be an empty list")
}
r, err := parseBigInt(decoded[10])
if err != nil {
return nil, err
}
s, err := parseBigInt(decoded[11])
if err != nil {
return nil, err
}
v, err := parseBigInt(decoded[9])
if err != nil {
return nil, err
}
// EIP-1559 and EIP-2930 transactions only support 0 or 1 for v
// Legacy and EIP-155 transactions support other values
// https://github.com/ethers-io/ethers.js/blob/56fabe987bb8c1e4891fdf1e5d3fe8a4c0471751/packages/transactions/src.ts/index.ts#L333
if !v.Equals(big.NewInt(0)) && !v.Equals(big.NewInt(1)) {
return nil, xerrors.Errorf("EIP-1559 transactions only support 0 or 1 for v")
}
args := Eth1559TxArgs{
ChainID: chainId,
Nonce: nonce,
To: to,
MaxPriorityFeePerGas: maxPriorityFeePerGas,
MaxFeePerGas: maxFeePerGas,
GasLimit: gasLimit,
Value: value,
Input: input,
V: v,
R: r,
S: s,
}
return &args, nil
}
func Eth1559TxArgsFromUnsignedFilecoinMessage(msg *types.Message) (*Eth1559TxArgs, error) {
if msg.Version != 0 {
return nil, fmt.Errorf("unsupported msg version: %d", msg.Version)
}
params, to, err := getEthParamsAndRecipient(msg)
if err != nil {
return nil, fmt.Errorf("failed to get eth params and recipient: %w", err)
}
return &Eth1559TxArgs{
ChainID: build.Eip155ChainId,
Nonce: int(msg.Nonce),
To: to,
Value: msg.Value,
Input: params,
MaxFeePerGas: msg.GasFeeCap,
MaxPriorityFeePerGas: msg.GasPremium,
GasLimit: int(msg.GasLimit),
}, nil
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,303 @@
package ethtypes
import (
"fmt"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/big"
typescrypto "github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/types"
)
var _ EthTransaction = (*EthLegacy155TxArgs)(nil)
// EthLegacy155TxArgs is a legacy Ethereum transaction that uses the EIP-155 chain replay protection mechanism
// by incorporating the chainId in the signature.
// See how the `V` value in the signature is derived from the chainId at
// https://github.com/ethereum/go-ethereum/blob/86a1f0c39494c8f5caddf6bd9fbddd4bdfa944fd/core/types/transaction_signing.go#L424
// For EthLegacy155TxArgs, the digest that is used to create a signed transaction includes the `ChainID` but the serialised RLP transaction
// does not include the `ChainID` as an explicit field. Instead, the `ChainID` is included in the V value of the signature as mentioned above.
type EthLegacy155TxArgs struct {
legacyTx *EthLegacyHomesteadTxArgs
}
func NewEthLegacy155TxArgs(tx *EthLegacyHomesteadTxArgs) *EthLegacy155TxArgs {
return &EthLegacy155TxArgs{legacyTx: tx}
}
func (tx *EthLegacy155TxArgs) GetLegacyTx() *EthLegacyHomesteadTxArgs {
return tx.legacyTx
}
func (tx *EthLegacy155TxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) {
from, err := EthAddressFromFilecoinAddress(smsg.Message.From)
if err != nil {
return EthTx{}, fmt.Errorf("sender was not an eth account")
}
hash, err := tx.TxHash()
if err != nil {
return EthTx{}, fmt.Errorf("failed to get tx hash: %w", err)
}
gasPrice := EthBigInt(tx.legacyTx.GasPrice)
ethTx := EthTx{
ChainID: build.Eip155ChainId,
Type: EthLegacyTxType,
Nonce: EthUint64(tx.legacyTx.Nonce),
Hash: hash,
To: tx.legacyTx.To,
Value: EthBigInt(tx.legacyTx.Value),
Input: tx.legacyTx.Input,
Gas: EthUint64(tx.legacyTx.GasLimit),
GasPrice: &gasPrice,
From: from,
R: EthBigInt(tx.legacyTx.R),
S: EthBigInt(tx.legacyTx.S),
V: EthBigInt(tx.legacyTx.V),
}
return ethTx, nil
}
func (tx *EthLegacy155TxArgs) ToUnsignedFilecoinMessage(from address.Address) (*types.Message, error) {
if err := validateEIP155ChainId(tx.legacyTx.V); err != nil {
return nil, fmt.Errorf("failed to validate EIP155 chain id: %w", err)
}
return tx.legacyTx.ToUnsignedFilecoinMessage(from)
}
func (tx *EthLegacy155TxArgs) ToRlpUnsignedMsg() ([]byte, error) {
return toRlpUnsignedMsg(tx)
}
func (tx *EthLegacy155TxArgs) TxHash() (EthHash, error) {
encoded, err := tx.ToRawTxBytesSigned()
if err != nil {
return EthHash{}, fmt.Errorf("failed to encode rlp signed msg: %w", err)
}
return EthHashFromTxBytes(encoded), nil
}
func (tx *EthLegacy155TxArgs) ToRawTxBytesSigned() ([]byte, error) {
packed1, err := tx.packTxFields()
if err != nil {
return nil, err
}
packed1 = packed1[:len(packed1)-3] // remove chainId, r and s as they are only used for signature verification
packed2, err := packSigFields(tx.legacyTx.V, tx.legacyTx.R, tx.legacyTx.S)
if err != nil {
return nil, err
}
encoded, err := EncodeRLP(append(packed1, packed2...))
if err != nil {
return nil, fmt.Errorf("failed to encode rlp signed msg: %w", err)
}
return encoded, nil
}
func (tx *EthLegacy155TxArgs) ToRlpSignedMsg() ([]byte, error) {
return toRlpSignedMsg(tx, tx.legacyTx.V, tx.legacyTx.R, tx.legacyTx.S)
}
func (tx *EthLegacy155TxArgs) Signature() (*typescrypto.Signature, error) {
if err := validateEIP155ChainId(tx.legacyTx.V); err != nil {
return nil, fmt.Errorf("failed to validate EIP155 chain id: %w", err)
}
r := tx.legacyTx.R.Int.Bytes()
s := tx.legacyTx.S.Int.Bytes()
v := tx.legacyTx.V.Int.Bytes()
sig := append([]byte{}, padLeadingZeros(r, 32)...)
sig = append(sig, padLeadingZeros(s, 32)...)
sig = append(sig, v...)
// pre-pend a one byte marker so nodes know that this is a legacy transaction
sig = append([]byte{EthLegacy155TxSignaturePrefix}, sig...)
if len(sig) != EthLegacy155TxSignatureLen0 && len(sig) != EthLegacy155TxSignatureLen1 {
return nil, fmt.Errorf("signature is not %d OR %d bytes; it is %d bytes", EthLegacy155TxSignatureLen0, EthLegacy155TxSignatureLen1,
len(sig))
}
return &typescrypto.Signature{
Type: typescrypto.SigTypeDelegated, Data: sig,
}, nil
}
func (tx *EthLegacy155TxArgs) Sender() (address.Address, error) {
if err := validateEIP155ChainId(tx.legacyTx.V); err != nil {
return address.Address{}, fmt.Errorf("failed to validate EIP155 chain id: %w", err)
}
return sender(tx)
}
func (tx *EthLegacy155TxArgs) Type() int {
return EthLegacyTxType
}
var big8 = big.NewInt(8)
func (tx *EthLegacy155TxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) {
if len(sig) != EthLegacy155TxSignatureLen0 && len(sig) != EthLegacy155TxSignatureLen1 {
return nil, fmt.Errorf("signature should be %d or %d bytes long but got %d bytes",
EthLegacy155TxSignatureLen0, EthLegacy155TxSignatureLen1, len(sig))
}
if sig[0] != EthLegacy155TxSignaturePrefix {
return nil, fmt.Errorf("expected signature prefix 0x%x, but got 0x%x", EthLegacy155TxSignaturePrefix, sig[0])
}
// Remove the prefix byte as it's only used for legacy transaction identification
sig = sig[1:]
// Extract the 'v' value from the signature
vValue := big.NewFromGo(big.NewInt(0).SetBytes(sig[64:]))
if err := validateEIP155ChainId(vValue); err != nil {
return nil, fmt.Errorf("failed to validate EIP155 chain id: %w", err)
}
// See https://github.com/ethereum/go-ethereum/blob/86a1f0c39494c8f5caddf6bd9fbddd4bdfa944fd/core/types/transaction_signing.go#L424
chainIdMul := big.Mul(big.NewIntUnsigned(build.Eip155ChainId), big.NewInt(2))
vValue = big.Sub(vValue, chainIdMul)
vValue = big.Sub(vValue, big8)
// Adjust 'v' value for compatibility with new transactions: 27 -> 0, 28 -> 1
if vValue.Equals(big.NewInt(27)) {
sig[64] = 0
} else if vValue.Equals(big.NewInt(28)) {
sig[64] = 1
} else {
return nil, fmt.Errorf("invalid 'v' value: expected 27 or 28, got %d", vValue.Int64())
}
return sig[0:65], nil
}
func (tx *EthLegacy155TxArgs) InitialiseSignature(sig typescrypto.Signature) error {
if sig.Type != typescrypto.SigTypeDelegated {
return fmt.Errorf("RecoverSignature only supports Delegated signature")
}
if len(sig.Data) != EthLegacy155TxSignatureLen0 && len(sig.Data) != EthLegacy155TxSignatureLen1 {
return fmt.Errorf("signature should be %d or %d bytes long, but got %d bytes", EthLegacy155TxSignatureLen0,
EthLegacy155TxSignatureLen1, len(sig.Data))
}
if sig.Data[0] != EthLegacy155TxSignaturePrefix {
return fmt.Errorf("expected signature prefix 0x01, but got 0x%x", sig.Data[0])
}
// ignore the first byte of the signature as it's only used for legacy transaction identification
r_, err := parseBigInt(sig.Data[1:33])
if err != nil {
return fmt.Errorf("cannot parse r into EthBigInt: %w", err)
}
s_, err := parseBigInt(sig.Data[33:65])
if err != nil {
return fmt.Errorf("cannot parse s into EthBigInt: %w", err)
}
v_, err := parseBigInt(sig.Data[65:])
if err != nil {
return fmt.Errorf("cannot parse v into EthBigInt: %w", err)
}
if err := validateEIP155ChainId(v_); err != nil {
return fmt.Errorf("failed to validate EIP155 chain id: %w", err)
}
tx.legacyTx.R = r_
tx.legacyTx.S = s_
tx.legacyTx.V = v_
return nil
}
func (tx *EthLegacy155TxArgs) packTxFields() ([]interface{}, error) {
nonce, err := formatInt(tx.legacyTx.Nonce)
if err != nil {
return nil, err
}
// format gas price
gasPrice, err := formatBigInt(tx.legacyTx.GasPrice)
if err != nil {
return nil, err
}
gasLimit, err := formatInt(tx.legacyTx.GasLimit)
if err != nil {
return nil, err
}
value, err := formatBigInt(tx.legacyTx.Value)
if err != nil {
return nil, err
}
chainIdBigInt := big.NewIntUnsigned(build.Eip155ChainId)
chainId, err := formatBigInt(chainIdBigInt)
if err != nil {
return nil, err
}
r, err := formatInt(0)
if err != nil {
return nil, err
}
s, err := formatInt(0)
if err != nil {
return nil, err
}
res := []interface{}{
nonce,
gasPrice,
gasLimit,
formatEthAddr(tx.legacyTx.To),
value,
tx.legacyTx.Input,
chainId,
r, s,
}
return res, nil
}
func validateEIP155ChainId(v big.Int) error {
chainId := deriveEIP155ChainId(v)
if !chainId.Equals(big.NewIntUnsigned(build.Eip155ChainId)) {
return fmt.Errorf("invalid chain id, expected %d, got %s", build.Eip155ChainId, chainId.String())
}
return nil
}
// deriveEIP155ChainId derives the chain id from the given v parameter
func deriveEIP155ChainId(v big.Int) big.Int {
if big.BitLen(v) <= 64 {
vUint64 := v.Uint64()
if vUint64 == 27 || vUint64 == 28 {
return big.NewInt(0)
}
return big.NewIntUnsigned((vUint64 - 35) / 2)
}
v = big.Sub(v, big.NewInt(35))
return big.Div(v, big.NewInt(2))
}
func calcEIP155TxSignatureLen(chain uint64, v int) int {
chainId := big.NewIntUnsigned(chain)
vVal := big.Add(big.Mul(chainId, big.NewInt(2)), big.NewInt(int64(v)))
vLen := len(vVal.Int.Bytes())
// EthLegacyHomesteadTxSignatureLen includes the 1 byte legacy tx marker prefix and also 1 byte for the V value.
// So we subtract 1 to not double count the length of the v value
return EthLegacyHomesteadTxSignatureLen + vLen - 1
}

View File

@ -0,0 +1,188 @@
package ethtypes
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/filecoin-project/go-state-types/big"
builtintypes "github.com/filecoin-project/go-state-types/builtin"
)
func TestEIP155Tx(t *testing.T) {
txStr := "f86680843b9aca00835dc1ba94c2dca9a18d4a4057921d1bcb22da05e68e46b1d06480820297a0f91ee69c4603c4f21131467ee9e06ad4a96d0a29fa8064db61f3adaea0eb6e92a07181e306bb8f773d94cc3b75e9835de00c004e072e6630c0c46971d38706bb01"
bz := mustDecodeHex(txStr)
tx, err := parseLegacyTx(bz)
require.NoError(t, err)
eth155Tx, ok := tx.(*EthLegacy155TxArgs)
require.True(t, ok)
// Verify nonce
require.EqualValues(t, 0, eth155Tx.legacyTx.Nonce)
// Verify recipient address
expectedToAddr, err := ParseEthAddress("0xc2dca9a18d4a4057921d1bcb22da05e68e46b1d0")
require.NoError(t, err)
require.EqualValues(t, expectedToAddr, *eth155Tx.legacyTx.To)
// Verify sender address
expectedFromAddr, err := ParseEthAddress("0xA2BBB73aC59b256415e91A820b224dbAF2C268FA")
require.NoError(t, err)
sender, err := eth155Tx.Sender()
require.NoError(t, err)
expectedFromFilecoinAddr, err := expectedFromAddr.ToFilecoinAddress()
require.NoError(t, err)
require.EqualValues(t, expectedFromFilecoinAddr, sender)
// Verify transaction value
expectedValue, ok := big.NewInt(0).SetString("100", 10)
require.True(t, ok)
require.True(t, eth155Tx.legacyTx.Value.Cmp(expectedValue) == 0)
// Verify gas limit and gas price
expectedGasPrice, ok := big.NewInt(0).SetString("1000000000", 10)
require.True(t, ok)
require.EqualValues(t, 6144442, eth155Tx.legacyTx.GasLimit)
require.True(t, eth155Tx.legacyTx.GasPrice.Cmp(expectedGasPrice) == 0)
require.Empty(t, eth155Tx.legacyTx.Input)
// Verify signature values (v, r, s)
expectedV, ok := big.NewInt(0).SetString("0297", 16)
require.True(t, ok)
require.True(t, eth155Tx.legacyTx.V.Cmp(expectedV) == 0)
expectedR, ok := big.NewInt(0).SetString("f91ee69c4603c4f21131467ee9e06ad4a96d0a29fa8064db61f3adaea0eb6e92", 16)
require.True(t, ok)
require.True(t, eth155Tx.legacyTx.R.Cmp(expectedR) == 0)
expectedS, ok := big.NewInt(0).SetString("7181e306bb8f773d94cc3b75e9835de00c004e072e6630c0c46971d38706bb01", 16)
require.True(t, ok)
require.True(t, eth155Tx.legacyTx.S.Cmp(expectedS) == 0)
// Convert to signed Filecoin message and verify fields
smsg, err := ToSignedFilecoinMessage(eth155Tx)
require.NoError(t, err)
require.EqualValues(t, smsg.Message.From, sender)
expectedToFilecoinAddr, err := eth155Tx.legacyTx.To.ToFilecoinAddress()
require.NoError(t, err)
require.EqualValues(t, smsg.Message.To, expectedToFilecoinAddr)
require.EqualValues(t, smsg.Message.Value, eth155Tx.legacyTx.Value)
require.EqualValues(t, smsg.Message.GasLimit, eth155Tx.legacyTx.GasLimit)
require.EqualValues(t, smsg.Message.GasFeeCap, eth155Tx.legacyTx.GasPrice)
require.EqualValues(t, smsg.Message.GasPremium, eth155Tx.legacyTx.GasPrice)
require.EqualValues(t, smsg.Message.Nonce, eth155Tx.legacyTx.Nonce)
require.Empty(t, smsg.Message.Params)
require.EqualValues(t, smsg.Message.Method, builtintypes.MethodsEVM.InvokeContract)
// Convert signed Filecoin message back to Ethereum transaction and verify equality
ethTx, err := EthTransactionFromSignedFilecoinMessage(smsg)
require.NoError(t, err)
convertedLegacyTx, ok := ethTx.(*EthLegacy155TxArgs)
require.True(t, ok)
eth155Tx.legacyTx.Input = nil
require.EqualValues(t, convertedLegacyTx, eth155Tx)
// Verify EthTx fields
ethTxVal, err := eth155Tx.ToEthTx(smsg)
require.NoError(t, err)
expectedHash, err := eth155Tx.TxHash()
require.NoError(t, err)
require.EqualValues(t, ethTxVal.Hash, expectedHash)
require.Nil(t, ethTxVal.MaxFeePerGas)
require.Nil(t, ethTxVal.MaxPriorityFeePerGas)
require.EqualValues(t, ethTxVal.Gas, eth155Tx.legacyTx.GasLimit)
require.EqualValues(t, ethTxVal.Value, eth155Tx.legacyTx.Value)
require.EqualValues(t, ethTxVal.Nonce, eth155Tx.legacyTx.Nonce)
require.EqualValues(t, ethTxVal.To, eth155Tx.legacyTx.To)
require.EqualValues(t, ethTxVal.From, expectedFromAddr)
}
func TestDeriveEIP155ChainId(t *testing.T) {
tests := []struct {
name string
v big.Int
expectedChainId big.Int
}{
{
name: "V equals 27",
v: big.NewInt(27),
expectedChainId: big.NewInt(0),
},
{
name: "V equals 28",
v: big.NewInt(28),
expectedChainId: big.NewInt(0),
},
{
name: "V small chain ID",
v: big.NewInt(37), // (37 - 35) / 2 = 1
expectedChainId: big.NewInt(1),
},
{
name: "V large chain ID",
v: big.NewInt(1001), // (1001 - 35) / 2 = 483
expectedChainId: big.NewInt(483),
},
{
name: "V very large chain ID",
v: big.NewInt(1 << 20), // (1048576 - 35) / 2 = 524770
expectedChainId: big.NewInt(524270),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := deriveEIP155ChainId(tt.v)
require.True(t, result.Equals(tt.expectedChainId), "Expected %s, got %s for V=%s", tt.expectedChainId.String(), result.String(), tt.v.String())
})
}
}
func TestCalcEIP155TxSignatureLen(t *testing.T) {
tests := []struct {
name string
chainID uint64
expected int
}{
{
name: "ChainID that fits in 1 byte",
chainID: 0x01,
expected: EthLegacyHomesteadTxSignatureLen + 1 - 1,
},
{
name: "ChainID that fits in 2 bytes",
chainID: 0x0100,
expected: EthLegacyHomesteadTxSignatureLen + 2 - 1,
},
{
name: "ChainID that fits in 3 bytes",
chainID: 0x010000,
expected: EthLegacyHomesteadTxSignatureLen + 3 - 1,
},
{
name: "ChainID that fits in 4 bytes",
chainID: 0x01000000,
expected: EthLegacyHomesteadTxSignatureLen + 4 - 1,
},
{
name: "ChainID that fits in 6 bytes",
chainID: 0x010000000000,
expected: EthLegacyHomesteadTxSignatureLen + 6 - 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := calcEIP155TxSignatureLen(tt.chainID, 1)
if result != tt.expected {
t.Errorf("calcEIP155TxSignatureLen(%d) = %d, want %d", tt.chainID, result, tt.expected)
}
})
}
}

View File

@ -0,0 +1,228 @@
package ethtypes
import (
"fmt"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/big"
typescrypto "github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/lotus/chain/types"
)
var _ EthTransaction = (*EthLegacyHomesteadTxArgs)(nil)
type EthLegacyHomesteadTxArgs struct {
Nonce int `json:"nonce"`
GasPrice big.Int `json:"gasPrice"`
GasLimit int `json:"gasLimit"`
To *EthAddress `json:"to"`
Value big.Int `json:"value"`
Input []byte `json:"input"`
V big.Int `json:"v"`
R big.Int `json:"r"`
S big.Int `json:"s"`
}
func (tx *EthLegacyHomesteadTxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) {
from, err := EthAddressFromFilecoinAddress(smsg.Message.From)
if err != nil {
return EthTx{}, fmt.Errorf("sender was not an eth account")
}
hash, err := tx.TxHash()
if err != nil {
return EthTx{}, fmt.Errorf("failed to get tx hash: %w", err)
}
gasPrice := EthBigInt(tx.GasPrice)
ethTx := EthTx{
ChainID: EthLegacyHomesteadTxChainID,
Type: EthLegacyTxType,
Nonce: EthUint64(tx.Nonce),
Hash: hash,
To: tx.To,
Value: EthBigInt(tx.Value),
Input: tx.Input,
Gas: EthUint64(tx.GasLimit),
GasPrice: &gasPrice,
From: from,
R: EthBigInt(tx.R),
S: EthBigInt(tx.S),
V: EthBigInt(tx.V),
}
return ethTx, nil
}
func (tx *EthLegacyHomesteadTxArgs) ToUnsignedFilecoinMessage(from address.Address) (*types.Message, error) {
mi, err := getFilecoinMethodInfo(tx.To, tx.Input)
if err != nil {
return nil, xerrors.Errorf("failed to get method info: %w", err)
}
return &types.Message{
Version: 0,
To: mi.to,
From: from,
Nonce: uint64(tx.Nonce),
Value: tx.Value,
GasLimit: int64(tx.GasLimit),
GasFeeCap: tx.GasPrice,
GasPremium: tx.GasPrice,
Method: mi.method,
Params: mi.params,
}, nil
}
func (tx *EthLegacyHomesteadTxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) {
if len(sig) != EthLegacyHomesteadTxSignatureLen {
return nil, fmt.Errorf("signature should be %d bytes long (1 byte metadata, %d bytes sig data), but got %d bytes",
EthLegacyHomesteadTxSignatureLen, EthLegacyHomesteadTxSignatureLen-1, len(sig))
}
if sig[0] != EthLegacyHomesteadTxSignaturePrefix {
return nil, fmt.Errorf("expected signature prefix 0x%x, but got 0x%x", EthLegacyHomesteadTxSignaturePrefix, sig[0])
}
// Remove the prefix byte as it's only used for legacy transaction identification
sig = sig[1:]
// Extract the 'v' value from the signature, which is the last byte in Ethereum signatures
vValue := big.NewFromGo(big.NewInt(0).SetBytes(sig[64:]))
// Adjust 'v' value for compatibility with new transactions: 27 -> 0, 28 -> 1
if vValue.Equals(big.NewInt(27)) {
sig[64] = 0
} else if vValue.Equals(big.NewInt(28)) {
sig[64] = 1
} else {
return nil, fmt.Errorf("invalid 'v' value: expected 27 or 28, got %d", vValue.Int64())
}
return sig, nil
}
func (tx *EthLegacyHomesteadTxArgs) ToRlpUnsignedMsg() ([]byte, error) {
return toRlpUnsignedMsg(tx)
}
func (tx *EthLegacyHomesteadTxArgs) TxHash() (EthHash, error) {
rlp, err := tx.ToRlpSignedMsg()
if err != nil {
return EthHash{}, err
}
return EthHashFromTxBytes(rlp), nil
}
func (tx *EthLegacyHomesteadTxArgs) ToRlpSignedMsg() ([]byte, error) {
return toRlpSignedMsg(tx, tx.V, tx.R, tx.S)
}
func (tx *EthLegacyHomesteadTxArgs) Signature() (*typescrypto.Signature, error) {
// throw an error if the v value is not 27 or 28
if !tx.V.Equals(big.NewInt(27)) && !tx.V.Equals(big.NewInt(28)) {
return nil, fmt.Errorf("legacy homestead transactions only support 27 or 28 for v")
}
r := tx.R.Int.Bytes()
s := tx.S.Int.Bytes()
v := tx.V.Int.Bytes()
sig := append([]byte{}, padLeadingZeros(r, 32)...)
sig = append(sig, padLeadingZeros(s, 32)...)
if len(v) == 0 {
sig = append(sig, 0)
} else {
sig = append(sig, v[0])
}
// pre-pend a one byte marker so nodes know that this is a legacy transaction
sig = append([]byte{EthLegacyHomesteadTxSignaturePrefix}, sig...)
if len(sig) != EthLegacyHomesteadTxSignatureLen {
return nil, fmt.Errorf("signature is not %d bytes", EthLegacyHomesteadTxSignatureLen)
}
return &typescrypto.Signature{
Type: typescrypto.SigTypeDelegated, Data: sig,
}, nil
}
func (tx *EthLegacyHomesteadTxArgs) Sender() (address.Address, error) {
return sender(tx)
}
func (tx *EthLegacyHomesteadTxArgs) Type() int {
return EthLegacyTxType
}
func (tx *EthLegacyHomesteadTxArgs) InitialiseSignature(sig typescrypto.Signature) error {
if sig.Type != typescrypto.SigTypeDelegated {
return fmt.Errorf("RecoverSignature only supports Delegated signature")
}
if len(sig.Data) != EthLegacyHomesteadTxSignatureLen {
return fmt.Errorf("signature should be %d bytes long, but got %d bytes", EthLegacyHomesteadTxSignatureLen, len(sig.Data))
}
if sig.Data[0] != EthLegacyHomesteadTxSignaturePrefix {
return fmt.Errorf("expected signature prefix 0x01, but got 0x%x", sig.Data[0])
}
// ignore the first byte of the signature as it's only used for legacy transaction identification
r_, err := parseBigInt(sig.Data[1:33])
if err != nil {
return fmt.Errorf("cannot parse r into EthBigInt: %w", err)
}
s_, err := parseBigInt(sig.Data[33:65])
if err != nil {
return fmt.Errorf("cannot parse s into EthBigInt: %w", err)
}
v_, err := parseBigInt([]byte{sig.Data[65]})
if err != nil {
return fmt.Errorf("cannot parse v into EthBigInt: %w", err)
}
if !v_.Equals(big.NewInt(27)) && !v_.Equals(big.NewInt(28)) {
return fmt.Errorf("legacy homestead transactions only support 27 or 28 for v")
}
tx.R = r_
tx.S = s_
tx.V = v_
return nil
}
func (tx *EthLegacyHomesteadTxArgs) packTxFields() ([]interface{}, error) {
nonce, err := formatInt(tx.Nonce)
if err != nil {
return nil, err
}
// format gas price
gasPrice, err := formatBigInt(tx.GasPrice)
if err != nil {
return nil, err
}
gasLimit, err := formatInt(tx.GasLimit)
if err != nil {
return nil, err
}
value, err := formatBigInt(tx.Value)
if err != nil {
return nil, err
}
res := []interface{}{
nonce,
gasPrice,
gasLimit,
formatEthAddr(tx.To),
value,
tx.Input,
}
return res, nil
}

View File

@ -0,0 +1,299 @@
package ethtypes
import (
"encoding/hex"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/filecoin-project/go-state-types/big"
builtintypes "github.com/filecoin-project/go-state-types/builtin"
"github.com/filecoin-project/lotus/lib/sigs"
)
func TestEthLegacyHomesteadTxArgs(t *testing.T) {
testcases := []struct {
RawTx string
ExpectedNonce uint64
ExpectedTo string
ExpectedInput string
ExpectedGasPrice big.Int
ExpectedGasLimit int
ExpectErr bool
}{
{
"0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804",
0x0,
"0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
"0xdeadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd",
big.NewInt(1),
0x5408,
false,
},
{
"0xf85f030182520794b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa07778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c",
0x3,
"0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"0x",
big.NewInt(1),
0x5207,
false,
},
}
for i, tc := range testcases {
// parse txargs
tx, err := parseLegacyTx(mustDecodeHex(tc.RawTx))
require.NoError(t, err)
msgRecovered, err := tx.ToRlpUnsignedMsg()
require.NoError(t, err)
// verify signatures
from, err := tx.Sender()
require.NoError(t, err)
smsg, err := ToSignedFilecoinMessage(tx)
require.NoError(t, err)
sig := smsg.Signature.Data[:]
sig = sig[1:]
vValue := big.NewInt(0).SetBytes(sig[64:])
vValue_ := big.Sub(big.NewFromGo(vValue), big.NewInt(27))
sig[64] = byte(vValue_.Uint64())
smsg.Signature.Data = sig
err = sigs.Verify(&smsg.Signature, from, msgRecovered)
require.NoError(t, err)
txArgs := tx.(*EthLegacyHomesteadTxArgs)
// verify data
require.EqualValues(t, tc.ExpectedNonce, txArgs.Nonce, i)
expectedTo, err := ParseEthAddress(tc.ExpectedTo)
require.NoError(t, err)
require.EqualValues(t, expectedTo, *txArgs.To, i)
require.EqualValues(t, tc.ExpectedInput, "0x"+hex.EncodeToString(txArgs.Input))
require.EqualValues(t, tc.ExpectedGasPrice, txArgs.GasPrice)
require.EqualValues(t, tc.ExpectedGasLimit, txArgs.GasLimit)
}
}
func TestLegacyHomesteadSignatures(t *testing.T) {
testcases := []struct {
RawTx string
ExpectedR string
ExpectedS string
ExpectedV string
ExpectErr bool
ExpectErrMsg string
ExpectVMismatch bool
}{
{
"0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804",
"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353",
"0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804",
"0x1b",
false,
"",
false,
},
{
"0xf85f030182520794b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa07778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c",
"0x98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a",
"0x7778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c",
"0x1b",
false,
"",
false,
},
{
"0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804",
"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353",
"0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804",
"0x1c",
false,
"",
true,
},
{
"0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804",
"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353",
"0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804",
"0x1f",
false,
"",
true,
},
{
"0x00",
"",
"",
"",
true,
"not a legacy eth transaction",
false,
},
}
for i, tc := range testcases {
tx, err := parseLegacyTx(mustDecodeHex(tc.RawTx))
if tc.ExpectErr {
require.Error(t, err)
require.Contains(t, err.Error(), tc.ExpectErrMsg)
continue
}
require.Nil(t, err)
sig, err := tx.Signature()
require.Nil(t, err)
require.NoError(t, tx.InitialiseSignature(*sig))
txArgs := tx.(*EthLegacyHomesteadTxArgs)
require.Equal(t, tc.ExpectedR, "0x"+txArgs.R.Text(16), i)
require.Equal(t, tc.ExpectedS, "0x"+txArgs.S.Text(16), i)
if tc.ExpectVMismatch {
require.NotEqual(t, tc.ExpectedV, "0x"+txArgs.V.Text(16), i)
} else {
require.Equal(t, tc.ExpectedV, "0x"+txArgs.V.Text(16), i)
}
}
}
// https://etherscan.io/getRawTx?tx=0xc55e2b90168af6972193c1f86fa4d7d7b31a29c156665d15b9cd48618b5177ef
// https://tools.deth.net/tx-decoder
func TestEtherScanLegacyRLP(t *testing.T) {
rlp := "0xf8718301efc58506fc23ac008305161594104994f45d9d697ca104e5704a7b77d7fec3537c890821878651a4d70000801ba051222d91a379452395d0abaff981af4cfcc242f25cfaf947dea8245a477731f9a03a997c910b4701cca5d933fb26064ee5af7fe3236ff0ef2b58aa50b25aff8ca5"
bz := mustDecodeHex(rlp)
tx, err := parseLegacyTx(bz)
require.NoError(t, err)
ethLegacyTx, ok := tx.(*EthLegacyHomesteadTxArgs)
require.True(t, ok)
// Verify nonce
require.EqualValues(t, 0x1efc5, ethLegacyTx.Nonce)
// Verify recipient address
expectedToAddr, err := ParseEthAddress("0x104994f45d9d697ca104e5704a7b77d7fec3537c")
require.NoError(t, err)
require.EqualValues(t, expectedToAddr, *ethLegacyTx.To)
// Verify sender address
expectedFromAddr, err := ParseEthAddress("0x32Be343B94f860124dC4fEe278FDCBD38C102D88")
require.NoError(t, err)
sender, err := ethLegacyTx.Sender()
require.NoError(t, err)
expectedFromFilecoinAddr, err := expectedFromAddr.ToFilecoinAddress()
require.NoError(t, err)
require.EqualValues(t, expectedFromFilecoinAddr, sender)
// Verify transaction value
expectedValue, ok := big.NewInt(0).SetString("821878651a4d70000", 16)
require.True(t, ok)
require.True(t, ethLegacyTx.Value.Cmp(expectedValue) == 0)
// Verify gas limit and gas price
expectedGasPrice, ok := big.NewInt(0).SetString("6fc23ac00", 16)
require.True(t, ok)
require.EqualValues(t, 0x51615, ethLegacyTx.GasLimit)
require.True(t, ethLegacyTx.GasPrice.Cmp(expectedGasPrice) == 0)
require.Empty(t, ethLegacyTx.Input)
// Verify signature values (v, r, s)
expectedV, ok := big.NewInt(0).SetString("1b", 16)
require.True(t, ok)
require.True(t, ethLegacyTx.V.Cmp(expectedV) == 0)
expectedR, ok := big.NewInt(0).SetString("51222d91a379452395d0abaff981af4cfcc242f25cfaf947dea8245a477731f9", 16)
require.True(t, ok)
require.True(t, ethLegacyTx.R.Cmp(expectedR) == 0)
expectedS, ok := big.NewInt(0).SetString("3a997c910b4701cca5d933fb26064ee5af7fe3236ff0ef2b58aa50b25aff8ca5", 16)
require.True(t, ok)
require.True(t, ethLegacyTx.S.Cmp(expectedS) == 0)
// Convert to signed Filecoin message and verify fields
smsg, err := ToSignedFilecoinMessage(ethLegacyTx)
require.NoError(t, err)
require.EqualValues(t, smsg.Message.From, sender)
expectedToFilecoinAddr, err := ethLegacyTx.To.ToFilecoinAddress()
require.NoError(t, err)
require.EqualValues(t, smsg.Message.To, expectedToFilecoinAddr)
require.EqualValues(t, smsg.Message.Value, ethLegacyTx.Value)
require.EqualValues(t, smsg.Message.GasLimit, ethLegacyTx.GasLimit)
require.EqualValues(t, smsg.Message.GasFeeCap, ethLegacyTx.GasPrice)
require.EqualValues(t, smsg.Message.GasPremium, ethLegacyTx.GasPrice)
require.EqualValues(t, smsg.Message.Nonce, ethLegacyTx.Nonce)
require.Empty(t, smsg.Message.Params)
require.EqualValues(t, smsg.Message.Method, builtintypes.MethodsEVM.InvokeContract)
// Convert signed Filecoin message back to Ethereum transaction and verify equality
ethTx, err := EthTransactionFromSignedFilecoinMessage(smsg)
require.NoError(t, err)
convertedLegacyTx, ok := ethTx.(*EthLegacyHomesteadTxArgs)
require.True(t, ok)
ethLegacyTx.Input = nil
require.EqualValues(t, convertedLegacyTx, ethLegacyTx)
// Verify EthTx fields
ethTxVal, err := ethLegacyTx.ToEthTx(smsg)
require.NoError(t, err)
expectedHash, err := ethLegacyTx.TxHash()
require.NoError(t, err)
require.EqualValues(t, ethTxVal.Hash, expectedHash)
require.Nil(t, ethTxVal.MaxFeePerGas)
require.Nil(t, ethTxVal.MaxPriorityFeePerGas)
require.EqualValues(t, ethTxVal.Gas, ethLegacyTx.GasLimit)
require.EqualValues(t, ethTxVal.Value, ethLegacyTx.Value)
require.EqualValues(t, ethTxVal.Nonce, ethLegacyTx.Nonce)
require.EqualValues(t, ethTxVal.To, ethLegacyTx.To)
require.EqualValues(t, ethTxVal.From, expectedFromAddr)
}
func TestFailurePaths(t *testing.T) {
// Test case for invalid RLP
invalidRLP := "0x08718301efc58506fc23ac008305161594104994f45d9d697ca104e5704a7b77d7fec3537c890821878651a4d70000801ba051222d91a379452395d0abaff981af4cfcc242f25cfaf947dea8245a477731f9a03a997c910b4701cca5d933fb26064ee5af7fe3236ff0ef2b58aa50b25aff8ca5"
decoded, err := hex.DecodeString(strings.TrimPrefix(invalidRLP, "0x"))
require.NoError(t, err)
_, err = parseLegacyTx(decoded)
require.Error(t, err, "Expected error for invalid RLP")
// Test case for mangled signature
mangledSignatureRLP := "0xf8718301efc58506fc23ac008305161594104994f45d9d697ca104e5704a7b77d7fec3537c890821878651a4d70000801ba051222d91a379452395d0abaff981af4cfcc242f25cfaf947dea8245a477731f9a03a997c910b4701cca5d933fb26064ee5af7fe3236ff0ef2b58aa50b25aff8ca5"
decodedSig, err := hex.DecodeString(strings.TrimPrefix(mangledSignatureRLP, "0x"))
require.NoError(t, err)
tx, err := parseLegacyTx(decodedSig)
require.NoError(t, err)
ethLegacyTx, ok := tx.(*EthLegacyHomesteadTxArgs)
require.True(t, ok)
// Mangle R value
ethLegacyTx.R = big.Add(ethLegacyTx.R, big.NewInt(1))
expectedFromAddr, err := ParseEthAddress("0x32Be343B94f860124dC4fEe278FDCBD38C102D88")
require.NoError(t, err)
expectedFromFilecoinAddr, err := expectedFromAddr.ToFilecoinAddress()
require.NoError(t, err)
senderAddr, err := ethLegacyTx.Sender()
require.NoError(t, err)
require.NotEqual(t, senderAddr, expectedFromFilecoinAddr, "Expected sender address to not match after mangling R value")
// Mangle V value
ethLegacyTx.V = big.NewInt(1)
_, err = ethLegacyTx.Sender()
require.Error(t, err, "Expected error when V value is not 27 or 28")
}

View File

@ -3,12 +3,12 @@ package ethtypes
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
mathbig "math/big"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/crypto/sha3"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
gocrypto "github.com/filecoin-project/go-crypto"
@ -21,8 +21,50 @@ import (
"github.com/filecoin-project/lotus/chain/types"
)
const Eip1559TxType = 2
const (
EthLegacyTxType = 0x00
EIP1559TxType = 0x02
)
const (
EthEIP1559TxSignatureLen = 65
EthLegacyHomesteadTxSignatureLen = 66
EthLegacyHomesteadTxSignaturePrefix = 0x01
EthLegacy155TxSignaturePrefix = 0x02
EthLegacyHomesteadTxChainID = 0x00
)
var (
EthLegacy155TxSignatureLen0 int
EthLegacy155TxSignatureLen1 int
)
func init() {
EthLegacy155TxSignatureLen0 = calcEIP155TxSignatureLen(build.Eip155ChainId, 35)
EthLegacy155TxSignatureLen1 = calcEIP155TxSignatureLen(build.Eip155ChainId, 36)
}
// EthTransaction defines the interface for Ethereum-like transactions.
// It provides methods to convert transactions to various formats,
// retrieve transaction details, and manipulate transaction signatures.
type EthTransaction interface {
Type() int
Sender() (address.Address, error)
Signature() (*typescrypto.Signature, error)
InitialiseSignature(sig typescrypto.Signature) error
ToUnsignedFilecoinMessage(from address.Address) (*types.Message, error)
ToRlpUnsignedMsg() ([]byte, error)
ToRlpSignedMsg() ([]byte, error)
TxHash() (EthHash, error)
ToVerifiableSignature(sig []byte) ([]byte, error)
ToEthTx(*types.SignedMessage) (EthTx, error)
}
// EthTx represents an Ethereum transaction structure, encapsulating fields that align with the standard Ethereum transaction components.
// This structure can represent both EIP-1559 transactions and legacy Homestead transactions:
// - In EIP-1559 transactions, the `GasPrice` field is set to nil/empty.
// - In legacy Homestead transactions, the `GasPrice` field is populated to specify the fee per unit of gas, while the `MaxFeePerGas` and `MaxPriorityFeePerGas` fields are set to nil/empty.
// Additionally, both the `ChainID` and the `Type` fields are set to 0 in legacy Homestead transactions to differentiate them from EIP-1559 transactions.
type EthTx struct {
ChainID EthUint64 `json:"chainId"`
Nonce EthUint64 `json:"nonce"`
@ -36,189 +78,129 @@ type EthTx struct {
Type EthUint64 `json:"type"`
Input EthBytes `json:"input"`
Gas EthUint64 `json:"gas"`
MaxFeePerGas EthBigInt `json:"maxFeePerGas"`
MaxPriorityFeePerGas EthBigInt `json:"maxPriorityFeePerGas"`
MaxFeePerGas *EthBigInt `json:"maxFeePerGas,omitempty"`
MaxPriorityFeePerGas *EthBigInt `json:"maxPriorityFeePerGas,omitempty"`
GasPrice *EthBigInt `json:"gasPrice,omitempty"`
AccessList []EthHash `json:"accessList"`
V EthBigInt `json:"v"`
R EthBigInt `json:"r"`
S EthBigInt `json:"s"`
}
type EthTxArgs struct {
ChainID int `json:"chainId"`
Nonce int `json:"nonce"`
To *EthAddress `json:"to"`
Value big.Int `json:"value"`
MaxFeePerGas big.Int `json:"maxFeePerGas"`
MaxPriorityFeePerGas big.Int `json:"maxPriorityFeePerGas"`
GasLimit int `json:"gasLimit"`
Input []byte `json:"input"`
V big.Int `json:"v"`
R big.Int `json:"r"`
S big.Int `json:"s"`
func (tx *EthTx) GasFeeCap() (EthBigInt, error) {
if tx.GasPrice == nil && tx.MaxFeePerGas == nil {
return EthBigInt{}, fmt.Errorf("gas fee cap is not set")
}
if tx.MaxFeePerGas != nil {
return *tx.MaxFeePerGas, nil
}
return *tx.GasPrice, nil
}
// EthTxFromSignedEthMessage does NOT populate:
// - BlockHash
// - BlockNumber
// - TransactionIndex
// - Hash
func EthTxFromSignedEthMessage(smsg *types.SignedMessage) (EthTx, error) {
// The from address is always an f410f address, never an ID or other address.
if !IsEthAddress(smsg.Message.From) {
return EthTx{}, xerrors.Errorf("sender must be an eth account, was %s", smsg.Message.From)
func (tx *EthTx) GasPremium() (EthBigInt, error) {
if tx.GasPrice == nil && tx.MaxPriorityFeePerGas == nil {
return EthBigInt{}, fmt.Errorf("gas premium is not set")
}
// Probably redundant, but we might as well check.
if tx.MaxPriorityFeePerGas != nil {
return *tx.MaxPriorityFeePerGas, nil
}
return *tx.GasPrice, nil
}
func EthTransactionFromSignedFilecoinMessage(smsg *types.SignedMessage) (EthTransaction, error) {
if smsg == nil {
return nil, errors.New("signed message is nil")
}
// Ensure the signature type is delegated.
if smsg.Signature.Type != typescrypto.SigTypeDelegated {
return EthTx{}, xerrors.Errorf("signature is not delegated type, is type: %d", smsg.Signature.Type)
return nil, fmt.Errorf("signature is not delegated type, is type: %d", smsg.Signature.Type)
}
txArgs, err := EthTxArgsFromUnsignedEthMessage(&smsg.Message)
// Convert Filecoin address to Ethereum address.
_, err := EthAddressFromFilecoinAddress(smsg.Message.From)
if err != nil {
return EthTx{}, xerrors.Errorf("failed to convert the unsigned message: %w", err)
return nil, fmt.Errorf("sender was not an eth account")
}
r, s, v, err := RecoverSignature(smsg.Signature)
// Extract Ethereum parameters and recipient from the message.
params, to, err := getEthParamsAndRecipient(&smsg.Message)
if err != nil {
return EthTx{}, xerrors.Errorf("failed to recover signature: %w", err)
return nil, fmt.Errorf("failed to parse input params and recipient: %w", err)
}
from, err := EthAddressFromFilecoinAddress(smsg.Message.From)
if err != nil {
// This should be impossible as we've already asserted that we have an EthAddress
// sender...
return EthTx{}, xerrors.Errorf("sender was not an eth account")
// Check for supported message version.
if smsg.Message.Version != 0 {
return nil, fmt.Errorf("unsupported msg version: %d", smsg.Message.Version)
}
return EthTx{
Nonce: EthUint64(txArgs.Nonce),
ChainID: EthUint64(txArgs.ChainID),
To: txArgs.To,
From: from,
Value: EthBigInt(txArgs.Value),
Type: Eip1559TxType,
Gas: EthUint64(txArgs.GasLimit),
MaxFeePerGas: EthBigInt(txArgs.MaxFeePerGas),
MaxPriorityFeePerGas: EthBigInt(txArgs.MaxPriorityFeePerGas),
AccessList: []EthHash{},
V: v,
R: r,
S: s,
Input: txArgs.Input,
}, nil
// Determine the type of transaction based on the signature length
switch len(smsg.Signature.Data) {
case EthEIP1559TxSignatureLen:
tx := Eth1559TxArgs{
ChainID: build.Eip155ChainId,
Nonce: int(smsg.Message.Nonce),
To: to,
Value: smsg.Message.Value,
Input: params,
MaxFeePerGas: smsg.Message.GasFeeCap,
MaxPriorityFeePerGas: smsg.Message.GasPremium,
GasLimit: int(smsg.Message.GasLimit),
}
if err := tx.InitialiseSignature(smsg.Signature); err != nil {
return nil, fmt.Errorf("failed to initialise signature: %w", err)
}
return &tx, nil
case EthLegacyHomesteadTxSignatureLen, EthLegacy155TxSignatureLen0, EthLegacy155TxSignatureLen1:
legacyTx := &EthLegacyHomesteadTxArgs{
Nonce: int(smsg.Message.Nonce),
To: to,
Value: smsg.Message.Value,
Input: params,
GasPrice: smsg.Message.GasFeeCap,
GasLimit: int(smsg.Message.GasLimit),
}
// Process based on the first byte of the signature
switch smsg.Signature.Data[0] {
case EthLegacyHomesteadTxSignaturePrefix:
if err := legacyTx.InitialiseSignature(smsg.Signature); err != nil {
return nil, fmt.Errorf("failed to initialise signature: %w", err)
}
return legacyTx, nil
case EthLegacy155TxSignaturePrefix:
tx := &EthLegacy155TxArgs{
legacyTx: legacyTx,
}
if err := tx.InitialiseSignature(smsg.Signature); err != nil {
return nil, fmt.Errorf("failed to initialise signature: %w", err)
}
return tx, nil
default:
return nil, fmt.Errorf("unsupported legacy transaction; first byte of signature is %d", smsg.Signature.Data[0])
}
default:
return nil, fmt.Errorf("unsupported signature length")
}
}
func EthTxArgsFromUnsignedEthMessage(msg *types.Message) (EthTxArgs, error) {
var (
to *EthAddress
params []byte
err error
)
if msg.Version != 0 {
return EthTxArgs{}, xerrors.Errorf("unsupported msg version: %d", msg.Version)
}
if len(msg.Params) > 0 {
paramsReader := bytes.NewReader(msg.Params)
params, err = cbg.ReadByteArray(paramsReader, uint64(len(msg.Params)))
if err != nil {
return EthTxArgs{}, xerrors.Errorf("failed to read params byte array: %w", err)
}
if paramsReader.Len() != 0 {
return EthTxArgs{}, xerrors.Errorf("extra data found in params")
}
if len(params) == 0 {
return EthTxArgs{}, xerrors.Errorf("non-empty params encode empty byte array")
}
}
if msg.To == builtintypes.EthereumAddressManagerActorAddr {
if msg.Method != builtintypes.MethodsEAM.CreateExternal {
return EthTxArgs{}, fmt.Errorf("unsupported EAM method")
}
} else if msg.Method == builtintypes.MethodsEVM.InvokeContract {
addr, err := EthAddressFromFilecoinAddress(msg.To)
if err != nil {
return EthTxArgs{}, err
}
to = &addr
} else {
return EthTxArgs{},
xerrors.Errorf("invalid methodnum %d: only allowed method is InvokeContract(%d)",
msg.Method, builtintypes.MethodsEVM.InvokeContract)
}
return EthTxArgs{
ChainID: build.Eip155ChainId,
Nonce: int(msg.Nonce),
To: to,
Value: msg.Value,
Input: params,
MaxFeePerGas: msg.GasFeeCap,
MaxPriorityFeePerGas: msg.GasPremium,
GasLimit: int(msg.GasLimit),
}, nil
}
func (tx *EthTxArgs) ToUnsignedMessage(from address.Address) (*types.Message, error) {
if tx.ChainID != build.Eip155ChainId {
return nil, xerrors.Errorf("unsupported chain id: %d", tx.ChainID)
}
var err error
var params []byte
if len(tx.Input) > 0 {
buf := new(bytes.Buffer)
if err = cbg.WriteByteArray(buf, tx.Input); err != nil {
return nil, xerrors.Errorf("failed to write input args: %w", err)
}
params = buf.Bytes()
}
var to address.Address
var method abi.MethodNum
// nil indicates the EAM, only CreateExternal is allowed
if tx.To == nil {
method = builtintypes.MethodsEAM.CreateExternal
to = builtintypes.EthereumAddressManagerActorAddr
} else {
method = builtintypes.MethodsEVM.InvokeContract
to, err = tx.To.ToFilecoinAddress()
if err != nil {
return nil, xerrors.Errorf("failed to convert To into filecoin addr: %w", err)
}
}
return &types.Message{
Version: 0,
To: to,
From: from,
Nonce: uint64(tx.Nonce),
Value: tx.Value,
GasLimit: int64(tx.GasLimit),
GasFeeCap: tx.MaxFeePerGas,
GasPremium: tx.MaxPriorityFeePerGas,
Method: method,
Params: params,
}, nil
}
func (tx *EthTxArgs) ToSignedMessage() (*types.SignedMessage, error) {
func ToSignedFilecoinMessage(tx EthTransaction) (*types.SignedMessage, error) {
from, err := tx.Sender()
if err != nil {
return nil, xerrors.Errorf("failed to calculate sender: %w", err)
return nil, fmt.Errorf("failed to calculate sender: %w", err)
}
unsignedMsg, err := tx.ToUnsignedMessage(from)
unsignedMsg, err := tx.ToUnsignedFilecoinMessage(from)
if err != nil {
return nil, xerrors.Errorf("failed to convert to unsigned msg: %w", err)
return nil, fmt.Errorf("failed to convert to unsigned msg: %w", err)
}
siggy, err := tx.Signature()
if err != nil {
return nil, xerrors.Errorf("failed to calculate signature: %w", err)
return nil, fmt.Errorf("failed to calculate signature: %w", err)
}
return &types.SignedMessage{
@ -227,349 +209,91 @@ func (tx *EthTxArgs) ToSignedMessage() (*types.SignedMessage, error) {
}, nil
}
func (tx *EthTxArgs) HashedOriginalRlpMsg() ([]byte, error) {
msg, err := tx.ToRlpUnsignedMsg()
if err != nil {
return nil, err
}
hasher := sha3.NewLegacyKeccak256()
hasher.Write(msg)
hash := hasher.Sum(nil)
return hash, nil
}
func (tx *EthTxArgs) ToRlpUnsignedMsg() ([]byte, error) {
packed, err := tx.packTxFields()
if err != nil {
return nil, err
}
encoded, err := EncodeRLP(packed)
if err != nil {
return nil, err
}
return append([]byte{0x02}, encoded...), nil
}
func (tx *EthTx) ToEthTxArgs() EthTxArgs {
return EthTxArgs{
ChainID: int(tx.ChainID),
Nonce: int(tx.Nonce),
To: tx.To,
Value: big.Int(tx.Value),
MaxFeePerGas: big.Int(tx.MaxFeePerGas),
MaxPriorityFeePerGas: big.Int(tx.MaxPriorityFeePerGas),
GasLimit: int(tx.Gas),
Input: tx.Input,
V: big.Int(tx.V),
R: big.Int(tx.R),
S: big.Int(tx.S),
}
}
func (tx *EthTx) TxHash() (EthHash, error) {
ethTxArgs := tx.ToEthTxArgs()
return (&ethTxArgs).TxHash()
}
func (tx *EthTxArgs) TxHash() (EthHash, error) {
rlp, err := tx.ToRlpSignedMsg()
if err != nil {
return EmptyEthHash, err
}
return EthHashFromTxBytes(rlp), nil
}
func (tx *EthTxArgs) ToRlpSignedMsg() ([]byte, error) {
packed1, err := tx.packTxFields()
if err != nil {
return nil, err
}
packed2, err := tx.packSigFields()
if err != nil {
return nil, err
}
encoded, err := EncodeRLP(append(packed1, packed2...))
if err != nil {
return nil, err
}
return append([]byte{0x02}, encoded...), nil
}
func (tx *EthTxArgs) packTxFields() ([]interface{}, error) {
chainId, err := formatInt(tx.ChainID)
if err != nil {
return nil, err
}
nonce, err := formatInt(tx.Nonce)
if err != nil {
return nil, err
}
maxPriorityFeePerGas, err := formatBigInt(tx.MaxPriorityFeePerGas)
if err != nil {
return nil, err
}
maxFeePerGas, err := formatBigInt(tx.MaxFeePerGas)
if err != nil {
return nil, err
}
gasLimit, err := formatInt(tx.GasLimit)
if err != nil {
return nil, err
}
value, err := formatBigInt(tx.Value)
if err != nil {
return nil, err
}
res := []interface{}{
chainId,
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
gasLimit,
formatEthAddr(tx.To),
value,
tx.Input,
[]interface{}{}, // access list
}
return res, nil
}
func (tx *EthTxArgs) packSigFields() ([]interface{}, error) {
r, err := formatBigInt(tx.R)
if err != nil {
return nil, err
}
s, err := formatBigInt(tx.S)
if err != nil {
return nil, err
}
v, err := formatBigInt(tx.V)
if err != nil {
return nil, err
}
res := []interface{}{v, r, s}
return res, nil
}
func (tx *EthTxArgs) Signature() (*typescrypto.Signature, error) {
r := tx.R.Int.Bytes()
s := tx.S.Int.Bytes()
v := tx.V.Int.Bytes()
sig := append([]byte{}, padLeadingZeros(r, 32)...)
sig = append(sig, padLeadingZeros(s, 32)...)
if len(v) == 0 {
sig = append(sig, 0)
} else {
sig = append(sig, v[0])
}
if len(sig) != 65 {
return nil, fmt.Errorf("signature is not 65 bytes")
}
return &typescrypto.Signature{
Type: typescrypto.SigTypeDelegated, Data: sig,
}, nil
}
func (tx *EthTxArgs) Sender() (address.Address, error) {
msg, err := tx.ToRlpUnsignedMsg()
if err != nil {
return address.Undef, err
}
hasher := sha3.NewLegacyKeccak256()
hasher.Write(msg)
hash := hasher.Sum(nil)
sig, err := tx.Signature()
if err != nil {
return address.Undef, err
}
pubk, err := gocrypto.EcRecover(hash, sig.Data)
if err != nil {
return address.Undef, err
}
ethAddr, err := EthAddressFromPubKey(pubk)
if err != nil {
return address.Undef, err
}
ea, err := CastEthAddress(ethAddr)
if err != nil {
return address.Undef, err
}
return ea.ToFilecoinAddress()
}
func RecoverSignature(sig typescrypto.Signature) (r, s, v EthBigInt, err error) {
if sig.Type != typescrypto.SigTypeDelegated {
return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("RecoverSignature only supports Delegated signature")
}
if len(sig.Data) != 65 {
return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("signature should be 65 bytes long, but got %d bytes", len(sig.Data))
}
r_, err := parseBigInt(sig.Data[0:32])
if err != nil {
return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse r into EthBigInt")
}
s_, err := parseBigInt(sig.Data[32:64])
if err != nil {
return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse s into EthBigInt")
}
v_, err := parseBigInt([]byte{sig.Data[64]})
if err != nil {
return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse v into EthBigInt")
}
return EthBigInt(r_), EthBigInt(s_), EthBigInt(v_), nil
}
func parseEip1559Tx(data []byte) (*EthTxArgs, error) {
if data[0] != 2 {
return nil, fmt.Errorf("not an EIP-1559 transaction: first byte is not 2")
}
d, err := DecodeRLP(data[1:])
if err != nil {
return nil, err
}
decoded, ok := d.([]interface{})
if !ok {
return nil, fmt.Errorf("not an EIP-1559 transaction: decoded data is not a list")
}
if len(decoded) != 12 {
return nil, fmt.Errorf("not an EIP-1559 transaction: should have 12 elements in the rlp list")
}
chainId, err := parseInt(decoded[0])
if err != nil {
return nil, err
}
nonce, err := parseInt(decoded[1])
if err != nil {
return nil, err
}
maxPriorityFeePerGas, err := parseBigInt(decoded[2])
if err != nil {
return nil, err
}
maxFeePerGas, err := parseBigInt(decoded[3])
if err != nil {
return nil, err
}
gasLimit, err := parseInt(decoded[4])
if err != nil {
return nil, err
}
to, err := parseEthAddr(decoded[5])
if err != nil {
return nil, err
}
value, err := parseBigInt(decoded[6])
if err != nil {
return nil, err
}
input, err := parseBytes(decoded[7])
if err != nil {
return nil, err
}
accessList, ok := decoded[8].([]interface{})
if !ok || (ok && len(accessList) != 0) {
return nil, fmt.Errorf("access list should be an empty list")
}
r, err := parseBigInt(decoded[10])
if err != nil {
return nil, err
}
s, err := parseBigInt(decoded[11])
if err != nil {
return nil, err
}
v, err := parseBigInt(decoded[9])
if err != nil {
return nil, err
}
// EIP-1559 and EIP-2930 transactions only support 0 or 1 for v
// Legacy and EIP-155 transactions support other values
// https://github.com/ethers-io/ethers.js/blob/56fabe987bb8c1e4891fdf1e5d3fe8a4c0471751/packages/transactions/src.ts/index.ts#L333
if !v.Equals(big.NewInt(0)) && !v.Equals(big.NewInt(1)) {
return nil, fmt.Errorf("EIP-1559 transactions only support 0 or 1 for v")
}
args := EthTxArgs{
ChainID: chainId,
Nonce: nonce,
To: to,
MaxPriorityFeePerGas: maxPriorityFeePerGas,
MaxFeePerGas: maxFeePerGas,
GasLimit: gasLimit,
Value: value,
Input: input,
V: v,
R: r,
S: s,
}
return &args, nil
}
func ParseEthTxArgs(data []byte) (*EthTxArgs, error) {
func ParseEthTransaction(data []byte) (EthTransaction, error) {
if len(data) == 0 {
return nil, fmt.Errorf("empty data")
}
if data[0] > 0x7f {
// legacy transaction
return nil, fmt.Errorf("legacy transaction is not supported")
}
if data[0] == 1 {
switch data[0] {
case 1:
// EIP-2930
return nil, fmt.Errorf("EIP-2930 transaction is not supported")
}
if data[0] == Eip1559TxType {
case EIP1559TxType:
// EIP-1559
return parseEip1559Tx(data)
default:
if data[0] > 0x7f {
tx, err := parseLegacyTx(data)
if err != nil {
return nil, fmt.Errorf("failed to parse legacy transaction: %w", err)
}
return tx, nil
}
}
return nil, fmt.Errorf("unsupported transaction type")
}
type methodInfo struct {
to address.Address
method abi.MethodNum
params []byte
}
func getFilecoinMethodInfo(recipient *EthAddress, input []byte) (*methodInfo, error) {
var params []byte
if len(input) > 0 {
buf := new(bytes.Buffer)
if err := cbg.WriteByteArray(buf, input); err != nil {
return nil, fmt.Errorf("failed to write input args: %w", err)
}
params = buf.Bytes()
}
var to address.Address
var method abi.MethodNum
if recipient == nil {
// If recipient is nil, use Ethereum Address Manager Actor and CreateExternal method
method = builtintypes.MethodsEAM.CreateExternal
to = builtintypes.EthereumAddressManagerActorAddr
} else {
// Otherwise, use InvokeContract method and convert EthAddress to Filecoin address
method = builtintypes.MethodsEVM.InvokeContract
var err error
to, err = recipient.ToFilecoinAddress()
if err != nil {
return nil, fmt.Errorf("failed to convert EthAddress to Filecoin address: %w", err)
}
}
return &methodInfo{
to: to,
method: method,
params: params,
}, nil
}
func packSigFields(v, r, s big.Int) ([]interface{}, error) {
rr, err := formatBigInt(r)
if err != nil {
return nil, err
}
ss, err := formatBigInt(s)
if err != nil {
return nil, err
}
vv, err := formatBigInt(v)
if err != nil {
return nil, err
}
res := []interface{}{vv, rr, ss}
return res, nil
}
func padLeadingZeros(data []byte, length int) []byte {
if len(data) >= length {
return data
@ -667,3 +391,204 @@ func parseEthAddr(v interface{}) (*EthAddress, error) {
}
return &addr, nil
}
func getEthParamsAndRecipient(msg *types.Message) (params []byte, to *EthAddress, err error) {
if len(msg.Params) > 0 {
paramsReader := bytes.NewReader(msg.Params)
var err error
params, err = cbg.ReadByteArray(paramsReader, uint64(len(msg.Params)))
if err != nil {
return nil, nil, fmt.Errorf("failed to read params byte array: %w", err)
}
if paramsReader.Len() != 0 {
return nil, nil, fmt.Errorf("extra data found in params")
}
if len(params) == 0 {
return nil, nil, fmt.Errorf("non-empty params encode empty byte array")
}
}
if msg.To == builtintypes.EthereumAddressManagerActorAddr {
if msg.Method != builtintypes.MethodsEAM.CreateExternal {
return nil, nil, fmt.Errorf("unsupported EAM method")
}
} else if msg.Method == builtintypes.MethodsEVM.InvokeContract {
addr, err := EthAddressFromFilecoinAddress(msg.To)
if err != nil {
return nil, nil, err
}
to = &addr
} else {
return nil, nil,
fmt.Errorf("invalid methodnum %d: only allowed method is InvokeContract(%d) or CreateExternal(%d)",
msg.Method, builtintypes.MethodsEVM.InvokeContract, builtintypes.MethodsEAM.CreateExternal)
}
return params, to, nil
}
func parseLegacyTx(data []byte) (EthTransaction, error) {
if data[0] <= 0x7f {
return nil, fmt.Errorf("not a legacy eth transaction")
}
d, err := DecodeRLP(data)
if err != nil {
return nil, err
}
decoded, ok := d.([]interface{})
if !ok {
return nil, fmt.Errorf("not a Legacy transaction: decoded data is not a list")
}
if len(decoded) != 9 {
return nil, fmt.Errorf("not a Legacy transaction: should have 9 elements in the rlp list")
}
nonce, err := parseInt(decoded[0])
if err != nil {
return nil, err
}
gasPrice, err := parseBigInt(decoded[1])
if err != nil {
return nil, err
}
gasLimit, err := parseInt(decoded[2])
if err != nil {
return nil, err
}
to, err := parseEthAddr(decoded[3])
if err != nil {
return nil, err
}
value, err := parseBigInt(decoded[4])
if err != nil {
return nil, err
}
input, ok := decoded[5].([]byte)
if !ok {
return nil, fmt.Errorf("input is not a byte slice")
}
v, err := parseBigInt(decoded[6])
if err != nil {
return nil, err
}
r, err := parseBigInt(decoded[7])
if err != nil {
return nil, err
}
s, err := parseBigInt(decoded[8])
if err != nil {
return nil, err
}
tx := &EthLegacyHomesteadTxArgs{
Nonce: nonce,
GasPrice: gasPrice,
GasLimit: gasLimit,
To: to,
Value: value,
Input: input,
V: v,
R: r,
S: s,
}
chainId := deriveEIP155ChainId(v)
if chainId.Equals(big.NewInt(0)) {
// This is a legacy Homestead transaction
if !v.Equals(big.NewInt(27)) && !v.Equals(big.NewInt(28)) {
return nil, fmt.Errorf("legacy homestead transactions only support 27 or 28 for v, got %d", v.Uint64())
}
return tx, nil
}
// This is a EIP-155 transaction -> ensure chainID protection
if err := validateEIP155ChainId(v); err != nil {
return nil, fmt.Errorf("failed to validate EIP155 chain id: %w", err)
}
return &EthLegacy155TxArgs{
legacyTx: tx,
}, nil
}
type RlpPackable interface {
packTxFields() ([]interface{}, error)
}
func toRlpUnsignedMsg(tx RlpPackable) ([]byte, error) {
packedFields, err := tx.packTxFields()
if err != nil {
return nil, err
}
encoded, err := EncodeRLP(packedFields)
if err != nil {
return nil, err
}
return encoded, nil
}
func toRlpSignedMsg(tx RlpPackable, V, R, S big.Int) ([]byte, error) {
packed1, err := tx.packTxFields()
if err != nil {
return nil, err
}
packed2, err := packSigFields(V, R, S)
if err != nil {
return nil, err
}
encoded, err := EncodeRLP(append(packed1, packed2...))
if err != nil {
return nil, fmt.Errorf("failed to encode rlp signed msg: %w", err)
}
return encoded, nil
}
func sender(tx EthTransaction) (address.Address, error) {
msg, err := tx.ToRlpUnsignedMsg()
if err != nil {
return address.Undef, fmt.Errorf("failed to get rlp unsigned msg: %w", err)
}
hasher := sha3.NewLegacyKeccak256()
hasher.Write(msg)
hash := hasher.Sum(nil)
sig, err := tx.Signature()
if err != nil {
return address.Undef, fmt.Errorf("failed to get signature: %w", err)
}
sigData, err := tx.ToVerifiableSignature(sig.Data)
if err != nil {
return address.Undef, fmt.Errorf("failed to get verifiable signature: %w", err)
}
pubk, err := gocrypto.EcRecover(hash, sigData)
if err != nil {
return address.Undef, fmt.Errorf("failed to recover pubkey: %w", err)
}
ethAddr, err := EthAddressFromPubKey(pubk)
if err != nil {
return address.Undef, fmt.Errorf("failed to get eth address from pubkey: %w", err)
}
ea, err := CastEthAddress(ethAddr)
if err != nil {
return address.Undef, fmt.Errorf("failed to cast eth address: %w", err)
}
return ea.ToFilecoinAddress()
}

File diff suppressed because one or more lines are too long

View File

@ -192,7 +192,7 @@ func TestDecodeError(t *testing.T) {
func TestDecode1(t *testing.T) {
b := mustDecodeHex("0x02f8758401df5e7680832c8411832c8411830767f89452963ef50e27e06d72d59fcb4f3c2a687be3cfef880de0b6b3a764000080c080a094b11866f453ad85a980e0e8a2fc98cbaeb4409618c7734a7e12ae2f66fd405da042dbfb1b37af102023830ceeee0e703ffba0b8b3afeb8fe59f405eca9ed61072")
decoded, err := ParseEthTxArgs(b)
decoded, err := parseEip1559Tx(b)
require.NoError(t, err)
sender, err := decoded.Sender()

View File

@ -88,16 +88,16 @@ var computeEthHashCmd = &cli.Command{
switch msg := msg.(type) {
case *types.SignedMessage:
tx, err := ethtypes.EthTxFromSignedEthMessage(msg)
tx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(msg)
if err != nil {
return fmt.Errorf("failed to convert from signed message: %w", err)
}
tx.Hash, err = tx.TxHash()
hash, err := tx.TxHash()
if err != nil {
return fmt.Errorf("failed to call TxHash: %w", err)
}
fmt.Println(tx.Hash)
fmt.Println(hash)
default:
return fmt.Errorf("not a signed message")
}

View File

@ -637,17 +637,17 @@ var backfillTxHashCmd = &cli.Command{
continue
}
tx, err := ethtypes.EthTxFromSignedEthMessage(smsg)
tx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(smsg)
if err != nil {
return fmt.Errorf("failed to convert from signed message: %w at epoch: %d", err, epoch)
}
tx.Hash, err = tx.TxHash()
hash, err := tx.TxHash()
if err != nil {
return fmt.Errorf("failed to calculate hash for ethTx: %w at epoch: %d", err, epoch)
}
res, err := insertStmt.Exec(tx.Hash.String(), smsg.Cid().String())
res, err := insertStmt.Exec(hash.String(), smsg.Cid().String())
if err != nil {
return fmt.Errorf("error inserting tx mapping to db: %s at epoch: %d", err, epoch)
}
@ -658,7 +658,7 @@ var backfillTxHashCmd = &cli.Command{
}
if rowsAffected > 0 {
log.Debugf("Inserted txhash %s, cid: %s at epoch: %d", tx.Hash.String(), smsg.Cid().String(), epoch)
log.Debugf("Inserted txhash %s, cid: %s at epoch: %d", hash.String(), smsg.Cid().String(), epoch)
}
totalRowsAffected += rowsAffected

View File

@ -2728,6 +2728,7 @@ Response:
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"gasPrice": "0x0",
"accessList": [
"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"
],
@ -2767,6 +2768,7 @@ Response:
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"gasPrice": "0x0",
"accessList": [
"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"
],
@ -2805,6 +2807,7 @@ Response:
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"gasPrice": "0x0",
"accessList": [
"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"
],
@ -2844,6 +2847,7 @@ Response:
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"gasPrice": "0x0",
"accessList": [
"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"
],

View File

@ -74,7 +74,7 @@ func TestEthAccountAbstraction(t *testing.T) {
msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK)
require.NoError(t, err)
txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(msgFromPlaceholder)
txArgs, err := ethtypes.Eth1559TxArgsFromUnsignedFilecoinMessage(msgFromPlaceholder)
require.NoError(t, err)
digest, err := txArgs.ToRlpUnsignedMsg()
@ -111,7 +111,7 @@ func TestEthAccountAbstraction(t *testing.T) {
msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK)
require.NoError(t, err)
txArgs, err = ethtypes.EthTxArgsFromUnsignedEthMessage(msgFromPlaceholder)
txArgs, err = ethtypes.Eth1559TxArgsFromUnsignedFilecoinMessage(msgFromPlaceholder)
require.NoError(t, err)
digest, err = txArgs.ToRlpUnsignedMsg()
@ -185,7 +185,7 @@ func TestEthAccountAbstractionFailure(t *testing.T) {
require.NoError(t, err)
msgFromPlaceholder.Value = abi.TokenAmount(types.MustParseFIL("1000"))
txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(msgFromPlaceholder)
txArgs, err := ethtypes.Eth1559TxArgsFromUnsignedFilecoinMessage(msgFromPlaceholder)
require.NoError(t, err)
digest, err := txArgs.ToRlpUnsignedMsg()
@ -224,7 +224,7 @@ func TestEthAccountAbstractionFailure(t *testing.T) {
msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK)
require.NoError(t, err)
txArgs, err = ethtypes.EthTxArgsFromUnsignedEthMessage(msgFromPlaceholder)
txArgs, err = ethtypes.Eth1559TxArgsFromUnsignedFilecoinMessage(msgFromPlaceholder)
require.NoError(t, err)
digest, err = txArgs.ToRlpUnsignedMsg()
@ -285,7 +285,7 @@ func TestEthAccountAbstractionFailsFromEvmActor(t *testing.T) {
maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx)
require.NoError(t, err)
tx := ethtypes.EthTxArgs{
tx := ethtypes.Eth1559TxArgs{
ChainID: build.Eip155ChainId,
Value: big.Zero(),
Nonce: 0,
@ -302,7 +302,7 @@ func TestEthAccountAbstractionFailsFromEvmActor(t *testing.T) {
client.EVM().SubmitTransaction(ctx, &tx)
smsg, err := tx.ToSignedMessage()
smsg, err := ethtypes.ToSignedFilecoinMessage(&tx)
require.NoError(t, err)
ml, err := client.StateWaitMsg(ctx, smsg.Cid(), 1, api.LookbackNoLimit, true)

View File

@ -463,7 +463,7 @@ func createRawSignedEthTx(ctx context.Context, t *testing.T, client *kit.TestFul
maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx)
require.NoError(t, err)
tx := ethtypes.EthTxArgs{
tx := ethtypes.Eth1559TxArgs{
ChainID: build.Eip155ChainId,
Value: big.NewInt(100),
Nonce: 0,

View File

@ -73,7 +73,7 @@ func TestDeployment(t *testing.T) {
require.NoError(t, err)
// now deploy a contract from the placeholder, and validate it went well
tx := ethtypes.EthTxArgs{
tx := ethtypes.Eth1559TxArgs{
ChainID: build.Eip155ChainId,
Value: big.Zero(),
Nonce: 0,

View File

@ -61,7 +61,7 @@ func TestTransactionHashLookup(t *testing.T) {
require.NoError(t, err)
// now deploy a contract from the embryo, and validate it went well
tx := ethtypes.EthTxArgs{
tx := ethtypes.Eth1559TxArgs{
ChainID: build.Eip155ChainId,
Value: big.Zero(),
Nonce: 0,
@ -367,7 +367,7 @@ func TestEthGetMessageCidByTransactionHashEthTx(t *testing.T) {
require.NoError(t, err)
// now deploy a contract from the embryo, and validate it went well
tx := ethtypes.EthTxArgs{
tx := ethtypes.Eth1559TxArgs{
ChainID: build.Eip155ChainId,
Value: big.Zero(),
Nonce: 0,
@ -385,7 +385,7 @@ func TestEthGetMessageCidByTransactionHashEthTx(t *testing.T) {
sender, err := tx.Sender()
require.NoError(t, err)
unsignedMessage, err := tx.ToUnsignedMessage(sender)
unsignedMessage, err := tx.ToUnsignedFilecoinMessage(sender)
require.NoError(t, err)
rawTxHash, err := tx.TxHash()

View File

@ -0,0 +1,400 @@
package itests
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/network"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/consensus/filcns"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
"github.com/filecoin-project/lotus/itests/kit"
)
func TestLegacyValueTransferValidSignature(t *testing.T) {
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()
// create a new Ethereum account
key, ethAddr, deployer := client.EVM().NewAccount()
_, ethAddr2, _ := client.EVM().NewAccount()
kit.SendFunds(ctx, t, client, deployer, types.FromFil(1000))
gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{
From: &ethAddr,
To: &ethAddr2,
Value: ethtypes.EthBigInt(big.NewInt(100)),
}})
require.NoError(t, err)
gaslimit, err := client.EthEstimateGas(ctx, gasParams)
require.NoError(t, err)
fmt.Println("gas limit is", gaslimit)
tx := ethtypes.EthLegacyHomesteadTxArgs{
Value: big.NewInt(100),
Nonce: 0,
To: &ethAddr2,
GasPrice: types.NanoFil,
GasLimit: int(gaslimit),
V: big.Zero(),
R: big.Zero(),
S: big.Zero(),
}
client.EVM().SignLegacyHomesteadTransaction(&tx, key.PrivateKey)
// Mangle signature
tx.V.Int.Xor(tx.V.Int, big.NewInt(1).Int)
signed, err := tx.ToRlpSignedMsg()
require.NoError(t, err)
// Submit transaction with bad signature
_, err = client.EVM().EthSendRawTransaction(ctx, signed)
require.Error(t, err)
// Submit transaction with valid signature
client.EVM().SignLegacyHomesteadTransaction(&tx, key.PrivateKey)
hash := client.EVM().SubmitTransaction(ctx, &tx)
receipt, err := client.EVM().WaitTransaction(ctx, hash)
require.NoError(t, err)
require.NotNil(t, receipt)
require.EqualValues(t, ethAddr, receipt.From)
require.EqualValues(t, ethAddr2, *receipt.To)
require.EqualValues(t, hash, receipt.TransactionHash)
// Success.
require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status)
// Validate that we sent the expected transaction.
ethTx, err := client.EthGetTransactionByHash(ctx, &hash)
require.NoError(t, err)
require.Nil(t, ethTx.MaxPriorityFeePerGas)
require.Nil(t, ethTx.MaxFeePerGas)
require.EqualValues(t, ethAddr, ethTx.From)
require.EqualValues(t, ethAddr2, *ethTx.To)
require.EqualValues(t, tx.Nonce, ethTx.Nonce)
require.EqualValues(t, hash, ethTx.Hash)
require.EqualValues(t, tx.Value, ethTx.Value)
require.EqualValues(t, 0, ethTx.Type)
require.EqualValues(t, 0, ethTx.ChainID)
require.EqualValues(t, ethtypes.EthBytes{}, ethTx.Input)
require.EqualValues(t, tx.GasLimit, ethTx.Gas)
require.EqualValues(t, tx.GasPrice, *ethTx.GasPrice)
require.EqualValues(t, tx.R, ethTx.R)
require.EqualValues(t, tx.S, ethTx.S)
require.EqualValues(t, tx.V, ethTx.V)
}
func TestLegacyEIP155ValueTransferValidSignatureFailsNV22(t *testing.T) {
blockTime := 100 * time.Millisecond
nv23Height := 10
// We will move to NV23 at epoch 10
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.UpgradeSchedule(stmgr.Upgrade{
Network: network.Version22,
Height: -1,
}, stmgr.Upgrade{
Network: network.Version23,
Height: abi.ChainEpoch(nv23Height),
Migration: filcns.UpgradeActorsV13,
}))
ens.InterconnectAll().BeginMining(blockTime)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
// create a new Ethereum account
key, ethAddr, deployer := client.EVM().NewAccount()
_, ethAddr2, _ := client.EVM().NewAccount()
kit.SendFunds(ctx, t, client, deployer, types.FromFil(1000))
gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{
From: &ethAddr,
To: &ethAddr2,
Value: ethtypes.EthBigInt(big.NewInt(100)),
}})
require.NoError(t, err)
gaslimit, err := client.EthEstimateGas(ctx, gasParams)
require.NoError(t, err)
legacyTx := &ethtypes.EthLegacyHomesteadTxArgs{
Value: big.NewInt(100),
Nonce: 0,
To: &ethAddr2,
GasPrice: types.NanoFil,
GasLimit: int(gaslimit),
V: big.Zero(),
R: big.Zero(),
S: big.Zero(),
}
tx := ethtypes.NewEthLegacy155TxArgs(legacyTx)
// TX will fail as we're still at NV22
client.EVM().SignLegacyEIP155Transaction(tx, key.PrivateKey, big.NewInt(build.Eip155ChainId))
signed, err := tx.ToRawTxBytesSigned()
require.NoError(t, err)
_, err = client.EVM().EthSendRawTransaction(ctx, signed)
require.Error(t, err)
require.Contains(t, err.Error(), "network version should be atleast NV23 for sending legacy ETH transactions")
}
func TestLegacyEIP155ValueTransferValidSignature(t *testing.T) {
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()
// create a new Ethereum account
key, ethAddr, deployer := client.EVM().NewAccount()
_, ethAddr2, _ := client.EVM().NewAccount()
kit.SendFunds(ctx, t, client, deployer, types.FromFil(1000))
gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{
From: &ethAddr,
To: &ethAddr2,
Value: ethtypes.EthBigInt(big.NewInt(100)),
}})
require.NoError(t, err)
gaslimit, err := client.EthEstimateGas(ctx, gasParams)
require.NoError(t, err)
legacyTx := &ethtypes.EthLegacyHomesteadTxArgs{
Value: big.NewInt(100),
Nonce: 0,
To: &ethAddr2,
GasPrice: types.NanoFil,
GasLimit: int(gaslimit),
V: big.Zero(),
R: big.Zero(),
S: big.Zero(),
}
tx := ethtypes.NewEthLegacy155TxArgs(legacyTx)
client.EVM().SignLegacyEIP155Transaction(tx, key.PrivateKey, big.NewInt(build.Eip155ChainId))
// Mangle signature
innerTx := tx.GetLegacyTx()
innerTx.V.Int.Xor(innerTx.V.Int, big.NewInt(1).Int)
signed, err := tx.ToRawTxBytesSigned()
require.NoError(t, err)
// Submit transaction with bad signature
_, err = client.EVM().EthSendRawTransaction(ctx, signed)
require.Error(t, err)
// Submit transaction with valid signature but incorrect chain ID
client.EVM().SignLegacyEIP155Transaction(tx, key.PrivateKey, big.NewInt(build.Eip155ChainId))
signed, err = tx.ToRawTxBytesSigned()
require.NoError(t, err)
hash, err := client.EVM().EthSendRawTransaction(ctx, signed)
require.NoError(t, err)
receipt, err := client.EVM().WaitTransaction(ctx, hash)
require.NoError(t, err)
require.NotNil(t, receipt)
require.EqualValues(t, ethAddr, receipt.From)
require.EqualValues(t, ethAddr2, *receipt.To)
require.EqualValues(t, hash, receipt.TransactionHash)
// Success.
require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status)
// Validate that we sent the expected transaction.
ethTx, err := client.EthGetTransactionByHash(ctx, &hash)
require.NoError(t, err)
require.Nil(t, ethTx.MaxPriorityFeePerGas)
require.Nil(t, ethTx.MaxFeePerGas)
innerTx = tx.GetLegacyTx()
require.EqualValues(t, ethAddr, ethTx.From)
require.EqualValues(t, ethAddr2, *ethTx.To)
require.EqualValues(t, innerTx.Nonce, ethTx.Nonce)
require.EqualValues(t, hash, ethTx.Hash)
require.EqualValues(t, innerTx.Value, ethTx.Value)
require.EqualValues(t, 0, ethTx.Type)
require.EqualValues(t, build.Eip155ChainId, ethTx.ChainID)
require.EqualValues(t, ethtypes.EthBytes{}, ethTx.Input)
require.EqualValues(t, innerTx.GasLimit, ethTx.Gas)
require.EqualValues(t, innerTx.GasPrice, *ethTx.GasPrice)
require.EqualValues(t, innerTx.R, ethTx.R)
require.EqualValues(t, innerTx.S, ethTx.S)
require.EqualValues(t, innerTx.V, ethTx.V)
}
func TestLegacyContractInvocation(t *testing.T) {
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()
// create a new Ethereum account
key, ethAddr, deployer := client.EVM().NewAccount()
// send some funds to the f410 address
kit.SendFunds(ctx, t, client, deployer, types.FromFil(10))
// DEPLOY CONTRACT
tx, err := deployLegacyContractTx(ctx, t, client, ethAddr)
require.NoError(t, err)
client.EVM().SignLegacyHomesteadTransaction(tx, key.PrivateKey)
// Mangle signature
tx.V.Int.Xor(tx.V.Int, big.NewInt(1).Int)
signed, err := tx.ToRlpSignedMsg()
require.NoError(t, err)
// Submit transaction with bad signature
_, err = client.EVM().EthSendRawTransaction(ctx, signed)
require.Error(t, err)
// Submit transaction with valid signature
client.EVM().SignLegacyHomesteadTransaction(tx, key.PrivateKey)
hash := client.EVM().SubmitTransaction(ctx, tx)
receipt, err := client.EVM().WaitTransaction(ctx, hash)
require.NoError(t, err)
require.NotNil(t, receipt)
require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status)
// Get contract address.
contractAddr := client.EVM().ComputeContractAddress(ethAddr, 0)
// INVOKE CONTRACT
// Params
// entry point for getBalance - f8b2cb4f
// address - ff00000000000000000000000000000000000064
params, err := hex.DecodeString("f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064")
require.NoError(t, err)
gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{
From: &ethAddr,
To: &contractAddr,
Data: params,
}})
require.NoError(t, err)
gaslimit, err := client.EthEstimateGas(ctx, gasParams)
require.NoError(t, err)
maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx)
require.NoError(t, err)
invokeTx := ethtypes.EthLegacyHomesteadTxArgs{
To: &contractAddr,
Value: big.Zero(),
Nonce: 1,
GasPrice: big.Int(maxPriorityFeePerGas),
GasLimit: int(gaslimit),
Input: params,
V: big.Zero(),
R: big.Zero(),
S: big.Zero(),
}
client.EVM().SignLegacyHomesteadTransaction(&invokeTx, key.PrivateKey)
// Mangle signature
invokeTx.V.Int.Xor(invokeTx.V.Int, big.NewInt(1).Int)
signed, err = invokeTx.ToRlpSignedMsg()
require.NoError(t, err)
// Submit transaction with bad signature
_, err = client.EVM().EthSendRawTransaction(ctx, signed)
require.Error(t, err)
// Submit transaction with valid signature
client.EVM().SignLegacyHomesteadTransaction(&invokeTx, key.PrivateKey)
hash = client.EVM().SubmitTransaction(ctx, &invokeTx)
receipt, err = client.EVM().WaitTransaction(ctx, hash)
require.NoError(t, err)
require.NotNil(t, receipt)
// Success.
require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status)
// Validate that we correctly computed the gas outputs.
mCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash)
require.NoError(t, err)
require.NotNil(t, mCid)
invokResult, err := client.StateReplay(ctx, types.EmptyTSK, *mCid)
require.NoError(t, err)
require.EqualValues(t, invokResult.GasCost.GasUsed, big.NewInt(int64(receipt.GasUsed)))
effectiveGasPrice := big.Div(invokResult.GasCost.TotalCost, invokResult.GasCost.GasUsed)
require.EqualValues(t, effectiveGasPrice, big.Int(receipt.EffectiveGasPrice))
}
func deployLegacyContractTx(ctx context.Context, t *testing.T, client *kit.TestFullNode, ethAddr ethtypes.EthAddress) (*ethtypes.EthLegacyHomesteadTxArgs, error) {
// install contract
contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex")
require.NoError(t, err)
contract, err := hex.DecodeString(string(contractHex))
require.NoError(t, err)
gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{
From: &ethAddr,
Data: contract,
}})
if err != nil {
return nil, err
}
gaslimit, err := client.EthEstimateGas(ctx, gasParams)
if err != nil {
return nil, err
}
maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx)
if err != nil {
return nil, err
}
// now deploy a contract from the embryo, and validate it went well
return &ethtypes.EthLegacyHomesteadTxArgs{
Value: big.Zero(),
Nonce: 0,
GasPrice: big.Int(maxPriorityFeePerGas),
GasLimit: int(gaslimit),
Input: contract,
V: big.Zero(),
R: big.Zero(),
S: big.Zero(),
}, nil
}

View File

@ -64,7 +64,7 @@ func TestValueTransferValidSignature(t *testing.T) {
maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx)
require.NoError(t, err)
tx := ethtypes.EthTxArgs{
tx := ethtypes.Eth1559TxArgs{
ChainID: build.Eip155ChainId,
Value: big.NewInt(100),
Nonce: 0,
@ -113,30 +113,13 @@ func TestValueTransferValidSignature(t *testing.T) {
require.EqualValues(t, 2, ethTx.Type)
require.EqualValues(t, ethtypes.EthBytes{}, ethTx.Input)
require.EqualValues(t, tx.GasLimit, ethTx.Gas)
require.EqualValues(t, tx.MaxFeePerGas, ethTx.MaxFeePerGas)
require.EqualValues(t, tx.MaxPriorityFeePerGas, ethTx.MaxPriorityFeePerGas)
require.EqualValues(t, tx.MaxFeePerGas, *ethTx.MaxFeePerGas)
require.EqualValues(t, tx.MaxPriorityFeePerGas, *ethTx.MaxPriorityFeePerGas)
require.EqualValues(t, tx.V, ethTx.V)
require.EqualValues(t, tx.R, ethTx.R)
require.EqualValues(t, tx.S, ethTx.S)
}
func TestLegacyTransaction(t *testing.T) {
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()
// This is a legacy style transaction obtained from etherscan
// Tx details: https://etherscan.io/getRawTx?tx=0x0763262208d89efeeb50c8bb05b50c537903fe9d7bdef3b223fd1f5f69f69b32
txBytes, err := hex.DecodeString("f86f830131cf8504a817c800825208942cf1e5a8250ded8835694ebeb90cfa0237fcb9b1882ec4a5251d1100008026a0f5f8d2244d619e211eeb634acd1bea0762b7b4c97bba9f01287c82bfab73f911a015be7982898aa7cc6c6f27ff33e999e4119d6cd51330353474b98067ff56d930")
require.NoError(t, err)
_, err = client.EVM().EthSendRawTransaction(ctx, txBytes)
require.ErrorContains(t, err, "legacy transaction is not supported")
}
func TestContractDeploymentValidSignature(t *testing.T) {
blockTime := 100 * time.Millisecond
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())
@ -255,7 +238,7 @@ func TestContractInvocation(t *testing.T) {
maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx)
require.NoError(t, err)
invokeTx := ethtypes.EthTxArgs{
invokeTx := ethtypes.Eth1559TxArgs{
ChainID: build.Eip155ChainId,
To: &contractAddr,
Value: big.Zero(),
@ -363,7 +346,7 @@ func TestGetBlockByNumber(t *testing.T) {
require.Equal(t, types.FromFil(10).Int, bal.Int)
}
func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr ethtypes.EthAddress, contract []byte) (*ethtypes.EthTxArgs, error) {
func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr ethtypes.EthAddress, contract []byte) (*ethtypes.Eth1559TxArgs, error) {
gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{
From: &ethAddr,
Data: contract,
@ -383,7 +366,7 @@ func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr eth
}
// now deploy a contract from the embryo, and validate it went well
return &ethtypes.EthTxArgs{
return &ethtypes.Eth1559TxArgs{
ChainID: build.Eip155ChainId,
Value: big.Zero(),
Nonce: 0,

View File

@ -678,7 +678,7 @@ func TestFEVMRecursiveActorCallEstimate(t *testing.T) {
nonce, err := client.MpoolGetNonce(ctx, ethFilAddr)
require.NoError(t, err)
tx := &ethtypes.EthTxArgs{
tx := &ethtypes.Eth1559TxArgs{
ChainID: build.Eip155ChainId,
To: &contractAddr,
Value: big.Zero(),
@ -695,7 +695,7 @@ func TestFEVMRecursiveActorCallEstimate(t *testing.T) {
client.EVM().SignTransaction(tx, key.PrivateKey)
hash := client.EVM().SubmitTransaction(ctx, tx)
smsg, err := tx.ToSignedMessage()
smsg, err := ethtypes.ToSignedFilecoinMessage(tx)
require.NoError(t, err)
_, err = client.StateWaitMsg(ctx, smsg.Cid(), 0, 0, false)
@ -834,7 +834,7 @@ func TestFEVMBareTransferTriggersSmartContractLogic(t *testing.T) {
maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx)
require.NoError(t, err)
tx := ethtypes.EthTxArgs{
tx := ethtypes.Eth1559TxArgs{
ChainID: build.Eip155ChainId,
Value: big.NewInt(100),
Nonce: 0,

View File

@ -44,6 +44,49 @@ func (f *TestFullNode) EVM() *EVM {
return &EVM{f}
}
// SignLegacyHomesteadTransaction signs a legacy Homstead Ethereum transaction in place with the supplied private key.
func (e *EVM) SignLegacyEIP155Transaction(tx *ethtypes.EthLegacy155TxArgs, privKey []byte, chainID big.Int) {
preimage, err := tx.ToRlpUnsignedMsg()
require.NoError(e.t, err)
// sign the RLP payload
signature, err := sigs.Sign(crypto.SigTypeDelegated, privKey, preimage)
require.NoError(e.t, err)
signature.Data = append([]byte{ethtypes.EthLegacy155TxSignaturePrefix}, signature.Data...)
chainIdMul := big.Mul(chainID, big.NewInt(2))
vVal := big.Add(chainIdMul, big.NewIntUnsigned(35))
switch signature.Data[len(signature.Data)-1] {
case 0:
vVal = big.Add(vVal, big.NewInt(0))
case 1:
vVal = big.Add(vVal, big.NewInt(1))
}
signature.Data = append(signature.Data[:65], vVal.Int.Bytes()...)
err = tx.InitialiseSignature(*signature)
require.NoError(e.t, err)
}
// SignLegacyHomesteadTransaction signs a legacy Homstead Ethereum transaction in place with the supplied private key.
func (e *EVM) SignLegacyHomesteadTransaction(tx *ethtypes.EthLegacyHomesteadTxArgs, privKey []byte) {
preimage, err := tx.ToRlpUnsignedMsg()
require.NoError(e.t, err)
// sign the RLP payload
signature, err := sigs.Sign(crypto.SigTypeDelegated, privKey, preimage)
require.NoError(e.t, err)
signature.Data = append([]byte{ethtypes.EthLegacyHomesteadTxSignaturePrefix}, signature.Data...)
signature.Data[len(signature.Data)-1] += 27
err = tx.InitialiseSignature(*signature)
require.NoError(e.t, err)
}
func (e *EVM) DeployContractWithValue(ctx context.Context, sender address.Address, bytecode []byte, value big.Int) eam.CreateReturn {
require := require.New(e.t)
@ -208,7 +251,7 @@ func (e *EVM) AssertAddressBalanceConsistent(ctx context.Context, addr address.A
}
// SignTransaction signs an Ethereum transaction in place with the supplied private key.
func (e *EVM) SignTransaction(tx *ethtypes.EthTxArgs, privKey []byte) {
func (e *EVM) SignTransaction(tx *ethtypes.Eth1559TxArgs, privKey []byte) {
preimage, err := tx.ToRlpUnsignedMsg()
require.NoError(e.t, err)
@ -216,16 +259,12 @@ func (e *EVM) SignTransaction(tx *ethtypes.EthTxArgs, privKey []byte) {
signature, err := sigs.Sign(crypto.SigTypeDelegated, privKey, preimage)
require.NoError(e.t, err)
r, s, v, err := ethtypes.RecoverSignature(*signature)
err = tx.InitialiseSignature(*signature)
require.NoError(e.t, err)
tx.V = big.Int(v)
tx.R = big.Int(r)
tx.S = big.Int(s)
}
// SubmitTransaction submits the transaction via the Eth endpoint.
func (e *EVM) SubmitTransaction(ctx context.Context, tx *ethtypes.EthTxArgs) ethtypes.EthHash {
func (e *EVM) SubmitTransaction(ctx context.Context, tx ethtypes.EthTransaction) ethtypes.EthHash {
signed, err := tx.ToRlpSignedMsg()
require.NoError(e.t, err)

View File

@ -294,14 +294,16 @@ func (a *EthModule) EthGetTransactionByHashLimited(ctx context.Context, txHash *
// This should be "fine" as anyone using an "Ethereum-centric" block
// explorer shouldn't care about seeing pending messages from native
// accounts.
tx, err := ethtypes.EthTxFromSignedEthMessage(p)
ethtx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(p)
if err != nil {
return nil, fmt.Errorf("could not convert Filecoin message into tx: %w", err)
}
tx.Hash, err = tx.TxHash()
tx, err := ethtx.ToEthTx(p)
if err != nil {
return nil, fmt.Errorf("could not compute tx hash for eth txn: %w", err)
return nil, fmt.Errorf("could not convert Eth transaction to EthTx: %w", err)
}
return &tx, nil
}
}
@ -817,12 +819,12 @@ func (a *EthModule) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error)
}
func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) {
txArgs, err := ethtypes.ParseEthTxArgs(rawTx)
txArgs, err := ethtypes.ParseEthTransaction(rawTx)
if err != nil {
return ethtypes.EmptyEthHash, err
}
smsg, err := txArgs.ToSignedMessage()
smsg, err := ethtypes.ToSignedFilecoinMessage(txArgs)
if err != nil {
return ethtypes.EmptyEthHash, err
}

View File

@ -250,7 +250,6 @@ func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTx
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
@ -449,7 +448,7 @@ func ethTxHashFromMessageCid(ctx context.Context, c cid.Cid, sa StateAPI) (ethty
func ethTxHashFromSignedMessage(smsg *types.SignedMessage) (ethtypes.EthHash, error) {
if smsg.Signature.Type == crypto.SigTypeDelegated {
tx, err := ethtypes.EthTxFromSignedEthMessage(smsg)
tx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(smsg)
if err != nil {
return ethtypes.EthHash{}, xerrors.Errorf("failed to convert from signed message: %w", err)
}
@ -468,14 +467,13 @@ func newEthTxFromSignedMessage(smsg *types.SignedMessage, st *state.StateTree) (
// This is an eth tx
if smsg.Signature.Type == crypto.SigTypeDelegated {
tx, err = ethtypes.EthTxFromSignedEthMessage(smsg)
ethTx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(smsg)
if err != nil {
return ethtypes.EthTx{}, xerrors.Errorf("failed to convert from signed message: %w", err)
}
tx.Hash, err = tx.TxHash()
tx, err = ethTx.ToEthTx(smsg)
if err != nil {
return ethtypes.EthTx{}, xerrors.Errorf("failed to calculate hash for ethTx: %w", err)
return ethtypes.EthTx{}, xerrors.Errorf("failed to convert from signed message: %w", err)
}
} else if smsg.Signature.Type == crypto.SigTypeSecp256k1 { // Secp Filecoin Message
tx, err = ethTxFromNativeMessage(smsg.VMMessage(), st)
@ -535,6 +533,9 @@ func ethTxFromNativeMessage(msg *types.Message, st *state.StateTree) (ethtypes.E
codec = uint64(multicodec.Cbor)
}
maxFeePerGas := ethtypes.EthBigInt(msg.GasFeeCap)
maxPriorityFeePerGas := ethtypes.EthBigInt(msg.GasPremium)
// We decode as a native call first.
ethTx := ethtypes.EthTx{
To: &to,
@ -543,10 +544,10 @@ func ethTxFromNativeMessage(msg *types.Message, st *state.StateTree) (ethtypes.E
Nonce: ethtypes.EthUint64(msg.Nonce),
ChainID: ethtypes.EthUint64(build.Eip155ChainId),
Value: ethtypes.EthBigInt(msg.Value),
Type: ethtypes.Eip1559TxType,
Type: ethtypes.EIP1559TxType,
Gas: ethtypes.EthUint64(msg.GasLimit),
MaxFeePerGas: ethtypes.EthBigInt(msg.GasFeeCap),
MaxPriorityFeePerGas: ethtypes.EthBigInt(msg.GasPremium),
MaxFeePerGas: &maxFeePerGas,
MaxPriorityFeePerGas: &maxPriorityFeePerGas,
AccessList: []ethtypes.EthHash{},
}
@ -653,6 +654,7 @@ func newEthTxFromMessageLookup(ctx context.Context, msgLookup *api.MsgLookup, tx
tx.BlockHash = &blkHash
tx.BlockNumber = &bn
tx.TransactionIndex = &ti
return tx, nil
}
@ -714,7 +716,18 @@ func newEthTxReceipt(ctx context.Context, tx ethtypes.EthTx, lookup *api.MsgLook
}
baseFee := parentTs.Blocks()[0].ParentBaseFee
gasOutputs := vm.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 api.EthTxReceipt{}, xerrors.Errorf("failed to get gas fee cap: %w", err)
}
gasPremium, err := tx.GasPremium()
if err != nil {
return api.EthTxReceipt{}, xerrors.Errorf("failed to get gas premium: %w", err)
}
gasOutputs := vm.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()

View File

@ -85,11 +85,12 @@ func (m *EthTxHashManager) ProcessSignedMessage(ctx context.Context, msg *types.
return
}
ethTx, err := ethtypes.EthTxFromSignedEthMessage(msg)
ethTx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(msg)
if err != nil {
log.Errorf("error converting filecoin message to eth tx: %s", err)
return
}
txHash, err := ethTx.TxHash()
if err != nil {
log.Errorf("error hashing transaction: %s", err)