2021-03-12 11:24:09 +00:00
package main
import (
"bufio"
2021-11-03 00:19:18 +00:00
"bytes"
"fmt"
2021-03-12 11:24:09 +00:00
"io"
"os"
"path/filepath"
"strings"
2021-03-12 11:24:18 +00:00
2022-05-16 21:29:29 +00:00
"github.com/ipfs/go-cid"
2022-05-16 17:51:56 +00:00
"github.com/filecoin-project/go-bitfield"
2022-05-16 21:29:29 +00:00
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/builtin"
miner8 "github.com/filecoin-project/go-state-types/builtin/v8/miner"
2022-05-16 17:51:56 +00:00
"github.com/filecoin-project/go-state-types/crypto"
2022-05-16 21:29:29 +00:00
power7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/power"
2022-05-16 17:51:56 +00:00
"github.com/filecoin-project/specs-actors/v7/actors/runtime/proof"
2021-11-03 00:19:18 +00:00
"github.com/docker/go-units"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
"github.com/filecoin-project/lotus/chain/actors/builtin/power"
"github.com/filecoin-project/lotus/chain/types"
lcli "github.com/filecoin-project/lotus/cli"
2021-03-12 11:24:18 +00:00
"github.com/mitchellh/go-homedir"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
2021-03-12 11:24:09 +00:00
)
var minerCmd = & cli . Command {
Name : "miner" ,
Usage : "miner-related utilities" ,
Subcommands : [ ] * cli . Command {
minerUnpackInfoCmd ,
2021-11-03 00:19:18 +00:00
minerCreateCmd ,
2021-11-07 17:57:33 +00:00
minerFaultsCmd ,
2022-05-16 17:51:56 +00:00
sendInvalidWindowPoStCmd ,
2022-05-16 21:29:29 +00:00
generateAndSendConsensusFaultCmd ,
2021-11-07 17:57:33 +00:00
} ,
}
var minerFaultsCmd = & cli . Command {
Name : "faults" ,
Usage : "Display a list of faulty sectors for a SP" ,
ArgsUsage : "[minerAddress]" ,
Flags : [ ] cli . Flag {
& cli . Uint64Flag {
Name : "expiring-in" ,
Usage : "only list sectors that are expiring in the next <n> epochs" ,
Value : 0 ,
} ,
} ,
Action : func ( cctx * cli . Context ) error {
if ! cctx . Args ( ) . Present ( ) {
return fmt . Errorf ( "must pass miner address" )
}
api , closer , err := lcli . GetFullNodeAPI ( cctx )
if err != nil {
return err
}
defer closer ( )
ctx := lcli . ReqContext ( cctx )
m , err := address . NewFromString ( cctx . Args ( ) . First ( ) )
if err != nil {
return err
}
faultBf , err := api . StateMinerFaults ( ctx , m , types . EmptyTSK )
if err != nil {
return err
}
2022-05-16 21:29:29 +00:00
faults , err := faultBf . All ( abi . MaxSectorNumber )
2021-11-07 17:57:33 +00:00
if err != nil {
return err
}
if len ( faults ) == 0 {
fmt . Println ( "no faults" )
return nil
}
expEpoch := abi . ChainEpoch ( cctx . Uint64 ( "expiring-in" ) )
if expEpoch == 0 {
fmt . Print ( "faulty sectors: " )
for _ , v := range faults {
fmt . Printf ( "%d " , v )
}
return nil
}
h , err := api . ChainHead ( ctx )
if err != nil {
return err
}
fmt . Printf ( "faulty sectors expiring in the next %d epochs: " , expEpoch )
for _ , v := range faults {
ss , err := api . StateSectorExpiration ( ctx , m , abi . SectorNumber ( v ) , types . EmptyTSK )
if err != nil {
return err
}
if ss . Early < h . Height ( ) + expEpoch {
fmt . Printf ( "%d " , v )
}
}
return nil
2021-11-03 00:19:18 +00:00
} ,
}
var minerCreateCmd = & cli . Command {
Name : "create" ,
Usage : "sends a create miner msg" ,
ArgsUsage : "[sender] [owner] [worker] [sector size]" ,
Action : func ( cctx * cli . Context ) error {
wapi , closer , err := lcli . GetFullNodeAPI ( cctx )
if err != nil {
return err
}
defer closer ( )
ctx := lcli . ReqContext ( cctx )
if cctx . Args ( ) . Len ( ) != 4 {
return xerrors . Errorf ( "expected 4 args (sender owner worker sectorSize)" )
}
sender , err := address . NewFromString ( cctx . Args ( ) . First ( ) )
if err != nil {
return err
}
owner , err := address . NewFromString ( cctx . Args ( ) . Get ( 1 ) )
if err != nil {
return err
}
worker , err := address . NewFromString ( cctx . Args ( ) . Get ( 2 ) )
if err != nil {
return err
}
ssize , err := units . RAMInBytes ( cctx . Args ( ) . Get ( 3 ) )
if err != nil {
return fmt . Errorf ( "failed to parse sector size: %w" , err )
}
// make sure the sender account exists on chain
_ , err = wapi . StateLookupID ( ctx , owner , types . EmptyTSK )
if err != nil {
return xerrors . Errorf ( "sender must exist on chain: %w" , err )
}
// make sure the worker account exists on chain
_ , err = wapi . StateLookupID ( ctx , worker , types . EmptyTSK )
if err != nil {
signed , err := wapi . MpoolPushMessage ( ctx , & types . Message {
From : sender ,
To : worker ,
Value : types . NewInt ( 0 ) ,
} , nil )
if err != nil {
return xerrors . Errorf ( "push worker init: %w" , err )
}
log . Infof ( "Initializing worker account %s, message: %s" , worker , signed . Cid ( ) )
log . Infof ( "Waiting for confirmation" )
mw , err := wapi . StateWaitMsg ( ctx , signed . Cid ( ) , build . MessageConfidence )
if err != nil {
return xerrors . Errorf ( "waiting for worker init: %w" , err )
}
if mw . Receipt . ExitCode != 0 {
return xerrors . Errorf ( "initializing worker account failed: exit code %d" , mw . Receipt . ExitCode )
}
}
// make sure the owner account exists on chain
_ , err = wapi . StateLookupID ( ctx , owner , types . EmptyTSK )
if err != nil {
signed , err := wapi . MpoolPushMessage ( ctx , & types . Message {
From : sender ,
To : owner ,
Value : types . NewInt ( 0 ) ,
} , nil )
if err != nil {
return xerrors . Errorf ( "push owner init: %w" , err )
}
log . Infof ( "Initializing owner account %s, message: %s" , worker , signed . Cid ( ) )
log . Infof ( "Wating for confirmation" )
mw , err := wapi . StateWaitMsg ( ctx , signed . Cid ( ) , build . MessageConfidence )
if err != nil {
return xerrors . Errorf ( "waiting for owner init: %w" , err )
}
if mw . Receipt . ExitCode != 0 {
return xerrors . Errorf ( "initializing owner account failed: exit code %d" , mw . Receipt . ExitCode )
}
}
// Note: the correct thing to do would be to call SealProofTypeFromSectorSize if actors version is v3 or later, but this still works
spt , err := miner . WindowPoStProofTypeFromSectorSize ( abi . SectorSize ( ssize ) )
if err != nil {
return xerrors . Errorf ( "getting post proof type: %w" , err )
}
2022-05-16 21:29:29 +00:00
params , err := actors . SerializeParams ( & power7 . CreateMinerParams {
2021-11-03 00:19:18 +00:00
Owner : owner ,
Worker : worker ,
WindowPoStProofType : spt ,
} )
if err != nil {
return err
}
createStorageMinerMsg := & types . Message {
To : power . Address ,
From : sender ,
Value : big . Zero ( ) ,
Method : power . Methods . CreateMiner ,
Params : params ,
}
signed , err := wapi . MpoolPushMessage ( ctx , createStorageMinerMsg , nil )
if err != nil {
return xerrors . Errorf ( "pushing createMiner message: %w" , err )
}
log . Infof ( "Pushed CreateMiner message: %s" , signed . Cid ( ) )
log . Infof ( "Waiting for confirmation" )
mw , err := wapi . StateWaitMsg ( ctx , signed . Cid ( ) , build . MessageConfidence )
if err != nil {
return xerrors . Errorf ( "waiting for createMiner message: %w" , err )
}
if mw . Receipt . ExitCode != 0 {
return xerrors . Errorf ( "create miner failed: exit code %d" , mw . Receipt . ExitCode )
}
2022-05-16 21:29:29 +00:00
var retval power7 . CreateMinerReturn
2021-11-03 00:19:18 +00:00
if err := retval . UnmarshalCBOR ( bytes . NewReader ( mw . Receipt . Return ) ) ; err != nil {
return err
}
log . Infof ( "New miners address is: %s (%s)" , retval . IDAddress , retval . RobustAddress )
return nil
2021-03-12 11:24:09 +00:00
} ,
}
var minerUnpackInfoCmd = & cli . Command {
2021-03-12 11:24:18 +00:00
Name : "unpack-info" ,
Usage : "unpack miner info all dump" ,
2021-03-12 11:24:09 +00:00
ArgsUsage : "[allinfo.txt] [dir]" ,
Action : func ( cctx * cli . Context ) error {
if cctx . Args ( ) . Len ( ) != 2 {
return xerrors . Errorf ( "expected 2 args" )
}
src , err := homedir . Expand ( cctx . Args ( ) . Get ( 0 ) )
if err != nil {
return xerrors . Errorf ( "expand src: %w" , err )
}
f , err := os . Open ( src )
if err != nil {
return xerrors . Errorf ( "open file: %w" , err )
}
defer f . Close ( ) // nolint
dest , err := homedir . Expand ( cctx . Args ( ) . Get ( 1 ) )
if err != nil {
return xerrors . Errorf ( "expand dest: %w" , err )
}
var outf * os . File
r := bufio . NewReader ( f )
for {
2021-03-12 11:24:18 +00:00
l , _ , err := r . ReadLine ( )
2021-03-12 11:24:09 +00:00
if err == io . EOF {
if outf != nil {
return outf . Close ( )
}
}
if err != nil {
return xerrors . Errorf ( "read line: %w" , err )
}
sl := string ( l )
if strings . HasPrefix ( sl , "#" ) {
if strings . Contains ( sl , ".." ) {
return xerrors . Errorf ( "bad name %s" , sl )
}
if strings . HasPrefix ( sl , "#: " ) {
if outf != nil {
if err := outf . Close ( ) ; err != nil {
return xerrors . Errorf ( "close out file: %w" , err )
}
}
p := filepath . Join ( dest , sl [ len ( "#: " ) : ] )
if err := os . MkdirAll ( filepath . Dir ( p ) , 0775 ) ; err != nil {
return xerrors . Errorf ( "mkdir: %w" , err )
}
outf , err = os . Create ( p )
if err != nil {
return xerrors . Errorf ( "create out file: %w" , err )
}
continue
}
if strings . HasPrefix ( sl , "##: " ) {
if outf != nil {
if err := outf . Close ( ) ; err != nil {
return xerrors . Errorf ( "close out file: %w" , err )
}
}
p := filepath . Join ( dest , "Per Sector Infos" , sl [ len ( "##: " ) : ] )
if err := os . MkdirAll ( filepath . Dir ( p ) , 0775 ) ; err != nil {
return xerrors . Errorf ( "mkdir: %w" , err )
}
outf , err = os . Create ( p )
if err != nil {
return xerrors . Errorf ( "create out file: %w" , err )
}
continue
}
}
if outf != nil {
if _ , err := outf . Write ( l ) ; err != nil {
return xerrors . Errorf ( "write line: %w" , err )
}
if _ , err := outf . Write ( [ ] byte ( "\n" ) ) ; err != nil {
return xerrors . Errorf ( "write line end: %w" , err )
}
}
}
} ,
2021-03-12 11:24:18 +00:00
}
2022-05-16 17:51:56 +00:00
var sendInvalidWindowPoStCmd = & cli . Command {
Name : "send-invalid-windowed-post" ,
Usage : "Sends an invalid windowed post for a specific deadline" ,
Description : ` Note: This is meant for testing purposes and should NOT be used on mainnet or you will be slashed ` ,
Flags : [ ] cli . Flag {
& cli . BoolFlag {
Name : "really-do-it" ,
Usage : "Actually send transaction performing the action" ,
Value : false ,
} ,
2022-05-16 18:14:43 +00:00
& cli . Int64SliceFlag {
Name : "partitions" ,
Usage : "list of partitions to compact sectors in" ,
Required : true ,
} ,
2022-05-16 17:51:56 +00:00
& cli . StringFlag {
Name : "actor" ,
Usage : "TODO" ,
} ,
} ,
Action : func ( cctx * cli . Context ) error {
if ! cctx . Bool ( "really-do-it" ) {
2022-05-16 21:29:29 +00:00
return xerrors . Errorf ( "Pass --really-do-it to actually execute this action" )
2022-05-16 17:51:56 +00:00
}
api , acloser , err := lcli . GetFullNodeAPI ( cctx )
if err != nil {
2022-05-24 19:24:54 +00:00
return xerrors . Errorf ( "getting api: %w" , err )
2022-05-16 17:51:56 +00:00
}
defer acloser ( )
ctx := lcli . ReqContext ( cctx )
maddr , err := address . NewFromString ( cctx . String ( "actor" ) )
if err != nil {
2022-05-24 19:24:54 +00:00
return xerrors . Errorf ( "getting actor address: %w" , err )
2022-05-16 17:51:56 +00:00
}
minfo , err := api . StateMinerInfo ( ctx , maddr , types . EmptyTSK )
if err != nil {
2022-05-24 19:24:54 +00:00
return xerrors . Errorf ( "getting mienr info: %w" , err )
2022-05-16 17:51:56 +00:00
}
deadline , err := api . StateMinerProvingDeadline ( ctx , maddr , types . EmptyTSK )
2022-05-24 19:24:54 +00:00
if err != nil {
return xerrors . Errorf ( "getting deadline: %w" , err )
}
2022-05-16 17:51:56 +00:00
2022-05-16 18:14:43 +00:00
partitionIndices := cctx . Int64Slice ( "partitions" )
if len ( partitionIndices ) <= 0 {
return fmt . Errorf ( "must include at least one partition to compact" )
}
2022-05-16 17:51:56 +00:00
chainHead , err := api . ChainHead ( ctx )
if err != nil {
return xerrors . Errorf ( "getting chain head: %w" , err )
}
checkRand , err := api . StateGetRandomnessFromTickets ( ctx , crypto . DomainSeparationTag_PoStChainCommit , deadline . Challenge , nil , chainHead . Key ( ) )
2022-05-24 19:24:54 +00:00
if err != nil {
return xerrors . Errorf ( "getting randomness: %w" , err )
}
2022-05-16 17:51:56 +00:00
proofSize , err := minfo . WindowPoStProofType . ProofSize ( )
if err != nil {
return xerrors . Errorf ( "getting proof size: %w" , err )
}
2022-05-16 21:29:29 +00:00
var partitions [ ] miner8 . PoStPartition
2022-05-16 18:14:43 +00:00
2022-05-16 21:29:29 +00:00
emptyProof := [ ] proof . PoStProof { {
2022-05-16 18:14:43 +00:00
PoStProof : minfo . WindowPoStProofType ,
2022-05-16 21:29:29 +00:00
ProofBytes : make ( [ ] byte , proofSize ) } }
2022-05-16 18:14:43 +00:00
for _ , partition := range partitionIndices {
2022-05-16 21:29:29 +00:00
newPartition := miner8 . PoStPartition {
2022-05-16 18:14:43 +00:00
Index : uint64 ( partition ) ,
2022-05-16 17:51:56 +00:00
Skipped : bitfield . New ( ) ,
2022-05-16 18:14:43 +00:00
}
partitions = append ( partitions , newPartition )
}
2022-05-16 21:29:29 +00:00
params := miner8 . SubmitWindowedPoStParams {
2022-05-16 18:14:43 +00:00
Deadline : deadline . Index ,
Partitions : partitions ,
2022-05-16 21:29:29 +00:00
Proofs : emptyProof ,
2022-05-16 17:51:56 +00:00
ChainCommitEpoch : deadline . Challenge ,
ChainCommitRand : checkRand ,
}
sp , err := actors . SerializeParams ( & params )
if err != nil {
return xerrors . Errorf ( "serializing params: %w" , err )
}
2022-05-16 18:14:43 +00:00
fmt . Printf ( "submitting bad PoST for %d paritions\n" , len ( partitionIndices ) )
2022-05-16 17:51:56 +00:00
smsg , err := api . MpoolPushMessage ( ctx , & types . Message {
From : minfo . Worker ,
To : maddr ,
2022-05-16 21:29:29 +00:00
Method : builtin . MethodsMiner . SubmitWindowedPoSt ,
Value : big . Zero ( ) ,
Params : sp ,
} , nil )
if err != nil {
return xerrors . Errorf ( "mpool push: %w" , err )
}
fmt . Println ( "Message CID:" , smsg . Cid ( ) )
return nil
} ,
}
var generateAndSendConsensusFaultCmd = & cli . Command {
Name : "generate-and-send-consensus-fault" ,
Usage : "Provided a block CID mined by the miner, will create another block at the same height, and send both block headers to generate a consensus fault." ,
Description : ` Note: This is meant for testing purposes and should NOT be used on mainnet or you will be slashed ` ,
Flags : [ ] cli . Flag {
& cli . BoolFlag {
Name : "really-do-it" ,
Usage : "Actually send transaction performing the action" ,
Value : false ,
} ,
& cli . StringFlag {
Name : "actor" ,
Usage : "TODO" ,
} ,
} ,
Action : func ( cctx * cli . Context ) error {
if ! cctx . Bool ( "really-do-it" ) {
return xerrors . Errorf ( "Pass --really-do-it to actually execute this action" )
}
if cctx . Args ( ) . Len ( ) != 1 {
return xerrors . Errorf ( "expected 1 arg (blockCID)" )
}
blockCid , err := cid . Parse ( cctx . Args ( ) . First ( ) )
if err != nil {
2022-05-24 19:24:54 +00:00
return xerrors . Errorf ( "getting first arg: %w" , err )
2022-05-16 21:29:29 +00:00
}
api , acloser , err := lcli . GetFullNodeAPI ( cctx )
if err != nil {
2022-05-24 19:24:54 +00:00
return xerrors . Errorf ( "getting chain head: %w" , err )
2022-05-16 21:29:29 +00:00
}
defer acloser ( )
ctx := lcli . ReqContext ( cctx )
maddr , err := address . NewFromString ( cctx . String ( "actor" ) )
if err != nil {
return xerrors . Errorf ( "getting actor address: %w" , err )
}
minfo , err := api . StateMinerInfo ( ctx , maddr , types . EmptyTSK )
if err != nil {
return xerrors . Errorf ( "getting miner info: %w" , err )
}
blockHeader , err := api . ChainGetBlock ( ctx , blockCid )
2022-05-24 19:24:54 +00:00
if err != nil {
return xerrors . Errorf ( "getting block header: %w" , err )
}
2022-05-16 21:29:29 +00:00
blockHeaderCopy := * blockHeader
blockHeaderCopy . ForkSignaling = blockHeader . ForkSignaling + 1
signingBytes , err := blockHeaderCopy . SigningBytes ( )
if err != nil {
return xerrors . Errorf ( "getting bytes to sign second block: %w" , err )
}
sig , err := api . WalletSign ( ctx , minfo . Worker , signingBytes )
if err != nil {
return xerrors . Errorf ( "signing second block: %w" , err )
}
blockHeaderCopy . BlockSig = sig
buf1 := new ( bytes . Buffer )
err = blockHeader . MarshalCBOR ( buf1 )
if err != nil {
return xerrors . Errorf ( "marshalling block header 1: %w" , err )
}
buf2 := new ( bytes . Buffer )
err = blockHeaderCopy . MarshalCBOR ( buf2 )
if err != nil {
return xerrors . Errorf ( "marshalling block header 2: %w" , err )
}
params := miner8 . ReportConsensusFaultParams {
BlockHeader1 : buf1 . Bytes ( ) ,
BlockHeader2 : buf2 . Bytes ( ) ,
}
sp , err := actors . SerializeParams ( & params )
if err != nil {
return xerrors . Errorf ( "serializing params: %w" , err )
}
smsg , err := api . MpoolPushMessage ( ctx , & types . Message {
From : minfo . Worker ,
To : maddr ,
Method : builtin . MethodsMiner . ReportConsensusFault ,
2022-05-16 17:51:56 +00:00
Value : big . Zero ( ) ,
Params : sp ,
} , nil )
if err != nil {
return xerrors . Errorf ( "mpool push: %w" , err )
}
fmt . Println ( "Message CID:" , smsg . Cid ( ) )
return nil
} ,
}