diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 75f6e91d17..88349bcca0 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -7,7 +7,7 @@ import ( "sync" "sync/atomic" - "github.com/0xPolygon/polygon-edge/blockchain/storage" + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/helper/common" "github.com/0xPolygon/polygon-edge/state" @@ -44,7 +44,7 @@ var ( type Blockchain struct { logger hclog.Logger // The logger object - db storage.Storage // The Storage object (database) + db *storagev2.Storage // The Storage object (database) consensus Verifier executor Executor txSigner TxSigner @@ -188,7 +188,7 @@ func (b *Blockchain) GetAvgGasPrice() *big.Int { // NewBlockchain creates a new blockchain object func NewBlockchain( logger hclog.Logger, - db storage.Storage, + db *storagev2.Storage, genesisConfig *chain.Chain, consensus Verifier, executor Executor, @@ -398,7 +398,7 @@ func (b *Blockchain) writeGenesis(genesis *chain.Genesis) error { // writeGenesisImpl writes the genesis file to the DB + blockchain reference func (b *Blockchain) writeGenesisImpl(header *types.Header) error { - batchWriter := storage.NewBatchWriter(b.db) + batchWriter := b.db.NewWriter() newTD := new(big.Int).SetUint64(header.Difficulty) @@ -438,9 +438,14 @@ func (b *Blockchain) GetTD(hash types.Hash) (*big.Int, bool) { return b.readTotalDifficulty(hash) } -// GetReceiptsByHash returns the receipts by their hash +// GetReceiptsByHash returns the receipts by block hash func (b *Blockchain) GetReceiptsByHash(hash types.Hash) ([]*types.Receipt, error) { - return b.db.ReadReceipts(hash) + n, err := b.db.ReadBlockLookup(hash) + if err != nil { + return nil, err + } + + return b.db.ReadReceipts(n, hash) } // GetBodyByHash returns the body by their hash @@ -468,7 +473,12 @@ func (b *Blockchain) readHeader(hash types.Hash) (*types.Header, bool) { } // Cache miss, load it from the DB - hh, err := b.db.ReadHeader(hash) + n, err := b.db.ReadBlockLookup(hash) + if err != nil { + return nil, false + } + + hh, err := b.db.ReadHeader(n, hash) if err != nil { return nil, false } @@ -482,7 +492,12 @@ func (b *Blockchain) readHeader(hash types.Hash) (*types.Header, bool) { // readBody reads the block's body, using the block hash func (b *Blockchain) readBody(hash types.Hash) (*types.Body, bool) { - bb, err := b.db.ReadBody(hash) + n, err := b.db.ReadBlockLookup(hash) + if err != nil { + return nil, false + } + + bb, err := b.db.ReadBody(n, hash) if err != nil { b.logger.Error("failed to read body", "err", err) @@ -491,9 +506,9 @@ func (b *Blockchain) readBody(hash types.Hash) (*types.Body, bool) { // To return from field in the transactions of the past blocks if updated := b.recoverFromFieldsInTransactions(bb.Transactions); updated { - batchWriter := storage.NewBatchWriter(b.db) + batchWriter := b.db.NewWriter() - batchWriter.PutBody(hash, bb) + batchWriter.PutBody(n, hash, bb) if err := batchWriter.WriteBatch(); err != nil { b.logger.Warn("failed to write body into storage", "hash", hash, "err", err) @@ -518,7 +533,12 @@ func (b *Blockchain) readTotalDifficulty(headerHash types.Hash) (*big.Int, bool) } // Miss, read the difficulty from the DB - dbDifficulty, ok := b.db.ReadTotalDifficulty(headerHash) + n, err := b.db.ReadBlockLookup(headerHash) + if err != nil { + return nil, false + } + + dbDifficulty, ok := b.db.ReadTotalDifficulty(n, headerHash) if !ok { return nil, false } @@ -573,7 +593,7 @@ func (b *Blockchain) WriteHeadersWithBodies(headers []*types.Header) error { for _, header := range headers { event := &Event{} - batchWriter := storage.NewBatchWriter(b.db) + batchWriter := b.db.NewWriter() isCanonical, newTD, err := b.writeHeaderImpl(batchWriter, event, header) if err != nil { @@ -811,7 +831,7 @@ func (b *Blockchain) WriteFullBlock(fblock *types.FullBlock, source string) erro header := block.Header - batchWriter := storage.NewBatchWriter(b.db) + batchWriter := b.db.NewWriter() if err := b.writeBody(batchWriter, block); err != nil { return err @@ -828,7 +848,7 @@ func (b *Blockchain) WriteFullBlock(fblock *types.FullBlock, source string) erro // write the receipts, do it only after the header has been written. // Otherwise, a client might ask for a header once the receipt is valid, // but before it is written into the storage - batchWriter.PutReceipts(block.Hash(), fblock.Receipts) + batchWriter.PutReceipts(block.Number(), block.Hash(), fblock.Receipts) // update snapshot if err := b.consensus.ProcessHeaders([]*types.Header{header}); err != nil { @@ -876,7 +896,7 @@ func (b *Blockchain) WriteBlock(block *types.Block, source string) error { header := block.Header - batchWriter := storage.NewBatchWriter(b.db) + batchWriter := b.db.NewWriter() if err := b.writeBody(batchWriter, block); err != nil { return err @@ -899,7 +919,7 @@ func (b *Blockchain) WriteBlock(block *types.Block, source string) error { // write the receipts, do it only after the header has been written. // Otherwise, a client might ask for a header once the receipt is valid, // but before it is written into the storage - batchWriter.PutReceipts(block.Hash(), blockReceipts) + batchWriter.PutReceipts(block.Number(), block.Hash(), blockReceipts) // update snapshot if err := b.consensus.ProcessHeaders([]*types.Header{header}); err != nil { @@ -990,7 +1010,7 @@ func (b *Blockchain) updateGasPriceAvgWithBlock(block *types.Block) { // writeBody writes the block body to the DB. // Additionally, it also updates the txn lookup, for txnHash -> block lookups -func (b *Blockchain) writeBody(batchWriter *storage.BatchWriter, block *types.Block) error { +func (b *Blockchain) writeBody(batchWriter *storagev2.Writer, block *types.Block) error { // Recover 'from' field in tx before saving // Because the block passed from the consensus layer doesn't have from field in tx, // due to missing encoding in RLP @@ -999,21 +1019,24 @@ func (b *Blockchain) writeBody(batchWriter *storage.BatchWriter, block *types.Bl } // Write the full body (txns + receipts) - batchWriter.PutBody(block.Header.Hash, block.Body()) + batchWriter.PutBody(block.Number(), block.Hash(), block.Body()) - // Write txn lookups (txHash -> block) + // Write txn lookups (txHash -> block number) for _, txn := range block.Transactions { - batchWriter.PutTxLookup(txn.Hash(), block.Hash()) + batchWriter.PutTxLookup(txn.Hash(), block.Number()) } return nil } // ReadTxLookup returns the block hash using the transaction hash -func (b *Blockchain) ReadTxLookup(hash types.Hash) (types.Hash, bool) { - v, ok := b.db.ReadTxLookup(hash) +func (b *Blockchain) ReadTxLookup(hash types.Hash) (uint64, bool) { + v, err := b.db.ReadTxLookup(hash) + if err != nil { + return 0, false + } - return v, ok + return v, true } // recoverFromFieldsInBlock recovers 'from' fields in the transactions of the given block @@ -1140,7 +1163,7 @@ func (b *Blockchain) dispatchEvent(evnt *Event) { // writeHeaderImpl writes a block and the data, assumes the genesis is already set // Returning parameters (is canonical header, new total difficulty, error) func (b *Blockchain) writeHeaderImpl( - batchWriter *storage.BatchWriter, evnt *Event, header *types.Header) (bool, *big.Int, error) { + batchWriter *storagev2.Writer, evnt *Event, header *types.Header) (bool, *big.Int, error) { // parent total difficulty of incoming header parentTD, ok := b.readTotalDifficulty(header.ParentHash) if !ok { @@ -1188,8 +1211,9 @@ func (b *Blockchain) writeHeaderImpl( } batchWriter.PutHeader(header) - batchWriter.PutTotalDifficulty(header.Hash, incomingTD) + batchWriter.PutTotalDifficulty(header.Number, header.Hash, incomingTD) batchWriter.PutForks(forks) + batchWriter.PutBlockLookup(header.Hash, header.Number) // new block has lower difficulty, create a new fork evnt.AddOldHeader(header) @@ -1202,7 +1226,7 @@ func (b *Blockchain) writeHeaderImpl( func (b *Blockchain) getForksToWrite(header *types.Header) ([]types.Hash, error) { forks, err := b.db.ReadForks() if err != nil { - if errors.Is(err, storage.ErrNotFound) { + if errors.Is(err, storagev2.ErrNotFound) { forks = []types.Hash{} } else { return nil, err @@ -1222,7 +1246,7 @@ func (b *Blockchain) getForksToWrite(header *types.Header) ([]types.Hash, error) // handleReorg handles a reorganization event func (b *Blockchain) handleReorg( - batchWriter *storage.BatchWriter, + batchWriter *storagev2.Writer, evnt *Event, oldHeader *types.Header, newHeader *types.Header, @@ -1414,7 +1438,7 @@ func (b *Blockchain) calcBaseFeeDelta(gasUsedDelta, parentGasTarget, baseFee uin } func (b *Blockchain) writeBatchAndUpdate( - batchWriter *storage.BatchWriter, + batchWriter *storagev2.Writer, header *types.Header, newTD *big.Int, isCanonnical bool) error { diff --git a/blockchain/blockchain_test.go b/blockchain/blockchain_test.go index 8b66c81b80..7f4cabdd9b 100644 --- a/blockchain/blockchain_test.go +++ b/blockchain/blockchain_test.go @@ -14,7 +14,6 @@ import ( "time" "github.com/0xPolygon/polygon-edge/helper/common" - "github.com/0xPolygon/polygon-edge/helper/hex" "github.com/0xPolygon/polygon-edge/state" "github.com/hashicorp/go-hclog" lru "github.com/hashicorp/golang-lru" @@ -23,9 +22,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/0xPolygon/polygon-edge/blockchain/storage" - "github.com/0xPolygon/polygon-edge/blockchain/storage/leveldb" - "github.com/0xPolygon/polygon-edge/blockchain/storage/memory" + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" + "github.com/0xPolygon/polygon-edge/blockchain/storagev2/leveldb" + "github.com/0xPolygon/polygon-edge/blockchain/storagev2/memory" "github.com/0xPolygon/polygon-edge/types" ) @@ -502,7 +501,7 @@ func TestInsertHeaders(t *testing.T) { assert.Equal(t, head.Hash, expected.Hash) forks, err := b.GetForks() - if err != nil && !errors.Is(err, storage.ErrNotFound) { + if err != nil && !errors.Is(err, storagev2.ErrNotFound) { t.Fatal(err) } @@ -546,7 +545,7 @@ func TestForkUnknownParents(t *testing.T) { h1 := AppendNewTestHeaders(h0[:5], 10) // Write genesis - batchWriter := storage.NewBatchWriter(b.db) + batchWriter := b.db.NewWriter() td := new(big.Int).SetUint64(h0[0].Difficulty) batchWriter.PutCanonicalHeader(h0[0], td) @@ -574,7 +573,7 @@ func TestBlockchainWriteBody(t *testing.T) { ) *Blockchain { t.Helper() - dbStorage, err := memory.NewMemoryStorage(nil) + dbStorage, err := memory.NewMemoryStorage() assert.NoError(t, err) chain := &Blockchain{ @@ -607,7 +606,7 @@ func TestBlockchainWriteBody(t *testing.T) { chain := newChain(t, txFromByTxHash, "t1") defer chain.db.Close() - batchWriter := storage.NewBatchWriter(chain.db) + batchWriter := chain.db.NewWriter() assert.NoError( t, @@ -636,7 +635,7 @@ func TestBlockchainWriteBody(t *testing.T) { chain := newChain(t, txFromByTxHash, "t2") defer chain.db.Close() - batchWriter := storage.NewBatchWriter(chain.db) + batchWriter := chain.db.NewWriter() assert.ErrorIs( t, @@ -668,8 +667,9 @@ func TestBlockchainWriteBody(t *testing.T) { chain := newChain(t, txFromByTxHash, "t3") defer chain.db.Close() - batchWriter := storage.NewBatchWriter(chain.db) + batchWriter := chain.db.NewWriter() + batchWriter.PutBlockLookup(block.Hash(), block.Number()) batchWriter.PutHeader(block.Header) assert.NoError(t, chain.writeBody(batchWriter, block)) @@ -877,7 +877,7 @@ func Test_recoverFromFieldsInTransactions(t *testing.T) { } func TestBlockchainReadBody(t *testing.T) { - dbStorage, err := memory.NewMemoryStorage(nil) + dbStorage, err := memory.NewMemoryStorage() assert.NoError(t, err) txFromByTxHash := make(map[types.Hash]types.Address) @@ -891,7 +891,7 @@ func TestBlockchainReadBody(t *testing.T) { }, } - batchWriter := storage.NewBatchWriter(b.db) + batchWriter := b.db.NewWriter() tx := types.NewTx(types.NewLegacyTx( types.WithValue(big.NewInt(10)), @@ -966,13 +966,20 @@ func TestCalculateGasLimit(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - storageCallback := func(storage *storage.MockStorage) { - storage.HookReadHeader(func(hash types.Hash) (*types.Header, error) { - return &types.Header{ - // This is going to be the parent block header - GasLimit: tt.parentGasLimit, - }, nil - }) + storageCallback := func(storage *storagev2.Storage) { + h := &types.Header{ + // This is going to be the parent block header + GasLimit: tt.parentGasLimit, + } + h.ComputeHash() + + w := storage.NewWriter() + + w.PutBlockLookup(h.Hash, h.Number) + w.PutHeader(h) + w.PutCanonicalHash(h.Number, h.Hash) + err := w.WriteBatch() + require.NoError(t, err) } b, blockchainErr := NewMockBlockchain(map[TestCallbackType]interface{}{ @@ -1068,16 +1075,7 @@ func TestBlockchain_VerifyBlockParent(t *testing.T) { t.Run("Missing parent block", func(t *testing.T) { t.Parallel() - // Set up the storage callback - storageCallback := func(storage *storage.MockStorage) { - storage.HookReadHeader(func(hash types.Hash) (*types.Header, error) { - return nil, errors.New("not found") - }) - } - - blockchain, err := NewMockBlockchain(map[TestCallbackType]interface{}{ - StorageCallback: storageCallback, - }) + blockchain, err := NewMockBlockchain(nil) if err != nil { t.Fatalf("unable to instantiate new blockchain, %v", err) } @@ -1092,14 +1090,21 @@ func TestBlockchain_VerifyBlockParent(t *testing.T) { assert.ErrorIs(t, blockchain.verifyBlockParent(block), ErrParentNotFound) }) - t.Run("Parent hash mismatch", func(t *testing.T) { + t.Run("Invalid parent hash", func(t *testing.T) { t.Parallel() // Set up the storage callback - storageCallback := func(storage *storage.MockStorage) { - storage.HookReadHeader(func(hash types.Hash) (*types.Header, error) { - return emptyHeader.Copy(), nil - }) + storageCallback := func(storage *storagev2.Storage) { + h := &types.Header{ + Hash: types.ZeroHash, + } + + w := storage.NewWriter() + + w.PutBlockLookup(h.Hash, h.Number) + w.PutHeader(h) + err := w.WriteBatch() + require.NoError(t, err) } blockchain, err := NewMockBlockchain(map[TestCallbackType]interface{}{ @@ -1122,37 +1127,15 @@ func TestBlockchain_VerifyBlockParent(t *testing.T) { t.Parallel() // Set up the storage callback - storageCallback := func(storage *storage.MockStorage) { - storage.HookReadHeader(func(hash types.Hash) (*types.Header, error) { - return emptyHeader.Copy(), nil - }) - } + storageCallback := func(storage *storagev2.Storage) { + h := emptyHeader - blockchain, err := NewMockBlockchain(map[TestCallbackType]interface{}{ - StorageCallback: storageCallback, - }) - if err != nil { - t.Fatalf("unable to instantiate new blockchain, %v", err) - } + w := storage.NewWriter() - // Create a dummy block with a number much higher than the parent - block := &types.Block{ - Header: &types.Header{ - Number: 10, - }, - } - - assert.ErrorIs(t, blockchain.verifyBlockParent(block), ErrParentHashMismatch) - }) - - t.Run("Invalid block sequence", func(t *testing.T) { - t.Parallel() - - // Set up the storage callback - storageCallback := func(storage *storage.MockStorage) { - storage.HookReadHeader(func(hash types.Hash) (*types.Header, error) { - return emptyHeader.Copy(), nil - }) + w.PutBlockLookup(h.Hash, h.Number) + w.PutHeader(h) + err := w.WriteBatch() + require.NoError(t, err) } blockchain, err := NewMockBlockchain(map[TestCallbackType]interface{}{ @@ -1180,10 +1163,15 @@ func TestBlockchain_VerifyBlockParent(t *testing.T) { parentHeader.GasLimit = 5000 // Set up the storage callback - storageCallback := func(storage *storage.MockStorage) { - storage.HookReadHeader(func(hash types.Hash) (*types.Header, error) { - return emptyHeader.Copy(), nil - }) + storageCallback := func(storage *storagev2.Storage) { + h := emptyHeader + + w := storage.NewWriter() + + w.PutBlockLookup(h.Hash, h.Number) + w.PutHeader(h) + err := w.WriteBatch() + require.NoError(t, err) } blockchain, err := NewMockBlockchain(map[TestCallbackType]interface{}{ @@ -1254,16 +1242,7 @@ func TestBlockchain_VerifyBlockBody(t *testing.T) { t.Run("Invalid execution result - missing parent", func(t *testing.T) { t.Parallel() - // Set up the storage callback - storageCallback := func(storage *storage.MockStorage) { - storage.HookReadHeader(func(hash types.Hash) (*types.Header, error) { - return nil, errors.New("not found") - }) - } - - blockchain, err := NewMockBlockchain(map[TestCallbackType]interface{}{ - StorageCallback: storageCallback, - }) + blockchain, err := NewMockBlockchain(nil) if err != nil { t.Fatalf("unable to instantiate new blockchain, %v", err) } @@ -1285,11 +1264,15 @@ func TestBlockchain_VerifyBlockBody(t *testing.T) { errBlockCreatorNotFound := errors.New("not found") // Set up the storage callback - storageCallback := func(storage *storage.MockStorage) { - // This is used for parent fetching - storage.HookReadHeader(func(hash types.Hash) (*types.Header, error) { - return emptyHeader.Copy(), nil - }) + storageCallback := func(storage *storagev2.Storage) { + h := emptyHeader + + w := storage.NewWriter() + + w.PutBlockLookup(types.ZeroHash, h.Number) + w.PutHeader(h) + err := w.WriteBatch() + require.NoError(t, err) } // Set up the verifier callback @@ -1325,11 +1308,15 @@ func TestBlockchain_VerifyBlockBody(t *testing.T) { errUnableToExecute := errors.New("unable to execute transactions") // Set up the storage callback - storageCallback := func(storage *storage.MockStorage) { - // This is used for parent fetching - storage.HookReadHeader(func(hash types.Hash) (*types.Header, error) { - return emptyHeader.Copy(), nil - }) + storageCallback := func(storage *storagev2.Storage) { + h := emptyHeader + + w := storage.NewWriter() + + w.PutBlockLookup(types.ZeroHash, h.Number) + w.PutHeader(h) + err := w.WriteBatch() + require.NoError(t, err) } executorCallback := func(executor *mockExecutor) { @@ -1547,10 +1534,6 @@ func TestBlockchain_CalculateBaseFee(t *testing.T) { func TestBlockchain_WriteFullBlock(t *testing.T) { t.Parallel() - getKey := func(p []byte, k []byte) []byte { - return append(append(make([]byte, 0, len(p)+len(k)), p...), k...) - } - db := map[string][]byte{} consensusMock := &MockVerifier{ processHeadersFn: func(hs []*types.Header) error { assert.Len(t, hs, 1) @@ -1559,10 +1542,7 @@ func TestBlockchain_WriteFullBlock(t *testing.T) { }, } - storageMock := storage.NewMockStorage() - storageMock.HookNewBatch(func() storage.Batch { - return memory.NewBatchMemory(db) - }) + storageMock, _ := memory.NewMemoryStorage() bc := &Blockchain{ gpAverage: &gasPriceAverage{ @@ -1604,6 +1584,7 @@ func TestBlockchain_WriteFullBlock(t *testing.T) { existingHeader.ComputeHash() bc.currentHeader.Store(existingHeader) bc.currentDifficulty.Store(existingTD) + bc.difficultyCache.Add(existingHeader.Hash, existingTD) header.ParentHash = existingHeader.Hash bc.txSigner = &mockSigner{ @@ -1622,10 +1603,15 @@ func TestBlockchain_WriteFullBlock(t *testing.T) { }, "polybft") require.NoError(t, err) - require.Equal(t, 0, len(db)) - require.Equal(t, uint64(1), bc.currentHeader.Load().Number) + require.Equal(t, existingHeader.Number, bc.currentHeader.Load().Number) + require.Equal(t, existingTD, bc.currentDifficulty.Load()) + require.True(t, bc.difficultyCache.Contains(existingHeader.Hash)) - // already existing block write + _, err = bc.db.ReadBlockLookup(existingHeader.Hash) + require.Error(t, err) + assert.ErrorIs(t, err, storagev2.ErrNotFound) + + // new block write err = bc.WriteFullBlock(&types.FullBlock{ Block: &types.Block{ Header: header, @@ -1635,16 +1621,39 @@ func TestBlockchain_WriteFullBlock(t *testing.T) { }, "polybft") require.NoError(t, err) - require.Equal(t, 8, len(db)) - require.Equal(t, uint64(2), bc.currentHeader.Load().Number) - require.NotNil(t, db[hex.EncodeToHex(getKey(storage.BODY, header.Hash.Bytes()))]) - require.NotNil(t, db[hex.EncodeToHex(getKey(storage.TX_LOOKUP_PREFIX, tx.Hash().Bytes()))]) - require.NotNil(t, db[hex.EncodeToHex(getKey(storage.HEADER, header.Hash.Bytes()))]) - require.NotNil(t, db[hex.EncodeToHex(getKey(storage.HEAD, storage.HASH))]) - require.NotNil(t, db[hex.EncodeToHex(getKey(storage.CANONICAL, common.EncodeUint64ToBytes(header.Number)))]) - require.NotNil(t, db[hex.EncodeToHex(getKey(storage.DIFFICULTY, header.Hash.Bytes()))]) - require.NotNil(t, db[hex.EncodeToHex(getKey(storage.CANONICAL, common.EncodeUint64ToBytes(header.Number)))]) - require.NotNil(t, db[hex.EncodeToHex(getKey(storage.RECEIPTS, header.Hash.Bytes()))]) + require.Equal(t, header.Number, bc.currentHeader.Load().Number) + + n, err := bc.db.ReadBlockLookup(header.Hash) + require.NoError(t, err) + require.Equal(t, header.Number, n) + + b, err := bc.db.ReadBody(header.Number, header.Hash) + require.NoError(t, err) + require.NotNil(t, b) + + l, err := bc.db.ReadTxLookup(tx.Hash()) + require.NoError(t, err) + require.Equal(t, header.Number, l) + + h, err := bc.db.ReadHeader(header.Number, header.Hash) + require.NoError(t, err) + require.NotNil(t, h) + + hh, ok := bc.db.ReadHeadHash() + require.True(t, ok) + require.Equal(t, header.Hash, hh) + + ch, ok := bc.db.ReadCanonicalHash(header.Number) + require.True(t, ok) + require.Equal(t, header.Hash, ch) + + td, ok := bc.db.ReadTotalDifficulty(header.Number, header.Hash) + require.True(t, ok) + require.NotNil(t, td) + + r, err := bc.db.ReadReceipts(header.Number, header.Hash) + require.NoError(t, err) + require.NotNil(t, r) } func TestDiskUsageWriteBatchAndUpdate(t *testing.T) { @@ -1752,7 +1761,7 @@ func blockWriter(tb testing.TB, numberOfBlocks uint64, blockTime, checkInterval for { <-blockTicker.C - batchWriter := storage.NewBatchWriter(db) + batchWriter := db.NewWriter() block.Block.Header.Number = counter.GetValue() block.Block.Header.Hash = types.StringToHash(fmt.Sprintf("%d", counter.GetValue())) @@ -1764,9 +1773,9 @@ func blockWriter(tb testing.TB, numberOfBlocks uint64, blockTime, checkInterval } batchWriter.PutHeader(block.Block.Header) - batchWriter.PutBody(block.Block.Hash(), block.Block.Body()) + batchWriter.PutBody(block.Block.Number(), block.Block.Hash(), block.Block.Body()) - batchWriter.PutReceipts(block.Block.Hash(), receipts) + batchWriter.PutReceipts(block.Block.Number(), block.Block.Hash(), receipts) require.NoError(tb, blockchain.writeBatchAndUpdate(batchWriter, block.Block.Header, big.NewInt(0), false)) diff --git a/blockchain/storage/leveldb/leveldb_perf_test.go b/blockchain/storage/leveldb/leveldb_perf_test.go new file mode 100644 index 0000000000..cbfd443ba1 --- /dev/null +++ b/blockchain/storage/leveldb/leveldb_perf_test.go @@ -0,0 +1,244 @@ +package leveldb + +import ( + "math/big" + "math/rand" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/blockchain" + "github.com/0xPolygon/polygon-edge/blockchain/storage" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" +) + +func createTxs(t *testing.T, startNonce, count int, from types.Address, to *types.Address) []*types.Transaction { + t.Helper() + + txs := make([]*types.Transaction, count) + + for i := range txs { + tx := types.NewTx(types.NewDynamicFeeTx( + types.WithGas(types.StateTransactionGasLimit), + types.WithNonce(uint64(startNonce+i)), + types.WithFrom(from), + types.WithTo(to), + types.WithValue(big.NewInt(2000)), + types.WithGasFeeCap(big.NewInt(100)), + types.WithGasTipCap(big.NewInt(10)), + )) + + txs[i] = tx + } + + return txs +} + +const letterBytes = "abcdef0123456789" + +func randStringBytes(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + + return string(b) +} + +func createBlock(t *testing.T) *types.FullBlock { + t.Helper() + + transactionsCount := 2500 + status := types.ReceiptSuccess + addr1 := types.StringToAddress("17878aa") + addr2 := types.StringToAddress("2bf5653") + b := &types.FullBlock{ + Block: &types.Block{ + Header: &types.Header{ + Number: 0, + ExtraData: make([]byte, 32), + Hash: types.ZeroHash, + }, + Transactions: createTxs(t, 0, transactionsCount, addr1, &addr2), + Uncles: blockchain.NewTestHeaders(10), + }, + Receipts: make([]*types.Receipt, transactionsCount), + } + + logs := make([]*types.Log, 10) + + for i := 0; i < 10; i++ { + logs[i] = &types.Log{ + Address: addr1, + Topics: []types.Hash{types.StringToHash("t1"), types.StringToHash("t2"), types.StringToHash("t3")}, + Data: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xbb, 0xaa, 0x01, 0x012}, + } + } + + for i := 0; i < len(b.Block.Transactions); i++ { + b.Receipts[i] = &types.Receipt{ + TxHash: b.Block.Transactions[i].Hash(), + Root: types.StringToHash("mockhashstring"), + TransactionType: types.LegacyTxType, + GasUsed: uint64(100000), + Status: &status, + Logs: logs, + CumulativeGasUsed: uint64(100000), + ContractAddress: &types.Address{0xaa, 0xbb, 0xcc, 0xdd, 0xab, 0xac}, + } + } + + for i := 0; i < 5; i++ { + b.Receipts[i].LogsBloom = types.CreateBloom(b.Receipts) + } + + return b +} + +func openStorage(t *testing.T, p string) (storage.Storage, func(), string) { + t.Helper() + + s, err := NewLevelDBStorage(p, hclog.NewNullLogger()) + require.NoError(t, err) + + closeFn := func() { + require.NoError(t, s.Close()) + + if err := s.Close(); err != nil { + t.Fatal(err) + } + + require.NoError(t, os.RemoveAll(p)) + } + + return s, closeFn, p +} + +func dbSize(t *testing.T, path string) int64 { + t.Helper() + + var size int64 + + err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error { + if err != nil { + t.Fail() + } + + if info != nil && !info.IsDir() && strings.Contains(info.Name(), ".ldb") { + size += info.Size() + } + + return err + }) + if err != nil { + t.Log(err) + } + + return size +} + +func updateBlock(t *testing.T, num uint64, b *types.FullBlock) *types.FullBlock { + t.Helper() + + var addr types.Address + + b.Block.Header.Number = num + b.Block.Header.ParentHash = types.StringToHash(randStringBytes(12)) + + for i := range b.Block.Transactions { + addr = types.StringToAddress(randStringBytes(8)) + b.Block.Transactions[i].SetTo(&addr) + b.Block.Transactions[i].ComputeHash() + b.Receipts[i].TxHash = b.Block.Transactions[i].Hash() + } + + b.Block.Header.ComputeHash() + + return b +} + +func prepareBatch(t *testing.T, s storage.Storage, b *types.FullBlock) *storage.BatchWriter { + t.Helper() + + batchWriter := storage.NewBatchWriter(s) + + // Lookup 'sorted' + batchWriter.PutHeadHash(b.Block.Header.Hash) + batchWriter.PutHeadNumber(b.Block.Number()) + + for _, tx := range b.Block.Transactions { + batchWriter.PutTxLookup(tx.Hash(), b.Block.Hash()) + } + + // Main DB sorted + batchWriter.PutBody(b.Block.Hash(), b.Block.Body()) + batchWriter.PutCanonicalHash(b.Block.Number(), b.Block.Hash()) + batchWriter.PutHeader(b.Block.Header) + batchWriter.PutReceipts(b.Block.Hash(), b.Receipts) + + return batchWriter +} + +func TestWriteBlockPerf(t *testing.T) { + t.SkipNow() + + s, _, path := openStorage(t, "/tmp/leveldbV1-test") + defer s.Close() + + var watchTime int64 + + count := 10000 + b := createBlock(t) + + for i := 1; i <= count; i++ { + updateBlock(t, uint64(i), b) + batchWriter := prepareBatch(t, s, b) + + tn := time.Now().UTC() + + if err := batchWriter.WriteBatch(); err != nil { + require.NoError(t, err) + } + + d := time.Since(tn) + watchTime += d.Milliseconds() + } + + time.Sleep(time.Second) + + size := dbSize(t, path) + t.Logf("\tdb size %d MB", size/(1024*1024)) + t.Logf("\ttotal WriteBatch %d ms", watchTime) +} + +func TestReadBlockPerf(t *testing.T) { + t.SkipNow() + + s, _, _ := openStorage(t, "/tmp/leveldbV1-test") + defer s.Close() + + var watchTime int64 + + count := 1000 + for i := 1; i <= count; i++ { + n := uint64(1 + rand.Intn(10000)) + + tn := time.Now().UTC() + h, ok := s.ReadCanonicalHash(n) + _, err2 := s.ReadBody(h) + _, err3 := s.ReadHeader(h) + _, err4 := s.ReadReceipts(h) + d := time.Since(tn) + + watchTime += d.Milliseconds() + + if !ok || err2 != nil || err3 != nil || err4 != nil { + t.Logf("\terror") + } + } + t.Logf("\ttotal read %d ms", watchTime) +} diff --git a/blockchain/storagev2/leveldb/batch.go b/blockchain/storagev2/leveldb/batch.go new file mode 100644 index 0000000000..89cd121c46 --- /dev/null +++ b/blockchain/storagev2/leveldb/batch.go @@ -0,0 +1,27 @@ +package leveldb + +import ( + "github.com/syndtr/goleveldb/leveldb" +) + +type batchLevelDB struct { + db *leveldb.DB + b *leveldb.Batch +} + +func newBatchLevelDB(db *leveldb.DB) *batchLevelDB { + return &batchLevelDB{ + db: db, + b: new(leveldb.Batch), + } +} + +func (b *batchLevelDB) Put(t uint8, k []byte, v []byte) { + mc := tableMapper[t] + k = append(append(make([]byte, 0, len(k)+len(mc)), k...), mc...) + b.b.Put(k, v) +} + +func (b *batchLevelDB) Write() error { + return b.db.Write(b.b, nil) +} diff --git a/blockchain/storagev2/leveldb/leveldb.go b/blockchain/storagev2/leveldb/leveldb.go new file mode 100644 index 0000000000..73f1e05784 --- /dev/null +++ b/blockchain/storagev2/leveldb/leveldb.go @@ -0,0 +1,100 @@ +package leveldb + +import ( + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" + "github.com/hashicorp/go-hclog" + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/opt" +) + +// levelDB is the leveldb implementation of the kv storage +type levelDB struct { + db *leveldb.DB +} + +var tableMapper = map[uint8][]byte{ + // Main DB + storagev2.BODY: []byte("b"), // DB key = block number + block hash + mapper, value = block body + storagev2.DIFFICULTY: []byte("d"), // DB key = block number + block hash + mapper, value = block total diffculty + storagev2.HEADER: []byte("h"), // DB key = block number + block hash + mapper, value = block header + storagev2.RECEIPTS: []byte("r"), // DB key = block number + block hash + mapper, value = block receipts + storagev2.CANONICAL: {}, // DB key = block number + mapper, value = block hash + + // Lookup DB + storagev2.FORK: {}, // DB key = FORK_KEY + mapper, value = fork hashes + storagev2.HEAD_HASH: {}, // DB key = HEAD_HASH_KEY + mapper, value = head hash + storagev2.HEAD_NUMBER: {}, // DB key = HEAD_NUMBER_KEY + mapper, value = head number + storagev2.BLOCK_LOOKUP: {}, // DB key = block hash + mapper, value = block number + storagev2.TX_LOOKUP: {}, // DB key = tx hash + mapper, value = block number +} + +// NewLevelDBStorage creates the new storage reference with leveldb default options +func NewLevelDBStorage(path string, logger hclog.Logger) (*storagev2.Storage, error) { + var ldbs [2]storagev2.Database + + // Open LevelDB storage + // Set default options + options := &opt.Options{ + BlockCacheCapacity: 64 * opt.MiB, + WriteBuffer: 128 * opt.MiB, // Two of these are used internally + } + + maindb, err := openLevelDBStorage(path, options) + if err != nil { + return nil, err + } + + // Open Lookup + // Set default options + options = &opt.Options{ + BlockCacheCapacity: 64 * opt.MiB, + WriteBuffer: opt.DefaultWriteBuffer, + } + path += "/lookup" + + lookup, err := openLevelDBStorage(path, options) + if err != nil { + return nil, err + } + + ldbs[0] = &levelDB{maindb} + ldbs[1] = &levelDB{lookup} + + return storagev2.Open(logger.Named("leveldb"), ldbs) +} + +func openLevelDBStorage(path string, options *opt.Options) (*leveldb.DB, error) { + db, err := leveldb.OpenFile(path, options) + if err != nil { + return nil, err + } + + return db, nil +} + +// Get retrieves the key-value pair in leveldb storage +func (l *levelDB) Get(t uint8, k []byte) ([]byte, bool, error) { + mc := tableMapper[t] + k = append(k, mc...) + + data, err := l.db.Get(k, nil) + if err != nil { + if err.Error() == "leveldb: not found" { + return nil, false, nil + } + + return nil, false, err + } + + return data, true, nil +} + +// Close closes the leveldb storage instance +func (l *levelDB) Close() error { + return l.db.Close() +} + +// NewBatch creates batch for database write operations +func (l *levelDB) NewBatch() storagev2.Batch { + return newBatchLevelDB(l.db) +} diff --git a/blockchain/storagev2/leveldb/leveldb_perf_test.go b/blockchain/storagev2/leveldb/leveldb_perf_test.go new file mode 100644 index 0000000000..337075f0c6 --- /dev/null +++ b/blockchain/storagev2/leveldb/leveldb_perf_test.go @@ -0,0 +1,118 @@ +package leveldb + +import ( + "math/rand" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func openStorage(t *testing.T, p string) (*storagev2.Storage, func(), string) { + t.Helper() + + s, err := NewLevelDBStorage(p, hclog.NewNullLogger()) + require.NoError(t, err) + + closeFn := func() { + require.NoError(t, s.Close()) + + if err := s.Close(); err != nil { + t.Fatal(err) + } + + require.NoError(t, os.RemoveAll(p)) + } + + return s, closeFn, p +} + +func dbSize(t *testing.T, path string) int64 { + t.Helper() + + var size int64 + + err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error { + if err != nil { + t.Fail() + } + + if info != nil && !info.IsDir() && strings.Contains(info.Name(), ".ldb") { + size += info.Size() + } + + return err + }) + if err != nil { + t.Log(err) + } + + return size +} + +func TestWriteBlockPerf(t *testing.T) { + t.SkipNow() + + s, _, path := openStorage(t, "/tmp/leveldbV2-test") + defer s.Close() + + var watchTime int64 + + count := 10000 + b := storagev2.CreateBlock(t) + + for i := 1; i <= count; i++ { + storagev2.UpdateBlock(t, uint64(i), b) + batchWriter := storagev2.PrepareBatch(t, s, b) + + tn := time.Now().UTC() + + require.NoError(t, batchWriter.WriteBatch()) + + d := time.Since(tn) + watchTime += d.Milliseconds() + } + + time.Sleep(time.Second) + + size := dbSize(t, path) + t.Logf("\tdb size %d MB", size/(1024*1024)) + t.Logf("\ttotal WriteBatch %d ms", watchTime) +} + +func TestReadBlockPerf(t *testing.T) { + t.SkipNow() + + s, _, _ := openStorage(t, "/tmp/leveldbV2-test") + defer s.Close() + + var watchTime int64 + + count := 1000 + for i := 1; i <= count; i++ { + n := uint64(1 + rand.Intn(10000)) + + tn := time.Now().UTC() + h, ok := s.ReadCanonicalHash(n) + _, err1 := s.ReadBody(n, h) + _, err3 := s.ReadHeader(n, h) + _, err4 := s.ReadReceipts(n, h) + b, err5 := s.ReadBlockLookup(h) + d := time.Since(tn) + + watchTime += d.Milliseconds() + + if !ok || err1 != nil || err3 != nil || err4 != nil || err5 != nil { + t.Logf("\terror") + } + + assert.Equal(t, n, b) + } + t.Logf("\ttotal read %d ms", watchTime) +} diff --git a/blockchain/storagev2/leveldb/leveldb_test.go b/blockchain/storagev2/leveldb/leveldb_test.go new file mode 100644 index 0000000000..d3d4dd8c3c --- /dev/null +++ b/blockchain/storagev2/leveldb/leveldb_test.go @@ -0,0 +1,151 @@ +package leveldb + +import ( + "context" + "os" + "os/signal" + "path/filepath" + "syscall" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" +) + +func newStorage(t *testing.T) (*storagev2.Storage, func()) { + t.Helper() + + path, err := os.MkdirTemp("/tmp", "minimal_storage") + if err != nil { + t.Fatal(err) + } + + s, err := NewLevelDBStorage(path, hclog.NewNullLogger()) + if err != nil { + t.Fatal(err) + } + + closeFn := func() { + if err := s.Close(); err != nil { + t.Fatal(err) + } + + if err := os.RemoveAll(path); err != nil { + t.Fatal(err) + } + } + + return s, closeFn +} + +func newStorageP(t *testing.T) (*storagev2.Storage, func(), string) { + t.Helper() + + p, err := os.MkdirTemp("", "leveldbV2-test") + require.NoError(t, err) + + require.NoError(t, os.MkdirAll(p, 0755)) + + s, err := NewLevelDBStorage(p, hclog.NewNullLogger()) + require.NoError(t, err) + + closeFn := func() { + require.NoError(t, s.Close()) + + if err := s.Close(); err != nil { + t.Fatal(err) + } + + require.NoError(t, os.RemoveAll(p)) + } + + return s, closeFn, p +} + +func countLdbFilesInPath(path string) int { + pattern := filepath.Join(path, "*.ldb") + + files, err := filepath.Glob(pattern) + if err != nil { + return -1 + } + + return len(files) +} + +func dirSize(t *testing.T, path string) int64 { + t.Helper() + + var size int64 + + err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error { + if err != nil { + t.Fail() + } + + if !info.IsDir() { + size += info.Size() + } + + return err + }) + if err != nil { + t.Log(err) + } + + return size +} + +func TestStorage(t *testing.T) { + storagev2.TestStorage(t, newStorage) +} + +func TestWriteFullBlock(t *testing.T) { + s, _, path := newStorageP(t) + defer s.Close() + + count := 100 + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*45) + + signchan := make(chan os.Signal, 1) + signal.Notify(signchan, syscall.SIGINT) + + go func() { + <-signchan + cancel() + }() + + blockchain := make(chan *types.FullBlock, 1) + go storagev2.GenerateBlocks(t, count, blockchain, ctx) + +insertloop: + for i := 1; i <= count; i++ { + select { + case <-ctx.Done(): + break insertloop + case b := <-blockchain: + batchWriter := s.NewWriter() + + batchWriter.PutBody(b.Block.Number(), b.Block.Hash(), b.Block.Body()) + + for _, tx := range b.Block.Transactions { + batchWriter.PutTxLookup(tx.Hash(), b.Block.Number()) + } + + batchWriter.PutHeader(b.Block.Header) + batchWriter.PutHeadNumber(uint64(i)) + batchWriter.PutHeadHash(b.Block.Header.Hash) + batchWriter.PutReceipts(b.Block.Number(), b.Block.Hash(), b.Receipts) + batchWriter.PutCanonicalHash(uint64(i), b.Block.Hash()) + require.NoError(t, batchWriter.WriteBatch()) + + size := dirSize(t, path) + t.Logf("writing block %d", i) + t.Logf("\tldb file count: %d", countLdbFilesInPath(path)) + t.Logf("\tdir size %d MBs", size/1_000_000) + } + } +} diff --git a/blockchain/storagev2/mdbx/batch.go b/blockchain/storagev2/mdbx/batch.go new file mode 100644 index 0000000000..24896038f1 --- /dev/null +++ b/blockchain/storagev2/mdbx/batch.go @@ -0,0 +1,46 @@ +//nolint:errcheck +package mdbx + +import ( + "runtime" + + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" + "github.com/erigontech/mdbx-go/mdbx" +) + +type batchMdbx struct { + tx *mdbx.Txn + dbi [storagev2.MAX_TABLES]mdbx.DBI +} + +func newBatchMdbx(db *MdbxDB) *batchMdbx { + runtime.LockOSThread() + + tx, err := db.env.BeginTxn(nil, 0) + if err != nil { + return nil + } + + return &batchMdbx{ + tx: tx, + dbi: db.dbi, + } +} + +func (b *batchMdbx) Put(t uint8, k []byte, v []byte) { + if t&storagev2.LOOKUP_INDEX != 0 { + // Random write + b.tx.Put(b.dbi[t], k, v, mdbx.NoDupData) + } else { + // Sequential write + b.tx.Put(b.dbi[t], k, v, mdbx.Append) // Append at the end + } +} + +func (b *batchMdbx) Write() error { + defer runtime.UnlockOSThread() + + _, err := b.tx.Commit() + + return err +} diff --git a/blockchain/storagev2/mdbx/mdbx.go b/blockchain/storagev2/mdbx/mdbx.go new file mode 100644 index 0000000000..4bfcb633f2 --- /dev/null +++ b/blockchain/storagev2/mdbx/mdbx.go @@ -0,0 +1,190 @@ +package mdbx + +import ( + "os" + + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" + "github.com/erigontech/mdbx-go/mdbx" + "github.com/hashicorp/go-hclog" +) + +type MdbxOpts struct { + path string + flags uint +} + +// MdbxDB is the mdbx implementation of the kv storage +type MdbxDB struct { + env *mdbx.Env + dbi [storagev2.MAX_TABLES]mdbx.DBI +} + +const ( + B uint64 = 1 + KB = B << 10 + MB = KB << 10 + GB = MB << 10 + TB = GB << 10 +) + +var ( + mapSize uint64 = 2 * TB + growthSize uint64 = 2 * GB +) + +var tableMapper = map[uint8]string{ + storagev2.BODY: "Body", + storagev2.CANONICAL: "Canonical", + storagev2.DIFFICULTY: "Difficulty", + storagev2.HEADER: "Header", + storagev2.RECEIPTS: "Receipts", + storagev2.FORK: "Fork", + storagev2.HEAD_HASH: "HeadHash", + storagev2.HEAD_NUMBER: "HeadNumber", + storagev2.BLOCK_LOOKUP: "BlockLookup", + storagev2.TX_LOOKUP: "TxLookup", +} + +// NewMdbxStorage creates the new storage reference for mdbx database +func NewMdbxStorage(path string, logger hclog.Logger) (*storagev2.Storage, error) { + var dbs [2]storagev2.Database + + // Set default options + opts := &MdbxOpts{ + path: path, + } + + env, err := mdbx.NewEnv() + if err != nil { + return nil, err + } + + if err = env.SetOption(mdbx.OptMaxDB, uint64(storagev2.MAX_TABLES)); err != nil { + return nil, err + } + + if err = env.SetGeometry(-1, -1, int(mapSize), int(growthSize), -1, int(defaultPageSize())); err != nil { + return nil, err + } + + err = env.Open(opts.path, opts.flags, 0664) + if err != nil { + return nil, err + } + + db := &MdbxDB{ + env: env, + } + + if err := db.openDBI(0); err != nil { + return nil, err + } + + dbs[0] = db + dbs[1] = nil + + return storagev2.Open(logger.Named("mdbx"), dbs) +} + +func defaultPageSize() uint64 { + osPageSize := os.Getpagesize() + if osPageSize < 4096 { // reduce further may lead to errors (because some data is just big) + osPageSize = 4096 + } else if osPageSize > mdbx.MaxPageSize { + osPageSize = mdbx.MaxPageSize + } + + osPageSize = osPageSize / 4096 * 4096 // ensure it's rounded + + return uint64(osPageSize) +} + +func (db *MdbxDB) view(f func(tx *mdbx.Txn) error) (err error) { + // can't use db.env.View method - because it calls commit for read transactions - it conflicts with write transactions. + tx, err := db.env.BeginTxn(nil, mdbx.Readonly) + if err != nil { + return err + } + + return f(tx) +} + +func (db *MdbxDB) update(f func(tx *mdbx.Txn) error) (err error) { + tx, err := db.env.BeginTxn(nil, 0) + if err != nil { + return err + } + + err = f(tx) + if err != nil { + return err + } + + _, err = tx.Commit() + + return err +} + +func (db *MdbxDB) openDBI(flags uint) error { + if flags&mdbx.Accede != 0 { + return db.view(func(tx *mdbx.Txn) error { + for i, name := range tableMapper { + dbi, err := tx.OpenDBISimple(name, mdbx.Accede) + if err == nil { + db.dbi[i] = dbi + } else { + return err + } + } + + return nil + }) + } + + err := db.update(func(tx *mdbx.Txn) error { + for i, name := range tableMapper { + dbi, err := tx.OpenDBISimple(name, mdbx.Create) + if err != nil { + return err + } + + db.dbi[i] = dbi + } + + return nil + }) + + return err +} + +// Get retrieves the key-value pair in mdbx storage +func (db *MdbxDB) Get(t uint8, k []byte) ([]byte, bool, error) { + tx, err := db.env.BeginTxn(nil, mdbx.Readonly) + defer tx.Abort() + + if err != nil { + return nil, false, err + } + + data, err := tx.Get(db.dbi[t], k) + if err != nil { + if err.Error() == "key not found" { + return nil, false, nil + } + + return nil, false, err + } + + return data, true, nil +} + +// Close closes the mdbx storage instance +func (db *MdbxDB) Close() error { + db.env.Close() + + return nil +} + +func (db *MdbxDB) NewBatch() storagev2.Batch { + return newBatchMdbx(db) +} diff --git a/blockchain/storagev2/mdbx/mdbx_perf_test.go b/blockchain/storagev2/mdbx/mdbx_perf_test.go new file mode 100644 index 0000000000..a611cbdbb4 --- /dev/null +++ b/blockchain/storagev2/mdbx/mdbx_perf_test.go @@ -0,0 +1,93 @@ +package mdbx + +import ( + "math/rand" + "os" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func openStorage(t *testing.T, p string) (*storagev2.Storage, func(), string) { + t.Helper() + + s, err := NewMdbxStorage(p, hclog.NewNullLogger()) + require.NoError(t, err) + + closeFn := func() { + require.NoError(t, s.Close()) + + if err := s.Close(); err != nil { + t.Fatal(err) + } + + require.NoError(t, os.RemoveAll(p)) + } + + return s, closeFn, p +} + +func TestWriteBlockPerf(t *testing.T) { + t.SkipNow() + + s, _, path := openStorage(t, "/tmp/mdbx-test") + defer s.Close() + + var watchTime int64 + + count := 10000 + b := storagev2.CreateBlock(t) + + for i := 1; i <= count; i++ { + storagev2.UpdateBlock(t, uint64(i), b) + batchWriter := storagev2.PrepareBatch(t, s, b) + + tn := time.Now().UTC() + + require.NoError(t, batchWriter.WriteBatch()) + + d := time.Since(tn) + watchTime += d.Milliseconds() + } + + time.Sleep(time.Second) + + size := dbSize(t, path) + t.Logf("\tdb size %d MB", size/(1024*1024)) + t.Logf("\ttotal WriteBatch %d ms", watchTime) +} + +func TestReadBlockPerf(t *testing.T) { + t.SkipNow() + + s, _, _ := openStorage(t, "/tmp/mdbx-test") + defer s.Close() + + var watchTime int64 + + count := 1000 + for i := 1; i <= count; i++ { + n := uint64(1 + rand.Intn(10000)) + + tn := time.Now().UTC() + h, ok := s.ReadCanonicalHash(n) + _, err1 := s.ReadBody(n, h) + _, err3 := s.ReadHeader(n, h) + _, err4 := s.ReadReceipts(n, h) + b, err5 := s.ReadBlockLookup(h) + d := time.Since(tn) + + watchTime += d.Milliseconds() + + if !ok || err1 != nil || err3 != nil || err4 != nil || err5 != nil { + t.Logf("\terror") + } + + assert.Equal(t, n, b) + } + t.Logf("\ttotal read %d ms", watchTime) +} diff --git a/blockchain/storagev2/mdbx/mdbx_test.go b/blockchain/storagev2/mdbx/mdbx_test.go new file mode 100644 index 0000000000..e790e134fa --- /dev/null +++ b/blockchain/storagev2/mdbx/mdbx_test.go @@ -0,0 +1,138 @@ +package mdbx + +import ( + "context" + "os" + "os/signal" + "path/filepath" + "strings" + "syscall" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" +) + +func newStorage(t *testing.T) (*storagev2.Storage, func()) { + t.Helper() + + path, err := os.MkdirTemp("/tmp", "minimal_storage") + if err != nil { + t.Fatal(err) + } + + s, err := NewMdbxStorage(path, hclog.NewNullLogger()) + if err != nil { + t.Fatal(err) + } + + closeFn := func() { + if err := s.Close(); err != nil { + t.Fatal(err) + } + + if err := os.RemoveAll(path); err != nil { + t.Fatal(err) + } + } + + return s, closeFn +} + +func newStorageP(t *testing.T) (*storagev2.Storage, func(), string) { + t.Helper() + + p, err := os.MkdirTemp("", "mdbx-test") + require.NoError(t, err) + + require.NoError(t, os.MkdirAll(p, 0755)) + + s, err := NewMdbxStorage(p, hclog.NewNullLogger()) + require.NoError(t, err) + + closeFn := func() { + require.NoError(t, s.Close()) + + if err := s.Close(); err != nil { + t.Fatal(err) + } + + require.NoError(t, os.RemoveAll(p)) + } + + return s, closeFn, p +} + +func dbSize(t *testing.T, path string) int64 { + t.Helper() + + var size int64 + + err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info != nil && !info.IsDir() && strings.Contains(info.Name(), ".dat") { + size += info.Size() + } + + return nil + }) + require.NoError(t, err) + + return size +} + +func TestStorage(t *testing.T) { + storagev2.TestStorage(t, newStorage) +} + +func TestWriteFullBlock(t *testing.T) { + s, _, path := newStorageP(t) + defer s.Close() + + count := 100 + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*45) + + signchan := make(chan os.Signal, 1) + signal.Notify(signchan, syscall.SIGINT) + + go func() { + <-signchan + cancel() + }() + + blockchain := make(chan *types.FullBlock, 1) + go storagev2.GenerateBlocks(t, count, blockchain, ctx) + +insertloop: + for i := 1; i <= count; i++ { + select { + case <-ctx.Done(): + break insertloop + case b := <-blockchain: + batchWriter := s.NewWriter() + + batchWriter.PutBody(b.Block.Number(), b.Block.Hash(), b.Block.Body()) + + for _, tx := range b.Block.Transactions { + batchWriter.PutTxLookup(tx.Hash(), b.Block.Number()) + } + + batchWriter.PutHeader(b.Block.Header) + batchWriter.PutHeadNumber(uint64(i)) + batchWriter.PutHeadHash(b.Block.Header.Hash) + batchWriter.PutReceipts(b.Block.Number(), b.Block.Hash(), b.Receipts) + batchWriter.PutCanonicalHash(uint64(i), b.Block.Hash()) + require.NoError(t, batchWriter.WriteBatch()) + + size := dbSize(t, path) + t.Logf("writing block %d", i) + t.Logf("\tdir size %d MBs", size/1_000_000) + } + } +} diff --git a/blockchain/storagev2/memory/batch.go b/blockchain/storagev2/memory/batch.go new file mode 100644 index 0000000000..dd60cc668e --- /dev/null +++ b/blockchain/storagev2/memory/batch.go @@ -0,0 +1,29 @@ +package memory + +import ( + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" + "github.com/0xPolygon/polygon-edge/helper/hex" +) + +type batchMemory struct { + db []memoryKV + valuesToPut [storagev2.MAX_TABLES][][2][]byte +} + +func newBatchMemory(db []memoryKV) *batchMemory { + return &batchMemory{db: db} +} + +func (b *batchMemory) Put(t uint8, k []byte, v []byte) { + b.valuesToPut[t] = append(b.valuesToPut[t], [2][]byte{k, v}) +} + +func (b *batchMemory) Write() error { + for i, j := range b.valuesToPut { + for _, x := range j { + b.db[i].kv[hex.EncodeToHex(x[0])] = x[1] + } + } + + return nil +} diff --git a/blockchain/storagev2/memory/memory.go b/blockchain/storagev2/memory/memory.go new file mode 100644 index 0000000000..8e719d4a51 --- /dev/null +++ b/blockchain/storagev2/memory/memory.go @@ -0,0 +1,48 @@ +package memory + +import ( + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" + "github.com/0xPolygon/polygon-edge/helper/hex" +) + +type memoryKV struct { + kv map[string][]byte +} +type memoryDB struct { + db []memoryKV +} + +// NewMemoryStorage creates the new storage reference with inmemory +func NewMemoryStorage() (*storagev2.Storage, error) { + var ldbs [2]storagev2.Database + + kvs := []memoryKV{} + + for i := 0; uint8(i) < storagev2.MAX_TABLES; i++ { + kvs = append(kvs, memoryKV{kv: map[string][]byte{}}) + } + + db := &memoryDB{db: kvs} + + ldbs[0] = db + ldbs[1] = nil + + return storagev2.Open(nil, ldbs) +} + +func (m *memoryDB) Get(t uint8, k []byte) ([]byte, bool, error) { + v, ok := m.db[t].kv[hex.EncodeToHex(k)] + if !ok { + return nil, false, nil + } + + return v, true, nil +} + +func (m *memoryDB) Close() error { + return nil +} + +func (m *memoryDB) NewBatch() storagev2.Batch { + return newBatchMemory(m.db) +} diff --git a/blockchain/storagev2/memory/memory_test.go b/blockchain/storagev2/memory/memory_test.go new file mode 100644 index 0000000000..f3ef02d618 --- /dev/null +++ b/blockchain/storagev2/memory/memory_test.go @@ -0,0 +1,26 @@ +package memory + +import ( + "testing" + + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" +) + +func TestStorage(t *testing.T) { + t.Helper() + + f := func(t *testing.T) (*storagev2.Storage, func()) { + t.Helper() + + s, err := NewMemoryStorage() + + if err != nil { + t.Logf("\t Error opening MemoryDB -> %s", err.Error()) + + return nil, func() {} + } + + return s, func() {} + } + storagev2.TestStorage(t, f) +} diff --git a/blockchain/storagev2/storage.go b/blockchain/storagev2/storage.go new file mode 100644 index 0000000000..eff6aada80 --- /dev/null +++ b/blockchain/storagev2/storage.go @@ -0,0 +1,103 @@ +//nolint:stylecheck +package storagev2 + +import ( + "fmt" + + "github.com/hashicorp/go-hclog" +) + +// Database interface. +type Database interface { + Close() error + Get(t uint8, k []byte) ([]byte, bool, error) + NewBatch() Batch +} + +// Database transaction/batch interface +type Batch interface { + Write() error + Put(t uint8, k []byte, v []byte) +} + +type Storage struct { + logger hclog.Logger + db [2]Database +} + +type Writer struct { + batch [2]Batch +} + +// Tables +const ( + BODY = uint8(0) + CANONICAL = uint8(2) + DIFFICULTY = uint8(4) + HEADER = uint8(6) + RECEIPTS = uint8(8) +) + +// Lookup tables +const ( + FORK = uint8(0) | LOOKUP_INDEX + HEAD_HASH = uint8(2) | LOOKUP_INDEX + HEAD_NUMBER = uint8(4) | LOOKUP_INDEX + BLOCK_LOOKUP = uint8(6) | LOOKUP_INDEX + TX_LOOKUP = uint8(8) | LOOKUP_INDEX +) + +const MAX_TABLES = uint8(20) + +// Database indexes +const ( + MAINDB_INDEX = uint8(0) + LOOKUP_INDEX = uint8(1) +) + +var ( + FORK_KEY = []byte("0000000f") + HEAD_HASH_KEY = []byte("0000000h") + HEAD_NUMBER_KEY = []byte("0000000n") +) + +var ErrNotFound = fmt.Errorf("not found") +var ErrInvalidData = fmt.Errorf("invalid data") + +func Open(logger hclog.Logger, db [2]Database) (*Storage, error) { + return &Storage{logger: logger, db: db}, nil +} + +func (s *Storage) Close() error { + for i, db := range s.db { + if db != nil { + err := db.Close() + if err != nil { + return err + } + + s.db[i] = nil + } + } + + return nil +} + +func (s *Storage) NewWriter() *Writer { + var batch [2]Batch + batch[0] = s.db[0].NewBatch() + + if s.db[1] != nil { + batch[1] = s.db[1].NewBatch() + } + + return &Writer{batch: batch} +} + +func getIndex(t uint8) uint8 { + if t&LOOKUP_INDEX != 0 { + return LOOKUP_INDEX + } + + return MAINDB_INDEX +} diff --git a/blockchain/storagev2/storage_read.go b/blockchain/storagev2/storage_read.go new file mode 100644 index 0000000000..56103a6845 --- /dev/null +++ b/blockchain/storagev2/storage_read.go @@ -0,0 +1,182 @@ +package storagev2 + +import ( + "math/big" + + "github.com/0xPolygon/polygon-edge/helper/common" + "github.com/0xPolygon/polygon-edge/types" +) + +// -- canonical hash -- + +// ReadCanonicalHash gets the hash from the number of the canonical block +func (s *Storage) ReadCanonicalHash(n uint64) (types.Hash, bool) { + data, ok := s.get(CANONICAL, common.EncodeUint64ToBytes(n)) + if !ok { + return types.Hash{}, false + } + + return types.BytesToHash(data), true +} + +// HEAD // + +// ReadHeadHash returns the hash of the head +func (s *Storage) ReadHeadHash() (types.Hash, bool) { + data, ok := s.get(HEAD_HASH, HEAD_HASH_KEY) + if !ok { + return types.Hash{}, false + } + + return types.BytesToHash(data), true +} + +// ReadHeadNumber returns the number of the head +func (s *Storage) ReadHeadNumber() (uint64, bool) { + data, ok := s.get(HEAD_NUMBER, HEAD_NUMBER_KEY) + if !ok { + return 0, false + } + + if len(data) != 8 { + return 0, false + } + + return common.EncodeBytesToUint64(data), true +} + +// FORK // + +// ReadForks read the current forks +func (s *Storage) ReadForks() ([]types.Hash, error) { + forks := &Forks{} + err := s.readRLP(FORK, FORK_KEY, forks) + + return *forks, err +} + +// DIFFICULTY // + +// ReadTotalDifficulty reads the difficulty +func (s *Storage) ReadTotalDifficulty(bn uint64, bh types.Hash) (*big.Int, bool) { + v, ok := s.get(DIFFICULTY, getKey(bn, bh)) + if !ok { + return nil, false + } + + return big.NewInt(0).SetBytes(v), true +} + +// HEADER // + +// ReadHeader reads the header +func (s *Storage) ReadHeader(bn uint64, bh types.Hash) (*types.Header, error) { + header := &types.Header{} + err := s.readRLP(HEADER, getKey(bn, bh), header) + + return header, err +} + +// BODY // + +// ReadBody reads the body +func (s *Storage) ReadBody(bn uint64, bh types.Hash) (*types.Body, error) { + body := &types.Body{} + if err := s.readRLP(BODY, getKey(bn, bh), body); err != nil { + return nil, err + } + + // must read header because block number is needed in order to calculate each tx hash + header := &types.Header{} + if err := s.readRLP(HEADER, getKey(bn, bh), header); err != nil { + return nil, err + } + + for _, tx := range body.Transactions { + tx.ComputeHash() + } + + return body, nil +} + +// RECEIPTS // + +// ReadReceipts reads the receipts +func (s *Storage) ReadReceipts(bn uint64, bh types.Hash) ([]*types.Receipt, error) { + receipts := &types.Receipts{} + err := s.readRLP(RECEIPTS, getKey(bn, bh), receipts) + + return *receipts, err +} + +// TX LOOKUP // + +// ReadTxLookup reads the block number using the transaction hash +func (s *Storage) ReadTxLookup(hash types.Hash) (uint64, error) { + return s.readLookup(TX_LOOKUP, hash) +} + +// BLOCK LOOKUP // + +// ReadBlockLookup reads the block number using the block hash +func (s *Storage) ReadBlockLookup(hash types.Hash) (uint64, error) { + return s.readLookup(BLOCK_LOOKUP, hash) +} + +func (s *Storage) readLookup(t uint8, hash types.Hash) (uint64, error) { + data, ok := s.get(t, hash.Bytes()) + if !ok { + return 0, ErrNotFound + } + + if len(data) != 8 { + return 0, ErrInvalidData + } + + return common.EncodeBytesToUint64(data), nil +} + +func (s *Storage) readRLP(t uint8, k []byte, raw types.RLPUnmarshaler) error { + data, ok, err := s.getDB(t).Get(t, k) + + if err != nil { + return err + } + + if !ok { + return ErrNotFound + } + + if obj, ok := raw.(types.RLPStoreUnmarshaler); ok { + // decode in the store format + if err := obj.UnmarshalStoreRLP(data); err != nil { + return err + } + } else { + // normal rlp decoding + if err := raw.UnmarshalRLP(data); err != nil { + return err + } + } + + return nil +} + +func (s *Storage) get(t uint8, k []byte) ([]byte, bool) { + data, ok, err := s.getDB(t).Get(t, k) + + if err != nil { + return nil, false + } + + return data, ok +} + +func (s *Storage) getDB(t uint8) Database { + i := getIndex(t) + if s.db[i] != nil { + return s.db[i] + } + + return s.db[MAINDB_INDEX] +} diff --git a/blockchain/storagev2/storage_write.go b/blockchain/storagev2/storage_write.go new file mode 100644 index 0000000000..e76c607a91 --- /dev/null +++ b/blockchain/storagev2/storage_write.go @@ -0,0 +1,105 @@ +package storagev2 + +import ( + "math/big" + + "github.com/0xPolygon/polygon-edge/helper/common" + "github.com/0xPolygon/polygon-edge/types" +) + +func (w *Writer) PutHeader(h *types.Header) { + // block_num_u64 + hash -> header (RLP) + w.putRlp(HEADER, getKey(h.Number, h.Hash), h) +} + +func (w *Writer) PutBody(bn uint64, bh types.Hash, body *types.Body) { + // block_num_u64 + hash -> body (RLP) + w.putRlp(BODY, getKey(bn, bh), body) +} + +func (w *Writer) PutHeadHash(h types.Hash) { + w.putIntoTable(HEAD_HASH, HEAD_HASH_KEY, h.Bytes()) +} + +func (w *Writer) PutHeadNumber(bn uint64) { + w.putIntoTable(HEAD_NUMBER, HEAD_NUMBER_KEY, common.EncodeUint64ToBytes(bn)) +} + +func (w *Writer) PutTxLookup(hash types.Hash, bn uint64) { + w.putIntoTable(TX_LOOKUP, hash.Bytes(), common.EncodeUint64ToBytes(bn)) +} + +func (w *Writer) PutBlockLookup(hash types.Hash, bn uint64) { + w.putIntoTable(BLOCK_LOOKUP, hash.Bytes(), common.EncodeUint64ToBytes(bn)) +} + +func (w *Writer) PutReceipts(bn uint64, bh types.Hash, receipts []*types.Receipt) { + rs := types.Receipts(receipts) + w.putRlp(RECEIPTS, getKey(bn, bh), &rs) +} + +func (w *Writer) PutCanonicalHeader(h *types.Header, diff *big.Int) { + w.PutHeader(h) + w.PutHeadHash(h.Hash) + w.PutHeadNumber(h.Number) + w.PutBlockLookup(h.Hash, h.Number) + w.PutCanonicalHash(h.Number, h.Hash) + w.PutTotalDifficulty(h.Number, h.Hash, diff) +} + +func (w *Writer) PutCanonicalHash(bn uint64, hash types.Hash) { + w.putIntoTable(CANONICAL, common.EncodeUint64ToBytes(bn), hash.Bytes()) +} + +func (w *Writer) PutTotalDifficulty(bn uint64, bh types.Hash, diff *big.Int) { + w.putIntoTable(DIFFICULTY, getKey(bn, bh), diff.Bytes()) +} + +func (w *Writer) PutForks(forks []types.Hash) { + fs := Forks(forks) + w.putRlp(FORK, FORK_KEY, &fs) +} + +func (w *Writer) putRlp(t uint8, k []byte, raw types.RLPMarshaler) { + var data []byte + + if obj, ok := raw.(types.RLPStoreMarshaler); ok { + data = obj.MarshalStoreRLPTo(nil) + } else { + data = raw.MarshalRLPTo(nil) + } + + w.putIntoTable(t, k, data) +} + +func (w *Writer) putIntoTable(t uint8, k []byte, data []byte) { + w.getBatch(t).Put(t, k, data) +} + +func (w *Writer) WriteBatch() error { + for i, b := range w.batch { + if b != nil { + err := b.Write() + if err != nil { + return err + } + + w.batch[i] = nil + } + } + + return nil +} + +func (w *Writer) getBatch(t uint8) Batch { + i := getIndex(t) + if w.batch[i] != nil { + return w.batch[i] + } + + return w.batch[MAINDB_INDEX] +} + +func getKey(n uint64, h types.Hash) []byte { + return append(append(make([]byte, 0, 40), common.EncodeUint64ToBytes(n)...), h.Bytes()...) +} diff --git a/blockchain/storagev2/testing.go b/blockchain/storagev2/testing.go new file mode 100644 index 0000000000..d98dd0454f --- /dev/null +++ b/blockchain/storagev2/testing.go @@ -0,0 +1,535 @@ +package storagev2 + +import ( + "context" + "crypto/rand" + "math/big" + "reflect" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/helper/hex" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type PlaceholderStorage func(t *testing.T) (*Storage, func()) + +var ( + addr1 = types.StringToAddress("1") + addr2 = types.StringToAddress("2") + + hash1 = types.StringToHash("1") + hash2 = types.StringToHash("2") +) + +// TestStorage tests a set of tests on a storage +func TestStorage(t *testing.T, m PlaceholderStorage) { + t.Helper() + + t.Run("testCanonicalChain", func(t *testing.T) { + testCanonicalChain(t, m) + }) + t.Run("testDifficulty", func(t *testing.T) { + testDifficulty(t, m) + }) + t.Run("testHead", func(t *testing.T) { + testHead(t, m) + }) + t.Run("testForks", func(t *testing.T) { + testForks(t, m) + }) + t.Run("testHeader", func(t *testing.T) { + testHeader(t, m) + }) + t.Run("testBody", func(t *testing.T) { + testBody(t, m) + }) + t.Run("testWriteCanonicalHeader", func(t *testing.T) { + testWriteCanonicalHeader(t, m) + }) + t.Run("testReceipts", func(t *testing.T) { + testReceipts(t, m) + }) +} + +func testCanonicalChain(t *testing.T, m PlaceholderStorage) { + t.Helper() + + s, closeFn := m(t) + defer closeFn() + + var cases = []struct { + Number uint64 + ParentHash types.Hash + Hash types.Hash + }{ + { + Number: 1, + ParentHash: types.StringToHash("111"), + }, + { + Number: 1, + ParentHash: types.StringToHash("222"), + }, + { + Number: 2, + ParentHash: types.StringToHash("111"), + }, + } + + for _, cc := range cases { + batch := s.NewWriter() + + h := &types.Header{ + Number: cc.Number, + ParentHash: cc.ParentHash, + ExtraData: []byte{0x1}, + } + + hash := h.Hash + + batch.PutHeader(h) + batch.PutCanonicalHash(cc.Number, hash) + + require.NoError(t, batch.WriteBatch()) + + data, ok := s.ReadCanonicalHash(cc.Number) + assert.True(t, ok) + + if !reflect.DeepEqual(data, hash) { + t.Fatal("not match") + } + } +} + +func testDifficulty(t *testing.T, m PlaceholderStorage) { + t.Helper() + + s, closeFn := m(t) + defer closeFn() + + var cases = []struct { + Diff *big.Int + }{ + { + Diff: big.NewInt(10), + }, + { + Diff: big.NewInt(11), + }, + { + Diff: big.NewInt(12), + }, + } + + for indx, cc := range cases { + batch := s.NewWriter() + + h := &types.Header{ + Number: uint64(indx), + ExtraData: []byte{}, + } + h.ComputeHash() + + batch.PutHeader(h) + batch.PutBlockLookup(h.Hash, h.Number) + batch.PutTotalDifficulty(h.Number, h.Hash, cc.Diff) + + require.NoError(t, batch.WriteBatch()) + + diff, ok := s.ReadTotalDifficulty(h.Number, h.Hash) + assert.True(t, ok) + + if !reflect.DeepEqual(cc.Diff, diff) { + t.Fatal("bad") + } + } +} + +func testHead(t *testing.T, m PlaceholderStorage) { + t.Helper() + + s, closeFn := m(t) + defer closeFn() + + for i := uint64(0); i < 5; i++ { + batch := s.NewWriter() + + h := &types.Header{ + Number: i, + ExtraData: []byte{}, + } + hash := h.Hash + + batch.PutHeader(h) + batch.PutHeadNumber(i) + batch.PutHeadHash(hash) + + require.NoError(t, batch.WriteBatch()) + + n2, ok := s.ReadHeadNumber() + assert.True(t, ok) + + if n2 != i { + t.Fatal("bad") + } + + hash1, ok := s.ReadHeadHash() + assert.True(t, ok) + + if !reflect.DeepEqual(hash1, hash) { + t.Fatal("bad") + } + } +} + +func testForks(t *testing.T, m PlaceholderStorage) { + t.Helper() + + s, closeFn := m(t) + defer closeFn() + + var cases = []struct { + Forks []types.Hash + }{ + {[]types.Hash{types.StringToHash("111"), types.StringToHash("222")}}, + {[]types.Hash{types.StringToHash("111")}}, + } + + for _, cc := range cases { + batch := s.NewWriter() + + batch.PutForks(cc.Forks) + + require.NoError(t, batch.WriteBatch()) + + forks, err := s.ReadForks() + assert.NoError(t, err) + + if !reflect.DeepEqual(cc.Forks, forks) { + t.Fatal("bad") + } + } +} + +func testHeader(t *testing.T, m PlaceholderStorage) { + t.Helper() + + s, closeFn := m(t) + defer closeFn() + + header := &types.Header{ + Number: 5, + Difficulty: 17179869184, + ParentHash: types.StringToHash("11"), + Timestamp: 10, + // if not set it will fail + ExtraData: hex.MustDecodeHex("0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"), + } + header.ComputeHash() + + batch := s.NewWriter() + + batch.PutHeader(header) + + require.NoError(t, batch.WriteBatch()) + + header1, err := s.ReadHeader(header.Number, header.Hash) + assert.NoError(t, err) + + if !reflect.DeepEqual(header, header1) { + t.Fatal("bad") + } +} + +func testBody(t *testing.T, m PlaceholderStorage) { + t.Helper() + + s, closeFn := m(t) + defer closeFn() + + header := &types.Header{ + Number: 5, + Difficulty: 10, + ParentHash: types.StringToHash("11"), + Timestamp: 10, + ExtraData: []byte{}, // if not set it will fail + } + + header.ComputeHash() + + batch := s.NewWriter() + + batch.PutHeader(header) + + require.NoError(t, batch.WriteBatch()) + + addr1 := types.StringToAddress("11") + t0 := types.NewTx(types.NewLegacyTx( + types.WithNonce(0), + types.WithTo(&addr1), + types.WithValue(big.NewInt(1)), + types.WithGas(11), + types.WithGasPrice(big.NewInt(11)), + types.WithInput([]byte{1, 2}), + types.WithSignatureValues(big.NewInt(1), nil, nil), + )) + t0.ComputeHash() + + addr2 := types.StringToAddress("22") + t1 := types.NewTx(types.NewLegacyTx( + types.WithNonce(0), + types.WithTo(&addr2), + types.WithValue(big.NewInt(1)), + types.WithGas(22), + types.WithGasPrice(big.NewInt(11)), + types.WithInput([]byte{4, 5}), + types.WithSignatureValues(big.NewInt(2), nil, nil), + )) + t1.ComputeHash() + + block := types.Block{ + Header: header, + Transactions: []*types.Transaction{t0, t1}, + } + + batch2 := s.NewWriter() + body0 := block.Body() + + batch2.PutBody(header.Number, header.Hash, body0) + + require.NoError(t, batch2.WriteBatch()) + + body1, err := s.ReadBody(header.Number, header.Hash) + assert.NoError(t, err) + + // NOTE: reflect.DeepEqual does not seem to work, check the hash of the transactions + tx0, tx1 := body0.Transactions, body1.Transactions + if len(tx0) != len(tx1) { + t.Fatal("lengths are different") + } + + for indx, i := range tx0 { + if i.Hash() != tx1[indx].Hash() { + t.Fatal("tx not correct") + } + } +} + +func testReceipts(t *testing.T, m PlaceholderStorage) { + t.Helper() + + s, closeFn := m(t) + defer closeFn() + + batch := s.NewWriter() + + h := &types.Header{ + Difficulty: 133, + Number: 11, + ExtraData: []byte{}, + } + h.ComputeHash() + + body := &types.Body{ + Transactions: []*types.Transaction{ + types.NewTx(types.NewStateTx( + types.WithNonce(1000), + types.WithGas(50), + types.WithGasPrice(new(big.Int).SetUint64(100)), + types.WithSignatureValues(big.NewInt(11), nil, nil), + )), + }, + } + receipts := []*types.Receipt{ + { + Root: types.StringToHash("1"), + CumulativeGasUsed: 10, + TxHash: body.Transactions[0].Hash(), + LogsBloom: types.Bloom{0x1}, + Logs: []*types.Log{ + { + Address: addr1, + Topics: []types.Hash{hash1, hash2}, + Data: []byte{0x1, 0x2}, + }, + { + Address: addr2, + Topics: []types.Hash{hash1}, + }, + }, + }, + { + Root: types.StringToHash("1"), + CumulativeGasUsed: 10, + TxHash: body.Transactions[0].Hash(), + LogsBloom: types.Bloom{0x1}, + GasUsed: 10, + ContractAddress: &types.Address{0x1}, + Logs: []*types.Log{ + { + Address: addr2, + Topics: []types.Hash{hash1}, + }, + }, + }, + } + + batch.PutHeader(h) + batch.PutBody(h.Number, h.Hash, body) + batch.PutReceipts(h.Number, h.Hash, receipts) + + require.NoError(t, batch.WriteBatch()) + + found, err := s.ReadReceipts(h.Number, h.Hash) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(receipts, found)) +} + +func testWriteCanonicalHeader(t *testing.T, m PlaceholderStorage) { + t.Helper() + + s, closeFn := m(t) + defer closeFn() + + h := &types.Header{ + Number: 100, + ExtraData: []byte{0x1}, + } + h.ComputeHash() + + diff := new(big.Int).SetUint64(100) + batch := s.NewWriter() + + batch.PutCanonicalHeader(h, diff) + + require.NoError(t, batch.WriteBatch()) + + hh, err := s.ReadHeader(h.Number, h.Hash) + assert.NoError(t, err) + + if !reflect.DeepEqual(h, hh) { + t.Fatal("bad header") + } + + headHash, ok := s.ReadHeadHash() + assert.True(t, ok) + + if headHash != h.Hash { + t.Fatal("head hash not correct") + } + + headNum, ok := s.ReadHeadNumber() + assert.True(t, ok) + + if headNum != h.Number { + t.Fatal("head num not correct") + } + + canHash, ok := s.ReadCanonicalHash(h.Number) + assert.True(t, ok) + + if canHash != h.Hash { + t.Fatal("canonical hash not correct") + } +} + +func generateTxs(t *testing.T, startNonce, count int, from types.Address, to *types.Address) []*types.Transaction { + t.Helper() + + txs := make([]*types.Transaction, count) + + for i := range txs { + tx := types.NewTx(types.NewDynamicFeeTx( + types.WithGas(types.StateTransactionGasLimit), + types.WithNonce(uint64(startNonce+i)), + types.WithFrom(from), + types.WithTo(to), + types.WithValue(big.NewInt(2000)), + types.WithGasFeeCap(big.NewInt(100)), + types.WithGasTipCap(big.NewInt(10)), + )) + + input := make([]byte, 1000) + _, err := rand.Read(input) + + require.NoError(t, err) + + tx.ComputeHash() + + txs[i] = tx + } + + return txs +} + +func generateBlock(t *testing.T, num uint64) *types.FullBlock { + t.Helper() + + transactionsCount := 2500 + status := types.ReceiptSuccess + addr1 := types.StringToAddress("17878aa") + addr2 := types.StringToAddress("2bf5653") + b := &types.FullBlock{ + Block: &types.Block{ + Header: &types.Header{ + Number: num, + ExtraData: make([]byte, 32), + Hash: types.ZeroHash, + }, + Transactions: generateTxs(t, 0, transactionsCount, addr1, &addr2), + // Uncles: blockchain.NewTestHeaders(10), + }, + Receipts: make([]*types.Receipt, transactionsCount), + } + + logs := make([]*types.Log, 10) + + for i := 0; i < 10; i++ { + logs[i] = &types.Log{ + Address: addr1, + Topics: []types.Hash{types.StringToHash("t1"), types.StringToHash("t2"), types.StringToHash("t3")}, + Data: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xbb, 0xaa, 0x01, 0x012}, + } + } + + for i := 0; i < len(b.Block.Transactions); i++ { + b.Receipts[i] = &types.Receipt{ + TxHash: b.Block.Transactions[i].Hash(), + Root: types.StringToHash("mockhashstring"), + TransactionType: types.LegacyTxType, + GasUsed: uint64(100000), + Status: &status, + Logs: logs, + CumulativeGasUsed: uint64(100000), + ContractAddress: &types.Address{0xaa, 0xbb, 0xcc, 0xdd, 0xab, 0xac}, + } + } + + for i := 0; i < 5; i++ { + b.Receipts[i].LogsBloom = types.CreateBloom(b.Receipts) + } + + return b +} + +func GenerateBlocks(t *testing.T, count int, ch chan *types.FullBlock, ctx context.Context) { + t.Helper() + + ticker := time.NewTicker(100 * time.Millisecond) + + for i := 1; i <= count; i++ { + b := generateBlock(t, uint64(i)) + select { + case <-ctx.Done(): + close(ch) + ticker.Stop() + + return + case <-ticker.C: + ch <- b + } + } +} diff --git a/blockchain/storagev2/testing_perf.go b/blockchain/storagev2/testing_perf.go new file mode 100644 index 0000000000..9926274887 --- /dev/null +++ b/blockchain/storagev2/testing_perf.go @@ -0,0 +1,137 @@ +package storagev2 + +import ( + "crypto/rand" + "math/big" + "testing" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/require" +) + +const letterBytes = "abcdef0123456789" + +func randStringBytes(t *testing.T, n int) string { + t.Helper() + + b := make([]byte, n) + _, err := rand.Reader.Read(b) + require.NoError(t, err) + + return string(b) +} + +func createTxs(t *testing.T, startNonce, count int, from types.Address, to *types.Address) []*types.Transaction { + t.Helper() + + txs := make([]*types.Transaction, count) + + for i := range txs { + tx := types.NewTx(types.NewDynamicFeeTx( + types.WithGas(types.StateTransactionGasLimit), + types.WithNonce(uint64(startNonce+i)), + types.WithFrom(from), + types.WithTo(to), + types.WithValue(big.NewInt(2000)), + types.WithGasFeeCap(big.NewInt(100)), + types.WithGasTipCap(big.NewInt(10)), + )) + + txs[i] = tx + } + + return txs +} + +func CreateBlock(t *testing.T) *types.FullBlock { + t.Helper() + + transactionsCount := 2500 + status := types.ReceiptSuccess + addr1 := types.StringToAddress("17878aa") + addr2 := types.StringToAddress("2bf5653") + b := &types.FullBlock{ + Block: &types.Block{ + Header: &types.Header{ + Number: 0, + ExtraData: make([]byte, 32), + Hash: types.ZeroHash, + }, + Transactions: createTxs(t, 0, transactionsCount, addr1, &addr2), + // Uncles: blockchain.NewTestHeaders(10), + }, + Receipts: make([]*types.Receipt, transactionsCount), + } + + logs := make([]*types.Log, 10) + + for i := 0; i < 10; i++ { + logs[i] = &types.Log{ + Address: addr1, + Topics: []types.Hash{types.StringToHash("t1"), types.StringToHash("t2"), types.StringToHash("t3")}, + Data: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xbb, 0xaa, 0x01, 0x012}, + } + } + + for i := 0; i < len(b.Block.Transactions); i++ { + b.Receipts[i] = &types.Receipt{ + TxHash: b.Block.Transactions[i].Hash(), + Root: types.StringToHash("mockhashstring"), + TransactionType: types.LegacyTxType, + GasUsed: uint64(100000), + Status: &status, + Logs: logs, + CumulativeGasUsed: uint64(100000), + ContractAddress: &types.Address{0xaa, 0xbb, 0xcc, 0xdd, 0xab, 0xac}, + } + } + + for i := 0; i < 5; i++ { + b.Receipts[i].LogsBloom = types.CreateBloom(b.Receipts) + } + + return b +} + +func UpdateBlock(t *testing.T, num uint64, b *types.FullBlock) *types.FullBlock { + t.Helper() + + var addr types.Address + + b.Block.Header.Number = num + b.Block.Header.ParentHash = types.StringToHash(randStringBytes(t, 12)) + + for i := range b.Block.Transactions { + addr = types.StringToAddress(randStringBytes(t, 8)) + b.Block.Transactions[i].SetTo(&addr) + b.Block.Transactions[i].ComputeHash() + b.Receipts[i].TxHash = b.Block.Transactions[i].Hash() + } + + b.Block.Header.ComputeHash() + + return b +} + +func PrepareBatch(t *testing.T, s *Storage, b *types.FullBlock) *Writer { + t.Helper() + + batchWriter := s.NewWriter() + + // Lookup 'sorted' + batchWriter.PutHeadHash(b.Block.Header.Hash) + batchWriter.PutHeadNumber(b.Block.Number()) + batchWriter.PutBlockLookup(b.Block.Hash(), b.Block.Number()) + + for _, tx := range b.Block.Transactions { + batchWriter.PutTxLookup(tx.Hash(), b.Block.Number()) + } + + // Main DB sorted + batchWriter.PutBody(b.Block.Number(), b.Block.Hash(), b.Block.Body()) + batchWriter.PutCanonicalHash(b.Block.Number(), b.Block.Hash()) + batchWriter.PutHeader(b.Block.Header) + batchWriter.PutReceipts(b.Block.Number(), b.Block.Hash(), b.Receipts) + + return batchWriter +} diff --git a/blockchain/storagev2/utils.go b/blockchain/storagev2/utils.go new file mode 100644 index 0000000000..a51d48affb --- /dev/null +++ b/blockchain/storagev2/utils.go @@ -0,0 +1,54 @@ +package storagev2 + +import ( + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/fastrlp" +) + +type Forks []types.Hash + +// MarshalRLPTo is a wrapper function for calling the type marshal implementation +func (f *Forks) MarshalRLPTo(dst []byte) []byte { + return types.MarshalRLPTo(f.MarshalRLPWith, dst) +} + +// MarshalRLPWith is the actual RLP marshal implementation for the type +func (f *Forks) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { + var vr *fastrlp.Value + + if len(*f) == 0 { + vr = ar.NewNullArray() + } else { + vr = ar.NewArray() + + for _, fork := range *f { + vr.Set(ar.NewCopyBytes(fork[:])) + } + } + + return vr +} + +// UnmarshalRLP is a wrapper function for calling the type unmarshal implementation +func (f *Forks) UnmarshalRLP(input []byte) error { + return types.UnmarshalRlp(f.UnmarshalRLPFrom, input) +} + +// UnmarshalRLPFrom is the actual RLP unmarshal implementation for the type +func (f *Forks) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { + elems, err := v.GetElems() + if err != nil { + return err + } + + forks := make([]types.Hash, len(elems)) + for indx, elem := range elems { + if err := elem.GetHash(forks[indx][:]); err != nil { + return err + } + } + + *f = forks + + return nil +} diff --git a/blockchain/testing.go b/blockchain/testing.go index 94b5f4eb43..761577ed83 100644 --- a/blockchain/testing.go +++ b/blockchain/testing.go @@ -6,8 +6,8 @@ import ( "math/big" "testing" - "github.com/0xPolygon/polygon-edge/blockchain/storage" - "github.com/0xPolygon/polygon-edge/blockchain/storage/memory" + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" + "github.com/0xPolygon/polygon-edge/blockchain/storagev2/memory" "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/state" @@ -117,7 +117,7 @@ func NewTestBlockchain(t *testing.T, headers []*types.Header) *Blockchain { } if len(headers) > 0 { - batchWriter := storage.NewBatchWriter(b.db) + batchWriter := b.db.NewWriter() td := new(big.Int).SetUint64(headers[0].Difficulty) batchWriter.PutCanonicalHeader(headers[0], td) @@ -159,7 +159,7 @@ func NewMockBlockchain( Forks: chain.AllForksEnabled, }, } - mockStorage = storage.NewMockStorage() + mockStorage, _ = memory.NewMemoryStorage() ) // Set up the mocks and callbacks @@ -196,7 +196,7 @@ func NewMockBlockchain( // Execute the storage callback if storageCallback, ok := callbackMap[StorageCallback]; ok { - callback, ok := storageCallback.(func(storage *storage.MockStorage)) + callback, ok := storageCallback.(func(storage *storagev2.Storage)) if !ok { return nil, errInvalidTypeAssertion } @@ -358,7 +358,7 @@ func newBlockChain(config *chain.Chain, executor Executor) (*Blockchain, error) executor = &mockExecutor{} } - db, err := memory.NewMemoryStorage(nil) + db, err := memory.NewMemoryStorage() if err != nil { return nil, err } diff --git a/command/regenesis/test_on_history.go b/command/regenesis/test_on_history.go index 1572116c58..3dbf4c6f99 100644 --- a/command/regenesis/test_on_history.go +++ b/command/regenesis/test_on_history.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - leveldb2 "github.com/0xPolygon/polygon-edge/blockchain/storage/leveldb" + leveldb2 "github.com/0xPolygon/polygon-edge/blockchain/storagev2/leveldb" "github.com/0xPolygon/polygon-edge/command" itrie "github.com/0xPolygon/polygon-edge/state/immutable-trie" "github.com/0xPolygon/polygon-edge/types" @@ -83,7 +83,14 @@ func HistoryTestCmd() *cobra.Command { return } - header, err := st.ReadHeader(canonicalHash) + bn, err := st.ReadBlockLookup(canonicalHash) + if err != nil { + outputter.SetError(fmt.Errorf("can't read block lookup %w", err)) + + return + } + + header, err := st.ReadHeader(bn, canonicalHash) if !ok { outputter.SetError(fmt.Errorf("can't read header %w", err)) diff --git a/e2e-polybft/e2e/storage_test.go b/e2e-polybft/e2e/storage_test.go new file mode 100644 index 0000000000..8ac1daf8e8 --- /dev/null +++ b/e2e-polybft/e2e/storage_test.go @@ -0,0 +1,151 @@ +package e2e + +import ( + "math/big" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/jsonrpc" + "github.com/umbracle/ethgo/wallet" + + "github.com/0xPolygon/polygon-edge/consensus/polybft" + "github.com/0xPolygon/polygon-edge/crypto" + "github.com/0xPolygon/polygon-edge/e2e-polybft/framework" + "github.com/0xPolygon/polygon-edge/types" +) + +func TestE2E_Storage(t *testing.T) { + sender, err := crypto.GenerateECDSAKey() + require.NoError(t, err) + + cluster := framework.NewTestCluster(t, 5, + framework.WithPremine(sender.Address()), + framework.WithBurnContract(&polybft.BurnContractInfo{BlockNumber: 0, Address: types.ZeroAddress}), + ) + defer cluster.Stop() + + cluster.WaitForReady(t) + + client := cluster.Servers[0].JSONRPC().Eth() + + num := 20 + + receivers := []ethgo.Address{} + + for i := 0; i < num; i++ { + key, err := wallet.GenerateKey() + require.NoError(t, err) + + receivers = append(receivers, key.Address()) + } + + txs := []*framework.TestTxn{} + + for i := 0; i < num; i++ { + func(i int, to ethgo.Address) { + // Send every second transaction as a dynamic fees one + var txn *types.Transaction + + if i%2 == 10 { // Intentionally disable it since dynamic fee tx not working + chainID, err := client.ChainID() + require.NoError(t, err) + + txn = types.NewTx(types.NewDynamicFeeTx( + types.WithGasFeeCap(big.NewInt(1000000000)), + types.WithGasTipCap(big.NewInt(100000000)), + types.WithChainID(chainID), + )) + } else { + txn = types.NewTx(types.NewLegacyTx( + types.WithGasPrice(ethgo.Gwei(2)), + )) + } + + txn.SetFrom(sender.Address()) + txn.SetTo((*types.Address)(&to)) + txn.SetGas(21000) + txn.SetValue(big.NewInt(int64(i))) + txn.SetNonce(uint64(i)) + + tx := cluster.SendTxn(t, sender, txn) + err = tx.Wait() + require.NoError(t, err) + + txs = append(txs, tx) + }(i, receivers[i]) + } + + err = cluster.WaitUntil(2*time.Minute, 2*time.Second, func() bool { + for i, receiver := range receivers { + balance, err := client.GetBalance(receiver, ethgo.Latest) + if err != nil { + return true + } + + t.Logf("Balance %s %s", receiver, balance) + + if balance.Uint64() != uint64(i) { + return false + } + } + + return true + }) + require.NoError(t, err) + + checkStorage(t, txs, client) +} + +func checkStorage(t *testing.T, txs []*framework.TestTxn, client *jsonrpc.Eth) { + t.Helper() + + for i, tx := range txs { + bn, err := client.GetBlockByNumber(ethgo.BlockNumber(tx.Receipt().BlockNumber), true) + require.NoError(t, err) + assert.NotNil(t, bn) + + bh, err := client.GetBlockByHash(bn.Hash, true) + require.NoError(t, err) + assert.NotNil(t, bh) + + if !reflect.DeepEqual(bn, bh) { + t.Fatal("blocks dont match") + } + + bt, err := client.GetTransactionByHash(tx.Receipt().TransactionHash) + require.NoError(t, err) + assert.NotNil(t, bt) + assert.Equal(t, tx.Txn().Value(), bt.Value) + assert.Equal(t, tx.Txn().Gas(), bt.Gas) + assert.Equal(t, tx.Txn().Nonce(), bt.Nonce) + assert.Equal(t, tx.Receipt().TransactionIndex, bt.TxnIndex) + assert.NotEmpty(t, bt.V) + assert.NotEmpty(t, bt.R) + assert.NotEmpty(t, bt.S) + assert.Equal(t, tx.Txn().From().Bytes(), bt.From.Bytes()) + assert.Equal(t, tx.Txn().To().Bytes(), bt.To.Bytes()) + + if i%2 == 10 { // Intentionally disable it since dynamic fee tx not working + assert.Equal(t, ethgo.TransactionDynamicFee, bt.Type) + assert.Equal(t, uint64(0), bt.GasPrice) + assert.NotNil(t, bt.ChainID) + } else { + // assert.Equal(t, ethgo.TransactionLegacy, bt.Type) + assert.Equal(t, ethgo.Gwei(2).Uint64(), bt.GasPrice) + } + + r, err := client.GetTransactionReceipt(tx.Receipt().TransactionHash) + require.NoError(t, err) + assert.NotNil(t, r) + assert.Equal(t, bt.TxnIndex, r.TransactionIndex) + assert.Equal(t, bt.Hash, r.TransactionHash) + assert.Equal(t, bt.BlockHash, r.BlockHash) + assert.Equal(t, bt.BlockNumber, r.BlockNumber) + assert.NotEmpty(t, r.LogsBloom) + assert.Equal(t, bt.To, r.To) + } +} diff --git a/go.mod b/go.mod index 996fc46a85..00d26c991e 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/docker/docker v24.0.9+incompatible github.com/docker/go-connections v0.5.0 github.com/envoyproxy/protoc-gen-validate v1.0.4 + github.com/erigontech/mdbx-go v0.37.1 github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.1 diff --git a/go.sum b/go.sum index 37ce381b7b..7386ed2543 100644 --- a/go.sum +++ b/go.sum @@ -154,6 +154,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/erigontech/mdbx-go v0.37.1 h1:Z4gxQrsHds+TcyQYvuEeu4Tia90I9xrrO6iduSfzRXg= +github.com/erigontech/mdbx-go v0.37.1/go.mod h1:FAMxbOgqOnRDx51j8HjuJZIgznbDwjX7LItd+/UWyA4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= diff --git a/jsonrpc/debug_endpoint.go b/jsonrpc/debug_endpoint.go index 1c853b7c1f..d36038e623 100644 --- a/jsonrpc/debug_endpoint.go +++ b/jsonrpc/debug_endpoint.go @@ -34,7 +34,7 @@ type debugBlockchainStore interface { GetHeaderByNumber(uint64) (*types.Header, bool) // ReadTxLookup returns a block hash in which a given txn was mined - ReadTxLookup(txnHash types.Hash) (types.Hash, bool) + ReadTxLookup(txnHash types.Hash) (uint64, bool) // GetBlockByHash gets a block using the provided hash GetBlockByHash(hash types.Hash, full bool) (*types.Block, bool) diff --git a/jsonrpc/debug_endpoint_test.go b/jsonrpc/debug_endpoint_test.go index 35a2a3653a..5e7f8871d2 100644 --- a/jsonrpc/debug_endpoint_test.go +++ b/jsonrpc/debug_endpoint_test.go @@ -18,7 +18,7 @@ import ( type debugEndpointMockStore struct { headerFn func() *types.Header getHeaderByNumberFn func(uint64) (*types.Header, bool) - readTxLookupFn func(types.Hash) (types.Hash, bool) + readTxLookupFn func(types.Hash) (uint64, bool) getBlockByHashFn func(types.Hash, bool) (*types.Block, bool) getBlockByNumberFn func(uint64, bool) (*types.Block, bool) traceBlockFn func(*types.Block, tracer.Tracer) ([]interface{}, error) @@ -36,7 +36,7 @@ func (s *debugEndpointMockStore) GetHeaderByNumber(num uint64) (*types.Header, b return s.getHeaderByNumberFn(num) } -func (s *debugEndpointMockStore) ReadTxLookup(txnHash types.Hash) (types.Hash, bool) { +func (s *debugEndpointMockStore) ReadTxLookup(txnHash types.Hash) (uint64, bool) { return s.readTxLookupFn(txnHash) } @@ -452,13 +452,13 @@ func TestTraceTransaction(t *testing.T) { txHash: testTxHash1, config: &TraceConfig{}, store: &debugEndpointMockStore{ - readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { + readTxLookupFn: func(hash types.Hash) (uint64, bool) { assert.Equal(t, testTxHash1, hash) - return testBlock10.Hash(), true + return testBlock10.Number(), true }, - getBlockByHashFn: func(hash types.Hash, full bool) (*types.Block, bool) { - assert.Equal(t, testBlock10.Hash(), hash) + getBlockByNumberFn: func(number uint64, full bool) (*types.Block, bool) { + assert.Equal(t, testBlock10.Number(), number) assert.True(t, full) return blockWithTx, true @@ -478,10 +478,10 @@ func TestTraceTransaction(t *testing.T) { txHash: testTxHash1, config: &TraceConfig{}, store: &debugEndpointMockStore{ - readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { + readTxLookupFn: func(hash types.Hash) (uint64, bool) { assert.Equal(t, testTxHash1, hash) - return types.ZeroHash, false + return 0, false }, }, result: nil, @@ -492,13 +492,13 @@ func TestTraceTransaction(t *testing.T) { txHash: testTxHash1, config: &TraceConfig{}, store: &debugEndpointMockStore{ - readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { + readTxLookupFn: func(hash types.Hash) (uint64, bool) { assert.Equal(t, testTxHash1, hash) - return testBlock10.Hash(), true + return testBlock10.Number(), true }, - getBlockByHashFn: func(hash types.Hash, full bool) (*types.Block, bool) { - assert.Equal(t, testBlock10.Hash(), hash) + getBlockByNumberFn: func(number uint64, full bool) (*types.Block, bool) { + assert.Equal(t, testBlock10.Number(), number) assert.True(t, full) return nil, false @@ -512,13 +512,13 @@ func TestTraceTransaction(t *testing.T) { txHash: testTxHash1, config: &TraceConfig{}, store: &debugEndpointMockStore{ - readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { + readTxLookupFn: func(hash types.Hash) (uint64, bool) { assert.Equal(t, testTxHash1, hash) - return testBlock10.Hash(), true + return testBlock10.Number(), true }, - getBlockByHashFn: func(hash types.Hash, full bool) (*types.Block, bool) { - assert.Equal(t, testBlock10.Hash(), hash) + getBlockByNumberFn: func(number uint64, full bool) (*types.Block, bool) { + assert.Equal(t, testBlock10.Number(), number) assert.True(t, full) return testBlock10, true @@ -532,13 +532,13 @@ func TestTraceTransaction(t *testing.T) { txHash: testTxHash1, config: &TraceConfig{}, store: &debugEndpointMockStore{ - readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { + readTxLookupFn: func(hash types.Hash) (uint64, bool) { assert.Equal(t, testTxHash1, hash) - return testBlock10.Hash(), true + return testBlock10.Number(), true }, - getBlockByHashFn: func(hash types.Hash, full bool) (*types.Block, bool) { - assert.Equal(t, testBlock10.Hash(), hash) + getBlockByNumberFn: func(number uint64, full bool) (*types.Block, bool) { + assert.Equal(t, testBlock10.Number(), number) assert.True(t, full) return &types.Block{ diff --git a/jsonrpc/eth_blockchain_test.go b/jsonrpc/eth_blockchain_test.go index d4448af7be..61c62c7b83 100644 --- a/jsonrpc/eth_blockchain_test.go +++ b/jsonrpc/eth_blockchain_test.go @@ -657,16 +657,16 @@ func (m *mockBlockStore) Header() *types.Header { return m.blocks[len(m.blocks)-1].Header } -func (m *mockBlockStore) ReadTxLookup(txnHash types.Hash) (types.Hash, bool) { +func (m *mockBlockStore) ReadTxLookup(txnHash types.Hash) (uint64, bool) { for _, block := range m.blocks { for _, txn := range block.Transactions { if txn.Hash() == txnHash { - return block.Hash(), true + return block.Number(), true } } } - return types.ZeroHash, false + return 0, false } func (m *mockBlockStore) GetPendingTx(txHash types.Hash) (*types.Transaction, bool) { diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index 9b4119087b..8b2978aea9 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -57,7 +57,7 @@ type ethBlockchainStore interface { GetBlockByNumber(num uint64, full bool) (*types.Block, bool) // ReadTxLookup returns a block hash in which a given txn was mined - ReadTxLookup(txnHash types.Hash) (types.Hash, bool) + ReadTxLookup(txnHash types.Hash) (uint64, bool) // GetReceiptsByHash returns the receipts for a block hash GetReceiptsByHash(hash types.Hash) ([]*types.Receipt, error) @@ -261,13 +261,13 @@ func (e *Eth) GetTransactionByHash(hash types.Hash) (interface{}, error) { // for the transaction with the provided hash findSealedTx := func() *transaction { // Check the chain state for the transaction - blockHash, ok := e.store.ReadTxLookup(hash) + blockNum, ok := e.store.ReadTxLookup(hash) if !ok { // Block not found in storage return nil } - block, ok := e.store.GetBlockByHash(blockHash, true) + block, ok := e.store.GetBlockByNumber(blockNum, true) if !ok { // Block receipts not found in storage return nil @@ -320,27 +320,27 @@ func (e *Eth) GetTransactionByHash(hash types.Hash) (interface{}, error) { // GetTransactionReceipt returns a transaction receipt by his hash func (e *Eth) GetTransactionReceipt(hash types.Hash) (interface{}, error) { - blockHash, ok := e.store.ReadTxLookup(hash) + blockNum, ok := e.store.ReadTxLookup(hash) if !ok { // txn not found return nil, nil } - block, ok := e.store.GetBlockByHash(blockHash, true) + block, ok := e.store.GetBlockByNumber(blockNum, true) if !ok { // block not found e.logger.Warn( - fmt.Sprintf("Block with hash [%s] not found", blockHash.String()), + fmt.Sprintf("Block with number [%d] not found", blockNum), ) return nil, nil } - receipts, err := e.store.GetReceiptsByHash(blockHash) + receipts, err := e.store.GetReceiptsByHash(block.Hash()) if err != nil { // block receipts not found e.logger.Warn( - fmt.Sprintf("Receipts for block with hash [%s] not found", blockHash.String()), + fmt.Sprintf("Receipts for block with hash [%s] not found", block.Hash().String()), ) return nil, nil @@ -349,7 +349,7 @@ func (e *Eth) GetTransactionReceipt(hash types.Hash) (interface{}, error) { if len(receipts) == 0 { // Receipts not written yet on the db e.logger.Warn( - fmt.Sprintf("No receipts found for block with hash [%s]", blockHash.String()), + fmt.Sprintf("No receipts found for block with hash [%s]", block.Hash().String()), ) return nil, nil diff --git a/jsonrpc/helper.go b/jsonrpc/helper.go index becff911b4..2b67fb43a3 100644 --- a/jsonrpc/helper.go +++ b/jsonrpc/helper.go @@ -93,18 +93,18 @@ func GetBlockHeader(number BlockNumber, store headerGetter) (*types.Header, erro } type txLookupAndBlockGetter interface { - ReadTxLookup(types.Hash) (types.Hash, bool) - GetBlockByHash(types.Hash, bool) (*types.Block, bool) + ReadTxLookup(types.Hash) (uint64, bool) + GetBlockByNumber(uint64, bool) (*types.Block, bool) } // GetTxAndBlockByTxHash returns the tx and the block including the tx by given tx hash func GetTxAndBlockByTxHash(txHash types.Hash, store txLookupAndBlockGetter) (*types.Transaction, *types.Block) { - blockHash, ok := store.ReadTxLookup(txHash) + blockNum, ok := store.ReadTxLookup(txHash) if !ok { return nil, nil } - block, ok := store.GetBlockByHash(blockHash, true) + block, ok := store.GetBlockByNumber(blockNum, true) if !ok { return nil, nil } diff --git a/jsonrpc/helper_test.go b/jsonrpc/helper_test.go index 19f521d6c7..c36bd6cd03 100644 --- a/jsonrpc/helper_test.go +++ b/jsonrpc/helper_test.go @@ -333,13 +333,13 @@ func TestGetTxAndBlockByTxHash(t *testing.T) { name: "should return tx and block", txHash: testTx1.Hash(), store: &debugEndpointMockStore{ - readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { + readTxLookupFn: func(hash types.Hash) (uint64, bool) { assert.Equal(t, testTx1.Hash(), hash) - return blockWithTx.Hash(), true + return blockWithTx.Number(), true }, - getBlockByHashFn: func(hash types.Hash, full bool) (*types.Block, bool) { - assert.Equal(t, blockWithTx.Hash(), hash) + getBlockByNumberFn: func(number uint64, full bool) (*types.Block, bool) { + assert.Equal(t, blockWithTx.Number(), number) assert.True(t, full) return blockWithTx, true @@ -352,26 +352,26 @@ func TestGetTxAndBlockByTxHash(t *testing.T) { name: "should return nil if ReadTxLookup returns nothing", txHash: testTx1.Hash(), store: &debugEndpointMockStore{ - readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { + readTxLookupFn: func(hash types.Hash) (uint64, bool) { assert.Equal(t, testTx1.Hash(), hash) - return types.ZeroHash, false + return 0, false }, }, tx: nil, block: nil, }, { - name: "should return nil if GetBlockByHash returns nothing", + name: "should return nil if GetBlockByNumber returns nothing", txHash: testTx1.Hash(), store: &debugEndpointMockStore{ - readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { + readTxLookupFn: func(hash types.Hash) (uint64, bool) { assert.Equal(t, testTx1.Hash(), hash) - return blockWithTx.Hash(), true + return blockWithTx.Number(), true }, - getBlockByHashFn: func(hash types.Hash, full bool) (*types.Block, bool) { - assert.Equal(t, blockWithTx.Hash(), hash) + getBlockByNumberFn: func(number uint64, full bool) (*types.Block, bool) { + assert.Equal(t, blockWithTx.Number(), number) assert.True(t, full) return nil, false @@ -384,13 +384,13 @@ func TestGetTxAndBlockByTxHash(t *testing.T) { name: "should return nil if the block doesn't include the tx", txHash: testTx1.Hash(), store: &debugEndpointMockStore{ - readTxLookupFn: func(hash types.Hash) (types.Hash, bool) { + readTxLookupFn: func(hash types.Hash) (uint64, bool) { assert.Equal(t, testTx1.Hash(), hash) - return blockWithTx.Hash(), true + return blockWithTx.Number(), true }, - getBlockByHashFn: func(hash types.Hash, full bool) (*types.Block, bool) { - assert.Equal(t, blockWithTx.Hash(), hash) + getBlockByNumberFn: func(number uint64, full bool) (*types.Block, bool) { + assert.Equal(t, blockWithTx.Number(), number) assert.True(t, full) return testBlock10, true diff --git a/server/server.go b/server/server.go index 94544447ff..1c42503401 100644 --- a/server/server.go +++ b/server/server.go @@ -12,9 +12,9 @@ import ( "path/filepath" "time" - "github.com/0xPolygon/polygon-edge/blockchain/storage" - "github.com/0xPolygon/polygon-edge/blockchain/storage/leveldb" - "github.com/0xPolygon/polygon-edge/blockchain/storage/memory" + "github.com/0xPolygon/polygon-edge/blockchain/storagev2" + "github.com/0xPolygon/polygon-edge/blockchain/storagev2/leveldb" + "github.com/0xPolygon/polygon-edge/blockchain/storagev2/memory" consensusPolyBFT "github.com/0xPolygon/polygon-edge/consensus/polybft" "github.com/0xPolygon/polygon-edge/forkmanager" "github.com/0xPolygon/polygon-edge/gasprice" @@ -168,7 +168,7 @@ func NewServer(config *Config) (*Server, error) { } // Set up datadog profiler - if ddErr := m.enableDataDogProfiler(); err != nil { + if ddErr := m.enableDataDogProfiler(); ddErr != nil { m.logger.Error("DataDog profiler setup failed", "err", ddErr.Error()) } @@ -286,10 +286,10 @@ func NewServer(config *Config) (*Server, error) { signer := crypto.NewSigner(config.Chain.Params.Forks.At(0), uint64(m.config.Chain.Params.ChainID)) // create storage instance for blockchain - var db storage.Storage + var db *storagev2.Storage { if m.config.DataDir == "" { - db, err = memory.NewMemoryStorage(nil) + db, err = memory.NewMemoryStorage() if err != nil { return nil, err }