Merge branch 'master' into chore/snake_context_through_blockstore_init

This commit is contained in:
Raúl Kripalani
2021-01-25 19:31:41 +00:00
103 changed files with 3323 additions and 689 deletions
+24 -2
View File
@@ -27,6 +27,16 @@ func main() {
Hidden: true,
Value: "~/.lotus", // TODO: Consider XDG_DATA_HOME
},
&cli.IntFlag{
Name: "limit",
Usage: "spam transaction count limit, <= 0 is no limit",
Value: 0,
},
&cli.IntFlag{
Name: "rate",
Usage: "spam transaction rate, count per second",
Value: 5,
},
},
Commands: []*cli.Command{runCmd},
}
@@ -52,11 +62,17 @@ var runCmd = &cli.Command{
defer closer()
ctx := lcli.ReqContext(cctx)
return sendSmallFundsTxs(ctx, api, addr, 5)
rate := cctx.Int("rate")
if rate <= 0 {
rate = 5
}
limit := cctx.Int("limit")
return sendSmallFundsTxs(ctx, api, addr, rate, limit)
},
}
func sendSmallFundsTxs(ctx context.Context, api api.FullNode, from address.Address, rate int) error {
func sendSmallFundsTxs(ctx context.Context, api api.FullNode, from address.Address, rate, limit int) error {
var sendSet []address.Address
for i := 0; i < 20; i++ {
naddr, err := api.WalletNew(ctx, types.KTSecp256k1)
@@ -66,9 +82,14 @@ func sendSmallFundsTxs(ctx context.Context, api api.FullNode, from address.Addre
sendSet = append(sendSet, naddr)
}
count := limit
tick := build.Clock.Ticker(time.Second / time.Duration(rate))
for {
if count <= 0 && limit > 0 {
fmt.Printf("%d messages sent.\n", limit)
return nil
}
select {
case <-tick.C:
msg := &types.Message{
@@ -81,6 +102,7 @@ func sendSmallFundsTxs(ctx context.Context, api api.FullNode, from address.Addre
if err != nil {
return err
}
count--
fmt.Println("Message sent: ", smsg.Cid())
case <-ctx.Done():
return nil
+15
View File
@@ -40,6 +40,8 @@ import (
var log = logging.Logger("lotus-bench")
type BenchResults struct {
EnvVar map[string]string
SectorSize abi.SectorSize
SectorNumber int
@@ -446,6 +448,15 @@ var sealBenchCmd = &cli.Command{
bo.VerifyWindowPostHot = verifyWindowpost2.Sub(verifyWindowpost1)
}
bo.EnvVar = make(map[string]string)
for _, envKey := range []string{"BELLMAN_NO_GPU", "FIL_PROOFS_MAXIMIZE_CACHING", "FIL_PROOFS_USE_GPU_COLUMN_BUILDER",
"FIL_PROOFS_USE_GPU_TREE_BUILDER", "FIL_PROOFS_USE_MULTICORE_SDR", "BELLMAN_CUSTOM_GPU"} {
envValue, found := os.LookupEnv(envKey)
if found {
bo.EnvVar[envKey] = envValue
}
}
if c.Bool("json-out") {
data, err := json.MarshalIndent(bo, "", " ")
if err != nil {
@@ -454,6 +465,10 @@ var sealBenchCmd = &cli.Command{
fmt.Println(string(data))
} else {
fmt.Println("environment variable list:")
for envKey, envValue := range bo.EnvVar {
fmt.Printf("%s=%s\n", envKey, envValue)
}
fmt.Printf("----\nresults (v28) SectorSize:(%d), SectorNumber:(%d)\n", sectorSize, sectorNumber)
if robench == "" {
fmt.Printf("seal: addPiece: %s (%s)\n", bo.SealingSum.AddPiece, bps(bo.SectorSize, bo.SectorNumber, bo.SealingSum.AddPiece))
+5
View File
@@ -57,6 +57,7 @@ type gatewayDepsAPI interface {
StateMarketBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MarketBalance, error)
StateMarketStorageDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*api.MarketDeal, error)
StateNetworkVersion(context.Context, types.TipSetKey) (network.Version, error)
StateSearchMsgLimited(ctx context.Context, msg cid.Cid, lookbackLimit abi.ChainEpoch) (*api.MsgLookup, error)
StateWaitMsgLimited(ctx context.Context, msg cid.Cid, confidence uint64, h abi.ChainEpoch) (*api.MsgLookup, error)
StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*api.ActorState, error)
StateMinerPower(context.Context, address.Address, types.TipSetKey) (*api.MinerPower, error)
@@ -299,6 +300,10 @@ func (a *GatewayAPI) StateNetworkVersion(ctx context.Context, tsk types.TipSetKe
return a.api.StateNetworkVersion(ctx, tsk)
}
func (a *GatewayAPI) StateSearchMsg(ctx context.Context, msg cid.Cid) (*api.MsgLookup, error) {
return a.api.StateSearchMsgLimited(ctx, msg, a.stateWaitLookbackLimit)
}
func (a *GatewayAPI) StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64) (*api.MsgLookup, error) {
return a.api.StateWaitMsgLimited(ctx, msg, confidence, a.stateWaitLookbackLimit)
}
+24 -33
View File
@@ -622,8 +622,8 @@ var actorControlSet = &cli.Command{
var actorSetOwnerCmd = &cli.Command{
Name: "set-owner",
Usage: "Set owner address",
ArgsUsage: "[address]",
Usage: "Set owner address (this command should be invoked twice, first with the old owner as the senderAddress, and then with the new owner)",
ArgsUsage: "[newOwnerAddress senderAddress]",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "really-do-it",
@@ -637,8 +637,8 @@ var actorSetOwnerCmd = &cli.Command{
return nil
}
if !cctx.Args().Present() {
return fmt.Errorf("must pass address of new owner address")
if cctx.NArg() != 2 {
return fmt.Errorf("must pass new owner address and sender address")
}
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
@@ -660,7 +660,17 @@ var actorSetOwnerCmd = &cli.Command{
return err
}
newAddr, err := api.StateLookupID(ctx, na, types.EmptyTSK)
newAddrId, err := api.StateLookupID(ctx, na, types.EmptyTSK)
if err != nil {
return err
}
fa, err := address.NewFromString(cctx.Args().Get(1))
if err != nil {
return err
}
fromAddrId, err := api.StateLookupID(ctx, fa, types.EmptyTSK)
if err != nil {
return err
}
@@ -675,13 +685,17 @@ var actorSetOwnerCmd = &cli.Command{
return err
}
sp, err := actors.SerializeParams(&newAddr)
if fromAddrId != mi.Owner && fromAddrId != newAddrId {
return xerrors.New("from address must either be the old owner or the new owner")
}
sp, err := actors.SerializeParams(&newAddrId)
if err != nil {
return xerrors.Errorf("serializing params: %w", err)
}
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
From: mi.Owner,
From: fromAddrId,
To: maddr,
Method: miner.Methods.ChangeOwnerAddress,
Value: big.Zero(),
@@ -691,7 +705,7 @@ var actorSetOwnerCmd = &cli.Command{
return xerrors.Errorf("mpool push: %w", err)
}
fmt.Println("Propose Message CID:", smsg.Cid())
fmt.Println("Message CID:", smsg.Cid())
// wait for it to get mined into a block
wait, err := api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence)
@@ -701,34 +715,11 @@ var actorSetOwnerCmd = &cli.Command{
// check it executed successfully
if wait.Receipt.ExitCode != 0 {
fmt.Println("Propose owner change failed!")
fmt.Println("owner change failed!")
return err
}
smsg, err = api.MpoolPushMessage(ctx, &types.Message{
From: newAddr,
To: maddr,
Method: miner.Methods.ChangeOwnerAddress,
Value: big.Zero(),
Params: sp,
}, nil)
if err != nil {
return xerrors.Errorf("mpool push: %w", err)
}
fmt.Println("Approve Message CID:", smsg.Cid())
// wait for it to get mined into a block
wait, err = api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence)
if err != nil {
return err
}
// check it executed successfully
if wait.Receipt.ExitCode != 0 {
fmt.Println("Approve owner change failed!")
return err
}
fmt.Println("message succeeded!")
return nil
},
+17 -3
View File
@@ -222,7 +222,7 @@ func infoCmdAct(cctx *cli.Context) error {
fmt.Printf(" PreCommit: %s\n", types.FIL(lockedFunds.PreCommitDeposits).Short())
fmt.Printf(" Pledge: %s\n", types.FIL(lockedFunds.InitialPledgeRequirement).Short())
fmt.Printf(" Vesting: %s\n", types.FIL(lockedFunds.VestingFunds).Short())
color.Green(" Available: %s", types.FIL(availBalance).Short())
colorTokenAmount(" Available: %s\n", availBalance)
mb, err := api.StateMarketBalance(ctx, maddr, types.EmptyTSK)
if err != nil {
@@ -232,7 +232,7 @@ func infoCmdAct(cctx *cli.Context) error {
fmt.Printf("Market Balance: %s\n", types.FIL(mb.Escrow).Short())
fmt.Printf(" Locked: %s\n", types.FIL(mb.Locked).Short())
color.Green(" Available: %s\n", types.FIL(big.Sub(mb.Escrow, mb.Locked)).Short())
colorTokenAmount(" Available: %s\n", big.Sub(mb.Escrow, mb.Locked))
wb, err := api.WalletBalance(ctx, mi.Worker)
if err != nil {
@@ -253,7 +253,7 @@ func infoCmdAct(cctx *cli.Context) error {
fmt.Printf(" Control: %s\n", types.FIL(cbsum).Short())
}
fmt.Printf("Total Spendable: %s\n", color.YellowString(types.FIL(spendable).Short()))
colorTokenAmount("Total Spendable: %s\n", spendable)
fmt.Println()
@@ -298,6 +298,10 @@ var stateList = []stateMeta{
{col: color.FgYellow, state: sealing.CommitWait},
{col: color.FgYellow, state: sealing.FinalizeSector},
{col: color.FgCyan, state: sealing.Terminating},
{col: color.FgCyan, state: sealing.TerminateWait},
{col: color.FgCyan, state: sealing.TerminateFinality},
{col: color.FgCyan, state: sealing.TerminateFailed},
{col: color.FgCyan, state: sealing.Removing},
{col: color.FgCyan, state: sealing.Removed},
@@ -355,3 +359,13 @@ func sectorsInfo(ctx context.Context, napi api.StorageMiner) error {
return nil
}
func colorTokenAmount(format string, amount abi.TokenAmount) {
if amount.GreaterThan(big.Zero()) {
color.Green(format, types.FIL(amount).Short())
} else if amount.Equals(big.Zero()) {
color.Yellow(format, types.FIL(amount).Short())
} else {
color.Red(format, types.FIL(amount).Short())
}
}
+10 -4
View File
@@ -451,7 +451,7 @@ func outputStorageDeals(out io.Writer, deals []storagemarket.MinerDeal, verbose
w := tabwriter.NewWriter(out, 2, 4, 2, ' ', 0)
if verbose {
_, _ = fmt.Fprintf(w, "Creation\tProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\tTransferChannelID\tMessage\n")
_, _ = fmt.Fprintf(w, "Creation\tVerified\tProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\tTransferChannelID\tMessage\n")
} else {
_, _ = fmt.Fprintf(w, "ProposalCid\tDealId\tState\tClient\tSize\tPrice\tDuration\n")
}
@@ -465,7 +465,7 @@ func outputStorageDeals(out io.Writer, deals []storagemarket.MinerDeal, verbose
fil := types.FIL(types.BigMul(deal.Proposal.StoragePricePerEpoch, types.NewInt(uint64(deal.Proposal.Duration()))))
if verbose {
_, _ = fmt.Fprintf(w, "%s\t", deal.CreationTime.Time().Format(time.Stamp))
_, _ = fmt.Fprintf(w, "%s\t%t\t", deal.CreationTime.Time().Format(time.Stamp), deal.Proposal.VerifiedDeal)
}
_, _ = fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\t%s", propcid, deal.DealID, storagemarket.DealStates[deal.State], deal.Proposal.Client, units.BytesSize(float64(deal.Proposal.PieceSize)), fil, deal.Proposal.Duration())
@@ -744,6 +744,11 @@ var transfersListCmd = &cli.Command{
Name: "list",
Usage: "List ongoing data transfers for this miner",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Usage: "print verbose transfer details",
},
&cli.BoolFlag{
Name: "color",
Usage: "use color in display output",
@@ -775,6 +780,7 @@ var transfersListCmd = &cli.Command{
return err
}
verbose := cctx.Bool("verbose")
completed := cctx.Bool("completed")
color := cctx.Bool("color")
watch := cctx.Bool("watch")
@@ -790,7 +796,7 @@ var transfersListCmd = &cli.Command{
tm.MoveCursor(1, 1)
lcli.OutputDataTransferChannels(tm.Screen, channels, completed, color, showFailed)
lcli.OutputDataTransferChannels(tm.Screen, channels, verbose, completed, color, showFailed)
tm.Flush()
@@ -815,7 +821,7 @@ var transfersListCmd = &cli.Command{
}
}
}
lcli.OutputDataTransferChannels(os.Stdout, channels, completed, color, showFailed)
lcli.OutputDataTransferChannels(os.Stdout, channels, verbose, completed, color, showFailed)
return nil
},
}
+1 -1
View File
@@ -446,7 +446,7 @@ var provingCheckProvableCmd = &cli.Command{
for parIdx, par := range partitions {
sectors := make(map[abi.SectorNumber]struct{})
sectorInfos, err := api.StateMinerSectors(ctx, addr, &par.AllSectors, types.EmptyTSK)
sectorInfos, err := api.StateMinerSectors(ctx, addr, &par.LiveSectors, types.EmptyTSK)
if err != nil {
return err
}
+116 -1
View File
@@ -35,6 +35,7 @@ var sectorsCmd = &cli.Command{
sectorsRefsCmd,
sectorsUpdateCmd,
sectorsPledgeCmd,
sectorsTerminateCmd,
sectorsRemoveCmd,
sectorsMarkForUpgradeCmd,
sectorsStartSealCmd,
@@ -396,9 +397,123 @@ var sectorsRefsCmd = &cli.Command{
},
}
var sectorsTerminateCmd = &cli.Command{
Name: "terminate",
Usage: "Terminate sector on-chain then remove (WARNING: This means losing power and collateral for the removed sector)",
ArgsUsage: "<sectorNum>",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "really-do-it",
Usage: "pass this flag if you know what you are doing",
},
},
Subcommands: []*cli.Command{
sectorsTerminateFlushCmd,
sectorsTerminatePendingCmd,
},
Action: func(cctx *cli.Context) error {
if !cctx.Bool("really-do-it") {
return xerrors.Errorf("pass --really-do-it to confirm this action")
}
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := lcli.ReqContext(cctx)
if cctx.Args().Len() != 1 {
return xerrors.Errorf("must pass sector number")
}
id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64)
if err != nil {
return xerrors.Errorf("could not parse sector number: %w", err)
}
return nodeApi.SectorTerminate(ctx, abi.SectorNumber(id))
},
}
var sectorsTerminateFlushCmd = &cli.Command{
Name: "flush",
Usage: "Send a terminate message if there are sectors queued for termination",
Action: func(cctx *cli.Context) error {
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := lcli.ReqContext(cctx)
mcid, err := nodeApi.SectorTerminateFlush(ctx)
if err != nil {
return err
}
if mcid == nil {
return xerrors.New("no sectors were queued for termination")
}
fmt.Println(mcid)
return nil
},
}
var sectorsTerminatePendingCmd = &cli.Command{
Name: "pending",
Usage: "List sector numbers of sectors pending termination",
Action: func(cctx *cli.Context) error {
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return err
}
defer closer()
api, nCloser, err := lcli.GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer nCloser()
ctx := lcli.ReqContext(cctx)
pending, err := nodeApi.SectorTerminatePending(ctx)
if err != nil {
return err
}
maddr, err := nodeApi.ActorAddress(ctx)
if err != nil {
return err
}
dl, err := api.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK)
if err != nil {
return xerrors.Errorf("getting proving deadline info failed: %w", err)
}
for _, id := range pending {
loc, err := api.StateSectorPartition(ctx, maddr, id.Number, types.EmptyTSK)
if err != nil {
return xerrors.Errorf("finding sector partition: %w", err)
}
fmt.Print(id.Number)
if loc.Deadline == (dl.Index+1)%miner.WPoStPeriodDeadlines || // not in next (in case the terminate message takes a while to get on chain)
loc.Deadline == dl.Index || // not in current
(loc.Deadline+1)%miner.WPoStPeriodDeadlines == dl.Index { // not in previous
fmt.Print(" (in proving window)")
}
fmt.Println()
}
return nil
},
}
var sectorsRemoveCmd = &cli.Command{
Name: "remove",
Usage: "Forcefully remove a sector (WARNING: This means losing power and collateral for the removed sector)",
Usage: "Forcefully remove a sector (WARNING: This means losing power and collateral for the removed sector (use 'terminate' for lower penalty))",
ArgsUsage: "<sectorNum>",
Flags: []cli.Flag{
&cli.BoolFlag{
+107
View File
@@ -1,14 +1,121 @@
package main
import (
"os"
dstore "github.com/ipfs/go-datastore"
"github.com/mitchellh/go-homedir"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"gopkg.in/cheggaaa/pb.v1"
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/lotus/chain/store"
lcli "github.com/filecoin-project/lotus/cli"
"github.com/filecoin-project/lotus/lib/backupds"
"github.com/filecoin-project/lotus/node/config"
"github.com/filecoin-project/lotus/node/repo"
)
var backupCmd = lcli.BackupCmd("repo", repo.FullNode, func(cctx *cli.Context) (lcli.BackupAPI, jsonrpc.ClientCloser, error) {
return lcli.GetFullNodeAPI(cctx)
})
func restore(cctx *cli.Context, r repo.Repo) error {
bf, err := homedir.Expand(cctx.Path("restore"))
if err != nil {
return xerrors.Errorf("expand backup file path: %w", err)
}
st, err := os.Stat(bf)
if err != nil {
return xerrors.Errorf("stat backup file (%s): %w", bf, err)
}
f, err := os.Open(bf)
if err != nil {
return xerrors.Errorf("opening backup file: %w", err)
}
defer f.Close() // nolint:errcheck
lr, err := r.Lock(repo.FullNode)
if err != nil {
return err
}
defer lr.Close() // nolint:errcheck
if cctx.IsSet("restore-config") {
log.Info("Restoring config")
cf, err := homedir.Expand(cctx.String("restore-config"))
if err != nil {
return xerrors.Errorf("expanding config path: %w", err)
}
_, err = os.Stat(cf)
if err != nil {
return xerrors.Errorf("stat config file (%s): %w", cf, err)
}
var cerr error
err = lr.SetConfig(func(raw interface{}) {
rcfg, ok := raw.(*config.FullNode)
if !ok {
cerr = xerrors.New("expected miner config")
return
}
ff, err := config.FromFile(cf, rcfg)
if err != nil {
cerr = xerrors.Errorf("loading config: %w", err)
return
}
*rcfg = *ff.(*config.FullNode)
})
if cerr != nil {
return cerr
}
if err != nil {
return xerrors.Errorf("setting config: %w", err)
}
} else {
log.Warn("--restore-config NOT SET, WILL USE DEFAULT VALUES")
}
log.Info("Restoring metadata backup")
mds, err := lr.Datastore("/metadata")
if err != nil {
return err
}
bar := pb.New64(st.Size())
br := bar.NewProxyReader(f)
bar.ShowTimeLeft = true
bar.ShowPercent = true
bar.ShowSpeed = true
bar.Units = pb.U_BYTES
bar.Start()
err = backupds.RestoreInto(br, mds)
bar.Finish()
if err != nil {
return xerrors.Errorf("restoring metadata: %w", err)
}
log.Info("Resetting chainstore metadata")
chainHead := dstore.NewKey("head")
if err := mds.Delete(chainHead); err != nil {
return xerrors.Errorf("clearing chain head: %w", err)
}
if err := store.FlushValidationCache(mds); err != nil {
return xerrors.Errorf("clearing chain validation cache: %w", err)
}
return nil
}
+20 -1
View File
@@ -144,6 +144,14 @@ var DaemonCmd = &cli.Command{
Name: "api-max-req-size",
Usage: "maximum API request size accepted by the JSON RPC server",
},
&cli.PathFlag{
Name: "restore",
Usage: "restore from backup file",
},
&cli.PathFlag{
Name: "restore-config",
Usage: "config file to use when restoring from backup",
},
},
Action: func(cctx *cli.Context) error {
isLite := cctx.Bool("lite")
@@ -203,9 +211,11 @@ var DaemonCmd = &cli.Command{
r.SetConfigPath(cctx.String("config"))
}
if err := r.Init(repo.FullNode); err != nil && err != repo.ErrRepoExists {
err = r.Init(repo.FullNode)
if err != nil && err != repo.ErrRepoExists {
return xerrors.Errorf("repo init error: %w", err)
}
freshRepo := err != repo.ErrRepoExists
if !isLite {
if err := paramfetch.GetParams(lcli.ReqContext(cctx), build.ParametersJSON(), 0); err != nil {
@@ -223,6 +233,15 @@ var DaemonCmd = &cli.Command{
genBytes = build.MaybeGenesis()
}
if cctx.IsSet("restore") {
if !freshRepo {
return xerrors.Errorf("restoring from backup is only possible with a fresh repo!")
}
if err := restore(cctx, r); err != nil {
return xerrors.Errorf("restoring from backup: %w", err)
}
}
chainfile := cctx.String("import-chain")
snapshot := cctx.String("import-snapshot")
if chainfile != "" || snapshot != "" {
+1 -1
View File
@@ -18,7 +18,7 @@ func TestProtocolCodenames(t *testing.T) {
t.Fatal("expected breeze codename")
}
if height := build.UpgradeActorsV2Height + 1; GetProtocolCodename(height) != "actorsv2" {
if height := build.UpgradeActorsV2Height + 1; GetProtocolCodename(abi.ChainEpoch(height)) != "actorsv2" {
t.Fatal("expected actorsv2 codename")
}
+134 -31
View File
@@ -1,33 +1,48 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"github.com/fatih/color"
"github.com/filecoin-project/go-address"
cbornode "github.com/ipfs/go-ipld-cbor"
"github.com/urfave/cli/v2"
"github.com/filecoin-project/lotus/conformance"
"github.com/filecoin-project/test-vectors/schema"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/conformance"
"github.com/filecoin-project/lotus/lib/blockstore"
)
var execFlags struct {
file string
out string
driverOpts cli.StringSlice
fallbackBlockstore bool
}
const (
optSaveBalances = "save-balances"
)
var execCmd = &cli.Command{
Name: "exec",
Description: "execute one or many test vectors against Lotus; supplied as a single JSON file, or a ndjson stdin stream",
Action: runExecLotus,
Description: "execute one or many test vectors against Lotus; supplied as a single JSON file, a directory, or a ndjson stdin stream",
Action: runExec,
Flags: []cli.Flag{
&repoFlag,
&cli.StringFlag{
Name: "file",
Usage: "input file; if not supplied, the vector will be read from stdin",
Usage: "input file or directory; if not supplied, the vector will be read from stdin",
TakesFile: true,
Destination: &execFlags.file,
},
@@ -36,10 +51,20 @@ var execCmd = &cli.Command{
Usage: "sets the full node API as a fallback blockstore; use this if you're transplanting vectors and get block not found errors",
Destination: &execFlags.fallbackBlockstore,
},
&cli.StringFlag{
Name: "out",
Usage: "output directory where to save the results, only used when the input is a directory",
Destination: &execFlags.out,
},
&cli.StringSliceFlag{
Name: "driver-opt",
Usage: "comma-separated list of driver options (EXPERIMENTAL; will change), supported: 'save-balances=<dst>', 'pipeline-basefee' (unimplemented); only available in single-file mode",
Destination: &execFlags.driverOpts,
},
},
}
func runExecLotus(c *cli.Context) error {
func runExec(c *cli.Context) error {
if execFlags.fallbackBlockstore {
if err := initialize(c); err != nil {
return fmt.Errorf("fallback blockstore was enabled, but could not resolve lotus API endpoint: %w", err)
@@ -48,30 +73,97 @@ func runExecLotus(c *cli.Context) error {
conformance.FallbackBlockstoreGetter = FullAPI
}
if file := execFlags.file; file != "" {
// we have a single test vector supplied as a file.
file, err := os.Open(file)
if err != nil {
return fmt.Errorf("failed to open test vector: %w", err)
}
var (
dec = json.NewDecoder(file)
tv schema.TestVector
)
if err = dec.Decode(&tv); err != nil {
return fmt.Errorf("failed to decode test vector: %w", err)
}
return executeTestVector(tv)
path := execFlags.file
if path == "" {
return execVectorsStdin()
}
fi, err := os.Stat(path)
if err != nil {
return err
}
if fi.IsDir() {
// we're in directory mode; ensure the out directory exists.
outdir := execFlags.out
if outdir == "" {
return fmt.Errorf("no output directory provided")
}
if err := ensureDir(outdir); err != nil {
return err
}
return execVectorDir(path, outdir)
}
// process tipset vector options.
if err := processTipsetOpts(); err != nil {
return err
}
_, err = execVectorFile(new(conformance.LogReporter), path)
return err
}
func processTipsetOpts() error {
for _, opt := range execFlags.driverOpts.Value() {
switch ss := strings.Split(opt, "="); {
case ss[0] == optSaveBalances:
filename := ss[1]
log.Printf("saving balances after each tipset in: %s", filename)
balancesFile, err := os.Create(filename)
if err != nil {
return err
}
w := bufio.NewWriter(balancesFile)
cb := func(bs blockstore.Blockstore, params *conformance.ExecuteTipsetParams, res *conformance.ExecuteTipsetResult) {
cst := cbornode.NewCborStore(bs)
st, err := state.LoadStateTree(cst, res.PostStateRoot)
if err != nil {
return
}
_ = st.ForEach(func(addr address.Address, actor *types.Actor) error {
_, err := fmt.Fprintln(w, params.ExecEpoch, addr, actor.Balance)
return err
})
_ = w.Flush()
}
conformance.TipsetVectorOpts.OnTipsetApplied = append(conformance.TipsetVectorOpts.OnTipsetApplied, cb)
}
}
return nil
}
func execVectorDir(path string, outdir string) error {
files, err := filepath.Glob(filepath.Join(path, "*"))
if err != nil {
return fmt.Errorf("failed to glob input directory %s: %w", path, err)
}
for _, f := range files {
outfile := strings.TrimSuffix(filepath.Base(f), filepath.Ext(f)) + ".out"
outpath := filepath.Join(outdir, outfile)
outw, err := os.Create(outpath)
if err != nil {
return fmt.Errorf("failed to create file %s: %w", outpath, err)
}
log.Printf("processing vector %s; sending output to %s", f, outpath)
log.SetOutput(io.MultiWriter(os.Stderr, outw)) // tee the output.
_, _ = execVectorFile(new(conformance.LogReporter), f)
log.SetOutput(os.Stderr)
_ = outw.Close()
}
return nil
}
func execVectorsStdin() error {
r := new(conformance.LogReporter)
for dec := json.NewDecoder(os.Stdin); ; {
var tv schema.TestVector
switch err := dec.Decode(&tv); err {
case nil:
if err = executeTestVector(tv); err != nil {
if _, err = executeTestVector(r, tv); err != nil {
return err
}
case io.EOF:
@@ -84,19 +176,30 @@ func runExecLotus(c *cli.Context) error {
}
}
func executeTestVector(tv schema.TestVector) error {
func execVectorFile(r conformance.Reporter, path string) (diffs []string, error error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open test vector: %w", err)
}
var tv schema.TestVector
if err = json.NewDecoder(file).Decode(&tv); err != nil {
return nil, fmt.Errorf("failed to decode test vector: %w", err)
}
return executeTestVector(r, tv)
}
func executeTestVector(r conformance.Reporter, tv schema.TestVector) (diffs []string, err error) {
log.Println("executing test vector:", tv.Meta.ID)
for _, v := range tv.Pre.Variants {
r := new(conformance.LogReporter)
switch class, v := tv.Class, v; class {
case "message":
conformance.ExecuteMessageVector(r, &tv, &v)
diffs, err = conformance.ExecuteMessageVector(r, &tv, &v)
case "tipset":
conformance.ExecuteTipsetVector(r, &tv, &v)
diffs, err = conformance.ExecuteTipsetVector(r, &tv, &v)
default:
return fmt.Errorf("test vector class %s not supported", class)
return nil, fmt.Errorf("test vector class %s not supported", class)
}
if r.Failed() {
@@ -106,5 +209,5 @@ func executeTestVector(tv schema.TestVector) error {
}
}
return nil
return diffs, err
}
+55 -2
View File
@@ -1,8 +1,14 @@
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"path/filepath"
"github.com/filecoin-project/test-vectors/schema"
"github.com/urfave/cli/v2"
)
@@ -21,6 +27,7 @@ type extractOpts struct {
retain string
precursor string
ignoreSanityChecks bool
squash bool
}
var extractFlags extractOpts
@@ -62,13 +69,13 @@ var extractCmd = &cli.Command{
},
&cli.StringFlag{
Name: "tsk",
Usage: "tipset key to extract into a vector",
Usage: "tipset key to extract into a vector, or range of tipsets in tsk1..tsk2 form",
Destination: &extractFlags.tsk,
},
&cli.StringFlag{
Name: "out",
Aliases: []string{"o"},
Usage: "file to write test vector to",
Usage: "file to write test vector to, or directory to write the batch to",
Destination: &extractFlags.file,
},
&cli.StringFlag{
@@ -93,6 +100,12 @@ var extractCmd = &cli.Command{
Value: false,
Destination: &extractFlags.ignoreSanityChecks,
},
&cli.BoolFlag{
Name: "squash",
Usage: "when extracting a tipset range, squash all tipsets into a single vector",
Value: false,
Destination: &extractFlags.squash,
},
},
}
@@ -106,3 +119,43 @@ func runExtract(_ *cli.Context) error {
return fmt.Errorf("unsupported vector class")
}
}
// writeVector writes the vector into the specified file, or to stdout if
// file is empty.
func writeVector(vector *schema.TestVector, file string) (err error) {
output := io.WriteCloser(os.Stdout)
if file := file; file != "" {
dir := filepath.Dir(file)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("unable to create directory %s: %w", dir, err)
}
output, err = os.Create(file)
if err != nil {
return err
}
defer output.Close() //nolint:errcheck
defer log.Printf("wrote test vector to file: %s", file)
}
enc := json.NewEncoder(output)
enc.SetIndent("", " ")
return enc.Encode(&vector)
}
// writeVectors writes each vector to a different file under the specified
// directory.
func writeVectors(dir string, vectors ...*schema.TestVector) error {
// verify the output directory exists.
if err := ensureDir(dir); err != nil {
return err
}
// write each vector to its file.
for _, v := range vectors {
id := v.Meta.ID
path := filepath.Join(dir, fmt.Sprintf("%s.json", id))
if err := writeVector(v, path); err != nil {
return err
}
}
return nil
}
+1 -25
View File
@@ -4,12 +4,9 @@ import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
"log"
"os"
"path/filepath"
"github.com/fatih/color"
"github.com/filecoin-project/go-address"
@@ -316,28 +313,7 @@ func doExtractMessage(opts extractOpts) error {
},
},
}
return writeVector(vector, opts.file)
}
func writeVector(vector schema.TestVector, file string) (err error) {
output := io.WriteCloser(os.Stdout)
if file := file; file != "" {
dir := filepath.Dir(file)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("unable to create directory %s: %w", dir, err)
}
output, err = os.Create(file)
if err != nil {
return err
}
defer output.Close() //nolint:errcheck
defer log.Printf("wrote test vector to file: %s", file)
}
enc := json.NewEncoder(output)
enc.SetIndent("", " ")
return enc.Encode(&vector)
return writeVector(&vector, opts.file)
}
// resolveFromChain queries the chain for the provided message, using the block CID to
+204 -113
View File
@@ -6,10 +6,12 @@ import (
"context"
"fmt"
"log"
"strings"
"github.com/filecoin-project/test-vectors/schema"
"github.com/ipfs/go-cid"
"github.com/filecoin-project/lotus/chain/types"
lcli "github.com/filecoin-project/lotus/cli"
"github.com/filecoin-project/lotus/conformance"
)
@@ -17,170 +19,259 @@ import (
func doExtractTipset(opts extractOpts) error {
ctx := context.Background()
if opts.tsk == "" {
return fmt.Errorf("tipset key cannot be empty")
}
if opts.retain != "accessed-cids" {
return fmt.Errorf("tipset extraction only supports 'accessed-cids' state retention")
}
ts, err := lcli.ParseTipSetRef(ctx, FullAPI, opts.tsk)
if err != nil {
return fmt.Errorf("failed to fetch tipset: %w", err)
if opts.tsk == "" {
return fmt.Errorf("tipset key cannot be empty")
}
log.Printf("tipset block count: %d", len(ts.Blocks()))
var blocks []schema.Block
for _, b := range ts.Blocks() {
msgs, err := FullAPI.ChainGetBlockMessages(ctx, b.Cid())
ss := strings.Split(opts.tsk, "..")
switch len(ss) {
case 1: // extracting a single tipset.
ts, err := lcli.ParseTipSetRef(ctx, FullAPI, opts.tsk)
if err != nil {
return fmt.Errorf("failed to get block messages (cid: %s): %w", b.Cid(), err)
return fmt.Errorf("failed to fetch tipset: %w", err)
}
v, err := extractTipsets(ctx, ts)
if err != nil {
return err
}
return writeVector(v, opts.file)
case 2: // extracting a range of tipsets.
left, err := lcli.ParseTipSetRef(ctx, FullAPI, ss[0])
if err != nil {
return fmt.Errorf("failed to fetch tipset %s: %w", ss[0], err)
}
right, err := lcli.ParseTipSetRef(ctx, FullAPI, ss[1])
if err != nil {
return fmt.Errorf("failed to fetch tipset %s: %w", ss[1], err)
}
log.Printf("block %s has %d messages", b.Cid(), len(msgs.Cids))
// resolve the tipset range.
tss, err := resolveTipsetRange(ctx, left, right)
if err != nil {
return err
}
packed := make([]schema.Base64EncodedBytes, 0, len(msgs.Cids))
for _, m := range msgs.BlsMessages {
b, err := m.Serialize()
// are are squashing all tipsets into a single multi-tipset vector?
if opts.squash {
vector, err := extractTipsets(ctx, tss...)
if err != nil {
return fmt.Errorf("failed to serialize message: %w", err)
return err
}
packed = append(packed, b)
return writeVector(vector, opts.file)
}
for _, m := range msgs.SecpkMessages {
b, err := m.Message.Serialize()
if err != nil {
return fmt.Errorf("failed to serialize message: %w", err)
}
packed = append(packed, b)
// we are generating a single-tipset vector per tipset.
vectors, err := extractIndividualTipsets(ctx, tss...)
if err != nil {
return err
}
blocks = append(blocks, schema.Block{
MinerAddr: b.Miner,
WinCount: b.ElectionProof.WinCount,
Messages: packed,
})
return writeVectors(opts.file, vectors...)
default:
return fmt.Errorf("unrecognized tipset format")
}
}
func resolveTipsetRange(ctx context.Context, left *types.TipSet, right *types.TipSet) (tss []*types.TipSet, err error) {
// start from the right tipset and walk back the chain until the left tipset, inclusive.
for curr := right; curr.Key() != left.Parents(); {
tss = append(tss, curr)
curr, err = FullAPI.ChainGetTipSet(ctx, curr.Parents())
if err != nil {
return nil, fmt.Errorf("failed to get tipset %s (height: %d): %w", curr.Parents(), curr.Height()-1, err)
}
}
// reverse the slice.
for i, j := 0, len(tss)-1; i < j; i, j = i+1, j-1 {
tss[i], tss[j] = tss[j], tss[i]
}
return tss, nil
}
func extractIndividualTipsets(ctx context.Context, tss ...*types.TipSet) (vectors []*schema.TestVector, err error) {
for _, ts := range tss {
v, err := extractTipsets(ctx, ts)
if err != nil {
return nil, err
}
vectors = append(vectors, v)
}
return vectors, nil
}
func extractTipsets(ctx context.Context, tss ...*types.TipSet) (*schema.TestVector, error) {
var (
// create a read-through store that uses ChainGetObject to fetch unknown CIDs.
pst = NewProxyingStores(ctx, FullAPI)
g = NewSurgeon(ctx, FullAPI, pst)
// recordingRand will record randomness so we can embed it in the test vector.
recordingRand = conformance.NewRecordingRand(new(conformance.LogReporter), FullAPI)
)
tbs, ok := pst.Blockstore.(TracingBlockstore)
if !ok {
return nil, fmt.Errorf("requested 'accessed-cids' state retention, but no tracing blockstore was present")
}
driver := conformance.NewDriver(ctx, schema.Selector{}, conformance.DriverOpts{
DisableVMFlush: true,
})
base := tss[0]
last := tss[len(tss)-1]
// this is the root of the state tree we start with.
root := ts.ParentState()
root := base.ParentState()
log.Printf("base state tree root CID: %s", root)
basefee := ts.Blocks()[0].ParentBaseFee
log.Printf("basefee: %s", basefee)
tipset := schema.Tipset{
BaseFee: *basefee.Int,
Blocks: blocks,
codename := GetProtocolCodename(base.Height())
nv, err := FullAPI.StateNetworkVersion(ctx, base.Key())
if err != nil {
return nil, err
}
// recordingRand will record randomness so we can embed it in the test vector.
recordingRand := conformance.NewRecordingRand(new(conformance.LogReporter), FullAPI)
version, err := FullAPI.Version(ctx)
if err != nil {
return nil, err
}
log.Printf("using state retention strategy: %s", extractFlags.retain)
ntwkName, err := FullAPI.StateNetworkName(ctx)
if err != nil {
return nil, err
}
tbs, ok := pst.Blockstore.(TracingBlockstore)
if !ok {
return fmt.Errorf("requested 'accessed-cids' state retention, but no tracing blockstore was present")
vector := schema.TestVector{
Class: schema.ClassTipset,
Meta: &schema.Metadata{
ID: fmt.Sprintf("@%d..@%d", base.Height(), last.Height()),
Gen: []schema.GenerationData{
{Source: fmt.Sprintf("network:%s", ntwkName)},
{Source: "github.com/filecoin-project/lotus", Version: version.String()}},
// will be completed by extra tipset stamps.
},
Selector: schema.Selector{
schema.SelectorMinProtocolVersion: codename,
},
Pre: &schema.Preconditions{
Variants: []schema.Variant{
{ID: codename, Epoch: int64(base.Height()), NetworkVersion: uint(nv)},
},
StateTree: &schema.StateTree{
RootCID: base.ParentState(),
},
},
Post: &schema.Postconditions{
StateTree: new(schema.StateTree),
},
}
tbs.StartTracing()
params := conformance.ExecuteTipsetParams{
Preroot: ts.ParentState(),
ParentEpoch: ts.Height() - 1,
Tipset: &tipset,
ExecEpoch: ts.Height(),
Rand: recordingRand,
}
result, err := driver.ExecuteTipset(pst.Blockstore, pst.Datastore, params)
if err != nil {
return fmt.Errorf("failed to execute tipset: %w", err)
roots := []cid.Cid{base.ParentState()}
for i, ts := range tss {
log.Printf("tipset %s block count: %d", ts.Key(), len(ts.Blocks()))
var blocks []schema.Block
for _, b := range ts.Blocks() {
msgs, err := FullAPI.ChainGetBlockMessages(ctx, b.Cid())
if err != nil {
return nil, fmt.Errorf("failed to get block messages (cid: %s): %w", b.Cid(), err)
}
log.Printf("block %s has %d messages", b.Cid(), len(msgs.Cids))
packed := make([]schema.Base64EncodedBytes, 0, len(msgs.Cids))
for _, m := range msgs.BlsMessages {
b, err := m.Serialize()
if err != nil {
return nil, fmt.Errorf("failed to serialize message: %w", err)
}
packed = append(packed, b)
}
for _, m := range msgs.SecpkMessages {
b, err := m.Message.Serialize()
if err != nil {
return nil, fmt.Errorf("failed to serialize message: %w", err)
}
packed = append(packed, b)
}
blocks = append(blocks, schema.Block{
MinerAddr: b.Miner,
WinCount: b.ElectionProof.WinCount,
Messages: packed,
})
}
basefee := base.Blocks()[0].ParentBaseFee
log.Printf("tipset basefee: %s", basefee)
tipset := schema.Tipset{
BaseFee: *basefee.Int,
Blocks: blocks,
EpochOffset: int64(i),
}
params := conformance.ExecuteTipsetParams{
Preroot: roots[len(roots)-1],
ParentEpoch: ts.Height() - 1,
Tipset: &tipset,
ExecEpoch: ts.Height(),
Rand: recordingRand,
}
result, err := driver.ExecuteTipset(pst.Blockstore, pst.Datastore, params)
if err != nil {
return nil, fmt.Errorf("failed to execute tipset: %w", err)
}
roots = append(roots, result.PostStateRoot)
// update the vector.
vector.ApplyTipsets = append(vector.ApplyTipsets, tipset)
vector.Post.ReceiptsRoots = append(vector.Post.ReceiptsRoots, result.ReceiptsRoot)
for _, res := range result.AppliedResults {
vector.Post.Receipts = append(vector.Post.Receipts, &schema.Receipt{
ExitCode: int64(res.ExitCode),
ReturnValue: res.Return,
GasUsed: res.GasUsed,
})
}
vector.Meta.Gen = append(vector.Meta.Gen, schema.GenerationData{
Source: "tipset:" + ts.Key().String(),
})
}
accessed := tbs.FinishTracing()
//
// ComputeBaseFee(ctx, baseTs)
// write a CAR with the accessed state into a buffer.
var (
out = new(bytes.Buffer)
gw = gzip.NewWriter(out)
)
if err := g.WriteCARIncluding(gw, accessed, ts.ParentState(), result.PostStateRoot); err != nil {
return err
if err := g.WriteCARIncluding(gw, accessed, roots...); err != nil {
return nil, err
}
if err = gw.Flush(); err != nil {
return err
return nil, err
}
if err = gw.Close(); err != nil {
return err
return nil, err
}
codename := GetProtocolCodename(ts.Height())
nv, err := FullAPI.StateNetworkVersion(ctx, ts.Key())
if err != nil {
return err
}
vector.Randomness = recordingRand.Recorded()
vector.Post.StateTree.RootCID = roots[len(roots)-1]
vector.CAR = out.Bytes()
version, err := FullAPI.Version(ctx)
if err != nil {
return err
}
ntwkName, err := FullAPI.StateNetworkName(ctx)
if err != nil {
return err
}
vector := schema.TestVector{
Class: schema.ClassTipset,
Meta: &schema.Metadata{
ID: opts.id,
Gen: []schema.GenerationData{
{Source: fmt.Sprintf("network:%s", ntwkName)},
{Source: fmt.Sprintf("tipset:%s", ts.Key())},
{Source: "github.com/filecoin-project/lotus", Version: version.String()}},
},
Selector: schema.Selector{
schema.SelectorMinProtocolVersion: codename,
},
Randomness: recordingRand.Recorded(),
CAR: out.Bytes(),
Pre: &schema.Preconditions{
Variants: []schema.Variant{
{ID: codename, Epoch: int64(ts.Height()), NetworkVersion: uint(nv)},
},
BaseFee: basefee.Int,
StateTree: &schema.StateTree{
RootCID: ts.ParentState(),
},
},
ApplyTipsets: []schema.Tipset{tipset},
Post: &schema.Postconditions{
StateTree: &schema.StateTree{
RootCID: result.PostStateRoot,
},
ReceiptsRoots: []cid.Cid{result.ReceiptsRoot},
},
}
for _, res := range result.AppliedResults {
vector.Post.Receipts = append(vector.Post.Receipts, &schema.Receipt{
ExitCode: int64(res.ExitCode),
ReturnValue: res.Return,
GasUsed: res.GasUsed,
})
}
return writeVector(vector, opts.file)
return &vector, nil
}
+16
View File
@@ -113,3 +113,19 @@ func destroy(_ *cli.Context) error {
}
return nil
}
func ensureDir(path string) error {
switch fi, err := os.Stat(path); {
case os.IsNotExist(err):
if err := os.MkdirAll(path, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %w", path, err)
}
case err == nil:
if !fi.IsDir() {
return fmt.Errorf("path %s is not a directory: %w", path, err)
}
default:
return fmt.Errorf("failed to stat directory %s: %w", path, err)
}
return nil
}
+1 -1
View File
@@ -202,7 +202,7 @@ func runSimulateCmd(_ *cli.Context) error {
},
}
if err := writeVector(vector, simulateFlags.out); err != nil {
if err := writeVector(&vector, simulateFlags.out); err != nil {
return fmt.Errorf("failed to write vector: %w", err)
}
+11
View File
@@ -149,3 +149,14 @@ func (pb *proxyingBlockstore) Put(block blocks.Block) error {
pb.lk.Unlock()
return pb.Blockstore.Put(block)
}
func (pb *proxyingBlockstore) PutMany(blocks []blocks.Block) error {
pb.lk.Lock()
if pb.tracing {
for _, b := range blocks {
pb.traced[b.Cid()] = struct{}{}
}
}
pb.lk.Unlock()
return pb.Blockstore.PutMany(blocks)
}