-
Notifications
You must be signed in to change notification settings - Fork 260
New DALC API #341
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
New DALC API #341
Changes from all commits
1879977
4db8cc2
cf371b2
695966a
f2395d7
13d3e68
55d20bb
d972d1f
5ff1ddd
1902905
8a758cb
eb2e87c
7dae373
3e4a966
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 |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ package block | |
| import ( | ||
| "context" | ||
| "fmt" | ||
| "sync" | ||
| "sync/atomic" | ||
| "time" | ||
|
|
||
|
|
@@ -12,6 +13,7 @@ import ( | |
| "github.com/tendermint/tendermint/crypto/merkle" | ||
| "github.com/tendermint/tendermint/proxy" | ||
| tmtypes "github.com/tendermint/tendermint/types" | ||
| "go.uber.org/multierr" | ||
|
|
||
| "github.com/celestiaorg/optimint/config" | ||
| "github.com/celestiaorg/optimint/da" | ||
|
|
@@ -22,6 +24,9 @@ import ( | |
| "github.com/celestiaorg/optimint/types" | ||
| ) | ||
|
|
||
| // defaultDABlockTime is used only if DABlockTime is not configured for manager | ||
| const defaultDABlockTime = 30 * time.Second | ||
|
|
||
| // Manager is responsible for aggregating transactions into blocks. | ||
| type Manager struct { | ||
| lastState state.State | ||
|
|
@@ -36,15 +41,21 @@ type Manager struct { | |
|
|
||
| dalc da.DataAvailabilityLayerClient | ||
| retriever da.BlockRetriever | ||
| // daHeight is the height of the latest processed DA block | ||
| daHeight uint64 | ||
|
|
||
| HeaderOutCh chan *types.Header | ||
| HeaderInCh chan *types.Header | ||
|
|
||
| syncTarget uint64 | ||
| blockInCh chan *types.Block | ||
| retrieveCh chan uint64 | ||
| syncCache map[uint64]*types.Block | ||
|
|
||
| // retrieveMtx is used by retrieveCond | ||
| retrieveMtx *sync.Mutex | ||
| // retrieveCond is used to notify sync goroutine (SyncLoop) that it needs to retrieve data | ||
| retrieveCond *sync.Cond | ||
|
|
||
| logger log.Logger | ||
| } | ||
|
|
||
|
|
@@ -72,12 +83,20 @@ func NewManager( | |
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if s.DAHeight < conf.DAStartHeight { | ||
| s.DAHeight = conf.DAStartHeight | ||
| } | ||
|
|
||
| proposerAddress, err := getAddress(proposerKey) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| if conf.DABlockTime == 0 { | ||
| logger.Info("WARNING: using default DA block time", "DABlockTime", defaultDABlockTime) | ||
| conf.DABlockTime = defaultDABlockTime | ||
| } | ||
|
adlerjohn marked this conversation as resolved.
|
||
|
|
||
| exec := state.NewBlockExecutor(proposerAddress, conf.NamespaceID, genesis.ChainID, mempool, proxyApp, eventBus, logger) | ||
| if s.LastBlockHeight+1 == genesis.InitialHeight { | ||
| res, err := exec.InitChain(genesis) | ||
|
|
@@ -100,13 +119,16 @@ func NewManager( | |
| executor: exec, | ||
| dalc: dalc, | ||
| retriever: dalc.(da.BlockRetriever), // TODO(tzdybal): do it in more gentle way (after MVP) | ||
| HeaderOutCh: make(chan *types.Header), | ||
| HeaderInCh: make(chan *types.Header), | ||
| blockInCh: make(chan *types.Block), | ||
| retrieveCh: make(chan uint64), | ||
| daHeight: s.DAHeight, | ||
| // channels are buffered to avoid blocking on input/output operations, buffer sizes are arbitrary | ||
| HeaderOutCh: make(chan *types.Header, 100), | ||
| HeaderInCh: make(chan *types.Header, 100), | ||
| blockInCh: make(chan *types.Block, 100), | ||
| retrieveMtx: new(sync.Mutex), | ||
| syncCache: make(map[uint64]*types.Block), | ||
| logger: logger, | ||
| } | ||
| agg.retrieveCond = sync.NewCond(agg.retrieveMtx) | ||
|
|
||
| return agg, nil | ||
| } | ||
|
|
@@ -142,8 +164,11 @@ func (m *Manager) AggregationLoop(ctx context.Context) { | |
| } | ||
|
|
||
| func (m *Manager) SyncLoop(ctx context.Context) { | ||
| daTicker := time.NewTicker(m.conf.DABlockTime) | ||
| for { | ||
| select { | ||
| case <-daTicker.C: | ||
| m.retrieveCond.Signal() | ||
| case header := <-m.HeaderInCh: | ||
| m.logger.Debug("block header received", "height", header.Height, "hash", header.Hash()) | ||
| newHeight := header.Height | ||
|
|
@@ -153,14 +178,15 @@ func (m *Manager) SyncLoop(ctx context.Context) { | |
| // it's handled gently in RetrieveLoop | ||
| if newHeight > currentHeight { | ||
| atomic.StoreUint64(&m.syncTarget, newHeight) | ||
| m.retrieveCh <- newHeight | ||
| m.retrieveCond.Signal() | ||
| } | ||
| case block := <-m.blockInCh: | ||
| m.logger.Debug("block body retrieved from DALC", | ||
| "height", block.Header.Height, | ||
| "hash", block.Hash(), | ||
| ) | ||
| m.syncCache[block.Header.Height] = block | ||
| m.retrieveCond.Signal() | ||
| currentHeight := m.store.Height() // TODO(tzdybal): maybe store a copy in memory | ||
| b1, ok1 := m.syncCache[currentHeight+1] | ||
| b2, ok2 := m.syncCache[currentHeight+2] | ||
|
|
@@ -181,6 +207,7 @@ func (m *Manager) SyncLoop(ctx context.Context) { | |
| continue | ||
| } | ||
|
|
||
| newState.DAHeight = atomic.LoadUint64(&m.daHeight) | ||
| m.lastState = newState | ||
| err = m.store.UpdateState(m.lastState) | ||
| if err != nil { | ||
|
|
@@ -195,50 +222,75 @@ func (m *Manager) SyncLoop(ctx context.Context) { | |
| } | ||
| } | ||
|
|
||
| // RetrieveLoop is responsible for interacting with DA layer. | ||
| func (m *Manager) RetrieveLoop(ctx context.Context) { | ||
| // waitCh is used to signal the retrieve loop, that it should process next blocks | ||
| // retrieveCond can be signalled in completely async manner, and goroutine below | ||
| // works as some kind of "buffer" for those signals | ||
| waitCh := make(chan interface{}) | ||
| go func() { | ||
| for { | ||
| m.retrieveMtx.Lock() | ||
| m.retrieveCond.Wait() | ||
| waitCh <- nil | ||
| m.retrieveMtx.Unlock() | ||
| if ctx.Err() != nil { | ||
| return | ||
| } | ||
| } | ||
| }() | ||
|
|
||
| for { | ||
| select { | ||
| case <-m.retrieveCh: | ||
| target := atomic.LoadUint64(&m.syncTarget) | ||
| for h := m.store.Height() + 1; h <= target; h++ { | ||
| m.logger.Debug("trying to retrieve block from DALC", "height", h) | ||
| m.mustRetrieveBlock(ctx, h) | ||
| case <-waitCh: | ||
| for { | ||
| daHeight := atomic.LoadUint64(&m.daHeight) | ||
| m.logger.Debug("retrieve", "daHeight", daHeight) | ||
| err := m.processNextDABlock() | ||
| if err != nil { | ||
| m.logger.Error("failed to retrieve block from DALC", "daHeight", daHeight, "errors", err.Error()) | ||
| break | ||
| } | ||
| atomic.AddUint64(&m.daHeight, 1) | ||
|
Contributor
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. Wait, are you sure this is the right use of atomics? What if
Contributor
Author
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. This is the only place where
Contributor
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. My concern is if e.g.
Contributor
Author
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. It's not possible right now, as there is only one thread incrementing this variable, and I don't see a reason to add more threads/goroutines. |
||
| } | ||
| case <-ctx.Done(): | ||
| return | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func (m *Manager) mustRetrieveBlock(ctx context.Context, height uint64) { | ||
| func (m *Manager) processNextDABlock() error { | ||
| // TODO(tzdybal): extract configuration option | ||
| maxRetries := 10 | ||
| daHeight := atomic.LoadUint64(&m.daHeight) | ||
|
|
||
| var err error | ||
| m.logger.Debug("trying to retrieve block from DA", "daHeight", daHeight) | ||
| for r := 0; r < maxRetries; r++ { | ||
| err := m.fetchBlock(ctx, height) | ||
| if err == nil { | ||
| return | ||
| blockResp, fetchErr := m.fetchBlock(daHeight) | ||
| if fetchErr != nil { | ||
| err = multierr.Append(err, fetchErr) | ||
| time.Sleep(100 * time.Millisecond) | ||
|
adlerjohn marked this conversation as resolved.
|
||
| } else { | ||
| for _, block := range blockResp.Blocks { | ||
| m.blockInCh <- block | ||
| } | ||
| return nil | ||
| } | ||
| // TODO(tzdybal): configuration option | ||
| // TODO(tzdybal): exponential backoff | ||
| time.Sleep(100 * time.Millisecond) | ||
| } | ||
| // TODO(tzdybal): this is only temporary solution, for MVP | ||
| panic("failed to retrieve block with DALC") | ||
| return err | ||
| } | ||
|
|
||
| func (m *Manager) fetchBlock(ctx context.Context, height uint64) error { | ||
| func (m *Manager) fetchBlock(daHeight uint64) (da.ResultRetrieveBlocks, error) { | ||
| var err error | ||
| blockRes := m.retriever.RetrieveBlock(height) | ||
| blockRes := m.retriever.RetrieveBlocks(daHeight) | ||
| switch blockRes.Code { | ||
| case da.StatusSuccess: | ||
| m.blockInCh <- blockRes.Block | ||
| case da.StatusError: | ||
| err = fmt.Errorf("failed to retrieve block: %s", blockRes.Message) | ||
| case da.StatusTimeout: | ||
| err = fmt.Errorf("timeout during retrieve block: %s", blockRes.Message) | ||
| } | ||
| return err | ||
| return blockRes, err | ||
| } | ||
|
|
||
| func (m *Manager) getRemainingSleep(start time.Time) time.Duration { | ||
|
|
@@ -305,6 +357,7 @@ func (m *Manager) publishBlock(ctx context.Context) error { | |
| return err | ||
| } | ||
|
|
||
| newState.DAHeight = atomic.LoadUint64(&m.daHeight) | ||
| m.lastState = newState | ||
| err = m.store.UpdateState(m.lastState) | ||
| if err != nil { | ||
|
|
@@ -320,6 +373,7 @@ func (m *Manager) publishBlock(ctx context.Context) error { | |
| } | ||
|
|
||
| func (m *Manager) broadcastBlock(ctx context.Context, block *types.Block) error { | ||
| m.logger.Debug("submitting block to DA layer", "height", block.Header.Height) | ||
| res := m.dalc.SubmitBlock(block) | ||
| if res.Code != da.StatusSuccess { | ||
| return fmt.Errorf("DA layer submission failed: %s", res.Message) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.