Skip to content
This repository was archived by the owner on Jan 24, 2025. It is now read-only.

Commit aa124fe

Browse files
authored
Merge pull request #673 from iotaledger/feat/validator-tipset
Feat: Add validator tip set
2 parents c91cb76 + 3127aee commit aa124fe

File tree

10 files changed

+432
-55
lines changed

10 files changed

+432
-55
lines changed

Diff for: pkg/protocol/engine/consensus/blockgadget/gadget_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ func TestBlockGadget(t *testing.T) {
3232
tf.Events.BlockPreConfirmed.Hook(checkOrder(&expectedPreConfirmationOrder, "pre-confirmation"))
3333
tf.Events.BlockConfirmed.Hook(checkOrder(&expectedConfirmationOrder, "confirmation"))
3434

35-
tf.SeatManager.AddRandomAccount("A")
36-
tf.SeatManager.AddRandomAccount("B")
35+
tf.SeatManager.AddRandomAccounts("A", "B")
3736

3837
tf.SeatManager.SetOnline("A")
3938
tf.SeatManager.SetOnline("B")

Diff for: pkg/protocol/engine/tipmanager/tests/testframework.go

+81-3
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ import (
77
"github.com/stretchr/testify/require"
88

99
"github.com/iotaledger/hive.go/ds"
10+
"github.com/iotaledger/hive.go/kvstore/mapdb"
1011
"github.com/iotaledger/hive.go/lo"
12+
"github.com/iotaledger/iota-core/pkg/core/account"
1113
"github.com/iotaledger/iota-core/pkg/model"
1214
"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
1315
"github.com/iotaledger/iota-core/pkg/protocol/engine/tipmanager"
1416
tipmanagerv1 "github.com/iotaledger/iota-core/pkg/protocol/engine/tipmanager/v1"
17+
"github.com/iotaledger/iota-core/pkg/protocol/sybilprotection/seatmanager/mock"
18+
"github.com/iotaledger/iota-core/pkg/storage/prunable/epochstore"
1519
iotago "github.com/iotaledger/iota.go/v4"
1620
"github.com/iotaledger/iota.go/v4/builder"
1721
"github.com/iotaledger/iota.go/v4/tpkg"
@@ -24,6 +28,9 @@ type TestFramework struct {
2428
tipMetadataByAlias map[string]tipmanager.TipMetadata
2529
blocksByID map[iotago.BlockID]*blocks.Block
2630
test *testing.T
31+
time time.Time
32+
33+
manualPOA mock.ManualPOA
2734

2835
API iotago.API
2936
}
@@ -35,27 +42,90 @@ func NewTestFramework(test *testing.T) *TestFramework {
3542
blocksByID: make(map[iotago.BlockID]*blocks.Block),
3643
test: test,
3744
API: tpkg.ZeroCostTestAPI,
45+
time: time.Now(),
46+
manualPOA: *mock.NewManualPOA(iotago.SingleVersionProvider(tpkg.ZeroCostTestAPI),
47+
epochstore.NewStore[*account.SeatedAccounts](
48+
nil,
49+
mapdb.NewMapDB(),
50+
func(index iotago.EpochIndex) iotago.EpochIndex { return index },
51+
(*account.SeatedAccounts).Bytes,
52+
account.SeatedAccountsFromBytes),
53+
),
3854
}
3955

4056
t.blockIDsByAlias["Genesis"] = iotago.EmptyBlockID
4157

4258
t.Instance = tipmanagerv1.New(func(blockID iotago.BlockID) (block *blocks.Block, exists bool) {
4359
block, exists = t.blocksByID[blockID]
4460
return block, exists
45-
})
61+
}, t.manualPOA.CommitteeInSlot)
4662

4763
return t
4864
}
4965

66+
func (t *TestFramework) Validator(alias string) iotago.AccountID {
67+
return t.manualPOA.AccountID(alias)
68+
}
69+
70+
func (t *TestFramework) AddValidators(aliases ...string) {
71+
t.manualPOA.AddRandomAccounts(aliases...)
72+
73+
for _, alias := range aliases {
74+
seat, exists := t.manualPOA.GetSeat(alias)
75+
if !exists {
76+
panic("seat does not exist")
77+
}
78+
79+
t.Instance.AddSeat(seat)
80+
}
81+
}
82+
5083
func (t *TestFramework) AddBlock(alias string) tipmanager.TipMetadata {
5184
t.tipMetadataByAlias[alias] = t.Instance.AddBlock(t.Block(alias))
85+
t.tipMetadataByAlias[alias].ID().RegisterAlias(alias)
5286

5387
return t.tipMetadataByAlias[alias]
5488
}
5589

56-
func (t *TestFramework) CreateBlock(alias string, parents map[iotago.ParentsType][]string, optBlockBuilder ...func(*builder.BasicBlockBuilder)) *blocks.Block {
90+
func (t *TestFramework) CreateBasicBlock(alias string, parents map[iotago.ParentsType][]string, optBlockBuilder ...func(*builder.BasicBlockBuilder)) *blocks.Block {
5791
blockBuilder := builder.NewBasicBlockBuilder(t.API)
58-
blockBuilder.IssuingTime(time.Now())
92+
93+
// Make sure that blocks don't have the same timestamp.
94+
t.time = t.time.Add(1)
95+
blockBuilder.IssuingTime(t.time)
96+
97+
if strongParents, strongParentsExist := parents[iotago.StrongParentType]; strongParentsExist {
98+
blockBuilder.StrongParents(lo.Map(strongParents, t.BlockID))
99+
}
100+
if weakParents, weakParentsExist := parents[iotago.WeakParentType]; weakParentsExist {
101+
blockBuilder.WeakParents(lo.Map(weakParents, t.BlockID))
102+
}
103+
if shallowLikeParents, shallowLikeParentsExist := parents[iotago.ShallowLikeParentType]; shallowLikeParentsExist {
104+
blockBuilder.ShallowLikeParents(lo.Map(shallowLikeParents, t.BlockID))
105+
}
106+
107+
if len(optBlockBuilder) > 0 {
108+
optBlockBuilder[0](blockBuilder)
109+
}
110+
111+
block, err := blockBuilder.Build()
112+
require.NoError(t.test, err)
113+
114+
modelBlock, err := model.BlockFromBlock(block)
115+
require.NoError(t.test, err)
116+
117+
t.blocksByID[modelBlock.ID()] = blocks.NewBlock(modelBlock)
118+
t.blockIDsByAlias[alias] = modelBlock.ID()
119+
120+
return t.blocksByID[modelBlock.ID()]
121+
}
122+
123+
func (t *TestFramework) CreateValidationBlock(alias string, parents map[iotago.ParentsType][]string, optBlockBuilder ...func(blockBuilder *builder.ValidationBlockBuilder)) *blocks.Block {
124+
blockBuilder := builder.NewValidationBlockBuilder(t.API)
125+
126+
// Make sure that blocks don't have the same timestamp.
127+
t.time = t.time.Add(1)
128+
blockBuilder.IssuingTime(t.time)
59129

60130
if strongParents, strongParentsExist := parents[iotago.StrongParentType]; strongParentsExist {
61131
blockBuilder.StrongParents(lo.Map(strongParents, t.BlockID))
@@ -115,6 +185,14 @@ func (t *TestFramework) RequireStrongTips(aliases ...string) {
115185
require.Equal(t.test, len(aliases), len(t.Instance.StrongTips()), "strongTips size does not match")
116186
}
117187

188+
func (t *TestFramework) RequireValidationTips(aliases ...string) {
189+
for _, alias := range aliases {
190+
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)
191+
}
192+
193+
require.Equal(t.test, len(aliases), len(t.Instance.ValidationTips()), "validationTips size does not match")
194+
}
195+
118196
func (t *TestFramework) RequireLivenessThresholdReached(alias string, expected bool) {
119197
require.Equal(t.test, expected, t.TipMetadata(alias).LivenessThresholdReached().Get())
120198
}

Diff for: pkg/protocol/engine/tipmanager/tests/tipmanager_test.go

+113-6
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@ import (
55

66
"github.com/iotaledger/iota-core/pkg/protocol/engine/tipmanager"
77
iotago "github.com/iotaledger/iota.go/v4"
8+
"github.com/iotaledger/iota.go/v4/builder"
9+
"github.com/iotaledger/iota.go/v4/tpkg"
810
)
911

1012
func TestTipManager(t *testing.T) {
1113
tf := NewTestFramework(t)
1214

13-
tf.CreateBlock("Bernd", map[iotago.ParentsType][]string{
15+
tf.CreateBasicBlock("Bernd", map[iotago.ParentsType][]string{
1416
iotago.StrongParentType: {"Genesis"},
1517
})
16-
tf.CreateBlock("Bernd1", map[iotago.ParentsType][]string{
18+
tf.CreateBasicBlock("Bernd1", map[iotago.ParentsType][]string{
1719
iotago.StrongParentType: {"Bernd"},
1820
})
19-
tf.CreateBlock("Bernd1.1", map[iotago.ParentsType][]string{
21+
tf.CreateBasicBlock("Bernd1.1", map[iotago.ParentsType][]string{
2022
iotago.StrongParentType: {"Bernd"},
2123
})
2224

@@ -33,13 +35,13 @@ func TestTipManager(t *testing.T) {
3335
func Test_Orphanage(t *testing.T) {
3436
tf := NewTestFramework(t)
3537

36-
tf.CreateBlock("A", map[iotago.ParentsType][]string{
38+
tf.CreateBasicBlock("A", map[iotago.ParentsType][]string{
3739
iotago.StrongParentType: {"Genesis"},
3840
})
39-
tf.CreateBlock("B", map[iotago.ParentsType][]string{
41+
tf.CreateBasicBlock("B", map[iotago.ParentsType][]string{
4042
iotago.StrongParentType: {"Genesis"},
4143
})
42-
tf.CreateBlock("C", map[iotago.ParentsType][]string{
44+
tf.CreateBasicBlock("C", map[iotago.ParentsType][]string{
4345
iotago.StrongParentType: {"A", "B"},
4446
})
4547

@@ -56,3 +58,108 @@ func Test_Orphanage(t *testing.T) {
5658
blockB.LivenessThresholdReached().Trigger()
5759
tf.RequireStrongTips("A")
5860
}
61+
62+
func Test_ValidationTips(t *testing.T) {
63+
tf := NewTestFramework(t)
64+
65+
tf.AddValidators("validatorA", "validatorB")
66+
67+
{
68+
tf.CreateBasicBlock("1", map[iotago.ParentsType][]string{
69+
iotago.StrongParentType: {"Genesis"},
70+
})
71+
tf.CreateBasicBlock("2", map[iotago.ParentsType][]string{
72+
iotago.StrongParentType: {"Genesis"},
73+
})
74+
75+
tf.AddBlock("1").TipPool().Set(tipmanager.StrongTipPool)
76+
tf.AddBlock("2").TipPool().Set(tipmanager.StrongTipPool)
77+
78+
tf.RequireStrongTips("1", "2")
79+
}
80+
81+
// Add validation tip for validatorA.
82+
{
83+
tf.CreateValidationBlock("3", map[iotago.ParentsType][]string{
84+
iotago.StrongParentType: {"2"},
85+
}, func(blockBuilder *builder.ValidationBlockBuilder) {
86+
blockBuilder.Sign(tf.Validator("validatorA"), tpkg.RandEd25519PrivateKey())
87+
})
88+
89+
tf.AddBlock("3").TipPool().Set(tipmanager.StrongTipPool)
90+
91+
tf.RequireValidationTips("3")
92+
tf.RequireStrongTips("1", "3")
93+
}
94+
95+
// Add validation tip for validatorB.
96+
{
97+
tf.CreateValidationBlock("4", map[iotago.ParentsType][]string{
98+
iotago.StrongParentType: {"1"},
99+
}, func(blockBuilder *builder.ValidationBlockBuilder) {
100+
blockBuilder.Sign(tf.Validator("validatorB"), tpkg.RandEd25519PrivateKey())
101+
})
102+
103+
tf.AddBlock("4").TipPool().Set(tipmanager.StrongTipPool)
104+
105+
tf.RequireValidationTips("3", "4")
106+
tf.RequireStrongTips("3", "4")
107+
}
108+
109+
// Add basic blocks in the future cone of the validation tips, referencing both existing validation tips.
110+
{
111+
tf.CreateBasicBlock("5", map[iotago.ParentsType][]string{
112+
iotago.StrongParentType: {"3", "4"},
113+
})
114+
115+
tf.AddBlock("5").TipPool().Set(tipmanager.StrongTipPool)
116+
117+
tf.RequireValidationTips("5")
118+
tf.RequireStrongTips("5")
119+
}
120+
121+
// Add basic blocks in the future cone of the validation tips.
122+
{
123+
tf.CreateBasicBlock("6", map[iotago.ParentsType][]string{
124+
iotago.StrongParentType: {"3"},
125+
})
126+
tf.CreateBasicBlock("7", map[iotago.ParentsType][]string{
127+
iotago.StrongParentType: {"4"},
128+
})
129+
130+
tf.AddBlock("6").TipPool().Set(tipmanager.StrongTipPool)
131+
tf.AddBlock("7").TipPool().Set(tipmanager.StrongTipPool)
132+
133+
tf.RequireValidationTips("5", "6", "7")
134+
tf.RequireStrongTips("5", "6", "7")
135+
}
136+
137+
// A newer validation block replaces the previous validation tip of that validator.
138+
{
139+
tf.CreateValidationBlock("8", map[iotago.ParentsType][]string{
140+
iotago.StrongParentType: {"6"},
141+
}, func(blockBuilder *builder.ValidationBlockBuilder) {
142+
blockBuilder.Sign(tf.Validator("validatorB"), tpkg.RandEd25519PrivateKey())
143+
})
144+
145+
tf.AddBlock("8").TipPool().Set(tipmanager.StrongTipPool)
146+
147+
tf.RequireValidationTips("5", "8")
148+
tf.RequireStrongTips("5", "7", "8")
149+
}
150+
151+
// A newer validation block (with parallel past cone) still becomes a validation tip and overwrites
152+
// the previous validation tip of that validator.
153+
{
154+
tf.CreateValidationBlock("9", map[iotago.ParentsType][]string{
155+
iotago.StrongParentType: {"Genesis"},
156+
}, func(blockBuilder *builder.ValidationBlockBuilder) {
157+
blockBuilder.Sign(tf.Validator("validatorA"), tpkg.RandEd25519PrivateKey())
158+
})
159+
160+
tf.AddBlock("9").TipPool().Set(tipmanager.StrongTipPool)
161+
162+
tf.RequireValidationTips("8", "9")
163+
tf.RequireStrongTips("5", "7", "8", "9")
164+
}
165+
}

Diff for: pkg/protocol/engine/tipmanager/tipmanager.go

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package tipmanager
22

33
import (
44
"github.com/iotaledger/hive.go/runtime/module"
5+
"github.com/iotaledger/iota-core/pkg/core/account"
56
"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
67
iotago "github.com/iotaledger/iota.go/v4"
78
)
@@ -27,6 +28,15 @@ type TipManager interface {
2728
// OnBlockAdded registers a callback that is triggered whenever a new Block was added to the TipManager.
2829
OnBlockAdded(handler func(block TipMetadata)) (unsubscribe func())
2930

31+
// AddSeat adds a validator seat to the tracking of the TipManager.
32+
AddSeat(seat account.SeatIndex)
33+
34+
// RemoveSeat removes a validator seat from the tracking of the TipManager.
35+
RemoveSeat(seat account.SeatIndex)
36+
37+
// ValidationTips returns the validation tips of the TipManager (with an optional limit).
38+
ValidationTips(optAmount ...int) []TipMetadata
39+
3040
// StrongTips returns the strong tips of the TipManager (with an optional limit).
3141
StrongTips(optAmount ...int) []TipMetadata
3242

Diff for: pkg/protocol/engine/tipmanager/v1/provider.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,29 @@ import (
55
"github.com/iotaledger/hive.go/runtime/event"
66
"github.com/iotaledger/hive.go/runtime/module"
77
"github.com/iotaledger/hive.go/runtime/workerpool"
8+
"github.com/iotaledger/iota-core/pkg/core/account"
89
"github.com/iotaledger/iota-core/pkg/protocol/engine"
910
"github.com/iotaledger/iota-core/pkg/protocol/engine/tipmanager"
11+
iotago "github.com/iotaledger/iota.go/v4"
1012
)
1113

1214
// NewProvider creates a new TipManager provider, that can be used to inject the component into an engine.
1315
func NewProvider() module.Provider[*engine.Engine, tipmanager.TipManager] {
1416
return module.Provide(func(e *engine.Engine) tipmanager.TipManager {
15-
t := New(e.BlockCache.Block)
17+
t := New(e.BlockCache.Block, e.SybilProtection.SeatManager().CommitteeInSlot)
1618

1719
e.Constructed.OnTrigger(func() {
1820
tipWorker := e.Workers.CreatePool("AddTip", workerpool.WithWorkerCount(2))
21+
1922
e.Events.Scheduler.BlockScheduled.Hook(lo.Void(t.AddBlock), event.WithWorkerPool(tipWorker))
2023
e.Events.Scheduler.BlockSkipped.Hook(lo.Void(t.AddBlock), event.WithWorkerPool(tipWorker))
2124
e.BlockCache.Evict.Hook(t.Evict)
2225

26+
e.Events.SeatManager.OnlineCommitteeSeatAdded.Hook(func(index account.SeatIndex, _ iotago.AccountID) {
27+
t.AddSeat(index)
28+
})
29+
e.Events.SeatManager.OnlineCommitteeSeatRemoved.Hook(t.RemoveSeat)
30+
2331
e.Events.TipManager.BlockAdded.LinkTo(t.blockAdded)
2432

2533
t.TriggerInitialized()

0 commit comments

Comments
 (0)