Skip to content

Commit 0c0b158

Browse files
committed
[WIP] test mining on a forked chain
1 parent e1a0572 commit 0c0b158

File tree

4 files changed

+106
-11
lines changed

4 files changed

+106
-11
lines changed

chain/checkpoint.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ func (syncer *Syncer) SyncCheckpoint(ctx context.Context, tsk types.TipSetKey) e
1313
return xerrors.Errorf("called with empty tsk")
1414
}
1515

16+
// XXX: I think this change here is correct (we won't have a tipset unless we have all parents) but:
17+
// 1. I'm not sure.
18+
// 2. The sync code is dumb and should look at what we have. Actually, it does look at what
19+
// we have... then it seems to ignore it?
20+
//
21+
// Without this change, the test fails because we can't sync the chain we already have!
1622
ts, err := syncer.ChainStore().LoadTipSet(ctx, tsk)
1723
if err != nil {
1824
tss, err := syncer.Exchange.GetBlocks(ctx, tsk, 1)
@@ -22,18 +28,18 @@ func (syncer *Syncer) SyncCheckpoint(ctx context.Context, tsk types.TipSetKey) e
2228
return xerrors.Errorf("expected 1 tipset, got %d", len(tss))
2329
}
2430
ts = tss[0]
25-
}
2631

27-
hts := syncer.ChainStore().GetHeaviestTipSet()
28-
if !hts.Equals(ts) {
29-
if anc, err := syncer.store.IsAncestorOf(ctx, ts, hts); err != nil {
30-
return xerrors.Errorf("failed to walk the chain when checkpointing: %w", err)
31-
} else if !anc {
32-
if err := syncer.collectChain(ctx, ts, hts, true); err != nil {
33-
return xerrors.Errorf("failed to collect chain for checkpoint: %w", err)
34-
}
35-
} // else new checkpoint is on the current chain, we definitely have the tipsets.
36-
} // else current head, no need to switch.
32+
hts := syncer.ChainStore().GetHeaviestTipSet()
33+
if !hts.Equals(ts) {
34+
if anc, err := syncer.store.IsAncestorOf(ctx, ts, hts); err != nil {
35+
return xerrors.Errorf("failed to walk the chain when checkpointing: %w", err)
36+
} else if !anc {
37+
if err := syncer.collectChain(ctx, ts, hts, true); err != nil {
38+
return xerrors.Errorf("failed to collect chain for checkpoint: %w", err)
39+
}
40+
} // else new checkpoint is on the current chain, we definitely have the tipsets.
41+
} // else current head, no need to switch.
42+
}
3743

3844
if err := syncer.ChainStore().SetCheckpoint(ctx, ts); err != nil {
3945
return xerrors.Errorf("failed to set the chain checkpoint: %w", err)

itests/checkpoint_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package itests
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/filecoin-project/go-state-types/abi"
9+
"github.com/filecoin-project/lotus/chain/types"
10+
"github.com/filecoin-project/lotus/itests/kit"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestCheckpointFork(t *testing.T) {
15+
ctx := context.Background()
16+
17+
blocktime := 100 * time.Millisecond
18+
// Create three miners.
19+
miners := make([]*kit.TestMiner, 3)
20+
n1, ens := kit.EnsembleOneMany(t,
21+
miners,
22+
kit.MockProofs(),
23+
kit.ThroughRPC(),
24+
)
25+
26+
// Start 2 of them.
27+
ens.InterconnectAll().BeginMining(blocktime, miners[:2]...)
28+
29+
{
30+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
31+
n1.WaitTillChain(ctx, kit.HeightAtLeast(abi.ChainEpoch(5)))
32+
cancel()
33+
}
34+
35+
// Wait till both participate in a single tipset.
36+
var target *types.TipSet
37+
{
38+
// find the first tipset where two miners mine a block.
39+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
40+
target = n1.WaitTillChain(ctx, func(ts *types.TipSet) bool {
41+
return len(ts.Blocks()) == 2
42+
})
43+
cancel()
44+
}
45+
46+
// Wait till we've moved on from that tipset.
47+
targetHeight := target.Height() + 10
48+
n1.WaitTillChain(ctx, kit.HeightAtLeast(targetHeight))
49+
50+
// Forcibly sync to this fork tipset.
51+
forkTs, err := types.NewTipSet(target.Blocks()[:1])
52+
require.NoError(t, err)
53+
require.NoError(t, n1.SyncCheckpoint(ctx, forkTs.Key()))
54+
ens.BeginMining(blocktime, miners[2])
55+
56+
// See if we can start making progress again!
57+
newHead := n1.WaitTillChain(ctx, kit.HeightAtLeast(targetHeight))
58+
forkTs2, err := n1.ChainGetTipSetByHeight(ctx, forkTs.Height(), newHead.Key())
59+
require.NoError(t, err)
60+
require.True(t, forkTs.Equals(forkTs2))
61+
}

itests/kit/ensemble_presets.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,28 @@ func EnsembleOneTwo(t *testing.T, opts ...interface{}) (*TestFullNode, *TestMine
7777
return &full, &one, &two, ens
7878
}
7979

80+
// EnsembleOneMany and starts an Ensemble with one full node and fills the miner slice with miners.
81+
// It does not interconnect nodes nor does it begin mining.
82+
//
83+
// This function supports passing both ensemble and node functional options.
84+
// Functional options are applied to all nodes.
85+
func EnsembleOneMany(t *testing.T, miners []*TestMiner, opts ...interface{}) (*TestFullNode, *Ensemble) {
86+
opts = append(opts, WithAllSubsystems())
87+
88+
eopts, nopts := siftOptions(t, opts)
89+
90+
var full TestFullNode
91+
ens := NewEnsemble(t, eopts...).FullNode(&full, nopts...)
92+
for i := range miners {
93+
var m TestMiner
94+
ens.Miner(&m, &full, nopts...)
95+
miners[i] = &m
96+
}
97+
ens.Start()
98+
99+
return &full, ens
100+
}
101+
80102
func siftOptions(t *testing.T, opts []interface{}) (eopts []EnsembleOpt, nopts []NodeOpt) {
81103
for _, v := range opts {
82104
switch o := v.(type) {

miner/miner.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,18 +348,24 @@ minerLoop:
348348
if err != nil {
349349
log.Errorf("<!!> SLASH FILTER ERRORED: %s", err)
350350
// Continue here, because it's _probably_ wiser to not submit this block
351+
base.NullRounds++
352+
// XXX: WAIT?
351353
continue
352354
}
353355

354356
if fault {
355357
log.Errorf("<!!> SLASH FILTER DETECTED FAULT due to blocks %s and %s", b.Header.Cid(), witness)
358+
base.NullRounds++
359+
// XXX: WAIT?
356360
continue
357361
}
358362
}
359363

360364
// Check for blocks created at the same height.
361365
if _, ok := m.minedBlockHeights.Get(b.Header.Height); ok {
362366
log.Warnw("Created a block at the same height as another block we've created", "height", b.Header.Height, "miner", b.Header.Miner, "parents", b.Header.Parents)
367+
base.NullRounds++
368+
// XXX: WAIT?
363369
continue
364370
}
365371

0 commit comments

Comments
 (0)