controller.go 3.91 KB
Newer Older
1 2 3 4
package syncnode

import (
	"context"
5
	"errors"
6 7
	"fmt"

8 9
	"github.com/ethereum/go-ethereum/log"

10 11 12 13 14
	"github.com/ethereum-optimism/optimism/op-service/locks"
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)

15 16 17
// SyncNodesController manages a collection of active sync nodes.
// Sync nodes are used to sync the supervisor,
// and subject to the canonical chain view as followed by the supervisor.
18
type SyncNodesController struct {
19
	logger log.Logger
20

21 22 23 24
	controllers locks.RWMap[types.ChainID, *locks.RWMap[*ManagedNode, struct{}]]

	backend backend
	db      chainsDB
25 26 27 28

	depSet depset.DependencySet
}

29 30
// NewSyncNodesController creates a new SyncNodeController
func NewSyncNodesController(l log.Logger, depset depset.DependencySet, db chainsDB, backend backend) *SyncNodesController {
31
	return &SyncNodesController{
32 33 34 35
		logger:  l,
		depSet:  depset,
		db:      db,
		backend: backend,
36 37 38
	}
}

39 40 41 42 43 44 45 46
func (snc *SyncNodesController) Close() error {
	snc.controllers.Range(func(chainID types.ChainID, controllers *locks.RWMap[*ManagedNode, struct{}]) bool {
		controllers.Range(func(node *ManagedNode, _ struct{}) bool {
			node.Close()
			return true
		})
		return true
	})
47 48 49
	return nil
}

50 51 52 53 54
// AttachNodeController attaches a node to be managed by the supervisor.
// If noSubscribe, the node is not actively polled/subscribed to, and requires manual ManagedNode.PullEvents calls.
func (snc *SyncNodesController) AttachNodeController(id types.ChainID, ctrl SyncControl, noSubscribe bool) (Node, error) {
	if !snc.depSet.HasChain(id) {
		return nil, fmt.Errorf("chain %v not in dependency set: %w", id, types.ErrUnknownChain)
55
	}
56 57 58
	// lazy init the controllers map for this chain
	if !snc.controllers.Has(id) {
		snc.controllers.Set(id, &locks.RWMap[*ManagedNode, struct{}]{})
59
	}
60 61 62 63 64 65
	controllersForChain, _ := snc.controllers.Get(id)
	node := NewManagedNode(snc.logger, id, ctrl, snc.db, snc.backend, noSubscribe)
	controllersForChain.Set(node, struct{}{})
	anchor, err := ctrl.AnchorPoint(context.Background())
	if err != nil {
		return nil, fmt.Errorf("failed to get anchor point: %w", err)
66
	}
67 68 69 70
	snc.maybeInitSafeDB(id, anchor)
	snc.maybeInitEventsDB(id, anchor)
	node.Start()
	return node, nil
71 72
}

73 74 75 76 77 78 79 80
// maybeInitSafeDB initializes the chain database if it is not already initialized
// it checks if the Local Safe database is empty, and loads it with the Anchor Point if so
func (snc *SyncNodesController) maybeInitSafeDB(id types.ChainID, anchor types.DerivedBlockRefPair) {
	_, err := snc.db.LocalSafe(id)
	if errors.Is(err, types.ErrFuture) {
		snc.logger.Debug("initializing chain database", "chain", id)
		if err := snc.db.UpdateCrossSafe(id, anchor.DerivedFrom, anchor.Derived); err != nil {
			snc.logger.Warn("failed to initialize cross safe", "chain", id, "error", err)
81
		}
82 83
		if err := snc.db.UpdateLocalSafe(id, anchor.DerivedFrom, anchor.Derived); err != nil {
			snc.logger.Warn("failed to initialize local safe", "chain", id, "error", err)
84
		}
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
		snc.logger.Debug("initialized chain database", "chain", id, "anchor", anchor)
	} else if err != nil {
		snc.logger.Warn("failed to check if chain database is initialized", "chain", id, "error", err)
	} else {
		snc.logger.Debug("chain database already initialized", "chain", id)
	}
}

func (snc *SyncNodesController) maybeInitEventsDB(id types.ChainID, anchor types.DerivedBlockRefPair) {
	_, _, _, err := snc.db.OpenBlock(id, 0)
	if errors.Is(err, types.ErrFuture) {
		snc.logger.Debug("initializing events database", "chain", id)
		err := snc.backend.UpdateLocalUnsafe(context.Background(), id, anchor.Derived)
		if err != nil {
			snc.logger.Warn("failed to seal initial block", "chain", id, "error", err)
100
		}
101 102 103 104 105
		snc.logger.Debug("initialized events database", "chain", id)
	} else if err != nil {
		snc.logger.Warn("failed to check if logDB is initialized", "chain", id, "error", err)
	} else {
		snc.logger.Debug("events database already initialized", "chain", id)
106 107
	}
}