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

Commit c6f349d

Browse files
authored
Merge pull request #651 from iotaledger/feat/only-witnesses-same-epoch
Do not propagate confirmation ratifiers over epoch boundaries
2 parents 1cf70b0 + 6a97462 commit c6f349d

File tree

4 files changed

+133
-1
lines changed

4 files changed

+133
-1
lines changed

pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/confirmation_ratification.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ func (g *Gadget) trackConfirmationRatifierWeight(votingBlock *blocks.Block) {
1313
}
1414

1515
ratifierBlockIndex := votingBlock.ID().Slot()
16+
ratifierBlockEpoch := votingBlock.ProtocolBlock().API.TimeProvider().EpochFromSlot(ratifierBlockIndex)
1617

1718
var toConfirm []*blocks.Block
1819

@@ -24,6 +25,14 @@ func (g *Gadget) trackConfirmationRatifierWeight(votingBlock *blocks.Block) {
2425
return false
2526
}
2627

28+
// Skip propagation if the block is not in the same epoch as the ratifier. This might delay the confirmation
29+
// of blocks at the end of the epoch but make sure that confirmation is safe in case where the minority of voters
30+
// got different seats for the next epoch.
31+
blockEpoch := block.ProtocolBlock().API.TimeProvider().EpochFromSlot(block.ID().Slot())
32+
if ratifierBlockEpoch != blockEpoch {
33+
return false
34+
}
35+
2736
// Skip propagation if the block is already confirmed.
2837
if block.IsConfirmed() {
2938
return false

pkg/protocol/engine/consensus/blockgadget/thresholdblockgadget/witness_weight.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ func (g *Gadget) TrackWitnessWeight(votingBlock *blocks.Block) {
1616
return
1717
}
1818

19+
votingBlockEpoch := votingBlock.ProtocolBlock().API.TimeProvider().EpochFromSlot(votingBlock.ID().Slot())
20+
1921
var toPreAccept []*blocks.Block
2022
toPreAcceptByID := ds.NewSet[iotago.BlockID]()
2123

@@ -32,7 +34,11 @@ func (g *Gadget) TrackWitnessWeight(votingBlock *blocks.Block) {
3234
propagateFurther = true
3335
}
3436

35-
if !block.IsPreConfirmed() && (shouldPreConfirm || anyChildInSet(block, toPreConfirmByID)) {
37+
// Skip propagation of pre-confirmation if the block is not in the same epoch as the votingBlock.
38+
// This might delay the (pre-)confirmation of blocks at the end of the epoch but make sure that (pre-)confirmation
39+
// is safe in case where the minority of voters got different seats for the next epoch.
40+
blockEpoch := block.ProtocolBlock().API.TimeProvider().EpochFromSlot(block.ID().Slot())
41+
if !block.IsPreConfirmed() && votingBlockEpoch == blockEpoch && (shouldPreConfirm || anyChildInSet(block, toPreConfirmByID)) {
3642
toPreConfirm = append([]*blocks.Block{block}, toPreConfirm...)
3743
toPreConfirmByID.Add(block.ID())
3844
propagateFurther = true

pkg/tests/confirmation_state_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/iotaledger/iota-core/pkg/testsuite"
1414
"github.com/iotaledger/iota-core/pkg/testsuite/mock"
1515
iotago "github.com/iotaledger/iota.go/v4"
16+
"github.com/iotaledger/iota.go/v4/api"
1617
)
1718

1819
func TestConfirmationFlags(t *testing.T) {
@@ -245,3 +246,88 @@ func TestConfirmationFlags(t *testing.T) {
245246
)
246247
}
247248
}
249+
250+
func TestConfirmationOverEpochBoundary(t *testing.T) {
251+
ts := testsuite.NewTestSuite(t,
252+
testsuite.WithProtocolParametersOptions(
253+
iotago.WithTimeProviderOptions(
254+
0,
255+
testsuite.GenesisTimeWithOffsetBySlots(1000, testsuite.DefaultSlotDurationInSeconds),
256+
testsuite.DefaultSlotDurationInSeconds,
257+
3,
258+
),
259+
iotago.WithLivenessOptions(
260+
10,
261+
10,
262+
3,
263+
4,
264+
5,
265+
),
266+
),
267+
)
268+
defer ts.Shutdown()
269+
270+
ts.AddValidatorNode("node0")
271+
ts.AddValidatorNode("node1")
272+
ts.AddValidatorNode("node2")
273+
ts.AddValidatorNode("node3")
274+
ts.AddNode("node4")
275+
276+
ts.Run(true)
277+
278+
// Issue blocks up until 1 slot more than the epoch.
279+
{
280+
ts.IssueBlocksAtSlots("", []iotago.SlotIndex{1, 2, 3, 4, 5, 6, 7, 8, 9}, 4, "Genesis", ts.Nodes(), true, false)
281+
282+
ts.AssertNodeState(ts.Nodes(),
283+
testsuite.WithLatestFinalizedSlot(5),
284+
testsuite.WithLatestCommitmentSlotIndex(6),
285+
testsuite.WithEqualStoredCommitmentAtIndex(6),
286+
testsuite.WithEvictedSlot(6),
287+
)
288+
289+
// Verify propagation of witness weight over epoch boundaries (slot 7).
290+
{
291+
// We propagate witness weight for pre-acceptance and acceptance over epoch boundaries.
292+
ts.AssertBlocksInCachePreAccepted(ts.BlocksWithPrefixes("7.0", "7.1", "7.2", "7.3"), true, ts.Nodes()...)
293+
ts.AssertBlocksInCacheAccepted(ts.BlocksWithPrefixes("7.0", "7.1", "7.2", "7.3"), true, ts.Nodes()...)
294+
295+
// We don't propagate pre-confirmation and confirmation over epoch boundaries:
296+
// There's 4 rows in a slot, everything except the last row should be pre-confirmed.
297+
ts.AssertBlocksInCachePreConfirmed(ts.BlocksWithPrefixes("7.0", "7.1", "7.2"), true, ts.Nodes()...)
298+
ts.AssertBlocksInCachePreConfirmed(ts.BlocksWithPrefixes("7.3"), false, ts.Nodes()...)
299+
// Accordingly, only the first 2 rows are confirmed.
300+
ts.AssertBlocksInCacheConfirmed(ts.BlocksWithPrefixes("7.0", "7.1"), true, ts.Nodes()...)
301+
ts.AssertBlocksInCacheConfirmed(ts.BlocksWithPrefixes("7.2", "7.3"), false, ts.Nodes()...)
302+
303+
}
304+
305+
// Slot 8 and 9 behaves normally, as they are in the new epoch.
306+
{
307+
ts.AssertBlocksInCacheAccepted(ts.BlocksWithPrefixes("8.0", "8.1", "8.2", "8.3", "9.0", "9.1"), true, ts.Nodes()...)
308+
ts.AssertBlocksInCacheAccepted(ts.BlocksWithPrefixes("9.2", "9.3"), false, ts.Nodes()...)
309+
ts.AssertBlocksInCachePreAccepted(ts.BlocksWithPrefixes("9.2"), true, ts.Nodes()...)
310+
ts.AssertBlocksInCachePreAccepted(ts.BlocksWithPrefixes("9.3"), false, ts.Nodes()...)
311+
312+
ts.AssertBlocksInCacheConfirmed(ts.BlocksWithPrefixes("8.0", "8.1", "8.2", "8.3", "9.0", "9.1"), true, ts.Nodes()...)
313+
ts.AssertBlocksInCacheConfirmed(ts.BlocksWithPrefixes("9.2", "9.3"), false, ts.Nodes()...)
314+
ts.AssertBlocksInCachePreConfirmed(ts.BlocksWithPrefixes("9.2"), true, ts.Nodes()...)
315+
ts.AssertBlocksInCachePreConfirmed(ts.BlocksWithPrefixes("9.3"), false, ts.Nodes()...)
316+
}
317+
}
318+
319+
// Issue more so that blocks at end of epoch become confirmed via finalization.
320+
{
321+
ts.IssueBlocksAtSlots("", []iotago.SlotIndex{10, 11, 12}, 4, "9.3", ts.Nodes(), true, false)
322+
323+
ts.AssertNodeState(ts.Nodes(),
324+
testsuite.WithLatestFinalizedSlot(8),
325+
testsuite.WithLatestCommitmentSlotIndex(9),
326+
testsuite.WithEqualStoredCommitmentAtIndex(9),
327+
testsuite.WithEvictedSlot(9),
328+
)
329+
330+
ts.AssertRetainerBlocksState(ts.BlocksWithPrefixes("7", "8"), api.BlockStateFinalized, ts.Nodes()...)
331+
ts.AssertRetainerBlocksState(ts.BlocksWithPrefixes("9", "10", "11"), api.BlockStateConfirmed, ts.Nodes()...)
332+
}
333+
}

pkg/testsuite/blocks_retainer.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package testsuite
2+
3+
import (
4+
"github.com/iotaledger/hive.go/ierrors"
5+
"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
6+
"github.com/iotaledger/iota-core/pkg/testsuite/mock"
7+
"github.com/iotaledger/iota.go/v4/api"
8+
)
9+
10+
func (t *TestSuite) AssertRetainerBlocksState(expectedBlocks []*blocks.Block, expectedState api.BlockState, nodes ...*mock.Node) {
11+
mustNodes(nodes)
12+
13+
for _, node := range nodes {
14+
for _, block := range expectedBlocks {
15+
t.Eventually(func() error {
16+
blockFromRetainer, err := node.Protocol.Engines.Main.Get().Retainer.BlockMetadata(block.ID())
17+
if err != nil {
18+
return ierrors.Errorf("AssertRetainerBlocksState: %s: block %s: error when loading %s", node.Name, block.ID(), err.Error())
19+
}
20+
21+
if expectedState != blockFromRetainer.BlockState {
22+
return ierrors.Errorf("AssertRetainerBlocksState: %s: block %s: expected %s, got %s", node.Name, block.ID(), expectedState, blockFromRetainer.BlockState)
23+
}
24+
25+
return nil
26+
})
27+
28+
t.AssertBlock(block, node)
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)