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

[Indexer] Pebble storage implementation #47

Merged
Merged
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
a817f0b
Implement the eth_call JSON-RPC endpoint
m-Peter Jan 23, 2024
570218c
Introduce a FlowAccessAPI interface for injecting a Flow gRPC client …
m-Peter Jan 29, 2024
655c8f5
Use access.Client interface from flow-go-sdk and generate a mock for it
m-Peter Jan 29, 2024
86a9487
Extract the bridged account call Cadence script to its own file and e…
m-Peter Jan 31, 2024
1b20bb5
Proper mock generation for FlowAccessClient proxy to access.Client
m-Peter Jan 31, 2024
cb7c919
Merge pull request #24 from m-Peter/eth-call-endpoint
m-Peter Jan 31, 2024
6f97c77
Implement the eth_sendRawTransaction JSON-RPC endpoint
m-Peter Jan 23, 2024
4ad8061
Add TODOs for the eth_sendRawTransaction endpoint
m-Peter Jan 31, 2024
0ef8dd5
Merge pull request #26 from m-Peter/eth-send-raw-transaction-endpoint
m-Peter Jan 31, 2024
746afca
Implement the eth_getTransactionByHash JSON-RPC endpoint
m-Peter Feb 1, 2024
4d4d3d3
Merge pull request #35 from m-Peter/eth-get-transaction-by-hash-endpoint
m-Peter Feb 1, 2024
a3510c3
Make value returned by eth_gasPrice configurable
m-Peter Feb 6, 2024
7a979d0
Move DefaultGasPrice variable to config file
m-Peter Feb 6, 2024
c8c43be
Merge pull request #39 from m-Peter/configurable-gas-price
m-Peter Feb 6, 2024
bf48fb2
Implement the eth_getBalance JSON-RPC endpoint
m-Peter Feb 6, 2024
036e395
Extract some common functions to a utils file
m-Peter Feb 7, 2024
99170a1
Implement more block & transaction related JSON-RPC endpoints
m-Peter Feb 7, 2024
bbac807
Create deploy.yml
franklywatson Feb 7, 2024
a0bc86b
Return propert blockHash and from in transaction endpoints
m-Peter Feb 8, 2024
0b6d8ab
Merge pull request #43 from m-Peter/block-and-tx-related-endpoints
m-Peter Feb 8, 2024
4ac588d
Implement the eth_getBlockReceipts JSON-RPC endpoint
m-Peter Feb 8, 2024
56879c9
Docker file
nialexsan Feb 9, 2024
d01b36b
test deployment
nialexsan Feb 9, 2024
49bea52
Setup improvements for running the Flow EVM Gateway
m-Peter Feb 9, 2024
b4cff0a
Merge pull request #46 from m-Peter/setup-improvements
nialexsan Feb 9, 2024
d47f861
tweak dockerfile
nialexsan Feb 9, 2024
38dc2a9
clone via https
nialexsan Feb 9, 2024
472e3fc
Merge remote-tracking branch 'origin/main' into nialexsan/docker
nialexsan Feb 9, 2024
929027f
fix port
nialexsan Feb 9, 2024
5d22705
fixed paths
nialexsan Feb 9, 2024
8fa33ed
run script
nialexsan Feb 10, 2024
429c349
listen on port
nialexsan Feb 10, 2024
f6c896c
add block store / get example
Feb 12, 2024
3ca189a
db open
Feb 12, 2024
5c43b9d
simple store test wip
Feb 12, 2024
c63b617
move mocks
Feb 12, 2024
272d813
provide dir
Feb 12, 2024
3f08093
add basic store test
Feb 12, 2024
09d49e4
add keys and prefix generator
Feb 12, 2024
febacbb
change mock
Feb 12, 2024
4c35159
add retrieve block test
Feb 12, 2024
a9df70d
change key names
Feb 12, 2024
3b7f8a1
add get set helpers
Feb 12, 2024
8dce968
get block refactor
Feb 12, 2024
a31d2a1
update tests
Feb 12, 2024
4923ca6
add not found test
Feb 12, 2024
eae2ec1
get block helper
Feb 12, 2024
0301e7c
prefix with option key
Feb 12, 2024
22621a7
first last height
Feb 12, 2024
35c565d
add first last cache
Feb 12, 2024
60fcc72
add first last cache
Feb 12, 2024
9582693
add store receipt and get
Feb 12, 2024
2b2c583
refactor receipt store
Feb 12, 2024
651ca18
add transaction store and get
Feb 12, 2024
889af80
add store block height
Feb 12, 2024
52b0b8b
move out transactions
Feb 12, 2024
46cb167
move out receipts
Feb 12, 2024
9be25f8
add constructors
Feb 12, 2024
d1217b2
update tests
Feb 12, 2024
0431119
change id bytes
Feb 12, 2024
44acc4c
use indexer test suite
Feb 12, 2024
56d127b
block height and options
Feb 12, 2024
38e106f
better name
Feb 12, 2024
27431a7
fix block index for height
Feb 12, 2024
10d3521
bugfix
Feb 12, 2024
52395c6
update test
Feb 12, 2024
63d70ae
address PR comments
nialexsan Feb 12, 2024
ff69c7e
Merge pull request #45 from onflow/nialexsan/docker
nialexsan Feb 12, 2024
6382a16
Merge pull request #44 from m-Peter/eth-get-block-receipts-endpoint
m-Peter Feb 13, 2024
9d503ad
use init height in test
Feb 13, 2024
45ac1da
extend last height test
Feb 13, 2024
42a260c
fix updating height cache
Feb 13, 2024
425360e
transaction store fix
Feb 13, 2024
d2ffa63
add more block tests
Feb 13, 2024
ebe2c17
extend tx tests
Feb 13, 2024
d37a196
extend block tests
Feb 13, 2024
2b5b89a
inclusive start end
Feb 13, 2024
67e4083
add blooms for heights
Feb 13, 2024
7174ac8
receipt by height
Feb 13, 2024
b611bc2
change keys
Feb 13, 2024
4d434c8
remove uneeded test
Feb 13, 2024
db202a5
Update to latest additions in flow-go and cadence
m-Peter Feb 13, 2024
d7fe126
receipt for storage
Feb 13, 2024
3f758a2
fix pointer
Feb 13, 2024
a192447
add storage receipt
Feb 13, 2024
68bf7bb
fix test receipt
Feb 13, 2024
659722b
add todo
Feb 13, 2024
4ef3fcd
use receipt for storage
Feb 13, 2024
557f44a
compare receipt
Feb 13, 2024
27ac917
load range fix
Feb 13, 2024
1418f55
change index api
Feb 13, 2024
1659cb1
fix range test
Feb 13, 2024
c8e6cf2
change integration testing to use pebble
Feb 13, 2024
31a2a66
Merge pull request #48 from m-Peter/update-to-latest-additions
m-Peter Feb 13, 2024
d398e27
add bootstrap
Feb 13, 2024
d23ef93
add basic config
Feb 13, 2024
02ff517
improve logging and some errors
Feb 13, 2024
fbabd1e
add parsing config
Feb 13, 2024
64bb455
add todo
Feb 13, 2024
0d14cb5
clean up main
Feb 13, 2024
675d743
refactor out config
Feb 13, 2024
a54f4ba
start server refactor
Feb 13, 2024
9bd53b2
Update deploy.yml with required Flow envs
franklywatson Feb 13, 2024
5a19a0e
merge
Feb 14, 2024
91d0d5b
resolve additional changes from merge
Feb 14, 2024
400374e
mod tidy
Feb 14, 2024
8c552d3
update mocks
Feb 14, 2024
d16516b
skip generating block option
Feb 14, 2024
b53a38a
compiles with merged main
Feb 14, 2024
fe938cc
move bootstrap
Feb 14, 2024
6e6d98a
add storage types
Feb 14, 2024
8b2b3f4
fix comment warnings
Feb 14, 2024
930e966
remove unneeded
Feb 14, 2024
c5e3156
fix receiver name warning
Feb 14, 2024
f966fe1
use proper errors and remove fake data
Feb 14, 2024
416ed1a
fix errors in the API
Feb 14, 2024
a1d6dfc
temp comment api tests
Feb 14, 2024
500da4d
remove old store
Feb 14, 2024
354a90b
remove old storage implementation
Feb 14, 2024
c35dcc7
add gitignore for integration
Feb 14, 2024
79d573f
fix starting up
Feb 14, 2024
72a4f54
fix default network
Feb 14, 2024
ab39199
better naming for ingestion
Feb 14, 2024
e17ccd7
Merge pull request #51 from onflow/gregor/index/integrate
devbugging Feb 27, 2024
92b3134
pr reviews fixes
Feb 27, 2024
e677992
add todo
Feb 27, 2024
7b1032a
pr review comment
Feb 27, 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
15 changes: 13 additions & 2 deletions integration/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/onflow/flow-evm-gateway/services/logs"
"math/big"
"os"
"testing"
"time"

Expand Down Expand Up @@ -40,8 +41,13 @@ func TestIntegration_TransferValue(t *testing.T) {
srv, err := startEmulator()
require.NoError(t, err)

dbDir := "./db-test"
defer func() {
_ = os.Remove(dbDir)
}()

ctx, cancelIngestion := context.WithCancel(context.Background())
blocks, receipts, txs, err := startEventIngestionEngine(ctx)
blocks, receipts, txs, err := startEventIngestionEngine(ctx, dbDir)
require.NoError(t, err)

defer func() {
Expand Down Expand Up @@ -131,8 +137,13 @@ func TestIntegration_DeployCallContract(t *testing.T) {
srv, err := startEmulator()
require.NoError(t, err)

dbDir := "./db-test"
defer func() {
_ = os.Remove(dbDir)
}()

ctx, cancelIngestion := context.WithCancel(context.Background())
blocks, receipts, txs, err := startEventIngestionEngine(ctx)
blocks, receipts, txs, err := startEventIngestionEngine(ctx, dbDir)
require.NoError(t, err)

defer func() {
Expand Down
19 changes: 13 additions & 6 deletions integration/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/onflow/flow-emulator/server"
"github.com/onflow/flow-evm-gateway/services/events"
"github.com/onflow/flow-evm-gateway/storage"
"github.com/onflow/flow-evm-gateway/storage/memory"
"github.com/onflow/flow-evm-gateway/storage/pebble"
sdk "github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/access/grpc"
"github.com/onflow/flow-go-sdk/crypto"
Expand Down Expand Up @@ -73,7 +73,7 @@ func startEmulator() (*server.EmulatorServer, error) {
// listening for events.
// todo for now we return index storage as a way to check the data if it was correctly
// indexed this will be in future replaced with evm gateway API access
func startEventIngestionEngine(ctx context.Context) (
func startEventIngestionEngine(ctx context.Context, dbDir string) (
storage.BlockIndexer,
storage.ReceiptIndexer,
storage.TransactionIndexer,
Expand All @@ -90,11 +90,18 @@ func startEventIngestionEngine(ctx context.Context) (
}

subscriber := events.NewRPCSubscriber(client)
blocks := memory.NewBlockStorage(memory.WithLatestHeight(blk.Height))
receipts := memory.NewReceiptStorage()
txs := memory.NewTransactionStorage()

log := logger.With().Str("component", "ingestion").Logger()
log := logger.With().Str("component", "database").Logger()
db, err := pebble.New(dbDir, log)
if err != nil {
return nil, nil, nil, err
}

blocks, err := pebble.NewBlocks(db, pebble.WithInitHeight(blk.Height))
receipts := pebble.NewReceipts(db)
txs := pebble.NewTransactions(db)

log = logger.With().Str("component", "ingestion").Logger()
engine := events.NewEventIngestionEngine(subscriber, blocks, receipts, txs, log)

go func() {
Expand Down
32 changes: 32 additions & 0 deletions models/receipt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package models

import (
"github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"math/big"
)

// StorageReceipt is a receipt representation for storage.
//
// This struct copies the geth.Receipt type found here: https://github.com/ethereum/go-ethereum/blob/9bbb9df18549d6f81c3d1f4fc6c65f71bc92490d/core/types/receipt.go#L52
// the reason is if we use geth.Receipt some values will be skipped when RLP encoding which is because
// geth node has the data locally, but we don't in evm gateway, so we can not reproduce those values
// and we need to store them
type StorageReceipt struct {
Type uint8
PostState []byte
Status uint64
CumulativeGasUsed uint64
// todo we could skip bloom to optimize storage and dynamically recalculate it
Bloom gethTypes.Bloom
Logs []*gethTypes.Log
TxHash common.Hash
ContractAddress common.Address
GasUsed uint64
EffectiveGasPrice *big.Int
BlobGasUsed uint64
BlobGasPrice *big.Int
BlockHash common.Hash
BlockNumber *big.Int
TransactionIndex uint
}
2 changes: 1 addition & 1 deletion services/logs/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (r *RangeFilter) Match() ([]*gethTypes.Log, error) {

logs := make([]*gethTypes.Log, 0)
for i, bloom := range blooms {
if !bloomMatch(bloom, r.criteria) {
if !bloomMatch(*bloom, r.criteria) {
continue
}

Expand Down
5 changes: 3 additions & 2 deletions storage/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ type ReceiptIndexer interface {
GetByBlockHeight(height *big.Int) (*gethTypes.Receipt, error)

// BloomsForBlockRange returns slice of bloom values and a slice of block heights
// corresponding to each item in the bloom slice.
// corresponding to each item in the bloom slice. It only matches the blooms between
// inclusive start and end block height.
// Expected errors:
// - errors.InvalidRange if the block by the height was not indexed or if the end and start values are invalid.
BloomsForBlockRange(start, end *big.Int) ([]gethTypes.Bloom, []*big.Int, error)
BloomsForBlockRange(start, end *big.Int) ([]*gethTypes.Bloom, []*big.Int, error)
}

type TransactionIndexer interface {
Expand Down
136 changes: 98 additions & 38 deletions storage/index_testsuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/onflow/flow-evm-gateway/storage/errors"
"github.com/onflow/flow-evm-gateway/storage/mocks"
"github.com/stretchr/testify/suite"
"math/big"
)
Expand All @@ -17,7 +18,7 @@ type BlockTestSuite struct {
func (b *BlockTestSuite) TestGet() {
b.Run("existing block", func() {
height := uint64(1)
block := newBlock(height)
block := mocks.NewBlock(height)
err := b.Blocks.Store(block)
b.Require().NoError(err)

Expand All @@ -33,41 +34,70 @@ func (b *BlockTestSuite) TestGet() {
})

b.Run("non-existing block", func() {
retBlock, err := b.Blocks.GetByID(common.HexToHash("0x10"))
b.Require().Nil(retBlock)
// non-existing id
bl, err := b.Blocks.GetByID(common.HexToHash("0x10"))
b.Require().Nil(bl)
b.Require().ErrorIs(err, errors.NotFound)

// non-existing height
bl, err = b.Blocks.GetByHeight(uint64(200))
b.Require().Nil(bl)
b.Require().ErrorIs(err, errors.NotFound)
})
}

func (b *BlockTestSuite) TestStore() {
block := newBlock(10)
block := mocks.NewBlock(10)

b.Run("success", func() {
err := b.Blocks.Store(block)
b.Require().NoError(err)

// we allow overwriting blocks to make the actions idempotent
err = b.Blocks.Store(block)
b.Require().NoError(err)
})

b.Run("failed to store same block", func() {
err := b.Blocks.Store(block)
b.Require().ErrorIs(err, errors.Duplicate)
b.Run("store multiple blocks, and get one", func() {
for i := 0; i < 10; i++ {
err := b.Blocks.Store(mocks.NewBlock(uint64(10 + i)))
b.Require().NoError(err)
}

bl, err := b.Blocks.GetByHeight(15)
b.Require().NoError(err)

id, err := bl.Hash()
b.Require().NoError(err)
blId, err := b.Blocks.GetByID(id)
b.Require().Equal(bl, blId)
})
}

func (b *BlockTestSuite) TestHeights() {
b.Run("first height", func() {
first, err := b.Blocks.FirstHeight()
b.Require().NoError(err)
b.Require().Equal(uint64(1), first)
for i := 0; i < 5; i++ {
first, err := b.Blocks.FirstHeight()
b.Require().NoError(err)
b.Require().Equal(uint64(1), first)

// shouldn't affect first height
lastHeight := uint64(100 + i)
err = b.Blocks.Store(mocks.NewBlock(lastHeight))
b.Require().NoError(err)
}
})

b.Run("last height", func() {
lastHeight := uint64(100)
err := b.Blocks.Store(newBlock(lastHeight))
b.Require().NoError(err)

last, err := b.Blocks.LatestHeight()
b.Require().NoError(err)
b.Require().Equal(lastHeight, last)
for i := 0; i < 5; i++ {
lastHeight := uint64(100 + i)
err := b.Blocks.Store(mocks.NewBlock(lastHeight))
b.Require().NoError(err)

last, err := b.Blocks.LatestHeight()
b.Require().NoError(err)
b.Require().Equal(lastHeight, last)
}
})
}

Expand All @@ -77,28 +107,23 @@ type ReceiptTestSuite struct {
}

func (s *ReceiptTestSuite) TestStoreReceipt() {
receipt := newReceipt(1, common.HexToHash("0xf1"))
receipt := mocks.NewReceipt(1, common.HexToHash("0xf1"))

s.Run("store receipt successfully", func() {
err := s.ReceiptIndexer.Store(receipt)
s.Require().NoError(err)
})

s.Run("store duplicate receipt", func() {
err := s.ReceiptIndexer.Store(receipt)
s.Require().ErrorIs(err, errors.Duplicate)
})
}

func (s *ReceiptTestSuite) TestGetReceiptByTransactionID() {
s.Run("existing transaction ID", func() {
receipt := newReceipt(2, common.HexToHash("0xf2"))
receipt := mocks.NewReceipt(2, common.HexToHash("0xf2"))
err := s.ReceiptIndexer.Store(receipt)
s.Require().NoError(err)

retReceipt, err := s.ReceiptIndexer.GetByTransactionID(receipt.TxHash)
s.Require().NoError(err)
s.Require().Equal(receipt, retReceipt)
s.compareReceipts(receipt, retReceipt)
})

s.Run("non-existing transaction ID", func() {
Expand All @@ -111,16 +136,16 @@ func (s *ReceiptTestSuite) TestGetReceiptByTransactionID() {

func (s *ReceiptTestSuite) TestGetReceiptByBlockID() {
s.Run("existing block ID", func() {
receipt := newReceipt(3, common.HexToHash("0x1"))
receipt := mocks.NewReceipt(3, common.HexToHash("0x1"))
err := s.ReceiptIndexer.Store(receipt)
s.Require().NoError(err)

retReceipt, err := s.ReceiptIndexer.GetByBlockHeight(receipt.BlockNumber)
s.Require().NoError(err)
s.Require().Equal(receipt, retReceipt)
s.compareReceipts(receipt, retReceipt)
})

s.Run("non-existing block ID", func() {
s.Run("non-existing block height", func() {
retReceipt, err := s.ReceiptIndexer.GetByBlockHeight(big.NewInt(1337))
s.Require().Nil(retReceipt)
s.Require().ErrorIs(err, errors.NotFound)
Expand All @@ -135,7 +160,7 @@ func (s *ReceiptTestSuite) TestBloomsForBlockRange() {
testBlooms := make([]*types.Bloom, 0)

for i := start.Uint64(); i < end.Uint64(); i++ {
r := newReceipt(i, common.HexToHash(fmt.Sprintf("0xf1%d", i)))
r := mocks.NewReceipt(i, common.HexToHash(fmt.Sprintf("0xf1%d", i)))
testBlooms = append(testBlooms, &r.Bloom)
err := s.ReceiptIndexer.Store(r)
s.Require().NoError(err)
Expand All @@ -146,6 +171,8 @@ func (s *ReceiptTestSuite) TestBloomsForBlockRange() {
s.Require().Len(blooms, len(testBlooms))
s.Require().Len(heights, len(testBlooms))
s.Require().Equal(testBlooms, blooms)

// todo smaller block range
})

s.Run("invalid block range", func() {
Expand All @@ -161,40 +188,73 @@ func (s *ReceiptTestSuite) TestBloomsForBlockRange() {
start := big.NewInt(100)
end := big.NewInt(105)
blooms, heights, err := s.ReceiptIndexer.BloomsForBlockRange(start, end)
s.Require().NoError(err)
s.Require().ErrorIs(err, errors.InvalidRange)
s.Require().Nil(blooms)
s.Require().Nil(heights)
})
}

func (s *ReceiptTestSuite) compareReceipts(expected *types.Receipt, actual *types.Receipt) {
s.Require().Equal(expected.BlockNumber, actual.BlockNumber)
s.Require().Equal(expected.TxHash, actual.TxHash)
s.Require().Equal(expected.Type, actual.Type)
s.Require().Equal(expected.PostState, actual.PostState)
s.Require().Equal(expected.Status, actual.Status)
s.Require().Equal(expected.CumulativeGasUsed, actual.CumulativeGasUsed)
s.Require().Equal(expected.Bloom, actual.Bloom)
s.Require().Equal(len(expected.Logs), len(actual.Logs))
for i := range expected.Logs {
s.Require().Equal(expected.Logs[i], actual.Logs[i])
}
s.Require().Equal(expected.TxHash, actual.TxHash)
s.Require().Equal(expected.ContractAddress, actual.ContractAddress)
s.Require().Equal(expected.GasUsed, actual.GasUsed)
s.Require().Equal(expected.EffectiveGasPrice, actual.EffectiveGasPrice)
s.Require().Equal(expected.BlobGasUsed, actual.BlobGasUsed)
s.Require().Equal(expected.BlockHash, actual.BlockHash)
s.Require().Equal(expected.BlockNumber, actual.BlockNumber)
s.Require().Equal(expected.TransactionIndex, actual.TransactionIndex)
}

type TransactionTestSuite struct {
suite.Suite
TransactionIndexer TransactionIndexer
}

func (s *TransactionTestSuite) TestStoreTransaction() {
tx := newTransaction(0)
tx := mocks.NewTransaction(0)

s.Run("store transaction successfully", func() {
err := s.TransactionIndexer.Store(tx)
s.Require().NoError(err)
})

s.Run("store duplicate transaction", func() {
err := s.TransactionIndexer.Store(tx)
s.Require().ErrorIs(err, errors.Duplicate)
})
}

func (s *TransactionTestSuite) TestGetTransaction() {
s.Run("existing transaction", func() {
tx := newTransaction(1)
tx := mocks.NewTransaction(1)
err := s.TransactionIndexer.Store(tx)
s.Require().NoError(err)

retTx, err := s.TransactionIndexer.Get(tx.Hash())
s.Require().NoError(err)
s.Require().Equal(tx, retTx)
s.Require().Equal(tx.Hash(), retTx.Hash()) // if hashes are equal the data must be equal

// allow same transaction overwrites
s.Require().NoError(s.TransactionIndexer.Store(retTx))
})

s.Run("store multiple transactions and get single", func() {
var tx *types.Transaction
for i := 0; i < 10; i++ {
tx = mocks.NewTransaction(uint64(10 + i))
err := s.TransactionIndexer.Store(tx)
s.Require().NoError(err)
}

t, err := s.TransactionIndexer.Get(tx.Hash())
s.Require().Equal(tx.Hash(), t.Hash())
s.Require().NoError(err)
})

s.Run("non-existing transaction", func() {
Expand Down
Loading