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

Integrate local state index #541

Closed
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
d2b8c85
rename evm and remote
sideninja Sep 11, 2024
4554f46
move evm interface and rename file
sideninja Sep 11, 2024
e800a25
move validation and context inside the state
sideninja Sep 11, 2024
e039861
add local client
sideninja Sep 11, 2024
3ba5ba2
add support for calls and estimate
sideninja Sep 11, 2024
460cde6
implement call on state
sideninja Sep 11, 2024
584029f
add client handler
sideninja Sep 11, 2024
edf82ef
add client handler balancing remote and local
sideninja Sep 11, 2024
b2bb7b0
change local client creation
sideninja Sep 11, 2024
e3490c3
update local client syntax
sideninja Sep 11, 2024
f851e83
bootstrap client handler
sideninja Sep 11, 2024
8613437
update test apis
sideninja Sep 11, 2024
56e4bca
Merge branch 'gregor/local-state/main' into gregor/local-state/integrate
sideninja Sep 11, 2024
aded8e3
handle failed results
sideninja Sep 12, 2024
ec6d724
handle failed results
sideninja Sep 12, 2024
6cf64e4
check errors by value
sideninja Sep 12, 2024
cf9c41d
add state re-execution height
sideninja Sep 12, 2024
8ed258d
add evm height
sideninja Sep 12, 2024
2a73273
add pebble implementation for block state
sideninja Sep 12, 2024
d912624
update block mock
sideninja Sep 12, 2024
4a7ca08
fend for receipt status
sideninja Sep 12, 2024
0280909
update executed block height
sideninja Sep 12, 2024
80e698f
wip api changes executed height
sideninja Sep 12, 2024
550151c
rename block latest executed and indexed heights
sideninja Sep 13, 2024
7d2d5d1
use latest executed height
sideninja Sep 13, 2024
6829472
return data nil handle
sideninja Sep 13, 2024
a4e896f
add ms response time
sideninja Sep 13, 2024
0ddcaa6
use latest executed height on client init
sideninja Sep 13, 2024
dd9f945
handle estimate failures
sideninja Sep 13, 2024
832c39d
use specific height, don't use latest height since the local and remo…
sideninja Sep 13, 2024
e8ef351
fix tests
sideninja Sep 13, 2024
f4c0b83
Merge branch 'main' into gregor/local-state/main
sideninja Sep 13, 2024
2ca4b19
Merge branch 'gregor/local-state/main' into gregor/local-state/state
sideninja Sep 13, 2024
4e7aab7
change evm client height from int to uint
sideninja Sep 13, 2024
d1651c4
update block hash resolver change
sideninja Sep 13, 2024
935f68e
client handler api updates
sideninja Sep 13, 2024
8584fa7
update remote client changes
sideninja Sep 13, 2024
1aa026c
return remote values
sideninja Sep 13, 2024
f6c4f74
improve time output to ms
sideninja Sep 13, 2024
14ba53b
added cadence arch and environment tests
sideninja Sep 13, 2024
2150fb5
update flow-go with fixed replayer
sideninja Sep 13, 2024
675c7e5
update flow-go
sideninja Sep 14, 2024
cf582e6
add todo comments
sideninja Sep 14, 2024
a4e500c
extend storage contract to have logs emitted and improve the test
sideninja Sep 15, 2024
c20e766
fix test issues and add comment
sideninja Sep 15, 2024
d07b8ff
update comment
sideninja Sep 15, 2024
d0b5da8
update flow-go to specific version
sideninja Sep 15, 2024
ce4eb30
update test with changes
sideninja Sep 15, 2024
5e4201f
remove legacy decode done in flow-go
sideninja Sep 15, 2024
5fb3080
Update README.md
sideninja Sep 15, 2024
4960b86
Merge branch 'gregor/local-state/integrate' into gregor/local-state/main
sideninja Sep 15, 2024
8c319c2
Merge branch 'gregor/local-state/state' into gregor/local-state/main
sideninja Sep 15, 2024
2d6477c
comment out
sideninja Sep 15, 2024
fde99b6
comment out
sideninja Sep 15, 2024
0d6af5e
return errs
sideninja Sep 15, 2024
da2f408
Merge remote-tracking branch 'origin/gregor/local-state/state' into g…
sideninja Sep 15, 2024
05b4f64
fix test changes in contract storage.sol
sideninja Sep 15, 2024
846283c
fix test changes in contract storage.sol
sideninja Sep 15, 2024
24243a3
Merge branch 'gregor/local-state/state' into gregor/local-state/main
sideninja Sep 16, 2024
703e692
remove uneeded block fetch
sideninja Sep 16, 2024
b1d62e0
Merge branch 'gregor/local-state/integrate' into gregor/local-state/main
sideninja Sep 16, 2024
a0881de
Merge branch 'gregor/local-state/main' into feature/state
sideninja Sep 16, 2024
dc3a030
Merge branch 'main' into gregor/local-state/integrate
sideninja Sep 16, 2024
169c245
Merge branch 'main' into gregor/local-state/engine
sideninja Sep 16, 2024
2ba6449
Merge branch 'gregor/local-state/engine' into feature/state
sideninja Sep 16, 2024
d5874db
Merge branch 'gregor/local-state/historic' into feature/state
sideninja Sep 16, 2024
728923e
use remote client for height and add comment
sideninja Sep 16, 2024
61b9e0e
restrict calling latest evm height
sideninja Sep 16, 2024
f75c2c9
remove receipt match
sideninja Sep 16, 2024
73ea0a5
Merge branch 'gregor/local-state/integrate' into feature/state
sideninja Sep 16, 2024
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
4 changes: 2 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func SupportedAPIs(
type BlockChainAPI struct {
logger zerolog.Logger
config *config.Config
evm requester.Requester
evm requester.EVMClient
blocks storage.BlockIndexer
transactions storage.TransactionIndexer
receipts storage.ReceiptIndexer
Expand All @@ -96,7 +96,7 @@ type BlockChainAPI struct {
func NewBlockChainAPI(
logger zerolog.Logger,
config *config.Config,
evm requester.Requester,
evm requester.EVMClient,
blocks storage.BlockIndexer,
transactions storage.TransactionIndexer,
receipts storage.ReceiptIndexer,
Expand Down
15 changes: 8 additions & 7 deletions bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type Bootstrap struct {
logger zerolog.Logger
config *config.Config
Client *requester.CrossSporkClient
Requester requester.Requester
EVMClient requester.EVMClient
Storages *Storages
Publishers *Publishers
collector metrics.Collector
Expand Down Expand Up @@ -238,19 +238,20 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
// create transaction pool
txPool := requester.NewTxPool(b.Client, b.Publishers.Transaction, b.logger)

evm, err := requester.NewEVM(
b.Client,
b.EVMClient, err = requester.NewClientHandler(
b.config,
b.Storages.Storage,
txPool,
signer,
b.logger,
b.Client,
b.Storages.Blocks,
txPool,
b.Storages.Receipts,
b.logger,
b.collector,
)
if err != nil {
return fmt.Errorf("failed to create EVM requester: %w", err)
}
b.Requester = evm

// create rate limiter for requests on the APIs. Tokens are number of requests allowed per 1 second interval
// if no limit is defined we specify max value, effectively disabling rate-limiting
Expand All @@ -267,7 +268,7 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
blockchainAPI, err := api.NewBlockChainAPI(
b.logger,
b.config,
evm,
b.EVMClient,
b.Storages.Blocks,
b.Storages.Transactions,
b.Storages.Receipts,
Expand Down
290 changes: 290 additions & 0 deletions services/requester/client_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
package requester

import (
"context"
"errors"
"math/big"
"reflect"
"sync"
"time"

"github.com/onflow/flow-go-sdk/crypto"
"github.com/onflow/go-ethereum/common"
"github.com/rs/zerolog"

"github.com/onflow/flow-evm-gateway/config"
"github.com/onflow/flow-evm-gateway/metrics"
"github.com/onflow/flow-evm-gateway/models"
"github.com/onflow/flow-evm-gateway/services/state"
"github.com/onflow/flow-evm-gateway/storage"
"github.com/onflow/flow-evm-gateway/storage/pebble"
)

var _ EVMClient = &ClientHandler{}

// ClientHandler handles remote and local client for executing EVM operations.
// The handler contains logic that can switch between using local or remote client
// and implements error handling logic that can prefer either remote result or
// local result.
type ClientHandler struct {
remote *RemoteClient
config *config.Config
store *pebble.Storage
blocks storage.BlockIndexer
receipts storage.ReceiptIndexer
logger zerolog.Logger
collector metrics.Collector
}

func NewClientHandler(
config *config.Config,
store *pebble.Storage,
txPool *TxPool,
signer crypto.Signer,
client *CrossSporkClient,
blocks storage.BlockIndexer,
receipts storage.ReceiptIndexer,
logger zerolog.Logger,
collector metrics.Collector,
) (*ClientHandler, error) {
remote, err := NewRemote(client, config, signer, logger, blocks, txPool, collector)
if err != nil {
return nil, err
}

return &ClientHandler{
remote: remote,
config: config,
store: store,
blocks: blocks,
receipts: receipts,
logger: logger,
collector: collector,
}, nil
}

func (c *ClientHandler) SendRawTransaction(ctx context.Context, data []byte) (common.Hash, error) {
// always use remote client
return c.remote.SendRawTransaction(ctx, data)
}

func (c *ClientHandler) GetBalance(
ctx context.Context,
address common.Address,
height int64,
) (*big.Int, error) {
local, err := c.localClient(height)
if err != nil {
return nil, err
}

return handleCall(func() (*big.Int, error) {
return local.GetBalance(ctx, address, height)
}, func() (*big.Int, error) {
return c.remote.GetBalance(ctx, address, height)
}, c.logger.With().Str("client-call", "get balance").Logger())
}

func (c *ClientHandler) Call(
ctx context.Context,
data []byte,
from common.Address,
height int64,
) ([]byte, error) {
local, err := c.localClient(height)
if err != nil {
return nil, err
}

return handleCall(func() ([]byte, error) {
return local.Call(ctx, data, from, height)
}, func() ([]byte, error) {
return c.remote.Call(ctx, data, from, height)
}, c.logger.With().Str("client-call", "call").Logger())
}

func (c *ClientHandler) EstimateGas(
ctx context.Context,
data []byte,
from common.Address,
height int64,
) (uint64, error) {
local, err := c.localClient(height)
if err != nil {
return 0, err
}

return handleCall(func() (uint64, error) {
return local.EstimateGas(ctx, data, from, height)
}, func() (uint64, error) {
return c.remote.EstimateGas(ctx, data, from, height)
}, c.logger.With().Str("client-call", "estimate gas").Logger())
}

func (c *ClientHandler) GetNonce(
ctx context.Context,
address common.Address,
height int64,
) (uint64, error) {
local, err := c.localClient(height)
if err != nil {
return 0, err
}

return handleCall(func() (uint64, error) {
return local.GetNonce(ctx, address, height)
}, func() (uint64, error) {
return c.remote.GetNonce(ctx, address, height)
}, c.logger.With().Str("client-call", "get nonce").Logger())
}

func (c *ClientHandler) GetCode(
ctx context.Context,
address common.Address,
height int64,
) ([]byte, error) {
local, err := c.localClient(height)
if err != nil {
return nil, err
}

return handleCall(func() ([]byte, error) {
return local.GetCode(ctx, address, height)
}, func() ([]byte, error) {
return c.remote.GetCode(ctx, address, height)
}, c.logger.With().Str("client-call", "get code").Logger())
}

func (c *ClientHandler) GetLatestEVMHeight(ctx context.Context) (uint64, error) {
local, err := c.localClient(models.LatestBlockNumber.Int64())
sideninja marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return 0, err
}

return handleCall(func() (uint64, error) {
return local.GetLatestEVMHeight(ctx)
}, func() (uint64, error) {
return c.remote.GetLatestEVMHeight(ctx)
}, c.logger.With().Str("client-call", "get latest height").Logger())
}

func (c *ClientHandler) GetStorageAt(
ctx context.Context,
address common.Address,
hash common.Hash,
height int64,
) (common.Hash, error) {
local, err := c.localClient(height)
if err != nil {
return common.Hash{}, err
}

return handleCall(func() (common.Hash, error) {
return local.GetStorageAt(ctx, address, hash, height)
}, func() (common.Hash, error) {
return c.remote.GetStorageAt(ctx, address, hash, height)
}, c.logger.With().Str("client-call", "get storage at").Logger())
}

func (c *ClientHandler) localClient(height int64) (*LocalClient, error) {
h := uint64(height)
if height < 0 {
latest, err := c.blocks.LatestEVMHeight()
if err != nil {
return nil, err
}
h = latest
}
block, err := c.blocks.GetByHeight(h)
if err != nil {
return nil, err
}

blockState, err := state.NewBlockState(
block,
c.config.FlowNetworkID,
c.store,
c.blocks,
c.receipts,
c.logger,
)

return NewLocalClient(blockState, c.blocks), nil
}

// handleCall takes in local and remote call and implements error handling logic to return
// correct result, it also compares the results in case there are no errors and reports any differences.
func handleCall[T any](
local func() (T, error),
remote func() (T, error),
logger zerolog.Logger,
) (T, error) {
logger.Info().Msg("executing state client call")

var localErr, remoteErr error
var localRes, remoteRes T

wg := sync.WaitGroup{}
wg.Add(2)
go func() {
s := time.Now()
localRes, localErr = local()
logger.Info().
Dur("execution-time", time.Since(s)).
Msg("local call executed")
wg.Done()
}()

go func() {
s := time.Now()
remoteRes, remoteErr = remote()
logger.Info().
Dur("execution-time", time.Since(s)).
Msg("remote call executed")
wg.Done()
}()

wg.Wait()

// happy case, both errs are nil and results are same
if localErr == nil && remoteErr == nil {
// if results are not same log the diff
if !reflect.DeepEqual(localRes, remoteRes) {
logger.Error().
Any("local", localRes).
Any("remote", remoteRes).
Msg("results from local and remote client are note the same")
}
}

// make sure if both return an error the errors are the same
if localErr != nil && remoteErr != nil {
if !errors.Is(localErr, remoteErr) {
logger.Error().
Str("local", localErr.Error()).
Str("remote", remoteErr.Error()).
Msg("errors from local and remote client are note the same")
}
}

// if remote received an error but local call worked, return the local result
// this can be due to rate-limits or pruned state on AN/EN
if localErr == nil && remoteErr != nil {
logger.Warn().
Str("remote-error", remoteErr.Error()).
Any("local-result", localRes).
Msg("error from remote client but not from local client")

return localRes, nil
}

// if remote succeeded but local received an error this is a bug
if localErr != nil && remoteErr == nil {
logger.Error().
Str("local-error", localErr.Error()).
Any("remote-result", remoteRes).
Msg("error from local client but not from remote client")
}

return remoteRes, remoteErr
}
41 changes: 41 additions & 0 deletions services/requester/evm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package requester

import (
"context"
"math/big"

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

type EVMClient interface {
// SendRawTransaction will submit signed transaction data to the network.
// The submitted EVM transaction hash is returned.
SendRawTransaction(ctx context.Context, data []byte) (common.Hash, error)

// GetBalance returns the amount of wei for the given address in the state of the
// given EVM block height.
GetBalance(ctx context.Context, address common.Address, evmHeight int64) (*big.Int, error)

// Call executes the given signed transaction data on the state for the given EVM block height.
// Note, this function doesn't make and changes in the state/blockchain and is
// useful to execute and retrieve values.
Call(ctx context.Context, data []byte, from common.Address, evmHeight int64) ([]byte, error)

// EstimateGas executes the given signed transaction data on the state for the given EVM block height.
// Note, this function doesn't make any changes in the state/blockchain and is
// useful to executed and retrieve the gas consumption and possible failures.
EstimateGas(ctx context.Context, data []byte, from common.Address, evmHeight int64) (uint64, error)

// GetNonce gets nonce from the network at the given EVM block height.
GetNonce(ctx context.Context, address common.Address, evmHeight int64) (uint64, error)

// GetCode returns the code stored at the given address in
// the state for the given EVM block height.
GetCode(ctx context.Context, address common.Address, evmHeight int64) ([]byte, error)

// GetLatestEVMHeight returns the latest EVM height of the network.
GetLatestEVMHeight(ctx context.Context) (uint64, error)

// GetStorageAt returns the storage from the state at the given address, key and block number.
GetStorageAt(ctx context.Context, address common.Address, hash common.Hash, evmHeight int64) (common.Hash, error)
}
Loading
Loading