diff --git a/chain/gen/slashfilter/slashfilter.go b/chain/gen/slashfilter/slashfilter.go index 0e6b00cfb..71b5dad9a 100644 --- a/chain/gen/slashfilter/slashfilter.go +++ b/chain/gen/slashfilter/slashfilter.go @@ -26,20 +26,30 @@ func New(dstore ds.Batching) *SlashFilter { } } -func (f *SlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader, parentEpoch abi.ChainEpoch) (cid.Cid, error) { +func (f *SlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader, parentEpoch abi.ChainEpoch) (cid.Cid, bool, error) { epochKey := ds.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, bh.Height)) { // double-fork mining (2 blocks at one epoch) - if witness, err := checkFault(ctx, f.byEpoch, epochKey, bh, "double-fork mining faults"); err != nil { - return witness, xerrors.Errorf("check double-fork mining faults: %w", err) + doubleForkWitness, doubleForkFault, err := checkFault(ctx, f.byEpoch, epochKey, bh, "double-fork mining faults") + if err != nil { + return cid.Undef, false, xerrors.Errorf("check double-fork mining faults: %w", err) + } + + if doubleForkFault { + return doubleForkWitness, doubleForkFault, nil } } parentsKey := ds.NewKey(fmt.Sprintf("/%s/%x", bh.Miner, types.NewTipSetKey(bh.Parents...).Bytes())) { // time-offset mining faults (2 blocks with the same parents) - if witness, err := checkFault(ctx, f.byParents, parentsKey, bh, "time-offset mining faults"); err != nil { - return witness, xerrors.Errorf("check time-offset mining faults: %w", err) + timeOffsetWitness, timeOffsetFault, err := checkFault(ctx, f.byParents, parentsKey, bh, "time-offset mining faults") + if err != nil { + return cid.Undef, false, xerrors.Errorf("check time-offset mining faults: %w", err) + } + + if timeOffsetFault { + return timeOffsetWitness, timeOffsetFault, nil } } @@ -50,19 +60,19 @@ func (f *SlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader, par parentEpochKey := ds.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, parentEpoch)) have, err := f.byEpoch.Has(ctx, parentEpochKey) if err != nil { - return cid.Undef, err + return cid.Undef, false, xerrors.Errorf("failed to read from db: %w", err) } if have { // If we had, make sure it's in our parent tipset cidb, err := f.byEpoch.Get(ctx, parentEpochKey) if err != nil { - return cid.Undef, xerrors.Errorf("getting other block cid: %w", err) + return cid.Undef, false, xerrors.Errorf("getting other block cid: %w", err) } _, parent, err := cid.CidFromBytes(cidb) if err != nil { - return cid.Undef, err + return cid.Undef, false, xerrors.Errorf("failed to read cid from bytes: %w", err) } var found bool @@ -73,45 +83,45 @@ func (f *SlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader, par } if !found { - return parent, xerrors.Errorf("produced block would trigger 'parent-grinding fault' consensus fault; miner: %s; bh: %s, expected parent: %s", bh.Miner, bh.Cid(), parent) + return parent, true, nil } } } if err := f.byParents.Put(ctx, parentsKey, bh.Cid().Bytes()); err != nil { - return cid.Undef, xerrors.Errorf("putting byEpoch entry: %w", err) + return cid.Undef, false, xerrors.Errorf("putting byEpoch entry: %w", err) } if err := f.byEpoch.Put(ctx, epochKey, bh.Cid().Bytes()); err != nil { - return cid.Undef, xerrors.Errorf("putting byEpoch entry: %w", err) + return cid.Undef, false, xerrors.Errorf("putting byEpoch entry: %w", err) } - return cid.Undef, nil + return cid.Undef, false, nil } -func checkFault(ctx context.Context, t ds.Datastore, key ds.Key, bh *types.BlockHeader, faultType string) (cid.Cid, error) { +func checkFault(ctx context.Context, t ds.Datastore, key ds.Key, bh *types.BlockHeader, faultType string) (cid.Cid, bool, error) { fault, err := t.Has(ctx, key) if err != nil { - return cid.Undef, xerrors.Errorf("failed to read from datastore: %w", err) + return cid.Undef, false, xerrors.Errorf("failed to read from datastore: %w", err) } if fault { cidb, err := t.Get(ctx, key) if err != nil { - return cid.Undef, xerrors.Errorf("getting other block cid: %w", err) + return cid.Undef, false, xerrors.Errorf("getting other block cid: %w", err) } _, other, err := cid.CidFromBytes(cidb) if err != nil { - return cid.Undef, err + return cid.Undef, false, xerrors.Errorf("failed to read cid of other block: %w", err) } if other == bh.Cid() { - return cid.Undef, nil + return cid.Undef, false, nil } - return other, xerrors.Errorf("produced block would trigger '%s' consensus fault; miner: %s; bh: %s, other: %s", faultType, bh.Miner, bh.Cid(), other) + return other, true, nil } - return cid.Undef, nil + return cid.Undef, false, nil } diff --git a/miner/miner.go b/miner/miner.go index e1737009b..1caaade96 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -324,9 +324,16 @@ minerLoop: "block-time", btime, "time", build.Clock.Now(), "difference", build.Clock.Since(btime)) } - if _, err = m.sf.MinedBlock(ctx, b.Header, base.TipSet.Height()+base.NullRounds); err != nil { - log.Errorf(" SLASH FILTER ERROR: %s", err) - if os.Getenv("LOTUS_MINER_NO_SLASHFILTER") != "_yes_i_know_i_can_and_probably_will_lose_all_my_fil_and_power_" { + if os.Getenv("LOTUS_MINER_NO_SLASHFILTER") != "_yes_i_know_i_can_and_probably_will_lose_all_my_fil_and_power_" { + witness, fault, err := m.sf.MinedBlock(ctx, b.Header, base.TipSet.Height()+base.NullRounds) + if err != nil { + log.Errorf(" SLASH FILTER ERRORED: %s", err) + // Continue here, because it's _probably_ wiser to not submit this block + continue + } + + if fault { + log.Errorf(" SLASH FILTER DETECTED FAULT due to witness %s", witness) continue } } diff --git a/node/impl/full/sync.go b/node/impl/full/sync.go index 9be43338e..223f5c29e 100644 --- a/node/impl/full/sync.go +++ b/node/impl/full/sync.go @@ -58,9 +58,16 @@ func (a *SyncAPI) SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) erro } if a.SlashFilter != nil && os.Getenv("LOTUS_NO_SLASHFILTER") != "_yes_i_know_i_can_and_probably_will_lose_all_my_fil_and_power_" { - if _, err = a.SlashFilter.MinedBlock(ctx, blk.Header, parent.Height); err != nil { - log.Errorf(" SLASH FILTER ERROR: %s", err) - return xerrors.Errorf(" SLASH FILTER ERROR: %w", err) + witness, fault, err := a.SlashFilter.MinedBlock(ctx, blk.Header, parent.Height) + if err != nil { + log.Errorf(" SLASH FILTER ERRORED: %s", err) + // Return an error here, because it's _probably_ wiser to not submit this block + return xerrors.Errorf(" SLASH FILTER ERRORED: %w", err) + } + + if fault { + log.Errorf(" SLASH FILTER DETECTED FAULT due to witness %s", witness) + return xerrors.Errorf(" SLASH FILTER DETECTED FAULT due to witness %s", witness) } }