diff --git a/chain/messagesigner/messagesigner.go b/chain/messagesigner/messagesigner.go index 6a622dd57..84dd6d0aa 100644 --- a/chain/messagesigner/messagesigner.go +++ b/chain/messagesigner/messagesigner.go @@ -202,7 +202,7 @@ func (ms *MessageSigner) dstoreKey(addr address.Address) datastore.Key { func SigningBytes(msg *types.Message, sigType crypto.SigType) ([]byte, error) { if sigType == crypto.SigTypeDelegated { - txArgs, err := ethtypes.EthTxArgsFromMessage(msg) + txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(msg) if err != nil { return nil, xerrors.Errorf("failed to reconstruct eth transaction: %w", err) } diff --git a/chain/signatures.go b/chain/signatures.go index c70ef5b74..1dc67fd2c 100644 --- a/chain/signatures.go +++ b/chain/signatures.go @@ -22,7 +22,7 @@ func AuthenticateMessage(msg *types.SignedMessage, signer address.Address) error typ := msg.Signature.Type switch typ { case crypto.SigTypeDelegated: - txArgs, err := ethtypes.EthTxArgsFromMessage(&msg.Message) + txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(&msg.Message) if err != nil { return xerrors.Errorf("failed to reconstruct eth transaction: %w", err) } diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index 18b922234..9f9df2381 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -56,7 +56,44 @@ type EthTxArgs struct { S big.Int `json:"s"` } -func EthTxArgsFromMessage(msg *types.Message) (EthTxArgs, error) { +// EthTxFromSignedEthMessage does NOT populate: +// - BlockHash +// - BlockNumber +// - TransactionIndex +// - From +// - Hash +func EthTxFromSignedEthMessage(smsg *types.SignedMessage) (EthTx, error) { + if smsg.Signature.Type != typescrypto.SigTypeDelegated { + return EthTx{}, xerrors.Errorf("signature is not delegated type, is type: %d", smsg.Signature.Type) + } + + txArgs, err := EthTxArgsFromUnsignedEthMessage(&smsg.Message) + if err != nil { + return EthTx{}, xerrors.Errorf("failed to convert the unsigned message: %w", err) + } + + r, s, v, err := RecoverSignature(smsg.Signature) + if err != nil { + return EthTx{}, xerrors.Errorf("failed to recover signature: %w", err) + } + + return EthTx{ + Nonce: EthUint64(txArgs.Nonce), + ChainID: EthUint64(txArgs.ChainID), + To: txArgs.To, + Value: EthBigInt(txArgs.Value), + Type: Eip1559TxType, + Gas: EthUint64(txArgs.GasLimit), + MaxFeePerGas: EthBigInt(txArgs.MaxFeePerGas), + MaxPriorityFeePerGas: EthBigInt(txArgs.MaxPriorityFeePerGas), + V: v, + R: r, + S: s, + Input: txArgs.Input, + }, nil +} + +func EthTxArgsFromUnsignedEthMessage(msg *types.Message) (EthTxArgs, error) { var ( to *EthAddress params []byte diff --git a/itests/eth_account_abstraction_test.go b/itests/eth_account_abstraction_test.go index 86b7e3e9b..e6d9723bf 100644 --- a/itests/eth_account_abstraction_test.go +++ b/itests/eth_account_abstraction_test.go @@ -72,7 +72,7 @@ func TestEthAccountAbstraction(t *testing.T) { msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) require.NoError(t, err) - txArgs, err := ethtypes.EthTxArgsFromMessage(msgFromPlaceholder) + txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(msgFromPlaceholder) require.NoError(t, err) digest, err := txArgs.ToRlpUnsignedMsg() @@ -108,7 +108,7 @@ func TestEthAccountAbstraction(t *testing.T) { msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) require.NoError(t, err) - txArgs, err = ethtypes.EthTxArgsFromMessage(msgFromPlaceholder) + txArgs, err = ethtypes.EthTxArgsFromUnsignedEthMessage(msgFromPlaceholder) require.NoError(t, err) digest, err = txArgs.ToRlpUnsignedMsg() @@ -180,7 +180,7 @@ func TestEthAccountAbstractionFailure(t *testing.T) { require.NoError(t, err) msgFromPlaceholder.Value = abi.TokenAmount(types.MustParseFIL("1000")) - txArgs, err := ethtypes.EthTxArgsFromMessage(msgFromPlaceholder) + txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(msgFromPlaceholder) require.NoError(t, err) digest, err := txArgs.ToRlpUnsignedMsg() @@ -218,7 +218,7 @@ func TestEthAccountAbstractionFailure(t *testing.T) { msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) require.NoError(t, err) - txArgs, err = ethtypes.EthTxArgsFromMessage(msgFromPlaceholder) + txArgs, err = ethtypes.EthTxArgsFromUnsignedEthMessage(msgFromPlaceholder) require.NoError(t, err) digest, err = txArgs.ToRlpUnsignedMsg() diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 14c69b2e1..5dbca7357 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -212,7 +212,7 @@ func (a *EthModule) EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthH if err != nil { return ethtypes.EthBlock{}, xerrors.Errorf("error loading tipset %s: %w", ts, err) } - return newEthBlockFromFilecoinTipSet(ctx, ts, fullTxInfo, a.Chain, a.ChainAPI, a.StateAPI) + return newEthBlockFromFilecoinTipSet(ctx, ts, fullTxInfo, a.Chain, a.StateAPI) } func (a *EthModule) parseBlkParam(ctx context.Context, blkParam string) (tipset *types.TipSet, err error) { @@ -249,7 +249,7 @@ func (a *EthModule) EthGetBlockByNumber(ctx context.Context, blkParam string, fu if err != nil { return ethtypes.EthBlock{}, err } - return newEthBlockFromFilecoinTipSet(ctx, ts, fullTxInfo, a.Chain, a.ChainAPI, a.StateAPI) + return newEthBlockFromFilecoinTipSet(ctx, ts, fullTxInfo, a.Chain, a.StateAPI) } func (a *EthModule) EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) { @@ -270,8 +270,8 @@ func (a *EthModule) EthGetTransactionByHash(ctx context.Context, txHash *ethtype // first, try to get the cid from mined transactions msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, c, api.LookbackNoLimit, true) - if err == nil { - tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, -1, a.Chain, a.StateAPI) + if err == nil && msgLookup != nil { + tx, err := newEthTxFromMessageLookup(ctx, msgLookup, -1, a.Chain, a.StateAPI) if err == nil { return &tx, nil } @@ -287,7 +287,7 @@ func (a *EthModule) EthGetTransactionByHash(ctx context.Context, txHash *ethtype for _, p := range pending { if p.Cid() == c { - tx, err := NewEthTxFromFilecoinMessage(ctx, p, a.StateAPI) + tx, err := newEthTxFromSignedMessage(ctx, p, a.StateAPI) if err != nil { return nil, fmt.Errorf("could not convert Filecoin message into tx: %s", err) } @@ -336,7 +336,7 @@ func (a *EthModule) EthGetMessageCidByTransactionHash(ctx context.Context, txHas } func (a *EthModule) EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) { - hash, err := EthTxHashFromFilecoinMessageCid(ctx, cid, a.StateAPI) + hash, err := EthTxHashFromMessageCid(ctx, cid, a.StateAPI) if hash == ethtypes.EmptyEthHash { // not found return nil, nil @@ -379,7 +379,7 @@ func (a *EthModule) EthGetTransactionReceipt(ctx context.Context, txHash ethtype return nil, nil } - tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, -1, a.Chain, a.StateAPI) + tx, err := newEthTxFromMessageLookup(ctx, msgLookup, -1, a.Chain, a.StateAPI) if err != nil { return nil, nil } @@ -605,7 +605,7 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint for ts.Height() >= abi.ChainEpoch(oldestBlkHeight) { // Unfortunately we need to rebuild the full message view so we can // totalize gas used in the tipset. - block, err := newEthBlockFromFilecoinTipSet(ctx, ts, false, a.Chain, a.ChainAPI, a.StateAPI) + block, err := newEthBlockFromFilecoinTipSet(ctx, ts, false, a.Chain, a.StateAPI) if err != nil { return ethtypes.EthFeeHistory{}, fmt.Errorf("cannot create eth block: %v", err) } @@ -1233,7 +1233,7 @@ func ethFilterResultFromEvents(evs []*filter.CollectedEvent, sa StateAPI) (*etht return nil, err } - log.TransactionHash, err = EthTxHashFromFilecoinMessageCid(context.TODO(), ev.MsgCid, sa) + log.TransactionHash, err = EthTxHashFromMessageCid(context.TODO(), ev.MsgCid, sa) if err != nil { return nil, err } @@ -1275,7 +1275,7 @@ func ethFilterResultFromMessages(cs []*types.SignedMessage, sa StateAPI) (*ethty res := ðtypes.EthFilterResult{} for _, c := range cs { - hash, err := EthTxHashFromSignedFilecoinMessage(context.TODO(), c, sa) + hash, err := EthTxHashFromSignedMessage(context.TODO(), c, sa) if err != nil { return nil, err } @@ -1376,7 +1376,7 @@ func (e *ethSubscription) start(ctx context.Context) { case *filter.CollectedEvent: resp.Result, err = ethFilterResultFromEvents([]*filter.CollectedEvent{vt}, e.StateAPI) case *types.TipSet: - eb, err := newEthBlockFromFilecoinTipSet(ctx, vt, true, e.Chain, e.ChainAPI, e.StateAPI) + eb, err := newEthBlockFromFilecoinTipSet(ctx, vt, true, e.Chain, e.StateAPI) if err != nil { break } @@ -1410,7 +1410,7 @@ func (e *ethSubscription) stop() { } } -func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTxInfo bool, cs *store.ChainStore, ca ChainAPI, sa StateAPI) (ethtypes.EthBlock, error) { +func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTxInfo bool, cs *store.ChainStore, sa StateAPI) (ethtypes.EthBlock, error) { parent, err := cs.LoadTipSet(ctx, ts.Parents()) if err != nil { return ethtypes.EthBlock{}, err @@ -1449,7 +1449,7 @@ func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTx } gasUsed += msgLookup.Receipt.GasUsed - tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, txIdx, cs, sa) + tx, err := newEthTxFromMessageLookup(ctx, msgLookup, txIdx, cs, sa) if err != nil { return ethtypes.EthBlock{}, nil } @@ -1509,11 +1509,11 @@ func lookupEthAddress(ctx context.Context, addr address.Address, sa StateAPI) (e return ethtypes.EthAddressFromFilecoinAddress(idAddr) } -func EthTxHashFromFilecoinMessageCid(ctx context.Context, c cid.Cid, sa StateAPI) (ethtypes.EthHash, error) { +func EthTxHashFromMessageCid(ctx context.Context, c cid.Cid, sa StateAPI) (ethtypes.EthHash, error) { smsg, err := sa.Chain.GetSignedMessage(ctx, c) if err == nil { // This is an Eth Tx, Secp message, Or BLS message in the mpool - return EthTxHashFromSignedFilecoinMessage(ctx, smsg, sa) + return EthTxHashFromSignedMessage(ctx, smsg, sa) } _, err = sa.Chain.GetMessage(ctx, c) @@ -1525,93 +1525,44 @@ func EthTxHashFromFilecoinMessageCid(ctx context.Context, c cid.Cid, sa StateAPI return ethtypes.EmptyEthHash, nil } -func EthTxHashFromSignedFilecoinMessage(ctx context.Context, smsg *types.SignedMessage, sa StateAPI) (ethtypes.EthHash, error) { +func EthTxHashFromSignedMessage(ctx context.Context, smsg *types.SignedMessage, sa StateAPI) (ethtypes.EthHash, error) { if smsg.Signature.Type == crypto.SigTypeDelegated { - ethTx, err := NewEthTxFromFilecoinMessage(ctx, smsg, sa) + ethTx, err := newEthTxFromSignedMessage(ctx, smsg, sa) if err != nil { return ethtypes.EmptyEthHash, err } return ethTx.Hash, nil + } else if smsg.Signature.Type == crypto.SigTypeSecp256k1 { + return ethtypes.EthHashFromCid(smsg.Cid()) + } else { // BLS message + return ethtypes.EthHashFromCid(smsg.Message.Cid()) } - - return ethtypes.EthHashFromCid(smsg.Cid()) } -func NewEthTxFromFilecoinMessage(ctx context.Context, smsg *types.SignedMessage, sa StateAPI) (ethtypes.EthTx, error) { - // Ignore errors here so we can still parse non-eth messages - fromEthAddr, _ := lookupEthAddress(ctx, smsg.Message.From, sa) - toEthAddr, _ := lookupEthAddress(ctx, smsg.Message.To, sa) - - toAddr := &toEthAddr - input := smsg.Message.Params +func newEthTxFromSignedMessage(ctx context.Context, smsg *types.SignedMessage, sa StateAPI) (ethtypes.EthTx, error) { + var tx ethtypes.EthTx var err error - // Check to see if we need to decode as contract deployment. - // We don't need to resolve the to address, because there's only one form (an ID). - if smsg.Message.To == builtintypes.EthereumAddressManagerActorAddr { - switch smsg.Message.Method { - case builtintypes.MethodsEAM.Create: - toAddr = nil - var params eam.CreateParams - err = params.UnmarshalCBOR(bytes.NewReader(smsg.Message.Params)) - input = params.Initcode - case builtintypes.MethodsEAM.Create2: - toAddr = nil - var params eam.Create2Params - err = params.UnmarshalCBOR(bytes.NewReader(smsg.Message.Params)) - input = params.Initcode - case builtintypes.MethodsEAM.CreateExternal: - toAddr = nil - var params abi.CborBytes - err = params.UnmarshalCBOR(bytes.NewReader(smsg.Message.Params)) - input = []byte(params) - } - if err != nil { - return ethtypes.EthTx{}, err - } - } - // Otherwise, try to decode as a cbor byte array. - // TODO: Actually check if this is an ethereum call. This code will work for demo purposes, but is not correct. - if toAddr != nil { - if decodedParams, err := cbg.ReadByteArray(bytes.NewReader(smsg.Message.Params), uint64(len(smsg.Message.Params))); err == nil { - input = decodedParams - } - } - - r, s, v, err := ethtypes.RecoverSignature(smsg.Signature) - if err != nil { - // we don't want to return error if the message is not an Eth tx - r, s, v = ethtypes.EthBigIntZero, ethtypes.EthBigIntZero, ethtypes.EthBigIntZero - } - - tx := ethtypes.EthTx{ - Nonce: ethtypes.EthUint64(smsg.Message.Nonce), - ChainID: ethtypes.EthUint64(build.Eip155ChainId), - From: fromEthAddr, - To: toAddr, - Value: ethtypes.EthBigInt(smsg.Message.Value), - Type: ethtypes.EthUint64(2), - Input: input, - Gas: ethtypes.EthUint64(smsg.Message.GasLimit), - MaxFeePerGas: ethtypes.EthBigInt(smsg.Message.GasFeeCap), - MaxPriorityFeePerGas: ethtypes.EthBigInt(smsg.Message.GasPremium), - V: v, - R: r, - S: s, - } // This is an eth tx if smsg.Signature.Type == crypto.SigTypeDelegated { + tx, err = ethtypes.EthTxFromSignedEthMessage(smsg) + if err != nil { + return ethtypes.EthTx{}, xerrors.Errorf("failed to convert from signed message: %w", err) + } + tx.Hash, err = tx.TxHash() if err != nil { - return tx, err + return ethtypes.EthTx{}, err } - } else if smsg.Signature.Type == crypto.SigTypeUnknown { // BLS Filecoin message - tx.Hash, err = ethtypes.EthHashFromCid(smsg.Message.Cid()) + } else if smsg.Signature.Type == crypto.SigTypeSecp256k1 { // Secp Filecoin Message + tx = ethTxFromNativeMessage(ctx, smsg.VMMessage(), sa) + tx.Hash, err = ethtypes.EthHashFromCid(smsg.Cid()) if err != nil { return tx, err } - } else { // Secp Filecoin Message - tx.Hash, err = ethtypes.EthHashFromCid(smsg.Cid()) + } else { // BLS Filecoin message + tx = ethTxFromNativeMessage(ctx, smsg.VMMessage(), sa) + tx.Hash, err = ethtypes.EthHashFromCid(smsg.Message.Cid()) if err != nil { return tx, err } @@ -1620,14 +1571,32 @@ func NewEthTxFromFilecoinMessage(ctx context.Context, smsg *types.SignedMessage, return tx, nil } -// newEthTxFromFilecoinMessageLookup creates an ethereum transaction from filecoin message lookup. If a negative txIdx is passed -// into the function, it looksup the transaction index of the message in the tipset, otherwise it uses the txIdx passed into the -// function -func newEthTxFromFilecoinMessageLookup(ctx context.Context, msgLookup *api.MsgLookup, txIdx int, cs *store.ChainStore, sa StateAPI) (ethtypes.EthTx, error) { - if msgLookup == nil { - return ethtypes.EthTx{}, fmt.Errorf("msg does not exist") +// ethTxFromNativeMessage does NOT populate: +// - BlockHash +// - BlockNumber +// - TransactionIndex +// - Hash +func ethTxFromNativeMessage(ctx context.Context, msg *types.Message, sa StateAPI) ethtypes.EthTx { + // We don't care if we error here, conversion is best effort for non-eth transactions + from, _ := lookupEthAddress(ctx, msg.From, sa) + to, _ := lookupEthAddress(ctx, msg.To, sa) + return ethtypes.EthTx{ + To: &to, + From: from, + Nonce: ethtypes.EthUint64(msg.Nonce), + ChainID: ethtypes.EthUint64(build.Eip155ChainId), + Value: ethtypes.EthBigInt(msg.Value), + Type: ethtypes.Eip1559TxType, + Gas: ethtypes.EthUint64(msg.GasLimit), + MaxFeePerGas: ethtypes.EthBigInt(msg.GasFeeCap), + MaxPriorityFeePerGas: ethtypes.EthBigInt(msg.GasPremium), } +} +// newEthTxFromMessageLookup creates an ethereum transaction from filecoin message lookup. If a negative txIdx is passed +// into the function, it looks up the transaction index of the message in the tipset, otherwise it uses the txIdx passed into the +// function +func newEthTxFromMessageLookup(ctx context.Context, msgLookup *api.MsgLookup, txIdx int, cs *store.ChainStore, sa StateAPI) (ethtypes.EthTx, error) { ts, err := cs.LoadTipSet(ctx, msgLookup.TipSet) if err != nil { return ethtypes.EthTx{}, err @@ -1676,13 +1645,13 @@ func newEthTxFromFilecoinMessageLookup(ctx context.Context, msgLookup *api.MsgLo smsg = &types.SignedMessage{ Message: *msg, Signature: crypto.Signature{ - Type: crypto.SigTypeUnknown, + Type: crypto.SigTypeBLS, Data: nil, }, } } - tx, err := NewEthTxFromFilecoinMessage(ctx, smsg, sa) + tx, err := newEthTxFromSignedMessage(ctx, smsg, sa) if err != nil { return ethtypes.EthTx{}, err } @@ -1728,16 +1697,6 @@ func newEthTxReceipt(ctx context.Context, tx ethtypes.EthTx, lookup *api.MsgLook LogsBloom: ethtypes.EmptyEthBloom[:], } - if receipt.To == nil && lookup.Receipt.ExitCode.IsSuccess() { - // Create and Create2 return the same things. - var ret eam.CreateReturn - if err := ret.UnmarshalCBOR(bytes.NewReader(lookup.Receipt.Return)); err != nil { - return api.EthTxReceipt{}, xerrors.Errorf("failed to parse contract creation result: %w", err) - } - addr := ethtypes.EthAddress(ret.EthAddress) - receipt.ContractAddress = &addr - } - if lookup.Receipt.ExitCode.IsSuccess() { receipt.Status = 1 } @@ -1745,6 +1704,24 @@ func newEthTxReceipt(ctx context.Context, tx ethtypes.EthTx, lookup *api.MsgLook receipt.Status = 0 } + receipt.GasUsed = ethtypes.EthUint64(lookup.Receipt.GasUsed) + + // TODO: handle CumulativeGasUsed + receipt.CumulativeGasUsed = ethtypes.EmptyEthInt + + effectiveGasPrice := big.Div(replay.GasCost.TotalCost, big.NewInt(lookup.Receipt.GasUsed)) + receipt.EffectiveGasPrice = ethtypes.EthBigInt(effectiveGasPrice) + + if receipt.To == nil && lookup.Receipt.ExitCode.IsSuccess() { + // Create and Create2 return the same things. + var ret eam.CreateExternalReturn + if err := ret.UnmarshalCBOR(bytes.NewReader(lookup.Receipt.Return)); err != nil { + return api.EthTxReceipt{}, xerrors.Errorf("failed to parse contract creation result: %w", err) + } + addr := ethtypes.EthAddress(ret.EthAddress) + receipt.ContractAddress = &addr + } + if len(events) > 0 { // TODO return a dummy non-zero bloom to signal that there are logs // need to figure out how worth it is to populate with a real bloom @@ -1788,14 +1765,6 @@ func newEthTxReceipt(ctx context.Context, tx ethtypes.EthTx, lookup *api.MsgLook } } - receipt.GasUsed = ethtypes.EthUint64(lookup.Receipt.GasUsed) - - // TODO: handle CumulativeGasUsed - receipt.CumulativeGasUsed = ethtypes.EmptyEthInt - - effectiveGasPrice := big.Div(replay.GasCost.TotalCost, big.NewInt(lookup.Receipt.GasUsed)) - receipt.EffectiveGasPrice = ethtypes.EthBigInt(effectiveGasPrice) - return receipt, nil } @@ -1811,7 +1780,7 @@ func (m *EthTxHashManager) Apply(ctx context.Context, from, to *types.TipSet) er continue } - hash, err := EthTxHashFromSignedFilecoinMessage(ctx, smsg, m.StateAPI) + hash, err := EthTxHashFromSignedMessage(ctx, smsg, m.StateAPI) if err != nil { return err } @@ -1848,7 +1817,7 @@ func WaitForMpoolUpdates(ctx context.Context, ch <-chan api.MpoolUpdate, manager continue } - ethTx, err := NewEthTxFromFilecoinMessage(ctx, u.Message, manager.StateAPI) + ethTx, err := newEthTxFromSignedMessage(ctx, u.Message, manager.StateAPI) if err != nil { log.Errorf("error converting filecoin message to eth tx: %s", err) }