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

[EVM] Offchain package - part 1 #6544

Merged
merged 9 commits into from
Oct 16, 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
15 changes: 8 additions & 7 deletions fvm/evm/debug/tracer_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package debug
package debug_test

import (
"encoding/json"
Expand All @@ -11,6 +11,7 @@ import (
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"

"github.com/onflow/flow-go/fvm/evm/debug"
"github.com/onflow/flow-go/fvm/evm/testutils"
"github.com/onflow/flow-go/model/flow"
)
Expand All @@ -23,13 +24,13 @@ func Test_CallTracer(t *testing.T) {

mockUpload := &testutils.MockUploader{
UploadFunc: func(id string, message json.RawMessage) error {
require.Equal(t, TraceID(txID, blockID), id)
require.Equal(t, debug.TraceID(txID, blockID), id)
require.Equal(t, res, message)
return nil
},
}

tracer, err := NewEVMCallTracer(mockUpload, zerolog.Nop())
tracer, err := debug.NewEVMCallTracer(mockUpload, zerolog.Nop())
require.NoError(t, err)
tracer.WithBlockID(blockID)

Expand Down Expand Up @@ -61,13 +62,13 @@ func Test_CallTracer(t *testing.T) {

mockUpload := &testutils.MockUploader{
UploadFunc: func(id string, message json.RawMessage) error {
require.Equal(t, TraceID(txID, blockID), id)
require.Equal(t, debug.TraceID(txID, blockID), id)
require.Equal(t, res, message)
return nil
},
}

tracer, err := NewEVMCallTracer(mockUpload, zerolog.Nop())
tracer, err := debug.NewEVMCallTracer(mockUpload, zerolog.Nop())
require.NoError(t, err)
tracer.WithBlockID(blockID)

Expand Down Expand Up @@ -107,7 +108,7 @@ func Test_CallTracer(t *testing.T) {
},
}

tracer, err := NewEVMCallTracer(mockUpload, zerolog.Nop())
tracer, err := debug.NewEVMCallTracer(mockUpload, zerolog.Nop())
require.NoError(t, err)
tracer.WithBlockID(blockID)

Expand All @@ -117,7 +118,7 @@ func Test_CallTracer(t *testing.T) {
})

t.Run("nop tracer", func(t *testing.T) {
tracer := nopTracer{}
tracer := debug.NopTracer
require.Nil(t, tracer.TxTracer())
})
}
9 changes: 5 additions & 4 deletions fvm/evm/debug/uploader_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package debug
package debug_test

import (
"context"
Expand All @@ -16,6 +16,7 @@ import (
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"

"github.com/onflow/flow-go/fvm/evm/debug"
"github.com/onflow/flow-go/model/flow"
testutils "github.com/onflow/flow-go/utils/unittest"
)
Expand All @@ -27,7 +28,7 @@ func Test_Uploader(t *testing.T) {
testutils.SkipUnless(t, testutils.TEST_REQUIRES_GCP_ACCESS, "requires GCP Bucket setup")

t.Run("successfuly upload traces", func(t *testing.T) {
uploader, err := NewGCPUploader(bucket)
uploader, err := debug.NewGCPUploader(bucket)
require.NoError(t, err)

const testID = "test_p"
Expand Down Expand Up @@ -56,10 +57,10 @@ func Test_TracerUploaderIntegration(t *testing.T) {
testutils.SkipUnless(t, testutils.TEST_REQUIRES_GCP_ACCESS, "requires GCP Bucket setup")

t.Run("successfuly uploads traces", func(t *testing.T) {
uploader, err := NewGCPUploader(bucket)
uploader, err := debug.NewGCPUploader(bucket)
require.NoError(t, err)

tracer, err := NewEVMCallTracer(uploader, zerolog.Nop())
tracer, err := debug.NewEVMCallTracer(uploader, zerolog.Nop())
require.NoError(t, err)

tr := tracer.TxTracer()
Expand Down
2 changes: 1 addition & 1 deletion fvm/evm/emulator/emulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1257,7 +1257,7 @@ func TestTransactionTracing(t *testing.T) {
testAccount.Address(),
types.Address{0x01, 0x02},
testContract.ByteCode,
1_000_000,
2_000_000,
big.NewInt(0),
testAccount.Nonce(),
),
Expand Down
63 changes: 19 additions & 44 deletions fvm/evm/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"github.com/onflow/cadence"
"github.com/onflow/cadence/encoding/ccf"
gethCommon "github.com/onflow/go-ethereum/common"
"github.com/onflow/go-ethereum/rlp"

"github.com/onflow/flow-go/fvm/evm/stdlib"
"github.com/onflow/flow-go/fvm/evm/types"
Expand Down Expand Up @@ -56,51 +55,27 @@ func NewTransactionEvent(
}

func (p *transactionEvent) ToCadence(chainID flow.ChainID) (cadence.Event, error) {
var encodedLogs []byte
var err error
if len(p.Result.Logs) > 0 {
encodedLogs, err = rlp.EncodeToBytes(p.Result.Logs)
if err != nil {
return cadence.Event{}, err
}
}

deployedAddress := cadence.String("")
if p.Result.DeployedContractAddress != nil {
deployedAddress = cadence.String(p.Result.DeployedContractAddress.String())
}

errorMsg := ""
if p.Result.VMError != nil {
errorMsg = p.Result.VMError.Error()
}
// both error would never happen at the same time
// but in case the priority is by validation error
if p.Result.ValidationError != nil {
errorMsg = p.Result.ValidationError.Error()
encodedLogs, err := p.Result.RLPEncodedLogs()
if err != nil {
return cadence.Event{}, err
}

eventType := stdlib.CadenceTypesForChain(chainID).TransactionExecuted

// the first 4 bytes of StateChangeCommitment is used as checksum
var checksum [ChecksumLength]byte
if len(p.Result.StateChangeCommitment) >= ChecksumLength {
copy(checksum[:ChecksumLength], p.Result.StateChangeCommitment[:ChecksumLength])
}
return cadence.NewEvent([]cadence.Value{
hashToCadenceArrayValue(p.Result.TxHash),
cadence.NewUInt16(p.Result.Index),
cadence.NewUInt8(p.Result.TxType),
bytesToCadenceUInt8ArrayValue(p.Payload),
cadence.NewUInt16(uint16(p.Result.ResultSummary().ErrorCode)),
cadence.String(errorMsg),
cadence.String(p.Result.ErrorMsg()),
cadence.NewUInt64(p.Result.GasConsumed),
deployedAddress,
cadence.String(p.Result.DeployedContractAddressString()),
bytesToCadenceUInt8ArrayValue(encodedLogs),
cadence.NewUInt64(p.BlockHeight),
bytesToCadenceUInt8ArrayValue(p.Result.ReturnedData),
bytesToCadenceUInt8ArrayValue(p.Result.PrecompiledCalls),
checksumToCadenceArrayValue(checksum),
checksumToCadenceArrayValue(p.Result.StateChangeChecksum()),
}).WithType(eventType), nil
}

Expand Down Expand Up @@ -194,19 +169,19 @@ func DecodeBlockEventPayload(event cadence.Event) (*BlockEventPayload, error) {
}

type TransactionEventPayload struct {
Hash gethCommon.Hash `cadence:"hash"`
Index uint16 `cadence:"index"`
TransactionType uint8 `cadence:"type"`
Payload []byte `cadence:"payload"`
ErrorCode uint16 `cadence:"errorCode"`
GasConsumed uint64 `cadence:"gasConsumed"`
ContractAddress string `cadence:"contractAddress"`
Logs []byte `cadence:"logs"`
BlockHeight uint64 `cadence:"blockHeight"`
ErrorMessage string `cadence:"errorMessage"`
ReturnedData []byte `cadence:"returnedData"`
PrecompiledCalls []byte `cadence:"precompiledCalls"`
StateUpdateChecksum [ChecksumLength]byte `cadence:"stateUpdateChecksum"`
Hash gethCommon.Hash `cadence:"hash"`
Index uint16 `cadence:"index"`
TransactionType uint8 `cadence:"type"`
Payload []byte `cadence:"payload"`
ErrorCode uint16 `cadence:"errorCode"`
GasConsumed uint64 `cadence:"gasConsumed"`
ContractAddress string `cadence:"contractAddress"`
Logs []byte `cadence:"logs"`
BlockHeight uint64 `cadence:"blockHeight"`
ErrorMessage string `cadence:"errorMessage"`
ReturnedData []byte `cadence:"returnedData"`
PrecompiledCalls []byte `cadence:"precompiledCalls"`
StateUpdateChecksum [types.ChecksumLength]byte `cadence:"stateUpdateChecksum"`
}

// transactionEventPayloadV0 legacy format of the transaction event without stateUpdateChecksum field
Expand Down
2 changes: 1 addition & 1 deletion fvm/evm/events/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func TestEVMTransactionExecutedEventCCFEncodingDecoding(t *testing.T) {
assert.Equal(t, tep.GasConsumed, txResult.GasConsumed)
assert.Equal(t, tep.ErrorMessage, txResult.VMError.Error())
assert.Equal(t, tep.ReturnedData, txResult.ReturnedData)
assert.Equal(t, tep.StateUpdateChecksum[:], stateUpdateCommit[:events.ChecksumLength])
assert.Equal(t, tep.StateUpdateChecksum[:], stateUpdateCommit[:types.ChecksumLength])
assert.Equal(
t,
tep.ContractAddress,
Expand Down
13 changes: 6 additions & 7 deletions fvm/evm/events/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package events
import (
"github.com/onflow/cadence"
gethCommon "github.com/onflow/go-ethereum/common"

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

// cadenceArrayTypeOfUInt8 is the Cadence type [UInt8]
Expand Down Expand Up @@ -31,16 +33,13 @@ func hashToCadenceArrayValue(hash gethCommon.Hash) cadence.Array {
WithType(cadenceHashType)
}

// ChecksumLength captures number of bytes a checksum uses
const ChecksumLength = 4

// checksumType is the Cadence type [UInt8;4]
var checksumType = cadence.NewConstantSizedArrayType(ChecksumLength, cadence.UInt8Type)
var checksumType = cadence.NewConstantSizedArrayType(types.ChecksumLength, cadence.UInt8Type)

// checksumToCadenceArrayValue converts a checksum ([4]byte) into a Cadence array of type [UInt8;4]
func checksumToCadenceArrayValue(checksum [ChecksumLength]byte) cadence.Array {
values := make([]cadence.Value, ChecksumLength)
for i := 0; i < ChecksumLength; i++ {
func checksumToCadenceArrayValue(checksum [types.ChecksumLength]byte) cadence.Array {
values := make([]cadence.Value, types.ChecksumLength)
for i := 0; i < types.ChecksumLength; i++ {
values[i] = cadence.NewUInt8(checksum[i])
}
return cadence.NewArray(values).
Expand Down
4 changes: 2 additions & 2 deletions fvm/evm/handler/blockHashList.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const (
// smaller fixed size buckets to minimize the
// number of bytes read and written during set/get operations.
type BlockHashList struct {
backend types.Backend
backend types.BackendStorage
rootAddress flow.Address

// cached meta data
Expand All @@ -46,7 +46,7 @@ type BlockHashList struct {
// It tries to load the metadata from the backend
// and if not exist it creates one
func NewBlockHashList(
backend types.Backend,
backend types.BackendStorage,
rootAddress flow.Address,
capacity int,
) (*BlockHashList, error) {
Expand Down
122 changes: 122 additions & 0 deletions fvm/evm/offchain/blocks/blocks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package blocks

import (
"fmt"

gethCommon "github.com/onflow/go-ethereum/common"

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

const BlockStoreLatestBlockMetaKey = "LatestBlockMeta"

// Blocks facilitates access to the recent block hash values
// and also the latest executed block meta data
type Blocks struct {
chainID flow.ChainID
storage types.BackendStorage
rootAddress flow.Address
bhl *handler.BlockHashList
}

// NewBlocks constructs a new blocks type
func NewBlocks(
chainID flow.ChainID,
rootAddress flow.Address,
storage types.BackendStorage,
) (*Blocks, error) {
var err error
blocks := &Blocks{
chainID: chainID,
storage: storage,
rootAddress: rootAddress,
}
blocks.bhl, err = handler.NewBlockHashList(
storage,
rootAddress,
handler.BlockHashListCapacity,
)
if err != nil {
return nil, err
}
// if empty insert genesis block hash
if blocks.bhl.IsEmpty() {
genesis := types.GenesisBlock(chainID)
err = blocks.PushBlockMeta(
NewMeta(
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
genesis.Height,
genesis.Timestamp,
genesis.PrevRandao,
))
if err != nil {
return nil, err
}
// push block hash
err = blocks.PushBlockHash(
genesis.Height,
types.GenesisBlockHash(chainID))
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
}
return blocks, nil
}

// PushBlock pushes a new block into the storage
func (b *Blocks) PushBlockMeta(
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
meta *Meta,
) error {
// check height order
if meta.Height > 0 {
bm, err := b.LatestBlock()
if err != nil {
return err
}
if meta.Height != bm.Height+1 {
return fmt.Errorf("out of order block meta push! got: %d, expected %d ", meta.Height, bm.Height+1)
}
}
return b.storeBlockMetaData(meta)
}

// PushBlockHash pushes a new block block hash into the storage
func (b *Blocks) PushBlockHash(
height uint64,
hash gethCommon.Hash,
) error {
return b.bhl.Push(height, hash)
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
}

func (b *Blocks) LatestBlock() (*Meta, error) {
return b.loadBlockMetaData()
}

// BlockHash returns the block hash for the given height
func (b *Blocks) BlockHash(height uint64) (gethCommon.Hash, error) {
_, hash, err := b.bhl.BlockHashByHeight(height)
return hash, err
}

// storeBlockMetaData stores the block meta data into storage
func (b *Blocks) storeBlockMetaData(bm *Meta) error {
// store the encoded data into backend
return b.storage.SetValue(
b.rootAddress[:],
[]byte(BlockStoreLatestBlockMetaKey),
bm.Encode(),
)
}

// loadBlockMetaData loads the block meta data from the storage
func (b *Blocks) loadBlockMetaData() (*Meta, error) {
data, err := b.storage.GetValue(
b.rootAddress[:],
[]byte(BlockStoreLatestBlockMetaKey),
)
if err != nil {
return nil, err
}
return MetaFromEncoded(data)
}
Loading
Loading