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

Commit

Permalink
Merge pull request #651 from iotaledger/feat/only-witnesses-same-epoch
Browse files Browse the repository at this point in the history
Do not propagate confirmation ratifiers over epoch boundaries
  • Loading branch information
jonastheis authored Jan 16, 2024
2 parents 1cf70b0 + 6a97462 commit c6f349d
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func (g *Gadget) trackConfirmationRatifierWeight(votingBlock *blocks.Block) {
}

ratifierBlockIndex := votingBlock.ID().Slot()
ratifierBlockEpoch := votingBlock.ProtocolBlock().API.TimeProvider().EpochFromSlot(ratifierBlockIndex)

var toConfirm []*blocks.Block

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

// Skip propagation if the block is not in the same epoch as the ratifier. This might delay the confirmation
// of blocks at the end of the epoch but make sure that confirmation is safe in case where the minority of voters
// got different seats for the next epoch.
blockEpoch := block.ProtocolBlock().API.TimeProvider().EpochFromSlot(block.ID().Slot())
if ratifierBlockEpoch != blockEpoch {
return false
}

// Skip propagation if the block is already confirmed.
if block.IsConfirmed() {
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ func (g *Gadget) TrackWitnessWeight(votingBlock *blocks.Block) {
return
}

votingBlockEpoch := votingBlock.ProtocolBlock().API.TimeProvider().EpochFromSlot(votingBlock.ID().Slot())

var toPreAccept []*blocks.Block
toPreAcceptByID := ds.NewSet[iotago.BlockID]()

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

if !block.IsPreConfirmed() && (shouldPreConfirm || anyChildInSet(block, toPreConfirmByID)) {
// Skip propagation of pre-confirmation if the block is not in the same epoch as the votingBlock.
// This might delay the (pre-)confirmation of blocks at the end of the epoch but make sure that (pre-)confirmation
// is safe in case where the minority of voters got different seats for the next epoch.
blockEpoch := block.ProtocolBlock().API.TimeProvider().EpochFromSlot(block.ID().Slot())
if !block.IsPreConfirmed() && votingBlockEpoch == blockEpoch && (shouldPreConfirm || anyChildInSet(block, toPreConfirmByID)) {
toPreConfirm = append([]*blocks.Block{block}, toPreConfirm...)
toPreConfirmByID.Add(block.ID())
propagateFurther = true
Expand Down
86 changes: 86 additions & 0 deletions pkg/tests/confirmation_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/iotaledger/iota-core/pkg/testsuite"
"github.com/iotaledger/iota-core/pkg/testsuite/mock"
iotago "github.com/iotaledger/iota.go/v4"
"github.com/iotaledger/iota.go/v4/api"
)

func TestConfirmationFlags(t *testing.T) {
Expand Down Expand Up @@ -245,3 +246,88 @@ func TestConfirmationFlags(t *testing.T) {
)
}
}

func TestConfirmationOverEpochBoundary(t *testing.T) {
ts := testsuite.NewTestSuite(t,
testsuite.WithProtocolParametersOptions(
iotago.WithTimeProviderOptions(
0,
testsuite.GenesisTimeWithOffsetBySlots(1000, testsuite.DefaultSlotDurationInSeconds),
testsuite.DefaultSlotDurationInSeconds,
3,
),
iotago.WithLivenessOptions(
10,
10,
3,
4,
5,
),
),
)
defer ts.Shutdown()

ts.AddValidatorNode("node0")
ts.AddValidatorNode("node1")
ts.AddValidatorNode("node2")
ts.AddValidatorNode("node3")
ts.AddNode("node4")

ts.Run(true)

// Issue blocks up until 1 slot more than the epoch.
{
ts.IssueBlocksAtSlots("", []iotago.SlotIndex{1, 2, 3, 4, 5, 6, 7, 8, 9}, 4, "Genesis", ts.Nodes(), true, false)

ts.AssertNodeState(ts.Nodes(),
testsuite.WithLatestFinalizedSlot(5),
testsuite.WithLatestCommitmentSlotIndex(6),
testsuite.WithEqualStoredCommitmentAtIndex(6),
testsuite.WithEvictedSlot(6),
)

// Verify propagation of witness weight over epoch boundaries (slot 7).
{
// We propagate witness weight for pre-acceptance and acceptance over epoch boundaries.
ts.AssertBlocksInCachePreAccepted(ts.BlocksWithPrefixes("7.0", "7.1", "7.2", "7.3"), true, ts.Nodes()...)
ts.AssertBlocksInCacheAccepted(ts.BlocksWithPrefixes("7.0", "7.1", "7.2", "7.3"), true, ts.Nodes()...)

// We don't propagate pre-confirmation and confirmation over epoch boundaries:
// There's 4 rows in a slot, everything except the last row should be pre-confirmed.
ts.AssertBlocksInCachePreConfirmed(ts.BlocksWithPrefixes("7.0", "7.1", "7.2"), true, ts.Nodes()...)
ts.AssertBlocksInCachePreConfirmed(ts.BlocksWithPrefixes("7.3"), false, ts.Nodes()...)
// Accordingly, only the first 2 rows are confirmed.
ts.AssertBlocksInCacheConfirmed(ts.BlocksWithPrefixes("7.0", "7.1"), true, ts.Nodes()...)
ts.AssertBlocksInCacheConfirmed(ts.BlocksWithPrefixes("7.2", "7.3"), false, ts.Nodes()...)

}

// Slot 8 and 9 behaves normally, as they are in the new epoch.
{
ts.AssertBlocksInCacheAccepted(ts.BlocksWithPrefixes("8.0", "8.1", "8.2", "8.3", "9.0", "9.1"), true, ts.Nodes()...)
ts.AssertBlocksInCacheAccepted(ts.BlocksWithPrefixes("9.2", "9.3"), false, ts.Nodes()...)
ts.AssertBlocksInCachePreAccepted(ts.BlocksWithPrefixes("9.2"), true, ts.Nodes()...)
ts.AssertBlocksInCachePreAccepted(ts.BlocksWithPrefixes("9.3"), false, ts.Nodes()...)

ts.AssertBlocksInCacheConfirmed(ts.BlocksWithPrefixes("8.0", "8.1", "8.2", "8.3", "9.0", "9.1"), true, ts.Nodes()...)
ts.AssertBlocksInCacheConfirmed(ts.BlocksWithPrefixes("9.2", "9.3"), false, ts.Nodes()...)
ts.AssertBlocksInCachePreConfirmed(ts.BlocksWithPrefixes("9.2"), true, ts.Nodes()...)
ts.AssertBlocksInCachePreConfirmed(ts.BlocksWithPrefixes("9.3"), false, ts.Nodes()...)
}
}

// Issue more so that blocks at end of epoch become confirmed via finalization.
{
ts.IssueBlocksAtSlots("", []iotago.SlotIndex{10, 11, 12}, 4, "9.3", ts.Nodes(), true, false)

ts.AssertNodeState(ts.Nodes(),
testsuite.WithLatestFinalizedSlot(8),
testsuite.WithLatestCommitmentSlotIndex(9),
testsuite.WithEqualStoredCommitmentAtIndex(9),
testsuite.WithEvictedSlot(9),
)

ts.AssertRetainerBlocksState(ts.BlocksWithPrefixes("7", "8"), api.BlockStateFinalized, ts.Nodes()...)
ts.AssertRetainerBlocksState(ts.BlocksWithPrefixes("9", "10", "11"), api.BlockStateConfirmed, ts.Nodes()...)
}
}
31 changes: 31 additions & 0 deletions pkg/testsuite/blocks_retainer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package testsuite

import (
"github.com/iotaledger/hive.go/ierrors"
"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
"github.com/iotaledger/iota-core/pkg/testsuite/mock"
"github.com/iotaledger/iota.go/v4/api"
)

func (t *TestSuite) AssertRetainerBlocksState(expectedBlocks []*blocks.Block, expectedState api.BlockState, nodes ...*mock.Node) {
mustNodes(nodes)

for _, node := range nodes {
for _, block := range expectedBlocks {
t.Eventually(func() error {
blockFromRetainer, err := node.Protocol.Engines.Main.Get().Retainer.BlockMetadata(block.ID())
if err != nil {
return ierrors.Errorf("AssertRetainerBlocksState: %s: block %s: error when loading %s", node.Name, block.ID(), err.Error())
}

if expectedState != blockFromRetainer.BlockState {
return ierrors.Errorf("AssertRetainerBlocksState: %s: block %s: expected %s, got %s", node.Name, block.ID(), expectedState, blockFromRetainer.BlockState)
}

return nil
})

t.AssertBlock(block, node)
}
}
}

0 comments on commit c6f349d

Please sign in to comment.