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

Commit a12e99a

Browse files
authored
Merge pull request #855 from iotaledger/feat/wallclock-drift
Make sure user knows that block was filtered out due to wallclock drift
2 parents b18b728 + 141ca7b commit a12e99a

File tree

5 files changed

+67
-17
lines changed

5 files changed

+67
-17
lines changed

pkg/protocol/blocks.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package protocol
22

33
import (
4+
"time"
5+
46
"github.com/libp2p/go-libp2p/core/peer"
57

68
"github.com/iotaledger/hive.go/ds/types"
@@ -15,6 +17,12 @@ import (
1517
iotago "github.com/iotaledger/iota.go/v4"
1618
)
1719

20+
var (
21+
ErrBlockTimeTooFarAheadInFuture = ierrors.New("a block cannot be too far ahead in the future")
22+
ErrUnsolidifiableCommitment = ierrors.New("block referencing unsolidifiable commitment is not allowed")
23+
ErrFailToUpdateDropBuffer = ierrors.New("failed to update dropped blocks buffer")
24+
)
25+
1826
// Blocks is a subcomponent of the protocol that is responsible for handling block requests and responses.
1927
type Blocks struct {
2028
// protocol contains a reference to the Protocol instance that this component belongs to.
@@ -87,18 +95,44 @@ func (b *Blocks) SendResponse(block *model.Block) {
8795
// ProcessResponse processes the given block response.
8896
func (b *Blocks) ProcessResponse(block *model.Block, from peer.ID) {
8997
b.workerPool.Submit(func() {
98+
// this check must happen before the block reaches the Engine. The Protocol needs a perception of the current time,
99+
// otherwise a malicous actor might trigger a chain switch by sending a block with a commitment in the future.
100+
if timeDelta := time.Since(block.ProtocolBlock().Header.IssuingTime); timeDelta < -b.protocol.Options.MaxAllowedWallClockDrift {
101+
b.LogWarn("filtered block, issuing time ahead", "block", block.ID(), "issuingTime", block.ProtocolBlock().Header.IssuingTime, "timeDelta", timeDelta, "deltaAllowed", b.protocol.Options.MaxAllowedWallClockDrift, "from", from, "err", ErrBlockTimeTooFarAheadInFuture)
102+
103+
b.protocol.Events.ProtocolFilter.Trigger(&BlockFilteredEvent{
104+
Block: block,
105+
Reason: ierrors.WithMessagef(ErrBlockTimeTooFarAheadInFuture, "block issuing time ahead by %v, time delta allowed: %d", -timeDelta, b.protocol.Options.MaxAllowedWallClockDrift),
106+
Source: from,
107+
})
108+
109+
return
110+
}
111+
90112
// abort if the commitment belongs to an evicted slot
91113
commitment, err := b.protocol.Commitments.Get(block.ProtocolBlock().Header.SlotCommitmentID, true)
92114
if err != nil && ierrors.Is(err, ErrorSlotEvicted) {
93115
b.LogError("dropped block referencing unsolidifiable commitment", "commitmentID", block.ProtocolBlock().Header.SlotCommitmentID, "blockID", block.ID(), "err", err)
94116

117+
b.protocol.Events.ProtocolFilter.Trigger(&BlockFilteredEvent{
118+
Block: block,
119+
Reason: ierrors.WithMessagef(ErrUnsolidifiableCommitment, "commitment %s slot has been evicted", block.ProtocolBlock().Header.SlotCommitmentID.String()),
120+
Source: from,
121+
})
122+
95123
return
96124
}
97125

98126
// add the block to the dropped blocks buffer if we could not dispatch it to the chain
99127
if commitment == nil || !commitment.Chain.Get().DispatchBlock(block, from) {
100128
if !b.droppedBlocksBuffer.Add(block.ProtocolBlock().Header.SlotCommitmentID, types.NewTuple(block, from)) {
101129
b.LogError("failed to add dropped block referencing unsolid commitment to dropped blocks buffer", "commitmentID", block.ProtocolBlock().Header.SlotCommitmentID, "blockID", block.ID())
130+
131+
b.protocol.Events.ProtocolFilter.Trigger(&BlockFilteredEvent{
132+
Block: block,
133+
Reason: ierrors.WithMessagef(ErrFailToUpdateDropBuffer, "failed to add block %s to dropped blocks buffer", block.ID().String()),
134+
Source: from,
135+
})
102136
} else {
103137
b.LogTrace("dropped block referencing unsolid commitment added to dropped blocks buffer", "commitmentID", block.ProtocolBlock().Header.SlotCommitmentID, "blockID", block.ID())
104138
}

pkg/protocol/events.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
package protocol
22

3-
import "github.com/iotaledger/iota-core/pkg/protocol/engine"
3+
import (
4+
"github.com/libp2p/go-libp2p/core/peer"
5+
6+
"github.com/iotaledger/hive.go/runtime/event"
7+
"github.com/iotaledger/iota-core/pkg/model"
8+
"github.com/iotaledger/iota-core/pkg/protocol/engine"
9+
)
410

511
// Events exposes the Events of the main engine of the protocol at a single endpoint.
612
//
713
// TODO: It should be replaced with reactive calls to the corresponding events and be deleted but we can do this in a
814
// later PR (to minimize the code changes to review).
915
type Events struct {
10-
Engine *engine.Events
16+
Engine *engine.Events
17+
ProtocolFilter *event.Event1[*BlockFilteredEvent]
1118
}
1219

1320
// NewEvents creates a new Events instance.
1421
func NewEvents() *Events {
1522
return &Events{
16-
Engine: engine.NewEvents(),
23+
Engine: engine.NewEvents(),
24+
ProtocolFilter: event.New1[*BlockFilteredEvent](),
1725
}
1826
}
27+
28+
type BlockFilteredEvent struct {
29+
Block *model.Block
30+
Reason error
31+
Source peer.ID
32+
}

pkg/protocol/network.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
package protocol
22

33
import (
4-
"time"
5-
64
"github.com/libp2p/go-libp2p/core/peer"
75

8-
"github.com/iotaledger/hive.go/ierrors"
96
"github.com/iotaledger/hive.go/log"
107
"github.com/iotaledger/iota-core/pkg/model"
118
"github.com/iotaledger/iota-core/pkg/network"
129
"github.com/iotaledger/iota-core/pkg/network/protocols/core"
1310
)
1411

15-
var ErrBlockTimeTooFarAheadInFuture = ierrors.New("a block cannot be too far ahead in the future")
16-
1712
// Network is a subcomponent of the protocol that is responsible for handling the network communication.
1813
type Network struct {
1914
// Protocol contains the network endpoint of the protocol.
@@ -42,13 +37,6 @@ func newNetwork(protocol *Protocol, networkEndpoint network.Endpoint) *Network {
4237
// OnBlockReceived overwrites the OnBlockReceived method of the core protocol to filter out invalid blocks.
4338
func (n *Network) OnBlockReceived(callback func(block *model.Block, src peer.ID)) (unsubscribe func()) {
4439
return n.Protocol.OnBlockReceived(func(block *model.Block, src peer.ID) {
45-
// filter blocks from the future
46-
if timeDelta := time.Since(block.ProtocolBlock().Header.IssuingTime); timeDelta < -n.protocol.Options.MaxAllowedWallClockDrift {
47-
n.LogWarn("filtered block, issuing time ahead", "block", block.ID(), "issuingTime", block.ProtocolBlock().Header.IssuingTime, "timeDelta", timeDelta, "deltaAllowed", n.protocol.Options.MaxAllowedWallClockDrift, "from", src, "err", ErrBlockTimeTooFarAheadInFuture)
48-
49-
return
50-
}
51-
5240
callback(block, src)
5341
})
5442
}

pkg/requesthandler/blockissuance.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/iotaledger/hive.go/lo"
99
"github.com/iotaledger/hive.go/runtime/event"
1010
"github.com/iotaledger/iota-core/pkg/model"
11+
"github.com/iotaledger/iota-core/pkg/protocol"
1112
"github.com/iotaledger/iota-core/pkg/protocol/engine/blocks"
1213
"github.com/iotaledger/iota-core/pkg/protocol/engine/filter/postsolidfilter"
1314
"github.com/iotaledger/iota-core/pkg/protocol/engine/filter/presolidfilter"
@@ -81,6 +82,15 @@ func (r *RequestHandler) submitBlockAndAwaitRetainer(ctx context.Context, block
8182
blockCtxCancel(errBlockRetained)
8283
}, event.WithWorkerPool(r.workerPool)).Unhook
8384

85+
protocolFilteredUnhook := r.protocol.Events.ProtocolFilter.Hook(func(event *protocol.BlockFilteredEvent) {
86+
if blockID != event.Block.ID() {
87+
return
88+
}
89+
90+
// signal that block was dropped by the protocol
91+
blockCtxCancel(event.Reason)
92+
}, event.WithWorkerPool(r.workerPool)).Unhook
93+
8494
blockPreFilteredUnhook := r.protocol.Events.Engine.PreSolidFilter.BlockPreFiltered.Hook(func(event *presolidfilter.BlockPreFilteredEvent) {
8595
if blockID != event.Block.ID() {
8696
return
@@ -99,7 +109,7 @@ func (r *RequestHandler) submitBlockAndAwaitRetainer(ctx context.Context, block
99109
blockCtxCancel(event.Reason)
100110
}, event.WithWorkerPool(r.workerPool)).Unhook
101111

102-
defer lo.BatchReverse(txRetainedUnhook, blockRetainedUnhook, blockPreFilteredUnhook, blockPostFilteredUnhook)()
112+
defer lo.BatchReverse(txRetainedUnhook, blockRetainedUnhook, protocolFilteredUnhook, blockPreFilteredUnhook, blockPostFilteredUnhook)()
103113

104114
if err := r.submitBlock(block); err != nil {
105115
return ierrors.Wrapf(err, "failed to issue block %s", blockID)

pkg/tests/blocktime_monotonicity_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/stretchr/testify/require"
99

10+
"github.com/iotaledger/hive.go/ierrors"
1011
"github.com/iotaledger/hive.go/lo"
1112
"github.com/iotaledger/hive.go/runtime/options"
1213
"github.com/iotaledger/iota-core/pkg/protocol"
@@ -49,11 +50,14 @@ func Test_MaxAllowedWallClockDrift(t *testing.T) {
4950

5051
tooFarAheadFutureBlock := lo.PanicOnErr(node0.Validator.CreateBasicBlock(context.Background(), "tooFarAheadFuture", mock.WithBasicBlockHeader(mock.WithIssuingTime(time.Now().Add(allowedDrift).Add(1*time.Second)))))
5152
ts.RegisterBlock("tooFarAheadFuture", tooFarAheadFutureBlock)
52-
require.NoError(t, node0.Validator.SubmitBlockWithoutAwaitingBooking(tooFarAheadFutureBlock.ModelBlock(), node0))
53+
err := node0.Validator.SubmitBlock(context.Background(), tooFarAheadFutureBlock.ModelBlock())
54+
require.Error(t, err)
55+
require.True(t, ierrors.Is(err, protocol.ErrBlockTimeTooFarAheadInFuture))
5356

5457
ts.AssertBlocksExist(ts.Blocks("past", "present", "acceptedFuture"), true, node0.Client)
5558
ts.AssertBlocksExist(ts.Blocks("tooFarAheadFuture"), false, node0.Client)
5659
}
60+
5761
func Test_BlockTimeMonotonicity(t *testing.T) {
5862
ts := testsuite.NewTestSuite(t,
5963
testsuite.WithProtocolParametersOptions(

0 commit comments

Comments
 (0)