Skip to content

Commit

Permalink
Merge pull request onflow#6544 from onflow/ramtin/evm-offchain-part1
Browse files Browse the repository at this point in the history
[EVM] Offchain package - part 1
  • Loading branch information
ramtinms authored Oct 16, 2024
2 parents 3ba181e + efcae1c commit 0dde730
Show file tree
Hide file tree
Showing 18 changed files with 486 additions and 76 deletions.
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(
genesis.Height,
genesis.Timestamp,
genesis.PrevRandao,
))
if err != nil {
return nil, err
}
// push block hash
err = blocks.PushBlockHash(
genesis.Height,
types.GenesisBlockHash(chainID))
if err != nil {
return nil, err
}
}
return blocks, nil
}

// PushBlock pushes a new block into the storage
func (b *Blocks) PushBlockMeta(
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)
}

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

0 comments on commit 0dde730

Please sign in to comment.