-
Notifications
You must be signed in to change notification settings - Fork 279
Refactor pending blocks handling #1568
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
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
8ec09c1
test: Add test for pending blocks and refactor code
tzdybal 054a1c0
wip: reimplement PendingBlocks using store and 'high-water-mark'
tzdybal d634794
feat: add metadata handling to store interface
tzdybal d973790
feat: load lastBlockHeight from store
tzdybal bded621
refactor: remove addPendingBlock function and update tests
tzdybal 632935b
feat: save lastSubmittedHeight into store
tzdybal e8eb37b
refactor: rename const and improve tests
tzdybal d567f77
refactor: updated getPendingBlocks to accept context
tzdybal 222d89c
refactor: add logger to PendingBlocks
tzdybal 1d93481
fix: add error logging when it's not possible to store latest submitt…
tzdybal 8530658
refactor: change signature of removeSubmittedBlocks
tzdybal e43432f
chore: minor improvements
tzdybal 90bebad
refactor: improve TestPendingBlocks
tzdybal e79287b
refactor: wrap errors in SetMetadata and GetMetadata
tzdybal 0b74b42
fix: address review comments
tzdybal 8f90acc
fix: move initialization of `pendingBlocks` to constructor
tzdybal 2fde0b1
test: replace assert with require in metadata tests
tzdybal 5c90639
test: add error handling in TestRemoveSubsetOfBlocks
tzdybal 7b52fa5
test: refactor PendingBlocks tests
tzdybal 95c074b
fix: improve setLastSubmittedHeight
tzdybal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,64 +1,112 @@ | ||
| package block | ||
|
|
||
| import ( | ||
| "sort" | ||
| "sync" | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "strconv" | ||
| "sync/atomic" | ||
|
|
||
| ds "github.com/ipfs/go-datastore" | ||
|
|
||
| "github.com/rollkit/rollkit/store" | ||
| "github.com/rollkit/rollkit/third_party/log" | ||
| "github.com/rollkit/rollkit/types" | ||
| ) | ||
|
|
||
| // LastSubmittedHeightKey is the key used for persisting the last submitted height in store. | ||
| const LastSubmittedHeightKey = "last submitted" | ||
|
|
||
| // PendingBlocks maintains blocks that need to be published to DA layer | ||
| // | ||
| // Important assertions: | ||
| // - blocks are safely stored in database before submission to DA | ||
| // - blocks are always pushed to DA in order (by height) | ||
| // - DA submission of multiple blocks is atomic - it's impossible to submit only part of a batch | ||
| // | ||
| // lastSubmittedHeight is updated only after receiving confirmation from DA. | ||
| // Worst case scenario is when blocks was successfully submitted to DA, but confirmation was not received (e.g. node was | ||
| // restarted, networking issue occurred). In this case blocks are re-submitted to DA (it's extra cost). | ||
| // rollkit is able to skip duplicate blocks so this shouldn't affect full nodes. | ||
| // TODO(tzdybal): we shouldn't try to push all pending blocks at once; this should depend on max blob size | ||
|
Manav-Aggarwal marked this conversation as resolved.
|
||
| type PendingBlocks struct { | ||
| pendingBlocks map[uint64]*types.Block | ||
| mtx *sync.RWMutex | ||
| store store.Store | ||
| logger log.Logger | ||
|
|
||
| // lastSubmittedHeight holds information about last block successfully submitted to DA | ||
| lastSubmittedHeight atomic.Uint64 | ||
| } | ||
|
|
||
| // NewPendingBlocks returns a new PendingBlocks struct | ||
| func NewPendingBlocks() *PendingBlocks { | ||
| return &PendingBlocks{ | ||
| pendingBlocks: make(map[uint64]*types.Block), | ||
| mtx: new(sync.RWMutex), | ||
| func NewPendingBlocks(store store.Store, logger log.Logger) (*PendingBlocks, error) { | ||
| pb := &PendingBlocks{ | ||
| store: store, | ||
| logger: logger, | ||
| } | ||
| if err := pb.init(); err != nil { | ||
| return nil, err | ||
| } | ||
| return pb, nil | ||
| } | ||
|
|
||
| // getPendingBlocks returns a sorted slice of pending blocks | ||
| // that need to be published to DA layer in order of block height | ||
| func (pb *PendingBlocks) getPendingBlocks() []*types.Block { | ||
| blocks := copyBlocks(pb) | ||
| sort.Slice(blocks, func(i, j int) bool { | ||
| return blocks[i].Height() < blocks[j].Height() | ||
| }) | ||
| return blocks | ||
| } | ||
| func (pb *PendingBlocks) getPendingBlocks(ctx context.Context) ([]*types.Block, error) { | ||
| lastSubmitted := pb.lastSubmittedHeight.Load() | ||
| height := pb.store.Height() | ||
|
|
||
| if lastSubmitted == height { | ||
| return nil, nil | ||
| } | ||
| if lastSubmitted > height { | ||
| panic(fmt.Sprintf("height of last block submitted to DA (%d) is greater than height of last block (%d)", | ||
|
tzdybal marked this conversation as resolved.
|
||
| lastSubmitted, height)) | ||
| } | ||
|
|
||
| // copyBlocks creates a copy of the pending blocks in a thread-safe manner. | ||
| // It returns a slice of pointers to the copied blocks. | ||
| func copyBlocks(pb *PendingBlocks) []*types.Block { | ||
| pb.mtx.RLock() | ||
| defer pb.mtx.RUnlock() | ||
| blocks := make([]*types.Block, 0, len(pb.pendingBlocks)) | ||
| for _, block := range pb.pendingBlocks { | ||
| blocks := make([]*types.Block, 0, height-lastSubmitted) | ||
| for i := lastSubmitted + 1; i <= height; i++ { | ||
| block, err := pb.store.GetBlock(ctx, i) | ||
| if err != nil { | ||
| // return as much as possible + error information | ||
| return blocks, err | ||
| } | ||
| blocks = append(blocks, block) | ||
| } | ||
| return blocks | ||
| return blocks, nil | ||
|
tzdybal marked this conversation as resolved.
|
||
| } | ||
|
|
||
| func (pb *PendingBlocks) isEmpty() bool { | ||
| pb.mtx.RLock() | ||
| defer pb.mtx.RUnlock() | ||
| return len(pb.pendingBlocks) == 0 | ||
| return pb.store.Height() == pb.lastSubmittedHeight.Load() | ||
|
tzdybal marked this conversation as resolved.
|
||
| } | ||
|
|
||
| func (pb *PendingBlocks) addPendingBlock(block *types.Block) { | ||
| pb.mtx.Lock() | ||
| defer pb.mtx.Unlock() | ||
| pb.pendingBlocks[block.Height()] = block | ||
| func (pb *PendingBlocks) setLastSubmittedHeight(ctx context.Context, newLastSubmittedHeight uint64) { | ||
| lsh := pb.lastSubmittedHeight.Load() | ||
|
|
||
| if newLastSubmittedHeight > lsh && pb.lastSubmittedHeight.CompareAndSwap(lsh, newLastSubmittedHeight) { | ||
| err := pb.store.SetMetadata(ctx, LastSubmittedHeightKey, []byte(strconv.FormatUint(newLastSubmittedHeight, 10))) | ||
| if err != nil { | ||
| // This indicates IO error in KV store. We can't do much about this. | ||
| // After next successful DA submission, update will be re-attempted (with new value). | ||
| // If store is not updated, after node restart some blocks will be re-submitted to DA. | ||
| pb.logger.Error("failed to store height of latest block submitted to DA", "err", err) | ||
|
tzdybal marked this conversation as resolved.
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| func (pb *PendingBlocks) removeSubmittedBlocks(blocks []*types.Block) { | ||
| pb.mtx.Lock() | ||
| defer pb.mtx.Unlock() | ||
| for _, block := range blocks { | ||
| delete(pb.pendingBlocks, block.Height()) | ||
| func (pb *PendingBlocks) init() error { | ||
| raw, err := pb.store.GetMetadata(context.Background(), LastSubmittedHeightKey) | ||
| if errors.Is(err, ds.ErrNotFound) { | ||
| // LastSubmittedHeightKey was never used, it's special case not actual error | ||
| // we don't need to modify lastSubmittedHeight | ||
| return nil | ||
| } | ||
| if err != nil { | ||
| return err | ||
| } | ||
| lsh, err := strconv.ParseUint(string(raw), 10, 64) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| pb.lastSubmittedHeight.CompareAndSwap(0, lsh) | ||
| return nil | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.