lotus/api/eth_transactions.go
2022-11-10 02:51:57 +02:00

509 lines
12 KiB
Go

package api
import (
"bytes"
"encoding/binary"
"fmt"
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"
"github.com/filecoin-project/go-state-types/builtin/v10/eam"
typescrypto "github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
)
const Eip1559TxType = 2
type EthTx struct {
ChainID EthUint64 `json:"chainId"`
Nonce EthUint64 `json:"nonce"`
Hash EthHash `json:"hash"`
BlockHash EthHash `json:"blockHash"`
BlockNumber EthUint64 `json:"blockNumber"`
TransactionIndex EthUint64 `json:"transacionIndex"`
From EthAddress `json:"from"`
To *EthAddress `json:"to"`
Value EthBigInt `json:"value"`
Type EthUint64 `json:"type"`
Input EthBytes `json:"input"`
Gas EthUint64 `json:"gas"`
GasLimit *EthUint64 `json:"gasLimit,omitempty"`
MaxFeePerGas EthBigInt `json:"maxFeePerGas"`
MaxPriorityFeePerGas EthBigInt `json:"maxPriorityFeePerGas"`
V EthBytes `json:"v"`
R EthBytes `json:"r"`
S EthBytes `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:"maxPrioritiyFeePerGas"`
GasLimit int `json:"gasLimit"`
Input []byte `json:"input"`
V []byte `json:"v"`
R []byte `json:"r"`
S []byte `json:"s"`
}
func NewEthTxArgsFromMessage(msg *types.Message) (EthTxArgs, error) {
var (
to *EthAddress
decodedParams []byte
paramsReader = bytes.NewReader(msg.Params)
)
if msg.To == builtintypes.EthereumAddressManagerActorAddr {
switch msg.Method {
case builtintypes.MethodsEAM.Create:
var create eam.CreateParams
if err := create.UnmarshalCBOR(paramsReader); err != nil {
return EthTxArgs{}, err
}
decodedParams = create.Initcode
case builtintypes.MethodsEAM.Create2:
var create2 eam.Create2Params
if err := create2.UnmarshalCBOR(paramsReader); err != nil {
return EthTxArgs{}, err
}
decodedParams = create2.Initcode
default:
return EthTxArgs{}, fmt.Errorf("unsupported EAM method")
}
} else {
addr, err := EthAddressFromFilecoinAddress(msg.To)
if err != nil {
return EthTxArgs{}, err
}
to = &addr
if len(msg.Params) > 0 {
params, err := cbg.ReadByteArray(paramsReader, uint64(len(msg.Params)))
if err != nil {
return EthTxArgs{}, err
}
decodedParams = params
}
}
return EthTxArgs{
ChainID: build.Eip155ChainId,
Nonce: int(msg.Nonce),
To: to,
Value: msg.Value,
Input: decodedParams,
MaxFeePerGas: msg.GasFeeCap,
MaxPriorityFeePerGas: msg.GasPremium,
GasLimit: int(msg.GasLimit),
}, nil
}
func (tx *EthTxArgs) ToSignedMessage() (*types.SignedMessage, error) {
from, err := tx.Sender()
if err != nil {
return nil, err
}
var to address.Address
var params []byte
if tx.To == nil && tx.Input == nil {
return nil, fmt.Errorf("to and input cannot both be empty")
}
var method abi.MethodNum
if tx.To == nil {
// TODO https://github.com/filecoin-project/ref-fvm/issues/992
// TODO unify with applyEvmMsg
// this is a contract creation
to = builtintypes.EthereumAddressManagerActorAddr
params2, err := actors.SerializeParams(&eam.CreateParams{
Initcode: tx.Input,
Nonce: uint64(tx.Nonce),
})
if err != nil {
return nil, fmt.Errorf("failed to serialize Create params: %w", err)
}
params = params2
method = builtintypes.MethodsEAM.Create
} else {
addr, err := tx.To.ToFilecoinAddress()
if err != nil {
return nil, err
}
to = addr
if len(tx.Input) > 0 {
var buf bytes.Buffer
if err := cbg.WriteByteArray(&buf, tx.Input); err != nil {
return nil, fmt.Errorf("failed to encode tx input into a cbor byte-string")
}
params = buf.Bytes()
method = builtintypes.MethodsEVM.InvokeContract
} else {
method = builtintypes.MethodSend
}
}
msg := &types.Message{
Nonce: uint64(tx.Nonce),
From: from,
To: to,
Value: tx.Value,
Method: method,
Params: params,
GasLimit: int64(tx.GasLimit),
GasFeeCap: tx.MaxFeePerGas,
GasPremium: tx.MaxPriorityFeePerGas,
}
sig, err := tx.Signature()
if err != nil {
return nil, err
}
signedMsg := types.SignedMessage{
Message: *msg,
Signature: *sig,
}
return &signedMsg, nil
}
func (tx *EthTxArgs) HashedOriginalRlpMsg() ([]byte, error) {
msg, err := tx.OriginalRlpMsg()
if err != nil {
return nil, err
}
hasher := sha3.NewLegacyKeccak256()
hasher.Write(msg)
hash := hasher.Sum(nil)
return hash, nil
}
func (tx *EthTxArgs) OriginalRlpMsg() ([]byte, 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
}
encoded, err := EncodeRLP(res)
if err != nil {
return nil, err
}
return append([]byte{0x02}, encoded...), nil
}
func (tx *EthTxArgs) Signature() (*typescrypto.Signature, error) {
if tx.V == nil || tx.R == nil || tx.S == nil {
return nil, fmt.Errorf("one of V, R, or S is nil")
}
sig := append([]byte{}, tx.R...)
sig = append(sig, tx.S...)
sig = append(sig, tx.V...)
if len(sig) != 65 {
return nil, fmt.Errorf("signature is not 65 bytes")
}
return &typescrypto.Signature{
Type: typescrypto.SigTypeDelegated, Data: sig,
}, nil
}
func (tx *EthTxArgs) Sender() (address.Address, error) {
msg, err := tx.OriginalRlpMsg()
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
}
// if we get an uncompressed public key (that's what we get from the library,
// but putting this check here for defensiveness), strip the prefix
if pubk[0] == 0x04 {
pubk = pubk[1:]
}
// Calculate the f4 address based on the keccak hash of the pubkey.
hasher.Reset()
hasher.Write(pubk)
ethAddr := hasher.Sum(nil)[12:]
return address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, ethAddr)
}
func parseEip1559Tx(data []byte) (*EthTxArgs, error) {
if data[0] != 2 {
return nil, fmt.Errorf("not an EIP-1559 transaction: first byte is not 2")
}
d, err := DecodeRLP(data[1:])
if err != nil {
return nil, err
}
decoded, ok := d.([]interface{})
if !ok {
return nil, fmt.Errorf("not an EIP-1559 transaction: decoded data is not a list")
}
if len(decoded) != 9 && len(decoded) != 12 {
return nil, fmt.Errorf("not an EIP-1559 transaction: should have 6 or 9 elements in the 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")
}
V, err := parseBytes(decoded[9])
if err != nil {
return nil, err
}
if len(V) == 0 {
V = []byte{0}
}
R, err := parseBytes(decoded[10])
if err != nil {
return nil, err
}
S, err := parseBytes(decoded[11])
if err != nil {
return nil, err
}
args := EthTxArgs{
ChainID: chainId,
Nonce: nonce,
To: to,
MaxPriorityFeePerGas: maxPriorityFeePerGas,
MaxFeePerGas: maxFeePerGas,
GasLimit: gasLimit,
Value: value,
Input: input,
R: padLeadingZeros(R, 32),
S: padLeadingZeros(S, 32),
V: V,
}
return &args, nil
}
func ParseEthTxArgs(data []byte) (*EthTxArgs, error) {
if data[0] > 0x7f {
// legacy transaction
return nil, fmt.Errorf("legacy transaction is not supported")
} else if data[0] == 1 {
// EIP-2930
return nil, fmt.Errorf("EIP-2930 transaction is not supported")
} else if data[0] == Eip1559TxType {
// EIP-1559
return parseEip1559Tx(data)
}
return nil, fmt.Errorf("unsupported transaction type")
}
func padLeadingZeros(data []byte, length int) []byte {
if len(data) >= length {
return data
}
zeros := make([]byte, length-len(data))
return append(zeros, data...)
}
func removeLeadingZeros(data []byte) []byte {
firstNonZeroIndex := len(data)
for i, b := range data {
if b > 0 {
firstNonZeroIndex = i
break
}
}
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")
}
return val, nil
}
func parseEthAddr(v interface{}) (*EthAddress, error) {
b, err := parseBytes(v)
if err != nil {
return nil, err
}
if b == nil || len(b) == 0 {
return nil, nil
}
addr, err := EthAddressFromBytes(b)
if err != nil {
return nil, err
}
return &addr, nil
}