package events import ( "context" "sync" "golang.org/x/xerrors" "github.com/filecoin-project/specs-actors/actors/abi" "go.opencensus.io/trace" "github.com/filecoin-project/lotus/chain/types" ) type heightEvents struct { lk sync.Mutex tsc *tipSetCache gcConfidence abi.ChainEpoch ctr triggerID heightTriggers map[triggerID]*heightHandler htTriggerHeights map[triggerH][]triggerID htHeights map[msgH][]triggerID ctx context.Context } func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error { ctx, span := trace.StartSpan(e.ctx, "events.HeightHeadChange") defer span.End() span.AddAttributes(trace.Int64Attribute("endHeight", int64(app[0].Height()))) span.AddAttributes(trace.Int64Attribute("reverts", int64(len(rev)))) span.AddAttributes(trace.Int64Attribute("applies", int64(len(app)))) e.lk.Lock() defer e.lk.Unlock() for _, ts := range rev { // TODO: log error if h below gcconfidence // revert height-based triggers revert := func(h abi.ChainEpoch, ts *types.TipSet) { for _, tid := range e.htHeights[h] { ctx, span := trace.StartSpan(ctx, "events.HeightRevert") rev := e.heightTriggers[tid].revert e.lk.Unlock() err := rev(ctx, ts) e.lk.Lock() e.heightTriggers[tid].called = false span.End() if err != nil { log.Errorf("reverting chain trigger (@H %d): %s", h, err) } } } revert(ts.Height(), ts) subh := ts.Height() - 1 for { cts, err := e.tsc.get(subh) if err != nil { return err } if cts != nil { break } revert(subh, ts) subh-- } if err := e.tsc.revert(ts); err != nil { return err } } for i := range app { ts := app[i] if err := e.tsc.add(ts); err != nil { return err } // height triggers apply := func(h abi.ChainEpoch, ts *types.TipSet) error { for _, tid := range e.htTriggerHeights[h] { hnd := e.heightTriggers[tid] if hnd.called { return nil } triggerH := h - abi.ChainEpoch(hnd.confidence) incTs, err := e.tsc.getNonNull(triggerH) if err != nil { return err } ctx, span := trace.StartSpan(ctx, "events.HeightApply") span.AddAttributes(trace.BoolAttribute("immediate", false)) handle := hnd.handle e.lk.Unlock() err = handle(ctx, incTs, h) e.lk.Lock() hnd.called = true span.End() if err != nil { log.Errorf("chain trigger (@H %d, called @ %d) failed: %+v", triggerH, ts.Height(), err) } } return nil } if err := apply(ts.Height(), ts); err != nil { return err } subh := ts.Height() - 1 for { cts, err := e.tsc.get(subh) if err != nil { return err } if cts != nil { break } if err := apply(subh, ts); err != nil { return err } subh-- } } return nil } // ChainAt invokes the specified `HeightHandler` when the chain reaches the // specified height+confidence threshold. If the chain is rolled-back under the // specified height, `RevertHandler` will be called. // // ts passed to handlers is the tipset at the specified, or above, if lower tipsets were null func (e *heightEvents) ChainAt(hnd HeightHandler, rev RevertHandler, confidence int, h abi.ChainEpoch) error { e.lk.Lock() // Tricky locking, check your locks if you modify this function! best, err := e.tsc.best() if err != nil { return xerrors.Errorf("error getting best tipset: %w", err) } bestH := best.Height() if bestH >= h+abi.ChainEpoch(confidence) { ts, err := e.tsc.getNonNull(h) if err != nil { log.Warnf("events.ChainAt: calling HandleFunc with nil tipset, not found in cache: %s", err) } e.lk.Unlock() ctx, span := trace.StartSpan(e.ctx, "events.HeightApply") span.AddAttributes(trace.BoolAttribute("immediate", true)) err = hnd(ctx, ts, bestH) span.End() if err != nil { return err } e.lk.Lock() best, err = e.tsc.best() if err != nil { return xerrors.Errorf("error getting best tipset: %w", err) } bestH = best.Height() } defer e.lk.Unlock() if bestH >= h+abi.ChainEpoch(confidence)+e.gcConfidence { return nil } triggerAt := h + abi.ChainEpoch(confidence) id := e.ctr e.ctr++ e.heightTriggers[id] = &heightHandler{ confidence: confidence, handle: hnd, revert: rev, } e.htHeights[h] = append(e.htHeights[h], id) e.htTriggerHeights[triggerAt] = append(e.htTriggerHeights[triggerAt], id) return nil }