Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Consolidate decoding of EVM.TransactionExecuted event for the contained transaction & receipt #354

Merged
merged 1 commit into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions models/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,13 @@ func (c *CadenceEvents) Transactions() ([]Transaction, []*StorageReceipt, error)
rcps := make([]*StorageReceipt, 0)
for _, e := range c.events.Events {
if isTransactionExecutedEvent(e.Value) {
rcp, err := decodeReceipt(e.Value)
if err != nil {
return nil, nil, err
}

tx, err := decodeTransaction(e.Value, rcp.BlockNumber.Uint64())
tx, receipt, err := decodeTransactionEvent(e.Value)
if err != nil {
return nil, nil, err
}

txs = append(txs, tx)
rcps = append(rcps, rcp)
rcps = append(rcps, receipt)
}
}

Expand Down
65 changes: 0 additions & 65 deletions models/receipt.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
package models

import (
"bytes"
"encoding/hex"
"fmt"
"math/big"

"github.com/onflow/flow-go/fvm/evm/types"

"github.com/onflow/cadence"
"github.com/onflow/go-ethereum/common"
"github.com/onflow/go-ethereum/common/hexutil"
gethTypes "github.com/onflow/go-ethereum/core/types"
"github.com/onflow/go-ethereum/rlp"
)

// TEMP: Remove this type after PreviewNet is reset
Expand Down Expand Up @@ -122,64 +115,6 @@ func NewStorageReceipt(receipt *gethTypes.Receipt) *StorageReceipt {
}
}

// decodeReceipt takes a cadence event for transaction executed and decodes it into the receipt.
func decodeReceipt(event cadence.Event) (*StorageReceipt, error) {
tx, err := types.DecodeTransactionEventPayload(event)
if err != nil {
return nil, fmt.Errorf("failed to cadence decode receipt: %w", err)
}

encLogs, err := hex.DecodeString(tx.Logs)
if err != nil {
return nil, fmt.Errorf("failed to hex decode receipt: %w", err)
}

var logs []*gethTypes.Log
if len(encLogs) > 0 {
err = rlp.Decode(bytes.NewReader(encLogs), &logs)
if err != nil {
return nil, fmt.Errorf("failed to rlp decode receipt: %w", err)
}
}

t, err := decodeTransaction(event, tx.BlockHeight)
if err != nil {
return nil, err
}

receipt := &gethTypes.Receipt{
BlockNumber: big.NewInt(int64(tx.BlockHeight)),
Type: tx.TransactionType,
Logs: logs,
TxHash: common.HexToHash(tx.Hash),
ContractAddress: common.HexToAddress(tx.ContractAddress),
GasUsed: tx.GasConsumed,
CumulativeGasUsed: tx.GasConsumed, // todo use cumulative after added to the tx result
EffectiveGasPrice: t.GasPrice(), // since there's no base fee we can always use gas price
TransactionIndex: uint(tx.Index),
BlockHash: common.HexToHash(tx.BlockHash),
}

if tx.ErrorCode == uint16(types.ErrCodeNoError) {
receipt.Status = gethTypes.ReceiptStatusSuccessful
} else {
receipt.Status = gethTypes.ReceiptStatusFailed
}

receipt.Bloom = gethTypes.CreateBloom([]*gethTypes.Receipt{receipt})

result := NewStorageReceipt(receipt)
if tx.ErrorCode == uint16(types.ExecutionErrCodeExecutionReverted) {
revert, err := hex.DecodeString(tx.ReturnedData)
if err != nil {
return nil, fmt.Errorf("failed to decode transaction return data: %w", err)
}
result.RevertReason = revert
}

return result, nil
}

// MarshalReceipt takes a receipt and its associated transaction,
// and marshals the receipt to the proper structure needed by
// eth_getTransactionReceipt.
Expand Down
2 changes: 1 addition & 1 deletion models/receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func Test_DecodeReceipts(t *testing.T) {
cdcEv, rec := createTestEvent(t, evmTxBinary)

receipt, err := decodeReceipt(cdcEv)
_, receipt, err := decodeTransactionEvent(cdcEv)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure comprehensive testing for decodeTransactionEvent.

The test function Test_DecodeReceipts uses the new decodeTransactionEvent function but only checks for no errors and compares logs. Consider adding more assertions to validate the Transaction object and other aspects of the Receipt object to ensure comprehensive testing.

Would you like me to help by adding more test cases or enhancing the existing ones?

require.NoError(t, err)

for i, l := range rec.Logs {
Expand Down
89 changes: 70 additions & 19 deletions models/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/onflow/go-ethereum/common"
"github.com/onflow/go-ethereum/core/txpool"
gethTypes "github.com/onflow/go-ethereum/core/types"
"github.com/onflow/go-ethereum/rlp"
)

const (
Expand Down Expand Up @@ -181,44 +182,94 @@ func (tc TransactionCall) MarshalBinary() ([]byte, error) {
return append([]byte{tc.Type()}, encoded...), err
}

// decodeTransaction takes a cadence event for transaction executed
// and decodes it into a Transaction interface. The concrete type
// will be either a TransactionCall or a DirectCall.
func decodeTransaction(event cadence.Event, evmHeight uint64) (Transaction, error) {
tx, err := types.DecodeTransactionEventPayload(event)
// decodeTransactionEvent takes a cadence event for transaction executed
// and decodes its payload into a Transaction interface and a StorageReceipt.
// The concrete type will be either a TransactionCall or a DirectCall.
func decodeTransactionEvent(
event cadence.Event,
) (Transaction, *StorageReceipt, error) {
txEvent, err := types.DecodeTransactionEventPayload(event)
if err != nil {
return nil, fmt.Errorf("failed to cadence decode transaction: %w", err)
return nil, nil, fmt.Errorf("failed to Cadence decode transaction event: %w", err)
}

encodedTx, err := hex.DecodeString(tx.Payload)
encodedTx, err := hex.DecodeString(txEvent.Payload)
if err != nil {
return nil, fmt.Errorf("failed to decode transaction hex: %w", err)
return nil, nil, fmt.Errorf("failed to hex-decode transaction payload: %w", err)
}

// check if the transaction data is actually from a direct call,
encodedLogs, err := hex.DecodeString(txEvent.Logs)
if err != nil {
return nil, nil, fmt.Errorf("failed to hex decode receipt: %w", err)
}

var logs []*gethTypes.Log
if len(encodedLogs) > 0 {
err = rlp.Decode(bytes.NewReader(encodedLogs), &logs)
if err != nil {
return nil, nil, fmt.Errorf("failed to RLP-decode receipt: %w", err)
}
}

gethReceipt := &gethTypes.Receipt{
BlockNumber: big.NewInt(int64(txEvent.BlockHeight)),
Type: txEvent.TransactionType,
Logs: logs,
TxHash: common.HexToHash(txEvent.Hash),
ContractAddress: common.HexToAddress(txEvent.ContractAddress),
GasUsed: txEvent.GasConsumed,
CumulativeGasUsed: txEvent.GasConsumed, // todo use cumulative after added to the tx result
TransactionIndex: uint(txEvent.Index),
BlockHash: common.HexToHash(txEvent.BlockHash),
}

if txEvent.ErrorCode == uint16(types.ErrCodeNoError) {
gethReceipt.Status = gethTypes.ReceiptStatusSuccessful
} else {
gethReceipt.Status = gethTypes.ReceiptStatusFailed
}

gethReceipt.Bloom = gethTypes.CreateBloom([]*gethTypes.Receipt{gethReceipt})

receipt := NewStorageReceipt(gethReceipt)
if txEvent.ErrorCode == uint16(types.ExecutionErrCodeExecutionReverted) {
revert, err := hex.DecodeString(txEvent.ReturnedData)
if err != nil {
return nil, nil, fmt.Errorf("failed to hex-decode transaction return data: %w", err)
}
receipt.RevertReason = revert
}

var tx Transaction
// check if the transaction payload is actually from a direct call,
// which is a special state transition in Flow EVM.
if tx.TransactionType == types.DirectCallTxType {
if txEvent.TransactionType == types.DirectCallTxType {
directCall, err := types.DirectCallFromEncoded(encodedTx)
if err != nil {
return nil, fmt.Errorf("failed to rlp decode direct call: %w", err)
return nil, nil, fmt.Errorf("failed to RLP-decode direct call: %w", err)
}
evmHeight := receipt.BlockNumber.Uint64()

return DirectCall{DirectCall: directCall, blockHeight: evmHeight}, nil
tx = DirectCall{DirectCall: directCall, blockHeight: evmHeight}
} else {
gethTx := &gethTypes.Transaction{}
if err := gethTx.UnmarshalBinary(encodedTx); err != nil {
return nil, nil, fmt.Errorf("failed to RLP-decode transaction: %w", err)
}
tx = TransactionCall{Transaction: gethTx}
}

gethTx := &gethTypes.Transaction{}
if err := gethTx.UnmarshalBinary(encodedTx); err != nil {
return nil, fmt.Errorf("failed to rlp decode transaction: %w", err)
}
// since there's no base fee we can always use gas price
receipt.EffectiveGasPrice = tx.GasPrice()

return TransactionCall{Transaction: gethTx}, nil
return tx, receipt, nil
}

func UnmarshalTransaction(value []byte, blockHeight uint64) (Transaction, error) {
if value[0] == types.DirectCallTxType {
directCall, err := types.DirectCallFromEncoded(value)
if err != nil {
return nil, fmt.Errorf("failed to rlp decode direct call: %w", err)
return nil, fmt.Errorf("failed to RLP-decode direct call: %w", err)
}

// TEMP: Remove `blockHeight` after PreviewNet is reset
Expand All @@ -233,7 +284,7 @@ func UnmarshalTransaction(value []byte, blockHeight uint64) (Transaction, error)
return TransactionCall{Transaction: tx}, nil
}

return nil, fmt.Errorf("failed to rlp decode transaction: %w", err)
return nil, fmt.Errorf("failed to RLP-decode transaction: %w", err)
}

return TransactionCall{Transaction: tx}, nil
Expand Down
10 changes: 5 additions & 5 deletions models/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func createTestEvent(t *testing.T, txBinary string) (cadence.Event, *types.Resul
func Test_DecodeEVMTransaction(t *testing.T) {
cdcEv, _ := createTestEvent(t, evmTxBinary)

decTx, err := decodeTransaction(cdcEv, 10)
decTx, _, err := decodeTransactionEvent(cdcEv)
require.NoError(t, err)
require.IsType(t, TransactionCall{}, decTx)

Expand Down Expand Up @@ -133,7 +133,7 @@ func Test_DecodeEVMTransaction(t *testing.T) {
func Test_DecodeDirectCall(t *testing.T) {
cdcEv, _ := createTestEvent(t, directCallBinary)

decTx, err := decodeTransaction(cdcEv, 10)
decTx, _, err := decodeTransactionEvent(cdcEv)
require.NoError(t, err)
require.IsType(t, DirectCall{}, decTx)

Expand Down Expand Up @@ -181,7 +181,7 @@ func Test_UnmarshalTransaction(t *testing.T) {

cdcEv, _ := createTestEvent(t, evmTxBinary)

tx, err := decodeTransaction(cdcEv, 10)
tx, _, err := decodeTransactionEvent(cdcEv)
require.NoError(t, err)

encodedTx, err := tx.MarshalBinary()
Expand Down Expand Up @@ -235,7 +235,7 @@ func Test_UnmarshalTransaction(t *testing.T) {

cdcEv, _ := createTestEvent(t, directCallBinary)

tx, err := decodeTransaction(cdcEv, 10)
tx, _, err := decodeTransactionEvent(cdcEv)
require.NoError(t, err)

encodedTx, err := tx.MarshalBinary()
Expand Down Expand Up @@ -287,7 +287,7 @@ func Test_UnmarshalTransaction(t *testing.T) {

cdcEv, _ := createTestEvent(t, directCallBinary)

tx, err := decodeTransaction(cdcEv, 10)
tx, _, err := decodeTransactionEvent(cdcEv)
require.NoError(t, err)

encodedTx, err := tx.MarshalBinary()
Expand Down
Loading