Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement a tooling for slasher #6119

Merged
merged 1 commit into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ func (node *Node) ConfigModule() *configModule.ConfigModule {
return node.configModule
}

func (node *Node) Sync() *syncer2.SyncerSubmodule {
return node.syncer
}

func (node *Node) Repo() repo.Repo {
return node.repo
}
Expand Down
13 changes: 10 additions & 3 deletions app/submodule/syncer/syncer_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,16 @@ func (sa *syncerAPI) SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) e
}

if !constants.NoSlashFilter {
if err := sa.syncer.SlashFilter.MinedBlock(ctx, blk.Header, parent.Height); err != nil {
log.Errorf("<!!> SLASH FILTER ERROR: %s", err)
return fmt.Errorf("<!!> SLASH FILTER ERROR: %v", err)
witness, fault, err := sa.syncer.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 fmt.Errorf("<!!> SLASH FILTER ERRORED: %w", err)
}

if fault {
log.Errorf("<!!> SLASH FILTER DETECTED FAULT due to witness %s", witness)
return fmt.Errorf("<!!> SLASH FILTER DETECTED FAULT due to witness %s", witness)
}
}

Expand Down
8 changes: 8 additions & 0 deletions cmd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
types2 "github.com/filecoin-project/venus/venus-shared/actors/types"
"github.com/filecoin-project/venus/venus-shared/utils"

"github.com/filecoin-project/venus/pkg/chainsync/slashfilter"
"github.com/filecoin-project/venus/pkg/util/ulimit"

paramfetch "github.com/filecoin-project/go-paramfetch"
Expand Down Expand Up @@ -295,6 +296,13 @@ func daemonRun(req *cmds.Request, re cmds.ResponseEmitter) error {
_ = re.Emit("--" + ELStdout + " option is deprecated\n")
}

if config.FaultReporter.EnableConsensusFaultReporter {
if err := slashfilter.SlashConsensus(req.Context, config.FaultReporter, fcn.Wallet().API(),
fcn.Chain().API(), fcn.Mpool().API(), fcn.Sync().API()); err != nil {
return fmt.Errorf("run consensus fault reporter failed: %v", err)
}
}

// Start the node.
if err := fcn.Start(req.Context); err != nil {
return err
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ require (
github.com/ipfs/go-cid v0.4.1
github.com/ipfs/go-datastore v0.6.0
github.com/ipfs/go-ds-badger2 v0.1.3
github.com/ipfs/go-ds-leveldb v0.5.0
github.com/ipfs/go-fs-lock v0.0.7
github.com/ipfs/go-graphsync v0.14.6
github.com/ipfs/go-ipfs-blockstore v1.3.0
Expand Down Expand Up @@ -98,6 +99,7 @@ require (
github.com/raulk/clock v1.1.0
github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e
github.com/stretchr/testify v1.8.3
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa
github.com/whyrusleeping/go-logging v0.0.1
github.com/whyrusleeping/go-sysinfo v0.0.0-20190219211824-4a357d4b90b1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,7 @@ github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIyk
github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8=
github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s=
github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s=
github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo=
github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82lquxrVbml/Q=
github.com/ipfs/go-ds-measure v0.2.0/go.mod h1:SEUD/rE2PwRa4IQEC5FuNAmjJCyYObZr9UvVh8V3JxE=
github.com/ipfs/go-fetcher v1.5.0/go.mod h1:5pDZ0393oRF/fHiLmtFZtpMNBQfHOYNPtryWedVuSWE=
Expand Down Expand Up @@ -2289,6 +2290,7 @@ github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t
github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs=
github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/texttheater/golang-levenshtein v0.0.0-20180516184445-d188e65d659e h1:T5PdfK/M1xyrHwynxMIVMWLS7f/qHwfslZphxtGnw7s=
Expand Down
51 changes: 31 additions & 20 deletions pkg/chainsync/slashfilter/mysqldb.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"github.com/filecoin-project/venus/venus-shared/types"
)

var log = logging.Logger("mysql")
var log = logging.Logger("slashfilter")

type MysqlSlashFilter struct {
_db *gorm.DB
Expand Down Expand Up @@ -66,53 +66,63 @@ func NewMysqlSlashFilter(cfg config.MySQLConfig) (ISlashFilter, error) {
}

// checkSameHeightFault check whether the miner mined multi block on the same height
func (f *MysqlSlashFilter) checkSameHeightFault(bh *types.BlockHeader) error {
func (f *MysqlSlashFilter) checkSameHeightFault(bh *types.BlockHeader) (cid.Cid, bool, error) {
var bk MinedBlock
err := f._db.Model(&MinedBlock{}).Take(&bk, "miner=? and epoch=?", bh.Miner.String(), bh.Height).Error
if err == gorm.ErrRecordNotFound {
return nil
return cid.Undef, false, nil
}

other, err := cid.Decode(bk.Cid)
if err != nil {
return err
return cid.Undef, false, err
}

if other == bh.Cid() {
return nil
return cid.Undef, false, nil
}
log.Infof("produced block would trigger double-fork mining faults consensus fault; miner: %s; bh: %s, other: %s", bh.Miner, bh.Cid(), other)

return fmt.Errorf("produced block would trigger double-fork mining faults consensus fault; miner: %s; bh: %s, other: %s", bh.Miner, bh.Cid(), other)
return other, true, nil
}

// checkSameParentFault check whether the miner mined block on the same parent
func (f *MysqlSlashFilter) checkSameParentFault(bh *types.BlockHeader) error {
func (f *MysqlSlashFilter) checkSameParentFault(bh *types.BlockHeader) (cid.Cid, bool, error) {
var bk MinedBlock
err := f._db.Model(&MinedBlock{}).Take(&bk, "miner=? and parent_key=?", bh.Miner.String(), types.NewTipSetKey(bh.Parents...).String()).Error
if err == gorm.ErrRecordNotFound {
return nil
return cid.Undef, false, nil
}

other, err := cid.Decode(bk.Cid)
if err != nil {
return err
return cid.Undef, false, err
}

if other == bh.Cid() {
return nil
return cid.Undef, false, nil
}

return fmt.Errorf("produced block would trigger time-offset mining faults consensus fault; miner: %s; bh: %s, other: %s", bh.Miner, bh.Cid(), other)
log.Infof("produced block would trigger time-offset mining faults consensus fault; miner: %s; bh: %s, other: %s", bh.Miner, bh.Cid(), other)
return other, true, nil
}

// MinedBlock check whether the block mined is slash
func (f *MysqlSlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader, parentEpoch abi.ChainEpoch) error {
if err := f.checkSameHeightFault(bh); err != nil {
return err
func (f *MysqlSlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader, parentEpoch abi.ChainEpoch) (cid.Cid, bool, error) {
doubleForkWitness, doubleForkFault, err := f.checkSameHeightFault(bh)
if err != nil {
return cid.Undef, false, fmt.Errorf("check double-fork mining faults: %w", err)
}
if doubleForkFault {
return doubleForkWitness, doubleForkFault, nil
}

if err := f.checkSameParentFault(bh); err != nil {
return err
timeOffsetWitness, timeOffsetFault, err := f.checkSameParentFault(bh)
if err != nil {
return cid.Undef, false, fmt.Errorf("check time-offset mining faults: %w", err)
}
if timeOffsetFault {
return timeOffsetWitness, timeOffsetFault, nil
}

{
Expand All @@ -125,7 +135,7 @@ func (f *MysqlSlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader
// if exit
parent, err := cid.Decode(bk.Cid)
if err != nil {
return err
return cid.Undef, false, err
}

var found bool
Expand All @@ -136,16 +146,17 @@ func (f *MysqlSlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader
}

if !found {
return fmt.Errorf("produced block would trigger 'parent-grinding fault' consensus fault; miner: %s; bh: %s, expected parent: %s", bh.Miner, bh.Cid(), parent)
log.Infof("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
}
} else if err != gorm.ErrRecordNotFound {
// other error except not found
return err
return cid.Undef, false, err
}
// if not exit good block
}

return f._db.Save(&MinedBlock{
return cid.Undef, false, f._db.Save(&MinedBlock{
ParentEpoch: int64(parentEpoch),
ParentKey: types.NewTipSetKey(bh.Parents...).String(),
Epoch: int64(bh.Height),
Expand Down
52 changes: 32 additions & 20 deletions pkg/chainsync/slashfilter/slashfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

// ISlashFilter used to detect whether the miner mined a invalidated block , support local db and mysql storage
type ISlashFilter interface {
MinedBlock(ctx context.Context, bh *types.BlockHeader, parentEpoch abi.ChainEpoch) error
MinedBlock(ctx context.Context, bh *types.BlockHeader, parentEpoch abi.ChainEpoch) (cid.Cid, bool, error)
}

// LocalSlashFilter use badger db to save mined block for detect slash consensus block
Expand All @@ -32,20 +32,30 @@ func NewLocalSlashFilter(dstore ds.Batching) ISlashFilter {
}

// MinedBlock check whether the block mined is slash
func (f *LocalSlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader, parentEpoch abi.ChainEpoch) error {
func (f *LocalSlashFilter) 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 err := checkFault(ctx, f.byEpoch, epochKey, bh, "double-fork mining faults"); err != nil {
return err
doubleForkWitness, doubleForkFault, err := checkFault(ctx, f.byEpoch, epochKey, bh, "double-fork mining faults")
if err != nil {
return cid.Undef, false, fmt.Errorf("check double-fork mining faults: %w", err)
}

if doubleForkFault {
return doubleForkWitness, doubleForkFault, nil
}
}

parentsKey := ds.NewKey(fmt.Sprintf("/%s/%s", bh.Miner, types.NewTipSetKey(bh.Parents...).String()))
{
// time-offset mining faults (2 blocks with the same parents)
if err := checkFault(ctx, f.byParents, parentsKey, bh, "time-offset mining faults"); err != nil {
return err
timeOffsetWitness, timeOffsetFault, err := checkFault(ctx, f.byParents, parentsKey, bh, "time-offset mining faults")
if err != nil {
return cid.Undef, false, fmt.Errorf("check time-offset mining faults: %w", err)
}

if timeOffsetFault {
return timeOffsetWitness, timeOffsetFault, nil
}
}

Expand All @@ -56,19 +66,19 @@ func (f *LocalSlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader
parentEpochKey := ds.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, parentEpoch))
have, err := f.byEpoch.Has(ctx, parentEpochKey)
if err != nil {
return err
return cid.Undef, false, 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 fmt.Errorf("getting other block cid: %w", err)
return cid.Undef, false, fmt.Errorf("getting other block cid: %w", err)
}

_, parent, err := cid.CidFromBytes(cidb)
if err != nil {
return err
return cid.Undef, false, err
}

var found bool
Expand All @@ -79,45 +89,47 @@ func (f *LocalSlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader
}

if !found {
return fmt.Errorf("produced block would trigger 'parent-grinding fault' consensus fault; miner: %s; bh: %s, expected parent: %s", bh.Miner, bh.Cid(), parent)
log.Infof("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 fmt.Errorf("putting byParents entry: %w", err)
return cid.Undef, false, fmt.Errorf("putting byParents entry: %w", err)
}

if err := f.byEpoch.Put(ctx, epochKey, bh.Cid().Bytes()); err != nil {
return fmt.Errorf("putting byEpoch entry: %w", err)
return cid.Undef, false, fmt.Errorf("putting byEpoch entry: %w", err)
}

return nil
return cid.Undef, false, nil
}

func checkFault(ctx context.Context, t ds.Datastore, key ds.Key, bh *types.BlockHeader, faultType string) 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 err
return cid.Undef, false, fmt.Errorf("failed to read from datastore: %w", err)
}

if fault {
cidb, err := t.Get(ctx, key)
if err != nil {
return fmt.Errorf("getting other block cid: %w", err)
return cid.Undef, false, fmt.Errorf("getting other block cid: %w", err)
}

_, other, err := cid.CidFromBytes(cidb)
if err != nil {
return err
return cid.Undef, false, err
}

if other == bh.Cid() {
return nil
return cid.Undef, false, nil
}

return fmt.Errorf("produced block would trigger '%s' consensus fault; miner: %s; bh: %s, other: %s", faultType, bh.Miner, bh.Cid(), other)
log.Infof("produced block would trigger '%s' consensus fault; miner: %s; bh: %s, other: %s", faultType, bh.Miner, bh.Cid(), other)
return other, true, nil
}

return nil
return cid.Undef, false, nil
}
Loading