From a43667a4c45c109646c4c56f9770f8b8da22beae Mon Sep 17 00:00:00 2001 From: Hans Moog <3293976+hmoog@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:27:44 +0100 Subject: [PATCH 01/13] Feat: Add validator tip set --- .../engine/tipmanager/v1/tip_metadata.go | 40 +++++++++++++++---- .../engine/tipmanager/v1/tipmanager.go | 11 +++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/pkg/protocol/engine/tipmanager/v1/tip_metadata.go b/pkg/protocol/engine/tipmanager/v1/tip_metadata.go index dcb28070b..7cca9a0ba 100644 --- a/pkg/protocol/engine/tipmanager/v1/tip_metadata.go +++ b/pkg/protocol/engine/tipmanager/v1/tip_metadata.go @@ -48,12 +48,22 @@ type TipMetadata struct { // that is connected to the tips. isReferencedByTips reactive.Variable[bool] + // isLatestValidatorBlock is true if the block is the latest block of a validator. + isLatestValidatorBlock reactive.Variable[bool] + + // referencesLatestValidatorBlock is true if the block is the latest validator block or has parents that reference + // the latest validator block. + referencesLatestValidatorBlock reactive.Variable[bool] + // isStrongTip is true if the block is a strong tip pool member and is not strongly referenced by other tips. isStrongTip reactive.Variable[bool] // isWeakTip is true if the block is a weak tip pool member and is not referenced by other tips. isWeakTip reactive.Variable[bool] + // isValidatorTip is true if the block is a strong tip and references the latest validator block. + isValidatorTip reactive.Variable[bool] + // isMarkedOrphaned is true if the liveness threshold has been reached and the block was not accepted. isMarkedOrphaned reactive.Variable[bool] @@ -85,21 +95,30 @@ type TipMetadata struct { // weaklyOrphanedWeakParents holds the number of weak parents that are weakly orphaned. weaklyOrphanedWeakParents reactive.Counter[bool] + + // parentsReferencingLatestValidatorBlock holds the number of parents that reference the latest validator block. + parentsReferencingLatestValidatorBlock reactive.Counter[bool] } // NewBlockMetadata creates a new TipMetadata instance. func NewBlockMetadata(block *blocks.Block) *TipMetadata { t := &TipMetadata{ - block: block, - tipPool: reactive.NewVariable[tipmanager.TipPool](tipmanager.TipPool.Max), - livenessThresholdReached: reactive.NewEvent(), - evicted: reactive.NewEvent(), - stronglyConnectedStrongChildren: reactive.NewCounter[bool](), - connectedWeakChildren: reactive.NewCounter[bool](), - stronglyOrphanedStrongParents: reactive.NewCounter[bool](), - weaklyOrphanedWeakParents: reactive.NewCounter[bool](), + block: block, + tipPool: reactive.NewVariable[tipmanager.TipPool](tipmanager.TipPool.Max), + livenessThresholdReached: reactive.NewEvent(), + evicted: reactive.NewEvent(), + isLatestValidatorBlock: reactive.NewVariable[bool](), + stronglyConnectedStrongChildren: reactive.NewCounter[bool](), + connectedWeakChildren: reactive.NewCounter[bool](), + stronglyOrphanedStrongParents: reactive.NewCounter[bool](), + weaklyOrphanedWeakParents: reactive.NewCounter[bool](), + parentsReferencingLatestValidatorBlock: reactive.NewCounter[bool](), } + t.referencesLatestValidatorBlock = reactive.NewDerivedVariable2(func(_ bool, isLatestValidatorBlock bool, parentsReferencingLatestValidatorBlock int) bool { + return isLatestValidatorBlock || parentsReferencingLatestValidatorBlock > 0 + }, t.isLatestValidatorBlock, t.parentsReferencingLatestValidatorBlock) + t.isMarkedOrphaned = reactive.NewDerivedVariable2[bool, bool](func(_ bool, isLivenessThresholdReached bool, isAccepted bool) bool { return isLivenessThresholdReached && !isAccepted }, t.livenessThresholdReached, block.Accepted()) @@ -160,6 +179,10 @@ func NewBlockMetadata(block *blocks.Block) *TipMetadata { return isWeakTipPoolMember && !isReferencedByTips }, t.isWeakTipPoolMember, t.isReferencedByTips) + t.isValidatorTip = reactive.NewDerivedVariable2(func(_ bool, isStrongTip bool, referencesLatestValidatorBlock bool) bool { + return isStrongTip && referencesLatestValidatorBlock + }, t.isStrongTip, t.referencesLatestValidatorBlock) + return t } @@ -206,6 +229,7 @@ func (t *TipMetadata) Evicted() reactive.Event { // 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.parentsReferencingLatestValidatorBlock.Monitor(strongParent.referencesLatestValidatorBlock) // unsubscribe when the parent is evicted, since we otherwise continue to hold a reference to it. unsubscribe := strongParent.stronglyConnectedStrongChildren.Monitor(t.isStronglyConnectedToTips) diff --git a/pkg/protocol/engine/tipmanager/v1/tipmanager.go b/pkg/protocol/engine/tipmanager/v1/tipmanager.go index e71e079af..bfa2383e4 100644 --- a/pkg/protocol/engine/tipmanager/v1/tipmanager.go +++ b/pkg/protocol/engine/tipmanager/v1/tipmanager.go @@ -22,6 +22,9 @@ type TipManager struct { // tipMetadataStorage contains the TipMetadata of all Blocks that are managed by the TipManager. tipMetadataStorage *shrinkingmap.ShrinkingMap[iotago.SlotIndex, *shrinkingmap.ShrinkingMap[iotago.BlockID, *TipMetadata]] + // validatorTipSet contains the subset of blocks from the strong tip set that reference the latest validator block. + validatorTipSet *randommap.RandomMap[iotago.BlockID, *TipMetadata] + // strongTipSet contains the blocks of the strong tip pool that have no referencing children. strongTipSet *randommap.RandomMap[iotago.BlockID, *TipMetadata] @@ -123,6 +126,14 @@ func (t *TipManager) Shutdown() { // setupBlockMetadata sets up the behavior of the given Block. func (t *TipManager) setupBlockMetadata(tipMetadata *TipMetadata) { + tipMetadata.isValidatorTip.OnUpdate(func(_ bool, isValidatorTip bool) { + if isValidatorTip { + t.validatorTipSet.Set(tipMetadata.ID(), tipMetadata) + } else { + t.validatorTipSet.Delete(tipMetadata.ID()) + } + }) + tipMetadata.isStrongTip.OnUpdate(func(_ bool, isStrongTip bool) { if isStrongTip { t.strongTipSet.Set(tipMetadata.ID(), tipMetadata) From 00a0533c771a9bc06121a9c133a950049c755432 Mon Sep 17 00:00:00 2001 From: Hans Moog <3293976+hmoog@users.noreply.github.com> Date: Thu, 18 Jan 2024 01:18:51 +0100 Subject: [PATCH 02/13] Feat: added management of "isLatestValidatorBlock" flag --- pkg/protocol/engine/tipmanager/tipmanager.go | 6 +++ .../engine/tipmanager/v1/tip_metadata.go | 26 +++++++++ .../engine/tipmanager/v1/tipmanager.go | 53 +++++++++++++++++-- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/pkg/protocol/engine/tipmanager/tipmanager.go b/pkg/protocol/engine/tipmanager/tipmanager.go index 878f2b670..8fb2808e2 100644 --- a/pkg/protocol/engine/tipmanager/tipmanager.go +++ b/pkg/protocol/engine/tipmanager/tipmanager.go @@ -27,6 +27,12 @@ type TipManager interface { // OnBlockAdded registers a callback that is triggered whenever a new Block was added to the TipManager. OnBlockAdded(handler func(block TipMetadata)) (unsubscribe func()) + // AddValidator adds a validator to the tracking of the TipManager. + AddValidator(accountID iotago.AccountID) (added bool) + + // RemoveValidator removes a validator from the tracking of the TipManager. + RemoveValidator(accountID iotago.AccountID) (removed bool) + // StrongTips returns the strong tips of the TipManager (with an optional limit). StrongTips(optAmount ...int) []TipMetadata diff --git a/pkg/protocol/engine/tipmanager/v1/tip_metadata.go b/pkg/protocol/engine/tipmanager/v1/tip_metadata.go index 7cca9a0ba..a2ca223cf 100644 --- a/pkg/protocol/engine/tipmanager/v1/tip_metadata.go +++ b/pkg/protocol/engine/tipmanager/v1/tip_metadata.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/iotaledger/hive.go/ds/reactive" + "github.com/iotaledger/hive.go/lo" "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" @@ -226,6 +227,31 @@ func (t *TipMetadata) Evicted() reactive.Event { return t.evicted } +// registerAsLatestValidatorBlock registers the TipMetadata as the latest validator block if it is newer than the +// currently registered block and sets the isLatestValidatorBlock variable accordingly. The function returns true if the +// operation was successful. +func (t *TipMetadata) registerAsLatestValidatorBlock(latestValidatorBlock reactive.Variable[*TipMetadata]) (registered bool) { + latestValidatorBlock.Compute(func(currentLatestValidatorBlock *TipMetadata) *TipMetadata { + registered = currentLatestValidatorBlock == nil || currentLatestValidatorBlock.block.IssuingTime().Before(t.block.IssuingTime()) + + return lo.Cond(registered, t, currentLatestValidatorBlock) + }) + + if registered { + t.isLatestValidatorBlock.Set(true) + + ifLatestValidatorBlockChanged := func(_ *TipMetadata, latestValidatorBlock *TipMetadata) bool { + return latestValidatorBlock != t + } + + latestValidatorBlock.OnUpdateOnce(func(_ *TipMetadata, _ *TipMetadata) { + t.isLatestValidatorBlock.Set(false) + }, ifLatestValidatorBlockChanged) + } + + 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) diff --git a/pkg/protocol/engine/tipmanager/v1/tipmanager.go b/pkg/protocol/engine/tipmanager/v1/tipmanager.go index bfa2383e4..c93dbb0d4 100644 --- a/pkg/protocol/engine/tipmanager/v1/tipmanager.go +++ b/pkg/protocol/engine/tipmanager/v1/tipmanager.go @@ -4,6 +4,7 @@ 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/runtime/event" @@ -22,6 +23,9 @@ type TipManager struct { // tipMetadataStorage contains the TipMetadata of all Blocks that are managed by the TipManager. tipMetadataStorage *shrinkingmap.ShrinkingMap[iotago.SlotIndex, *shrinkingmap.ShrinkingMap[iotago.BlockID, *TipMetadata]] + // latestValidatorBlocks contains a Variable for each validator that stores the latest validator block. + latestValidatorBlocks *shrinkingmap.ShrinkingMap[iotago.AccountID, reactive.Variable[*TipMetadata]] + // validatorTipSet contains the subset of blocks from the strong tip set that reference the latest validator block. validatorTipSet *randommap.RandomMap[iotago.BlockID, *TipMetadata] @@ -47,11 +51,12 @@ type TipManager struct { // New creates a new TipManager. func New(blockRetriever func(blockID iotago.BlockID) (block *blocks.Block, exists bool)) *TipManager { t := &TipManager{ - retrieveBlock: blockRetriever, - tipMetadataStorage: shrinkingmap.New[iotago.SlotIndex, *shrinkingmap.ShrinkingMap[iotago.BlockID, *TipMetadata]](), - strongTipSet: randommap.New[iotago.BlockID, *TipMetadata](), - weakTipSet: randommap.New[iotago.BlockID, *TipMetadata](), - blockAdded: event.New1[tipmanager.TipMetadata](), + retrieveBlock: blockRetriever, + tipMetadataStorage: shrinkingmap.New[iotago.SlotIndex, *shrinkingmap.ShrinkingMap[iotago.BlockID, *TipMetadata]](), + latestValidatorBlocks: shrinkingmap.New[iotago.AccountID, reactive.Variable[*TipMetadata]](), + strongTipSet: randommap.New[iotago.BlockID, *TipMetadata](), + weakTipSet: randommap.New[iotago.BlockID, *TipMetadata](), + blockAdded: event.New1[tipmanager.TipMetadata](), } t.TriggerConstructed() @@ -83,6 +88,25 @@ func (t *TipManager) OnBlockAdded(handler func(block tipmanager.TipMetadata)) (u return t.blockAdded.Hook(handler).Unhook } +// AddValidator adds a validator to the tracking of the TipManager. +func (t *TipManager) AddValidator(accountID iotago.AccountID) (added bool) { + _, added = t.latestValidatorBlocks.GetOrCreate(accountID, func() reactive.Variable[*TipMetadata] { + return reactive.NewVariable[*TipMetadata]() + }) + + return added +} + +// RemoveValidator removes a validator from the tracking of the TipManager. +func (t *TipManager) RemoveValidator(accountID iotago.AccountID) (removed bool) { + latestValidatorBlock, removed := t.latestValidatorBlocks.DeleteAndReturn(accountID) + if removed { + latestValidatorBlock.Set(nil) + } + + return removed +} + // StrongTips returns the strong tips of the TipManager (with an optional limit). func (t *TipManager) StrongTips(optAmount ...int) []tipmanager.TipMetadata { return t.selectTips(t.strongTipSet, optAmount...) @@ -126,6 +150,10 @@ func (t *TipManager) Shutdown() { // setupBlockMetadata sets up the behavior of the given Block. func (t *TipManager) setupBlockMetadata(tipMetadata *TipMetadata) { + tipMetadata.isStrongTipPoolMember.WithNonEmptyValue(func(_ bool) func() { + return t.trackLatestValidatorBlock(tipMetadata) + }) + tipMetadata.isValidatorTip.OnUpdate(func(_ bool, isValidatorTip bool) { if isValidatorTip { t.validatorTipSet.Set(tipMetadata.ID(), tipMetadata) @@ -161,6 +189,21 @@ func (t *TipManager) setupBlockMetadata(tipMetadata *TipMetadata) { t.blockAdded.Trigger(tipMetadata) } +// trackLatestValidatorBlock tracks the latest validator block and takes care of marking the corresponding TipMetadata. +func (t *TipManager) trackLatestValidatorBlock(tipMetadata *TipMetadata) (teardown func()) { + latestValidatorBlock, exists := t.latestValidatorBlocks.Get(tipMetadata.Block().ProtocolBlock().Header.IssuerID) + if !exists || !tipMetadata.registerAsLatestValidatorBlock(latestValidatorBlock) { + return + } + + // reset the latest validator block to nil if we are still the latest one during teardown + return func() { + latestValidatorBlock.Compute(func(latestValidatorBlock *TipMetadata) *TipMetadata { + return lo.Cond(latestValidatorBlock == tipMetadata, nil, latestValidatorBlock) + }) + } +} + // forEachParentByType iterates through the parents of the given block and calls the consumer for each parent. func (t *TipManager) forEachParentByType(block *blocks.Block, consumer func(parentType iotago.ParentsType, parentMetadata *TipMetadata)) { for _, parent := range block.ParentsWithType() { From a82f6eafcd39402c5e0ef1769b88a233e6e10976 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:29:13 +0800 Subject: [PATCH 03/13] Add missing initialization of validation tip set and getter --- pkg/protocol/engine/tipmanager/v1/tipmanager.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/protocol/engine/tipmanager/v1/tipmanager.go b/pkg/protocol/engine/tipmanager/v1/tipmanager.go index c93dbb0d4..667dd8534 100644 --- a/pkg/protocol/engine/tipmanager/v1/tipmanager.go +++ b/pkg/protocol/engine/tipmanager/v1/tipmanager.go @@ -54,6 +54,7 @@ func New(blockRetriever func(blockID iotago.BlockID) (block *blocks.Block, exist retrieveBlock: blockRetriever, tipMetadataStorage: shrinkingmap.New[iotago.SlotIndex, *shrinkingmap.ShrinkingMap[iotago.BlockID, *TipMetadata]](), latestValidatorBlocks: shrinkingmap.New[iotago.AccountID, reactive.Variable[*TipMetadata]](), + validatorTipSet: randommap.New[iotago.BlockID, *TipMetadata](), strongTipSet: randommap.New[iotago.BlockID, *TipMetadata](), weakTipSet: randommap.New[iotago.BlockID, *TipMetadata](), blockAdded: event.New1[tipmanager.TipMetadata](), @@ -107,6 +108,10 @@ func (t *TipManager) RemoveValidator(accountID iotago.AccountID) (removed bool) return removed } +func (t *TipManager) ValidationTips(optAmount ...int) []tipmanager.TipMetadata { + return t.selectTips(t.validatorTipSet, optAmount...) +} + // StrongTips returns the strong tips of the TipManager (with an optional limit). func (t *TipManager) StrongTips(optAmount ...int) []tipmanager.TipMetadata { return t.selectTips(t.strongTipSet, optAmount...) From f75b6b9bf4e78c7c53e5d4bec325339d02ad6934 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:31:05 +0800 Subject: [PATCH 04/13] Rename CreateBlock to CreateBasicBlock in testframework --- .../engine/tipmanager/tests/testframework.go | 2 +- .../engine/tipmanager/tests/tipmanager_test.go | 12 ++++++------ .../engine/tipselection/v1/tip_selection_test.go | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/protocol/engine/tipmanager/tests/testframework.go b/pkg/protocol/engine/tipmanager/tests/testframework.go index 781dc41b9..3a90f2cb9 100644 --- a/pkg/protocol/engine/tipmanager/tests/testframework.go +++ b/pkg/protocol/engine/tipmanager/tests/testframework.go @@ -53,7 +53,7 @@ func (t *TestFramework) AddBlock(alias string) tipmanager.TipMetadata { return t.tipMetadataByAlias[alias] } -func (t *TestFramework) CreateBlock(alias string, parents map[iotago.ParentsType][]string, optBlockBuilder ...func(*builder.BasicBlockBuilder)) *blocks.Block { +func (t *TestFramework) CreateBasicBlock(alias string, parents map[iotago.ParentsType][]string, optBlockBuilder ...func(*builder.BasicBlockBuilder)) *blocks.Block { blockBuilder := builder.NewBasicBlockBuilder(t.API) blockBuilder.IssuingTime(time.Now()) diff --git a/pkg/protocol/engine/tipmanager/tests/tipmanager_test.go b/pkg/protocol/engine/tipmanager/tests/tipmanager_test.go index b994fa882..00a6b2a47 100644 --- a/pkg/protocol/engine/tipmanager/tests/tipmanager_test.go +++ b/pkg/protocol/engine/tipmanager/tests/tipmanager_test.go @@ -10,13 +10,13 @@ import ( func TestTipManager(t *testing.T) { tf := NewTestFramework(t) - tf.CreateBlock("Bernd", map[iotago.ParentsType][]string{ + tf.CreateBasicBlock("Bernd", map[iotago.ParentsType][]string{ iotago.StrongParentType: {"Genesis"}, }) - tf.CreateBlock("Bernd1", map[iotago.ParentsType][]string{ + tf.CreateBasicBlock("Bernd1", map[iotago.ParentsType][]string{ iotago.StrongParentType: {"Bernd"}, }) - tf.CreateBlock("Bernd1.1", map[iotago.ParentsType][]string{ + tf.CreateBasicBlock("Bernd1.1", map[iotago.ParentsType][]string{ iotago.StrongParentType: {"Bernd"}, }) @@ -33,13 +33,13 @@ func TestTipManager(t *testing.T) { func Test_Orphanage(t *testing.T) { tf := NewTestFramework(t) - tf.CreateBlock("A", map[iotago.ParentsType][]string{ + tf.CreateBasicBlock("A", map[iotago.ParentsType][]string{ iotago.StrongParentType: {"Genesis"}, }) - tf.CreateBlock("B", map[iotago.ParentsType][]string{ + tf.CreateBasicBlock("B", map[iotago.ParentsType][]string{ iotago.StrongParentType: {"Genesis"}, }) - tf.CreateBlock("C", map[iotago.ParentsType][]string{ + tf.CreateBasicBlock("C", map[iotago.ParentsType][]string{ iotago.StrongParentType: {"A", "B"}, }) diff --git a/pkg/protocol/engine/tipselection/v1/tip_selection_test.go b/pkg/protocol/engine/tipselection/v1/tip_selection_test.go index a69695d55..e99d328da 100644 --- a/pkg/protocol/engine/tipselection/v1/tip_selection_test.go +++ b/pkg/protocol/engine/tipselection/v1/tip_selection_test.go @@ -12,7 +12,7 @@ import ( func TestTipSelection_DynamicLivenessThreshold_NoWitnesses(t *testing.T) { tf := NewTestFramework(t) - tf.TipManager.CreateBlock("Block", map[iotago.ParentsType][]string{iotago.StrongParentType: {"Genesis"}}) + tf.TipManager.CreateBasicBlock("Block", map[iotago.ParentsType][]string{iotago.StrongParentType: {"Genesis"}}) tf.TipManager.AddBlock("Block") expectedLivenessThreshold := tf.ExpectedLivenessThreshold("Block") @@ -43,7 +43,7 @@ func TestTipSelection_DynamicLivenessThreshold_NoWitnesses(t *testing.T) { func TestTipSelection_DynamicLivenessThreshold_WithSingleWitness(t *testing.T) { tf := NewTestFramework(t) - tf.TipManager.CreateBlock("Block", map[iotago.ParentsType][]string{iotago.StrongParentType: {"Genesis"}}) + tf.TipManager.CreateBasicBlock("Block", map[iotago.ParentsType][]string{iotago.StrongParentType: {"Genesis"}}) tf.TipManager.AddBlock("Block") expectedLivenessThresholdWithoutWitnesses := tf.ExpectedLivenessThreshold("Block") @@ -95,7 +95,7 @@ func TestTipSelection_DynamicLivenessThreshold_WithSingleWitness(t *testing.T) { func TestTipSelection_DynamicLivenessThreshold_WithMaxWitnesses(t *testing.T) { tf := NewTestFramework(t) - tf.TipManager.CreateBlock("Block", map[iotago.ParentsType][]string{iotago.StrongParentType: {"Genesis"}}) + tf.TipManager.CreateBasicBlock("Block", map[iotago.ParentsType][]string{iotago.StrongParentType: {"Genesis"}}) tf.TipManager.AddBlock("Block") livenessThresholdZero := tf.ExpectedLivenessThreshold("Block") @@ -178,7 +178,7 @@ func TestTipSelection_DynamicLivenessThreshold_WithMaxWitnesses(t *testing.T) { func TestDynamicLivenessThreshold(t *testing.T) { const committeeSize = 10 tf := NewTestFramework(t, WithCommitteeSize(committeeSize)) - tf.TipManager.CreateBlock("Block", map[iotago.ParentsType][]string{iotago.StrongParentType: {"Genesis"}}) + tf.TipManager.CreateBasicBlock("Block", map[iotago.ParentsType][]string{iotago.StrongParentType: {"Genesis"}}) tf.TipManager.AddBlock("Block") livenessThresholdLowerBound := tf.TipManager.API.ProtocolParameters().LivenessThresholdLowerBound() From b8b5b7136774126cf864f5f256961fd9178c6870 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:44:22 +0800 Subject: [PATCH 05/13] Add test for validation tips --- .../engine/tipmanager/tests/testframework.go | 66 ++++++++++- .../tipmanager/tests/tipmanager_test.go | 109 ++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) diff --git a/pkg/protocol/engine/tipmanager/tests/testframework.go b/pkg/protocol/engine/tipmanager/tests/testframework.go index 3a90f2cb9..8e74c4cee 100644 --- a/pkg/protocol/engine/tipmanager/tests/testframework.go +++ b/pkg/protocol/engine/tipmanager/tests/testframework.go @@ -23,7 +23,9 @@ type TestFramework struct { blockIDsByAlias map[string]iotago.BlockID tipMetadataByAlias map[string]tipmanager.TipMetadata blocksByID map[iotago.BlockID]*blocks.Block + validatorByAlias map[string]iotago.AccountID test *testing.T + time time.Time API iotago.API } @@ -33,8 +35,10 @@ func NewTestFramework(test *testing.T) *TestFramework { blockIDsByAlias: make(map[string]iotago.BlockID), tipMetadataByAlias: make(map[string]tipmanager.TipMetadata), blocksByID: make(map[iotago.BlockID]*blocks.Block), + validatorByAlias: make(map[string]iotago.AccountID), test: test, API: tpkg.ZeroCostTestAPI, + time: time.Now(), } t.blockIDsByAlias["Genesis"] = iotago.EmptyBlockID @@ -47,6 +51,22 @@ func NewTestFramework(test *testing.T) *TestFramework { return t } +func (t *TestFramework) Validator(alias string) iotago.AccountID { + validator, validatorExists := t.validatorByAlias[alias] + require.True(t.test, validatorExists) + + return validator +} + +func (t *TestFramework) AddValidator(alias string) { + accountID := iotago.AccountID(tpkg.Rand32ByteArray()) + accountID.RegisterAlias(alias) + + t.validatorByAlias[alias] = accountID + + t.Instance.AddValidator(accountID) +} + func (t *TestFramework) AddBlock(alias string) tipmanager.TipMetadata { t.tipMetadataByAlias[alias] = t.Instance.AddBlock(t.Block(alias)) @@ -55,7 +75,43 @@ func (t *TestFramework) AddBlock(alias string) tipmanager.TipMetadata { func (t *TestFramework) CreateBasicBlock(alias string, parents map[iotago.ParentsType][]string, optBlockBuilder ...func(*builder.BasicBlockBuilder)) *blocks.Block { blockBuilder := builder.NewBasicBlockBuilder(t.API) - blockBuilder.IssuingTime(time.Now()) + + // Make sure that blocks don't have the same timestamp. + t.time = t.time.Add(1) + blockBuilder.IssuingTime(t.time) + + if strongParents, strongParentsExist := parents[iotago.StrongParentType]; strongParentsExist { + blockBuilder.StrongParents(lo.Map(strongParents, t.BlockID)) + } + if weakParents, weakParentsExist := parents[iotago.WeakParentType]; weakParentsExist { + blockBuilder.WeakParents(lo.Map(weakParents, t.BlockID)) + } + if shallowLikeParents, shallowLikeParentsExist := parents[iotago.ShallowLikeParentType]; shallowLikeParentsExist { + blockBuilder.ShallowLikeParents(lo.Map(shallowLikeParents, t.BlockID)) + } + + if len(optBlockBuilder) > 0 { + optBlockBuilder[0](blockBuilder) + } + + block, err := blockBuilder.Build() + require.NoError(t.test, err) + + modelBlock, err := model.BlockFromBlock(block) + require.NoError(t.test, err) + + t.blocksByID[modelBlock.ID()] = blocks.NewBlock(modelBlock) + t.blockIDsByAlias[alias] = modelBlock.ID() + + return t.blocksByID[modelBlock.ID()] +} + +func (t *TestFramework) CreateValidationBlock(alias string, parents map[iotago.ParentsType][]string, optBlockBuilder ...func(blockBuilder *builder.ValidationBlockBuilder)) *blocks.Block { + blockBuilder := builder.NewValidationBlockBuilder(t.API) + + // Make sure that blocks don't have the same timestamp. + t.time = t.time.Add(1) + blockBuilder.IssuingTime(t.time) if strongParents, strongParentsExist := parents[iotago.StrongParentType]; strongParentsExist { blockBuilder.StrongParents(lo.Map(strongParents, t.BlockID)) @@ -115,6 +171,14 @@ func (t *TestFramework) RequireStrongTips(aliases ...string) { require.Equal(t.test, len(aliases), len(t.Instance.StrongTips()), "strongTips size does not match") } +func (t *TestFramework) RequireValidationTips(aliases ...string) { + for _, alias := range aliases { + require.True(t.test, ds.NewSet(lo.Map(t.Instance.ValidationTips(), tipmanager.TipMetadata.ID)...).Has(t.BlockID(alias)), "validationTips does not contain block '%s'", alias) + } + + require.Equal(t.test, len(aliases), len(t.Instance.ValidationTips()), "validationTips size does not match") +} + func (t *TestFramework) RequireLivenessThresholdReached(alias string, expected bool) { require.Equal(t.test, expected, t.TipMetadata(alias).LivenessThresholdReached().Get()) } diff --git a/pkg/protocol/engine/tipmanager/tests/tipmanager_test.go b/pkg/protocol/engine/tipmanager/tests/tipmanager_test.go index 00a6b2a47..ce7a066b4 100644 --- a/pkg/protocol/engine/tipmanager/tests/tipmanager_test.go +++ b/pkg/protocol/engine/tipmanager/tests/tipmanager_test.go @@ -5,6 +5,8 @@ import ( "github.com/iotaledger/iota-core/pkg/protocol/engine/tipmanager" iotago "github.com/iotaledger/iota.go/v4" + "github.com/iotaledger/iota.go/v4/builder" + "github.com/iotaledger/iota.go/v4/tpkg" ) func TestTipManager(t *testing.T) { @@ -56,3 +58,110 @@ func Test_Orphanage(t *testing.T) { blockB.LivenessThresholdReached().Trigger() tf.RequireStrongTips("A") } + +func Test_ValidationTips(t *testing.T) { + tf := NewTestFramework(t) + + { + tf.CreateBasicBlock("1", map[iotago.ParentsType][]string{ + iotago.StrongParentType: {"Genesis"}, + }) + tf.CreateBasicBlock("2", map[iotago.ParentsType][]string{ + iotago.StrongParentType: {"Genesis"}, + }) + + tf.AddBlock("1").TipPool().Set(tipmanager.StrongTipPool) + tf.AddBlock("2").TipPool().Set(tipmanager.StrongTipPool) + + tf.RequireStrongTips("1", "2") + } + + // Add validation tip for validatorA. + { + tf.AddValidator("validatorA") + + tf.CreateValidationBlock("3", map[iotago.ParentsType][]string{ + iotago.StrongParentType: {"2"}, + }, func(blockBuilder *builder.ValidationBlockBuilder) { + blockBuilder.Sign(tf.Validator("validatorA"), tpkg.RandEd25519PrivateKey()) + }) + + tf.AddBlock("3").TipPool().Set(tipmanager.StrongTipPool) + + tf.RequireValidationTips("3") + tf.RequireStrongTips("1", "3") + } + + // Add validation tip for validatorB. + { + tf.AddValidator("validatorB") + + tf.CreateValidationBlock("4", map[iotago.ParentsType][]string{ + iotago.StrongParentType: {"1"}, + }, func(blockBuilder *builder.ValidationBlockBuilder) { + blockBuilder.Sign(tf.Validator("validatorB"), tpkg.RandEd25519PrivateKey()) + }) + + tf.AddBlock("4").TipPool().Set(tipmanager.StrongTipPool) + + tf.RequireValidationTips("3", "4") + tf.RequireStrongTips("3", "4") + } + + // Add basic blocks in the future cone of the validation tips, referencing both existing validation tips. + { + tf.CreateBasicBlock("5", map[iotago.ParentsType][]string{ + iotago.StrongParentType: {"3", "4"}, + }) + + tf.AddBlock("5").TipPool().Set(tipmanager.StrongTipPool) + + tf.RequireValidationTips("5") + tf.RequireStrongTips("5") + } + + // Add basic blocks in the future cone of the validation tips. + { + tf.CreateBasicBlock("6", map[iotago.ParentsType][]string{ + iotago.StrongParentType: {"3"}, + }) + tf.CreateBasicBlock("7", map[iotago.ParentsType][]string{ + iotago.StrongParentType: {"4"}, + }) + + tf.AddBlock("6").TipPool().Set(tipmanager.StrongTipPool) + tf.AddBlock("7").TipPool().Set(tipmanager.StrongTipPool) + + tf.RequireValidationTips("5", "6", "7") + tf.RequireStrongTips("5", "6", "7") + } + + // A newer validation block replaces the previous validation tip of that validator. + { + tf.CreateValidationBlock("8", map[iotago.ParentsType][]string{ + iotago.StrongParentType: {"6"}, + }, func(blockBuilder *builder.ValidationBlockBuilder) { + blockBuilder.Sign(tf.Validator("validatorB"), tpkg.RandEd25519PrivateKey()) + }) + + tf.AddBlock("8").TipPool().Set(tipmanager.StrongTipPool) + + tf.RequireValidationTips("5", "8") + tf.RequireStrongTips("5", "7", "8") + } + + // A newer validation block (with parallel past cone) still becomes a validation tip and overwrites + // the previous validation tip of that validator. + { + tf.CreateValidationBlock("9", map[iotago.ParentsType][]string{ + iotago.StrongParentType: {"Genesis"}, + }, func(blockBuilder *builder.ValidationBlockBuilder) { + blockBuilder.Sign(tf.Validator("validatorA"), tpkg.RandEd25519PrivateKey()) + }) + + tf.AddBlock("9").TipPool().Set(tipmanager.StrongTipPool) + + tf.RequireValidationTips("8", "9") + tf.RequireStrongTips("5", "7", "8", "9") + } +} From 4ee434b69235d1cde49fe90b93681c604993e888 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:53:39 +0800 Subject: [PATCH 06/13] Select one validation tip when SelectTips is called --- pkg/protocol/engine/tipmanager/tipmanager.go | 3 ++ .../engine/tipselection/v1/tip_selection.go | 29 +++++++++++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/pkg/protocol/engine/tipmanager/tipmanager.go b/pkg/protocol/engine/tipmanager/tipmanager.go index 8fb2808e2..8093b8968 100644 --- a/pkg/protocol/engine/tipmanager/tipmanager.go +++ b/pkg/protocol/engine/tipmanager/tipmanager.go @@ -33,6 +33,9 @@ type TipManager interface { // RemoveValidator removes a validator from the tracking of the TipManager. RemoveValidator(accountID iotago.AccountID) (removed bool) + // ValidationTips returns the validation tips of the TipManager (with an optional limit). + ValidationTips(optAmount ...int) []TipMetadata + // StrongTips returns the strong tips of the TipManager (with an optional limit). StrongTips(optAmount ...int) []TipMetadata diff --git a/pkg/protocol/engine/tipselection/v1/tip_selection.go b/pkg/protocol/engine/tipselection/v1/tip_selection.go index e9312d8e4..9a362a9c8 100644 --- a/pkg/protocol/engine/tipselection/v1/tip_selection.go +++ b/pkg/protocol/engine/tipselection/v1/tip_selection.go @@ -5,6 +5,7 @@ import ( "github.com/iotaledger/hive.go/ds" "github.com/iotaledger/hive.go/ds/reactive" + "github.com/iotaledger/hive.go/ds/types" "github.com/iotaledger/hive.go/ierrors" "github.com/iotaledger/hive.go/lo" "github.com/iotaledger/hive.go/runtime/module" @@ -111,7 +112,7 @@ func (t *TipSelection) SelectTips(amount int) (references model.ParentReferences _ = t.spendDAG.ReadConsistent(func(_ spenddag.ReadLockedSpendDAG[iotago.TransactionID, mempool.StateID, ledger.BlockVoteRank]) error { previousLikedInsteadConflicts := ds.NewSet[iotago.TransactionID]() - if t.collectReferences(references, iotago.StrongParentType, t.tipManager.StrongTips, func(tip tipmanager.TipMetadata) { + if t.collectReferences(func(tip tipmanager.TipMetadata) { addedLikedInsteadReferences, updatedLikedInsteadConflicts, err := t.likedInsteadReferences(previousLikedInsteadConflicts, tip) if err != nil { tip.TipPool().Set(tipmanager.WeakTipPool) @@ -124,17 +125,22 @@ func (t *TipSelection) SelectTips(amount int) (references model.ParentReferences previousLikedInsteadConflicts = updatedLikedInsteadConflicts } - }, amount); len(references[iotago.StrongParentType]) == 0 { + }, + // We select one validation tip as a strong parent. This is a security step to ensure that the tangle maintains + // acceptance by stitching together validation blocks. + types.NewTuple[func(optAmount ...int) []tipmanager.TipMetadata, int](t.tipManager.ValidationTips, 1), + types.NewTuple[func(optAmount ...int) []tipmanager.TipMetadata, int](t.tipManager.StrongTips, amount-1), + ); len(references[iotago.StrongParentType]) == 0 { references[iotago.StrongParentType] = iotago.BlockIDs{t.rootBlock()} } - t.collectReferences(references, iotago.WeakParentType, t.tipManager.WeakTips, func(tip tipmanager.TipMetadata) { + t.collectReferences(func(tip tipmanager.TipMetadata) { if !t.isValidWeakTip(tip.Block()) { tip.TipPool().Set(tipmanager.DroppedTipPool) } else if !shallowLikesParents.Has(tip.ID()) { references[iotago.WeakParentType] = append(references[iotago.WeakParentType], tip.ID()) } - }, t.optMaxWeakReferences) + }, types.NewTuple[func(optAmount ...int) []tipmanager.TipMetadata, int](t.tipManager.WeakTips, t.optMaxWeakReferences)) return nil }) @@ -210,9 +216,11 @@ func (t *TipSelection) likedInsteadReferences(likedConflicts ds.Set[iotago.Trans // collectReferences collects tips from a tip selector (and calls the callback for each tip) until the amount of // references of the given type is reached. -func (t *TipSelection) collectReferences(references model.ParentReferences, parentsType iotago.ParentsType, tipSelector func(optAmount ...int) []tipmanager.TipMetadata, callback func(tipmanager.TipMetadata), amount int) { +func (t *TipSelection) collectReferences(callback func(tipmanager.TipMetadata), tipSelectorsAmount ...*types.Tuple[func(optAmount ...int) []tipmanager.TipMetadata, int]) { seenTips := ds.NewSet[iotago.BlockID]() - selectUniqueTips := func(amount int) (uniqueTips []tipmanager.TipMetadata) { + + // selectUniqueTips selects 'amount' unique tips from the given tip selector. + selectUniqueTips := func(tipSelector func(optAmount ...int) []tipmanager.TipMetadata, amount int) (uniqueTips []tipmanager.TipMetadata) { if amount > 0 { for _, tip := range tipSelector(amount + seenTips.Size()) { if seenTips.Add(tip.ID()) { @@ -228,9 +236,12 @@ func (t *TipSelection) collectReferences(references model.ParentReferences, pare return uniqueTips } - for tipCandidates := selectUniqueTips(amount); len(tipCandidates) != 0; tipCandidates = selectUniqueTips(amount - len(references[parentsType])) { - for _, tip := range tipCandidates { - callback(tip) + // We select the desired amount of tips from all given tip selectors, respectively. + for _, tipSelectorAmount := range tipSelectorsAmount { + for tipCandidates := selectUniqueTips(tipSelectorAmount.A, tipSelectorAmount.B); len(tipCandidates) != 0; tipCandidates = selectUniqueTips(tipSelectorAmount.A, tipSelectorAmount.B) { + for _, tip := range tipCandidates { + callback(tip) + } } } } From f8e0172b2c889cc488a4af04999b8d36e0e4bc1b Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Fri, 19 Jan 2024 15:13:51 +0800 Subject: [PATCH 07/13] Only track validation blocks for validationTips --- pkg/protocol/engine/tipmanager/v1/tip_metadata.go | 10 +++++----- pkg/protocol/engine/tipmanager/v1/tipmanager.go | 11 ++++++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/pkg/protocol/engine/tipmanager/v1/tip_metadata.go b/pkg/protocol/engine/tipmanager/v1/tip_metadata.go index a2ca223cf..e9a8c0781 100644 --- a/pkg/protocol/engine/tipmanager/v1/tip_metadata.go +++ b/pkg/protocol/engine/tipmanager/v1/tip_metadata.go @@ -240,13 +240,13 @@ func (t *TipMetadata) registerAsLatestValidatorBlock(latestValidatorBlock reacti if registered { t.isLatestValidatorBlock.Set(true) - ifLatestValidatorBlockChanged := func(_ *TipMetadata, latestValidatorBlock *TipMetadata) bool { - return latestValidatorBlock != t - } - + // Once the latestValidatorBlock is updated again (by another block), we need to reset the isLatestValidatorBlock + // variable. latestValidatorBlock.OnUpdateOnce(func(_ *TipMetadata, _ *TipMetadata) { t.isLatestValidatorBlock.Set(false) - }, ifLatestValidatorBlockChanged) + }, func(_ *TipMetadata, latestValidatorBlock *TipMetadata) bool { + return latestValidatorBlock != t + }) } return registered diff --git a/pkg/protocol/engine/tipmanager/v1/tipmanager.go b/pkg/protocol/engine/tipmanager/v1/tipmanager.go index 667dd8534..5d5d7d69c 100644 --- a/pkg/protocol/engine/tipmanager/v1/tipmanager.go +++ b/pkg/protocol/engine/tipmanager/v1/tipmanager.go @@ -196,8 +196,17 @@ func (t *TipManager) setupBlockMetadata(tipMetadata *TipMetadata) { // trackLatestValidatorBlock tracks the latest validator block and takes care of marking the corresponding TipMetadata. func (t *TipManager) trackLatestValidatorBlock(tipMetadata *TipMetadata) (teardown func()) { + if _, isValidationBlock := tipMetadata.Block().ValidationBlock(); !isValidationBlock { + return + } + + // We only track the validation blocks of validators that are tracked by the TipManager (via AddValidator). latestValidatorBlock, exists := t.latestValidatorBlocks.Get(tipMetadata.Block().ProtocolBlock().Header.IssuerID) - if !exists || !tipMetadata.registerAsLatestValidatorBlock(latestValidatorBlock) { + if !exists { + return + } + + if !tipMetadata.registerAsLatestValidatorBlock(latestValidatorBlock) { return } From e2b4628be618c639e22038bbdd9366fd9d0521ae Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Fri, 19 Jan 2024 21:11:55 +0800 Subject: [PATCH 08/13] Adjust TipManager to receive a CommitteeInSlot retriever and wire up events to start tracking of validators in TipManager --- .../engine/tipmanager/tests/testframework.go | 33 +++++++---- pkg/protocol/engine/tipmanager/tipmanager.go | 9 +-- pkg/protocol/engine/tipmanager/v1/provider.go | 10 +++- .../engine/tipmanager/v1/tipmanager.go | 55 ++++++++++++------- .../seatmanager/mock/mockseatmanager.go | 4 ++ 5 files changed, 75 insertions(+), 36 deletions(-) diff --git a/pkg/protocol/engine/tipmanager/tests/testframework.go b/pkg/protocol/engine/tipmanager/tests/testframework.go index 8e74c4cee..bc9b17a8a 100644 --- a/pkg/protocol/engine/tipmanager/tests/testframework.go +++ b/pkg/protocol/engine/tipmanager/tests/testframework.go @@ -7,11 +7,15 @@ import ( "github.com/stretchr/testify/require" "github.com/iotaledger/hive.go/ds" + "github.com/iotaledger/hive.go/kvstore/mapdb" "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/iota-core/pkg/core/account" "github.com/iotaledger/iota-core/pkg/model" "github.com/iotaledger/iota-core/pkg/protocol/engine/blocks" "github.com/iotaledger/iota-core/pkg/protocol/engine/tipmanager" tipmanagerv1 "github.com/iotaledger/iota-core/pkg/protocol/engine/tipmanager/v1" + "github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager/mock" + "github.com/iotaledger/iota-core/pkg/storage/prunable/epochstore" iotago "github.com/iotaledger/iota.go/v4" "github.com/iotaledger/iota.go/v4/builder" "github.com/iotaledger/iota.go/v4/tpkg" @@ -23,10 +27,11 @@ type TestFramework struct { blockIDsByAlias map[string]iotago.BlockID tipMetadataByAlias map[string]tipmanager.TipMetadata blocksByID map[iotago.BlockID]*blocks.Block - validatorByAlias map[string]iotago.AccountID test *testing.T time time.Time + manualPOA mock.ManualPOA + API iotago.API } @@ -35,10 +40,17 @@ func NewTestFramework(test *testing.T) *TestFramework { blockIDsByAlias: make(map[string]iotago.BlockID), tipMetadataByAlias: make(map[string]tipmanager.TipMetadata), blocksByID: make(map[iotago.BlockID]*blocks.Block), - validatorByAlias: make(map[string]iotago.AccountID), test: test, API: tpkg.ZeroCostTestAPI, time: time.Now(), + manualPOA: *mock.NewManualPOA(iotago.SingleVersionProvider(tpkg.ZeroCostTestAPI), + epochstore.NewStore[*account.Accounts]( + nil, + mapdb.NewMapDB(), + func(index iotago.EpochIndex) iotago.EpochIndex { return index }, + (*account.Accounts).Bytes, + account.AccountsFromBytes), + ), } t.blockIDsByAlias["Genesis"] = iotago.EmptyBlockID @@ -46,25 +58,24 @@ func NewTestFramework(test *testing.T) *TestFramework { t.Instance = tipmanagerv1.New(func(blockID iotago.BlockID) (block *blocks.Block, exists bool) { block, exists = t.blocksByID[blockID] return block, exists - }) + }, t.manualPOA.CommitteeInSlot) return t } func (t *TestFramework) Validator(alias string) iotago.AccountID { - validator, validatorExists := t.validatorByAlias[alias] - require.True(t.test, validatorExists) - - return validator + return t.manualPOA.AccountID(alias) } func (t *TestFramework) AddValidator(alias string) { - accountID := iotago.AccountID(tpkg.Rand32ByteArray()) - accountID.RegisterAlias(alias) + t.manualPOA.AddRandomAccount(alias) - t.validatorByAlias[alias] = accountID + seat, exists := t.manualPOA.GetSeat(alias) + if !exists { + panic("seat does not exist") + } - t.Instance.AddValidator(accountID) + t.Instance.AddSeat(seat) } func (t *TestFramework) AddBlock(alias string) tipmanager.TipMetadata { diff --git a/pkg/protocol/engine/tipmanager/tipmanager.go b/pkg/protocol/engine/tipmanager/tipmanager.go index 8093b8968..d3aa6ae4d 100644 --- a/pkg/protocol/engine/tipmanager/tipmanager.go +++ b/pkg/protocol/engine/tipmanager/tipmanager.go @@ -2,6 +2,7 @@ package tipmanager import ( "github.com/iotaledger/hive.go/runtime/module" + "github.com/iotaledger/iota-core/pkg/core/account" "github.com/iotaledger/iota-core/pkg/protocol/engine/blocks" iotago "github.com/iotaledger/iota.go/v4" ) @@ -27,11 +28,11 @@ type TipManager interface { // OnBlockAdded registers a callback that is triggered whenever a new Block was added to the TipManager. OnBlockAdded(handler func(block TipMetadata)) (unsubscribe func()) - // AddValidator adds a validator to the tracking of the TipManager. - AddValidator(accountID iotago.AccountID) (added bool) + // AddSeat adds a validator seat to the tracking of the TipManager. + AddSeat(seat account.SeatIndex) - // RemoveValidator removes a validator from the tracking of the TipManager. - RemoveValidator(accountID iotago.AccountID) (removed bool) + // RemoveSeat removes a validator seat from the tracking of the TipManager. + RemoveSeat(seat account.SeatIndex) // ValidationTips returns the validation tips of the TipManager (with an optional limit). ValidationTips(optAmount ...int) []TipMetadata diff --git a/pkg/protocol/engine/tipmanager/v1/provider.go b/pkg/protocol/engine/tipmanager/v1/provider.go index eb859133d..0a824884a 100644 --- a/pkg/protocol/engine/tipmanager/v1/provider.go +++ b/pkg/protocol/engine/tipmanager/v1/provider.go @@ -5,21 +5,29 @@ import ( "github.com/iotaledger/hive.go/runtime/event" "github.com/iotaledger/hive.go/runtime/module" "github.com/iotaledger/hive.go/runtime/workerpool" + "github.com/iotaledger/iota-core/pkg/core/account" "github.com/iotaledger/iota-core/pkg/protocol/engine" "github.com/iotaledger/iota-core/pkg/protocol/engine/tipmanager" + iotago "github.com/iotaledger/iota.go/v4" ) // NewProvider creates a new TipManager provider, that can be used to inject the component into an engine. func NewProvider() module.Provider[*engine.Engine, tipmanager.TipManager] { return module.Provide(func(e *engine.Engine) tipmanager.TipManager { - t := New(e.BlockCache.Block) + t := New(e.BlockCache.Block, e.SybilProtection.SeatManager().CommitteeInSlot) e.Constructed.OnTrigger(func() { tipWorker := e.Workers.CreatePool("AddTip", workerpool.WithWorkerCount(2)) + e.Events.Scheduler.BlockScheduled.Hook(lo.Void(t.AddBlock), event.WithWorkerPool(tipWorker)) e.Events.Scheduler.BlockSkipped.Hook(lo.Void(t.AddBlock), event.WithWorkerPool(tipWorker)) e.BlockCache.Evict.Hook(t.Evict) + e.Events.SeatManager.OnlineCommitteeSeatAdded.Hook(func(index account.SeatIndex, _ iotago.AccountID) { + t.AddSeat(index) + }) + e.Events.SeatManager.OnlineCommitteeSeatRemoved.Hook(t.RemoveSeat) + e.Events.TipManager.BlockAdded.LinkTo(t.blockAdded) t.TriggerInitialized() diff --git a/pkg/protocol/engine/tipmanager/v1/tipmanager.go b/pkg/protocol/engine/tipmanager/v1/tipmanager.go index 5d5d7d69c..61fd09b35 100644 --- a/pkg/protocol/engine/tipmanager/v1/tipmanager.go +++ b/pkg/protocol/engine/tipmanager/v1/tipmanager.go @@ -10,6 +10,7 @@ import ( "github.com/iotaledger/hive.go/runtime/event" "github.com/iotaledger/hive.go/runtime/module" "github.com/iotaledger/hive.go/runtime/syncutils" + "github.com/iotaledger/iota-core/pkg/core/account" "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" @@ -20,11 +21,14 @@ type TipManager struct { // retrieveBlock is a function that retrieves a Block from the Tangle. retrieveBlock func(blockID iotago.BlockID) (block *blocks.Block, exists bool) + // retrieveCommitteeInSlot is a function that retrieves the committee in a given slot. + retrieveCommitteeInSlot func(slot iotago.SlotIndex) (*account.SeatedAccounts, bool) + // tipMetadataStorage contains the TipMetadata of all Blocks that are managed by the TipManager. tipMetadataStorage *shrinkingmap.ShrinkingMap[iotago.SlotIndex, *shrinkingmap.ShrinkingMap[iotago.BlockID, *TipMetadata]] // latestValidatorBlocks contains a Variable for each validator that stores the latest validator block. - latestValidatorBlocks *shrinkingmap.ShrinkingMap[iotago.AccountID, reactive.Variable[*TipMetadata]] + latestValidatorBlocks *shrinkingmap.ShrinkingMap[account.SeatIndex, reactive.Variable[*TipMetadata]] // validatorTipSet contains the subset of blocks from the strong tip set that reference the latest validator block. validatorTipSet *randommap.RandomMap[iotago.BlockID, *TipMetadata] @@ -49,15 +53,19 @@ type TipManager struct { } // New creates a new TipManager. -func New(blockRetriever func(blockID iotago.BlockID) (block *blocks.Block, exists bool)) *TipManager { +func New( + blockRetriever func(blockID iotago.BlockID) (block *blocks.Block, exists bool), + retrieveCommitteeInSlot func(slot iotago.SlotIndex) (*account.SeatedAccounts, bool), +) *TipManager { t := &TipManager{ - retrieveBlock: blockRetriever, - tipMetadataStorage: shrinkingmap.New[iotago.SlotIndex, *shrinkingmap.ShrinkingMap[iotago.BlockID, *TipMetadata]](), - latestValidatorBlocks: shrinkingmap.New[iotago.AccountID, reactive.Variable[*TipMetadata]](), - validatorTipSet: randommap.New[iotago.BlockID, *TipMetadata](), - strongTipSet: randommap.New[iotago.BlockID, *TipMetadata](), - weakTipSet: randommap.New[iotago.BlockID, *TipMetadata](), - blockAdded: event.New1[tipmanager.TipMetadata](), + retrieveBlock: blockRetriever, + retrieveCommitteeInSlot: retrieveCommitteeInSlot, + tipMetadataStorage: shrinkingmap.New[iotago.SlotIndex, *shrinkingmap.ShrinkingMap[iotago.BlockID, *TipMetadata]](), + latestValidatorBlocks: shrinkingmap.New[account.SeatIndex, reactive.Variable[*TipMetadata]](), + validatorTipSet: randommap.New[iotago.BlockID, *TipMetadata](), + strongTipSet: randommap.New[iotago.BlockID, *TipMetadata](), + weakTipSet: randommap.New[iotago.BlockID, *TipMetadata](), + blockAdded: event.New1[tipmanager.TipMetadata](), } t.TriggerConstructed() @@ -89,23 +97,20 @@ func (t *TipManager) OnBlockAdded(handler func(block tipmanager.TipMetadata)) (u return t.blockAdded.Hook(handler).Unhook } -// AddValidator adds a validator to the tracking of the TipManager. -func (t *TipManager) AddValidator(accountID iotago.AccountID) (added bool) { - _, added = t.latestValidatorBlocks.GetOrCreate(accountID, func() reactive.Variable[*TipMetadata] { +// AddSeat adds a validator to the tracking of the TipManager. +func (t *TipManager) AddSeat(seat account.SeatIndex) { + t.latestValidatorBlocks.GetOrCreate(seat, func() reactive.Variable[*TipMetadata] { return reactive.NewVariable[*TipMetadata]() }) - - return added } -// RemoveValidator removes a validator from the tracking of the TipManager. -func (t *TipManager) RemoveValidator(accountID iotago.AccountID) (removed bool) { - latestValidatorBlock, removed := t.latestValidatorBlocks.DeleteAndReturn(accountID) +// RemoveSeat removes a validator from the tracking of the TipManager. +func (t *TipManager) RemoveSeat(seat account.SeatIndex) { + latestValidatorBlock, removed := t.latestValidatorBlocks.DeleteAndReturn(seat) if removed { latestValidatorBlock.Set(nil) } - return removed } func (t *TipManager) ValidationTips(optAmount ...int) []tipmanager.TipMetadata { @@ -200,8 +205,18 @@ func (t *TipManager) trackLatestValidatorBlock(tipMetadata *TipMetadata) (teardo return } - // We only track the validation blocks of validators that are tracked by the TipManager (via AddValidator). - latestValidatorBlock, exists := t.latestValidatorBlocks.Get(tipMetadata.Block().ProtocolBlock().Header.IssuerID) + committee, exists := t.retrieveCommitteeInSlot(tipMetadata.Block().ID().Slot()) + if !exists { + return + } + + seat, exists := committee.GetSeat(tipMetadata.Block().ProtocolBlock().Header.IssuerID) + if !exists { + return + } + + // We only track the validation blocks of validators that are tracked by the TipManager (via AddSeat). + latestValidatorBlock, exists := t.latestValidatorBlocks.Get(seat) if !exists { return } diff --git a/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go b/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go index edd0832b2..16bbe6d09 100644 --- a/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go +++ b/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go @@ -108,6 +108,10 @@ func (m *ManualPOA) AccountID(alias string) iotago.AccountID { return id } +func (m *ManualPOA) GetSeat(alias string) (account.SeatIndex, bool) { + return m.committee.GetSeat(m.AccountID(alias)) +} + func (m *ManualPOA) SetOnline(aliases ...string) { for _, alias := range aliases { seat, exists := m.committee.GetSeat(m.AccountID(alias)) From 7f24d25697236ada6983fbdbee913a40ad4d334b Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Fri, 19 Jan 2024 21:15:56 +0800 Subject: [PATCH 09/13] Rename validator to validation block --- .../engine/tipmanager/v1/tip_metadata.go | 74 +++++++++---------- .../engine/tipmanager/v1/tipmanager.go | 42 +++++------ 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/pkg/protocol/engine/tipmanager/v1/tip_metadata.go b/pkg/protocol/engine/tipmanager/v1/tip_metadata.go index e9a8c0781..9fcbcf67f 100644 --- a/pkg/protocol/engine/tipmanager/v1/tip_metadata.go +++ b/pkg/protocol/engine/tipmanager/v1/tip_metadata.go @@ -49,12 +49,12 @@ type TipMetadata struct { // that is connected to the tips. isReferencedByTips reactive.Variable[bool] - // isLatestValidatorBlock is true if the block is the latest block of a validator. - isLatestValidatorBlock reactive.Variable[bool] + // isLatestValidationBlock is true if the block is the latest block of a validator. + isLatestValidationBlock reactive.Variable[bool] - // referencesLatestValidatorBlock is true if the block is the latest validator block or has parents that reference + // referencesLatestValidationBlock is true if the block is the latest validator block or has parents that reference // the latest validator block. - referencesLatestValidatorBlock reactive.Variable[bool] + referencesLatestValidationBlock reactive.Variable[bool] // isStrongTip is true if the block is a strong tip pool member and is not strongly referenced by other tips. isStrongTip reactive.Variable[bool] @@ -62,8 +62,8 @@ type TipMetadata struct { // isWeakTip is true if the block is a weak tip pool member and is not referenced by other tips. isWeakTip reactive.Variable[bool] - // isValidatorTip is true if the block is a strong tip and references the latest validator block. - isValidatorTip reactive.Variable[bool] + // isValidationTip is true if the block is a strong tip and references the latest validator block. + isValidationTip reactive.Variable[bool] // isMarkedOrphaned is true if the liveness threshold has been reached and the block was not accepted. isMarkedOrphaned reactive.Variable[bool] @@ -97,28 +97,28 @@ type TipMetadata struct { // weaklyOrphanedWeakParents holds the number of weak parents that are weakly orphaned. weaklyOrphanedWeakParents reactive.Counter[bool] - // parentsReferencingLatestValidatorBlock holds the number of parents that reference the latest validator block. - parentsReferencingLatestValidatorBlock reactive.Counter[bool] + // parentsReferencingLatestValidationBlock holds the number of parents that reference the latest validator block. + parentsReferencingLatestValidationBlock reactive.Counter[bool] } // NewBlockMetadata creates a new TipMetadata instance. func NewBlockMetadata(block *blocks.Block) *TipMetadata { t := &TipMetadata{ - block: block, - tipPool: reactive.NewVariable[tipmanager.TipPool](tipmanager.TipPool.Max), - livenessThresholdReached: reactive.NewEvent(), - evicted: reactive.NewEvent(), - isLatestValidatorBlock: reactive.NewVariable[bool](), - stronglyConnectedStrongChildren: reactive.NewCounter[bool](), - connectedWeakChildren: reactive.NewCounter[bool](), - stronglyOrphanedStrongParents: reactive.NewCounter[bool](), - weaklyOrphanedWeakParents: reactive.NewCounter[bool](), - parentsReferencingLatestValidatorBlock: reactive.NewCounter[bool](), + block: block, + tipPool: reactive.NewVariable[tipmanager.TipPool](tipmanager.TipPool.Max), + livenessThresholdReached: reactive.NewEvent(), + evicted: reactive.NewEvent(), + isLatestValidationBlock: reactive.NewVariable[bool](), + stronglyConnectedStrongChildren: reactive.NewCounter[bool](), + connectedWeakChildren: reactive.NewCounter[bool](), + stronglyOrphanedStrongParents: reactive.NewCounter[bool](), + weaklyOrphanedWeakParents: reactive.NewCounter[bool](), + parentsReferencingLatestValidationBlock: reactive.NewCounter[bool](), } - t.referencesLatestValidatorBlock = reactive.NewDerivedVariable2(func(_ bool, isLatestValidatorBlock bool, parentsReferencingLatestValidatorBlock int) bool { - return isLatestValidatorBlock || parentsReferencingLatestValidatorBlock > 0 - }, t.isLatestValidatorBlock, t.parentsReferencingLatestValidatorBlock) + 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 @@ -180,9 +180,9 @@ func NewBlockMetadata(block *blocks.Block) *TipMetadata { return isWeakTipPoolMember && !isReferencedByTips }, t.isWeakTipPoolMember, t.isReferencedByTips) - t.isValidatorTip = reactive.NewDerivedVariable2(func(_ bool, isStrongTip bool, referencesLatestValidatorBlock bool) bool { - return isStrongTip && referencesLatestValidatorBlock - }, t.isStrongTip, t.referencesLatestValidatorBlock) + t.isValidationTip = reactive.NewDerivedVariable2(func(_ bool, isStrongTip bool, referencesLatestValidationBlock bool) bool { + return isStrongTip && referencesLatestValidationBlock + }, t.isStrongTip, t.referencesLatestValidationBlock) return t } @@ -227,25 +227,25 @@ func (t *TipMetadata) Evicted() reactive.Event { return t.evicted } -// registerAsLatestValidatorBlock registers the TipMetadata as the latest validator block if it is newer than the -// currently registered block and sets the isLatestValidatorBlock variable accordingly. The function returns true if the +// 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) registerAsLatestValidatorBlock(latestValidatorBlock reactive.Variable[*TipMetadata]) (registered bool) { - latestValidatorBlock.Compute(func(currentLatestValidatorBlock *TipMetadata) *TipMetadata { - registered = currentLatestValidatorBlock == nil || currentLatestValidatorBlock.block.IssuingTime().Before(t.block.IssuingTime()) +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, currentLatestValidatorBlock) + return lo.Cond(registered, t, currentLatestValidationBlock) }) if registered { - t.isLatestValidatorBlock.Set(true) + t.isLatestValidationBlock.Set(true) - // Once the latestValidatorBlock is updated again (by another block), we need to reset the isLatestValidatorBlock + // Once the latestValidationBlock is updated again (by another block), we need to reset the isLatestValidationBlock // variable. - latestValidatorBlock.OnUpdateOnce(func(_ *TipMetadata, _ *TipMetadata) { - t.isLatestValidatorBlock.Set(false) - }, func(_ *TipMetadata, latestValidatorBlock *TipMetadata) bool { - return latestValidatorBlock != t + latestValidationBlock.OnUpdateOnce(func(_ *TipMetadata, _ *TipMetadata) { + t.isLatestValidationBlock.Set(false) + }, func(_ *TipMetadata, latestValidationBlock *TipMetadata) bool { + return latestValidationBlock != t }) } @@ -255,7 +255,7 @@ func (t *TipMetadata) registerAsLatestValidatorBlock(latestValidatorBlock reacti // 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.parentsReferencingLatestValidatorBlock.Monitor(strongParent.referencesLatestValidatorBlock) + 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) diff --git a/pkg/protocol/engine/tipmanager/v1/tipmanager.go b/pkg/protocol/engine/tipmanager/v1/tipmanager.go index 61fd09b35..4206e04e8 100644 --- a/pkg/protocol/engine/tipmanager/v1/tipmanager.go +++ b/pkg/protocol/engine/tipmanager/v1/tipmanager.go @@ -27,11 +27,11 @@ type TipManager struct { // tipMetadataStorage contains the TipMetadata of all Blocks that are managed by the TipManager. tipMetadataStorage *shrinkingmap.ShrinkingMap[iotago.SlotIndex, *shrinkingmap.ShrinkingMap[iotago.BlockID, *TipMetadata]] - // latestValidatorBlocks contains a Variable for each validator that stores the latest validator block. - latestValidatorBlocks *shrinkingmap.ShrinkingMap[account.SeatIndex, reactive.Variable[*TipMetadata]] + // latestValidationBlocks contains a Variable for each validator that stores the latest validation block. + latestValidationBlocks *shrinkingmap.ShrinkingMap[account.SeatIndex, reactive.Variable[*TipMetadata]] - // validatorTipSet contains the subset of blocks from the strong tip set that reference the latest validator block. - validatorTipSet *randommap.RandomMap[iotago.BlockID, *TipMetadata] + // validationTipSet contains the subset of blocks from the strong tip set that reference the latest validation block. + validationTipSet *randommap.RandomMap[iotago.BlockID, *TipMetadata] // strongTipSet contains the blocks of the strong tip pool that have no referencing children. strongTipSet *randommap.RandomMap[iotago.BlockID, *TipMetadata] @@ -61,8 +61,8 @@ func New( retrieveBlock: blockRetriever, retrieveCommitteeInSlot: retrieveCommitteeInSlot, tipMetadataStorage: shrinkingmap.New[iotago.SlotIndex, *shrinkingmap.ShrinkingMap[iotago.BlockID, *TipMetadata]](), - latestValidatorBlocks: shrinkingmap.New[account.SeatIndex, reactive.Variable[*TipMetadata]](), - validatorTipSet: randommap.New[iotago.BlockID, *TipMetadata](), + latestValidationBlocks: shrinkingmap.New[account.SeatIndex, reactive.Variable[*TipMetadata]](), + validationTipSet: randommap.New[iotago.BlockID, *TipMetadata](), strongTipSet: randommap.New[iotago.BlockID, *TipMetadata](), weakTipSet: randommap.New[iotago.BlockID, *TipMetadata](), blockAdded: event.New1[tipmanager.TipMetadata](), @@ -99,22 +99,22 @@ func (t *TipManager) OnBlockAdded(handler func(block tipmanager.TipMetadata)) (u // AddSeat adds a validator to the tracking of the TipManager. func (t *TipManager) AddSeat(seat account.SeatIndex) { - t.latestValidatorBlocks.GetOrCreate(seat, func() reactive.Variable[*TipMetadata] { + t.latestValidationBlocks.GetOrCreate(seat, func() reactive.Variable[*TipMetadata] { return reactive.NewVariable[*TipMetadata]() }) } // RemoveSeat removes a validator from the tracking of the TipManager. func (t *TipManager) RemoveSeat(seat account.SeatIndex) { - latestValidatorBlock, removed := t.latestValidatorBlocks.DeleteAndReturn(seat) + latestValidationBlock, removed := t.latestValidationBlocks.DeleteAndReturn(seat) if removed { - latestValidatorBlock.Set(nil) + latestValidationBlock.Set(nil) } } func (t *TipManager) ValidationTips(optAmount ...int) []tipmanager.TipMetadata { - return t.selectTips(t.validatorTipSet, optAmount...) + return t.selectTips(t.validationTipSet, optAmount...) } // StrongTips returns the strong tips of the TipManager (with an optional limit). @@ -161,14 +161,14 @@ func (t *TipManager) Shutdown() { // setupBlockMetadata sets up the behavior of the given Block. func (t *TipManager) setupBlockMetadata(tipMetadata *TipMetadata) { tipMetadata.isStrongTipPoolMember.WithNonEmptyValue(func(_ bool) func() { - return t.trackLatestValidatorBlock(tipMetadata) + return t.trackLatestValidationBlock(tipMetadata) }) - tipMetadata.isValidatorTip.OnUpdate(func(_ bool, isValidatorTip bool) { - if isValidatorTip { - t.validatorTipSet.Set(tipMetadata.ID(), tipMetadata) + tipMetadata.isValidationTip.OnUpdate(func(_ bool, isValidationTip bool) { + if isValidationTip { + t.validationTipSet.Set(tipMetadata.ID(), tipMetadata) } else { - t.validatorTipSet.Delete(tipMetadata.ID()) + t.validationTipSet.Delete(tipMetadata.ID()) } }) @@ -199,8 +199,8 @@ func (t *TipManager) setupBlockMetadata(tipMetadata *TipMetadata) { t.blockAdded.Trigger(tipMetadata) } -// trackLatestValidatorBlock tracks the latest validator block and takes care of marking the corresponding TipMetadata. -func (t *TipManager) trackLatestValidatorBlock(tipMetadata *TipMetadata) (teardown func()) { +// trackLatestValidationBlock tracks the latest validator block and takes care of marking the corresponding TipMetadata. +func (t *TipManager) trackLatestValidationBlock(tipMetadata *TipMetadata) (teardown func()) { if _, isValidationBlock := tipMetadata.Block().ValidationBlock(); !isValidationBlock { return } @@ -216,19 +216,19 @@ func (t *TipManager) trackLatestValidatorBlock(tipMetadata *TipMetadata) (teardo } // We only track the validation blocks of validators that are tracked by the TipManager (via AddSeat). - latestValidatorBlock, exists := t.latestValidatorBlocks.Get(seat) + latestValidationBlock, exists := t.latestValidationBlocks.Get(seat) if !exists { return } - if !tipMetadata.registerAsLatestValidatorBlock(latestValidatorBlock) { + if !tipMetadata.registerAsLatestValidationBlock(latestValidationBlock) { return } // reset the latest validator block to nil if we are still the latest one during teardown return func() { - latestValidatorBlock.Compute(func(latestValidatorBlock *TipMetadata) *TipMetadata { - return lo.Cond(latestValidatorBlock == tipMetadata, nil, latestValidatorBlock) + latestValidationBlock.Compute(func(latestValidationBlock *TipMetadata) *TipMetadata { + return lo.Cond(latestValidationBlock == tipMetadata, nil, latestValidationBlock) }) } } From 1d52744bcaeb501e54b3bcbd4216d75b83357cea Mon Sep 17 00:00:00 2001 From: Hans Moog <3293976+hmoog@users.noreply.github.com> Date: Mon, 22 Jan 2024 21:36:22 +0100 Subject: [PATCH 10/13] Fix: fixed test --- .../consensus/blockgadget/gadget_test.go | 3 +- .../engine/tipmanager/tests/testframework.go | 17 ++++++----- .../tipmanager/tests/tipmanager_test.go | 6 ++-- .../seatmanager/mock/mockseatmanager.go | 30 +++++++++++-------- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/pkg/protocol/engine/consensus/blockgadget/gadget_test.go b/pkg/protocol/engine/consensus/blockgadget/gadget_test.go index 982731ca9..be92435f6 100644 --- a/pkg/protocol/engine/consensus/blockgadget/gadget_test.go +++ b/pkg/protocol/engine/consensus/blockgadget/gadget_test.go @@ -32,8 +32,7 @@ func TestBlockGadget(t *testing.T) { tf.Events.BlockPreConfirmed.Hook(checkOrder(&expectedPreConfirmationOrder, "pre-confirmation")) tf.Events.BlockConfirmed.Hook(checkOrder(&expectedConfirmationOrder, "confirmation")) - tf.SeatManager.AddRandomAccount("A") - tf.SeatManager.AddRandomAccount("B") + tf.SeatManager.AddRandomAccounts("A", "B") tf.SeatManager.SetOnline("A") tf.SeatManager.SetOnline("B") diff --git a/pkg/protocol/engine/tipmanager/tests/testframework.go b/pkg/protocol/engine/tipmanager/tests/testframework.go index bc9b17a8a..f71db4a63 100644 --- a/pkg/protocol/engine/tipmanager/tests/testframework.go +++ b/pkg/protocol/engine/tipmanager/tests/testframework.go @@ -67,19 +67,22 @@ func (t *TestFramework) Validator(alias string) iotago.AccountID { return t.manualPOA.AccountID(alias) } -func (t *TestFramework) AddValidator(alias string) { - t.manualPOA.AddRandomAccount(alias) +func (t *TestFramework) AddValidators(aliases ...string) { + t.manualPOA.AddRandomAccounts(aliases...) - seat, exists := t.manualPOA.GetSeat(alias) - if !exists { - panic("seat does not exist") - } + for _, alias := range aliases { + seat, exists := t.manualPOA.GetSeat(alias) + if !exists { + panic("seat does not exist") + } - t.Instance.AddSeat(seat) + t.Instance.AddSeat(seat) + } } func (t *TestFramework) AddBlock(alias string) tipmanager.TipMetadata { t.tipMetadataByAlias[alias] = t.Instance.AddBlock(t.Block(alias)) + t.tipMetadataByAlias[alias].ID().RegisterAlias(alias) return t.tipMetadataByAlias[alias] } diff --git a/pkg/protocol/engine/tipmanager/tests/tipmanager_test.go b/pkg/protocol/engine/tipmanager/tests/tipmanager_test.go index ce7a066b4..d141da05d 100644 --- a/pkg/protocol/engine/tipmanager/tests/tipmanager_test.go +++ b/pkg/protocol/engine/tipmanager/tests/tipmanager_test.go @@ -62,6 +62,8 @@ func Test_Orphanage(t *testing.T) { func Test_ValidationTips(t *testing.T) { tf := NewTestFramework(t) + tf.AddValidators("validatorA", "validatorB") + { tf.CreateBasicBlock("1", map[iotago.ParentsType][]string{ iotago.StrongParentType: {"Genesis"}, @@ -78,8 +80,6 @@ func Test_ValidationTips(t *testing.T) { // Add validation tip for validatorA. { - tf.AddValidator("validatorA") - tf.CreateValidationBlock("3", map[iotago.ParentsType][]string{ iotago.StrongParentType: {"2"}, }, func(blockBuilder *builder.ValidationBlockBuilder) { @@ -94,8 +94,6 @@ func Test_ValidationTips(t *testing.T) { // Add validation tip for validatorB. { - tf.AddValidator("validatorB") - tf.CreateValidationBlock("4", map[iotago.ParentsType][]string{ iotago.StrongParentType: {"1"}, }, func(blockBuilder *builder.ValidationBlockBuilder) { diff --git a/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go b/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go index 16bbe6d09..84fcc05b0 100644 --- a/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go +++ b/pkg/protocol/sybilprotection/seatmanager/mock/mockseatmanager.go @@ -58,18 +58,24 @@ func NewManualPOAProvider() module.Provider[*engine.Engine, seatmanager.SeatMana }) } -func (m *ManualPOA) AddRandomAccount(alias string) iotago.AccountID { - id := iotago.AccountID(tpkg.Rand32ByteArray()) - id.RegisterAlias(alias) - if err := m.accounts.Set(id, &account.Pool{ // We don't care about pools with PoA, but need to set something to avoid division by zero errors. - PoolStake: 1, - ValidatorStake: 1, - FixedCost: 1, - }); err != nil { - panic(err) - } +func (m *ManualPOA) AddRandomAccounts(aliases ...string) (accountIDs []iotago.AccountID) { + accountIDs = make([]iotago.AccountID, len(aliases)) + + for i, alias := range aliases { + id := iotago.AccountID(tpkg.Rand32ByteArray()) + id.RegisterAlias(alias) + if err := m.accounts.Set(id, &account.Pool{ // We don't care about pools with PoA, but need to set something to avoid division by zero errors. + PoolStake: 1, + ValidatorStake: 1, + FixedCost: 1, + }); err != nil { + panic(err) + } - m.aliases.Set(alias, id) + m.aliases.Set(alias, id) + + accountIDs[i] = id + } m.committee = m.accounts.SeatedAccounts(m.accounts.IDs()...) @@ -77,7 +83,7 @@ func (m *ManualPOA) AddRandomAccount(alias string) iotago.AccountID { panic(err) } - return id + return accountIDs } func (m *ManualPOA) AddAccount(id iotago.AccountID, alias string) iotago.AccountID { From 650d06f93b7a75428cdf5077917b1219b497d9ab Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Tue, 30 Jan 2024 10:17:27 +0100 Subject: [PATCH 11/13] Fix tip selection to select corrent number of unique tips. --- pkg/protocol/engine/tipselection/v1/tip_selection.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/protocol/engine/tipselection/v1/tip_selection.go b/pkg/protocol/engine/tipselection/v1/tip_selection.go index 9a362a9c8..0b05b9f85 100644 --- a/pkg/protocol/engine/tipselection/v1/tip_selection.go +++ b/pkg/protocol/engine/tipselection/v1/tip_selection.go @@ -214,7 +214,7 @@ func (t *TipSelection) likedInsteadReferences(likedConflicts ds.Set[iotago.Trans return references, updatedLikedConflicts, nil } -// collectReferences collects tips from a tip selector (and calls the callback for each tip) until the amount of +// collectReferences collects tips from a tip selector (and calls the callback for each tip) until the number of // references of the given type is reached. func (t *TipSelection) collectReferences(callback func(tipmanager.TipMetadata), tipSelectorsAmount ...*types.Tuple[func(optAmount ...int) []tipmanager.TipMetadata, int]) { seenTips := ds.NewSet[iotago.BlockID]() @@ -236,9 +236,14 @@ func (t *TipSelection) collectReferences(callback func(tipmanager.TipMetadata), return uniqueTips } - // We select the desired amount of tips from all given tip selectors, respectively. + accumulatedTipAmount := 0 + // We select the desired number of tips from all given tip selectors, respectively. for _, tipSelectorAmount := range tipSelectorsAmount { - for tipCandidates := selectUniqueTips(tipSelectorAmount.A, tipSelectorAmount.B); len(tipCandidates) != 0; tipCandidates = selectUniqueTips(tipSelectorAmount.A, tipSelectorAmount.B) { + // Make sure we select the total number of unique tips and not just the number of tips from the given tip pool, + // because of how selectUniqueTips works. + accumulatedTipAmount += tipSelectorAmount.B + + for tipCandidates := selectUniqueTips(tipSelectorAmount.A, accumulatedTipAmount); len(tipCandidates) != 0; tipCandidates = selectUniqueTips(tipSelectorAmount.A, accumulatedTipAmount) { for _, tip := range tipCandidates { callback(tip) } From cddc16fef05b4bef5550797db97446e9f740dcb3 Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:48:28 +0100 Subject: [PATCH 12/13] Fix tip selection to select correct number of tips --- .../engine/tipselection/v1/tip_selection.go | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/pkg/protocol/engine/tipselection/v1/tip_selection.go b/pkg/protocol/engine/tipselection/v1/tip_selection.go index 0b05b9f85..88db07991 100644 --- a/pkg/protocol/engine/tipselection/v1/tip_selection.go +++ b/pkg/protocol/engine/tipselection/v1/tip_selection.go @@ -125,6 +125,8 @@ func (t *TipSelection) SelectTips(amount int) (references model.ParentReferences previousLikedInsteadConflicts = updatedLikedInsteadConflicts } + }, func() int { + return len(references[iotago.StrongParentType]) }, // We select one validation tip as a strong parent. This is a security step to ensure that the tangle maintains // acceptance by stitching together validation blocks. @@ -140,6 +142,8 @@ func (t *TipSelection) SelectTips(amount int) (references model.ParentReferences } else if !shallowLikesParents.Has(tip.ID()) { references[iotago.WeakParentType] = append(references[iotago.WeakParentType], tip.ID()) } + }, func() int { + return len(references[iotago.WeakParentType]) }, types.NewTuple[func(optAmount ...int) []tipmanager.TipMetadata, int](t.tipManager.WeakTips, t.optMaxWeakReferences)) return nil @@ -216,17 +220,17 @@ func (t *TipSelection) likedInsteadReferences(likedConflicts ds.Set[iotago.Trans // collectReferences collects tips from a tip selector (and calls the callback for each tip) until the number of // references of the given type is reached. -func (t *TipSelection) collectReferences(callback func(tipmanager.TipMetadata), tipSelectorsAmount ...*types.Tuple[func(optAmount ...int) []tipmanager.TipMetadata, int]) { +func (t *TipSelection) collectReferences(callback func(tipmanager.TipMetadata), referencesCountCallback func() int, tipSelectorsAmount ...*types.Tuple[func(optAmount ...int) []tipmanager.TipMetadata, int]) { seenTips := ds.NewSet[iotago.BlockID]() // selectUniqueTips selects 'amount' unique tips from the given tip selector. - selectUniqueTips := func(tipSelector func(optAmount ...int) []tipmanager.TipMetadata, amount int) (uniqueTips []tipmanager.TipMetadata) { - if amount > 0 { - for _, tip := range tipSelector(amount + seenTips.Size()) { + selectUniqueTips := func(tipSelector func(optAmount ...int) []tipmanager.TipMetadata, currentReferencesCount int, targetAmount int) (uniqueTips []tipmanager.TipMetadata) { + if targetAmount > 0 { + for _, tip := range tipSelector(targetAmount + seenTips.Size()) { if seenTips.Add(tip.ID()) { uniqueTips = append(uniqueTips, tip) - if len(uniqueTips) == amount { + if currentReferencesCount+len(uniqueTips) == targetAmount { break } } @@ -243,10 +247,22 @@ func (t *TipSelection) collectReferences(callback func(tipmanager.TipMetadata), // because of how selectUniqueTips works. accumulatedTipAmount += tipSelectorAmount.B - for tipCandidates := selectUniqueTips(tipSelectorAmount.A, accumulatedTipAmount); len(tipCandidates) != 0; tipCandidates = selectUniqueTips(tipSelectorAmount.A, accumulatedTipAmount) { + tipCandidates := selectUniqueTips(tipSelectorAmount.A, referencesCountCallback(), accumulatedTipAmount) + + // We exit the loop in two cases: + // 1. When we've seen all the tips and there are no more unique tips to process (len(tipCandidates) != 0). + // 2. When we've successfully selected the desired number of tips and added them to the references (referencesCountCallback() >= accumulatedTipAmount). + for len(tipCandidates) != 0 { for _, tip := range tipCandidates { callback(tip) } + + referencesCount := referencesCountCallback() + if referencesCount >= accumulatedTipAmount { + break + } + + tipCandidates = selectUniqueTips(tipSelectorAmount.A, referencesCount, accumulatedTipAmount) } } } From 3127aeeafb10c3015678a79a11e2579fe9d51c7a Mon Sep 17 00:00:00 2001 From: Piotr Macek <4007944+piotrm50@users.noreply.github.com> Date: Thu, 1 Feb 2024 09:24:14 +0100 Subject: [PATCH 13/13] Fix unit test after merging develop --- pkg/protocol/engine/tipmanager/tests/testframework.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/protocol/engine/tipmanager/tests/testframework.go b/pkg/protocol/engine/tipmanager/tests/testframework.go index f71db4a63..a4503b99b 100644 --- a/pkg/protocol/engine/tipmanager/tests/testframework.go +++ b/pkg/protocol/engine/tipmanager/tests/testframework.go @@ -44,12 +44,12 @@ func NewTestFramework(test *testing.T) *TestFramework { API: tpkg.ZeroCostTestAPI, time: time.Now(), manualPOA: *mock.NewManualPOA(iotago.SingleVersionProvider(tpkg.ZeroCostTestAPI), - epochstore.NewStore[*account.Accounts]( + epochstore.NewStore[*account.SeatedAccounts]( nil, mapdb.NewMapDB(), func(index iotago.EpochIndex) iotago.EpochIndex { return index }, - (*account.Accounts).Bytes, - account.AccountsFromBytes), + (*account.SeatedAccounts).Bytes, + account.SeatedAccountsFromBytes), ), }