Skip to content

Commit 1d72eff

Browse files
committed
go/worker/storage/committe: Factor out checkpoint validation
Also penalize peers that advertised bad checkpoints.
1 parent fed2afc commit 1d72eff

2 files changed

Lines changed: 97 additions & 30 deletions

File tree

go/worker/storage/committee/checkpoint_sync.go

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"time"
1313

1414
"github.com/oasisprotocol/oasis-core/go/p2p/rpc"
15+
"github.com/oasisprotocol/oasis-core/go/roothash/api/block"
1516
storageApi "github.com/oasisprotocol/oasis-core/go/storage/api"
1617
"github.com/oasisprotocol/oasis-core/go/storage/mkvs/checkpoint"
1718
"github.com/oasisprotocol/oasis-core/go/worker/storage/p2p/checkpointsync"
@@ -367,40 +368,30 @@ func sortCheckpoints(s []*checkpointsync.Checkpoint) {
367368
})
368369
}
369370

370-
func (w *Worker) checkCheckpointUsable(ctx context.Context, cp *checkpointsync.Checkpoint, remainingMask outstandingMask, genesisRound uint64) bool {
371-
namespace := w.commonNode.Runtime.ID()
372-
if !namespace.Equal(&cp.Root.Namespace) {
373-
// Not for the right runtime.
374-
return false
375-
}
376-
if cp.Root.Version == genesisRound && cp.Root.Type == storageApi.RootTypeIO {
377-
// Never fetch i/o root for genesis round.
378-
return false
371+
func validateCheckpoint(cp *checkpointsync.Checkpoint, blk *block.Block) error {
372+
if !blk.Header.Namespace.Equal((&cp.Root.Namespace)) {
373+
return fmt.Errorf("namespace mismatch: got %s, want %s", cp.Root.Namespace, blk.Header.Namespace)
379374
}
380375

381-
blk, err := w.commonNode.Runtime.History().GetCommittedBlock(ctx, cp.Root.Version)
382-
if err != nil {
383-
w.logger.Error("can't get block information for checkpoint, skipping", "err", err, "root", cp.Root)
384-
return false
376+
for _, root := range blk.Header.StorageRoots() {
377+
if cp.Root.Equal(&root) {
378+
return nil
379+
}
385380
}
381+
return fmt.Errorf("checkpoint metadata with unexpected root %s", cp.Root)
382+
}
383+
384+
func (w *Worker) checkCheckpointUsable(cp *checkpointsync.Checkpoint, remainingMask outstandingMask, genesisRound uint64) bool {
386385
_, lastIORoot, lastStateRoot := w.GetLastSynced()
387-
lastVersions := map[storageApi.RootType]uint64{
388-
storageApi.RootTypeIO: lastIORoot.Version,
389-
storageApi.RootTypeState: lastStateRoot.Version,
390-
}
391-
if namespace.Equal(&blk.Header.Namespace) {
392-
for _, root := range blk.Header.StorageRoots() {
393-
if cp.Root.Type == root.Type && root.Hash.Equal(&cp.Root.Hash) {
394-
// Do we already have this root?
395-
if lastVersions[cp.Root.Type] < cp.Root.Version && remainingMask.contains(cp.Root.Type) {
396-
return true
397-
}
398-
return false
399-
}
400-
}
386+
var lastVersion uint64
387+
switch cp.Root.Type {
388+
case storageApi.RootTypeIO:
389+
lastVersion = lastIORoot.Version
390+
case storageApi.RootTypeState:
391+
lastVersion = lastStateRoot.Version
401392
}
402-
w.logger.Info("checkpoint for unknown root skipped", "root", cp.Root)
403-
return false
393+
394+
return lastVersion < cp.Root.Version && remainingMask.contains(cp.Root.Type)
404395
}
405396

406397
func (w *Worker) syncCheckpoints(ctx context.Context, genesisRound uint64, wantOnlyGenesis bool) (*blockSummary, error) {
@@ -447,7 +438,32 @@ func (w *Worker) syncCheckpoints(ctx context.Context, genesisRound uint64, wantO
447438

448439
for _, check := range cps {
449440

450-
if check.Root.Version < genesisRound || !w.checkCheckpointUsable(ctx, check, remainingRoots, genesisRound) {
441+
if check.Root.Version < genesisRound {
442+
continue
443+
}
444+
445+
if check.Root.Version == genesisRound && check.Root.Type == storageApi.RootTypeIO {
446+
// Genesis round has no i/o root. Some peers may still advertise one after
447+
// dump-restore upgrades; ignore it without penalizing.
448+
continue
449+
}
450+
451+
blk, err := w.commonNode.Runtime.History().GetCommittedBlock(ctx, check.Root.Version)
452+
if err != nil {
453+
w.logger.Error("can't get block information for checkpoint, skipping", "err", err, "root", check.Root)
454+
continue
455+
}
456+
457+
if err := validateCheckpoint(check, blk); err != nil {
458+
w.logger.Error("invalid checkpoint received, penalizing bad peers", "checkpoint", check)
459+
for _, peer := range check.Peers {
460+
peer.RecordBadPeer()
461+
}
462+
continue
463+
}
464+
465+
if !w.checkCheckpointUsable(check, remainingRoots, genesisRound) {
466+
w.logger.Info("checkpoint not usable", "checkpoint", check)
451467
continue
452468
}
453469

go/worker/storage/committee/checkpoint_sync_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55

66
"github.com/stretchr/testify/assert"
77

8+
"github.com/oasisprotocol/oasis-core/go/common"
89
"github.com/oasisprotocol/oasis-core/go/p2p/rpc"
10+
"github.com/oasisprotocol/oasis-core/go/roothash/api/block"
911
"github.com/oasisprotocol/oasis-core/go/storage/mkvs/checkpoint"
1012
"github.com/oasisprotocol/oasis-core/go/storage/mkvs/node"
1113
"github.com/oasisprotocol/oasis-core/go/worker/storage/p2p/checkpointsync"
@@ -51,3 +53,52 @@ func TestSortCheckpoints(t *testing.T) {
5153

5254
assert.Equal(t, s, []*checkpointsync.Checkpoint{cp1, cp2, cp3, cp4})
5355
}
56+
57+
func TestValidateCheckpoint(t *testing.T) {
58+
runtimeID := common.NewTestNamespaceFromSeed([]byte("test namespace"), 0)
59+
blk := block.NewGenesisBlock(runtimeID, 0)
60+
61+
validRoot := blk.Header.StorageRootState()
62+
63+
wrongNamespaceRoot := validRoot
64+
wrongNamespaceRoot.Namespace = common.NewTestNamespaceFromSeed([]byte("test namespace invalid"), 0)
65+
66+
unexpectedRoot := validRoot
67+
unexpectedRoot.Hash[0] ^= 0xff // flip bits in the first byte so that hashes don't match.
68+
69+
for _, tc := range []struct {
70+
name string
71+
root node.Root
72+
errPrefix string
73+
}{
74+
{
75+
name: "valid root",
76+
root: validRoot,
77+
},
78+
{
79+
name: "namespace mismatch",
80+
root: wrongNamespaceRoot,
81+
errPrefix: "namespace mismatch:",
82+
},
83+
{
84+
name: "unexpected root",
85+
root: unexpectedRoot,
86+
errPrefix: "checkpoint metadata with unexpected root",
87+
},
88+
} {
89+
t.Run(tc.name, func(t *testing.T) {
90+
cp := &checkpointsync.Checkpoint{
91+
Metadata: &checkpoint.Metadata{
92+
Root: tc.root,
93+
},
94+
}
95+
96+
err := validateCheckpoint(cp, blk)
97+
if tc.errPrefix == "" {
98+
assert.NoError(t, err)
99+
return
100+
}
101+
assert.ErrorContains(t, err, tc.errPrefix)
102+
})
103+
}
104+
}

0 commit comments

Comments
 (0)