From a4e71174297138fdb4d25240fafe4b90abfc656d Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Sun, 27 Sep 2020 17:52:26 -0400 Subject: [PATCH 1/3] Add lotus shed util to validate a tipset --- api/api_full.go | 3 ++ api/apistruct/struct.go | 5 ++++ chain/store/store.go | 10 +++++++ cmd/lotus-shed/main.go | 1 + cmd/lotus-shed/sync.go | 64 +++++++++++++++++++++++++++++++++++++++++ node/impl/full/sync.go | 26 +++++++++++++++++ 6 files changed, 109 insertions(+) create mode 100644 cmd/lotus-shed/sync.go diff --git a/api/api_full.go b/api/api_full.go index 6d2d0c7b5..0e5622f4c 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -176,6 +176,9 @@ type FullNode interface { // the reason. SyncCheckBad(ctx context.Context, bcid cid.Cid) (string, error) + // SyncValidateTipset indicates whether the provided tipset is valid or not + SyncValidateTipset(ctx context.Context, tsk types.TipSetKey) (bool, error) + // MethodGroup: Mpool // The Mpool methods are for interacting with the message pool. The message pool // manages all incoming and outgoing 'messages' going over the network. diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index d5b6950ad..cc2b8b5b5 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -112,6 +112,7 @@ type FullNodeStruct struct { SyncMarkBad func(ctx context.Context, bcid cid.Cid) error `perm:"admin"` SyncUnmarkBad func(ctx context.Context, bcid cid.Cid) error `perm:"admin"` SyncCheckBad func(ctx context.Context, bcid cid.Cid) (string, error) `perm:"read"` + SyncValidateTipset func(ctx context.Context, tsk types.TipSetKey) (bool, error) `perm:"read"` MpoolGetConfig func(context.Context) (*types.MpoolConfig, error) `perm:"read"` MpoolSetConfig func(context.Context, *types.MpoolConfig) error `perm:"write"` @@ -735,6 +736,10 @@ func (c *FullNodeStruct) SyncCheckBad(ctx context.Context, bcid cid.Cid) (string return c.Internal.SyncCheckBad(ctx, bcid) } +func (c *FullNodeStruct) SyncValidateTipset(ctx context.Context, tsk types.TipSetKey) (bool, error) { + return c.Internal.SyncValidateTipset(ctx, tsk) +} + func (c *FullNodeStruct) StateNetworkName(ctx context.Context) (dtypes.NetworkName, error) { return c.Internal.StateNetworkName(ctx) } diff --git a/chain/store/store.go b/chain/store/store.go index 6c93db7a0..0806fb921 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -286,6 +286,16 @@ func (cs *ChainStore) MarkBlockAsValidated(ctx context.Context, blkid cid.Cid) e return nil } +func (cs *ChainStore) UnmarkBlockAsValidated(ctx context.Context, blkid cid.Cid) error { + key := blockValidationCacheKeyPrefix.Instance(blkid.String()) + + if err := cs.ds.Delete(key); err != nil { + return xerrors.Errorf("removing from valid block cache: %w", err) + } + + return nil +} + func (cs *ChainStore) SetGenesis(b *types.BlockHeader) error { ts, err := types.NewTipSet([]*types.BlockHeader{b}) if err != nil { diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index c7ded7a25..3864d3014 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -38,6 +38,7 @@ func main() { exportChainCmd, consensusCmd, serveDealStatsCmd, + syncCmd, } app := &cli.App{ diff --git a/cmd/lotus-shed/sync.go b/cmd/lotus-shed/sync.go new file mode 100644 index 000000000..bfe7cc8b7 --- /dev/null +++ b/cmd/lotus-shed/sync.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" + "github.com/urfave/cli/v2" +) + +var syncCmd = &cli.Command{ + Name: "sync", + Usage: "tools for diagnosing sync issues", + Flags: []cli.Flag{}, + Subcommands: []*cli.Command{ + syncValidateCmd, + }, +} + +var syncValidateCmd = &cli.Command{ + Name: "validate", + Usage: "checks whether a provided tipset is valid", + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + + defer closer() + ctx := lcli.ReqContext(cctx) + + if cctx.Args().Len() < 1 { + fmt.Println("usage: ...") + fmt.Println("At least one block cid must be provided") + return nil + } + + args := cctx.Args().Slice() + + var tscids []cid.Cid + for _, s := range args { + c, err := cid.Decode(s) + if err != nil { + return fmt.Errorf("block cid was invalid: %s", err) + } + tscids = append(tscids, c) + } + + tsk := types.NewTipSetKey(tscids...) + + valid, err := api.SyncValidateTipset(ctx, tsk) + if err != nil { + fmt.Println("Tipset is invalid: ", err) + } + + if valid { + fmt.Println("Tipset is valid") + } + + return nil + }, +} diff --git a/node/impl/full/sync.go b/node/impl/full/sync.go index dc3bfe230..221942673 100644 --- a/node/impl/full/sync.go +++ b/node/impl/full/sync.go @@ -126,3 +126,29 @@ func (a *SyncAPI) SyncCheckBad(ctx context.Context, bcid cid.Cid) (string, error return reason, nil } + +func (a *SyncAPI) SyncValidateTipset(ctx context.Context, tsk types.TipSetKey) (bool, error) { + ts, err := a.Syncer.ChainStore().LoadTipSet(tsk) + if err != nil { + return false, err + } + + fts, err := a.Syncer.ChainStore().TryFillTipSet(ts) + if err != nil { + return false, err + } + + for _, blk := range tsk.Cids() { + err = a.Syncer.ChainStore().UnmarkBlockAsValidated(ctx, blk) + if err != nil { + return false, err + } + } + + err = a.Syncer.ValidateTipSet(ctx, fts) + if err != nil { + return false, err + } + + return true, nil +} From 73d193bd9cd1b4d5e9355058febed293087a9364 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Sun, 27 Sep 2020 18:08:58 -0400 Subject: [PATCH 2/3] Update docs --- documentation/en/api-methods.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/documentation/en/api-methods.md b/documentation/en/api-methods.md index ed082ccbf..29271bdd5 100644 --- a/documentation/en/api-methods.md +++ b/documentation/en/api-methods.md @@ -169,6 +169,7 @@ * [SyncState](#SyncState) * [SyncSubmitBlock](#SyncSubmitBlock) * [SyncUnmarkBad](#SyncUnmarkBad) + * [SyncValidateTipset](#SyncValidateTipset) * [Wallet](#Wallet) * [WalletBalance](#WalletBalance) * [WalletDefaultAddress](#WalletDefaultAddress) @@ -4379,6 +4380,28 @@ Inputs: Response: `{}` +### SyncValidateTipset +SyncValidateTipset indicates whether the provided tipset is valid or not + + +Perms: read + +Inputs: +```json +[ + [ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + { + "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" + } + ] +] +``` + +Response: `true` + ## Wallet From c45c8f34a16605a087bd4c6b5b022f8828784156 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 30 Sep 2020 01:39:06 -0400 Subject: [PATCH 3/3] Parametrise whether sync validators should use cache --- chain/sync.go | 28 ++++++++++++++++------------ chain/sync_test.go | 4 ++-- node/impl/full/sync.go | 9 +-------- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/chain/sync.go b/chain/sync.go index 78e5178d1..b2e3bb7f1 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -597,7 +597,7 @@ func isPermanent(err error) bool { return !errors.Is(err, ErrTemporal) } -func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet) error { +func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet, useCache bool) error { ctx, span := trace.StartSpan(ctx, "validateTipSet") defer span.End() @@ -613,7 +613,7 @@ func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet) b := b // rebind to a scoped variable futures = append(futures, async.Err(func() error { - if err := syncer.ValidateBlock(ctx, b); err != nil { + if err := syncer.ValidateBlock(ctx, b, useCache); err != nil { if isPermanent(err) { syncer.bad.Add(b.Cid(), NewBadBlockReason([]cid.Cid{b.Cid()}, err.Error())) } @@ -680,7 +680,7 @@ func blockSanityChecks(h *types.BlockHeader) error { } // ValidateBlock should match up with 'Semantical Validation' in validation.md in the spec -func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (err error) { +func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock, useCache bool) (err error) { defer func() { // b.Cid() could panic for empty blocks that are used in tests. if rerr := recover(); rerr != nil { @@ -689,13 +689,15 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (er } }() - isValidated, err := syncer.store.IsBlockValidated(ctx, b.Cid()) - if err != nil { - return xerrors.Errorf("check block validation cache %s: %w", b.Cid(), err) - } + if useCache { + isValidated, err := syncer.store.IsBlockValidated(ctx, b.Cid()) + if err != nil { + return xerrors.Errorf("check block validation cache %s: %w", b.Cid(), err) + } - if isValidated { - return nil + if isValidated { + return nil + } } validationStart := build.Clock.Now() @@ -959,8 +961,10 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (er return mulErr } - if err := syncer.store.MarkBlockAsValidated(ctx, b.Cid()); err != nil { - return xerrors.Errorf("caching block validation %s: %w", b.Cid(), err) + if useCache { + if err := syncer.store.MarkBlockAsValidated(ctx, b.Cid()); err != nil { + return xerrors.Errorf("caching block validation %s: %w", b.Cid(), err) + } } return nil @@ -1462,7 +1466,7 @@ func (syncer *Syncer) syncMessagesAndCheckState(ctx context.Context, headers []* return syncer.iterFullTipsets(ctx, headers, func(ctx context.Context, fts *store.FullTipSet) error { log.Debugw("validating tipset", "height", fts.TipSet().Height(), "size", len(fts.TipSet().Cids())) - if err := syncer.ValidateTipSet(ctx, fts); err != nil { + if err := syncer.ValidateTipSet(ctx, fts, true); err != nil { log.Errorf("failed to validate tipset: %+v", err) return xerrors.Errorf("message processing failed: %w", err) } diff --git a/chain/sync_test.go b/chain/sync_test.go index 7a839be2b..1b06f604b 100644 --- a/chain/sync_test.go +++ b/chain/sync_test.go @@ -732,7 +732,7 @@ func TestSyncInputs(t *testing.T) { err := s.ValidateBlock(context.TODO(), &types.FullBlock{ Header: &types.BlockHeader{}, - }) + }, false) if err == nil { t.Fatal("should error on empty block") } @@ -741,7 +741,7 @@ func TestSyncInputs(t *testing.T) { h.ElectionProof = nil - err = s.ValidateBlock(context.TODO(), &types.FullBlock{Header: h}) + err = s.ValidateBlock(context.TODO(), &types.FullBlock{Header: h}, false) if err == nil { t.Fatal("should error on block with nil election proof") } diff --git a/node/impl/full/sync.go b/node/impl/full/sync.go index 221942673..1bd3af415 100644 --- a/node/impl/full/sync.go +++ b/node/impl/full/sync.go @@ -138,14 +138,7 @@ func (a *SyncAPI) SyncValidateTipset(ctx context.Context, tsk types.TipSetKey) ( return false, err } - for _, blk := range tsk.Cids() { - err = a.Syncer.ChainStore().UnmarkBlockAsValidated(ctx, blk) - if err != nil { - return false, err - } - } - - err = a.Syncer.ValidateTipSet(ctx, fts) + err = a.Syncer.ValidateTipSet(ctx, fts, false) if err != nil { return false, err }