-
Notifications
You must be signed in to change notification settings - Fork 209
Cache EVM block proposal and defer storage writes to commit #8491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 6 commits
4312d4d
5b7f841
edf84f5
5048baf
3896575
cd645a8
a2e6bc9
9dff1a3
3768a7e
9d95174
d92c088
98cdea4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,4 +1,4 @@ | ||||||
| package handler | ||||||
| package environment | ||||||
|
|
||||||
| import ( | ||||||
| "fmt" | ||||||
|
|
@@ -10,6 +10,25 @@ | |||||
| "github.com/onflow/flow-go/model/flow" | ||||||
| ) | ||||||
|
|
||||||
| // BlockStore stores the chain of blocks | ||||||
| type EVMBlockStore interface { | ||||||
| // LatestBlock returns the latest appended block | ||||||
| LatestBlock() (*types.Block, error) | ||||||
|
|
||||||
| // BlockHash returns the hash of the block at the given height | ||||||
| BlockHash(height uint64) (gethCommon.Hash, error) | ||||||
|
|
||||||
| // BlockProposal returns the active block proposal | ||||||
| BlockProposal() (*types.BlockProposal, error) | ||||||
|
|
||||||
| // StageBlockProposal updates the in-memory block proposal without writing to | ||||||
| // storage. Persistence happens at transaction end via FlushBlockProposal. | ||||||
| StageBlockProposal(*types.BlockProposal) | ||||||
|
|
||||||
| // CommitBlockProposal commits the block proposal and update the chain of blocks | ||||||
| CommitBlockProposal(*types.BlockProposal) error | ||||||
| } | ||||||
|
|
||||||
| const ( | ||||||
| BlockHashListCapacity = 256 | ||||||
| BlockStoreLatestBlockKey = "LatestBlock" | ||||||
|
|
@@ -18,55 +37,66 @@ | |||||
|
|
||||||
| type BlockStore struct { | ||||||
| chainID flow.ChainID | ||||||
| backend types.Backend | ||||||
| storage ValueStore | ||||||
| blockInfo BlockInfo | ||||||
| randGen RandomGenerator | ||||||
| rootAddress flow.Address | ||||||
| cached *types.BlockProposal | ||||||
| } | ||||||
|
|
||||||
| var _ types.BlockStore = &BlockStore{} | ||||||
| var _ EVMBlockStore = &BlockStore{} | ||||||
|
|
||||||
| // NewBlockStore constructs a new block store | ||||||
| func NewBlockStore( | ||||||
| chainID flow.ChainID, | ||||||
| backend types.Backend, | ||||||
| storage ValueStore, | ||||||
| blockInfo BlockInfo, | ||||||
| randGen RandomGenerator, | ||||||
| rootAddress flow.Address, | ||||||
| ) *BlockStore { | ||||||
| return &BlockStore{ | ||||||
| chainID: chainID, | ||||||
| backend: backend, | ||||||
| storage: storage, | ||||||
| blockInfo: blockInfo, | ||||||
| randGen: randGen, | ||||||
| rootAddress: rootAddress, | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // BlockProposal returns the block proposal to be updated by the handler | ||||||
| func (bs *BlockStore) BlockProposal() (*types.BlockProposal, error) { | ||||||
| if bs.cached != nil { | ||||||
| return bs.cached, nil | ||||||
| } | ||||||
| // first fetch it from the storage | ||||||
| data, err := bs.backend.GetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockProposalKey)) | ||||||
| data, err := bs.storage.GetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockProposalKey)) | ||||||
| if err != nil { | ||||||
| return nil, err | ||||||
| } | ||||||
| if len(data) != 0 { | ||||||
| return types.NewBlockProposalFromBytes(data) | ||||||
| bp, err := types.NewBlockProposalFromBytes(data) | ||||||
| if err != nil { | ||||||
| return nil, err | ||||||
| } | ||||||
| bs.cached = bp | ||||||
| return bp, nil | ||||||
| } | ||||||
| bp, err := bs.constructBlockProposal() | ||||||
| if err != nil { | ||||||
| return nil, err | ||||||
| } | ||||||
| // store block proposal | ||||||
| err = bs.UpdateBlockProposal(bp) | ||||||
| if err != nil { | ||||||
| return nil, err | ||||||
| } | ||||||
| bs.cached = bp | ||||||
| return bp, nil | ||||||
| } | ||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||
|
|
||||||
| func (bs *BlockStore) constructBlockProposal() (*types.BlockProposal, error) { | ||||||
| // if available construct a new one | ||||||
| cadenceHeight, err := bs.backend.GetCurrentBlockHeight() | ||||||
| cadenceHeight, err := bs.blockInfo.GetCurrentBlockHeight() | ||||||
| if err != nil { | ||||||
| return nil, err | ||||||
| } | ||||||
|
|
||||||
| cadenceBlock, found, err := bs.backend.GetBlockAtHeight(cadenceHeight) | ||||||
| cadenceBlock, found, err := bs.blockInfo.GetBlockAtHeight(cadenceHeight) | ||||||
| if err != nil { | ||||||
| return nil, err | ||||||
| } | ||||||
|
|
@@ -90,7 +120,7 @@ | |||||
|
|
||||||
| // read a random value for block proposal | ||||||
| prevrandao := gethCommon.Hash{} | ||||||
| err = bs.backend.ReadRandom(prevrandao[:]) | ||||||
| err = bs.randGen.ReadRandom(prevrandao[:]) | ||||||
| if err != nil { | ||||||
| return nil, err | ||||||
| } | ||||||
|
|
@@ -107,13 +137,13 @@ | |||||
| } | ||||||
|
|
||||||
| // UpdateBlockProposal updates the block proposal | ||||||
| func (bs *BlockStore) UpdateBlockProposal(bp *types.BlockProposal) error { | ||||||
| func (bs *BlockStore) updateBlockProposal(bp *types.BlockProposal) error { | ||||||
| blockProposalBytes, err := bp.ToBytes() | ||||||
| if err != nil { | ||||||
| return types.NewFatalError(err) | ||||||
| } | ||||||
|
|
||||||
| return bs.backend.SetValue( | ||||||
| return bs.storage.SetValue( | ||||||
| bs.rootAddress[:], | ||||||
| []byte(BlockStoreLatestBlockProposalKey), | ||||||
| blockProposalBytes, | ||||||
|
|
@@ -129,7 +159,7 @@ | |||||
| return types.NewFatalError(err) | ||||||
| } | ||||||
|
|
||||||
| err = bs.backend.SetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockKey), blockBytes) | ||||||
| err = bs.storage.SetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockKey), blockBytes) | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
|
|
@@ -153,17 +183,17 @@ | |||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
| err = bs.UpdateBlockProposal(newBP) | ||||||
| err = bs.updateBlockProposal(newBP) | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
|
|
||||||
| bs.cached = newBP | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can clear the cache after the block proposal is committed, because it won't be used any more.
Suggested change
The CommitBlockProposal is used at the system tx, which is the last tx in a flow block, so there is no more tx to read this cache. There are two keys in storage:
The relationship is that IMO, the CommitBlockProposal could just remove the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made a PR for this change and added some test cases |
||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| // LatestBlock returns the latest executed block | ||||||
| func (bs *BlockStore) LatestBlock() (*types.Block, error) { | ||||||
| data, err := bs.backend.GetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockKey)) | ||||||
| data, err := bs.storage.GetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockKey)) | ||||||
| if err != nil { | ||||||
| return nil, err | ||||||
| } | ||||||
|
|
@@ -184,7 +214,7 @@ | |||||
| } | ||||||
|
|
||||||
| func (bs *BlockStore) getBlockHashList() (*BlockHashList, error) { | ||||||
| bhl, err := NewBlockHashList(bs.backend, bs.rootAddress, BlockHashListCapacity) | ||||||
| bhl, err := NewBlockHashList(bs.storage, bs.rootAddress, BlockHashListCapacity) | ||||||
| if err != nil { | ||||||
| return nil, err | ||||||
| } | ||||||
|
|
@@ -201,3 +231,23 @@ | |||||
|
|
||||||
| return bhl, nil | ||||||
| } | ||||||
|
|
||||||
| func (bs *BlockStore) ResetBlockProposal() { | ||||||
| bs.cached = nil | ||||||
| } | ||||||
|
|
||||||
| func (bs *BlockStore) StageBlockProposal(bp *types.BlockProposal) { | ||||||
| bs.cached = bp | ||||||
| } | ||||||
|
|
||||||
| func (bs *BlockStore) FlushBlockProposal() error { | ||||||
| if bs.cached == nil { | ||||||
| return nil | ||||||
| } | ||||||
| err := bs.updateBlockProposal(bs.cached) | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BlockHashListstill is not cached per transaction.BlockStoreonly keepscached *types.BlockProposal;getBlockHashList()reconstructs the hash list from storage on everyBlockHash()/CommitBlockProposal()path. RepeatedBLOCKHASHaccess inside one Cadence transaction will therefore stay on the metered storage path, which leaves part of the targeted optimization on the table.Possible direction
type BlockStore struct { chainID flow.ChainID storage ValueStore blockInfo BlockInfo randGen RandomGenerator rootAddress flow.Address cached *types.BlockProposal + cachedBlockHashList *BlockHashList } @@ func (bs *BlockStore) getBlockHashList() (*BlockHashList, error) { + if bs.cachedBlockHashList != nil { + return bs.cachedBlockHashList, nil + } + bhl, err := NewBlockHashList(bs.storage, bs.rootAddress, BlockHashListCapacity) if err != nil { return nil, err } @@ if bhl.IsEmpty() { err = bhl.Push( types.GenesisBlock(bs.chainID).Height, types.GenesisBlockHash(bs.chainID), ) if err != nil { return nil, err } } + bs.cachedBlockHashList = bhl return bhl, nil }Also applies to: 224-240
🤖 Prompt for AI Agents