From c95d4b3b3017e62a3e69213d4af9049007c4aca5 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Thu, 25 Apr 2024 14:25:21 +0530 Subject: [PATCH 01/27] poc for eth legacy tx --- chain/consensus/signatures.go | 17 +- chain/messagepool/messagepool.go | 1 + chain/messagepool/provider.go | 2 + chain/messagesigner/messagesigner.go | 2 +- chain/sub/incoming.go | 9 + chain/types/ethtypes/eth_1559_transactions.go | 404 ++++++++++ ..._test.go => eth_1559_transactions_test.go} | 21 +- .../eth_legacy_homestead_transactions.go | 283 +++++++ .../eth_legacy_homestead_transactions_test.go | 137 ++++ chain/types/ethtypes/eth_transactions.go | 706 ++++++------------ chain/types/ethtypes/rlp_test.go | 2 +- cmd/lotus-shed/eth.go | 4 +- itests/eth_account_abstraction_test.go | 10 +- itests/eth_conformance_test.go | 2 +- itests/eth_deploy_test.go | 2 +- itests/eth_hash_lookup_test.go | 4 +- itests/eth_legacy_transactions_test.go | 111 +++ itests/eth_transactions_test.go | 27 +- itests/fevm_test.go | 4 +- itests/kit/evm.go | 38 +- node/impl/full/eth.go | 12 +- node/impl/full/eth_utils.go | 28 +- node/impl/full/txhashmanager.go | 5 +- 23 files changed, 1291 insertions(+), 540 deletions(-) create mode 100644 chain/types/ethtypes/eth_1559_transactions.go rename chain/types/ethtypes/{eth_transactions_test.go => eth_1559_transactions_test.go} (99%) create mode 100644 chain/types/ethtypes/eth_legacy_homestead_transactions.go create mode 100644 chain/types/ethtypes/eth_legacy_homestead_transactions_test.go create mode 100644 itests/eth_legacy_transactions_test.go diff --git a/chain/consensus/signatures.go b/chain/consensus/signatures.go index cb0e229a85b..75195490e29 100644 --- a/chain/consensus/signatures.go +++ b/chain/consensus/signatures.go @@ -1,6 +1,8 @@ package consensus import ( + "fmt" + "golang.org/x/xerrors" "github.com/filecoin-project/go-address" @@ -18,11 +20,14 @@ import ( // must be recognized by the registered verifier for the signature type. func AuthenticateMessage(msg *types.SignedMessage, signer address.Address) error { var digest []byte - typ := msg.Signature.Type + cpy := (*msg).Signature + cpy.Data = make([]byte, len(msg.Signature.Data)) + copy(cpy.Data, msg.Signature.Data) + switch typ { case crypto.SigTypeDelegated: - txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(&msg.Message) + txArgs, err := ethtypes.EthereumTransactionFromSignedEthMessage(msg) if err != nil { return xerrors.Errorf("failed to reconstruct eth transaction: %w", err) } @@ -40,11 +45,17 @@ func AuthenticateMessage(msg *types.SignedMessage, signer address.Address) error return xerrors.Errorf("failed to repack eth rlp message: %w", err) } digest = rlpEncodedMsg + cpy.Data, err = txArgs.VerifiableSignature(cpy.Data) + if err != nil { + return xerrors.Errorf("failed to get verifiable signature: %w", err) + } + fmt.Println("signature.Data: ", cpy.Data) default: digest = msg.Message.Cid().Bytes() } - if err := sigs.Verify(&msg.Signature, signer, digest); err != nil { + fmt.Println("SIGNATURE IN AUTH IS", cpy.Data) + if err := sigs.Verify(&cpy, signer, digest); err != nil { return xerrors.Errorf("message %s has invalid signature (type %d): %w", msg.Cid(), typ, err) } return nil diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 7d55b0b16f5..a13416f5c11 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -824,6 +824,7 @@ func (mp *MessagePool) VerifyMsgSig(m *types.SignedMessage) error { return nil } + fmt.Println("SIGNATURE HERE IS", m.Signature.Data) if err := consensus.AuthenticateMessage(m, m.Message.From); err != nil { return xerrors.Errorf("failed to validate signature: %w", err) } diff --git a/chain/messagepool/provider.go b/chain/messagepool/provider.go index 8d1a971539a..9567fc34009 100644 --- a/chain/messagepool/provider.go +++ b/chain/messagepool/provider.go @@ -3,6 +3,7 @@ package messagepool import ( "context" "errors" + "fmt" "time" lru "github.com/hashicorp/golang-lru/v2" @@ -118,6 +119,7 @@ func (mpp *mpoolProvider) PutMessage(ctx context.Context, m types.ChainMsg) (cid } func (mpp *mpoolProvider) PubSubPublish(k string, v []byte) error { + fmt.Println("HELLO") return mpp.ps.Publish(k, v) // nolint } diff --git a/chain/messagesigner/messagesigner.go b/chain/messagesigner/messagesigner.go index cd31a3b739e..aaef376cc4d 100644 --- a/chain/messagesigner/messagesigner.go +++ b/chain/messagesigner/messagesigner.go @@ -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.Eth1559TxArgsFromUnsignedEthMessage(msg) if err != nil { return nil, xerrors.Errorf("failed to reconstruct eth transaction: %w", err) } diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index b50ddc46779..6a40fe27fad 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/binary" + "fmt" "sync" "time" @@ -404,33 +405,41 @@ func (mv *MessageValidator) validateLocalMessage(ctx context.Context, msg *pubsu stats.Record(ctx, metrics.MessagePublished.M(1)) m, err := types.DecodeSignedMessage(msg.Message.GetData()) + fmt.Println("signed message is", m.Signature.Data) if err != nil { log.Warnf("failed to decode local message: %s", err) recordFailure(ctx, metrics.MessageValidationFailure, "decode") + fmt.Println("111111111") return pubsub.ValidationIgnore } if m.Size() > messagepool.MaxMessageSize { log.Warnf("local message is too large! (%dB)", m.Size()) recordFailure(ctx, metrics.MessageValidationFailure, "oversize") + fmt.Println("111111112") return pubsub.ValidationIgnore } if m.Message.To == address.Undef { log.Warn("local message has invalid destination address") recordFailure(ctx, metrics.MessageValidationFailure, "undef-addr") + fmt.Println("111111113") return pubsub.ValidationIgnore } if !m.Message.Value.LessThan(types.TotalFilecoinInt) { log.Warnf("local messages has too high value: %s", m.Message.Value) recordFailure(ctx, metrics.MessageValidationFailure, "value-too-high") + fmt.Println("111111114") return pubsub.ValidationIgnore } if err := mv.mpool.VerifyMsgSig(m); err != nil { + fmt.Println("error is BOY", err) log.Warnf("signature verification failed for local message: %s", err) recordFailure(ctx, metrics.MessageValidationFailure, "verify-sig") + fmt.Println("111111115") + return pubsub.ValidationIgnore } diff --git a/chain/types/ethtypes/eth_1559_transactions.go b/chain/types/ethtypes/eth_1559_transactions.go new file mode 100644 index 00000000000..1d22e52697c --- /dev/null +++ b/chain/types/ethtypes/eth_1559_transactions.go @@ -0,0 +1,404 @@ +package ethtypes + +import ( + "fmt" + + "golang.org/x/crypto/sha3" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + gocrypto "github.com/filecoin-project/go-crypto" + "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" +) + +const Eip1559TxType = 2 + +var _ EthereumTransaction = (*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) ToUnsignedMessage(from address.Address) (*types.Message, error) { + tx.ChainID = build.Eip155ChainId + mi, err := filecoin_method_info(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 +} + +// This function has been deduplicated and now uses the common implementation from EthLegacyHomesteadTxArgs +func (tx *Eth1559TxArgs) ToSignedMessage() (*types.SignedMessage, error) { + return toSignedMessageCommon(tx) +} + +func (tx *Eth1559TxArgs) 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 *Eth1559TxArgs) TxHash() (EthHash, error) { + rlp, err := tx.ToRlpSignedMsg() + if err != nil { + return EmptyEthHash, err + } + + return EthHashFromTxBytes(rlp), nil +} + +func (tx *Eth1559TxArgs) ToRlpSignedMsg() ([]byte, error) { + tx.ChainID = build.Eip155ChainId + packed1, err := tx.packTxFields() + if err != nil { + return nil, err + } + + packed2, err := packSigFields(tx.V, tx.R, tx.S) + 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 *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 (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, fmt.Errorf("signature is not 65 bytes") + } + return &typescrypto.Signature{ + Type: typescrypto.SigTypeDelegated, Data: sig, + }, nil +} + +func (tx *Eth1559TxArgs) 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 (tx *Eth1559TxArgs) VerifiableSignature(sig []byte) ([]byte, error) { + return sig, nil +} + +func (tx *Eth1559TxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) { + var ( + to *EthAddress + params []byte + err 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) + + chainId := EthUint64(build.Eip155ChainId) + ethTx := EthTx{ + ChainID: &chainId, + Type: Eip1559TxType, + Nonce: EthUint64(tx.Nonce), + Hash: hash, + To: to, + Value: EthBigInt(tx.Value), + Input: params, + Gas: EthUint64(tx.GasLimit), + GasPrice: gasFeeCap, + MaxFeePerGas: &gasFeeCap, + MaxPriorityFeePerGas: &gasPremium, + From: from, + R: EthBigInt(tx.R), + S: EthBigInt(tx.S), + V: EthBigInt(tx.V), + } + + return ethTx, nil +} + +func (tx *Eth1559TxArgs) SetEthSignatureValues(sig typescrypto.Signature) error { + if sig.Type != typescrypto.SigTypeDelegated { + return fmt.Errorf("RecoverSignature only supports Delegated signature") + } + + if len(sig.Data) != 65 { + return 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 fmt.Errorf("cannot parse r into EthBigInt") + } + + s_, err := parseBigInt(sig.Data[32:64]) + if err != nil { + return fmt.Errorf("cannot parse s into EthBigInt") + } + + v_, err := parseBigInt([]byte{sig.Data[64]}) + if err != nil { + return fmt.Errorf("cannot parse v into EthBigInt") + } + + tx.R = r_ + tx.S = s_ + tx.V = v_ + + return nil +} + +func parseEip1559Tx(data []byte) (*Eth1559TxArgs, 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 := 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 Eth1559TxArgsFromUnsignedEthMessage(msg *types.Message) (*Eth1559TxArgs, error) { + var ( + to *EthAddress + params []byte + err error + ) + + if msg.Version != 0 { + return nil, xerrors.Errorf("unsupported msg version: %d", msg.Version) + } + + params, to, err = parseMessageParamsAndReceipient(msg) + if err != nil { + return nil, 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 +} diff --git a/chain/types/ethtypes/eth_transactions_test.go b/chain/types/ethtypes/eth_1559_transactions_test.go similarity index 99% rename from chain/types/ethtypes/eth_transactions_test.go rename to chain/types/ethtypes/eth_1559_transactions_test.go index 68abc55dd49..1cc0267ece1 100644 --- a/chain/types/ethtypes/eth_transactions_test.go +++ b/chain/types/ethtypes/eth_1559_transactions_test.go @@ -27,10 +27,10 @@ type TxTestcase struct { TxJSON string NosigTx string Input EthBytes - Output EthTxArgs + Output Eth1559TxArgs } -func TestTxArgs(t *testing.T) { +func TestEIP1559TxArgs(t *testing.T) { testcases, err := prepareTxTestcases() require.Nil(t, err) require.NotEmpty(t, testcases) @@ -39,7 +39,7 @@ func TestTxArgs(t *testing.T) { comment := fmt.Sprintf("case %d: \n%s\n%s", i, tc.TxJSON, hex.EncodeToString(tc.Input)) // parse txargs - txArgs, err := ParseEthTxArgs(tc.Input) + txArgs, err := parseEip1559Tx(tc.Input) require.NoError(t, err, comment) msgRecovered, err := txArgs.ToRlpUnsignedMsg() @@ -63,7 +63,7 @@ func TestTxArgs(t *testing.T) { } } -func TestSignatures(t *testing.T) { +func TestEIP1559Signatures(t *testing.T) { testcases := []struct { RawTx string ExpectedR string @@ -95,7 +95,7 @@ func TestSignatures(t *testing.T) { } for _, tc := range testcases { - tx, err := ParseEthTxArgs(mustDecodeHex(tc.RawTx)) + tx, err := parseEip1559Tx(mustDecodeHex(tc.RawTx)) if tc.ExpectErr { require.Error(t, err) continue @@ -105,16 +105,15 @@ func TestSignatures(t *testing.T) { sig, err := tx.Signature() require.Nil(t, err) - r, s, v, err := RecoverSignature(*sig) - require.Nil(t, err) + tx.SetEthSignatureValues(*sig) - marshaledR, err := r.MarshalJSON() + marshaledR, err := tx.R.MarshalJSON() require.Nil(t, err) - marshaledS, err := s.MarshalJSON() + marshaledS, err := tx.S.MarshalJSON() require.Nil(t, err) - marshaledV, err := v.MarshalJSON() + marshaledV, err := tx.V.MarshalJSON() require.Nil(t, err) require.Equal(t, tc.ExpectedR, string(marshaledR)) @@ -234,7 +233,7 @@ func prepareTxTestcases() ([]TxTestcase, error) { res := []TxTestcase{} for _, tc := range testcases { - tx := EthTxArgs{} + tx := Eth1559TxArgs{} err := json.Unmarshal([]byte(tc.Output), &tx) if err != nil { return nil, err diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go new file mode 100644 index 00000000000..4b7e36f723b --- /dev/null +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -0,0 +1,283 @@ +package ethtypes + +import ( + "fmt" + + "github.com/filecoin-project/go-address" + gocrypto "github.com/filecoin-project/go-crypto" + "github.com/filecoin-project/go-state-types/big" + typescrypto "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/lotus/chain/types" + "golang.org/x/crypto/sha3" + "golang.org/x/xerrors" +) + +// define a one byte prefix for legacy homestead eth transactions +const LegacyHomesteadEthTxPrefix = 0x01 + +var _ EthereumTransaction = (*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 { + // 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") + } + hash, err := tx.TxHash() + if err != nil { + return EthTx{}, err + } + + ethTx := EthTx{ + Type: 0x00, + Nonce: EthUint64(tx.Nonce), + Hash: hash, + To: tx.To, + Value: EthBigInt(tx.Value), + Input: tx.Input, + Gas: EthUint64(tx.GasLimit), + GasPrice: EthBigInt(tx.GasPrice), + From: from, + R: EthBigInt(tx.R), + S: EthBigInt(tx.S), + V: EthBigInt(tx.V), + } + + return ethTx, nil +} + +func (tx *EthLegacyHomesteadTxArgs) ToUnsignedMessage(from address.Address) (*types.Message, error) { + mi, err := filecoin_method_info(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) VerifiableSignature(sig []byte) ([]byte, error) { + if len(sig) != 66 { + return nil, fmt.Errorf("signature should be 66 bytes long, but got %d bytes", len(sig)) + } + if sig[0] != LegacyHomesteadEthTxPrefix { + return nil, fmt.Errorf("signature prefix should be 0x01, but got %x", sig[0]) + } + + fmt.Println("sig here is", sig) + + sig = sig[1:] + + // legacy transactions have a `V` value of 27 or 28 but new transactions have a `V` value of 0 or 1 + vValue := big.NewInt(0).SetBytes(sig[64:]) + if vValue.Uint64() != 27 && vValue.Uint64() != 28 { + return nil, fmt.Errorf("v value is not 27 or 28 for legacy transaction") + } + vValue_ := big.Sub(big.NewFromGo(vValue), big.NewInt(27)) + // we ignore the first byte of the signature data because it is the prefix for legacy txns + sig[64] = byte(vValue_.Uint64()) + + return sig, nil +} + +func (tx *EthLegacyHomesteadTxArgs) ToSignedMessage() (*types.SignedMessage, error) { + return toSignedMessageCommon(tx) +} + +func (tx *EthLegacyHomesteadTxArgs) ToRlpUnsignedMsg() ([]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 (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) { + packed1, err := tx.packTxFields() + if err != nil { + return nil, err + } + + packed2, err := packSigFields(tx.V, tx.R, tx.S) + if err != nil { + return nil, err + } + + encoded, err := EncodeRLP(append(packed1, packed2...)) + if err != nil { + return nil, err + } + return encoded, 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 +} + +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]) + } + if len(sig) != 65 { + return nil, fmt.Errorf("signature is not 65 bytes") + } + + // pre-pend a one byte marker so nodes know that this is a legacy transaction + sig = append([]byte{LegacyHomesteadEthTxPrefix}, sig...) + + return &typescrypto.Signature{ + Type: typescrypto.SigTypeDelegated, Data: sig, + }, nil +} + +func (tx *EthLegacyHomesteadTxArgs) 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 + } + + fmt.Println("sig: ", sig.Data) + + sigData, err := tx.VerifiableSignature(sig.Data) + if err != nil { + return address.Undef, err + } + + fmt.Println("sigData: ", sigData) + + pubk, err := gocrypto.EcRecover(hash, sigData) + 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 (tx *EthLegacyHomesteadTxArgs) SetEthSignatureValues(sig typescrypto.Signature) error { + if sig.Type != typescrypto.SigTypeDelegated { + return fmt.Errorf("RecoverSignature only supports Delegated signature") + } + + if len(sig.Data) != 66 { + return fmt.Errorf("signature should be 66 bytes long, but got %d bytes", len(sig.Data)) + } + + // ignore the first byte of the tx + + r_, err := parseBigInt(sig.Data[1:33]) + if err != nil { + return fmt.Errorf("cannot parse r into EthBigInt") + } + + s_, err := parseBigInt(sig.Data[33:65]) + if err != nil { + return fmt.Errorf("cannot parse s into EthBigInt") + } + + v_, err := parseBigInt([]byte{sig.Data[65]}) + if err != nil { + return fmt.Errorf("cannot parse v into EthBigInt") + } + tx.R = r_ + tx.S = s_ + tx.V = v_ + fmt.Println("tx.V: ", tx.V) + fmt.Println("tx.R: ", tx.R) + fmt.Println("tx.S: ", tx.S) + return nil +} diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go new file mode 100644 index 00000000000..546bdd4c6c9 --- /dev/null +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go @@ -0,0 +1,137 @@ +package ethtypes + +import ( + "encoding/hex" + "testing" + + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/lib/sigs" + + "github.com/stretchr/testify/require" +) + +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 + txArgs, err := parseLegacyHomesteadTx(mustDecodeHex(tc.RawTx)) + require.NoError(t, err) + + msgRecovered, err := txArgs.ToRlpUnsignedMsg() + require.NoError(t, err) + + // verify signatures + from, err := txArgs.Sender() + require.NoError(t, err) + + smsg, err := txArgs.ToSignedMessage() + 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) + + // 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 + }{ + { + "0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + `"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353"`, + `"0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804"`, + `"0x1b"`, + false, + }, + { + "0xf85f030182520794b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa07778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c", + `"0x98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a"`, + `"0x7778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c"`, + `"0x1b"`, + false, + }, + { + "0x00", + `""`, + `""`, + `""`, + true, + }, + } + + for i, tc := range testcases { + tx, err := parseLegacyHomesteadTx(mustDecodeHex(tc.RawTx)) + if tc.ExpectErr { + require.Error(t, err) + continue + } + require.Nil(t, err) + + sig, err := tx.Signature() + require.Nil(t, err) + + tx.SetEthSignatureValues(*sig) + + marshaledR, err := tx.R.MarshalJSON() + require.Nil(t, err) + + marshaledS, err := tx.S.MarshalJSON() + require.Nil(t, err) + + marshaledV, err := tx.V.MarshalJSON() + require.Nil(t, err) + + require.Equal(t, tc.ExpectedR, string(marshaledR), i) + require.Equal(t, tc.ExpectedS, string(marshaledS), i) + require.Equal(t, tc.ExpectedV, string(marshaledV), i) + } +} diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index a3b1d01502a..3fe73ed5dcf 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -6,25 +6,32 @@ import ( "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" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" builtintypes "github.com/filecoin-project/go-state-types/builtin" typescrypto "github.com/filecoin-project/go-state-types/crypto" - - "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" ) -const Eip1559TxType = 2 +type EthereumTransaction interface { + ToUnsignedMessage(from address.Address) (*types.Message, error) + ToSignedMessage() (*types.SignedMessage, error) + ToRlpUnsignedMsg() ([]byte, error) + TxHash() (EthHash, error) + ToRlpSignedMsg() ([]byte, error) + packTxFields() ([]interface{}, error) + Signature() (*typescrypto.Signature, error) + Sender() (address.Address, error) + SetEthSignatureValues(sig typescrypto.Signature) error + VerifiableSignature(sig []byte) ([]byte, error) + ToEthTx(*types.SignedMessage) (EthTx, error) +} type EthTx struct { - ChainID EthUint64 `json:"chainId"` + ChainID *EthUint64 `json:"chainId,omitempty"` Nonce EthUint64 `json:"nonce"` Hash EthHash `json:"hash"` BlockHash *EthHash `json:"blockHash"` @@ -36,142 +43,123 @@ type EthTx struct { Type EthUint64 `json:"type"` Input EthBytes `json:"input"` Gas EthUint64 `json:"gas"` - MaxFeePerGas EthBigInt `json:"maxFeePerGas"` - MaxPriorityFeePerGas EthBigInt `json:"maxPriorityFeePerGas"` - AccessList []EthHash `json:"accessList"` + MaxFeePerGas *EthBigInt `json:"maxFeePerGas,omitempty"` + MaxPriorityFeePerGas *EthBigInt `json:"maxPriorityFeePerGas,omitempty"` + GasPrice EthBigInt `json:"gasPrice"` + AccessList []EthHash `json:"accessList,omitempty"` 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 { + return tx.GasPrice +} + +func (tx *EthTx) GasPremium() EthBigInt { + if tx.MaxPriorityFeePerGas == nil { + return tx.GasPrice + } + return *tx.MaxPriorityFeePerGas } -// EthTxFromSignedEthMessage does NOT populate: -// - BlockHash -// - BlockNumber -// - TransactionIndex -// - Hash -func EthTxFromSignedEthMessage(smsg *types.SignedMessage) (EthTx, error) { +func EthereumTransactionFromSignedEthMessage(smsg *types.SignedMessage) (EthereumTransaction, 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) + return nil, xerrors.Errorf("sender must be an eth account, was %s", smsg.Message.From) } // Probably redundant, but we might as well check. if smsg.Signature.Type != typescrypto.SigTypeDelegated { - return EthTx{}, xerrors.Errorf("signature is not delegated type, is type: %d", smsg.Signature.Type) + return nil, xerrors.Errorf("signature is not delegated type, is type: %d", smsg.Signature.Type) } - txArgs, err := EthTxArgsFromUnsignedEthMessage(&smsg.Message) + _, err := EthAddressFromFilecoinAddress(smsg.Message.From) if err != nil { - return EthTx{}, xerrors.Errorf("failed to convert the unsigned message: %w", err) + // This should be impossible as we've already asserted that we have an EthAddress + // sender... + return nil, xerrors.Errorf("sender was not an eth account") } - r, s, v, err := RecoverSignature(smsg.Signature) + params, to, err := parseMessageParamsAndReceipient(&smsg.Message) if err != nil { - return EthTx{}, xerrors.Errorf("failed to recover signature: %w", err) + return nil, 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") - } - - 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 -} - -func EthTxArgsFromUnsignedEthMessage(msg *types.Message) (EthTxArgs, error) { - var ( - to *EthAddress - params []byte - err error - ) + msg := smsg.Message if msg.Version != 0 { - return EthTxArgs{}, xerrors.Errorf("unsupported msg version: %d", msg.Version) + return nil, 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) + switch len(smsg.Signature.Data) { + case 66: + if smsg.Signature.Data[0] != LegacyHomesteadEthTxPrefix { + return nil, fmt.Errorf("unsupported legacy transaction; first byte of signature is %d", + smsg.Signature.Data[0]) } - if paramsReader.Len() != 0 { - return EthTxArgs{}, xerrors.Errorf("extra data found in params") + tx := EthLegacyHomesteadTxArgs{ + Nonce: int(msg.Nonce), + To: to, + Value: msg.Value, + Input: params, + GasPrice: msg.GasFeeCap, + GasLimit: int(msg.GasLimit), } - if len(params) == 0 { - return EthTxArgs{}, xerrors.Errorf("non-empty params encode empty byte array") + tx.SetEthSignatureValues(smsg.Signature) + fmt.Println("FINISHED SETTING ETH SIGNATURE VALUES") + return &tx, nil + case 65: + tx := Eth1559TxArgs{ + Nonce: int(msg.Nonce), + To: to, + Value: msg.Value, + Input: params, + MaxFeePerGas: msg.GasFeeCap, + MaxPriorityFeePerGas: msg.GasPremium, + GasLimit: int(msg.GasLimit), } + tx.SetEthSignatureValues(smsg.Signature) + return &tx, nil + default: + return nil, fmt.Errorf("unsupported signature length: %d", len(smsg.Signature.Data)) } +} - 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) +func ParseEthTx(data []byte) (EthereumTransaction, error) { + if len(data) == 0 { + return nil, fmt.Errorf("empty data") } - 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 -} + if data[0] > 0x7f { + return parseLegacyHomesteadTx(data) + } + + if data[0] == 1 { + // EIP-2930 + return nil, fmt.Errorf("EIP-2930 transaction is not supported") + } -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) + if data[0] == Eip1559TxType { + // EIP-1559 + return parseEip1559Tx(data) } + return nil, fmt.Errorf("unsupported transaction type") +} + +type methodInfo struct { + to address.Address + method abi.MethodNum + params []byte +} + +func filecoin_method_info(recipient *EthAddress, input []byte) (*methodInfo, error) { var err error var params []byte - if len(tx.Input) > 0 { + if len(input) > 0 { buf := new(bytes.Buffer) - if err = cbg.WriteByteArray(buf, tx.Input); err != nil { + if err = cbg.WriteByteArray(buf, input); err != nil { return nil, xerrors.Errorf("failed to write input args: %w", err) } params = buf.Bytes() @@ -180,490 +168,274 @@ func (tx *EthTxArgs) ToUnsignedMessage(from address.Address) (*types.Message, er var to address.Address var method abi.MethodNum // nil indicates the EAM, only CreateExternal is allowed - if tx.To == nil { + if recipient == nil { method = builtintypes.MethodsEAM.CreateExternal to = builtintypes.EthereumAddressManagerActorAddr } else { method = builtintypes.MethodsEVM.InvokeContract - to, err = tx.To.ToFilecoinAddress() + to, err = recipient.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, + return &methodInfo{ + to: to, + method: method, + params: params, }, nil } -func (tx *EthTxArgs) ToSignedMessage() (*types.SignedMessage, error) { - from, err := tx.Sender() - if err != nil { - return nil, xerrors.Errorf("failed to calculate sender: %w", err) - } - - unsignedMsg, err := tx.ToUnsignedMessage(from) - if err != nil { - return nil, xerrors.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 &types.SignedMessage{ - Message: *unsignedMsg, - Signature: *siggy, - }, nil -} - -func (tx *EthTxArgs) HashedOriginalRlpMsg() ([]byte, error) { - msg, err := tx.ToRlpUnsignedMsg() +func packSigFields(v, r, s big.Int) ([]interface{}, error) { + rr, err := formatBigInt(r) 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() + ss, err := formatBigInt(s) if err != nil { return nil, err } - encoded, err := EncodeRLP(packed) + vv, err := formatBigInt(v) 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 (ðTxArgs).TxHash() + res := []interface{}{vv, rr, ss} + return res, nil } -func (tx *EthTxArgs) TxHash() (EthHash, error) { - rlp, err := tx.ToRlpSignedMsg() - if err != nil { - return EmptyEthHash, err +func padLeadingZeros(data []byte, length int) []byte { + if len(data) >= length { + return data } - - return EthHashFromTxBytes(rlp), nil + zeros := make([]byte, length-len(data)) + return append(zeros, data...) } -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 +func removeLeadingZeros(data []byte) []byte { + firstNonZeroIndex := len(data) + for i, b := range data { + if b > 0 { + firstNonZeroIndex = i + break + } } - return append([]byte{0x02}, encoded...), nil + return data[firstNonZeroIndex:] } -func (tx *EthTxArgs) packTxFields() ([]interface{}, error) { - chainId, err := formatInt(tx.ChainID) +func formatInt(val int) ([]byte, error) { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.BigEndian, int64(val)) if err != nil { return nil, err } + return removeLeadingZeros(buf.Bytes()), nil +} - nonce, err := formatInt(tx.Nonce) - if err != nil { - return nil, err +func formatEthAddr(addr *EthAddress) []byte { + if addr == nil { + return nil } + return addr[:] +} - maxPriorityFeePerGas, err := formatBigInt(tx.MaxPriorityFeePerGas) +func formatBigInt(val big.Int) ([]byte, error) { + b, err := val.Bytes() if err != nil { return nil, err } + return removeLeadingZeros(b), nil +} - maxFeePerGas, err := formatBigInt(tx.MaxFeePerGas) - if err != nil { - return nil, err +func parseInt(v interface{}) (int, error) { + data, ok := v.([]byte) + if !ok { + return 0, fmt.Errorf("cannot parse interface to int: input is not a byte array") } - - gasLimit, err := formatInt(tx.GasLimit) - if err != nil { - return nil, err + if len(data) == 0 { + return 0, nil } - - value, err := formatBigInt(tx.Value) - if err != nil { - return nil, err + if len(data) > 8 { + return 0, fmt.Errorf("cannot parse interface to int: length is more than 8 bytes") } - - res := []interface{}{ - chainId, - nonce, - maxPriorityFeePerGas, - maxFeePerGas, - gasLimit, - formatEthAddr(tx.To), - value, - tx.Input, - []interface{}{}, // access list + var value int64 + r := bytes.NewReader(append(make([]byte, 8-len(data)), data...)) + if err := binary.Read(r, binary.BigEndian, &value); err != nil { + return 0, fmt.Errorf("cannot parse interface to EthUint64: %w", err) } - return res, nil + return int(value), 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 +func parseBigInt(v interface{}) (big.Int, error) { + data, ok := v.([]byte) + if !ok { + return big.Zero(), fmt.Errorf("cannot parse interface to big.Int: input is not a byte array") } - - v, err := formatBigInt(tx.V) - if err != nil { - return nil, err + if len(data) == 0 { + return big.Zero(), nil } - - res := []interface{}{v, r, s} - return res, nil + var b mathbig.Int + b.SetBytes(data) + return big.NewFromGo(&b), 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") +func parseBytes(v interface{}) ([]byte, error) { + val, ok := v.([]byte) + if !ok { + return nil, fmt.Errorf("cannot parse interface into bytes: input is not a byte array") } - return &typescrypto.Signature{ - Type: typescrypto.SigTypeDelegated, Data: sig, - }, nil + return val, 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) +func parseEthAddr(v interface{}) (*EthAddress, error) { + b, err := parseBytes(v) if err != nil { - return address.Undef, err + return nil, err } - - ethAddr, err := EthAddressFromPubKey(pubk) - if err != nil { - return address.Undef, err + if len(b) == 0 { + return nil, nil } - - ea, err := CastEthAddress(ethAddr) + addr, err := CastEthAddress(b) if err != nil { - return address.Undef, err + return nil, err } - - return ea.ToFilecoinAddress() + return &addr, nil } -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]) +func toSignedMessageCommon(tx EthereumTransaction) (*types.SignedMessage, error) { + from, err := tx.Sender() if err != nil { - return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse r into EthBigInt") + return nil, xerrors.Errorf("failed to calculate sender: %w", err) } - s_, err := parseBigInt(sig.Data[32:64]) + unsignedMsg, err := tx.ToUnsignedMessage(from) if err != nil { - return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse s into EthBigInt") + return nil, xerrors.Errorf("failed to convert to unsigned msg: %w", err) } - v_, err := parseBigInt([]byte{sig.Data[64]}) + siggy, err := tx.Signature() if err != nil { - return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse v into EthBigInt") + return nil, xerrors.Errorf("failed to calculate signature: %w", err) } - return EthBigInt(r_), EthBigInt(s_), EthBigInt(v_), nil + return &types.SignedMessage{ + Message: *unsignedMsg, + Signature: *siggy, + }, 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") +func parseLegacyHomesteadTx(data []byte) (*EthLegacyHomesteadTxArgs, error) { + if data[0] <= 0x7f { + return nil, fmt.Errorf("not a legacy eth transaction") } - d, err := DecodeRLP(data[1:]) + d, err := DecodeRLP(data) 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") + return nil, fmt.Errorf("not a Legacy transaction: decoded data is not a list") } - chainId, err := parseInt(decoded[0]) - if err != nil { - return nil, err - } - - nonce, err := parseInt(decoded[1]) - if err != nil { - return nil, err + if len(decoded) != 9 { + return nil, fmt.Errorf("not a Legacy transaction: should have 9 elements in the rlp list") } - maxPriorityFeePerGas, err := parseBigInt(decoded[2]) + nonce, err := parseInt(decoded[0]) if err != nil { return nil, err } - maxFeePerGas, err := parseBigInt(decoded[3]) + gasPrice, err := parseBigInt(decoded[1]) if err != nil { return nil, err } - gasLimit, err := parseInt(decoded[4]) + gasLimit, err := parseInt(decoded[2]) if err != nil { return nil, err } - to, err := parseEthAddr(decoded[5]) + to, err := parseEthAddr(decoded[3]) if err != nil { return nil, err } - value, err := parseBigInt(decoded[6]) + value, err := parseBigInt(decoded[4]) 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") + input, ok := decoded[5].([]byte) + if !ok { + return nil, fmt.Errorf("input is not a byte slice") } - r, err := parseBigInt(decoded[10]) + v, err := parseBigInt(decoded[6]) if err != nil { return nil, err } - s, err := parseBigInt(decoded[11]) + r, err := parseBigInt(decoded[7]) if err != nil { return nil, err } - v, err := parseBigInt(decoded[9]) + s, err := parseBigInt(decoded[8]) 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) { - 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 { - // EIP-2930 - return nil, fmt.Errorf("EIP-2930 transaction is not supported") - } - - if data[0] == Eip1559TxType { - // EIP-1559 - return parseEip1559Tx(data) - } - - return nil, fmt.Errorf("unsupported transaction type") + return &EthLegacyHomesteadTxArgs{ + Nonce: nonce, + GasPrice: gasPrice, + GasLimit: gasLimit, + To: to, + Value: value, + Input: input, + V: v, + R: r, + S: s, + }, nil } -func padLeadingZeros(data []byte, length int) []byte { - if len(data) >= length { - return data - } - zeros := make([]byte, length-len(data)) - return append(zeros, data...) -} +func parseMessageParamsAndReceipient(msg *types.Message) ([]byte, *EthAddress, error) { + var params []byte + var to *EthAddress -func removeLeadingZeros(data []byte) []byte { - firstNonZeroIndex := len(data) - for i, b := range data { - if b > 0 { - firstNonZeroIndex = i - break + 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, xerrors.Errorf("failed to read params byte array: %w", err) + } + if paramsReader.Len() != 0 { + return nil, nil, xerrors.Errorf("extra data found in params") + } + if len(params) == 0 { + return nil, nil, xerrors.Errorf("non-empty params encode empty byte array") } } - return data[firstNonZeroIndex:] -} - -func formatInt(val int) ([]byte, error) { - buf := new(bytes.Buffer) - err := binary.Write(buf, binary.BigEndian, int64(val)) - if err != nil { - return nil, err - } - return removeLeadingZeros(buf.Bytes()), nil -} - -func formatEthAddr(addr *EthAddress) []byte { - if addr == nil { - return nil - } - return addr[:] -} - -func formatBigInt(val big.Int) ([]byte, error) { - b, err := val.Bytes() - if err != nil { - return nil, err - } - return removeLeadingZeros(b), nil -} - -func parseInt(v interface{}) (int, error) { - data, ok := v.([]byte) - if !ok { - return 0, fmt.Errorf("cannot parse interface to int: input is not a byte array") - } - if len(data) == 0 { - return 0, nil - } - if len(data) > 8 { - return 0, fmt.Errorf("cannot parse interface to int: length is more than 8 bytes") - } - var value int64 - r := bytes.NewReader(append(make([]byte, 8-len(data)), data...)) - if err := binary.Read(r, binary.BigEndian, &value); err != nil { - return 0, fmt.Errorf("cannot parse interface to EthUint64: %w", err) - } - return int(value), nil -} - -func parseBigInt(v interface{}) (big.Int, error) { - data, ok := v.([]byte) - if !ok { - return big.Zero(), fmt.Errorf("cannot parse interface to big.Int: input is not a byte array") - } - if len(data) == 0 { - return big.Zero(), nil - } - var b mathbig.Int - b.SetBytes(data) - return big.NewFromGo(&b), nil -} -func parseBytes(v interface{}) ([]byte, error) { - val, ok := v.([]byte) - if !ok { - return nil, fmt.Errorf("cannot parse interface into bytes: input is not a 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, + xerrors.Errorf("invalid methodnum %d: only allowed method is InvokeContract(%d)", + msg.Method, builtintypes.MethodsEVM.InvokeContract) } - return val, nil -} -func parseEthAddr(v interface{}) (*EthAddress, error) { - b, err := parseBytes(v) - if err != nil { - return nil, err - } - if len(b) == 0 { - return nil, nil - } - addr, err := CastEthAddress(b) - if err != nil { - return nil, err - } - return &addr, nil + return params, to, nil } diff --git a/chain/types/ethtypes/rlp_test.go b/chain/types/ethtypes/rlp_test.go index 0ce6e15d926..0c74cf58c0a 100644 --- a/chain/types/ethtypes/rlp_test.go +++ b/chain/types/ethtypes/rlp_test.go @@ -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() diff --git a/cmd/lotus-shed/eth.go b/cmd/lotus-shed/eth.go index fde4f96f68f..359294adbb8 100644 --- a/cmd/lotus-shed/eth.go +++ b/cmd/lotus-shed/eth.go @@ -93,11 +93,11 @@ var computeEthHashCmd = &cli.Command{ 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") } diff --git a/itests/eth_account_abstraction_test.go b/itests/eth_account_abstraction_test.go index 5ca672674a6..913902d8eaf 100644 --- a/itests/eth_account_abstraction_test.go +++ b/itests/eth_account_abstraction_test.go @@ -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.Eth1559TxArgsFromUnsignedEthMessage(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.Eth1559TxArgsFromUnsignedEthMessage(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.Eth1559TxArgsFromUnsignedEthMessage(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.Eth1559TxArgsFromUnsignedEthMessage(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, diff --git a/itests/eth_conformance_test.go b/itests/eth_conformance_test.go index 9c1b2ae34ef..13d42e741cd 100644 --- a/itests/eth_conformance_test.go +++ b/itests/eth_conformance_test.go @@ -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, diff --git a/itests/eth_deploy_test.go b/itests/eth_deploy_test.go index 68861f98fd8..b3a4c26f7f7 100644 --- a/itests/eth_deploy_test.go +++ b/itests/eth_deploy_test.go @@ -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, diff --git a/itests/eth_hash_lookup_test.go b/itests/eth_hash_lookup_test.go index 0a2c11d3cf1..1cb2cae892e 100644 --- a/itests/eth_hash_lookup_test.go +++ b/itests/eth_hash_lookup_test.go @@ -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, diff --git a/itests/eth_legacy_transactions_test.go b/itests/eth_legacy_transactions_test.go new file mode 100644 index 00000000000..84493c0b0b3 --- /dev/null +++ b/itests/eth_legacy_transactions_test.go @@ -0,0 +1,111 @@ +package itests + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/big" + + "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() + + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + _, ethAddr2, _ := client.EVM().NewAccount() + fmt.Println("Deployer address: ", deployer) + + kit.SendFunds(ctx, t, client, deployer, types.FromFil(1000)) + + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ + From: ðAddr, + Data: contract, + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) + require.NoError(t, err) + + tx := ethtypes.EthLegacyHomesteadTxArgs{ + Value: big.NewInt(100), + Nonce: 0, + To: ðAddr2, + GasPrice: types.NanoFil, + GasLimit: int(gaslimit), + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + require.NoError(t, client.EVM().SignLegacyTransaction(&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 + require.NoError(t, client.EVM().SignLegacyTransaction(&tx, key.PrivateKey)) + tx.V = big.NewInt(int64(tx.V.Int.Uint64()) + 27) + fmt.Println("V: ", tx.V) + fmt.Println("WILL SUBMIT VALID NOW OK") + + hash := client.EVM().SubmitLegacyTransaction(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.Nil(t, ethTx.ChainID) + + 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, 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) +} diff --git a/itests/eth_transactions_test.go b/itests/eth_transactions_test.go index 9e9fb7b87c4..ff1d513e8c8 100644 --- a/itests/eth_transactions_test.go +++ b/itests/eth_transactions_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/hex" "encoding/json" + "fmt" "os" "testing" "time" @@ -64,7 +65,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, @@ -84,6 +85,7 @@ func TestValueTransferValidSignature(t *testing.T) { signed, err := tx.ToRlpSignedMsg() require.NoError(t, err) // Submit transaction with bad signature + fmt.Println("SENDING TO", *tx.To) _, err = client.EVM().EthSendRawTransaction(ctx, signed) require.Error(t, err) @@ -120,23 +122,6 @@ func TestValueTransferValidSignature(t *testing.T) { 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 +240,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 +348,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: ðAddr, Data: contract, @@ -383,7 +368,7 @@ func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr eth } // now deploy a contract from the embryo, and validate it went well - return ðtypes.EthTxArgs{ + return ðtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, Value: big.Zero(), Nonce: 0, diff --git a/itests/fevm_test.go b/itests/fevm_test.go index 2dcb8ef1dff..9b4c97b6f57 100644 --- a/itests/fevm_test.go +++ b/itests/fevm_test.go @@ -678,7 +678,7 @@ func TestFEVMRecursiveActorCallEstimate(t *testing.T) { nonce, err := client.MpoolGetNonce(ctx, ethFilAddr) require.NoError(t, err) - tx := ðtypes.EthTxArgs{ + tx := ðtypes.Eth1559TxArgs{ ChainID: build.Eip155ChainId, To: &contractAddr, Value: big.Zero(), @@ -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, diff --git a/itests/kit/evm.go b/itests/kit/evm.go index 0d7af25782b..e326582e2b8 100644 --- a/itests/kit/evm.go +++ b/itests/kit/evm.go @@ -44,6 +44,32 @@ func (f *TestFullNode) EVM() *EVM { return &EVM{f} } +// SignTransaction signs an Ethereum transaction in place with the supplied private key. +func (e *EVM) SignLegacyTransaction(tx *ethtypes.EthLegacyHomesteadTxArgs, privKey []byte) error { + 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.LegacyHomesteadEthTxPrefix}, signature.Data...) + + fmt.Println("doing now") + + return tx.SetEthSignatureValues(*signature) +} + +func (e *EVM) SubmitLegacyTransaction(ctx context.Context, tx *ethtypes.EthLegacyHomesteadTxArgs) ethtypes.EthHash { + signed, err := tx.ToRlpSignedMsg() + require.NoError(e.t, err) + + fmt.Println("submitting now", tx.V) + hash, err := e.EthSendRawTransaction(ctx, signed) + require.NoError(e.t, err) + + return hash +} + func (e *EVM) DeployContractWithValue(ctx context.Context, sender address.Address, bytecode []byte, value big.Int) eam.CreateReturn { require := require.New(e.t) @@ -208,7 +234,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 +242,11 @@ 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) - require.NoError(e.t, err) - - tx.V = big.Int(v) - tx.R = big.Int(r) - tx.S = big.Int(s) + tx.SetEthSignatureValues(*signature) } // 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.Eth1559TxArgs) ethtypes.EthHash { signed, err := tx.ToRlpSignedMsg() require.NoError(e.t, err) @@ -293,6 +314,7 @@ func (e *EVM) InvokeContractByFuncNameExpectExit(ctx context.Context, fromAddr a } func (e *EVM) WaitTransaction(ctx context.Context, hash ethtypes.EthHash) (*api.EthTxReceipt, error) { + fmt.Println("\n looking up hash: ", hash) if mcid, err := e.EthGetMessageCidByTransactionHash(ctx, &hash); err != nil { return nil, err } else if mcid == nil { diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index e7aeafa9085..3f881e70746 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -294,15 +294,15 @@ 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) + //tx, err := ethtypes.EthTxFromSignedEthMessage(p) if err != nil { return nil, fmt.Errorf("could not convert Filecoin message into tx: %w", err) } - tx.Hash, err = tx.TxHash() + //tx.Hash, err = tx.TxHash() if err != nil { return nil, fmt.Errorf("could not compute tx hash for eth txn: %w", err) } - return &tx, nil + return nil, nil } } // Ethereum clients expect an empty response when the message was not found @@ -406,10 +406,12 @@ func (a *EthModule) EthGetTransactionReceiptLimited(ctx context.Context, txHash // This isn't an eth transaction we have the mapping for, so let's look it up as a filecoin message if c == cid.Undef { + fmt.Println("DOING THIS") c = txHash.ToCid() } msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, c, limit, true) + fmt.Println("msgLookup", msgLookup) if err != nil { return nil, xerrors.Errorf("failed to lookup Eth Txn %s as %s: %w", txHash, c, err) } @@ -817,7 +819,7 @@ 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.ParseEthTx(rawTx) if err != nil { return ethtypes.EmptyEthHash, err } @@ -827,6 +829,8 @@ func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.Et return ethtypes.EmptyEthHash, err } + fmt.Println("sumitting to mpool", smsg.Signature.Data) + _, err = a.MpoolAPI.MpoolPush(ctx, smsg) if err != nil { return ethtypes.EmptyEthHash, err diff --git a/node/impl/full/eth_utils.go b/node/impl/full/eth_utils.go index 50e76f84ef4..514662f60d3 100644 --- a/node/impl/full/eth_utils.go +++ b/node/impl/full/eth_utils.go @@ -251,7 +251,10 @@ 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) + if smsg.Signature.Type != crypto.SigTypeDelegated { + chainID := ethtypes.EthUint64(build.Eip155ChainId) + tx.ChainID = &chainID + } tx.BlockHash = &blkHash tx.BlockNumber = &bn tx.TransactionIndex = &ti @@ -448,7 +451,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.EthereumTransactionFromSignedEthMessage(smsg) if err != nil { return ethtypes.EthHash{}, xerrors.Errorf("failed to convert from signed message: %w", err) } @@ -467,14 +470,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.EthereumTransactionFromSignedEthMessage(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) @@ -534,18 +536,22 @@ func ethTxFromNativeMessage(msg *types.Message, st *state.StateTree) (ethtypes.E codec = uint64(multicodec.Cbor) } + chainId := ethtypes.EthUint64(build.Eip155ChainId) + maxFeePerGas := ethtypes.EthBigInt(msg.GasFeeCap) + maxPriorityFeePerGas := ethtypes.EthBigInt(msg.GasPremium) + // We decode as a native call first. ethTx := ethtypes.EthTx{ To: &to, From: from, Input: encodeFilecoinParamsAsABI(msg.Method, codec, msg.Params), Nonce: ethtypes.EthUint64(msg.Nonce), - ChainID: ethtypes.EthUint64(build.Eip155ChainId), + ChainID: &chainId, Value: ethtypes.EthBigInt(msg.Value), 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{}, } @@ -713,7 +719,8 @@ 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) + + gasOutputs := vm.ComputeGasOutputs(lookup.Receipt.GasUsed, int64(tx.Gas), baseFee, big.Int(tx.GasFeeCap()), big.Int(tx.GasPremium()), true) totalSpent := big.Sum(gasOutputs.BaseFeeBurn, gasOutputs.MinerTip, gasOutputs.OverEstimationBurn) effectiveGasPrice := big.Zero() @@ -725,6 +732,7 @@ func newEthTxReceipt(ctx context.Context, tx ethtypes.EthTx, lookup *api.MsgLook if receipt.To == nil && lookup.Receipt.ExitCode.IsSuccess() { // Create and Create2 return the same things. var ret eam.CreateExternalReturn + fmt.Println("RETURN IS", lookup.Receipt.Return) if err := ret.UnmarshalCBOR(bytes.NewReader(lookup.Receipt.Return)); err != nil { return api.EthTxReceipt{}, xerrors.Errorf("failed to parse contract creation result: %w", err) } diff --git a/node/impl/full/txhashmanager.go b/node/impl/full/txhashmanager.go index 64c488d377c..2ed636469b8 100644 --- a/node/impl/full/txhashmanager.go +++ b/node/impl/full/txhashmanager.go @@ -2,6 +2,7 @@ package full import ( "context" + "fmt" "time" "github.com/filecoin-project/go-state-types/abi" @@ -85,17 +86,19 @@ func (m *EthTxHashManager) ProcessSignedMessage(ctx context.Context, msg *types. return } - ethTx, err := ethtypes.EthTxFromSignedEthMessage(msg) + ethTx, err := ethtypes.EthereumTransactionFromSignedEthMessage(msg) if err != nil { log.Errorf("error converting filecoin message to eth tx: %s", err) return } txHash, err := ethTx.TxHash() + fmt.Println("UPSERTING TO", msg.Message.To) if err != nil { log.Errorf("error hashing transaction: %s", err) return } + fmt.Println("UPSERTING", txHash) err = m.TransactionHashLookup.UpsertHash(txHash, msg.Cid()) if err != nil { log.Errorf("error inserting tx mapping to db: %s", err) From 369787f650c5ca6b7d9569abd90c8bc85add6d9c Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Thu, 25 Apr 2024 16:01:49 +0530 Subject: [PATCH 02/27] print statements --- chain/store/messages.go | 2 ++ chain/types/ethtypes/eth_transactions.go | 2 ++ chain/vm/fvm.go | 9 ++++++++- node/impl/full/eth.go | 11 +++++++---- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/chain/store/messages.go b/chain/store/messages.go index c23f900d7cb..2ed04a38e5f 100644 --- a/chain/store/messages.go +++ b/chain/store/messages.go @@ -2,6 +2,7 @@ package store import ( "context" + "fmt" block "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" @@ -58,6 +59,7 @@ func (cs *ChainStore) GetMessage(ctx context.Context, c cid.Cid) (*types.Message msg, err = types.DecodeMessage(b) return err }) + fmt.Println("ERROR IS", err) return msg, err } diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index 3fe73ed5dcf..bf5e2c4ca44 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "fmt" + "github.com/filecoin-project/lotus/build" mathbig "math/big" "github.com/filecoin-project/go-address" @@ -111,6 +112,7 @@ func EthereumTransactionFromSignedEthMessage(smsg *types.SignedMessage) (Ethereu return &tx, nil case 65: tx := Eth1559TxArgs{ + ChainID: build.Eip155ChainId, Nonce: int(msg.Nonce), To: to, Value: msg.Value, diff --git a/chain/vm/fvm.go b/chain/vm/fvm.go index c8c02dddd40..f6d528ca56d 100644 --- a/chain/vm/fvm.go +++ b/chain/vm/fvm.go @@ -412,7 +412,14 @@ func NewDebugFVM(ctx context.Context, opts *VMOpts) (*FVM, error) { return ret, nil } -func (vm *FVM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, error) { +func (vm *FVM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (rets *ApplyRet, err error) { + fmt.Printf("APPLYING MESSAGE:%+v", cmsg.VMMessage()) + defer func() { + if err != nil { + fmt.Printf("applying msg: %w", err) + } + }() + start := build.Clock.Now() defer atomic.AddUint64(&StatApplied, 1) vmMsg := cmsg.VMMessage() diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 3f881e70746..2bc9b4230f0 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -294,15 +294,18 @@ 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.EthereumTransactionFromSignedEthMessage(p) + if err != nil { + return nil, fmt.Errorf("could not convert Filecoin message into tx: %w", err) + } + tx, err := ethtx.ToEthTx(p) if err != nil { return nil, fmt.Errorf("could not convert Filecoin message into tx: %w", err) } - //tx.Hash, err = tx.TxHash() if err != nil { return nil, fmt.Errorf("could not compute tx hash for eth txn: %w", err) } - return nil, nil + return &tx, nil } } // Ethereum clients expect an empty response when the message was not found @@ -411,7 +414,7 @@ func (a *EthModule) EthGetTransactionReceiptLimited(ctx context.Context, txHash } msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, c, limit, true) - fmt.Println("msgLookup", msgLookup) + fmt.Printf("msgLookup: %+v", msgLookup) if err != nil { return nil, xerrors.Errorf("failed to lookup Eth Txn %s as %s: %w", txHash, c, err) } From fca57fc99a573049b6bd4d331a1012c4b3d21cb7 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Thu, 25 Apr 2024 16:41:30 +0530 Subject: [PATCH 03/27] finished --- chain/types/ethtypes/eth_1559_transactions.go | 14 +++----------- .../ethtypes/eth_legacy_homestead_transactions.go | 5 ++++- chain/types/ethtypes/eth_transactions.go | 11 +++++++---- itests/eth_transactions_test.go | 4 ++-- node/impl/full/eth_utils.go | 9 +++++---- 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/chain/types/ethtypes/eth_1559_transactions.go b/chain/types/ethtypes/eth_1559_transactions.go index 1d22e52697c..b1cfdf45df4 100644 --- a/chain/types/ethtypes/eth_1559_transactions.go +++ b/chain/types/ethtypes/eth_1559_transactions.go @@ -204,12 +204,6 @@ func (tx *Eth1559TxArgs) VerifiableSignature(sig []byte) ([]byte, error) { } func (tx *Eth1559TxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) { - var ( - to *EthAddress - params []byte - err error - ) - from, err := EthAddressFromFilecoinAddress(smsg.Message.From) if err != nil { return EthTx{}, xerrors.Errorf("sender was not an eth account") @@ -221,17 +215,15 @@ func (tx *Eth1559TxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) { gasFeeCap := EthBigInt(tx.MaxFeePerGas) gasPremium := EthBigInt(tx.MaxPriorityFeePerGas) - chainId := EthUint64(build.Eip155ChainId) ethTx := EthTx{ - ChainID: &chainId, + ChainID: EthUint64(build.Eip155ChainId), Type: Eip1559TxType, Nonce: EthUint64(tx.Nonce), Hash: hash, - To: to, + To: tx.To, Value: EthBigInt(tx.Value), - Input: params, + Input: tx.Input, Gas: EthUint64(tx.GasLimit), - GasPrice: gasFeeCap, MaxFeePerGas: &gasFeeCap, MaxPriorityFeePerGas: &gasPremium, From: from, diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go index 4b7e36f723b..2b52c08713c 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -41,7 +41,10 @@ func (tx *EthLegacyHomesteadTxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, e return EthTx{}, err } + gasPrice := EthBigInt(tx.GasPrice) + ethTx := EthTx{ + ChainID: 0x00, Type: 0x00, Nonce: EthUint64(tx.Nonce), Hash: hash, @@ -49,7 +52,7 @@ func (tx *EthLegacyHomesteadTxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, e Value: EthBigInt(tx.Value), Input: tx.Input, Gas: EthUint64(tx.GasLimit), - GasPrice: EthBigInt(tx.GasPrice), + GasPrice: &gasPrice, From: from, R: EthBigInt(tx.R), S: EthBigInt(tx.S), diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index bf5e2c4ca44..f712fe3ea5d 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -32,7 +32,7 @@ type EthereumTransaction interface { } type EthTx struct { - ChainID *EthUint64 `json:"chainId,omitempty"` + ChainID EthUint64 `json:"chainId,omitempty"` Nonce EthUint64 `json:"nonce"` Hash EthHash `json:"hash"` BlockHash *EthHash `json:"blockHash"` @@ -46,7 +46,7 @@ type EthTx struct { Gas EthUint64 `json:"gas"` MaxFeePerGas *EthBigInt `json:"maxFeePerGas,omitempty"` MaxPriorityFeePerGas *EthBigInt `json:"maxPriorityFeePerGas,omitempty"` - GasPrice EthBigInt `json:"gasPrice"` + GasPrice *EthBigInt `json:"gasPrice,omitempty"` AccessList []EthHash `json:"accessList,omitempty"` V EthBigInt `json:"v"` R EthBigInt `json:"r"` @@ -54,12 +54,15 @@ type EthTx struct { } func (tx *EthTx) GasFeeCap() EthBigInt { - return tx.GasPrice + if tx.GasPrice == nil { + return *tx.MaxFeePerGas + } + return *tx.GasPrice } func (tx *EthTx) GasPremium() EthBigInt { if tx.MaxPriorityFeePerGas == nil { - return tx.GasPrice + return *tx.GasPrice } return *tx.MaxPriorityFeePerGas } diff --git a/itests/eth_transactions_test.go b/itests/eth_transactions_test.go index ff1d513e8c8..06e836e3f80 100644 --- a/itests/eth_transactions_test.go +++ b/itests/eth_transactions_test.go @@ -115,8 +115,8 @@ 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) diff --git a/node/impl/full/eth_utils.go b/node/impl/full/eth_utils.go index 514662f60d3..cbcc8dd9740 100644 --- a/node/impl/full/eth_utils.go +++ b/node/impl/full/eth_utils.go @@ -252,8 +252,7 @@ func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTx } if smsg.Signature.Type != crypto.SigTypeDelegated { - chainID := ethtypes.EthUint64(build.Eip155ChainId) - tx.ChainID = &chainID + tx.ChainID = build.Eip155ChainId } tx.BlockHash = &blkHash tx.BlockNumber = &bn @@ -536,7 +535,6 @@ func ethTxFromNativeMessage(msg *types.Message, st *state.StateTree) (ethtypes.E codec = uint64(multicodec.Cbor) } - chainId := ethtypes.EthUint64(build.Eip155ChainId) maxFeePerGas := ethtypes.EthBigInt(msg.GasFeeCap) maxPriorityFeePerGas := ethtypes.EthBigInt(msg.GasPremium) @@ -546,7 +544,7 @@ func ethTxFromNativeMessage(msg *types.Message, st *state.StateTree) (ethtypes.E From: from, Input: encodeFilecoinParamsAsABI(msg.Method, codec, msg.Params), Nonce: ethtypes.EthUint64(msg.Nonce), - ChainID: &chainId, + ChainID: build.Eip155ChainId, Value: ethtypes.EthBigInt(msg.Value), Type: ethtypes.Eip1559TxType, Gas: ethtypes.EthUint64(msg.GasLimit), @@ -658,6 +656,9 @@ func newEthTxFromMessageLookup(ctx context.Context, msgLookup *api.MsgLookup, tx tx.BlockHash = &blkHash tx.BlockNumber = &bn tx.TransactionIndex = &ti + + fmt.Printf("eth tx is: %+v", tx) + return tx, nil } From 440040e845e3e9ebff750a500e516ea71ce0edb1 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Thu, 25 Apr 2024 17:04:21 +0530 Subject: [PATCH 04/27] tests work --- itests/eth_legacy_transactions_test.go | 168 ++++++++++++++++++++++++- itests/eth_transactions_test.go | 32 +++++ 2 files changed, 199 insertions(+), 1 deletion(-) diff --git a/itests/eth_legacy_transactions_test.go b/itests/eth_legacy_transactions_test.go index 84493c0b0b3..df311fd93bf 100644 --- a/itests/eth_legacy_transactions_test.go +++ b/itests/eth_legacy_transactions_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "github.com/filecoin-project/go-state-types/manifest" "os" "testing" "time" @@ -94,7 +95,6 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { require.NoError(t, err) require.Nil(t, ethTx.MaxPriorityFeePerGas) require.Nil(t, ethTx.MaxFeePerGas) - require.Nil(t, ethTx.ChainID) require.EqualValues(t, ethAddr, ethTx.From) require.EqualValues(t, ethAddr2, *ethTx.To) @@ -109,3 +109,169 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { require.EqualValues(t, tx.S, ethTx.S) require.EqualValues(t, tx.V, ethTx.V) } + +func TestLegacyContractDeploymentValidSignature(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() + + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + // 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)) + + // verify the deployer address is a placeholder. + client.AssertActorType(ctx, deployer, manifest.PlaceholderKey) + + tx, err := deployLegacyContractTx(ctx, client, ethAddr, contract) + require.NoError(t, err) + + require.NoError(t, client.EVM().SignLegacyTransaction(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 + require.NoError(t, client.EVM().SignLegacyTransaction(tx, key.PrivateKey)) + tx.V = big.NewInt(int64(tx.V.Int.Uint64()) + 27) + hash := client.EVM().SubmitLegacyTransaction(ctx, tx) + + receipt, err := client.EVM().WaitTransaction(ctx, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + + // Success. + require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) + + // Verify that the deployer is now an account. + client.AssertActorType(ctx, deployer, manifest.EthAccountKey) + + // Verify that the nonce was incremented. + nonce, err := client.MpoolGetNonce(ctx, deployer) + require.NoError(t, err) + require.EqualValues(t, 1, nonce) + + // Verify that the deployer is now an account. + client.AssertActorType(ctx, deployer, manifest.EthAccountKey) +} + +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() + + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + // 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, client, ethAddr, contract) + require.NoError(t, err) + + client.EVM().SignLegacyTransaction(tx, key.PrivateKey) + tx.V = big.NewInt(int64(tx.V.Int.Uint64()) + 27) + hash := client.EVM().SubmitLegacyTransaction(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: ðAddr, + 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().SignLegacyTransaction(&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().SignLegacyTransaction(&invokeTx, key.PrivateKey) + invokeTx.V = big.NewInt(int64(invokeTx.V.Int.Uint64()) + 27) + hash = client.EVM().SubmitLegacyTransaction(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)) +} diff --git a/itests/eth_transactions_test.go b/itests/eth_transactions_test.go index 06e836e3f80..fe4339ecea6 100644 --- a/itests/eth_transactions_test.go +++ b/itests/eth_transactions_test.go @@ -348,6 +348,38 @@ func TestGetBlockByNumber(t *testing.T) { require.Equal(t, types.FromFil(10).Int, bal.Int) } +func deployLegacyContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr ethtypes.EthAddress, contract []byte) (*ethtypes.EthLegacyHomesteadTxArgs, error) { + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ + From: ðAddr, + 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 ðtypes.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 +} + 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: ðAddr, From 6695369d98d2813bebb092b6401af7f662bfaf02 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Thu, 25 Apr 2024 20:20:01 +0530 Subject: [PATCH 05/27] remove print statements --- chain/consensus/signatures.go | 38 ++++++++++++++------------------ chain/messagepool/messagepool.go | 2 -- chain/messagepool/provider.go | 2 -- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/chain/consensus/signatures.go b/chain/consensus/signatures.go index 75195490e29..32350259f71 100644 --- a/chain/consensus/signatures.go +++ b/chain/consensus/signatures.go @@ -1,8 +1,6 @@ package consensus import ( - "fmt" - "golang.org/x/xerrors" "github.com/filecoin-project/go-address" @@ -20,43 +18,41 @@ import ( // must be recognized by the registered verifier for the signature type. func AuthenticateMessage(msg *types.SignedMessage, signer address.Address) error { var digest []byte - typ := msg.Signature.Type - cpy := (*msg).Signature - cpy.Data = make([]byte, len(msg.Signature.Data)) - copy(cpy.Data, msg.Signature.Data) + signatureType := msg.Signature.Type + signatureCopy := msg.Signature + signatureCopy.Data = make([]byte, len(msg.Signature.Data)) + copy(signatureCopy.Data, msg.Signature.Data) - switch typ { + switch signatureType { case crypto.SigTypeDelegated: - txArgs, err := ethtypes.EthereumTransactionFromSignedEthMessage(msg) + ethTx, err := ethtypes.EthereumTransactionFromSignedEthMessage(msg) if err != nil { - return xerrors.Errorf("failed to reconstruct eth transaction: %w", err) + return xerrors.Errorf("failed to reconstruct Ethereum transaction: %w", err) } - roundTripMsg, err := txArgs.ToUnsignedMessage(msg.Message.From) + filecoinMsg, err := ethTx.ToUnsignedMessage(msg.Message.From) if err != nil { - return xerrors.Errorf("failed to reconstruct filecoin msg: %w", err) + return xerrors.Errorf("failed to reconstruct Filecoin message: %w", err) } - if !msg.Message.Equals(roundTripMsg) { - return xerrors.New("ethereum tx failed to roundtrip") + if !msg.Message.Equals(filecoinMsg) { + return xerrors.New("Ethereum transaction roundtrip mismatch") } - rlpEncodedMsg, err := txArgs.ToRlpUnsignedMsg() + 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 - cpy.Data, err = txArgs.VerifiableSignature(cpy.Data) + signatureCopy.Data, err = ethTx.VerifiableSignature(signatureCopy.Data) if err != nil { - return xerrors.Errorf("failed to get verifiable signature: %w", err) + return xerrors.Errorf("failed to verify signature: %w", err) } - fmt.Println("signature.Data: ", cpy.Data) default: digest = msg.Message.Cid().Bytes() } - fmt.Println("SIGNATURE IN AUTH IS", cpy.Data) - if err := sigs.Verify(&cpy, 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 } diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index a13416f5c11..0201be26518 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -823,8 +823,6 @@ func (mp *MessagePool) VerifyMsgSig(m *types.SignedMessage) error { // already validated, great return nil } - - fmt.Println("SIGNATURE HERE IS", m.Signature.Data) if err := consensus.AuthenticateMessage(m, m.Message.From); err != nil { return xerrors.Errorf("failed to validate signature: %w", err) } diff --git a/chain/messagepool/provider.go b/chain/messagepool/provider.go index 9567fc34009..8d1a971539a 100644 --- a/chain/messagepool/provider.go +++ b/chain/messagepool/provider.go @@ -3,7 +3,6 @@ package messagepool import ( "context" "errors" - "fmt" "time" lru "github.com/hashicorp/golang-lru/v2" @@ -119,7 +118,6 @@ func (mpp *mpoolProvider) PutMessage(ctx context.Context, m types.ChainMsg) (cid } func (mpp *mpoolProvider) PubSubPublish(k string, v []byte) error { - fmt.Println("HELLO") return mpp.ps.Publish(k, v) // nolint } From 9e63361b1ecfd468e7ec93e732d1334ba9da8d76 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Thu, 25 Apr 2024 20:26:33 +0530 Subject: [PATCH 06/27] Remove all print statements --- chain/store/messages.go | 2 -- chain/sub/incoming.go | 9 ------- chain/types/ethtypes/eth_1559_transactions.go | 24 +++++++++---------- .../eth_legacy_homestead_transactions.go | 9 ------- chain/vm/fvm.go | 7 ------ 5 files changed, 11 insertions(+), 40 deletions(-) diff --git a/chain/store/messages.go b/chain/store/messages.go index 2ed04a38e5f..c23f900d7cb 100644 --- a/chain/store/messages.go +++ b/chain/store/messages.go @@ -2,7 +2,6 @@ package store import ( "context" - "fmt" block "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" @@ -59,7 +58,6 @@ func (cs *ChainStore) GetMessage(ctx context.Context, c cid.Cid) (*types.Message msg, err = types.DecodeMessage(b) return err }) - fmt.Println("ERROR IS", err) return msg, err } diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index 6a40fe27fad..b50ddc46779 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/binary" - "fmt" "sync" "time" @@ -405,41 +404,33 @@ func (mv *MessageValidator) validateLocalMessage(ctx context.Context, msg *pubsu stats.Record(ctx, metrics.MessagePublished.M(1)) m, err := types.DecodeSignedMessage(msg.Message.GetData()) - fmt.Println("signed message is", m.Signature.Data) if err != nil { log.Warnf("failed to decode local message: %s", err) recordFailure(ctx, metrics.MessageValidationFailure, "decode") - fmt.Println("111111111") return pubsub.ValidationIgnore } if m.Size() > messagepool.MaxMessageSize { log.Warnf("local message is too large! (%dB)", m.Size()) recordFailure(ctx, metrics.MessageValidationFailure, "oversize") - fmt.Println("111111112") return pubsub.ValidationIgnore } if m.Message.To == address.Undef { log.Warn("local message has invalid destination address") recordFailure(ctx, metrics.MessageValidationFailure, "undef-addr") - fmt.Println("111111113") return pubsub.ValidationIgnore } if !m.Message.Value.LessThan(types.TotalFilecoinInt) { log.Warnf("local messages has too high value: %s", m.Message.Value) recordFailure(ctx, metrics.MessageValidationFailure, "value-too-high") - fmt.Println("111111114") return pubsub.ValidationIgnore } if err := mv.mpool.VerifyMsgSig(m); err != nil { - fmt.Println("error is BOY", err) log.Warnf("signature verification failed for local message: %s", err) recordFailure(ctx, metrics.MessageValidationFailure, "verify-sig") - fmt.Println("111111115") - return pubsub.ValidationIgnore } diff --git a/chain/types/ethtypes/eth_1559_transactions.go b/chain/types/ethtypes/eth_1559_transactions.go index b1cfdf45df4..133c2620fd6 100644 --- a/chain/types/ethtypes/eth_1559_transactions.go +++ b/chain/types/ethtypes/eth_1559_transactions.go @@ -1,8 +1,6 @@ package ethtypes import ( - "fmt" - "golang.org/x/crypto/sha3" "golang.org/x/xerrors" @@ -159,7 +157,7 @@ func (tx *Eth1559TxArgs) Signature() (*typescrypto.Signature, error) { } if len(sig) != 65 { - return nil, fmt.Errorf("signature is not 65 bytes") + return nil, xerrors.Errorf("signature is not 65 bytes") } return &typescrypto.Signature{ Type: typescrypto.SigTypeDelegated, Data: sig, @@ -237,26 +235,26 @@ func (tx *Eth1559TxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) { func (tx *Eth1559TxArgs) SetEthSignatureValues(sig typescrypto.Signature) error { if sig.Type != typescrypto.SigTypeDelegated { - return fmt.Errorf("RecoverSignature only supports Delegated signature") + return xerrors.Errorf("RecoverSignature only supports Delegated signature") } if len(sig.Data) != 65 { - return fmt.Errorf("signature should be 65 bytes long, but got %d bytes", len(sig.Data)) + 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 fmt.Errorf("cannot parse r into EthBigInt") + return xerrors.Errorf("cannot parse r into EthBigInt") } s_, err := parseBigInt(sig.Data[32:64]) if err != nil { - return fmt.Errorf("cannot parse s into EthBigInt") + return xerrors.Errorf("cannot parse s into EthBigInt") } v_, err := parseBigInt([]byte{sig.Data[64]}) if err != nil { - return fmt.Errorf("cannot parse v into EthBigInt") + return xerrors.Errorf("cannot parse v into EthBigInt") } tx.R = r_ @@ -268,7 +266,7 @@ func (tx *Eth1559TxArgs) SetEthSignatureValues(sig typescrypto.Signature) error func parseEip1559Tx(data []byte) (*Eth1559TxArgs, error) { if data[0] != 2 { - return nil, fmt.Errorf("not an EIP-1559 transaction: first byte is not 2") + return nil, xerrors.Errorf("not an EIP-1559 transaction: first byte is not 2") } d, err := DecodeRLP(data[1:]) @@ -277,11 +275,11 @@ func parseEip1559Tx(data []byte) (*Eth1559TxArgs, error) { } decoded, ok := d.([]interface{}) if !ok { - return nil, fmt.Errorf("not an EIP-1559 transaction: decoded data is not a list") + return nil, xerrors.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") + return nil, xerrors.Errorf("not an EIP-1559 transaction: should have 12 elements in the rlp list") } chainId, err := parseInt(decoded[0]) @@ -326,7 +324,7 @@ func parseEip1559Tx(data []byte) (*Eth1559TxArgs, error) { accessList, ok := decoded[8].([]interface{}) if !ok || (ok && len(accessList) != 0) { - return nil, fmt.Errorf("access list should be an empty list") + return nil, xerrors.Errorf("access list should be an empty list") } r, err := parseBigInt(decoded[10]) @@ -348,7 +346,7 @@ func parseEip1559Tx(data []byte) (*Eth1559TxArgs, error) { // 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") + return nil, xerrors.Errorf("EIP-1559 transactions only support 0 or 1 for v") } args := Eth1559TxArgs{ diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go index 2b52c08713c..678305c4ef5 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -90,8 +90,6 @@ func (tx *EthLegacyHomesteadTxArgs) VerifiableSignature(sig []byte) ([]byte, err return nil, fmt.Errorf("signature prefix should be 0x01, but got %x", sig[0]) } - fmt.Println("sig here is", sig) - sig = sig[1:] // legacy transactions have a `V` value of 27 or 28 but new transactions have a `V` value of 0 or 1 @@ -224,15 +222,11 @@ func (tx *EthLegacyHomesteadTxArgs) Sender() (address.Address, error) { return address.Undef, err } - fmt.Println("sig: ", sig.Data) - sigData, err := tx.VerifiableSignature(sig.Data) if err != nil { return address.Undef, err } - fmt.Println("sigData: ", sigData) - pubk, err := gocrypto.EcRecover(hash, sigData) if err != nil { return address.Undef, err @@ -279,8 +273,5 @@ func (tx *EthLegacyHomesteadTxArgs) SetEthSignatureValues(sig typescrypto.Signat tx.R = r_ tx.S = s_ tx.V = v_ - fmt.Println("tx.V: ", tx.V) - fmt.Println("tx.R: ", tx.R) - fmt.Println("tx.S: ", tx.S) return nil } diff --git a/chain/vm/fvm.go b/chain/vm/fvm.go index f6d528ca56d..850d7f57703 100644 --- a/chain/vm/fvm.go +++ b/chain/vm/fvm.go @@ -413,13 +413,6 @@ func NewDebugFVM(ctx context.Context, opts *VMOpts) (*FVM, error) { } func (vm *FVM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (rets *ApplyRet, err error) { - fmt.Printf("APPLYING MESSAGE:%+v", cmsg.VMMessage()) - defer func() { - if err != nil { - fmt.Printf("applying msg: %w", err) - } - }() - start := build.Clock.Now() defer atomic.AddUint64(&StatApplied, 1) vmMsg := cmsg.VMMessage() From 266826d176407e5b10fa77fa3c4a50973181a70f Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Fri, 26 Apr 2024 09:34:03 +0530 Subject: [PATCH 07/27] remove extraneous changes --- chain/messagepool/messagepool.go | 1 + chain/vm/fvm.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 0201be26518..7d55b0b16f5 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -823,6 +823,7 @@ func (mp *MessagePool) VerifyMsgSig(m *types.SignedMessage) error { // already validated, great return nil } + if err := consensus.AuthenticateMessage(m, m.Message.From); err != nil { return xerrors.Errorf("failed to validate signature: %w", err) } diff --git a/chain/vm/fvm.go b/chain/vm/fvm.go index 850d7f57703..c8c02dddd40 100644 --- a/chain/vm/fvm.go +++ b/chain/vm/fvm.go @@ -412,7 +412,7 @@ func NewDebugFVM(ctx context.Context, opts *VMOpts) (*FVM, error) { return ret, nil } -func (vm *FVM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (rets *ApplyRet, err error) { +func (vm *FVM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, error) { start := build.Clock.Now() defer atomic.AddUint64(&StatApplied, 1) vmMsg := cmsg.VMMessage() From 4d01481ebe1d4822a9627512319de1ed8faa8528 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Fri, 26 Apr 2024 12:27:06 +0530 Subject: [PATCH 08/27] cleaned up code and interface --- chain/consensus/signatures.go | 6 +- chain/messagesigner/messagesigner.go | 2 +- chain/types/ethtypes/eth_1559_transactions.go | 132 ++++---- .../ethtypes/eth_1559_transactions_test.go | 37 +-- .../eth_legacy_homestead_transactions.go | 247 ++++++++++----- .../eth_legacy_homestead_transactions_test.go | 37 +-- chain/types/ethtypes/eth_transactions.go | 281 ++++++++---------- chain/types/ethtypes/eth_transactions_test.go | 1 + itests/eth_account_abstraction_test.go | 10 +- itests/eth_hash_lookup_test.go | 2 +- itests/eth_legacy_transactions_test.go | 10 +- itests/fevm_test.go | 2 +- itests/kit/evm.go | 12 +- node/impl/full/eth.go | 6 +- node/impl/full/eth_utils.go | 16 +- node/impl/full/txhashmanager.go | 2 +- 16 files changed, 413 insertions(+), 390 deletions(-) create mode 100644 chain/types/ethtypes/eth_transactions_test.go diff --git a/chain/consensus/signatures.go b/chain/consensus/signatures.go index 32350259f71..98d8019dd8c 100644 --- a/chain/consensus/signatures.go +++ b/chain/consensus/signatures.go @@ -25,11 +25,11 @@ func AuthenticateMessage(msg *types.SignedMessage, signer address.Address) error switch signatureType { case crypto.SigTypeDelegated: - ethTx, err := ethtypes.EthereumTransactionFromSignedEthMessage(msg) + ethTx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(msg) if err != nil { return xerrors.Errorf("failed to reconstruct Ethereum transaction: %w", err) } - filecoinMsg, err := ethTx.ToUnsignedMessage(msg.Message.From) + filecoinMsg, err := ethTx.ToUnsignedFilecoinMessage(msg.Message.From) if err != nil { return xerrors.Errorf("failed to reconstruct Filecoin message: %w", err) } @@ -43,7 +43,7 @@ func AuthenticateMessage(msg *types.SignedMessage, signer address.Address) error return xerrors.Errorf("failed to encode RLP message: %w", err) } digest = rlpEncodedMsg - signatureCopy.Data, err = ethTx.VerifiableSignature(signatureCopy.Data) + signatureCopy.Data, err = ethTx.ToVerifiableSignature(signatureCopy.Data) if err != nil { return xerrors.Errorf("failed to verify signature: %w", err) } diff --git a/chain/messagesigner/messagesigner.go b/chain/messagesigner/messagesigner.go index aaef376cc4d..8a00bbc5c70 100644 --- a/chain/messagesigner/messagesigner.go +++ b/chain/messagesigner/messagesigner.go @@ -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.Eth1559TxArgsFromUnsignedEthMessage(msg) + txArgs, err := ethtypes.Eth1559TxArgsFromUnsignedFilecoinMessage(msg) if err != nil { return nil, xerrors.Errorf("failed to reconstruct eth transaction: %w", err) } diff --git a/chain/types/ethtypes/eth_1559_transactions.go b/chain/types/ethtypes/eth_1559_transactions.go index 133c2620fd6..22982bc1d54 100644 --- a/chain/types/ethtypes/eth_1559_transactions.go +++ b/chain/types/ethtypes/eth_1559_transactions.go @@ -1,6 +1,8 @@ package ethtypes import ( + "fmt" + "golang.org/x/crypto/sha3" "golang.org/x/xerrors" @@ -13,9 +15,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) -const Eip1559TxType = 2 - -var _ EthereumTransaction = (*Eth1559TxArgs)(nil) +var _ EthTransaction = (*Eth1559TxArgs)(nil) type Eth1559TxArgs struct { ChainID int `json:"chainId"` @@ -31,9 +31,11 @@ type Eth1559TxArgs struct { S big.Int `json:"s"` } -func (tx *Eth1559TxArgs) ToUnsignedMessage(from address.Address) (*types.Message, error) { - tx.ChainID = build.Eip155ChainId - mi, err := filecoin_method_info(tx.To, tx.Input) +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) } @@ -52,11 +54,6 @@ func (tx *Eth1559TxArgs) ToUnsignedMessage(from address.Address) (*types.Message }, nil } -// This function has been deduplicated and now uses the common implementation from EthLegacyHomesteadTxArgs -func (tx *Eth1559TxArgs) ToSignedMessage() (*types.SignedMessage, error) { - return toSignedMessageCommon(tx) -} - func (tx *Eth1559TxArgs) ToRlpUnsignedMsg() ([]byte, error) { packed, err := tx.packTxFields() if err != nil { @@ -67,7 +64,7 @@ func (tx *Eth1559TxArgs) ToRlpUnsignedMsg() ([]byte, error) { if err != nil { return nil, err } - return append([]byte{0x02}, encoded...), nil + return append([]byte{Eip1559TxType}, encoded...), nil } func (tx *Eth1559TxArgs) TxHash() (EthHash, error) { @@ -80,7 +77,6 @@ func (tx *Eth1559TxArgs) TxHash() (EthHash, error) { } func (tx *Eth1559TxArgs) ToRlpSignedMsg() ([]byte, error) { - tx.ChainID = build.Eip155ChainId packed1, err := tx.packTxFields() if err != nil { return nil, err @@ -98,51 +94,6 @@ func (tx *Eth1559TxArgs) ToRlpSignedMsg() ([]byte, error) { return append([]byte{0x02}, encoded...), 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 (tx *Eth1559TxArgs) Signature() (*typescrypto.Signature, error) { r := tx.R.Int.Bytes() s := tx.S.Int.Bytes() @@ -197,7 +148,7 @@ func (tx *Eth1559TxArgs) Sender() (address.Address, error) { return ea.ToFilecoinAddress() } -func (tx *Eth1559TxArgs) VerifiableSignature(sig []byte) ([]byte, error) { +func (tx *Eth1559TxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) { return sig, nil } @@ -233,7 +184,7 @@ func (tx *Eth1559TxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) { return ethTx, nil } -func (tx *Eth1559TxArgs) SetEthSignatureValues(sig typescrypto.Signature) error { +func (tx *Eth1559TxArgs) InitialiseSignature(sig typescrypto.Signature) error { if sig.Type != typescrypto.SigTypeDelegated { return xerrors.Errorf("RecoverSignature only supports Delegated signature") } @@ -264,8 +215,53 @@ func (tx *Eth1559TxArgs) SetEthSignatureValues(sig typescrypto.Signature) error 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] != 2 { + if data[0] != Eip1559TxType { return nil, xerrors.Errorf("not an EIP-1559 transaction: first byte is not 2") } @@ -365,20 +361,14 @@ func parseEip1559Tx(data []byte) (*Eth1559TxArgs, error) { return &args, nil } -func Eth1559TxArgsFromUnsignedEthMessage(msg *types.Message) (*Eth1559TxArgs, error) { - var ( - to *EthAddress - params []byte - err error - ) - +func Eth1559TxArgsFromUnsignedFilecoinMessage(msg *types.Message) (*Eth1559TxArgs, error) { if msg.Version != 0 { - return nil, xerrors.Errorf("unsupported msg version: %d", msg.Version) + return nil, fmt.Errorf("unsupported msg version: %d", msg.Version) } - params, to, err = parseMessageParamsAndReceipient(msg) + params, to, err := getEthParamsAndRecipient(msg) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get eth params and recipient: %w", err) } return &Eth1559TxArgs{ diff --git a/chain/types/ethtypes/eth_1559_transactions_test.go b/chain/types/ethtypes/eth_1559_transactions_test.go index 1cc0267ece1..69835925fef 100644 --- a/chain/types/ethtypes/eth_1559_transactions_test.go +++ b/chain/types/ethtypes/eth_1559_transactions_test.go @@ -50,7 +50,7 @@ func TestEIP1559TxArgs(t *testing.T) { from, err := txArgs.Sender() require.NoError(t, err, comment) - smsg, err := txArgs.ToSignedMessage() + smsg, err := ToSignedFilecoinMessage(txArgs) require.NoError(t, err, comment) err = sigs.Verify(&smsg.Signature, from, msgRecovered) @@ -73,23 +73,23 @@ func TestEIP1559Signatures(t *testing.T) { }{ { "0x02f8598401df5e76028301d69083086a5e835532dd808080c080a0457e33227ac7ceee2ef121755e26b872b6fb04221993f9939349bb7b0a3e1595a02d8ef379e1d2a9e30fa61c92623cc9ed72d80cf6a48cfea341cb916bcc0a81bc", - `"0x457e33227ac7ceee2ef121755e26b872b6fb04221993f9939349bb7b0a3e1595"`, - `"0x2d8ef379e1d2a9e30fa61c92623cc9ed72d80cf6a48cfea341cb916bcc0a81bc"`, - `"0x0"`, + "0x457e33227ac7ceee2ef121755e26b872b6fb04221993f9939349bb7b0a3e1595", + "0x2d8ef379e1d2a9e30fa61c92623cc9ed72d80cf6a48cfea341cb916bcc0a81bc", + "0x0", false, }, { "0x02f8598401df5e76038301d69083086a5e835532dd808080c001a012a232866dcb0671eb0ddc01fb9c01d6ef384ec892bb29691ed0d2d293052ddfa052a6ae38c6139930db21a00eee2a4caced9a6500991b823d64ec664d003bc4b1", - `"0x12a232866dcb0671eb0ddc01fb9c01d6ef384ec892bb29691ed0d2d293052ddf"`, - `"0x52a6ae38c6139930db21a00eee2a4caced9a6500991b823d64ec664d003bc4b1"`, - `"0x1"`, + "0x12a232866dcb0671eb0ddc01fb9c01d6ef384ec892bb29691ed0d2d293052ddf", + "0x52a6ae38c6139930db21a00eee2a4caced9a6500991b823d64ec664d003bc4b1", + "0x1", false, }, { "0x00", - `""`, - `""`, - `""`, + "", + "", + "", true, }, } @@ -105,20 +105,11 @@ func TestEIP1559Signatures(t *testing.T) { sig, err := tx.Signature() require.Nil(t, err) - tx.SetEthSignatureValues(*sig) + require.NoError(t, tx.InitialiseSignature(*sig)) - marshaledR, err := tx.R.MarshalJSON() - require.Nil(t, err) - - marshaledS, err := tx.S.MarshalJSON() - require.Nil(t, err) - - marshaledV, err := tx.V.MarshalJSON() - require.Nil(t, err) - - require.Equal(t, tc.ExpectedR, string(marshaledR)) - require.Equal(t, tc.ExpectedS, string(marshaledS)) - require.Equal(t, tc.ExpectedV, string(marshaledV)) + require.Equal(t, tc.ExpectedR, "0x"+tx.R.Text(16)) + require.Equal(t, tc.ExpectedS, "0x"+tx.S.Text(16)) + require.Equal(t, tc.ExpectedV, "0x"+tx.V.Text(16)) } } diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go index 678305c4ef5..0bab91b20ab 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -12,10 +12,11 @@ import ( "golang.org/x/xerrors" ) -// define a one byte prefix for legacy homestead eth transactions -const LegacyHomesteadEthTxPrefix = 0x01 +const ( + legacyHomesteadTxSignatureLen = 66 +) -var _ EthereumTransaction = (*EthLegacyHomesteadTxArgs)(nil) +var _ EthTransaction = (*EthLegacyHomesteadTxArgs)(nil) type EthLegacyHomesteadTxArgs struct { Nonce int `json:"nonce"` @@ -34,18 +35,17 @@ func (tx *EthLegacyHomesteadTxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, e 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") + return EthTx{}, fmt.Errorf("sender was not an eth account") } hash, err := tx.TxHash() if err != nil { - return EthTx{}, err + return EthTx{}, fmt.Errorf("failed to get tx hash: %w", err) } gasPrice := EthBigInt(tx.GasPrice) - ethTx := EthTx{ - ChainID: 0x00, - Type: 0x00, + ChainID: ethLegacyHomesteadTxChainID, + Type: ethLegacyHomesteadTxType, Nonce: EthUint64(tx.Nonce), Hash: hash, To: tx.To, @@ -62,8 +62,8 @@ func (tx *EthLegacyHomesteadTxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, e return ethTx, nil } -func (tx *EthLegacyHomesteadTxArgs) ToUnsignedMessage(from address.Address) (*types.Message, error) { - mi, err := filecoin_method_info(tx.To, tx.Input) +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) } @@ -82,30 +82,33 @@ func (tx *EthLegacyHomesteadTxArgs) ToUnsignedMessage(from address.Address) (*ty }, nil } -func (tx *EthLegacyHomesteadTxArgs) VerifiableSignature(sig []byte) ([]byte, error) { - if len(sig) != 66 { - return nil, fmt.Errorf("signature should be 66 bytes long, but got %d bytes", len(sig)) +func (tx *EthLegacyHomesteadTxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) { + sigCopy := make([]byte, len(sig)) + copy(sigCopy, sig) + + if len(sigCopy) != legacyHomesteadTxSignatureLen { + return nil, fmt.Errorf("signature should be %d bytes long, but got %d bytes", legacyHomesteadTxSignatureLen, len(sigCopy)) } - if sig[0] != LegacyHomesteadEthTxPrefix { - return nil, fmt.Errorf("signature prefix should be 0x01, but got %x", sig[0]) + if sigCopy[0] != LegacyHomesteadEthTxSignaturePrefix { + return nil, fmt.Errorf("expected signature prefix 0x01, but got 0x%x", sigCopy[0]) } - sig = sig[1:] + // Remove the prefix byte as it's only used for legacy transaction identification + sigCopy = sigCopy[1:] - // legacy transactions have a `V` value of 27 or 28 but new transactions have a `V` value of 0 or 1 - vValue := big.NewInt(0).SetBytes(sig[64:]) - if vValue.Uint64() != 27 && vValue.Uint64() != 28 { - return nil, fmt.Errorf("v value is not 27 or 28 for legacy transaction") - } - vValue_ := big.Sub(big.NewFromGo(vValue), big.NewInt(27)) - // we ignore the first byte of the signature data because it is the prefix for legacy txns - sig[64] = byte(vValue_.Uint64()) + // Extract the 'v' value from the signature, which is the last byte in Ethereum signatures + vValue := big.NewFromGo(big.NewInt(0).SetBytes(sigCopy[64:])) - return sig, nil -} + // Adjust 'v' value for compatibility with new transactions: 27 -> 0, 28 -> 1 + if vValue.Equals(big.NewInt(27)) { + sigCopy[64] = 0 + } else if vValue.Equals(big.NewInt(28)) { + sigCopy[64] = 1 + } else { + return nil, fmt.Errorf("invalid 'v' value: expected 27 or 28, got %d", vValue.Int64()) + } -func (tx *EthLegacyHomesteadTxArgs) ToSignedMessage() (*types.SignedMessage, error) { - return toSignedMessageCommon(tx) + return sigCopy, nil } func (tx *EthLegacyHomesteadTxArgs) ToRlpUnsignedMsg() ([]byte, error) { @@ -146,39 +149,6 @@ func (tx *EthLegacyHomesteadTxArgs) ToRlpSignedMsg() ([]byte, error) { return encoded, 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 -} - 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)) { @@ -195,12 +165,12 @@ func (tx *EthLegacyHomesteadTxArgs) Signature() (*typescrypto.Signature, error) } else { sig = append(sig, v[0]) } - if len(sig) != 65 { - return nil, fmt.Errorf("signature is not 65 bytes") - } - // pre-pend a one byte marker so nodes know that this is a legacy transaction - sig = append([]byte{LegacyHomesteadEthTxPrefix}, sig...) + sig = append([]byte{LegacyHomesteadEthTxSignaturePrefix}, sig...) + + if len(sig) != legacyHomesteadTxSignatureLen { + return nil, fmt.Errorf("signature is not %d bytes", legacyHomesteadTxSignatureLen) + } return &typescrypto.Signature{ Type: typescrypto.SigTypeDelegated, Data: sig, @@ -210,7 +180,7 @@ func (tx *EthLegacyHomesteadTxArgs) Signature() (*typescrypto.Signature, error) func (tx *EthLegacyHomesteadTxArgs) Sender() (address.Address, error) { msg, err := tx.ToRlpUnsignedMsg() if err != nil { - return address.Undef, err + return address.Undef, fmt.Errorf("failed to get rlp unsigned msg: %w", err) } hasher := sha3.NewLegacyKeccak256() @@ -219,59 +189,176 @@ func (tx *EthLegacyHomesteadTxArgs) Sender() (address.Address, error) { sig, err := tx.Signature() if err != nil { - return address.Undef, err + return address.Undef, fmt.Errorf("failed to get signature: %w", err) } - sigData, err := tx.VerifiableSignature(sig.Data) + sigData, err := tx.ToVerifiableSignature(sig.Data) if err != nil { - return address.Undef, err + return address.Undef, fmt.Errorf("failed to get verifiable signature: %w", err) } pubk, err := gocrypto.EcRecover(hash, sigData) if err != nil { - return address.Undef, err + return address.Undef, fmt.Errorf("failed to recover pubkey: %w", err) } ethAddr, err := EthAddressFromPubKey(pubk) if err != nil { - return address.Undef, err + return address.Undef, fmt.Errorf("failed to get eth address from pubkey: %w", err) } ea, err := CastEthAddress(ethAddr) if err != nil { - return address.Undef, err + return address.Undef, fmt.Errorf("failed to cast eth address: %w", err) } return ea.ToFilecoinAddress() } -func (tx *EthLegacyHomesteadTxArgs) SetEthSignatureValues(sig typescrypto.Signature) error { +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) != 66 { - return fmt.Errorf("signature should be 66 bytes long, but got %d bytes", len(sig.Data)) + if len(sig.Data) != legacyHomesteadTxSignatureLen { + return fmt.Errorf("signature should be %d bytes long, but got %d bytes", legacyHomesteadTxSignatureLen, len(sig.Data)) } - // ignore the first byte of the tx + if sig.Data[0] != LegacyHomesteadEthTxSignaturePrefix { + return fmt.Errorf("expected signature prefix 0x01, but got 0x%x", sig.Data[0]) + } + // ignore the first byte of the tx 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") + 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") + 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") + return fmt.Errorf("cannot parse v into EthBigInt: %w", err) } tx.R = r_ tx.S = s_ tx.V = v_ return nil } + +func parseLegacyHomesteadTx(data []byte) (*EthLegacyHomesteadTxArgs, 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 + } + + // legacy homestead transactions only support 27 or 28 for v + 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") + } + + return &EthLegacyHomesteadTxArgs{ + Nonce: nonce, + GasPrice: gasPrice, + GasLimit: gasLimit, + To: to, + Value: value, + Input: input, + V: v, + R: r, + S: s, + }, 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 +} diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go index 546bdd4c6c9..083991ae6a4 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go @@ -52,7 +52,7 @@ func TestEthLegacyHomesteadTxArgs(t *testing.T) { from, err := txArgs.Sender() require.NoError(t, err) - smsg, err := txArgs.ToSignedMessage() + smsg, err := ToSignedFilecoinMessage(txArgs) require.NoError(t, err) sig := smsg.Signature.Data[:] @@ -87,23 +87,23 @@ func TestLegacyHomesteadSignatures(t *testing.T) { }{ { "0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", - `"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353"`, - `"0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804"`, - `"0x1b"`, + "0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353", + "0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + "0x1b", false, }, { "0xf85f030182520794b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa07778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c", - `"0x98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a"`, - `"0x7778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c"`, - `"0x1b"`, + "0x98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a", + "0x7778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c", + "0x1b", false, }, { "0x00", - `""`, - `""`, - `""`, + "", + "", + "", true, }, } @@ -119,19 +119,10 @@ func TestLegacyHomesteadSignatures(t *testing.T) { sig, err := tx.Signature() require.Nil(t, err) - tx.SetEthSignatureValues(*sig) + require.NoError(t, tx.InitialiseSignature(*sig)) - marshaledR, err := tx.R.MarshalJSON() - require.Nil(t, err) - - marshaledS, err := tx.S.MarshalJSON() - require.Nil(t, err) - - marshaledV, err := tx.V.MarshalJSON() - require.Nil(t, err) - - require.Equal(t, tc.ExpectedR, string(marshaledR), i) - require.Equal(t, tc.ExpectedS, string(marshaledS), i) - require.Equal(t, tc.ExpectedV, string(marshaledV), i) + require.Equal(t, tc.ExpectedR, "0x"+tx.R.Text(16), i) + require.Equal(t, tc.ExpectedS, "0x"+tx.S.Text(16), i) + require.Equal(t, tc.ExpectedV, "0x"+tx.V.Text(16), i) } } diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index f712fe3ea5d..ab013e426ba 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -4,9 +4,10 @@ import ( "bytes" "encoding/binary" "fmt" - "github.com/filecoin-project/lotus/build" mathbig "math/big" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" @@ -14,25 +15,39 @@ import ( typescrypto "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/chain/types" cbg "github.com/whyrusleeping/cbor-gen" - "golang.org/x/xerrors" ) -type EthereumTransaction interface { - ToUnsignedMessage(from address.Address) (*types.Message, error) - ToSignedMessage() (*types.SignedMessage, error) +const ( + // LegacyHomesteadEthTxSignaturePrefix defines the prefix byte used to identify the signature of a legacy Homestead Ethereum transaction. + LegacyHomesteadEthTxSignaturePrefix = 0x01 + Eip1559TxType = 0x2 + + ethLegacyHomesteadTxType = 0x0 + ethLegacyHomesteadTxChainID = 0x0 +) + +// 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 { + Sender() (address.Address, error) + Signature() (*typescrypto.Signature, error) + InitialiseSignature(sig typescrypto.Signature) error + ToUnsignedFilecoinMessage(from address.Address) (*types.Message, error) ToRlpUnsignedMsg() ([]byte, error) - TxHash() (EthHash, error) ToRlpSignedMsg() ([]byte, error) - packTxFields() ([]interface{}, error) - Signature() (*typescrypto.Signature, error) - Sender() (address.Address, error) - SetEthSignatureValues(sig typescrypto.Signature) error - VerifiableSignature(sig []byte) ([]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,omitempty"` + ChainID EthUint64 `json:"chainId"` Nonce EthUint64 `json:"nonce"` Hash EthHash `json:"hash"` BlockHash *EthHash `json:"blockHash"` @@ -47,97 +62,134 @@ type EthTx struct { MaxFeePerGas *EthBigInt `json:"maxFeePerGas,omitempty"` MaxPriorityFeePerGas *EthBigInt `json:"maxPriorityFeePerGas,omitempty"` GasPrice *EthBigInt `json:"gasPrice,omitempty"` - AccessList []EthHash `json:"accessList,omitempty"` + AccessList []EthHash `json:"accessList"` V EthBigInt `json:"v"` R EthBigInt `json:"r"` S EthBigInt `json:"s"` } -func (tx *EthTx) GasFeeCap() EthBigInt { - if tx.GasPrice == nil { - return *tx.MaxFeePerGas +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 + return *tx.GasPrice, nil } -func (tx *EthTx) GasPremium() EthBigInt { - if tx.MaxPriorityFeePerGas == nil { - return *tx.GasPrice +func (tx *EthTx) GasPremium() (EthBigInt, error) { + if tx.GasPrice == nil && tx.MaxPriorityFeePerGas == nil { + return EthBigInt{}, fmt.Errorf("gas premium is not set") } - return *tx.MaxPriorityFeePerGas + + if tx.MaxPriorityFeePerGas != nil { + return *tx.MaxPriorityFeePerGas, nil + } + + return *tx.GasPrice, nil } -func EthereumTransactionFromSignedEthMessage(smsg *types.SignedMessage) (EthereumTransaction, error) { - // The from address is always an f410f address, never an ID or other address. +func EthTransactionFromSignedFilecoinMessage(smsg *types.SignedMessage) (EthTransaction, error) { + // Validate the sender's address format. if !IsEthAddress(smsg.Message.From) { - return nil, xerrors.Errorf("sender must be an eth account, was %s", smsg.Message.From) + return nil, fmt.Errorf("sender must be an eth account, was %s", smsg.Message.From) } - // Probably redundant, but we might as well check. + // Ensure the signature type is delegated. if smsg.Signature.Type != typescrypto.SigTypeDelegated { - return nil, 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) } + // Convert Filecoin address to Ethereum address. _, err := EthAddressFromFilecoinAddress(smsg.Message.From) if err != nil { - // This should be impossible as we've already asserted that we have an EthAddress - // sender... - return nil, xerrors.Errorf("sender was not an eth account") + return nil, fmt.Errorf("sender was not an eth account") } - params, to, err := parseMessageParamsAndReceipient(&smsg.Message) + // Extract Ethereum parameters and recipient from the message. + params, to, err := getEthParamsAndRecipient(&smsg.Message) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse input params and recipient: %w", err) } - msg := smsg.Message - - if msg.Version != 0 { - return nil, xerrors.Errorf("unsupported msg version: %d", msg.Version) + // Check for supported message version. + if smsg.Message.Version != 0 { + return nil, fmt.Errorf("unsupported msg version: %d", smsg.Message.Version) } + // Process based on the signature data length. switch len(smsg.Signature.Data) { - case 66: - if smsg.Signature.Data[0] != LegacyHomesteadEthTxPrefix { + case 66: // Legacy Homestead transaction + if smsg.Signature.Data[0] != LegacyHomesteadEthTxSignaturePrefix { return nil, fmt.Errorf("unsupported legacy transaction; first byte of signature is %d", smsg.Signature.Data[0]) } tx := EthLegacyHomesteadTxArgs{ - Nonce: int(msg.Nonce), + Nonce: int(smsg.Message.Nonce), To: to, - Value: msg.Value, + Value: smsg.Message.Value, Input: params, - GasPrice: msg.GasFeeCap, - GasLimit: int(msg.GasLimit), + GasPrice: smsg.Message.GasFeeCap, + GasLimit: int(smsg.Message.GasLimit), + } + if err := tx.InitialiseSignature(smsg.Signature); err != nil { + return nil, fmt.Errorf("failed to initialise signature: %w", err) } - tx.SetEthSignatureValues(smsg.Signature) - fmt.Println("FINISHED SETTING ETH SIGNATURE VALUES") return &tx, nil - case 65: + case 65: // EIP-1559 transaction tx := Eth1559TxArgs{ ChainID: build.Eip155ChainId, - Nonce: int(msg.Nonce), + Nonce: int(smsg.Message.Nonce), To: to, - Value: msg.Value, + Value: smsg.Message.Value, Input: params, - MaxFeePerGas: msg.GasFeeCap, - MaxPriorityFeePerGas: msg.GasPremium, - GasLimit: int(msg.GasLimit), + 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) } - tx.SetEthSignatureValues(smsg.Signature) return &tx, nil default: return nil, fmt.Errorf("unsupported signature length: %d", len(smsg.Signature.Data)) } } -func ParseEthTx(data []byte) (EthereumTransaction, error) { +func ToSignedFilecoinMessage(tx EthTransaction) (*types.SignedMessage, error) { + from, err := tx.Sender() + if err != nil { + return nil, fmt.Errorf("failed to calculate sender: %w", err) + } + + unsignedMsg, err := tx.ToUnsignedFilecoinMessage(from) + if err != nil { + return nil, fmt.Errorf("failed to convert to unsigned msg: %w", err) + } + + siggy, err := tx.Signature() + if err != nil { + return nil, fmt.Errorf("failed to calculate signature: %w", err) + } + + return &types.SignedMessage{ + Message: *unsignedMsg, + Signature: *siggy, + }, nil +} + +func ParseEthTransaction(data []byte) (EthTransaction, error) { if len(data) == 0 { return nil, fmt.Errorf("empty data") } if data[0] > 0x7f { - return parseLegacyHomesteadTx(data) + tx, err := parseLegacyHomesteadTx(data) + if err != nil { + return nil, fmt.Errorf("failed to parse legacy homestead transaction: %w", err) + } + return tx, nil } if data[0] == 1 { @@ -159,28 +211,30 @@ type methodInfo struct { params []byte } -func filecoin_method_info(recipient *EthAddress, input []byte) (*methodInfo, error) { - var err error +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, xerrors.Errorf("failed to write input args: %w", err) + 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 - // nil indicates the EAM, only CreateExternal is allowed + 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, xerrors.Errorf("failed to convert To into filecoin addr: %w", err) + return nil, fmt.Errorf("failed to convert EthAddress to Filecoin address: %w", err) } } @@ -309,120 +363,19 @@ func parseEthAddr(v interface{}) (*EthAddress, error) { return &addr, nil } -func toSignedMessageCommon(tx EthereumTransaction) (*types.SignedMessage, error) { - from, err := tx.Sender() - if err != nil { - return nil, xerrors.Errorf("failed to calculate sender: %w", err) - } - - unsignedMsg, err := tx.ToUnsignedMessage(from) - if err != nil { - return nil, xerrors.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 &types.SignedMessage{ - Message: *unsignedMsg, - Signature: *siggy, - }, nil -} - -func parseLegacyHomesteadTx(data []byte) (*EthLegacyHomesteadTxArgs, 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 - } - - return &EthLegacyHomesteadTxArgs{ - Nonce: nonce, - GasPrice: gasPrice, - GasLimit: gasLimit, - To: to, - Value: value, - Input: input, - V: v, - R: r, - S: s, - }, nil -} - -func parseMessageParamsAndReceipient(msg *types.Message) ([]byte, *EthAddress, error) { - var params []byte - var to *EthAddress - +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, xerrors.Errorf("failed to read params byte array: %w", err) + return nil, nil, fmt.Errorf("failed to read params byte array: %w", err) } if paramsReader.Len() != 0 { - return nil, nil, xerrors.Errorf("extra data found in params") + return nil, nil, fmt.Errorf("extra data found in params") } if len(params) == 0 { - return nil, nil, xerrors.Errorf("non-empty params encode empty byte array") + return nil, nil, fmt.Errorf("non-empty params encode empty byte array") } } @@ -438,7 +391,7 @@ func parseMessageParamsAndReceipient(msg *types.Message) ([]byte, *EthAddress, e to = &addr } else { return nil, nil, - xerrors.Errorf("invalid methodnum %d: only allowed method is InvokeContract(%d)", + fmt.Errorf("invalid methodnum %d: only allowed method is InvokeContract(%d)", msg.Method, builtintypes.MethodsEVM.InvokeContract) } diff --git a/chain/types/ethtypes/eth_transactions_test.go b/chain/types/ethtypes/eth_transactions_test.go new file mode 100644 index 00000000000..cc8e1cb2040 --- /dev/null +++ b/chain/types/ethtypes/eth_transactions_test.go @@ -0,0 +1 @@ +package ethtypes diff --git a/itests/eth_account_abstraction_test.go b/itests/eth_account_abstraction_test.go index 913902d8eaf..58e122e1603 100644 --- a/itests/eth_account_abstraction_test.go +++ b/itests/eth_account_abstraction_test.go @@ -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.Eth1559TxArgsFromUnsignedEthMessage(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.Eth1559TxArgsFromUnsignedEthMessage(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.Eth1559TxArgsFromUnsignedEthMessage(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.Eth1559TxArgsFromUnsignedEthMessage(msgFromPlaceholder) + txArgs, err = ethtypes.Eth1559TxArgsFromUnsignedFilecoinMessage(msgFromPlaceholder) require.NoError(t, err) digest, err = txArgs.ToRlpUnsignedMsg() @@ -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) diff --git a/itests/eth_hash_lookup_test.go b/itests/eth_hash_lookup_test.go index 1cb2cae892e..bb73bddac53 100644 --- a/itests/eth_hash_lookup_test.go +++ b/itests/eth_hash_lookup_test.go @@ -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() diff --git a/itests/eth_legacy_transactions_test.go b/itests/eth_legacy_transactions_test.go index df311fd93bf..885464d25d5 100644 --- a/itests/eth_legacy_transactions_test.go +++ b/itests/eth_legacy_transactions_test.go @@ -62,7 +62,7 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { S: big.Zero(), } - require.NoError(t, client.EVM().SignLegacyTransaction(&tx, key.PrivateKey)) + client.EVM().SignLegacyTransaction(&tx, key.PrivateKey) // Mangle signature tx.V.Int.Xor(tx.V.Int, big.NewInt(1).Int) @@ -73,7 +73,7 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { require.Error(t, err) // Submit transaction with valid signature - require.NoError(t, client.EVM().SignLegacyTransaction(&tx, key.PrivateKey)) + client.EVM().SignLegacyTransaction(&tx, key.PrivateKey) tx.V = big.NewInt(int64(tx.V.Int.Uint64()) + 27) fmt.Println("V: ", tx.V) fmt.Println("WILL SUBMIT VALID NOW OK") @@ -104,7 +104,7 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { require.EqualValues(t, 0, ethTx.Type) 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.GasPrice, *ethTx.GasPrice) require.EqualValues(t, tx.R, ethTx.R) require.EqualValues(t, tx.S, ethTx.S) require.EqualValues(t, tx.V, ethTx.V) @@ -138,7 +138,7 @@ func TestLegacyContractDeploymentValidSignature(t *testing.T) { tx, err := deployLegacyContractTx(ctx, client, ethAddr, contract) require.NoError(t, err) - require.NoError(t, client.EVM().SignLegacyTransaction(tx, key.PrivateKey)) + client.EVM().SignLegacyTransaction(tx, key.PrivateKey) // Mangle signature tx.V.Int.Xor(tx.V.Int, big.NewInt(1).Int) @@ -149,7 +149,7 @@ func TestLegacyContractDeploymentValidSignature(t *testing.T) { require.Error(t, err) // Submit transaction with valid signature - require.NoError(t, client.EVM().SignLegacyTransaction(tx, key.PrivateKey)) + client.EVM().SignLegacyTransaction(tx, key.PrivateKey) tx.V = big.NewInt(int64(tx.V.Int.Uint64()) + 27) hash := client.EVM().SubmitLegacyTransaction(ctx, tx) diff --git a/itests/fevm_test.go b/itests/fevm_test.go index 9b4c97b6f57..864d4d1c88c 100644 --- a/itests/fevm_test.go +++ b/itests/fevm_test.go @@ -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) diff --git a/itests/kit/evm.go b/itests/kit/evm.go index e326582e2b8..ae7e8b68206 100644 --- a/itests/kit/evm.go +++ b/itests/kit/evm.go @@ -45,18 +45,17 @@ func (f *TestFullNode) EVM() *EVM { } // SignTransaction signs an Ethereum transaction in place with the supplied private key. -func (e *EVM) SignLegacyTransaction(tx *ethtypes.EthLegacyHomesteadTxArgs, privKey []byte) error { +func (e *EVM) SignLegacyTransaction(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.LegacyHomesteadEthTxPrefix}, signature.Data...) + signature.Data = append([]byte{ethtypes.LegacyHomesteadEthTxSignaturePrefix}, signature.Data...) - fmt.Println("doing now") - - return tx.SetEthSignatureValues(*signature) + err = tx.InitialiseSignature(*signature) + require.NoError(e.t, err) } func (e *EVM) SubmitLegacyTransaction(ctx context.Context, tx *ethtypes.EthLegacyHomesteadTxArgs) ethtypes.EthHash { @@ -242,7 +241,8 @@ func (e *EVM) SignTransaction(tx *ethtypes.Eth1559TxArgs, privKey []byte) { signature, err := sigs.Sign(crypto.SigTypeDelegated, privKey, preimage) require.NoError(e.t, err) - tx.SetEthSignatureValues(*signature) + err = tx.InitialiseSignature(*signature) + require.NoError(e.t, err) } // SubmitTransaction submits the transaction via the Eth endpoint. diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 2bc9b4230f0..5dff2eb550f 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -294,7 +294,7 @@ 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. - ethtx, err := ethtypes.EthereumTransactionFromSignedEthMessage(p) + ethtx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(p) if err != nil { return nil, fmt.Errorf("could not convert Filecoin message into tx: %w", err) } @@ -822,12 +822,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.ParseEthTx(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 } diff --git a/node/impl/full/eth_utils.go b/node/impl/full/eth_utils.go index cbcc8dd9740..816a8614c6a 100644 --- a/node/impl/full/eth_utils.go +++ b/node/impl/full/eth_utils.go @@ -450,7 +450,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.EthereumTransactionFromSignedEthMessage(smsg) + tx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(smsg) if err != nil { return ethtypes.EthHash{}, xerrors.Errorf("failed to convert from signed message: %w", err) } @@ -469,7 +469,7 @@ func newEthTxFromSignedMessage(smsg *types.SignedMessage, st *state.StateTree) ( // This is an eth tx if smsg.Signature.Type == crypto.SigTypeDelegated { - ethTx, err := ethtypes.EthereumTransactionFromSignedEthMessage(smsg) + ethTx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(smsg) if err != nil { return ethtypes.EthTx{}, xerrors.Errorf("failed to convert from signed message: %w", err) } @@ -721,7 +721,17 @@ 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.GasFeeCap()), big.Int(tx.GasPremium()), 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() diff --git a/node/impl/full/txhashmanager.go b/node/impl/full/txhashmanager.go index 2ed636469b8..4f5212fb66b 100644 --- a/node/impl/full/txhashmanager.go +++ b/node/impl/full/txhashmanager.go @@ -86,7 +86,7 @@ func (m *EthTxHashManager) ProcessSignedMessage(ctx context.Context, msg *types. return } - ethTx, err := ethtypes.EthereumTransactionFromSignedEthMessage(msg) + ethTx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(msg) if err != nil { log.Errorf("error converting filecoin message to eth tx: %s", err) return From 18fd140e8d8bebd7e90949eb6231fdecea5538ff Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Fri, 26 Apr 2024 12:44:17 +0530 Subject: [PATCH 09/27] run make jen --- .circleci/config.yml | 6 ++++ build/openrpc/full.json | 20 +++++++++++ build/openrpc/gateway.json | 10 ++++++ .../eth_legacy_homestead_transactions.go | 6 ++-- .../eth_legacy_homestead_transactions_test.go | 5 +-- chain/types/ethtypes/eth_transactions.go | 5 +-- cmd/lotus-shed/eth.go | 2 +- cmd/lotus-shed/indexes.go | 8 ++--- documentation/en/api-v1-unstable-methods.md | 4 +++ go.mod | 14 ++++---- go.sum | 14 ++++++++ itests/eth_legacy_transactions_test.go | 34 ++++++++++++++++++- itests/eth_transactions_test.go | 32 ----------------- 13 files changed, 109 insertions(+), 51 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index be70019a3be..17ecba33a5a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -747,6 +747,12 @@ workflows: - build suite: itest-eth_hash_lookup target: "./itests/eth_hash_lookup_test.go" + - test: + name: test-itest-eth_legacy_transactions + requires: + - build + suite: itest-eth_legacy_transactions + target: "./itests/eth_legacy_transactions_test.go" - test: name: test-itest-eth_transactions requires: diff --git a/build/openrpc/full.json b/build/openrpc/full.json index ae12bc302c2..08eeef97fd8 100644 --- a/build/openrpc/full.json +++ b/build/openrpc/full.json @@ -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", diff --git a/build/openrpc/gateway.json b/build/openrpc/gateway.json index faa2c8536c7..2edd17926c1 100644 --- a/build/openrpc/gateway.json +++ b/build/openrpc/gateway.json @@ -3077,6 +3077,7 @@ "gas": "0x5", "maxFeePerGas": "0x0", "maxPriorityFeePerGas": "0x0", + "gasPrice": "0x0", "accessList": [ "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" ], @@ -3132,6 +3133,10 @@ "title": "number", "type": "number" }, + "gasPrice": { + "additionalProperties": false, + "type": "object" + }, "hash": { "items": { "description": "Number is a number", @@ -3281,6 +3286,7 @@ "gas": "0x5", "maxFeePerGas": "0x0", "maxPriorityFeePerGas": "0x0", + "gasPrice": "0x0", "accessList": [ "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" ], @@ -3336,6 +3342,10 @@ "title": "number", "type": "number" }, + "gasPrice": { + "additionalProperties": false, + "type": "object" + }, "hash": { "items": { "description": "Number is a number", diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go index 0bab91b20ab..e7e4d9346f6 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -3,13 +3,15 @@ package ethtypes import ( "fmt" + "golang.org/x/crypto/sha3" + "golang.org/x/xerrors" + "github.com/filecoin-project/go-address" gocrypto "github.com/filecoin-project/go-crypto" "github.com/filecoin-project/go-state-types/big" typescrypto "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/lotus/chain/types" - "golang.org/x/crypto/sha3" - "golang.org/x/xerrors" ) const ( diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go index 083991ae6a4..3b91ba97c54 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go @@ -4,10 +4,11 @@ import ( "encoding/hex" "testing" + "github.com/stretchr/testify/require" + "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/lotus/lib/sigs" - "github.com/stretchr/testify/require" + "github.com/filecoin-project/lotus/lib/sigs" ) func TestEthLegacyHomesteadTxArgs(t *testing.T) { diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index ab013e426ba..5b6da312e11 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -6,15 +6,16 @@ import ( "fmt" mathbig "math/big" - "github.com/filecoin-project/lotus/build" + cbg "github.com/whyrusleeping/cbor-gen" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" builtintypes "github.com/filecoin-project/go-state-types/builtin" typescrypto "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" - cbg "github.com/whyrusleeping/cbor-gen" ) const ( diff --git a/cmd/lotus-shed/eth.go b/cmd/lotus-shed/eth.go index 359294adbb8..46bd1b43c29 100644 --- a/cmd/lotus-shed/eth.go +++ b/cmd/lotus-shed/eth.go @@ -88,7 +88,7 @@ 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) } diff --git a/cmd/lotus-shed/indexes.go b/cmd/lotus-shed/indexes.go index 12ebe0082b5..fb33ae883fb 100644 --- a/cmd/lotus-shed/indexes.go +++ b/cmd/lotus-shed/indexes.go @@ -581,17 +581,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) } @@ -602,7 +602,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 diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index dee290b343e..e04850012e7 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -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" ], diff --git a/go.mod b/go.mod index 674148ec3db..52af5d32e40 100644 --- a/go.mod +++ b/go.mod @@ -160,15 +160,15 @@ require ( go.uber.org/fx v1.20.1 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.20.0 + golang.org/x/crypto v0.22.0 golang.org/x/exp v0.0.0-20240213143201-ec583247a57a - golang.org/x/net v0.21.0 - golang.org/x/sync v0.6.0 - golang.org/x/sys v0.17.0 - golang.org/x/term v0.17.0 + golang.org/x/net v0.24.0 + golang.org/x/sync v0.7.0 + golang.org/x/sys v0.19.0 + golang.org/x/term v0.19.0 golang.org/x/text v0.14.0 golang.org/x/time v0.5.0 - golang.org/x/tools v0.18.0 + golang.org/x/tools v0.20.0 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 gopkg.in/cheggaaa/pb.v1 v1.0.28 gotest.tools v2.2.0+incompatible @@ -327,7 +327,7 @@ require ( go.uber.org/dig v1.17.1 // indirect go.uber.org/mock v0.4.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect - golang.org/x/mod v0.15.0 // indirect + golang.org/x/mod v0.17.0 // indirect gonum.org/v1/gonum v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect google.golang.org/grpc v1.60.1 // indirect diff --git a/go.sum b/go.sum index 4e6ce0dec0f..327a2922a95 100644 --- a/go.sum +++ b/go.sum @@ -1842,6 +1842,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1885,6 +1887,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1950,6 +1954,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1976,6 +1982,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2077,6 +2085,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2087,6 +2097,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2168,6 +2180,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/itests/eth_legacy_transactions_test.go b/itests/eth_legacy_transactions_test.go index 885464d25d5..1e66cdefe54 100644 --- a/itests/eth_legacy_transactions_test.go +++ b/itests/eth_legacy_transactions_test.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/filecoin-project/go-state-types/manifest" "os" "testing" "time" @@ -13,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" @@ -275,3 +275,35 @@ func TestLegacyContractInvocation(t *testing.T) { effectiveGasPrice := big.Div(invokResult.GasCost.TotalCost, invokResult.GasCost.GasUsed) require.EqualValues(t, effectiveGasPrice, big.Int(receipt.EffectiveGasPrice)) } + +func deployLegacyContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr ethtypes.EthAddress, contract []byte) (*ethtypes.EthLegacyHomesteadTxArgs, error) { + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ + From: ðAddr, + 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 ðtypes.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 +} diff --git a/itests/eth_transactions_test.go b/itests/eth_transactions_test.go index fe4339ecea6..06e836e3f80 100644 --- a/itests/eth_transactions_test.go +++ b/itests/eth_transactions_test.go @@ -348,38 +348,6 @@ func TestGetBlockByNumber(t *testing.T) { require.Equal(t, types.FromFil(10).Int, bal.Int) } -func deployLegacyContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr ethtypes.EthAddress, contract []byte) (*ethtypes.EthLegacyHomesteadTxArgs, error) { - gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ - From: ðAddr, - 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 ðtypes.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 -} - 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: ðAddr, From ad2ec8f6e097e4c7d2680290a575ec0df39ece3f Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Fri, 26 Apr 2024 12:49:41 +0530 Subject: [PATCH 10/27] dont duplicate signature --- .../eth_legacy_homestead_transactions.go | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go index e7e4d9346f6..40a32baac9b 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -85,32 +85,29 @@ func (tx *EthLegacyHomesteadTxArgs) ToUnsignedFilecoinMessage(from address.Addre } func (tx *EthLegacyHomesteadTxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) { - sigCopy := make([]byte, len(sig)) - copy(sigCopy, sig) - - if len(sigCopy) != legacyHomesteadTxSignatureLen { - return nil, fmt.Errorf("signature should be %d bytes long, but got %d bytes", legacyHomesteadTxSignatureLen, len(sigCopy)) + if len(sig) != legacyHomesteadTxSignatureLen { + return nil, fmt.Errorf("signature should be %d bytes long, but got %d bytes", legacyHomesteadTxSignatureLen, len(sig)) } - if sigCopy[0] != LegacyHomesteadEthTxSignaturePrefix { - return nil, fmt.Errorf("expected signature prefix 0x01, but got 0x%x", sigCopy[0]) + if sig[0] != LegacyHomesteadEthTxSignaturePrefix { + return nil, fmt.Errorf("expected signature prefix 0x01, but got 0x%x", sig[0]) } // Remove the prefix byte as it's only used for legacy transaction identification - sigCopy = sigCopy[1:] + 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(sigCopy[64:])) + 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)) { - sigCopy[64] = 0 + sig[64] = 0 } else if vValue.Equals(big.NewInt(28)) { - sigCopy[64] = 1 + sig[64] = 1 } else { return nil, fmt.Errorf("invalid 'v' value: expected 27 or 28, got %d", vValue.Int64()) } - return sigCopy, nil + return sig, nil } func (tx *EthLegacyHomesteadTxArgs) ToRlpUnsignedMsg() ([]byte, error) { From 07d248f75aa542cf9a61cfad67f5e4e29abaf06b Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Fri, 26 Apr 2024 13:26:37 +0530 Subject: [PATCH 11/27] go mod tidy and remove prints --- go.sum | 14 -------------- node/impl/full/eth.go | 11 +++-------- node/impl/full/eth_utils.go | 7 +++---- node/impl/full/txhashmanager.go | 4 +--- 4 files changed, 7 insertions(+), 29 deletions(-) diff --git a/go.sum b/go.sum index 327a2922a95..7f447d55f05 100644 --- a/go.sum +++ b/go.sum @@ -1840,8 +1840,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= -golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1885,8 +1883,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1952,8 +1948,6 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1980,8 +1974,6 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2083,8 +2075,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -2095,8 +2085,6 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2178,8 +2166,6 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 5dff2eb550f..3f5bfb6af86 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -298,13 +298,12 @@ func (a *EthModule) EthGetTransactionByHashLimited(ctx context.Context, txHash * if err != nil { return nil, fmt.Errorf("could not convert Filecoin message into tx: %w", err) } + tx, err := ethtx.ToEthTx(p) if err != nil { - return nil, fmt.Errorf("could not convert Filecoin message into tx: %w", err) - } - 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 } } @@ -409,12 +408,10 @@ func (a *EthModule) EthGetTransactionReceiptLimited(ctx context.Context, txHash // This isn't an eth transaction we have the mapping for, so let's look it up as a filecoin message if c == cid.Undef { - fmt.Println("DOING THIS") c = txHash.ToCid() } msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, c, limit, true) - fmt.Printf("msgLookup: %+v", msgLookup) if err != nil { return nil, xerrors.Errorf("failed to lookup Eth Txn %s as %s: %w", txHash, c, err) } @@ -832,8 +829,6 @@ func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.Et return ethtypes.EmptyEthHash, err } - fmt.Println("sumitting to mpool", smsg.Signature.Data) - _, err = a.MpoolAPI.MpoolPush(ctx, smsg) if err != nil { return ethtypes.EmptyEthHash, err diff --git a/node/impl/full/eth_utils.go b/node/impl/full/eth_utils.go index 816a8614c6a..ef1e26c6f92 100644 --- a/node/impl/full/eth_utils.go +++ b/node/impl/full/eth_utils.go @@ -251,6 +251,8 @@ func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTx return ethtypes.EthBlock{}, xerrors.Errorf("failed to convert msg to ethTx: %w", err) } + // Don't override the chainID if this is not an EIP-1559 transaction. + // The chainID has already been set to 0 for leagcy ETH transactions. if smsg.Signature.Type != crypto.SigTypeDelegated { tx.ChainID = build.Eip155ChainId } @@ -544,7 +546,7 @@ func ethTxFromNativeMessage(msg *types.Message, st *state.StateTree) (ethtypes.E From: from, Input: encodeFilecoinParamsAsABI(msg.Method, codec, msg.Params), Nonce: ethtypes.EthUint64(msg.Nonce), - ChainID: build.Eip155ChainId, + ChainID: ethtypes.EthUint64(build.Eip155ChainId), Value: ethtypes.EthBigInt(msg.Value), Type: ethtypes.Eip1559TxType, Gas: ethtypes.EthUint64(msg.GasLimit), @@ -657,8 +659,6 @@ func newEthTxFromMessageLookup(ctx context.Context, msgLookup *api.MsgLookup, tx tx.BlockNumber = &bn tx.TransactionIndex = &ti - fmt.Printf("eth tx is: %+v", tx) - return tx, nil } @@ -743,7 +743,6 @@ func newEthTxReceipt(ctx context.Context, tx ethtypes.EthTx, lookup *api.MsgLook if receipt.To == nil && lookup.Receipt.ExitCode.IsSuccess() { // Create and Create2 return the same things. var ret eam.CreateExternalReturn - fmt.Println("RETURN IS", lookup.Receipt.Return) if err := ret.UnmarshalCBOR(bytes.NewReader(lookup.Receipt.Return)); err != nil { return api.EthTxReceipt{}, xerrors.Errorf("failed to parse contract creation result: %w", err) } diff --git a/node/impl/full/txhashmanager.go b/node/impl/full/txhashmanager.go index 4f5212fb66b..ba5de0fcb40 100644 --- a/node/impl/full/txhashmanager.go +++ b/node/impl/full/txhashmanager.go @@ -2,7 +2,6 @@ package full import ( "context" - "fmt" "time" "github.com/filecoin-project/go-state-types/abi" @@ -91,14 +90,13 @@ func (m *EthTxHashManager) ProcessSignedMessage(ctx context.Context, msg *types. log.Errorf("error converting filecoin message to eth tx: %s", err) return } + txHash, err := ethTx.TxHash() - fmt.Println("UPSERTING TO", msg.Message.To) if err != nil { log.Errorf("error hashing transaction: %s", err) return } - fmt.Println("UPSERTING", txHash) err = m.TransactionHashLookup.UpsertHash(txHash, msg.Cid()) if err != nil { log.Errorf("error inserting tx mapping to db: %s", err) From 2e66b808b4957b6c43a24307a743559af77774da Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Fri, 26 Apr 2024 13:46:39 +0530 Subject: [PATCH 12/27] clean up tests --- .../eth_legacy_homestead_transactions.go | 5 +++++ itests/eth_legacy_transactions_test.go | 17 +++++------------ itests/eth_transactions_test.go | 2 -- itests/kit/evm.go | 18 ++++-------------- 4 files changed, 14 insertions(+), 28 deletions(-) diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go index 40a32baac9b..c90692cb3a7 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -242,6 +242,11 @@ func (tx *EthLegacyHomesteadTxArgs) InitialiseSignature(sig typescrypto.Signatur 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_ diff --git a/itests/eth_legacy_transactions_test.go b/itests/eth_legacy_transactions_test.go index 1e66cdefe54..141a8658bfb 100644 --- a/itests/eth_legacy_transactions_test.go +++ b/itests/eth_legacy_transactions_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/hex" "encoding/json" - "fmt" "os" "testing" "time" @@ -38,7 +37,6 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { // create a new Ethereum account key, ethAddr, deployer := client.EVM().NewAccount() _, ethAddr2, _ := client.EVM().NewAccount() - fmt.Println("Deployer address: ", deployer) kit.SendFunds(ctx, t, client, deployer, types.FromFil(1000)) @@ -74,11 +72,8 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { // Submit transaction with valid signature client.EVM().SignLegacyTransaction(&tx, key.PrivateKey) - tx.V = big.NewInt(int64(tx.V.Int.Uint64()) + 27) - fmt.Println("V: ", tx.V) - fmt.Println("WILL SUBMIT VALID NOW OK") - hash := client.EVM().SubmitLegacyTransaction(ctx, &tx) + hash := client.EVM().SubmitTransaction(ctx, &tx) receipt, err := client.EVM().WaitTransaction(ctx, hash) require.NoError(t, err) @@ -102,6 +97,7 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { 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) @@ -150,8 +146,7 @@ func TestLegacyContractDeploymentValidSignature(t *testing.T) { // Submit transaction with valid signature client.EVM().SignLegacyTransaction(tx, key.PrivateKey) - tx.V = big.NewInt(int64(tx.V.Int.Uint64()) + 27) - hash := client.EVM().SubmitLegacyTransaction(ctx, tx) + hash := client.EVM().SubmitTransaction(ctx, tx) receipt, err := client.EVM().WaitTransaction(ctx, hash) require.NoError(t, err) @@ -198,8 +193,7 @@ func TestLegacyContractInvocation(t *testing.T) { require.NoError(t, err) client.EVM().SignLegacyTransaction(tx, key.PrivateKey) - tx.V = big.NewInt(int64(tx.V.Int.Uint64()) + 27) - hash := client.EVM().SubmitLegacyTransaction(ctx, tx) + hash := client.EVM().SubmitTransaction(ctx, tx) receipt, err := client.EVM().WaitTransaction(ctx, hash) require.NoError(t, err) @@ -254,8 +248,7 @@ func TestLegacyContractInvocation(t *testing.T) { // Submit transaction with valid signature client.EVM().SignLegacyTransaction(&invokeTx, key.PrivateKey) - invokeTx.V = big.NewInt(int64(invokeTx.V.Int.Uint64()) + 27) - hash = client.EVM().SubmitLegacyTransaction(ctx, &invokeTx) + hash = client.EVM().SubmitTransaction(ctx, &invokeTx) receipt, err = client.EVM().WaitTransaction(ctx, hash) require.NoError(t, err) diff --git a/itests/eth_transactions_test.go b/itests/eth_transactions_test.go index 06e836e3f80..3d7721a865b 100644 --- a/itests/eth_transactions_test.go +++ b/itests/eth_transactions_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/hex" "encoding/json" - "fmt" "os" "testing" "time" @@ -85,7 +84,6 @@ func TestValueTransferValidSignature(t *testing.T) { signed, err := tx.ToRlpSignedMsg() require.NoError(t, err) // Submit transaction with bad signature - fmt.Println("SENDING TO", *tx.To) _, err = client.EVM().EthSendRawTransaction(ctx, signed) require.Error(t, err) diff --git a/itests/kit/evm.go b/itests/kit/evm.go index ae7e8b68206..e85c2b09c50 100644 --- a/itests/kit/evm.go +++ b/itests/kit/evm.go @@ -44,7 +44,7 @@ func (f *TestFullNode) EVM() *EVM { return &EVM{f} } -// SignTransaction signs an Ethereum transaction in place with the supplied private key. +// SignLegacyTransaction signs a legacy Homstead Ethereum transaction in place with the supplied private key. func (e *EVM) SignLegacyTransaction(tx *ethtypes.EthLegacyHomesteadTxArgs, privKey []byte) { preimage, err := tx.ToRlpUnsignedMsg() require.NoError(e.t, err) @@ -52,23 +52,14 @@ func (e *EVM) SignLegacyTransaction(tx *ethtypes.EthLegacyHomesteadTxArgs, privK // sign the RLP payload signature, err := sigs.Sign(crypto.SigTypeDelegated, privKey, preimage) require.NoError(e.t, err) + signature.Data = append([]byte{ethtypes.LegacyHomesteadEthTxSignaturePrefix}, signature.Data...) + signature.Data[len(signature.Data)-1] += 27 err = tx.InitialiseSignature(*signature) require.NoError(e.t, err) } -func (e *EVM) SubmitLegacyTransaction(ctx context.Context, tx *ethtypes.EthLegacyHomesteadTxArgs) ethtypes.EthHash { - signed, err := tx.ToRlpSignedMsg() - require.NoError(e.t, err) - - fmt.Println("submitting now", tx.V) - hash, err := e.EthSendRawTransaction(ctx, signed) - require.NoError(e.t, err) - - return hash -} - func (e *EVM) DeployContractWithValue(ctx context.Context, sender address.Address, bytecode []byte, value big.Int) eam.CreateReturn { require := require.New(e.t) @@ -246,7 +237,7 @@ func (e *EVM) SignTransaction(tx *ethtypes.Eth1559TxArgs, privKey []byte) { } // SubmitTransaction submits the transaction via the Eth endpoint. -func (e *EVM) SubmitTransaction(ctx context.Context, tx *ethtypes.Eth1559TxArgs) ethtypes.EthHash { +func (e *EVM) SubmitTransaction(ctx context.Context, tx ethtypes.EthTransaction) ethtypes.EthHash { signed, err := tx.ToRlpSignedMsg() require.NoError(e.t, err) @@ -314,7 +305,6 @@ func (e *EVM) InvokeContractByFuncNameExpectExit(ctx context.Context, fromAddr a } func (e *EVM) WaitTransaction(ctx context.Context, hash ethtypes.EthHash) (*api.EthTxReceipt, error) { - fmt.Println("\n looking up hash: ", hash) if mcid, err := e.EthGetMessageCidByTransactionHash(ctx, &hash); err != nil { return nil, err } else if mcid == nil { From 4be21ffc77b9645db9e1c7b1dacee1d2b1cc5048 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Fri, 26 Apr 2024 20:27:08 +0530 Subject: [PATCH 13/27] test for conversion --- chain/types/ethtypes/eth_transactions.go | 4 + chain/types/ethtypes/eth_transactions_test.go | 166 ++++++++++++++++++ go.mod | 1 + 3 files changed, 171 insertions(+) diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index 5b6da312e11..82f6e482566 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -3,6 +3,7 @@ package ethtypes import ( "bytes" "encoding/binary" + "errors" "fmt" mathbig "math/big" @@ -92,6 +93,9 @@ func (tx *EthTx) GasPremium() (EthBigInt, error) { } func EthTransactionFromSignedFilecoinMessage(smsg *types.SignedMessage) (EthTransaction, error) { + if smsg == nil { + return nil, errors.New("signed message is nil") + } // Validate the sender's address format. if !IsEthAddress(smsg.Message.From) { return nil, fmt.Errorf("sender must be an eth account, was %s", smsg.Message.From) diff --git a/chain/types/ethtypes/eth_transactions_test.go b/chain/types/ethtypes/eth_transactions_test.go index cc8e1cb2040..03c6b062c09 100644 --- a/chain/types/ethtypes/eth_transactions_test.go +++ b/chain/types/ethtypes/eth_transactions_test.go @@ -1 +1,167 @@ package ethtypes + +import ( + "testing" + + "github.com/test-go/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/chain/types" +) + +func TestEthTransactionFromSignedFilecoinMessage(t *testing.T) { + eip1559sig := make([]byte, 65) + eip1559sig[0] = 1 + + legacySig := make([]byte, 66) + legacySig[0] = 1 + legacySig[65] = 27 + + pubKeyHex := "0x04cfecc0520d906cbfea387759246e89d85e2998843e56ad1c41de247ce10b3e4c453aa73c8de13c178d94461b6fa3f8b6f74406ce43d2fbab6992d0b283394242" + pubk := mustDecodeHex(pubKeyHex) + addrHash, err := EthAddressFromPubKey(pubk) + require.NoError(t, err) + from, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, addrHash) + require.NoError(t, err) + + fromEth, err := EthAddressFromFilecoinAddress(from) + require.NoError(t, err) + + to, err := address.NewIDAddress(1) + require.NoError(t, err) + + toEth, err := EthAddressFromFilecoinAddress(to) + require.NoError(t, err) + + tcs := map[string]struct { + msg *types.SignedMessage + expectedErr string + validateFunc func(t *testing.T, smsg *types.SignedMessage, tx EthTransaction) + }{ + "empty": { + expectedErr: "signed message is nil", + }, + "invalid-signature": { + msg: &types.SignedMessage{ + Message: types.Message{ + To: builtintypes.EthereumAddressManagerActorAddr, + From: from, + Method: builtintypes.MethodsEAM.CreateExternal, + }, + Signature: crypto.Signature{ + Type: crypto.SigTypeDelegated, + Data: []byte{1}, + }, + }, + expectedErr: "unsupported signature length", + }, + "valid-eip1559": { + msg: &types.SignedMessage{ + Message: types.Message{ + From: from, + To: to, + Value: big.NewInt(10), + GasFeeCap: big.NewInt(11), + GasPremium: big.NewInt(12), + GasLimit: 13, + Nonce: 14, + Method: builtintypes.MethodsEVM.InvokeContract, + }, + Signature: crypto.Signature{ + Type: crypto.SigTypeDelegated, + Data: eip1559sig, + }, + }, + validateFunc: func(t *testing.T, smsg *types.SignedMessage, tx EthTransaction) { + eip1559tx := tx.(*Eth1559TxArgs) + require.Equal(t, big.NewInt(10), eip1559tx.Value) + require.Equal(t, big.NewInt(11), eip1559tx.MaxFeePerGas) + require.Equal(t, big.NewInt(12), eip1559tx.MaxPriorityFeePerGas) + require.EqualValues(t, uint64(13), eip1559tx.GasLimit) + require.EqualValues(t, uint64(14), eip1559tx.Nonce) + require.EqualValues(t, toEth, *eip1559tx.To) + require.EqualValues(t, 314, eip1559tx.ChainID) + require.Empty(t, eip1559tx.Input) + + ethTx, err := tx.ToEthTx(smsg) + require.NoError(t, err) + require.EqualValues(t, 314, ethTx.ChainID) + require.EqualValues(t, 14, ethTx.Nonce) + hash, err := eip1559tx.TxHash() + require.NoError(t, err) + require.EqualValues(t, hash, ethTx.Hash) + require.EqualValues(t, fromEth, ethTx.From) + require.EqualValues(t, toEth, *ethTx.To) + require.EqualValues(t, big.NewInt(10), ethTx.Value) + require.EqualValues(t, 13, ethTx.Gas) + require.EqualValues(t, big.NewInt(11), *ethTx.MaxFeePerGas) + require.EqualValues(t, big.NewInt(12), *ethTx.MaxPriorityFeePerGas) + require.Nil(t, ethTx.GasPrice) + require.Empty(t, ethTx.AccessList) + }, + }, + "valid-legacy": { + msg: &types.SignedMessage{ + Message: types.Message{ + From: from, + To: to, + Value: big.NewInt(10), + GasFeeCap: big.NewInt(11), + GasPremium: big.NewInt(12), + GasLimit: 13, + Nonce: 14, + Method: builtintypes.MethodsEVM.InvokeContract, + }, + Signature: crypto.Signature{ + Type: crypto.SigTypeDelegated, + Data: legacySig, + }, + }, + validateFunc: func(t *testing.T, smsg *types.SignedMessage, tx EthTransaction) { + legacyTx := tx.(*EthLegacyHomesteadTxArgs) + require.Equal(t, big.NewInt(10), legacyTx.Value) + require.EqualValues(t, uint64(13), legacyTx.GasLimit) + require.EqualValues(t, uint64(14), legacyTx.Nonce) + require.EqualValues(t, toEth, *legacyTx.To) + require.EqualValues(t, big.NewInt(11), legacyTx.GasPrice) + require.Empty(t, legacyTx.Input) + + ethTx, err := tx.ToEthTx(smsg) + require.NoError(t, err) + require.EqualValues(t, 0, ethTx.ChainID) + require.EqualValues(t, 14, ethTx.Nonce) + hash, err := legacyTx.TxHash() + require.NoError(t, err) + require.EqualValues(t, big.NewInt(11), *ethTx.GasPrice) + require.EqualValues(t, hash, ethTx.Hash) + require.EqualValues(t, fromEth, ethTx.From) + require.EqualValues(t, toEth, *ethTx.To) + require.EqualValues(t, big.NewInt(10), ethTx.Value) + require.EqualValues(t, 13, ethTx.Gas) + require.Nil(t, ethTx.MaxFeePerGas) + require.Nil(t, ethTx.MaxPriorityFeePerGas) + require.Empty(t, ethTx.AccessList) + require.EqualValues(t, big.NewInt(27), ethTx.V) + }, + }, + } + + for name, tc := range tcs { + t.Run(name, func(t *testing.T) { + tx, err := EthTransactionFromSignedFilecoinMessage(tc.msg) + if tc.expectedErr != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedErr) + } else { + require.NoError(t, err) + } + if tc.validateFunc != nil { + tc.validateFunc(t, tc.msg, tx) + } + }) + } +} diff --git a/go.mod b/go.mod index 52af5d32e40..0635e393197 100644 --- a/go.mod +++ b/go.mod @@ -141,6 +141,7 @@ require ( github.com/samber/lo v1.39.0 github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + github.com/test-go/testify v1.1.4 github.com/triplewz/poseidon v0.0.0-20230828015038-79d8165c88ed github.com/urfave/cli/v2 v2.25.5 github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba From f867359823b5c1e843a7139ab70e3f2a53e05cf3 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Mon, 29 Apr 2024 10:42:56 +0530 Subject: [PATCH 14/27] changes as per review --- chain/consensus/signatures.go | 4 ++-- chain/types/ethtypes/eth_legacy_homestead_transactions.go | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/chain/consensus/signatures.go b/chain/consensus/signatures.go index 98d8019dd8c..1bc68d4b31a 100644 --- a/chain/consensus/signatures.go +++ b/chain/consensus/signatures.go @@ -20,11 +20,11 @@ func AuthenticateMessage(msg *types.SignedMessage, signer address.Address) error var digest []byte signatureType := msg.Signature.Type signatureCopy := msg.Signature - signatureCopy.Data = make([]byte, len(msg.Signature.Data)) - copy(signatureCopy.Data, msg.Signature.Data) switch signatureType { case crypto.SigTypeDelegated: + 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 Ethereum transaction: %w", err) diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go index c90692cb3a7..479b53c275c 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -86,10 +86,11 @@ func (tx *EthLegacyHomesteadTxArgs) ToUnsignedFilecoinMessage(from address.Addre func (tx *EthLegacyHomesteadTxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) { if len(sig) != legacyHomesteadTxSignatureLen { - return nil, fmt.Errorf("signature should be %d bytes long, but got %d bytes", legacyHomesteadTxSignatureLen, len(sig)) + return nil, fmt.Errorf("signature should be %d bytes long (1 byte metadata, %d bytes sig data), but got %d bytes", + legacyHomesteadTxSignatureLen, legacyHomesteadTxSignatureLen-1, len(sig)) } if sig[0] != LegacyHomesteadEthTxSignaturePrefix { - return nil, fmt.Errorf("expected signature prefix 0x01, but got 0x%x", sig[0]) + return nil, fmt.Errorf("expected signature prefix 0x%x, but got 0x%x", LegacyHomesteadEthTxSignaturePrefix, sig[0]) } // Remove the prefix byte as it's only used for legacy transaction identification From 9265211179636c045652f1f269b43908ace5b4e8 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Mon, 29 Apr 2024 12:10:41 +0530 Subject: [PATCH 15/27] more unit tests for legacy txns --- .../eth_legacy_homestead_transactions_test.go | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go index 3b91ba97c54..a59fe5032ed 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go @@ -2,11 +2,13 @@ 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" ) @@ -127,3 +129,130 @@ func TestLegacyHomesteadSignatures(t *testing.T) { require.Equal(t, tc.ExpectedV, "0x"+tx.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) + + ethLegacyTx, err := parseLegacyHomesteadTx(bz) + require.NoError(t, err) + + // 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 = parseLegacyHomesteadTx(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) + + ethLegacyTx, err := parseLegacyHomesteadTx(decodedSig) + require.NoError(t, err) + + // 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") +} From af4c2cda175cb73be1a40f9c86880059b0f5abe1 Mon Sep 17 00:00:00 2001 From: Aarsh Shah Date: Wed, 1 May 2024 13:57:44 +0400 Subject: [PATCH 16/27] Apply suggestions from code review Co-authored-by: Rod Vagg --- chain/types/ethtypes/eth_1559_transactions.go | 2 +- chain/types/ethtypes/eth_transactions.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chain/types/ethtypes/eth_1559_transactions.go b/chain/types/ethtypes/eth_1559_transactions.go index 22982bc1d54..8720dc5e72f 100644 --- a/chain/types/ethtypes/eth_1559_transactions.go +++ b/chain/types/ethtypes/eth_1559_transactions.go @@ -262,7 +262,7 @@ func (tx *Eth1559TxArgs) packTxFields() ([]interface{}, error) { func parseEip1559Tx(data []byte) (*Eth1559TxArgs, error) { if data[0] != Eip1559TxType { - return nil, xerrors.Errorf("not an EIP-1559 transaction: first byte is not 2") + return nil, xerrors.Errorf(fmt.Sprintf("not an EIP-1559 transaction: first byte is not %d", Eip1559TxType)) } d, err := DecodeRLP(data[1:]) diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index 82f6e482566..39f8e4b7892 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -22,10 +22,10 @@ import ( const ( // LegacyHomesteadEthTxSignaturePrefix defines the prefix byte used to identify the signature of a legacy Homestead Ethereum transaction. LegacyHomesteadEthTxSignaturePrefix = 0x01 - Eip1559TxType = 0x2 + Eip1559TxType = 0x02 - ethLegacyHomesteadTxType = 0x0 - ethLegacyHomesteadTxChainID = 0x0 + ethLegacyHomesteadTxType = 0x00 + ethLegacyHomesteadTxChainID = 0x00 ) // EthTransaction defines the interface for Ethereum-like transactions. From 8807aed82bd5fd828249d258e17439611afb3473 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Wed, 1 May 2024 15:50:13 +0530 Subject: [PATCH 17/27] address review comments from Rodd --- .../eth_legacy_homestead_transactions.go | 16 ++-- chain/types/ethtypes/eth_transactions.go | 29 ++++--- go.sum | 3 +- itests/eth_legacy_transactions_test.go | 86 ++++--------------- 4 files changed, 38 insertions(+), 96 deletions(-) diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go index 479b53c275c..6f2ce94fa32 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -14,10 +14,6 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) -const ( - legacyHomesteadTxSignatureLen = 66 -) - var _ EthTransaction = (*EthLegacyHomesteadTxArgs)(nil) type EthLegacyHomesteadTxArgs struct { @@ -85,9 +81,9 @@ func (tx *EthLegacyHomesteadTxArgs) ToUnsignedFilecoinMessage(from address.Addre } func (tx *EthLegacyHomesteadTxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) { - if len(sig) != legacyHomesteadTxSignatureLen { + if len(sig) != LegacyEthTxSignatureLen { return nil, fmt.Errorf("signature should be %d bytes long (1 byte metadata, %d bytes sig data), but got %d bytes", - legacyHomesteadTxSignatureLen, legacyHomesteadTxSignatureLen-1, len(sig)) + LegacyEthTxSignatureLen, LegacyEthTxSignatureLen-1, len(sig)) } if sig[0] != LegacyHomesteadEthTxSignaturePrefix { return nil, fmt.Errorf("expected signature prefix 0x%x, but got 0x%x", LegacyHomesteadEthTxSignaturePrefix, sig[0]) @@ -168,8 +164,8 @@ func (tx *EthLegacyHomesteadTxArgs) Signature() (*typescrypto.Signature, error) // pre-pend a one byte marker so nodes know that this is a legacy transaction sig = append([]byte{LegacyHomesteadEthTxSignaturePrefix}, sig...) - if len(sig) != legacyHomesteadTxSignatureLen { - return nil, fmt.Errorf("signature is not %d bytes", legacyHomesteadTxSignatureLen) + if len(sig) != LegacyEthTxSignatureLen { + return nil, fmt.Errorf("signature is not %d bytes", LegacyEthTxSignatureLen) } return &typescrypto.Signature{ @@ -220,8 +216,8 @@ func (tx *EthLegacyHomesteadTxArgs) InitialiseSignature(sig typescrypto.Signatur return fmt.Errorf("RecoverSignature only supports Delegated signature") } - if len(sig.Data) != legacyHomesteadTxSignatureLen { - return fmt.Errorf("signature should be %d bytes long, but got %d bytes", legacyHomesteadTxSignatureLen, len(sig.Data)) + if len(sig.Data) != LegacyEthTxSignatureLen { + return fmt.Errorf("signature should be %d bytes long, but got %d bytes", LegacyEthTxSignatureLen, len(sig.Data)) } if sig.Data[0] != LegacyHomesteadEthTxSignaturePrefix { diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index 39f8e4b7892..04af0a1c497 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -20,6 +20,8 @@ import ( ) const ( + EIP1559EthTxSignatureLen = 65 + LegacyEthTxSignatureLen = 66 // LegacyHomesteadEthTxSignaturePrefix defines the prefix byte used to identify the signature of a legacy Homestead Ethereum transaction. LegacyHomesteadEthTxSignaturePrefix = 0x01 Eip1559TxType = 0x02 @@ -125,7 +127,7 @@ func EthTransactionFromSignedFilecoinMessage(smsg *types.SignedMessage) (EthTran // Process based on the signature data length. switch len(smsg.Signature.Data) { - case 66: // Legacy Homestead transaction + case LegacyEthTxSignatureLen: // Legacy Homestead transaction if smsg.Signature.Data[0] != LegacyHomesteadEthTxSignaturePrefix { return nil, fmt.Errorf("unsupported legacy transaction; first byte of signature is %d", smsg.Signature.Data[0]) @@ -142,7 +144,7 @@ func EthTransactionFromSignedFilecoinMessage(smsg *types.SignedMessage) (EthTran return nil, fmt.Errorf("failed to initialise signature: %w", err) } return &tx, nil - case 65: // EIP-1559 transaction + case EIP1559EthTxSignatureLen: // EIP-1559 transaction tx := Eth1559TxArgs{ ChainID: build.Eip155ChainId, Nonce: int(smsg.Message.Nonce), @@ -189,22 +191,21 @@ func ParseEthTransaction(data []byte) (EthTransaction, error) { return nil, fmt.Errorf("empty data") } - if data[0] > 0x7f { - tx, err := parseLegacyHomesteadTx(data) - if err != nil { - return nil, fmt.Errorf("failed to parse legacy homestead transaction: %w", err) - } - return tx, nil - } - - 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 := parseLegacyHomesteadTx(data) + if err != nil { + return nil, fmt.Errorf("failed to parse legacy homestead transaction: %w", err) + } + return tx, nil + } } return nil, fmt.Errorf("unsupported transaction type") diff --git a/go.sum b/go.sum index 7f447d55f05..252eed481b9 100644 --- a/go.sum +++ b/go.sum @@ -354,7 +354,6 @@ github.com/filecoin-project/go-state-types v0.0.0-20201102161440-c8033295a1fc/go github.com/filecoin-project/go-state-types v0.1.0/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.1.6/go.mod h1:UwGVoMsULoCK+bWjEdd/xLCvLAQFBC7EDT477SKml+Q= github.com/filecoin-project/go-state-types v0.1.10/go.mod h1:UwGVoMsULoCK+bWjEdd/xLCvLAQFBC7EDT477SKml+Q= -github.com/filecoin-project/go-state-types v0.11.2-0.20230712101859-8f37624fa540/go.mod h1:SyNPwTsU7I22gL2r0OAPcImvLoTVfgRwdK/Y5rR1zz8= github.com/filecoin-project/go-state-types v0.13.1 h1:4CivvlcHAIoAtFFVVlZtokynaMQu5XLXGoTKhQkfG1I= github.com/filecoin-project/go-state-types v0.13.1/go.mod h1:cHpOPup9H1g2T29dKHAjC2sc7/Ef5ypjuW9A3I+e9yY= github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= @@ -953,6 +952,7 @@ github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6K github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/koalacxr/quantile v0.0.1 h1:wAW+SQ286Erny9wOjVww96t8ws+x5Zj6AKHDULUK+o0= @@ -2065,6 +2065,7 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/itests/eth_legacy_transactions_test.go b/itests/eth_legacy_transactions_test.go index 141a8658bfb..cba949f21e7 100644 --- a/itests/eth_legacy_transactions_test.go +++ b/itests/eth_legacy_transactions_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/require" "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/go-state-types/manifest" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" @@ -27,13 +26,6 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - // install contract - contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") - require.NoError(t, err) - - contract, err := hex.DecodeString(string(contractHex)) - require.NoError(t, err) - // create a new Ethereum account key, ethAddr, deployer := client.EVM().NewAccount() _, ethAddr2, _ := client.EVM().NewAccount() @@ -41,8 +33,9 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { kit.SendFunds(ctx, t, client, deployer, types.FromFil(1000)) gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ - From: ðAddr, - Data: contract, + From: ðAddr, + To: ðAddr2, + Value: ethtypes.EthBigInt(big.NewInt(100)), }}) require.NoError(t, err) @@ -106,7 +99,7 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { require.EqualValues(t, tx.V, ethTx.V) } -func TestLegacyContractDeploymentValidSignature(t *testing.T) { +func TestLegacyContractInvocation(t *testing.T) { blockTime := 100 * time.Millisecond client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) @@ -115,23 +108,13 @@ func TestLegacyContractDeploymentValidSignature(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - // install contract - contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") - require.NoError(t, err) - - contract, err := hex.DecodeString(string(contractHex)) - require.NoError(t, err) - // 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)) - // verify the deployer address is a placeholder. - client.AssertActorType(ctx, deployer, manifest.PlaceholderKey) - - tx, err := deployLegacyContractTx(ctx, client, ethAddr, contract) + // DEPLOY CONTRACT + tx, err := deployLegacyContractTx(t, ctx, client, ethAddr) require.NoError(t, err) client.EVM().SignLegacyTransaction(tx, key.PrivateKey) @@ -146,53 +129,7 @@ func TestLegacyContractDeploymentValidSignature(t *testing.T) { // Submit transaction with valid signature client.EVM().SignLegacyTransaction(tx, key.PrivateKey) - hash := client.EVM().SubmitTransaction(ctx, tx) - - receipt, err := client.EVM().WaitTransaction(ctx, hash) - require.NoError(t, err) - require.NotNil(t, receipt) - - // Success. - require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) - - // Verify that the deployer is now an account. - client.AssertActorType(ctx, deployer, manifest.EthAccountKey) - - // Verify that the nonce was incremented. - nonce, err := client.MpoolGetNonce(ctx, deployer) - require.NoError(t, err) - require.EqualValues(t, 1, nonce) - - // Verify that the deployer is now an account. - client.AssertActorType(ctx, deployer, manifest.EthAccountKey) -} -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() - - // install contract - contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") - require.NoError(t, err) - - contract, err := hex.DecodeString(string(contractHex)) - require.NoError(t, err) - - // 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, client, ethAddr, contract) - require.NoError(t, err) - - client.EVM().SignLegacyTransaction(tx, key.PrivateKey) hash := client.EVM().SubmitTransaction(ctx, tx) receipt, err := client.EVM().WaitTransaction(ctx, hash) @@ -240,7 +177,7 @@ func TestLegacyContractInvocation(t *testing.T) { // Mangle signature invokeTx.V.Int.Xor(invokeTx.V.Int, big.NewInt(1).Int) - signed, err := invokeTx.ToRlpSignedMsg() + signed, err = invokeTx.ToRlpSignedMsg() require.NoError(t, err) // Submit transaction with bad signature _, err = client.EVM().EthSendRawTransaction(ctx, signed) @@ -269,7 +206,14 @@ func TestLegacyContractInvocation(t *testing.T) { require.EqualValues(t, effectiveGasPrice, big.Int(receipt.EffectiveGasPrice)) } -func deployLegacyContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr ethtypes.EthAddress, contract []byte) (*ethtypes.EthLegacyHomesteadTxArgs, error) { +func deployLegacyContractTx(t *testing.T, ctx context.Context, 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: ðAddr, Data: contract, From c62a67dd61ab3af1cd39a38474b470841f7423ac Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Wed, 1 May 2024 16:45:54 +0530 Subject: [PATCH 18/27] changes as per zen's 2nd review --- .../eth_legacy_homestead_transactions.go | 2 - .../eth_legacy_homestead_transactions_test.go | 53 ++++++++++++++++--- node/impl/full/eth_utils.go | 5 -- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go index 6f2ce94fa32..21f7073f62c 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -31,8 +31,6 @@ type EthLegacyHomesteadTxArgs struct { func (tx *EthLegacyHomesteadTxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) { 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{}, fmt.Errorf("sender was not an eth account") } hash, err := tx.TxHash() diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go index a59fe5032ed..650f8259186 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go @@ -82,11 +82,13 @@ func TestEthLegacyHomesteadTxArgs(t *testing.T) { func TestLegacyHomesteadSignatures(t *testing.T) { testcases := []struct { - RawTx string - ExpectedR string - ExpectedS string - ExpectedV string - ExpectErr bool + RawTx string + ExpectedR string + ExpectedS string + ExpectedV string + ExpectErr bool + ExpectErrMsg string + ExpectVMismatch bool }{ { "0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", @@ -94,6 +96,8 @@ func TestLegacyHomesteadSignatures(t *testing.T) { "0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", "0x1b", false, + "", + false, }, { "0xf85f030182520794b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a801ba098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa07778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c", @@ -101,6 +105,35 @@ func TestLegacyHomesteadSignatures(t *testing.T) { "0x7778cde41a8a37f6a087622b38bc201bd3e7df06dce067569d4def1b53dba98c", "0x1b", false, + "", + false, + }, + { + "0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + "0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353", + "0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + "0x1c", + false, + "", + true, + }, + { + "0xf882800182540894095e7baea6a6c7c4c2dfeb977efac326af552d8780a3deadbeef0000000101010010101010101010101010101aaabbbbbbcccccccddddddddd1ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a01fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + "0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353", + "0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804", + "0x1f", + false, + "", + true, + }, + { + "0xf86f830131cf8504a817c800825208942cf1e5a8250ded8835694ebeb90cfa0237fcb9b1882ec4a5251d1100008026a0f5f8d2244d619e211eeb634acd1bea0762b7b4c97bba9f01287c82bfab73f911a015be7982898aa7cc6c6f27ff33e999e4119d6cd51330353474b98067ff56d930", + "0xf5f8d2244d619e211eeb634acd1bea0762b7b4c97bba9f01287c82bfab73f911", + "0x15be7982898aa7cc6c6f27ff33e999e4119d6cd51330353474b98067ff56d930", + "0x26", + true, + "only support 27 or 28 for v", + false, }, { "0x00", @@ -108,6 +141,8 @@ func TestLegacyHomesteadSignatures(t *testing.T) { "", "", true, + "not a legacy eth transaction", + false, }, } @@ -115,6 +150,7 @@ func TestLegacyHomesteadSignatures(t *testing.T) { tx, err := parseLegacyHomesteadTx(mustDecodeHex(tc.RawTx)) if tc.ExpectErr { require.Error(t, err) + require.Contains(t, err.Error(), tc.ExpectErrMsg) continue } require.Nil(t, err) @@ -126,7 +162,12 @@ func TestLegacyHomesteadSignatures(t *testing.T) { require.Equal(t, tc.ExpectedR, "0x"+tx.R.Text(16), i) require.Equal(t, tc.ExpectedS, "0x"+tx.S.Text(16), i) - require.Equal(t, tc.ExpectedV, "0x"+tx.V.Text(16), i) + + if tc.ExpectVMismatch { + require.NotEqual(t, tc.ExpectedV, "0x"+tx.V.Text(16), i) + } else { + require.Equal(t, tc.ExpectedV, "0x"+tx.V.Text(16), i) + } } } diff --git a/node/impl/full/eth_utils.go b/node/impl/full/eth_utils.go index ef1e26c6f92..c48ce7e68e6 100644 --- a/node/impl/full/eth_utils.go +++ b/node/impl/full/eth_utils.go @@ -251,11 +251,6 @@ func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTx return ethtypes.EthBlock{}, xerrors.Errorf("failed to convert msg to ethTx: %w", err) } - // Don't override the chainID if this is not an EIP-1559 transaction. - // The chainID has already been set to 0 for leagcy ETH transactions. - if smsg.Signature.Type != crypto.SigTypeDelegated { - tx.ChainID = build.Eip155ChainId - } tx.BlockHash = &blkHash tx.BlockNumber = &bn tx.TransactionIndex = &ti From a0e1801bf64ef224dc33a1b2c2af1d196d44eadc Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Wed, 1 May 2024 16:47:13 +0530 Subject: [PATCH 19/27] go mod tidy --- go.sum | 1 - 1 file changed, 1 deletion(-) diff --git a/go.sum b/go.sum index 01931fba4e7..4f6daaf54c0 100644 --- a/go.sum +++ b/go.sum @@ -355,7 +355,6 @@ github.com/filecoin-project/go-state-types v0.0.0-20201102161440-c8033295a1fc/go github.com/filecoin-project/go-state-types v0.1.0/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.1.6/go.mod h1:UwGVoMsULoCK+bWjEdd/xLCvLAQFBC7EDT477SKml+Q= github.com/filecoin-project/go-state-types v0.1.10/go.mod h1:UwGVoMsULoCK+bWjEdd/xLCvLAQFBC7EDT477SKml+Q= -github.com/filecoin-project/go-state-types v0.13.1 h1:4CivvlcHAIoAtFFVVlZtokynaMQu5XLXGoTKhQkfG1I= github.com/filecoin-project/go-state-types v0.13.1/go.mod h1:cHpOPup9H1g2T29dKHAjC2sc7/Ef5ypjuW9A3I+e9yY= github.com/filecoin-project/go-state-types v0.13.3 h1:9JPkC0E6HDtfHbaOIrFiCDzT/Z0jRTb9En4Y4Ci/b3w= github.com/filecoin-project/go-state-types v0.13.3/go.mod h1:cHpOPup9H1g2T29dKHAjC2sc7/Ef5ypjuW9A3I+e9yY= From 2a6611480871d306477a7ef052494800a172f24e Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Fri, 3 May 2024 13:47:59 +0530 Subject: [PATCH 20/27] itests passing for 155 tx --- chain/types/ethtypes/eth_1559_transactions.go | 8 +- .../ethtypes/eth_legacy_155_transactions.go | 190 ++++++++++++++++++ .../eth_legacy_homestead_transactions.go | 105 ++-------- .../eth_legacy_homestead_transactions_test.go | 42 ++-- chain/types/ethtypes/eth_transactions.go | 176 +++++++++++++--- itests/kit/evm.go | 2 +- node/impl/full/eth_utils.go | 2 +- 7 files changed, 375 insertions(+), 150 deletions(-) create mode 100644 chain/types/ethtypes/eth_legacy_155_transactions.go diff --git a/chain/types/ethtypes/eth_1559_transactions.go b/chain/types/ethtypes/eth_1559_transactions.go index 8720dc5e72f..01e8f9e53b7 100644 --- a/chain/types/ethtypes/eth_1559_transactions.go +++ b/chain/types/ethtypes/eth_1559_transactions.go @@ -64,7 +64,7 @@ func (tx *Eth1559TxArgs) ToRlpUnsignedMsg() ([]byte, error) { if err != nil { return nil, err } - return append([]byte{Eip1559TxType}, encoded...), nil + return append([]byte{EIP1559TxType}, encoded...), nil } func (tx *Eth1559TxArgs) TxHash() (EthHash, error) { @@ -166,7 +166,7 @@ func (tx *Eth1559TxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) { ethTx := EthTx{ ChainID: EthUint64(build.Eip155ChainId), - Type: Eip1559TxType, + Type: EIP1559TxType, Nonce: EthUint64(tx.Nonce), Hash: hash, To: tx.To, @@ -261,8 +261,8 @@ func (tx *Eth1559TxArgs) packTxFields() ([]interface{}, error) { } func parseEip1559Tx(data []byte) (*Eth1559TxArgs, error) { - if data[0] != Eip1559TxType { - return nil, xerrors.Errorf(fmt.Sprintf("not an EIP-1559 transaction: first byte is not %d", Eip1559TxType)) + if data[0] != EIP1559TxType { + return nil, xerrors.Errorf("not an EIP-1559 transaction: first byte is not %d", EIP1559TxType) } d, err := DecodeRLP(data[1:]) diff --git a/chain/types/ethtypes/eth_legacy_155_transactions.go b/chain/types/ethtypes/eth_legacy_155_transactions.go new file mode 100644 index 00000000000..282b3315991 --- /dev/null +++ b/chain/types/ethtypes/eth_legacy_155_transactions.go @@ -0,0 +1,190 @@ +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) + +type EthLegacy155TxArgs struct { + legacyTx *EthLegacyHomesteadTxArgs +} + +// implement all interface methods +func (tx *EthLegacy155TxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) { + ethTx, err := tx.legacyTx.ToEthTx(smsg) + if err != nil { + return EthTx{}, fmt.Errorf("failed to convert legacy tx to eth tx: %w", err) + } + if err := validateEIP155ChainId(tx.legacyTx.V); err != nil { + return EthTx{}, fmt.Errorf("failed to validate EIP155 chain id: %w", err) + } + + ethTx.ChainID = build.Eip155ChainId + 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 tx.legacyTx.ToRlpUnsignedMsg() +} + +func (tx *EthLegacy155TxArgs) TxHash() (EthHash, error) { + return tx.legacyTx.TxHash() +} + +func (tx *EthLegacy155TxArgs) ToRlpSignedMsg() ([]byte, error) { + return tx.legacyTx.ToRlpSignedMsg() +} + +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) != EthLegacy155TxSignatureLen { + return nil, fmt.Errorf("signature is not %d bytes; it is %d bytes", EthLegacy155TxSignatureLen, 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 tx.legacyTx.Sender() +} + +var big8 = big.NewInt(8) + +func (tx *EthLegacy155TxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) { + if len(sig) != EthLegacy155TxSignatureLen { + return nil, fmt.Errorf("signature should be %d bytes long (1 byte metadata, %d bytes sig data), but got %d bytes", + EthLegacy155TxSignatureLen, EthLegacy155TxSignatureLen-1, 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, which is the last byte in Ethereum signatures + vValue := big.NewFromGo(big.NewInt(0).SetBytes(sig[64:])) + + 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, 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) != EthLegacy155TxSignatureLen { + return fmt.Errorf("signature should be %d bytes long, but got %d bytes", EthLegacy155TxSignatureLen, 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 tx 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) { + return tx.legacyTx.packTxFields() +} + +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) int { + chainId := big.NewIntUnsigned(chain) + vVal := big.Add(big.Mul(chainId, big.NewInt(2)), big.NewInt(36)) + 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 +} diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go index 21f7073f62c..3bf69303e07 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -40,8 +40,8 @@ func (tx *EthLegacyHomesteadTxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, e gasPrice := EthBigInt(tx.GasPrice) ethTx := EthTx{ - ChainID: ethLegacyHomesteadTxChainID, - Type: ethLegacyHomesteadTxType, + ChainID: EthLegacyHomesteadTxChainID, + Type: EthLegacyTxType, Nonce: EthUint64(tx.Nonce), Hash: hash, To: tx.To, @@ -79,12 +79,12 @@ func (tx *EthLegacyHomesteadTxArgs) ToUnsignedFilecoinMessage(from address.Addre } func (tx *EthLegacyHomesteadTxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) { - if len(sig) != LegacyEthTxSignatureLen { + 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", - LegacyEthTxSignatureLen, LegacyEthTxSignatureLen-1, len(sig)) + EthLegacyHomesteadTxSignatureLen, EthLegacyHomesteadTxSignatureLen-1, len(sig)) } - if sig[0] != LegacyHomesteadEthTxSignaturePrefix { - return nil, fmt.Errorf("expected signature prefix 0x%x, but got 0x%x", LegacyHomesteadEthTxSignaturePrefix, sig[0]) + 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 @@ -160,10 +160,10 @@ func (tx *EthLegacyHomesteadTxArgs) Signature() (*typescrypto.Signature, error) sig = append(sig, v[0]) } // pre-pend a one byte marker so nodes know that this is a legacy transaction - sig = append([]byte{LegacyHomesteadEthTxSignaturePrefix}, sig...) + sig = append([]byte{EthLegacyHomesteadTxSignaturePrefix}, sig...) - if len(sig) != LegacyEthTxSignatureLen { - return nil, fmt.Errorf("signature is not %d bytes", LegacyEthTxSignatureLen) + if len(sig) != EthLegacyHomesteadTxSignatureLen { + return nil, fmt.Errorf("signature is not %d bytes", EthLegacyHomesteadTxSignatureLen) } return &typescrypto.Signature{ @@ -214,11 +214,11 @@ func (tx *EthLegacyHomesteadTxArgs) InitialiseSignature(sig typescrypto.Signatur return fmt.Errorf("RecoverSignature only supports Delegated signature") } - if len(sig.Data) != LegacyEthTxSignatureLen { - return fmt.Errorf("signature should be %d bytes long, but got %d bytes", LegacyEthTxSignatureLen, len(sig.Data)) + 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] != LegacyHomesteadEthTxSignaturePrefix { + if sig.Data[0] != EthLegacyHomesteadTxSignaturePrefix { return fmt.Errorf("expected signature prefix 0x01, but got 0x%x", sig.Data[0]) } @@ -248,87 +248,6 @@ func (tx *EthLegacyHomesteadTxArgs) InitialiseSignature(sig typescrypto.Signatur return nil } -func parseLegacyHomesteadTx(data []byte) (*EthLegacyHomesteadTxArgs, 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 - } - - // legacy homestead transactions only support 27 or 28 for v - 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") - } - - return &EthLegacyHomesteadTxArgs{ - Nonce: nonce, - GasPrice: gasPrice, - GasLimit: gasLimit, - To: to, - Value: value, - Input: input, - V: v, - R: r, - S: s, - }, nil -} - func (tx *EthLegacyHomesteadTxArgs) packTxFields() ([]interface{}, error) { nonce, err := formatInt(tx.Nonce) if err != nil { diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go index 650f8259186..cb4b4ac4e56 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions_test.go @@ -45,17 +45,17 @@ func TestEthLegacyHomesteadTxArgs(t *testing.T) { for i, tc := range testcases { // parse txargs - txArgs, err := parseLegacyHomesteadTx(mustDecodeHex(tc.RawTx)) + tx, err := parseLegacyTx(mustDecodeHex(tc.RawTx)) require.NoError(t, err) - msgRecovered, err := txArgs.ToRlpUnsignedMsg() + msgRecovered, err := tx.ToRlpUnsignedMsg() require.NoError(t, err) // verify signatures - from, err := txArgs.Sender() + from, err := tx.Sender() require.NoError(t, err) - smsg, err := ToSignedFilecoinMessage(txArgs) + smsg, err := ToSignedFilecoinMessage(tx) require.NoError(t, err) sig := smsg.Signature.Data[:] @@ -68,6 +68,7 @@ func TestEthLegacyHomesteadTxArgs(t *testing.T) { err = sigs.Verify(&smsg.Signature, from, msgRecovered) require.NoError(t, err) + txArgs := tx.(*EthLegacyHomesteadTxArgs) // verify data require.EqualValues(t, tc.ExpectedNonce, txArgs.Nonce, i) @@ -126,15 +127,6 @@ func TestLegacyHomesteadSignatures(t *testing.T) { "", true, }, - { - "0xf86f830131cf8504a817c800825208942cf1e5a8250ded8835694ebeb90cfa0237fcb9b1882ec4a5251d1100008026a0f5f8d2244d619e211eeb634acd1bea0762b7b4c97bba9f01287c82bfab73f911a015be7982898aa7cc6c6f27ff33e999e4119d6cd51330353474b98067ff56d930", - "0xf5f8d2244d619e211eeb634acd1bea0762b7b4c97bba9f01287c82bfab73f911", - "0x15be7982898aa7cc6c6f27ff33e999e4119d6cd51330353474b98067ff56d930", - "0x26", - true, - "only support 27 or 28 for v", - false, - }, { "0x00", "", @@ -147,7 +139,7 @@ func TestLegacyHomesteadSignatures(t *testing.T) { } for i, tc := range testcases { - tx, err := parseLegacyHomesteadTx(mustDecodeHex(tc.RawTx)) + tx, err := parseLegacyTx(mustDecodeHex(tc.RawTx)) if tc.ExpectErr { require.Error(t, err) require.Contains(t, err.Error(), tc.ExpectErrMsg) @@ -160,13 +152,15 @@ func TestLegacyHomesteadSignatures(t *testing.T) { require.NoError(t, tx.InitialiseSignature(*sig)) - require.Equal(t, tc.ExpectedR, "0x"+tx.R.Text(16), i) - require.Equal(t, tc.ExpectedS, "0x"+tx.S.Text(16), i) + 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"+tx.V.Text(16), i) + require.NotEqual(t, tc.ExpectedV, "0x"+txArgs.V.Text(16), i) } else { - require.Equal(t, tc.ExpectedV, "0x"+tx.V.Text(16), i) + require.Equal(t, tc.ExpectedV, "0x"+txArgs.V.Text(16), i) } } } @@ -177,9 +171,12 @@ func TestEtherScanLegacyRLP(t *testing.T) { rlp := "0xf8718301efc58506fc23ac008305161594104994f45d9d697ca104e5704a7b77d7fec3537c890821878651a4d70000801ba051222d91a379452395d0abaff981af4cfcc242f25cfaf947dea8245a477731f9a03a997c910b4701cca5d933fb26064ee5af7fe3236ff0ef2b58aa50b25aff8ca5" bz := mustDecodeHex(rlp) - ethLegacyTx, err := parseLegacyHomesteadTx(bz) + tx, err := parseLegacyTx(bz) require.NoError(t, err) + ethLegacyTx, ok := tx.(*EthLegacyHomesteadTxArgs) + require.True(t, ok) + // Verify nonce require.EqualValues(t, 0x1efc5, ethLegacyTx.Nonce) @@ -269,7 +266,7 @@ func TestFailurePaths(t *testing.T) { decoded, err := hex.DecodeString(strings.TrimPrefix(invalidRLP, "0x")) require.NoError(t, err) - _, err = parseLegacyHomesteadTx(decoded) + _, err = parseLegacyTx(decoded) require.Error(t, err, "Expected error for invalid RLP") // Test case for mangled signature @@ -277,9 +274,12 @@ func TestFailurePaths(t *testing.T) { decodedSig, err := hex.DecodeString(strings.TrimPrefix(mangledSignatureRLP, "0x")) require.NoError(t, err) - ethLegacyTx, err := parseLegacyHomesteadTx(decodedSig) + 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)) diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index 04af0a1c497..46ed27443ca 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -20,16 +20,26 @@ import ( ) const ( - EIP1559EthTxSignatureLen = 65 - LegacyEthTxSignatureLen = 66 - // LegacyHomesteadEthTxSignaturePrefix defines the prefix byte used to identify the signature of a legacy Homestead Ethereum transaction. - LegacyHomesteadEthTxSignaturePrefix = 0x01 - Eip1559TxType = 0x02 - - ethLegacyHomesteadTxType = 0x00 - ethLegacyHomesteadTxChainID = 0x00 + EthLegacyTxType = 0x00 + EIP1559TxType = 0x02 ) +const ( + EthEIP1559TxSignatureLen = 65 + EthLegacyHomesteadTxSignatureLen = 66 + EthLegacyHomesteadTxSignaturePrefix = 0x01 + EthLegacy155TxSignaturePrefix = 0x02 + EthLegacyHomesteadTxChainID = 0x00 +) + +var ( + EthLegacy155TxSignatureLen int +) + +func init() { + EthLegacy155TxSignatureLen = calcEIP155TxSignatureLen(build.Eip155ChainId) +} + // EthTransaction defines the interface for Ethereum-like transactions. // It provides methods to convert transactions to various formats, // retrieve transaction details, and manipulate transaction signatures. @@ -125,26 +135,9 @@ func EthTransactionFromSignedFilecoinMessage(smsg *types.SignedMessage) (EthTran return nil, fmt.Errorf("unsupported msg version: %d", smsg.Message.Version) } - // Process based on the signature data length. + // Determine the type of transaction based on the signature length switch len(smsg.Signature.Data) { - case LegacyEthTxSignatureLen: // Legacy Homestead transaction - if smsg.Signature.Data[0] != LegacyHomesteadEthTxSignaturePrefix { - return nil, fmt.Errorf("unsupported legacy transaction; first byte of signature is %d", - smsg.Signature.Data[0]) - } - tx := EthLegacyHomesteadTxArgs{ - Nonce: int(smsg.Message.Nonce), - To: to, - Value: smsg.Message.Value, - Input: params, - GasPrice: smsg.Message.GasFeeCap, - 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 EIP1559EthTxSignatureLen: // EIP-1559 transaction + case EthEIP1559TxSignatureLen: tx := Eth1559TxArgs{ ChainID: build.Eip155ChainId, Nonce: int(smsg.Message.Nonce), @@ -159,8 +152,37 @@ func EthTransactionFromSignedFilecoinMessage(smsg *types.SignedMessage) (EthTran return nil, fmt.Errorf("failed to initialise signature: %w", err) } return &tx, nil + + case EthLegacyHomesteadTxSignatureLen, EthLegacy155TxSignatureLen: + 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: %d", len(smsg.Signature.Data)) + return nil, fmt.Errorf("unsupported signature length") } } @@ -195,12 +217,12 @@ func ParseEthTransaction(data []byte) (EthTransaction, error) { case 1: // EIP-2930 return nil, fmt.Errorf("EIP-2930 transaction is not supported") - case Eip1559TxType: + case EIP1559TxType: // EIP-1559 return parseEip1559Tx(data) default: if data[0] > 0x7f { - tx, err := parseLegacyHomesteadTx(data) + tx, err := parseLegacyTx(data) if err != nil { return nil, fmt.Errorf("failed to parse legacy homestead transaction: %w", err) } @@ -403,3 +425,97 @@ func getEthParamsAndRecipient(msg *types.Message) (params []byte, to *EthAddress 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 +} diff --git a/itests/kit/evm.go b/itests/kit/evm.go index e85c2b09c50..f4c02c57c99 100644 --- a/itests/kit/evm.go +++ b/itests/kit/evm.go @@ -53,7 +53,7 @@ func (e *EVM) SignLegacyTransaction(tx *ethtypes.EthLegacyHomesteadTxArgs, privK signature, err := sigs.Sign(crypto.SigTypeDelegated, privKey, preimage) require.NoError(e.t, err) - signature.Data = append([]byte{ethtypes.LegacyHomesteadEthTxSignaturePrefix}, signature.Data...) + signature.Data = append([]byte{ethtypes.EthLegacyHomesteadTxSignaturePrefix}, signature.Data...) signature.Data[len(signature.Data)-1] += 27 err = tx.InitialiseSignature(*signature) diff --git a/node/impl/full/eth_utils.go b/node/impl/full/eth_utils.go index f80a7a4a711..1031671ce27 100644 --- a/node/impl/full/eth_utils.go +++ b/node/impl/full/eth_utils.go @@ -543,7 +543,7 @@ 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: &maxFeePerGas, MaxPriorityFeePerGas: &maxPriorityFeePerGas, From 770d427479cf43ae2cd48b0353427ab8a1071222 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Fri, 3 May 2024 20:50:59 +0530 Subject: [PATCH 21/27] first working version for EIP-155 transactions --- .../ethtypes/eth_legacy_155_transactions.go | 145 +++++++++++++++++- itests/eth_legacy_transactions_test.go | 35 +++++ node/impl/full/eth.go | 3 + 3 files changed, 176 insertions(+), 7 deletions(-) diff --git a/chain/types/ethtypes/eth_legacy_155_transactions.go b/chain/types/ethtypes/eth_legacy_155_transactions.go index 282b3315991..c289904fb55 100644 --- a/chain/types/ethtypes/eth_legacy_155_transactions.go +++ b/chain/types/ethtypes/eth_legacy_155_transactions.go @@ -3,6 +3,9 @@ package ethtypes import ( "fmt" + gocrypto "github.com/filecoin-project/go-crypto" + "golang.org/x/crypto/sha3" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/big" typescrypto "github.com/filecoin-project/go-state-types/crypto" @@ -38,15 +41,54 @@ func (tx *EthLegacy155TxArgs) ToUnsignedFilecoinMessage(from address.Address) (* } func (tx *EthLegacy155TxArgs) ToRlpUnsignedMsg() ([]byte, error) { - return tx.legacyTx.ToRlpUnsignedMsg() + packedFields, err := tx.packTxFields() + if err != nil { + fmt.Println("failed to pack tx fields", err) + return nil, err + } + encoded, err := EncodeRLP(packedFields) + if err != nil { + fmt.Println("failed to encode tx fields", err) + return nil, err + } + return encoded, nil } func (tx *EthLegacy155TxArgs) TxHash() (EthHash, error) { - return tx.legacyTx.TxHash() + packed1, err := tx.packTxFields() + if err != nil { + return EthHash{}, err + } + packed1 = packed1[:len(packed1)-3] // remove r and s + + packed2, err := packSigFields(tx.legacyTx.V, tx.legacyTx.R, tx.legacyTx.S) + if err != nil { + return EthHash{}, err + } + encoded, err := EncodeRLP(append(packed1, packed2...)) + if err != nil { + return EthHash{}, err + } + + return EthHashFromTxBytes(encoded), nil } func (tx *EthLegacy155TxArgs) ToRlpSignedMsg() ([]byte, error) { - return tx.legacyTx.ToRlpSignedMsg() + packed1, err := tx.packTxFields() + if err != nil { + return nil, err + } + + 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, err + } + return encoded, nil } func (tx *EthLegacy155TxArgs) Signature() (*typescrypto.Signature, error) { @@ -77,7 +119,45 @@ 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 tx.legacyTx.Sender() + 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) + } + + fmt.Println("sigData length is", len(sigData)) + + 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) + } + + fmt.Println("ea is", ea) + + return ea.ToFilecoinAddress() } var big8 = big.NewInt(8) @@ -110,8 +190,7 @@ func (tx *EthLegacy155TxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) return nil, fmt.Errorf("invalid 'v' value: expected 27 or 28, got %d", vValue.Int64()) } - return sig, nil - + return sig[0:65], nil } func (tx *EthLegacy155TxArgs) InitialiseSignature(sig typescrypto.Signature) error { @@ -154,7 +233,59 @@ func (tx *EthLegacy155TxArgs) InitialiseSignature(sig typescrypto.Signature) err } func (tx *EthLegacy155TxArgs) packTxFields() ([]interface{}, error) { - return tx.legacyTx.packTxFields() + 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 + } + + if err := validateEIP155ChainId(tx.legacyTx.V); err != nil { + return nil, fmt.Errorf("failed to validate EIP155 chain id: %w", 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 { diff --git a/itests/eth_legacy_transactions_test.go b/itests/eth_legacy_transactions_test.go index cba949f21e7..6a65a337dd9 100644 --- a/itests/eth_legacy_transactions_test.go +++ b/itests/eth_legacy_transactions_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/hex" "encoding/json" + "fmt" "os" "testing" "time" @@ -41,6 +42,7 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { gaslimit, err := client.EthEstimateGas(ctx, gasParams) require.NoError(t, err) + fmt.Println("gas limit is", gaslimit) tx := ethtypes.EthLegacyHomesteadTxArgs{ Value: big.NewInt(100), @@ -99,6 +101,39 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { require.EqualValues(t, tx.V, ethTx.V) } +func TestEIP155Tx(t *testing.T) { + rlpHex := "f86d8083030e1b83291e739490322092a524e0e43a2ec80ec6f35100d24799f28898a7d9b8314c000080820298a0dc782de2fec8cd45e699075beb756ad731943d19a33332fa36e72fd94802ed10a056b3e1e36f2851402661daf0b3284bc8d15db005d1fade908c1117c6ae37429d" + rlpBytes, err := hex.DecodeString(rlpHex) + require.NoError(t, err) + 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() + + from := "0xf0a3b487d026406F5ca18891dB6896a3dA900F29" + ea, err := ethtypes.ParseEthAddress(from) + require.NoError(t, err) + fa, err := ea.ToFilecoinAddress() + require.NoError(t, err) + fmt.Println("fil addr is", fa.String()) + kit.SendFunds(ctx, t, client, fa, types.FromFil(20000)) + + hash, err := client.EthSendRawTransaction(ctx, rlpBytes) + require.NoError(t, err) + + receipt, err := client.EVM().WaitTransaction(ctx, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + require.EqualValues(t, hash, receipt.TransactionHash) + + // Success. + require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) + +} + func TestLegacyContractInvocation(t *testing.T) { blockTime := 100 * time.Millisecond client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 3f5bfb6af86..ec53468e466 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -3,6 +3,7 @@ package full import ( "bytes" "context" + "encoding/hex" "errors" "fmt" "os" @@ -819,6 +820,8 @@ func (a *EthModule) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) } func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) { + log.Warn("rawTx", "rawTx", hex.EncodeToString(rawTx)) + txArgs, err := ethtypes.ParseEthTransaction(rawTx) if err != nil { return ethtypes.EmptyEthHash, err From f1df4ce32f83084b9a03e921fb210ae0742c596e Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Mon, 6 May 2024 13:51:43 +0530 Subject: [PATCH 22/27] green itest --- chain/types/ethtypes/eth_1559_transactions.go | 56 +----- .../ethtypes/eth_legacy_155_transactions.go | 153 ++++++---------- .../eth_legacy_155_transactions_test.go | 167 ++++++++++++++++++ .../eth_legacy_homestead_transactions.go | 64 +------ chain/types/ethtypes/eth_transactions.go | 80 ++++++++- itests/eth_legacy_transactions_test.go | 89 ++++++++-- itests/kit/evm.go | 33 +++- node/impl/full/eth.go | 3 +- 8 files changed, 413 insertions(+), 232 deletions(-) create mode 100644 chain/types/ethtypes/eth_legacy_155_transactions_test.go diff --git a/chain/types/ethtypes/eth_1559_transactions.go b/chain/types/ethtypes/eth_1559_transactions.go index 01e8f9e53b7..4753e37e4a4 100644 --- a/chain/types/ethtypes/eth_1559_transactions.go +++ b/chain/types/ethtypes/eth_1559_transactions.go @@ -3,11 +3,9 @@ package ethtypes import ( "fmt" - "golang.org/x/crypto/sha3" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" - gocrypto "github.com/filecoin-project/go-crypto" "github.com/filecoin-project/go-state-types/big" typescrypto "github.com/filecoin-project/go-state-types/crypto" @@ -55,12 +53,7 @@ func (tx *Eth1559TxArgs) ToUnsignedFilecoinMessage(from address.Address) (*types } func (tx *Eth1559TxArgs) ToRlpUnsignedMsg() ([]byte, error) { - packed, err := tx.packTxFields() - if err != nil { - return nil, err - } - - encoded, err := EncodeRLP(packed) + encoded, err := toRlpUnsignedMsg(tx) if err != nil { return nil, err } @@ -77,21 +70,11 @@ func (tx *Eth1559TxArgs) TxHash() (EthHash, error) { } func (tx *Eth1559TxArgs) ToRlpSignedMsg() ([]byte, error) { - packed1, err := tx.packTxFields() - if err != nil { - return nil, err - } - - packed2, err := packSigFields(tx.V, tx.R, tx.S) + encoded, err := toRlpSignedMsg(tx, tx.V, tx.R, tx.S) if err != nil { return nil, err } - - encoded, err := EncodeRLP(append(packed1, packed2...)) - if err != nil { - return nil, err - } - return append([]byte{0x02}, encoded...), nil + return append([]byte{EIP1559TxType}, encoded...), nil } func (tx *Eth1559TxArgs) Signature() (*typescrypto.Signature, error) { @@ -116,36 +99,7 @@ func (tx *Eth1559TxArgs) Signature() (*typescrypto.Signature, error) { } func (tx *Eth1559TxArgs) 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() + return sender(tx) } func (tx *Eth1559TxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) { @@ -189,7 +143,7 @@ func (tx *Eth1559TxArgs) InitialiseSignature(sig typescrypto.Signature) error { return xerrors.Errorf("RecoverSignature only supports Delegated signature") } - if len(sig.Data) != 65 { + if len(sig.Data) != EthEIP1559TxSignatureLen { return xerrors.Errorf("signature should be 65 bytes long, but got %d bytes", len(sig.Data)) } diff --git a/chain/types/ethtypes/eth_legacy_155_transactions.go b/chain/types/ethtypes/eth_legacy_155_transactions.go index c289904fb55..23859d9ff8c 100644 --- a/chain/types/ethtypes/eth_legacy_155_transactions.go +++ b/chain/types/ethtypes/eth_legacy_155_transactions.go @@ -3,101 +3,103 @@ package ethtypes import ( "fmt" - gocrypto "github.com/filecoin-project/go-crypto" - "golang.org/x/crypto/sha3" - "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. type EthLegacy155TxArgs struct { - legacyTx *EthLegacyHomesteadTxArgs + LegacyTx *EthLegacyHomesteadTxArgs } -// implement all interface methods func (tx *EthLegacy155TxArgs) ToEthTx(smsg *types.SignedMessage) (EthTx, error) { - ethTx, err := tx.legacyTx.ToEthTx(smsg) + from, err := EthAddressFromFilecoinAddress(smsg.Message.From) if err != nil { - return EthTx{}, fmt.Errorf("failed to convert legacy tx to eth tx: %w", err) + return EthTx{}, fmt.Errorf("sender was not an eth account") } - if err := validateEIP155ChainId(tx.legacyTx.V); err != nil { - return EthTx{}, fmt.Errorf("failed to validate EIP155 chain id: %w", err) + 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), } - ethTx.ChainID = build.Eip155ChainId return ethTx, nil } func (tx *EthLegacy155TxArgs) ToUnsignedFilecoinMessage(from address.Address) (*types.Message, error) { - if err := validateEIP155ChainId(tx.legacyTx.V); err != nil { + 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) + return tx.LegacyTx.ToUnsignedFilecoinMessage(from) } func (tx *EthLegacy155TxArgs) ToRlpUnsignedMsg() ([]byte, error) { - packedFields, err := tx.packTxFields() - if err != nil { - fmt.Println("failed to pack tx fields", err) - return nil, err - } - encoded, err := EncodeRLP(packedFields) - if err != nil { - fmt.Println("failed to encode tx fields", err) - return nil, err - } - return encoded, nil + return toRlpUnsignedMsg(tx) } func (tx *EthLegacy155TxArgs) TxHash() (EthHash, error) { - packed1, err := tx.packTxFields() - if err != nil { - return EthHash{}, err - } - packed1 = packed1[:len(packed1)-3] // remove r and s - - packed2, err := packSigFields(tx.legacyTx.V, tx.legacyTx.R, tx.legacyTx.S) - if err != nil { - return EthHash{}, err - } - encoded, err := EncodeRLP(append(packed1, packed2...)) + encoded, err := tx.ToRawTxBytesSigned() if err != nil { - return EthHash{}, err + return EthHash{}, fmt.Errorf("failed to encode rlp signed msg: %w", err) } return EthHashFromTxBytes(encoded), nil } -func (tx *EthLegacy155TxArgs) ToRlpSignedMsg() ([]byte, error) { +func (tx *EthLegacy155TxArgs) ToRawTxBytesSigned() ([]byte, error) { packed1, err := tx.packTxFields() if err != nil { return nil, err } - packed2, err := packSigFields(tx.legacyTx.V, tx.legacyTx.R, tx.legacyTx.S) + 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, err + 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 { + 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() + 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)...) @@ -116,48 +118,10 @@ func (tx *EthLegacy155TxArgs) Signature() (*typescrypto.Signature, error) { } func (tx *EthLegacy155TxArgs) Sender() (address.Address, error) { - if err := validateEIP155ChainId(tx.legacyTx.V); err != nil { + if err := validateEIP155ChainId(tx.LegacyTx.V); err != nil { return address.Address{}, fmt.Errorf("failed to validate EIP155 chain id: %w", err) } - 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) - } - - fmt.Println("sigData length is", len(sigData)) - - 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) - } - - fmt.Println("ea is", ea) - - return ea.ToFilecoinAddress() + return sender(tx) } var big8 = big.NewInt(8) @@ -174,7 +138,7 @@ func (tx *EthLegacy155TxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) // 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 + // Extract the 'v' value from the signature vValue := big.NewFromGo(big.NewInt(0).SetBytes(sig[64:])) chainIdMul := big.Mul(big.NewIntUnsigned(build.Eip155ChainId), big.NewInt(2)) @@ -226,38 +190,34 @@ func (tx *EthLegacy155TxArgs) InitialiseSignature(sig typescrypto.Signature) err return fmt.Errorf("failed to validate EIP155 chain id: %w", err) } - tx.legacyTx.R = r_ - tx.legacyTx.S = s_ - tx.legacyTx.V = v_ + 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) + nonce, err := formatInt(tx.LegacyTx.Nonce) if err != nil { return nil, err } // format gas price - gasPrice, err := formatBigInt(tx.legacyTx.GasPrice) + gasPrice, err := formatBigInt(tx.LegacyTx.GasPrice) if err != nil { return nil, err } - gasLimit, err := formatInt(tx.legacyTx.GasLimit) + gasLimit, err := formatInt(tx.LegacyTx.GasLimit) if err != nil { return nil, err } - value, err := formatBigInt(tx.legacyTx.Value) + value, err := formatBigInt(tx.LegacyTx.Value) if err != nil { return nil, err } - if err := validateEIP155ChainId(tx.legacyTx.V); err != nil { - return nil, fmt.Errorf("failed to validate EIP155 chain id: %w", err) - } - chainIdBigInt := big.NewIntUnsigned(build.Eip155ChainId) chainId, err := formatBigInt(chainIdBigInt) if err != nil { @@ -278,14 +238,13 @@ func (tx *EthLegacy155TxArgs) packTxFields() ([]interface{}, error) { nonce, gasPrice, gasLimit, - formatEthAddr(tx.legacyTx.To), + formatEthAddr(tx.LegacyTx.To), value, - tx.legacyTx.Input, + tx.LegacyTx.Input, chainId, r, s, } return res, nil - } func validateEIP155ChainId(v big.Int) error { diff --git a/chain/types/ethtypes/eth_legacy_155_transactions_test.go b/chain/types/ethtypes/eth_legacy_155_transactions_test.go new file mode 100644 index 00000000000..eaf2c803349 --- /dev/null +++ b/chain/types/ethtypes/eth_legacy_155_transactions_test.go @@ -0,0 +1,167 @@ +package ethtypes + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/lib/sigs" +) + +func TestEthLegacy155TxArgs(t *testing.T) { + testcases := []struct { + RawTx string + ExpectedNonce uint64 + ExpectedTo string + ExpectedInput string + ExpectedGasPrice big.Int + ExpectedGasLimit int + + ExpectErr bool + }{ + { + "0xf8708310aa048504a817c80083015f9094f8c911c68f6a6b912fe735bbd953c3379336cbf3880e19fb7ff12c8c308025a09abb6d2bb66c9520f76391169e1155e8426e41ce9888e16d93512083038de023a020bbd446f8dcbdd996a5e1d0663ec8e28c33d5cb254e323855331be71bc8b0fb", + 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 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) + assert.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) + if result != tt.expected { + t.Errorf("calcEIP155TxSignatureLen(%d) = %d, want %d", tt.chainID, result, tt.expected) + } + }) + } +} diff --git a/chain/types/ethtypes/eth_legacy_homestead_transactions.go b/chain/types/ethtypes/eth_legacy_homestead_transactions.go index 3bf69303e07..c463f44e363 100644 --- a/chain/types/ethtypes/eth_legacy_homestead_transactions.go +++ b/chain/types/ethtypes/eth_legacy_homestead_transactions.go @@ -3,11 +3,9 @@ package ethtypes import ( "fmt" - "golang.org/x/crypto/sha3" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" - gocrypto "github.com/filecoin-project/go-crypto" "github.com/filecoin-project/go-state-types/big" typescrypto "github.com/filecoin-project/go-state-types/crypto" @@ -106,15 +104,7 @@ func (tx *EthLegacyHomesteadTxArgs) ToVerifiableSignature(sig []byte) ([]byte, e } func (tx *EthLegacyHomesteadTxArgs) ToRlpUnsignedMsg() ([]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 + return toRlpUnsignedMsg(tx) } func (tx *EthLegacyHomesteadTxArgs) TxHash() (EthHash, error) { @@ -126,21 +116,7 @@ func (tx *EthLegacyHomesteadTxArgs) TxHash() (EthHash, error) { } func (tx *EthLegacyHomesteadTxArgs) ToRlpSignedMsg() ([]byte, error) { - packed1, err := tx.packTxFields() - if err != nil { - return nil, err - } - - packed2, err := packSigFields(tx.V, tx.R, tx.S) - if err != nil { - return nil, err - } - - encoded, err := EncodeRLP(append(packed1, packed2...)) - if err != nil { - return nil, err - } - return encoded, nil + return toRlpSignedMsg(tx, tx.V, tx.R, tx.S) } func (tx *EthLegacyHomesteadTxArgs) Signature() (*typescrypto.Signature, error) { @@ -172,41 +148,7 @@ func (tx *EthLegacyHomesteadTxArgs) Signature() (*typescrypto.Signature, error) } func (tx *EthLegacyHomesteadTxArgs) Sender() (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() + return sender(tx) } func (tx *EthLegacyHomesteadTxArgs) InitialiseSignature(sig typescrypto.Signature) error { diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index 46ed27443ca..556776df424 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -8,8 +8,10 @@ import ( mathbig "math/big" cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/crypto/sha3" "github.com/filecoin-project/go-address" + gocrypto "github.com/filecoin-project/go-crypto" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" builtintypes "github.com/filecoin-project/go-state-types/builtin" @@ -171,7 +173,7 @@ func EthTransactionFromSignedFilecoinMessage(smsg *types.SignedMessage) (EthTran return legacyTx, nil case EthLegacy155TxSignaturePrefix: tx := &EthLegacy155TxArgs{ - legacyTx: legacyTx, + LegacyTx: legacyTx, } if err := tx.InitialiseSignature(smsg.Signature); err != nil { return nil, fmt.Errorf("failed to initialise signature: %w", err) @@ -224,7 +226,7 @@ func ParseEthTransaction(data []byte) (EthTransaction, error) { if data[0] > 0x7f { tx, err := parseLegacyTx(data) if err != nil { - return nil, fmt.Errorf("failed to parse legacy homestead transaction: %w", err) + return nil, fmt.Errorf("failed to parse legacy transaction: %w", err) } return tx, nil } @@ -516,6 +518,78 @@ func parseLegacyTx(data []byte) (EthTransaction, error) { } return &EthLegacy155TxArgs{ - legacyTx: tx, + 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() +} diff --git a/itests/eth_legacy_transactions_test.go b/itests/eth_legacy_transactions_test.go index 6a65a337dd9..3928e0ada39 100644 --- a/itests/eth_legacy_transactions_test.go +++ b/itests/eth_legacy_transactions_test.go @@ -13,6 +13,7 @@ import ( "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/itests/kit" @@ -55,7 +56,7 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { S: big.Zero(), } - client.EVM().SignLegacyTransaction(&tx, key.PrivateKey) + client.EVM().SignLegacyHomesteadTransaction(&tx, key.PrivateKey) // Mangle signature tx.V.Int.Xor(tx.V.Int, big.NewInt(1).Int) @@ -66,7 +67,7 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { require.Error(t, err) // Submit transaction with valid signature - client.EVM().SignLegacyTransaction(&tx, key.PrivateKey) + client.EVM().SignLegacyHomesteadTransaction(&tx, key.PrivateKey) hash := client.EVM().SubmitTransaction(ctx, &tx) @@ -101,10 +102,7 @@ func TestLegacyValueTransferValidSignature(t *testing.T) { require.EqualValues(t, tx.V, ethTx.V) } -func TestEIP155Tx(t *testing.T) { - rlpHex := "f86d8083030e1b83291e739490322092a524e0e43a2ec80ec6f35100d24799f28898a7d9b8314c000080820298a0dc782de2fec8cd45e699075beb756ad731943d19a33332fa36e72fd94802ed10a056b3e1e36f2851402661daf0b3284bc8d15db005d1fade908c1117c6ae37429d" - rlpBytes, err := hex.DecodeString(rlpHex) - require.NoError(t, err) +func TestLegacyEIP155ValueTransferValidSignature(t *testing.T) { blockTime := 100 * time.Millisecond client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) @@ -113,25 +111,84 @@ func TestEIP155Tx(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - from := "0xf0a3b487d026406F5ca18891dB6896a3dA900F29" - ea, err := ethtypes.ParseEthAddress(from) + // 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: ðAddr, + To: ðAddr2, + Value: ethtypes.EthBigInt(big.NewInt(100)), + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) + require.NoError(t, err) + + legacyTx := ðtypes.EthLegacyHomesteadTxArgs{ + Value: big.NewInt(100), + Nonce: 0, + To: ðAddr2, + GasPrice: types.NanoFil, + GasLimit: int(gaslimit), + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + tx := ðtypes.EthLegacy155TxArgs{ + LegacyTx: legacyTx, + } + + client.EVM().SignLegacyEIP155Transaction(tx, key.PrivateKey, big.NewInt(build.Eip155ChainId)) + // Mangle signature + tx.LegacyTx.V.Int.Xor(tx.LegacyTx.V.Int, big.NewInt(1).Int) + + signed, err := tx.ToRawTxBytesSigned() require.NoError(t, err) - fa, err := ea.ToFilecoinAddress() + // 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) - fmt.Println("fil addr is", fa.String()) - kit.SendFunds(ctx, t, client, fa, types.FromFil(20000)) - hash, err := client.EthSendRawTransaction(ctx, rlpBytes) + 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) + + require.EqualValues(t, ethAddr, ethTx.From) + require.EqualValues(t, ethAddr2, *ethTx.To) + require.EqualValues(t, tx.LegacyTx.Nonce, ethTx.Nonce) + require.EqualValues(t, hash, ethTx.Hash) + require.EqualValues(t, tx.LegacyTx.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, tx.LegacyTx.GasLimit, ethTx.Gas) + require.EqualValues(t, tx.LegacyTx.GasPrice, *ethTx.GasPrice) + require.EqualValues(t, tx.LegacyTx.R, ethTx.R) + require.EqualValues(t, tx.LegacyTx.S, ethTx.S) + require.EqualValues(t, tx.LegacyTx.V, ethTx.V) } func TestLegacyContractInvocation(t *testing.T) { @@ -152,7 +209,7 @@ func TestLegacyContractInvocation(t *testing.T) { tx, err := deployLegacyContractTx(t, ctx, client, ethAddr) require.NoError(t, err) - client.EVM().SignLegacyTransaction(tx, key.PrivateKey) + client.EVM().SignLegacyHomesteadTransaction(tx, key.PrivateKey) // Mangle signature tx.V.Int.Xor(tx.V.Int, big.NewInt(1).Int) @@ -163,7 +220,7 @@ func TestLegacyContractInvocation(t *testing.T) { require.Error(t, err) // Submit transaction with valid signature - client.EVM().SignLegacyTransaction(tx, key.PrivateKey) + client.EVM().SignLegacyHomesteadTransaction(tx, key.PrivateKey) hash := client.EVM().SubmitTransaction(ctx, tx) @@ -208,7 +265,7 @@ func TestLegacyContractInvocation(t *testing.T) { S: big.Zero(), } - client.EVM().SignLegacyTransaction(&invokeTx, key.PrivateKey) + client.EVM().SignLegacyHomesteadTransaction(&invokeTx, key.PrivateKey) // Mangle signature invokeTx.V.Int.Xor(invokeTx.V.Int, big.NewInt(1).Int) @@ -219,7 +276,7 @@ func TestLegacyContractInvocation(t *testing.T) { require.Error(t, err) // Submit transaction with valid signature - client.EVM().SignLegacyTransaction(&invokeTx, key.PrivateKey) + client.EVM().SignLegacyHomesteadTransaction(&invokeTx, key.PrivateKey) hash = client.EVM().SubmitTransaction(ctx, &invokeTx) receipt, err = client.EVM().WaitTransaction(ctx, hash) diff --git a/itests/kit/evm.go b/itests/kit/evm.go index f4c02c57c99..083891798b5 100644 --- a/itests/kit/evm.go +++ b/itests/kit/evm.go @@ -44,8 +44,37 @@ func (f *TestFullNode) EVM() *EVM { return &EVM{f} } -// SignLegacyTransaction signs a legacy Homstead Ethereum transaction in place with the supplied private key. -func (e *EVM) SignLegacyTransaction(tx *ethtypes.EthLegacyHomesteadTxArgs, privKey []byte) { +// 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...) + + fmt.Println(build.Eip155ChainId) + + 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) diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index ec53468e466..c40fc25c3e5 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -3,7 +3,6 @@ package full import ( "bytes" "context" - "encoding/hex" "errors" "fmt" "os" @@ -820,7 +819,7 @@ func (a *EthModule) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) } func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) { - log.Warn("rawTx", "rawTx", hex.EncodeToString(rawTx)) + log.Warnw("raw tx", "raw", rawTx) txArgs, err := ethtypes.ParseEthTransaction(rawTx) if err != nil { From fd20cd730bc140d71fdf431aa9d0b89b12aa804f Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Mon, 6 May 2024 13:56:41 +0530 Subject: [PATCH 23/27] add docs --- chain/types/ethtypes/eth_legacy_155_transactions.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/chain/types/ethtypes/eth_legacy_155_transactions.go b/chain/types/ethtypes/eth_legacy_155_transactions.go index 23859d9ff8c..c9d8c703a2f 100644 --- a/chain/types/ethtypes/eth_legacy_155_transactions.go +++ b/chain/types/ethtypes/eth_legacy_155_transactions.go @@ -15,6 +15,10 @@ 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 } @@ -141,6 +145,7 @@ func (tx *EthLegacy155TxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) // Extract the 'v' value from the signature vValue := big.NewFromGo(big.NewInt(0).SetBytes(sig[64:])) + // 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) From a98727ba32c226014953d0d0e2b0f01a3f2880a5 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Mon, 6 May 2024 14:31:04 +0530 Subject: [PATCH 24/27] tests --- .../eth_legacy_155_transactions_test.go | 167 ++++++++++-------- node/impl/full/eth.go | 3 +- 2 files changed, 96 insertions(+), 74 deletions(-) diff --git a/chain/types/ethtypes/eth_legacy_155_transactions_test.go b/chain/types/ethtypes/eth_legacy_155_transactions_test.go index eaf2c803349..96abbf11ca0 100644 --- a/chain/types/ethtypes/eth_legacy_155_transactions_test.go +++ b/chain/types/ethtypes/eth_legacy_155_transactions_test.go @@ -1,85 +1,106 @@ package ethtypes import ( - "encoding/hex" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-state-types/big" - - "github.com/filecoin-project/lotus/lib/sigs" + builtintypes "github.com/filecoin-project/go-state-types/builtin" ) -func TestEthLegacy155TxArgs(t *testing.T) { - testcases := []struct { - RawTx string - ExpectedNonce uint64 - ExpectedTo string - ExpectedInput string - ExpectedGasPrice big.Int - ExpectedGasLimit int - - ExpectErr bool - }{ - { - "0xf8708310aa048504a817c80083015f9094f8c911c68f6a6b912fe735bbd953c3379336cbf3880e19fb7ff12c8c308025a09abb6d2bb66c9520f76391169e1155e8426e41ce9888e16d93512083038de023a020bbd446f8dcbdd996a5e1d0663ec8e28c33d5cb254e323855331be71bc8b0fb", - 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 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) { @@ -118,7 +139,7 @@ func TestDeriveEIP155ChainId(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := deriveEIP155ChainId(tt.v) - assert.True(t, result.Equals(tt.expectedChainId), "Expected %s, got %s for V=%s", tt.expectedChainId.String(), result.String(), tt.v.String()) + require.True(t, result.Equals(tt.expectedChainId), "Expected %s, got %s for V=%s", tt.expectedChainId.String(), result.String(), tt.v.String()) }) } } diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index c40fc25c3e5..4d19a6cd289 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -3,6 +3,7 @@ package full import ( "bytes" "context" + "encoding/hex" "errors" "fmt" "os" @@ -819,7 +820,7 @@ func (a *EthModule) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) } func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) { - log.Warnw("raw tx", "raw", rawTx) + fmt.Println("raw tx", hex.EncodeToString(rawTx)) txArgs, err := ethtypes.ParseEthTransaction(rawTx) if err != nil { From 24d9ece42d3ce58145303937e9e6af3cdfe33780 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Mon, 6 May 2024 14:32:22 +0530 Subject: [PATCH 25/27] remove print stmt --- node/impl/full/eth.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 4d19a6cd289..3f5bfb6af86 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -3,7 +3,6 @@ package full import ( "bytes" "context" - "encoding/hex" "errors" "fmt" "os" @@ -820,8 +819,6 @@ func (a *EthModule) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) } func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) { - fmt.Println("raw tx", hex.EncodeToString(rawTx)) - txArgs, err := ethtypes.ParseEthTransaction(rawTx) if err != nil { return ethtypes.EmptyEthHash, err From 7f202a44ab78980119eb692096e42ef4131c82ed Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Mon, 6 May 2024 15:40:38 +0530 Subject: [PATCH 26/27] remove print stmt --- itests/kit/evm.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/itests/kit/evm.go b/itests/kit/evm.go index 083891798b5..865d5c2f2b0 100644 --- a/itests/kit/evm.go +++ b/itests/kit/evm.go @@ -55,8 +55,6 @@ func (e *EVM) SignLegacyEIP155Transaction(tx *ethtypes.EthLegacy155TxArgs, privK signature.Data = append([]byte{ethtypes.EthLegacy155TxSignaturePrefix}, signature.Data...) - fmt.Println(build.Eip155ChainId) - chainIdMul := big.Mul(chainID, big.NewInt(2)) vVal := big.Add(chainIdMul, big.NewIntUnsigned(35)) From 0dfac4b75910fff5cc9c366b60a19b21b3b001cc Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Mon, 6 May 2024 16:18:06 +0530 Subject: [PATCH 27/27] validate signature --- chain/types/ethtypes/eth_legacy_155_transactions.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chain/types/ethtypes/eth_legacy_155_transactions.go b/chain/types/ethtypes/eth_legacy_155_transactions.go index c9d8c703a2f..dc591a025b1 100644 --- a/chain/types/ethtypes/eth_legacy_155_transactions.go +++ b/chain/types/ethtypes/eth_legacy_155_transactions.go @@ -145,6 +145,10 @@ func (tx *EthLegacy155TxArgs) ToVerifiableSignature(sig []byte) ([]byte, error) // 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)