Skip to content
This repository has been archived by the owner on Jun 9, 2024. It is now read-only.

refactor(blockchain): Minor cleanup and add safety checks. #1266

Merged
merged 16 commits into from
Nov 1, 2023
4 changes: 3 additions & 1 deletion cosmos/x/evm/keeper/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ func (k *Keeper) Precommit(ctx context.Context) error {
block := k.chain.GetBlockByNumber(blockNum)
if block == nil {
panic(
fmt.Sprintf("EVM BLOCK FAILURE AT BLOCK %d", blockNum),
fmt.Sprintf(
"EVM BLOCK %d FAILED TO PROCESS - hash: %s", blockNum, block.Hash(),
),
)
} else if block.NumberU64() != blockNum {
panic(
Expand Down
5 changes: 2 additions & 3 deletions cosmos/x/evm/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,8 @@ func (k *Keeper) InitGenesis(ctx sdk.Context, genState *core.Genesis) error {
}

// Insert to chain.
k.chain.
PreparePlugins(ctx.WithEventManager(sdk.NewEventManager()))
return k.chain.InsertBlockWithoutSetHead(genState.ToBlock())
k.chain.PreparePlugins(ctx.WithEventManager(sdk.NewEventManager()))
return k.chain.InsertGenesisBlock(genState.ToBlock())
}

// ExportGenesis returns the exported genesis state.
Expand Down
7 changes: 4 additions & 3 deletions cosmos/x/evm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,17 @@ import (
"pkg.berachain.dev/polaris/cosmos/config"
"pkg.berachain.dev/polaris/cosmos/x/evm/plugins/state"
"pkg.berachain.dev/polaris/cosmos/x/evm/types"
"pkg.berachain.dev/polaris/eth/core"
ethprecompile "pkg.berachain.dev/polaris/eth/core/precompile"
coretypes "pkg.berachain.dev/polaris/eth/core/types"
"pkg.berachain.dev/polaris/eth/params"
)

type Blockchain interface {
PreparePlugins(context.Context)
Config() *params.ChainConfig
core.ChainWriter
core.ChainReader
InsertGenesisBlock(*coretypes.Block) error
InsertBlockWithoutSetHead(*coretypes.Block) error
GetBlockByNumber(uint64) *coretypes.Block
}

type Keeper struct {
Expand Down
19 changes: 9 additions & 10 deletions eth/core/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type blockchain struct {

engine consensus.Engine
processor core.Processor
validator core.Validator

// statedb is the state database that is used to mange state during transactions.
statedb state.StateDB
Expand All @@ -81,8 +82,6 @@ type blockchain struct {
finalizedBlock atomic.Pointer[types.Block]
// currentReceipts is the current/pending receipts.
currentReceipts atomic.Value
// currentLogs is the current/pending logs.
currentLogs atomic.Value

// receiptsCache is a cache of the receipts for the last `defaultCacheSizeBytes` bytes of
// blocks. blockHash -> receipts
Expand All @@ -98,14 +97,13 @@ type blockchain struct {
txLookupCache *lru.Cache[common.Hash, *types.TxLookupEntry]

// subscription event feeds
scope event.SubscriptionScope
chainFeed event.Feed
chainHeadFeed event.Feed
logsFeed event.Feed
pendingLogsFeed event.Feed
rmLogsFeed event.Feed // currently never used
chainSideFeed event.Feed // currently never used
logger log.Logger
scope event.SubscriptionScope
chainFeed event.Feed
chainHeadFeed event.Feed
logsFeed event.Feed
rmLogsFeed event.Feed // currently never used
chainSideFeed event.Feed // currently never used
logger log.Logger
}

// =========================================================================
Expand Down Expand Up @@ -134,6 +132,7 @@ func NewChain(
}
bc.statedb = state.NewStateDB(bc.sp, bc.pp)
bc.processor = core.NewStateProcessor(bc.config, bc, bc.engine)
bc.validator = core.NewBlockValidator(bc.config, bc, bc.engine)
// TODO: hmm...
bc.currentBlock.Store(
types.NewBlock(&types.Header{Time: 0, Number: big.NewInt(0),
itsdevbear marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
1 change: 0 additions & 1 deletion eth/core/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ func (bc *blockchain) CurrentFinalBlock() *types.Header {
// CurrentSafeBlock retrieves the current safe block of the canonical
// chain. The block is retrieved from the blockchain's internal cache.
func (bc *blockchain) CurrentSafeBlock() *types.Header {
// TODO: determine the difference between safe and final in polaris.
return bc.CurrentFinalBlock()
}

Expand Down
21 changes: 21 additions & 0 deletions eth/core/chain_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,27 @@ func (bc *blockchain) StateAtBlockNumber(number uint64) (state.StateDB, error) {
return state.NewStateDB(sp, bc.pp), nil
}

// HasBlockAndState checks if the blockchain has a block and its state at
// a given hash and number.
func (bc *blockchain) HasBlockAndState(hash common.Hash, number uint64) bool {
// Check for State.
if sdb, err := bc.StateAt(hash); sdb == nil || err == nil {
sdb, err = bc.StateAtBlockNumber(number)
if sdb == nil || err != nil {
return false
}
}

// Check for Block.
if block := bc.GetBlockByNumber(number); block == nil {
block = bc.GetBlockByHash(hash)
if block == nil {
return false
}
}
return true
}
itsdevbear marked this conversation as resolved.
Show resolved Hide resolved

// GetVMConfig returns the vm.Config for the current chain.
func (bc *blockchain) GetVMConfig() *vm.Config {
return bc.vmConfig
Expand Down
201 changes: 121 additions & 80 deletions eth/core/chain_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,83 +22,160 @@ package core

import (
"context"
"time"
"errors"

"github.com/ethereum/go-ethereum/core"

"pkg.berachain.dev/polaris/eth/core/state"
"pkg.berachain.dev/polaris/eth/core/types"
"pkg.berachain.dev/polaris/eth/log"
)

// ChainWriter defines methods that are used to perform state and block transitions.
type ChainWriter interface {
LoadLastState(context.Context, uint64) error
InsertBlock(block *types.Block, receipts types.Receipts, logs []*types.Log) error
InsertGenesisBlock(block *types.Block) error
InsertBlockWithoutSetHead(block *types.Block) error
WriteBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log,
state state.StateDB, emitHeadEvent bool) (status core.WriteStatus, err error)
}

// WriteBlockAndSetHead is a no-op in the current implementation. Potentially usable later.
func (*blockchain) WriteBlockAndSetHead(
_ *types.Block, _ []*types.Receipt, _ []*types.Log, _ state.StateDB,
_ bool) (core.WriteStatus, error) {
return core.NonStatTy, nil
// InsertGenesisBlock inserts the genesis block into the blockchain.
func (bc *blockchain) InsertGenesisBlock(block *types.Block) error {
// TODO: add more validation here.
if block.NumberU64() != 0 {
return errors.New("not the genesis block")
}
_, err := bc.WriteBlockAndSetHead(block, nil, nil, nil, true)
return err
}

// InsertBlockWithoutSetHead inserts a block into the blockchain without setting the head.
// For now, it is a huge lie. It does infact set the head.
func (bc *blockchain) InsertBlockWithoutSetHead(block *types.Block) error {
// Retrieve the parent block and it's state to execute on top
// parent := bc.GetBlock(block.ParentHash(), block.NumberU64()-1)
// if parent == nil {
// return fmt.Errorf("parent block not found")
// }

// Process block using the parent state as reference point
pstart := time.Now()
receipts, logs, _, err := bc.processor.Process(block, bc.statedb, *bc.vmConfig)
// Validate that we are about to insert a valid block.
if err := bc.validator.ValidateBody(block); err != nil {
log.Error("invalid block body", "err", err)
return err
}

// Process the incoming EVM block.
receipts, logs, usedGas, err := bc.processor.Process(block, bc.statedb, *bc.vmConfig)
if err != nil {
log.Error("failed to process block", "num", block.NumberU64())
return err
}
ptime := time.Since(pstart)
bc.logger.Info("processed block in", "time", ptime)
return bc.InsertBlock(block, receipts, logs)
}

// InsertBlock inserts a block into the canonical chain and updates the state of the blockchain.
func (bc *blockchain) InsertBlock(
block *types.Block,
receipts types.Receipts,
logs []*types.Log,
) error {
var err error
if _, err = bc.statedb.Commit(
block.NumberU64(),
bc.config.IsEIP158(block.Header().Number),
); err != nil {
// ValidateState validates the statedb post block processing.
if err = bc.validator.ValidateState(block, bc.statedb, receipts, usedGas); err != nil {
log.Error("invalid state after processing block", "num", block.NumberU64())
return err
}

// TODO: prepare historical plugin here?
// TBH still think we should deprecate it and run in another routine as indexer.
// We can just immediately finalize the block. It's okay in this context.
if _, err = bc.WriteBlockAndSetHead(
block, receipts, logs, nil, true); err != nil {
log.Error("failed to write block", "num", block.NumberU64())
return err
}

// ***************************************** //
// TODO: add safety check for canonicallness //
// ***************************************** //
return err
}

// *********************************************** //
// TODO: restructure this function / flow it sucks //
// *********************************************** //
blockHash, blockNum := block.Hash(), block.Number().Uint64()
bc.logger.Info(
"finalizing evm block", "hash", blockHash.Hex(), "num_txs", len(receipts))
// SetHeadAndFinalize sets the head of the blockchain to the given block and finalizes the block.
itsdevbear marked this conversation as resolved.
Show resolved Hide resolved
func (bc *blockchain) WriteBlockAndSetHead(
block *types.Block, receipts []*types.Receipt, logs []*types.Log,
_ state.StateDB, emitHeadEvent bool,
) (core.WriteStatus, error) {
// Write the block to the store.
if err := bc.writeBlockWithState(block, receipts); err != nil {
return core.NonStatTy, err
}
currentBlock := bc.currentBlock.Load()

// We need to error if the parent is not the head block.
if block.NumberU64() > 0 && block.ParentHash() != currentBlock.Hash() {
log.Error("canonical chain broken",
"block-number", block.NumberU64(), "block-hash", block.ParentHash().Hex())
return core.NonStatTy, errors.New("canonical chain broken")
}

// Set the current block.
bc.currentBlock.Store(block)

// TODO: this is fine to do here but not really semantically correct
// and is very confusing.
// For clarity reasons, we should make the cosmos chain make a separate call
// to finalize the block.
bc.finalizedBlock.Store(block)

// Write the receipts cache.
// TODO deprecate this cache?
if receipts != nil {
bc.currentReceipts.Store(receipts)
bc.receiptsCache.Add(block.Hash(), receipts)
}

// Fire off the feeds.
bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
if len(logs) > 0 {
bc.logsFeed.Send(logs)
}

// In theory, we should fire a ChainHeadEvent when we inject
// a canonical block, but sometimes we can insert a batch of
// canonical blocks. Avoid firing too many ChainHeadEvents,
// we will fire an accumulated ChainHeadEvent and disable fire
// event here.
if emitHeadEvent {
bc.chainHeadFeed.Send(ChainHeadEvent{Block: block})
}

return core.CanonStatTy, nil
}
itsdevbear marked this conversation as resolved.
Show resolved Hide resolved

// store the block header on the host chain
err = bc.bp.StoreHeader(block.Header())
// writeBlockWithState writes the block along with its state (receipts and logs)
// into the blockchain.
func (bc *blockchain) writeBlockWithState(
block *types.Block, receipts []*types.Receipt,
) error {
// In Polaris since we are using single block finality.
// Finalized == Current == Safe. All are the same.
// Store the header as well as update all the finalized stuff.
err := bc.bp.StoreHeader(block.Header())
if err != nil {
bc.logger.Error("failed to store block header", "err", err)
return err
}

// Irrelevant of the canonical status, write the block itself to the database.
// TODO THIS NEEDS TO WRITE TO EXTERNAL DB.
if err = bc.writeHistoricalData(block, receipts); err != nil {
return err
}

// Commit all cached state changes into underlying memory database.
// In Polaris this is a no-op.
_, err = bc.statedb.Commit(block.NumberU64(), bc.config.IsEIP158(block.Number()))
if err != nil {
return err
}

bc.logger.Info(
"finalizing evm block", "hash", block.Hash().Hex(), "num_txs", len(receipts))

return nil
}

// InsertBlock inserts a block into the canonical chain and updates the state of the blockchain.
// TODO: WRITE TO EXTERNAL STORE
func (bc *blockchain) writeHistoricalData(
block *types.Block,
receipts types.Receipts,
) error {
var err error
blockHash, blockNum := block.Hash(), block.Number().Uint64()

// store the block, receipts, and txs on the host chain if historical plugin is supported
if bc.hp != nil {
if err = bc.hp.StoreBlock(block); err != nil {
Expand All @@ -115,41 +192,5 @@ func (bc *blockchain) InsertBlock(
}
}

// mark the current block, receipts, and logs
if block != nil {
bc.currentBlock.Store(block)
bc.finalizedBlock.Store(block)

bc.blockNumCache.Add(blockNum, block)
bc.blockHashCache.Add(blockHash, block)

for txIndex, tx := range block.Transactions() {
bc.txLookupCache.Add(
tx.Hash(),
&types.TxLookupEntry{
Tx: tx,
TxIndex: uint64(txIndex),
BlockNum: blockNum,
BlockHash: blockHash,
},
)
}
}
if receipts != nil {
bc.currentReceipts.Store(receipts)
bc.receiptsCache.Add(blockHash, receipts)
}
if logs != nil {
bc.pendingLogsFeed.Send(logs)
bc.currentLogs.Store(logs)
if len(logs) > 0 {
bc.logsFeed.Send(logs)
}
}

// Send chain events.
bc.chainFeed.Send(ChainEvent{Block: block, Hash: blockHash, Logs: logs})
bc.chainHeadFeed.Send(ChainHeadEvent{Block: block})

return nil
}
1 change: 0 additions & 1 deletion eth/polar/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,6 @@ func (b *backend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) eve
}

func (b *backend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
b.logger.Debug("called eth.rpc.backend.SubscribeLogsEvent", "ch", ch)
return b.polar.blockchain.SubscribeLogsEvent(ch)
}

Expand Down
Loading