diff --git a/pkg/protocol/engine/blocks/block.go b/pkg/protocol/engine/blocks/block.go index 0d204e660..41aa4a27b 100644 --- a/pkg/protocol/engine/blocks/block.go +++ b/pkg/protocol/engine/blocks/block.go @@ -34,7 +34,7 @@ type Block struct { payloadSpenderIDs ds.Set[iotago.TransactionID] // BlockGadget block - preAccepted bool + preAccepted reactive.Variable[bool] acceptanceRatifiers ds.Set[account.SeatIndex] accepted reactive.Variable[bool] preConfirmed bool @@ -86,6 +86,7 @@ func NewBlock(modelBlock *model.Block) *Block { solid: reactive.NewVariable[bool](), invalid: reactive.NewVariable[bool](), booked: reactive.NewVariable[bool](), + preAccepted: reactive.NewVariable[bool](), accepted: reactive.NewVariable[bool](), weightPropagated: reactive.NewVariable[bool](), notarized: reactive.NewEvent(), @@ -106,22 +107,17 @@ func NewRootBlock(blockID iotago.BlockID, commitmentID iotago.CommitmentID, issu commitmentID: commitmentID, issuingTime: issuingTime, }, - solid: reactive.NewVariable[bool](), + solid: reactive.NewVariable[bool]().Init(true), invalid: reactive.NewVariable[bool](), - booked: reactive.NewVariable[bool](), - preAccepted: true, - accepted: reactive.NewVariable[bool](), - weightPropagated: reactive.NewVariable[bool](), + booked: reactive.NewVariable[bool]().Init(true), + preAccepted: reactive.NewVariable[bool]().Init(true), + accepted: reactive.NewVariable[bool]().Init(true), + weightPropagated: reactive.NewVariable[bool]().Init(true), notarized: reactive.NewEvent(), scheduled: true, } - // This should be true since we commit and evict on acceptance. - b.solid.Set(true) - b.booked.Set(true) - b.weightPropagated.Set(true) - b.notarized.Set(true) - b.accepted.Set(true) + b.notarized.Trigger() return b } @@ -138,6 +134,7 @@ func NewMissingBlock(blockID iotago.BlockID) *Block { solid: reactive.NewVariable[bool](), invalid: reactive.NewVariable[bool](), booked: reactive.NewVariable[bool](), + preAccepted: reactive.NewVariable[bool](), accepted: reactive.NewVariable[bool](), weightPropagated: reactive.NewVariable[bool](), notarized: reactive.NewEvent(), @@ -428,24 +425,19 @@ func (b *Block) SetPayloadSpenderIDs(payloadSpenderIDs ds.Set[iotago.Transaction b.payloadSpenderIDs = payloadSpenderIDs } +// PreAccepted returns a reactive variable that is true if the Block was pre accepted. +func (b *Block) PreAccepted() reactive.Variable[bool] { + return b.preAccepted +} + // IsPreAccepted returns true if the Block was preAccepted. func (b *Block) IsPreAccepted() bool { - b.mutex.RLock() - defer b.mutex.RUnlock() - - return b.preAccepted + return b.preAccepted.Get() } // SetPreAccepted sets the Block as preAccepted. func (b *Block) SetPreAccepted() (wasUpdated bool) { - b.mutex.Lock() - defer b.mutex.Unlock() - - if wasUpdated = !b.preAccepted; wasUpdated { - b.preAccepted = true - } - - return wasUpdated + return !b.preAccepted.Set(true) } func (b *Block) AddAcceptanceRatifier(seat account.SeatIndex) (added bool) { @@ -644,7 +636,7 @@ func (b *Block) String() string { builder.AddField(stringify.NewStructField("Invalid", b.invalid.Get())) builder.AddField(stringify.NewStructField("Booked", b.booked.Get())) builder.AddField(stringify.NewStructField("Witnesses", b.witnesses)) - builder.AddField(stringify.NewStructField("PreAccepted", b.preAccepted)) + builder.AddField(stringify.NewStructField("PreAccepted", b.preAccepted.Get())) builder.AddField(stringify.NewStructField("AcceptanceRatifiers", b.acceptanceRatifiers.String())) builder.AddField(stringify.NewStructField("Accepted", b.accepted.Get())) builder.AddField(stringify.NewStructField("PreConfirmed", b.preConfirmed)) diff --git a/pkg/protocol/engine/notarization/slotnotarization/manager.go b/pkg/protocol/engine/notarization/slotnotarization/manager.go index d475ce635..65a41f00a 100644 --- a/pkg/protocol/engine/notarization/slotnotarization/manager.go +++ b/pkg/protocol/engine/notarization/slotnotarization/manager.go @@ -144,7 +144,7 @@ func (m *Manager) ForceCommit(slot iotago.SlotIndex) (*model.Commitment, error) } func (m *Manager) ForceCommitUntil(commitUntilSlot iotago.SlotIndex) error { - m.LogInfof("Force commit until slot %d", commitUntilSlot) + m.LogInfo("force committing until", "slot", commitUntilSlot) for i := m.storage.Settings().LatestCommitment().Slot() + 1; i <= commitUntilSlot; i++ { if _, err := m.ForceCommit(i); err != nil { diff --git a/pkg/protocol/engine/tipmanager/tipmanager.go b/pkg/protocol/engine/tipmanager/tip_manager.go similarity index 100% rename from pkg/protocol/engine/tipmanager/tipmanager.go rename to pkg/protocol/engine/tipmanager/tip_manager.go diff --git a/pkg/protocol/engine/tipmanager/tip_pool.go b/pkg/protocol/engine/tipmanager/tip_pool.go index 32715aa2e..153a031d2 100644 --- a/pkg/protocol/engine/tipmanager/tip_pool.go +++ b/pkg/protocol/engine/tipmanager/tip_pool.go @@ -25,3 +25,17 @@ func (t TipPool) Max(other TipPool) TipPool { return other } + +// String returns a human-readable representation of the TipPool. +func (t TipPool) String() string { + switch t { + case StrongTipPool: + return "StrongTipPool" + case WeakTipPool: + return "WeakTipPool" + case DroppedTipPool: + return "DroppedTipPool" + default: + return "UndefinedTipPool" + } +} diff --git a/pkg/protocol/engine/tipmanager/v1/tipmanager.go b/pkg/protocol/engine/tipmanager/v1/tip_manager.go similarity index 90% rename from pkg/protocol/engine/tipmanager/v1/tipmanager.go rename to pkg/protocol/engine/tipmanager/v1/tip_manager.go index e4c7559c1..4ac94a8fb 100644 --- a/pkg/protocol/engine/tipmanager/v1/tipmanager.go +++ b/pkg/protocol/engine/tipmanager/v1/tip_manager.go @@ -1,12 +1,11 @@ package tipmanagerv1 import ( - "fmt" - "github.com/iotaledger/hive.go/ds/randommap" "github.com/iotaledger/hive.go/ds/reactive" "github.com/iotaledger/hive.go/ds/shrinkingmap" "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/hive.go/log" "github.com/iotaledger/hive.go/runtime/event" "github.com/iotaledger/hive.go/runtime/module" "github.com/iotaledger/hive.go/runtime/syncutils" @@ -58,7 +57,7 @@ func New( blockRetriever func(blockID iotago.BlockID) (block *blocks.Block, exists bool), retrieveCommitteeInSlot func(slot iotago.SlotIndex) (*account.SeatedAccounts, bool), ) *TipManager { - return module.InitSimpleLifecycle(&TipManager{ + t := &TipManager{ Module: subModule, retrieveBlock: blockRetriever, retrieveCommitteeInSlot: retrieveCommitteeInSlot, @@ -68,7 +67,11 @@ func New( strongTipSet: randommap.New[iotago.BlockID, *TipMetadata](), weakTipSet: randommap.New[iotago.BlockID, *TipMetadata](), blockAdded: event.New1[tipmanager.TipMetadata](), - }) + } + + t.initLogging() + + return module.InitSimpleLifecycle(t) } // AddBlock adds a Block to the TipManager and returns the TipMetadata if the Block was added successfully. @@ -78,12 +81,9 @@ func (t *TipManager) AddBlock(block *blocks.Block) tipmanager.TipMetadata { return nil } - tipMetadata, created := storage.GetOrCreate(block.ID(), func() *TipMetadata { - return NewBlockMetadata(block) - }) - + tipMetadata, created := storage.GetOrCreate(block.ID(), t.tipMetadataFactory(block)) if created { - t.setupBlockMetadata(tipMetadata) + t.trackTipMetadata(tipMetadata) } return tipMetadata @@ -153,8 +153,26 @@ func (t *TipManager) Reset() { lo.ForEach(t.weakTipSet.Keys(), func(id iotago.BlockID) { t.weakTipSet.Delete(id) }) } -// setupBlockMetadata sets up the behavior of the given Block. -func (t *TipManager) setupBlockMetadata(tipMetadata *TipMetadata) { +// initLogging initializes the logging of the TipManager. +func (t *TipManager) initLogging() { + logLevel := log.LevelTrace + + t.blockAdded.Hook(func(metadata tipmanager.TipMetadata) { + t.Log("block added", logLevel, "blockID", metadata.ID()) + }) +} + +// tipMetadataFactory creates a function that can be called to create a new TipMetadata instance for the given Block. +func (t *TipManager) tipMetadataFactory(block *blocks.Block) func() *TipMetadata { + return func() *TipMetadata { + return NewTipMetadata(t.NewChildLogger(block.ID().String()), block) + } +} + +// trackTipMetadata sets up the tracking of the given TipMetadata in the TipManager. +func (t *TipManager) trackTipMetadata(tipMetadata *TipMetadata) { + t.blockAdded.Trigger(tipMetadata) + tipMetadata.isStrongTipPoolMember.WithNonEmptyValue(func(_ bool) func() { return t.trackLatestValidationBlock(tipMetadata) }) @@ -190,8 +208,6 @@ func (t *TipManager) setupBlockMetadata(tipMetadata *TipMetadata) { tipMetadata.connectWeakParent(parentMetadata) } }) - - t.blockAdded.Trigger(tipMetadata) } // trackLatestValidationBlock tracks the latest validator block and takes care of marking the corresponding TipMetadata. @@ -232,23 +248,18 @@ func (t *TipManager) trackLatestValidationBlock(tipMetadata *TipMetadata) (teard func (t *TipManager) forEachParentByType(block *blocks.Block, consumer func(parentType iotago.ParentsType, parentMetadata *TipMetadata)) { for _, parent := range block.ParentsWithType() { if metadataStorage := t.metadataStorage(parent.ID.Slot()); metadataStorage != nil { - // Make sure we don't add root blocks back to the tips. + // make sure we don't add root blocks back to the tips. parentBlock, exists := t.retrieveBlock(parent.ID) - if !exists || parentBlock.IsRootBlock() { continue } - if parentBlock.ModelBlock() == nil { - fmt.Printf(">> parentBlock exists, but parentBlock.ProtocolBlock() == nil\n ParentBlock: %s\n Block: %s\n", parentBlock.String(), block.String()) + parentMetadata, created := metadataStorage.GetOrCreate(parent.ID, t.tipMetadataFactory(parentBlock)) + if created { + t.trackTipMetadata(parentMetadata) } - parentMetadata, created := metadataStorage.GetOrCreate(parent.ID, func() *TipMetadata { return NewBlockMetadata(parentBlock) }) consumer(parent.Type, parentMetadata) - - if created { - t.setupBlockMetadata(parentMetadata) - } } } } @@ -285,6 +296,3 @@ func (t *TipManager) selectTips(tipSet *randommap.RandomMap[iotago.BlockID, *Tip return lo.Map(tipSet.Values(), func(tip *TipMetadata) tipmanager.TipMetadata { return tip }) } - -// code contract (make sure the type implements all required methods). -var _ tipmanager.TipManager = new(TipManager) diff --git a/pkg/protocol/engine/tipmanager/v1/tip_metadata.go b/pkg/protocol/engine/tipmanager/v1/tip_metadata.go index 9fcbcf67f..68eda1e0b 100644 --- a/pkg/protocol/engine/tipmanager/v1/tip_metadata.go +++ b/pkg/protocol/engine/tipmanager/v1/tip_metadata.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/iotaledger/hive.go/ds/reactive" - "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/hive.go/log" "github.com/iotaledger/iota-core/pkg/protocol/engine/blocks" "github.com/iotaledger/iota-core/pkg/protocol/engine/tipmanager" iotago "github.com/iotaledger/iota.go/v4" @@ -99,16 +99,37 @@ type TipMetadata struct { // parentsReferencingLatestValidationBlock holds the number of parents that reference the latest validator block. parentsReferencingLatestValidationBlock reactive.Counter[bool] + + log.Logger } -// NewBlockMetadata creates a new TipMetadata instance. -func NewBlockMetadata(block *blocks.Block) *TipMetadata { +// NewTipMetadata creates a new TipMetadata instance. +func NewTipMetadata(logger log.Logger, block *blocks.Block) *TipMetadata { t := &TipMetadata{ + Logger: logger, + block: block, tipPool: reactive.NewVariable[tipmanager.TipPool](tipmanager.TipPool.Max), livenessThresholdReached: reactive.NewEvent(), evicted: reactive.NewEvent(), + isStrongTipPoolMember: reactive.NewVariable[bool](), + isWeakTipPoolMember: reactive.NewVariable[bool](), + isStronglyConnectedToTips: reactive.NewVariable[bool](), + isConnectedToTips: reactive.NewVariable[bool](), + isStronglyReferencedByTips: reactive.NewVariable[bool](), + isWeaklyReferencedByTips: reactive.NewVariable[bool](), + isReferencedByTips: reactive.NewVariable[bool](), isLatestValidationBlock: reactive.NewVariable[bool](), + referencesLatestValidationBlock: reactive.NewVariable[bool](), + isStrongTip: reactive.NewVariable[bool](), + isWeakTip: reactive.NewVariable[bool](), + isValidationTip: reactive.NewVariable[bool](), + isMarkedOrphaned: reactive.NewVariable[bool](), + isOrphaned: reactive.NewVariable[bool](), + anyStrongParentStronglyOrphaned: reactive.NewVariable[bool](), + anyWeakParentWeaklyOrphaned: reactive.NewVariable[bool](), + isStronglyOrphaned: reactive.NewVariable[bool](), + isWeaklyOrphaned: reactive.NewVariable[bool](), stronglyConnectedStrongChildren: reactive.NewCounter[bool](), connectedWeakChildren: reactive.NewCounter[bool](), stronglyOrphanedStrongParents: reactive.NewCounter[bool](), @@ -116,73 +137,8 @@ func NewBlockMetadata(block *blocks.Block) *TipMetadata { parentsReferencingLatestValidationBlock: reactive.NewCounter[bool](), } - t.referencesLatestValidationBlock = reactive.NewDerivedVariable2(func(_ bool, isLatestValidationBlock bool, parentsReferencingLatestValidationBlock int) bool { - return isLatestValidationBlock || parentsReferencingLatestValidationBlock > 0 - }, t.isLatestValidationBlock, t.parentsReferencingLatestValidationBlock) - - t.isMarkedOrphaned = reactive.NewDerivedVariable2[bool, bool](func(_ bool, isLivenessThresholdReached bool, isAccepted bool) bool { - return isLivenessThresholdReached && !isAccepted - }, t.livenessThresholdReached, block.Accepted()) - - t.anyStrongParentStronglyOrphaned = reactive.NewDerivedVariable[bool, int](func(_ bool, stronglyOrphanedStrongParents int) bool { - return stronglyOrphanedStrongParents > 0 - }, t.stronglyOrphanedStrongParents) - - t.anyWeakParentWeaklyOrphaned = reactive.NewDerivedVariable[bool, int](func(_ bool, weaklyOrphanedWeakParents int) bool { - return weaklyOrphanedWeakParents > 0 - }, t.weaklyOrphanedWeakParents) - - t.isStronglyOrphaned = reactive.NewDerivedVariable3[bool, bool, bool, bool](func(_ bool, isMarkedOrphaned bool, anyStrongParentStronglyOrphaned bool, anyWeakParentWeaklyOrphaned bool) bool { - return isMarkedOrphaned || anyStrongParentStronglyOrphaned || anyWeakParentWeaklyOrphaned - }, t.isMarkedOrphaned, t.anyStrongParentStronglyOrphaned, t.anyWeakParentWeaklyOrphaned) - - t.isWeaklyOrphaned = reactive.NewDerivedVariable2[bool, bool, bool](func(_ bool, isMarkedOrphaned bool, anyWeakParentWeaklyOrphaned bool) bool { - return isMarkedOrphaned || anyWeakParentWeaklyOrphaned - }, t.isMarkedOrphaned, t.anyWeakParentWeaklyOrphaned) - - t.isOrphaned = reactive.NewDerivedVariable2[bool, bool, bool](func(_ bool, isStronglyOrphaned bool, isWeaklyOrphaned bool) bool { - return isStronglyOrphaned || isWeaklyOrphaned - }, t.isStronglyOrphaned, t.isWeaklyOrphaned) - - t.isStrongTipPoolMember = reactive.NewDerivedVariable3(func(_ bool, tipPool tipmanager.TipPool, isOrphaned bool, isEvicted bool) bool { - return tipPool == tipmanager.StrongTipPool && !isOrphaned && !isEvicted - }, t.tipPool, t.isOrphaned, t.evicted) - - t.isWeakTipPoolMember = reactive.NewDerivedVariable3(func(_ bool, tipPool tipmanager.TipPool, isOrphaned bool, isEvicted bool) bool { - return tipPool == tipmanager.WeakTipPool && !isOrphaned && !isEvicted - }, t.tipPool, t.isOrphaned, t.evicted) - - t.isStronglyReferencedByTips = reactive.NewDerivedVariable[bool, int](func(_ bool, stronglyConnectedStrongChildren int) bool { - return stronglyConnectedStrongChildren > 0 - }, t.stronglyConnectedStrongChildren) - - t.isWeaklyReferencedByTips = reactive.NewDerivedVariable[bool, int](func(_ bool, connectedWeakChildren int) bool { - return connectedWeakChildren > 0 - }, t.connectedWeakChildren) - - t.isReferencedByTips = reactive.NewDerivedVariable2[bool, bool, bool](func(_ bool, isWeaklyReferencedByTips bool, isStronglyReferencedByTips bool) bool { - return isWeaklyReferencedByTips || isStronglyReferencedByTips - }, t.isWeaklyReferencedByTips, t.isStronglyReferencedByTips) - - t.isStronglyConnectedToTips = reactive.NewDerivedVariable2(func(_ bool, isStrongTipPoolMember bool, isStronglyReferencedByTips bool) bool { - return isStrongTipPoolMember || isStronglyReferencedByTips - }, t.isStrongTipPoolMember, t.isStronglyReferencedByTips) - - t.isConnectedToTips = reactive.NewDerivedVariable3(func(_ bool, isReferencedByTips bool, isStrongTipPoolMember bool, isWeakTipPoolMember bool) bool { - return isReferencedByTips || isStrongTipPoolMember || isWeakTipPoolMember - }, t.isReferencedByTips, t.isStrongTipPoolMember, t.isWeakTipPoolMember) - - t.isStrongTip = reactive.NewDerivedVariable2[bool, bool, bool](func(_ bool, isStrongTipPoolMember bool, isStronglyReferencedByTips bool) bool { - return isStrongTipPoolMember && !isStronglyReferencedByTips - }, t.isStrongTipPoolMember, t.isStronglyReferencedByTips) - - t.isWeakTip = reactive.NewDerivedVariable2[bool, bool, bool](func(_ bool, isWeakTipPoolMember bool, isReferencedByTips bool) bool { - return isWeakTipPoolMember && !isReferencedByTips - }, t.isWeakTipPoolMember, t.isReferencedByTips) - - t.isValidationTip = reactive.NewDerivedVariable2(func(_ bool, isStrongTip bool, referencesLatestValidationBlock bool) bool { - return isStrongTip && referencesLatestValidationBlock - }, t.isStrongTip, t.referencesLatestValidationBlock) + t.initLogging() + t.initBehavior() return t } @@ -227,50 +183,6 @@ func (t *TipMetadata) Evicted() reactive.Event { return t.evicted } -// registerAsLatestValidationBlock registers the TipMetadata as the latest validation block if it is newer than the -// currently registered block and sets the isLatestValidationBlock variable accordingly. The function returns true if the -// operation was successful. -func (t *TipMetadata) registerAsLatestValidationBlock(latestValidationBlock reactive.Variable[*TipMetadata]) (registered bool) { - latestValidationBlock.Compute(func(currentLatestValidationBlock *TipMetadata) *TipMetadata { - registered = currentLatestValidationBlock == nil || currentLatestValidationBlock.block.IssuingTime().Before(t.block.IssuingTime()) - - return lo.Cond(registered, t, currentLatestValidationBlock) - }) - - if registered { - t.isLatestValidationBlock.Set(true) - - // Once the latestValidationBlock is updated again (by another block), we need to reset the isLatestValidationBlock - // variable. - latestValidationBlock.OnUpdateOnce(func(_ *TipMetadata, _ *TipMetadata) { - t.isLatestValidationBlock.Set(false) - }, func(_ *TipMetadata, latestValidationBlock *TipMetadata) bool { - return latestValidationBlock != t - }) - } - - return registered -} - -// connectStrongParent sets up the parent and children related properties for a strong parent. -func (t *TipMetadata) connectStrongParent(strongParent *TipMetadata) { - t.stronglyOrphanedStrongParents.Monitor(strongParent.isStronglyOrphaned) - t.parentsReferencingLatestValidationBlock.Monitor(strongParent.referencesLatestValidationBlock) - - // unsubscribe when the parent is evicted, since we otherwise continue to hold a reference to it. - unsubscribe := strongParent.stronglyConnectedStrongChildren.Monitor(t.isStronglyConnectedToTips) - strongParent.evicted.OnUpdate(func(_ bool, _ bool) { unsubscribe() }) -} - -// connectWeakParent sets up the parent and children related properties for a weak parent. -func (t *TipMetadata) connectWeakParent(weakParent *TipMetadata) { - t.weaklyOrphanedWeakParents.Monitor(weakParent.isWeaklyOrphaned) - - // unsubscribe when the parent is evicted, since we otherwise continue to hold a reference to it. - unsubscribe := weakParent.connectedWeakChildren.Monitor(t.isConnectedToTips) - weakParent.evicted.OnUpdate(func(_ bool, _ bool) { unsubscribe() }) -} - // String returns a human-readable representation of the TipMetadata. func (t *TipMetadata) String() string { return fmt.Sprintf( @@ -321,5 +233,156 @@ func (t *TipMetadata) String() string { ) } -// code contract (make sure the type implements all required methods). -var _ tipmanager.TipMetadata = new(TipMetadata) +// initLogging initializes the logging for the TipMetadata. +func (t *TipMetadata) initLogging() { + logLevel := log.LevelTrace + + t.tipPool.LogUpdates(t, logLevel, "TipPool") + t.livenessThresholdReached.LogUpdates(t, logLevel, "LivenessThresholdReached") + t.evicted.LogUpdates(t, logLevel, "Evicted") + t.isStrongTipPoolMember.LogUpdates(t, logLevel, "IsStrongTipPoolMember") + t.isWeakTipPoolMember.LogUpdates(t, logLevel, "IsWeakTipPoolMember") + t.isStronglyConnectedToTips.LogUpdates(t, logLevel, "IsStronglyConnectedToTips") + t.isConnectedToTips.LogUpdates(t, logLevel, "IsConnectedToTips") + t.isStronglyReferencedByTips.LogUpdates(t, logLevel, "IsStronglyReferencedByTips") + t.isWeaklyReferencedByTips.LogUpdates(t, logLevel, "IsWeaklyReferencedByTips") + t.isReferencedByTips.LogUpdates(t, logLevel, "IsReferencedByTips") + t.isLatestValidationBlock.LogUpdates(t, logLevel, "IsLatestValidationBlock") + t.referencesLatestValidationBlock.LogUpdates(t, logLevel, "ReferencesLatestValidationBlock") + t.isStrongTip.LogUpdates(t, logLevel, "IsStrongTip") + t.isWeakTip.LogUpdates(t, logLevel, "IsWeakTip") + t.isValidationTip.LogUpdates(t, logLevel, "IsValidationTip") + t.isMarkedOrphaned.LogUpdates(t, logLevel, "IsMarkedOrphaned") + t.isOrphaned.LogUpdates(t, logLevel, "IsOrphaned") + t.anyStrongParentStronglyOrphaned.LogUpdates(t, logLevel, "AnyStrongParentStronglyOrphaned") + t.anyWeakParentWeaklyOrphaned.LogUpdates(t, logLevel, "AnyWeakParentWeaklyOrphaned") + t.isStronglyOrphaned.LogUpdates(t, logLevel, "IsStronglyOrphaned") + t.isWeaklyOrphaned.LogUpdates(t, logLevel, "IsWeaklyOrphaned") + t.stronglyConnectedStrongChildren.LogUpdates(t, logLevel, "StronglyConnectedStrongChildren") + t.connectedWeakChildren.LogUpdates(t, logLevel, "ConnectedWeakChildren") + t.stronglyOrphanedStrongParents.LogUpdates(t, logLevel, "StronglyOrphanedStrongParents") + t.weaklyOrphanedWeakParents.LogUpdates(t, logLevel, "WeaklyOrphanedWeakParents") + t.parentsReferencingLatestValidationBlock.LogUpdates(t, logLevel, "ParentsReferencingLatestValidationBlock") + + t.evicted.OnTrigger(t.UnsubscribeFromParentLogger) +} + +// initBehavior initializes the behavior of the TipMetadata. +func (t *TipMetadata) initBehavior() { + t.referencesLatestValidationBlock.DeriveValueFrom(reactive.NewDerivedVariable2(func(_ bool, isLatestValidationBlock bool, parentsReferencingLatestValidationBlock int) bool { + return isLatestValidationBlock || parentsReferencingLatestValidationBlock > 0 + }, t.isLatestValidationBlock, t.parentsReferencingLatestValidationBlock)) + + t.anyStrongParentStronglyOrphaned.DeriveValueFrom(reactive.NewDerivedVariable[bool, int](func(_ bool, stronglyOrphanedStrongParents int) bool { + return stronglyOrphanedStrongParents > 0 + }, t.stronglyOrphanedStrongParents)) + + t.anyWeakParentWeaklyOrphaned.DeriveValueFrom(reactive.NewDerivedVariable[bool, int](func(_ bool, weaklyOrphanedWeakParents int) bool { + return weaklyOrphanedWeakParents > 0 + }, t.weaklyOrphanedWeakParents)) + + t.isStronglyOrphaned.DeriveValueFrom(reactive.NewDerivedVariable3[bool, bool, bool, bool](func(_ bool, isMarkedOrphaned bool, anyStrongParentStronglyOrphaned bool, anyWeakParentWeaklyOrphaned bool) bool { + return isMarkedOrphaned || anyStrongParentStronglyOrphaned || anyWeakParentWeaklyOrphaned + }, t.isMarkedOrphaned, t.anyStrongParentStronglyOrphaned, t.anyWeakParentWeaklyOrphaned)) + + t.isWeaklyOrphaned.DeriveValueFrom(reactive.NewDerivedVariable2[bool, bool, bool](func(_ bool, isMarkedOrphaned bool, anyWeakParentWeaklyOrphaned bool) bool { + return isMarkedOrphaned || anyWeakParentWeaklyOrphaned + }, t.isMarkedOrphaned, t.anyWeakParentWeaklyOrphaned)) + + t.isOrphaned.DeriveValueFrom(reactive.NewDerivedVariable2[bool, bool, bool](func(_ bool, isStronglyOrphaned bool, isWeaklyOrphaned bool) bool { + return isStronglyOrphaned || isWeaklyOrphaned + }, t.isStronglyOrphaned, t.isWeaklyOrphaned)) + + t.isStrongTipPoolMember.DeriveValueFrom(reactive.NewDerivedVariable3(func(_ bool, tipPool tipmanager.TipPool, isOrphaned bool, isEvicted bool) bool { + return tipPool == tipmanager.StrongTipPool && !isOrphaned && !isEvicted + }, t.tipPool, t.isOrphaned, t.evicted)) + + t.isWeakTipPoolMember.DeriveValueFrom(reactive.NewDerivedVariable3(func(_ bool, tipPool tipmanager.TipPool, isOrphaned bool, isEvicted bool) bool { + return tipPool == tipmanager.WeakTipPool && !isOrphaned && !isEvicted + }, t.tipPool, t.isOrphaned, t.evicted)) + + t.isStronglyReferencedByTips.DeriveValueFrom(reactive.NewDerivedVariable[bool, int](func(_ bool, stronglyConnectedStrongChildren int) bool { + return stronglyConnectedStrongChildren > 0 + }, t.stronglyConnectedStrongChildren)) + + t.isWeaklyReferencedByTips.DeriveValueFrom(reactive.NewDerivedVariable[bool, int](func(_ bool, connectedWeakChildren int) bool { + return connectedWeakChildren > 0 + }, t.connectedWeakChildren)) + + t.isReferencedByTips.DeriveValueFrom(reactive.NewDerivedVariable2[bool, bool, bool](func(_ bool, isWeaklyReferencedByTips bool, isStronglyReferencedByTips bool) bool { + return isWeaklyReferencedByTips || isStronglyReferencedByTips + }, t.isWeaklyReferencedByTips, t.isStronglyReferencedByTips)) + + t.isStronglyConnectedToTips.DeriveValueFrom(reactive.NewDerivedVariable2(func(_ bool, isStrongTipPoolMember bool, isStronglyReferencedByTips bool) bool { + return isStrongTipPoolMember || isStronglyReferencedByTips + }, t.isStrongTipPoolMember, t.isStronglyReferencedByTips)) + + t.isConnectedToTips.DeriveValueFrom(reactive.NewDerivedVariable3(func(_ bool, isReferencedByTips bool, isStrongTipPoolMember bool, isWeakTipPoolMember bool) bool { + return isReferencedByTips || isStrongTipPoolMember || isWeakTipPoolMember + }, t.isReferencedByTips, t.isStrongTipPoolMember, t.isWeakTipPoolMember)) + + t.isStrongTip.DeriveValueFrom(reactive.NewDerivedVariable2[bool, bool, bool](func(_ bool, isStrongTipPoolMember bool, isStronglyReferencedByTips bool) bool { + return isStrongTipPoolMember && !isStronglyReferencedByTips + }, t.isStrongTipPoolMember, t.isStronglyReferencedByTips)) + + t.isWeakTip.DeriveValueFrom(reactive.NewDerivedVariable2[bool, bool, bool](func(_ bool, isWeakTipPoolMember bool, isReferencedByTips bool) bool { + return isWeakTipPoolMember && !isReferencedByTips + }, t.isWeakTipPoolMember, t.isReferencedByTips)) + + t.isValidationTip.DeriveValueFrom(reactive.NewDerivedVariable2(func(_ bool, isStrongTip bool, referencesLatestValidationBlock bool) bool { + return isStrongTip && referencesLatestValidationBlock + }, t.isStrongTip, t.referencesLatestValidationBlock)) + + // unsubscribe from external properties when the block is evicted. + t.evicted.OnTrigger( + t.isMarkedOrphaned.DeriveValueFrom(reactive.NewDerivedVariable2[bool, bool](func(_ bool, isLivenessThresholdReached bool, isPreAccepted bool) bool { + return isLivenessThresholdReached && !isPreAccepted + }, t.livenessThresholdReached, t.block.PreAccepted())), + ) +} + +// registerAsLatestValidationBlock registers the TipMetadata as the latest validation block if it is newer than the +// currently registered block and sets the isLatestValidationBlock variable accordingly. The function returns true if the +// operation was successful. +func (t *TipMetadata) registerAsLatestValidationBlock(latestValidationBlock reactive.Variable[*TipMetadata]) (registered bool) { + latestValidationBlock.Compute(func(currentBlock *TipMetadata) *TipMetadata { + if registered = currentBlock == nil || currentBlock.block.IssuingTime().Before(t.block.IssuingTime()); registered { + return t + } + + return currentBlock + }) + + if registered { + t.isLatestValidationBlock.Set(true) + + // Once the latestValidationBlock is updated again (by another block), we need to reset the + // isLatestValidationBlock variable. + latestValidationBlock.OnUpdateOnce(func(_ *TipMetadata, _ *TipMetadata) { + t.isLatestValidationBlock.Set(false) + }, func(_ *TipMetadata, latestValidationBlock *TipMetadata) bool { + return latestValidationBlock != t + }) + } + + return registered +} + +// connectStrongParent sets up the parent and children related properties for a strong parent. +func (t *TipMetadata) connectStrongParent(strongParent *TipMetadata) { + t.stronglyOrphanedStrongParents.Monitor(strongParent.isStronglyOrphaned) + t.parentsReferencingLatestValidationBlock.Monitor(strongParent.referencesLatestValidationBlock) + + // unsubscribe when the parent is evicted, since we otherwise continue to hold a reference to it. + unsubscribe := strongParent.stronglyConnectedStrongChildren.Monitor(t.isStronglyConnectedToTips) + strongParent.evicted.OnTrigger(unsubscribe) +} + +// connectWeakParent sets up the parent and children related properties for a weak parent. +func (t *TipMetadata) connectWeakParent(weakParent *TipMetadata) { + t.weaklyOrphanedWeakParents.Monitor(weakParent.isWeaklyOrphaned) + + // unsubscribe when the parent is evicted, since we otherwise continue to hold a reference to it. + unsubscribe := weakParent.connectedWeakChildren.Monitor(t.isConnectedToTips) + weakParent.evicted.OnUpdate(func(_ bool, _ bool) { unsubscribe() }) +}