From 231b2070d4bf9d1492536817454c3b7467738d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sat, 25 Nov 2023 22:29:57 +0100 Subject: [PATCH] feat: chain import: don't walk to genesis - 2-3x faster snapshot import (#11446) * chain import: don't walk to genesis * fix daemon build * fast snapshot genesis: address review --- chain/store/index_test.go | 2 +- chain/store/snapshot.go | 46 +++++++++++++++++++++++++------- chain/store/store_test.go | 6 ++--- cmd/lotus-bench/import.go | 2 +- cmd/lotus-shed/genesis-verify.go | 2 +- cmd/lotus/daemon.go | 12 +++------ 6 files changed, 47 insertions(+), 23 deletions(-) diff --git a/chain/store/index_test.go b/chain/store/index_test.go index a3a4ad6ce7e..3cde400626a 100644 --- a/chain/store/index_test.go +++ b/chain/store/index_test.go @@ -42,7 +42,7 @@ func TestIndexSeeks(t *testing.T) { cs := store.NewChainStore(nbs, nbs, syncds.MutexWrap(datastore.NewMapDatastore()), filcns.Weight, nil) defer cs.Close() //nolint:errcheck - _, err = cs.Import(ctx, bytes.NewReader(gencar)) + _, _, err = cs.Import(ctx, bytes.NewReader(gencar)) if err != nil { t.Fatal(err) } diff --git a/chain/store/snapshot.go b/chain/store/snapshot.go index 301a5f87bfd..de2190c5d7a 100644 --- a/chain/store/snapshot.go +++ b/chain/store/snapshot.go @@ -60,7 +60,7 @@ func (cs *ChainStore) Export(ctx context.Context, ts *types.TipSet, inclRecentRo }) } -func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, error) { +func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (head *types.TipSet, genesis *types.BlockHeader, err error) { // TODO: writing only to the state blockstore is incorrect. // At this time, both the state and chain blockstores are backed by the // universal store. When we physically segregate the stores, we will need @@ -69,7 +69,7 @@ func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, e br, err := carv2.NewBlockReader(r) if err != nil { - return nil, xerrors.Errorf("loadcar failed: %w", err) + return nil, nil, xerrors.Errorf("loadcar failed: %w", err) } s := cs.StateBlockstore() @@ -80,27 +80,51 @@ func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, e putThrottle <- nil } + if len(br.Roots) == 0 { + return nil, nil, xerrors.Errorf("no roots in snapshot car file") + } + nextTailCid := br.Roots[0] + + var tailBlock types.BlockHeader + tailBlock.Height = abi.ChainEpoch(-1) + var buf []blocks.Block for { blk, err := br.Next() if err != nil { + + // we're at the end if err == io.EOF { if len(buf) > 0 { if err := s.PutMany(ctx, buf); err != nil { - return nil, err + return nil, nil, err } } break } - return nil, err + return nil, nil, err + } + + // check for header block, looking for genesis + if blk.Cid() == nextTailCid && tailBlock.Height != 0 { + if err := tailBlock.UnmarshalCBOR(bytes.NewReader(blk.RawData())); err != nil { + return nil, nil, xerrors.Errorf("failed to unmarshal genesis block: %w", err) + } + if len(tailBlock.Parents) > 0 { + nextTailCid = tailBlock.Parents[0] + } else { + // note: even the 0th block has a parent linking to the cbor genesis block + return nil, nil, xerrors.Errorf("current block (epoch %d cid %s) has no parents", tailBlock.Height, tailBlock.Cid()) + } } + // append to batch buf = append(buf, blk) if len(buf) > 1000 { if lastErr := <-putThrottle; lastErr != nil { // consume one error to have the right to add one - return nil, lastErr + return nil, nil, lastErr } go func(buf []blocks.Block) { @@ -113,13 +137,17 @@ func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, e // check errors for i := 0; i < parallelPuts; i++ { if lastErr := <-putThrottle; lastErr != nil { - return nil, lastErr + return nil, nil, lastErr } } + if tailBlock.Height != 0 { + return nil, nil, xerrors.Errorf("expected genesis block to have height 0 (genesis), got %d: %s", tailBlock.Height, tailBlock.Cid()) + } + root, err := cs.LoadTipSet(ctx, types.NewTipSetKey(br.Roots...)) if err != nil { - return nil, xerrors.Errorf("failed to load root tipset from chainfile: %w", err) + return nil, nil, xerrors.Errorf("failed to load root tipset from chainfile: %w", err) } ts := root @@ -135,10 +163,10 @@ func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, e } if err := cs.PersistTipsets(ctx, tssToPersist); err != nil { - return nil, xerrors.Errorf("failed to persist tipsets: %w", err) + return nil, nil, xerrors.Errorf("failed to persist tipsets: %w", err) } - return root, nil + return root, &tailBlock, nil } type walkSchedTaskType int diff --git a/chain/store/store_test.go b/chain/store/store_test.go index 9c717fdbef4..1ecfc474a02 100644 --- a/chain/store/store_test.go +++ b/chain/store/store_test.go @@ -118,7 +118,7 @@ func TestChainExportImport(t *testing.T) { cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), filcns.Weight, nil) defer cs.Close() //nolint:errcheck - root, err := cs.Import(context.TODO(), buf) + root, _, err := cs.Import(context.TODO(), buf) if err != nil { t.Fatal(err) } @@ -153,7 +153,7 @@ func TestChainImportTipsetKeyCid(t *testing.T) { cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), filcns.Weight, nil) defer cs.Close() //nolint:errcheck - root, err := cs.Import(ctx, buf) + root, _, err := cs.Import(ctx, buf) require.NoError(t, err) require.Truef(t, root.Equals(last), "imported chain differed from exported chain") @@ -202,7 +202,7 @@ func TestChainExportImportFull(t *testing.T) { cs := store.NewChainStore(nbs, nbs, ds, filcns.Weight, nil) defer cs.Close() //nolint:errcheck - root, err := cs.Import(context.TODO(), buf) + root, _, err := cs.Import(context.TODO(), buf) if err != nil { t.Fatal(err) } diff --git a/cmd/lotus-bench/import.go b/cmd/lotus-bench/import.go index 95b91054a75..16adbad60be 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -304,7 +304,7 @@ var importBenchCmd = &cli.Command{ return fmt.Errorf("no CAR file provided for import") } - head, err = cs.Import(cctx.Context, carFile) + head, _, err = cs.Import(cctx.Context, carFile) if err != nil { return err } diff --git a/cmd/lotus-shed/genesis-verify.go b/cmd/lotus-shed/genesis-verify.go index 6795f1528a1..4ead8467ea2 100644 --- a/cmd/lotus-shed/genesis-verify.go +++ b/cmd/lotus-shed/genesis-verify.go @@ -62,7 +62,7 @@ var genesisVerifyCmd = &cli.Command{ return xerrors.Errorf("opening the car file: %w", err) } - ts, err := cs.Import(cctx.Context, f) + ts, _, err := cs.Import(cctx.Context, f) if err != nil { return err } diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 58c326586a7..44da4139ab3 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -565,7 +565,7 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) } bar.Start() - ts, err := cst.Import(ctx, ir) + ts, gen, err := cst.Import(ctx, ir) bar.Finish() if err != nil { @@ -576,18 +576,14 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) return xerrors.Errorf("flushing validation cache failed: %w", err) } - gb, err := cst.GetTipsetByHeight(ctx, 0, ts, true) - if err != nil { - return err - } - - err = cst.SetGenesis(ctx, gb.Blocks()[0]) + log.Infof("setting genesis") + err = cst.SetGenesis(ctx, gen) if err != nil { return err } if !snapshot { - shd, err := drand.BeaconScheduleFromDrandSchedule(build.DrandConfigSchedule(), gb.MinTimestamp(), nil) + shd, err := drand.BeaconScheduleFromDrandSchedule(build.DrandConfigSchedule(), gen.Timestamp, nil) if err != nil { return xerrors.Errorf("failed to construct beacon schedule: %w", err) }