This commit is contained in:
Sami Mäkelä
2020-08-12 14:12:33 +03:00
450 changed files with 39800 additions and 9232 deletions
+1 -4
View File
@@ -2,7 +2,6 @@ package cli
import (
"fmt"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
@@ -125,11 +124,9 @@ var authApiInfoToken = &cli.Command{
return xerrors.Errorf("could not get API info: %w", err)
}
envVar := envForRepo(t)
// TODO: Log in audit log when it is implemented
fmt.Printf("%s=%s:%s\n", envVar, string(token), ainfo.Addr)
fmt.Printf("%s=%s:%s\n", envForRepo(t), string(token), ainfo.Addr)
return nil
},
}
+103 -9
View File
@@ -15,6 +15,7 @@ import (
"github.com/filecoin-project/go-address"
cborutil "github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/builtin/account"
"github.com/filecoin-project/specs-actors/actors/builtin/market"
@@ -27,6 +28,7 @@ import (
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
types "github.com/filecoin-project/lotus/chain/types"
)
@@ -46,6 +48,7 @@ var chainCmd = &cli.Command{
chainBisectCmd,
chainExportCmd,
slashConsensusFault,
chainGasPriceCmd,
},
}
@@ -364,6 +367,10 @@ var chainListCmd = &cli.Command{
Usage: "specify the format to print out tipsets",
Value: "<height>: (<time>) <blocks>",
},
&cli.BoolFlag{
Name: "gas-stats",
Usage: "view gas statistics for the chain",
},
},
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
@@ -405,8 +412,70 @@ var chainListCmd = &cli.Command{
tss = append(tss, head)
}
for i := len(tss) - 1; i >= 0; i-- {
printTipSet(cctx.String("format"), tss[i])
if cctx.Bool("gas-stats") {
otss := make([]*types.TipSet, 0, len(tss))
for i := len(tss) - 1; i >= 0; i-- {
otss = append(otss, tss[i])
}
tss = otss
for i, ts := range tss {
pbf := ts.Blocks()[0].ParentBaseFee
fmt.Printf("%d: %d blocks (baseFee: %s -> maxFee: %s)\n", ts.Height(), len(ts.Blocks()), ts.Blocks()[0].ParentBaseFee, types.FIL(types.BigMul(pbf, types.NewInt(uint64(build.BlockGasLimit)))))
for _, b := range ts.Blocks() {
msgs, err := api.ChainGetBlockMessages(ctx, b.Cid())
if err != nil {
return err
}
var limitSum int64
psum := big.NewInt(0)
for _, m := range msgs.BlsMessages {
limitSum += m.GasLimit
psum = big.Add(psum, m.GasPremium)
}
for _, m := range msgs.SecpkMessages {
limitSum += m.Message.GasLimit
psum = big.Add(psum, m.Message.GasPremium)
}
lenmsgs := len(msgs.BlsMessages) + len(msgs.SecpkMessages)
avgpremium := big.Zero()
if lenmsgs > 0 {
avgpremium = big.Div(psum, big.NewInt(int64(lenmsgs)))
}
fmt.Printf("\t%s: \t%d msgs, gasLimit: %d / %d (%0.2f%%), avgPremium: %s\n", b.Miner, len(msgs.BlsMessages)+len(msgs.SecpkMessages), limitSum, build.BlockGasLimit, 100*float64(limitSum)/float64(build.BlockGasLimit), avgpremium)
}
if i < len(tss)-1 {
msgs, err := api.ChainGetParentMessages(ctx, tss[i+1].Blocks()[0].Cid())
if err != nil {
return err
}
var limitSum int64
for _, m := range msgs {
limitSum += m.Message.GasLimit
}
recpts, err := api.ChainGetParentReceipts(ctx, tss[i+1].Blocks()[0].Cid())
if err != nil {
return err
}
var gasUsed int64
for _, r := range recpts {
gasUsed += r.GasUsed
}
fmt.Printf("\ttipset: \t%d msgs, %d / %d (%0.2f%%)\n", len(msgs), gasUsed, limitSum, 100*float64(gasUsed)/float64(limitSum))
}
fmt.Println()
}
} else {
for i := len(tss) - 1; i >= 0; i-- {
printTipSet(cctx.String("format"), tss[i])
}
}
return nil
},
@@ -931,13 +1000,11 @@ var slashConsensusFault = &cli.Command{
}
msg := &types.Message{
To: maddr,
From: def,
Value: types.NewInt(0),
GasPrice: types.NewInt(1),
GasLimit: 10000000,
Method: builtin.MethodsMiner.ReportConsensusFault,
Params: enc,
To: maddr,
From: def,
Value: types.NewInt(0),
Method: builtin.MethodsMiner.ReportConsensusFault,
Params: enc,
}
smsg, err := api.MpoolPushMessage(ctx, msg)
@@ -950,3 +1017,30 @@ var slashConsensusFault = &cli.Command{
return nil
},
}
var chainGasPriceCmd = &cli.Command{
Name: "gas-price",
Usage: "Estimate gas prices",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
nb := []int{1, 2, 3, 5, 10, 20, 50, 100, 300}
for _, nblocks := range nb {
addr := builtin.SystemActorAddr // TODO: make real when used in GasEstimateGasPremium
est, err := api.GasEstimateGasPremium(ctx, uint64(nblocks), addr, 10000, types.EmptyTSK)
if err != nil {
return err
}
fmt.Printf("%d blocks: %s (%s)\n", nblocks, est, types.FIL(est))
}
return nil
},
}
+498 -46
View File
@@ -1,13 +1,17 @@
package cli
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"text/tabwriter"
"time"
"github.com/docker/go-units"
"github.com/fatih/color"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-cidutil/cidenc"
"github.com/libp2p/go-libp2p-core/peer"
@@ -17,12 +21,16 @@ import (
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-fil-markets/storagemarket"
"github.com/filecoin-project/go-multistore"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/builtin/market"
"github.com/filecoin-project/lotus/api"
lapi "github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/lib/tablewriter"
)
var CidBaseFlag = cli.StringFlag{
@@ -55,16 +63,18 @@ var clientCmd = &cli.Command{
Name: "client",
Usage: "Make deals, store data, retrieve data",
Subcommands: []*cli.Command{
clientImportCmd,
clientDropCmd,
clientCommPCmd,
clientLocalCmd,
clientDealCmd,
clientFindCmd,
clientRetrieveCmd,
clientQueryAskCmd,
clientListDeals,
clientCarGenCmd,
WithCategory("storage", clientDealCmd),
WithCategory("storage", clientQueryAskCmd),
WithCategory("storage", clientListDeals),
WithCategory("storage", clientGetDealCmd),
WithCategory("data", clientImportCmd),
WithCategory("data", clientDropCmd),
WithCategory("data", clientLocalCmd),
WithCategory("retrieval", clientFindCmd),
WithCategory("retrieval", clientRetrieveCmd),
WithCategory("util", clientCommPCmd),
WithCategory("util", clientCarGenCmd),
WithCategory("util", clientInfoCmd),
},
}
@@ -91,6 +101,11 @@ var clientImportCmd = &cli.Command{
}
defer closer()
ctx := ReqContext(cctx)
if cctx.NArg() != 1 {
return xerrors.New("expected input path as the only arg")
}
absPath, err := filepath.Abs(cctx.Args().First())
if err != nil {
return err
@@ -135,14 +150,14 @@ var clientDropCmd = &cli.Command{
defer closer()
ctx := ReqContext(cctx)
var ids []int64
var ids []multistore.StoreID
for i, s := range cctx.Args().Slice() {
id, err := strconv.ParseInt(s, 10, 64)
id, err := strconv.ParseInt(s, 10, 0)
if err != nil {
return xerrors.Errorf("parsing %d-th import ID: %w", i, err)
}
ids = append(ids, id)
ids = append(ids, multistore.StoreID(id))
}
for _, id := range ids {
@@ -157,7 +172,7 @@ var clientDropCmd = &cli.Command{
var clientCommPCmd = &cli.Command{
Name: "commP",
Usage: "calculate the piece-cid (commP) of a CAR file",
Usage: "Calculate the piece-cid (commP) of a CAR file",
ArgsUsage: "[inputFile minerAddress]",
Flags: []cli.Flag{
&CidBaseFlag,
@@ -197,7 +212,7 @@ var clientCommPCmd = &cli.Command{
var clientCarGenCmd = &cli.Command{
Name: "generate-car",
Usage: "generate a car file from input",
Usage: "Generate a car file from input",
ArgsUsage: "[inputPath outputPath]",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
@@ -303,6 +318,10 @@ var clientDealCmd = &cli.Command{
&CidBaseFlag,
},
Action: func(cctx *cli.Context) error {
if !cctx.Args().Present() {
return interactiveDeal(cctx)
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
@@ -336,6 +355,10 @@ var clientDealCmd = &cli.Command{
return err
}
if abi.ChainEpoch(dur) < build.MinDealDuration {
return xerrors.Errorf("minimum deal duration is %d blocks", build.MinDealDuration)
}
var a address.Address
if from := cctx.String("from"); from != "" {
faddr, err := address.NewFromString(from)
@@ -420,10 +443,207 @@ var clientDealCmd = &cli.Command{
},
}
func interactiveDeal(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
state := "import"
var data cid.Cid
var days int
var maddr address.Address
var ask storagemarket.StorageAsk
var epochPrice big.Int
var epochs abi.ChainEpoch
var a address.Address
if from := cctx.String("from"); from != "" {
faddr, err := address.NewFromString(from)
if err != nil {
return xerrors.Errorf("failed to parse 'from' address: %w", err)
}
a = faddr
} else {
def, err := api.WalletDefaultAddress(ctx)
if err != nil {
return err
}
a = def
}
printErr := func(err error) {
fmt.Printf("%s %s\n", color.RedString("Error:"), err.Error())
}
for {
// TODO: better exit handling
if err := ctx.Err(); err != nil {
return err
}
switch state {
case "import":
fmt.Print("Data CID (from " + color.YellowString("lotus client import") + "): ")
var cidStr string
_, err := fmt.Scan(&cidStr)
if err != nil {
printErr(xerrors.Errorf("reading cid string: %w", err))
continue
}
data, err = cid.Parse(cidStr)
if err != nil {
printErr(xerrors.Errorf("parsing cid string: %w", err))
continue
}
state = "duration"
case "duration":
fmt.Print("Deal duration (days): ")
_, err := fmt.Scan(&days)
if err != nil {
printErr(xerrors.Errorf("parsing duration: %w", err))
continue
}
state = "miner"
case "miner":
fmt.Print("Miner Address (t0..): ")
var maddrStr string
_, err := fmt.Scan(&maddrStr)
if err != nil {
printErr(xerrors.Errorf("reading miner address: %w", err))
continue
}
maddr, err = address.NewFromString(maddrStr)
if err != nil {
printErr(xerrors.Errorf("parsing miner address: %w", err))
continue
}
state = "query"
case "query":
color.Blue(".. querying miner ask")
mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
if err != nil {
printErr(xerrors.Errorf("failed to get peerID for miner: %w", err))
state = "miner"
continue
}
a, err := api.ClientQueryAsk(ctx, mi.PeerId, maddr)
if err != nil {
printErr(xerrors.Errorf("failed to query ask: %w", err))
state = "miner"
continue
}
ask = *a.Ask
// TODO: run more validation
state = "confirm"
case "confirm":
fromBal, err := api.WalletBalance(ctx, a)
if err != nil {
return xerrors.Errorf("checking from address balance: %w", err)
}
color.Blue(".. calculating data size\n")
ds, err := api.ClientDealSize(ctx, data)
if err != nil {
return err
}
dur := 24 * time.Hour * time.Duration(days)
epochs = abi.ChainEpoch(dur / (time.Duration(build.BlockDelaySecs) * time.Second))
// TODO: do some more or epochs math (round to miner PP, deal start buffer)
gib := types.NewInt(1 << 30)
// TODO: price is based on PaddedPieceSize, right?
epochPrice = types.BigDiv(types.BigMul(ask.Price, types.NewInt(uint64(ds.PieceSize))), gib)
totalPrice := types.BigMul(epochPrice, types.NewInt(uint64(epochs)))
fmt.Printf("-----\n")
fmt.Printf("Proposing from %s\n", a)
fmt.Printf("\tBalance: %s\n", types.FIL(fromBal))
fmt.Printf("\n")
fmt.Printf("Piece size: %s (Payload size: %s)\n", units.BytesSize(float64(ds.PieceSize)), units.BytesSize(float64(ds.PayloadSize)))
fmt.Printf("Duration: %s\n", dur)
fmt.Printf("Total price: ~%s (%s per epoch)\n", types.FIL(totalPrice), types.FIL(epochPrice))
state = "accept"
case "accept":
fmt.Print("\nAccept (yes/no): ")
var yn string
_, err := fmt.Scan(&yn)
if err != nil {
return err
}
if yn == "no" {
return nil
}
if yn != "yes" {
fmt.Println("Type in full 'yes' or 'no'")
continue
}
state = "execute"
case "execute":
color.Blue(".. executing")
proposal, err := api.ClientStartDeal(ctx, &lapi.StartDealParams{
Data: &storagemarket.DataRef{
TransferType: storagemarket.TTGraphsync,
Root: data,
},
Wallet: a,
Miner: maddr,
EpochPrice: epochPrice,
MinBlocksDuration: uint64(epochs),
DealStartEpoch: abi.ChainEpoch(cctx.Int64("start-epoch")),
FastRetrieval: cctx.Bool("fast-retrieval"),
VerifiedDeal: false, // TODO: Allow setting
})
if err != nil {
return err
}
encoder, err := GetCidEncoder(cctx)
if err != nil {
return err
}
fmt.Println("\nDeal CID:", color.GreenString(encoder.Encode(*proposal)))
return nil
default:
return xerrors.Errorf("unknown state: %s", state)
}
}
}
var clientFindCmd = &cli.Command{
Name: "find",
Usage: "find data in the network",
Usage: "Find data in the network",
ArgsUsage: "[dataCid]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "pieceCid",
Usage: "require data to be retrieved from a specific Piece CID",
},
},
Action: func(cctx *cli.Context) error {
if !cctx.Args().Present() {
fmt.Println("Usage: find [CID]")
@@ -453,26 +673,37 @@ var clientFindCmd = &cli.Command{
fmt.Println("LOCAL")
}
offers, err := api.ClientFindData(ctx, file)
var pieceCid *cid.Cid
if cctx.String("pieceCid") != "" {
parsed, err := cid.Parse(cctx.String("pieceCid"))
if err != nil {
return err
}
pieceCid = &parsed
}
offers, err := api.ClientFindData(ctx, file, pieceCid)
if err != nil {
return err
}
for _, offer := range offers {
if offer.Err != "" {
fmt.Printf("ERR %s@%s: %s\n", offer.Miner, offer.MinerPeerID, offer.Err)
fmt.Printf("ERR %s@%s: %s\n", offer.Miner, offer.MinerPeer.ID, offer.Err)
continue
}
fmt.Printf("RETRIEVAL %s@%s-%sfil-%s\n", offer.Miner, offer.MinerPeerID, types.FIL(offer.MinPrice), types.SizeStr(types.NewInt(offer.Size)))
fmt.Printf("RETRIEVAL %s@%s-%s-%s\n", offer.Miner, offer.MinerPeer.ID, types.FIL(offer.MinPrice), types.SizeStr(types.NewInt(offer.Size)))
}
return nil
},
}
const DefaultMaxRetrievePrice = 1
var clientRetrieveCmd = &cli.Command{
Name: "retrieve",
Usage: "retrieve data from network",
Usage: "Retrieve data from network",
ArgsUsage: "[dataCid outputPath]",
Flags: []cli.Flag{
&cli.StringFlag{
@@ -487,11 +718,18 @@ var clientRetrieveCmd = &cli.Command{
Name: "miner",
Usage: "miner address for retrieval, if not present it'll use local discovery",
},
&cli.StringFlag{
Name: "maxPrice",
Usage: fmt.Sprintf("maximum price the client is willing to consider (default: %d FIL)", DefaultMaxRetrievePrice),
},
&cli.StringFlag{
Name: "pieceCid",
Usage: "require data to be retrieved from a specific Piece CID",
},
},
Action: func(cctx *cli.Context) error {
if cctx.NArg() != 2 {
fmt.Println("Usage: retrieve [CID] [outfile]")
return nil
return ShowHelp(cctx, fmt.Errorf("incorrect number of arguments"))
}
fapi, closer, err := GetFullNodeAPI(cctx)
@@ -528,10 +766,34 @@ var clientRetrieveCmd = &cli.Command{
return nil
}*/ // TODO: fix
var pieceCid *cid.Cid
if cctx.String("pieceCid") != "" {
parsed, err := cid.Parse(cctx.String("pieceCid"))
if err != nil {
return err
}
pieceCid = &parsed
}
var offer api.QueryOffer
minerStrAddr := cctx.String("miner")
if minerStrAddr == "" { // Local discovery
offers, err := fapi.ClientFindData(ctx, file)
offers, err := fapi.ClientFindData(ctx, file, pieceCid)
var cleaned []api.QueryOffer
// filter out offers that errored
for _, o := range offers {
if o.Err == "" {
cleaned = append(cleaned, o)
}
}
offers = cleaned
// sort by price low to high
sort.Slice(offers, func(i, j int) bool {
return offers[i].MinPrice.LessThan(offers[j].MinPrice)
})
if err != nil {
return err
}
@@ -547,7 +809,7 @@ var clientRetrieveCmd = &cli.Command{
if err != nil {
return err
}
offer, err = fapi.ClientMinerQueryOffer(ctx, file, minerAddr)
offer, err = fapi.ClientMinerQueryOffer(ctx, minerAddr, file, pieceCid)
if err != nil {
return err
}
@@ -556,6 +818,21 @@ var clientRetrieveCmd = &cli.Command{
return fmt.Errorf("The received offer errored: %s", offer.Err)
}
maxPrice := types.FromFil(DefaultMaxRetrievePrice)
if cctx.String("maxPrice") != "" {
maxPriceFil, err := types.ParseFIL(cctx.String("maxPrice"))
if err != nil {
return xerrors.Errorf("parsing maxPrice: %w", err)
}
maxPrice = types.BigInt(maxPriceFil)
}
if offer.MinPrice.GreaterThan(maxPrice) {
return xerrors.Errorf("failed to find offer satisfying maxPrice: %s", maxPrice)
}
ref := &lapi.FileRef{
Path: cctx.Args().Get(1),
IsCAR: cctx.Bool("car"),
@@ -571,7 +848,7 @@ var clientRetrieveCmd = &cli.Command{
var clientQueryAskCmd = &cli.Command{
Name: "query-ask",
Usage: "find a miners ask",
Usage: "Find a miners ask",
ArgsUsage: "[minerAddress]",
Flags: []cli.Flag{
&cli.StringFlag{
@@ -655,6 +932,18 @@ var clientQueryAskCmd = &cli.Command{
var clientListDeals = &cli.Command{
Name: "list-deals",
Usage: "List storage market deals",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Usage: "print verbose deal details",
},
&cli.BoolFlag{
Name: "color",
Usage: "use color in display output",
Value: true,
},
},
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
@@ -687,36 +976,199 @@ var clientListDeals = &cli.Command{
} else {
onChain, err := api.StateMarketStorageDeal(ctx, v.DealID, head.Key())
if err != nil {
return err
deals = append(deals, deal{LocalDeal: v})
} else {
deals = append(deals, deal{
LocalDeal: v,
OnChainDealState: onChain.State,
})
}
}
}
color := cctx.Bool("color")
if cctx.Bool("verbose") {
w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0)
fmt.Fprintf(w, "DealCid\tDealId\tProvider\tState\tOn Chain?\tSlashed?\tPieceCID\tSize\tPrice\tDuration\tMessage\n")
for _, d := range deals {
onChain := "N"
if d.OnChainDealState.SectorStartEpoch != -1 {
onChain = fmt.Sprintf("Y (epoch %d)", d.OnChainDealState.SectorStartEpoch)
}
deals = append(deals, deal{
LocalDeal: v,
OnChainDealState: onChain.State,
slashed := "N"
if d.OnChainDealState.SlashEpoch != -1 {
slashed = fmt.Sprintf("Y (epoch %d)", d.OnChainDealState.SlashEpoch)
}
price := types.FIL(types.BigMul(d.LocalDeal.PricePerEpoch, types.NewInt(d.LocalDeal.Duration)))
fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%d\t%s\n", d.LocalDeal.ProposalCid, d.LocalDeal.DealID, d.LocalDeal.Provider, dealStateString(color, d.LocalDeal.State), onChain, slashed, d.LocalDeal.PieceCID, types.SizeStr(types.NewInt(d.LocalDeal.Size)), price, d.LocalDeal.Duration, d.LocalDeal.Message)
}
return w.Flush()
} else {
w := tablewriter.New(tablewriter.Col("DealCid"),
tablewriter.Col("DealId"),
tablewriter.Col("Provider"),
tablewriter.Col("State"),
tablewriter.Col("On Chain?"),
tablewriter.Col("Slashed?"),
tablewriter.Col("PieceCID"),
tablewriter.Col("Size"),
tablewriter.Col("Price"),
tablewriter.Col("Duration"),
tablewriter.NewLineCol("Message"))
for _, d := range deals {
propcid := d.LocalDeal.ProposalCid.String()
propcid = "..." + propcid[len(propcid)-8:]
onChain := "N"
if d.OnChainDealState.SectorStartEpoch != -1 {
onChain = fmt.Sprintf("Y (epoch %d)", d.OnChainDealState.SectorStartEpoch)
}
slashed := "N"
if d.OnChainDealState.SlashEpoch != -1 {
slashed = fmt.Sprintf("Y (epoch %d)", d.OnChainDealState.SlashEpoch)
}
piece := d.LocalDeal.PieceCID.String()
piece = "..." + piece[len(piece)-8:]
price := types.FIL(types.BigMul(d.LocalDeal.PricePerEpoch, types.NewInt(d.LocalDeal.Duration)))
w.Write(map[string]interface{}{
"DealCid": propcid,
"DealId": d.LocalDeal.DealID,
"Provider": d.LocalDeal.Provider,
"State": dealStateString(color, d.LocalDeal.State),
"On Chain?": onChain,
"Slashed?": slashed,
"PieceCID": piece,
"Size": types.SizeStr(types.NewInt(d.LocalDeal.Size)),
"Price": price,
"Duration": d.LocalDeal.Duration,
"Message": d.LocalDeal.Message,
})
}
return w.Flush(os.Stdout)
}
w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0)
fmt.Fprintf(w, "DealCid\tDealId\tProvider\tState\tOn Chain?\tSlashed?\tPieceCID\tSize\tPrice\tDuration\tMessage\n")
for _, d := range deals {
onChain := "N"
if d.OnChainDealState.SectorStartEpoch != -1 {
onChain = fmt.Sprintf("Y (epoch %d)", d.OnChainDealState.SectorStartEpoch)
}
slashed := "N"
if d.OnChainDealState.SlashEpoch != -1 {
slashed = fmt.Sprintf("Y (epoch %d)", d.OnChainDealState.SlashEpoch)
}
fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%d\t%s\n", d.LocalDeal.ProposalCid, d.LocalDeal.DealID, d.LocalDeal.Provider, storagemarket.DealStates[d.LocalDeal.State], onChain, slashed, d.LocalDeal.PieceCID, types.SizeStr(types.NewInt(d.LocalDeal.Size)), d.LocalDeal.PricePerEpoch, d.LocalDeal.Duration, d.LocalDeal.Message)
}
return w.Flush()
},
}
func dealStateString(c bool, state storagemarket.StorageDealStatus) string {
s := storagemarket.DealStates[state]
if !c {
return s
}
switch state {
case storagemarket.StorageDealError, storagemarket.StorageDealExpired:
return color.RedString(s)
case storagemarket.StorageDealActive:
return color.GreenString(s)
default:
return s
}
}
type deal struct {
LocalDeal lapi.DealInfo
OnChainDealState market.DealState
}
var clientGetDealCmd = &cli.Command{
Name: "get-deal",
Usage: "Print detailed deal information",
Action: func(cctx *cli.Context) error {
if !cctx.Args().Present() {
return cli.ShowCommandHelp(cctx, cctx.Command.Name)
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
propcid, err := cid.Decode(cctx.Args().First())
if err != nil {
return err
}
di, err := api.ClientGetDealInfo(ctx, propcid)
if err != nil {
return err
}
out := map[string]interface{}{
"DealInfo: ": di,
}
if di.DealID != 0 {
onChain, err := api.StateMarketStorageDeal(ctx, di.DealID, types.EmptyTSK)
if err != nil {
return err
}
out["OnChain"] = onChain
}
b, err := json.MarshalIndent(out, "", " ")
if err != nil {
return err
}
fmt.Println(string(b))
return nil
},
}
var clientInfoCmd = &cli.Command{
Name: "info",
Usage: "Print storage market client information",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "client",
Usage: "specify storage client address",
},
},
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
var addr address.Address
if clientFlag := cctx.String("client"); clientFlag != "" {
ca, err := address.NewFromString("client")
if err != nil {
return err
}
addr = ca
} else {
def, err := api.WalletDefaultAddress(ctx)
if err != nil {
return err
}
addr = def
}
balance, err := api.StateMarketBalance(ctx, addr, types.EmptyTSK)
if err != nil {
return err
}
fmt.Printf("Client Market Info:\n")
fmt.Printf("Locked Funds:\t%s\n", types.FIL(balance.Locked))
fmt.Printf("Escrowed Funds:\t%s\n", types.FIL(balance.Escrow))
return nil
},
}
+43 -21
View File
@@ -72,13 +72,25 @@ func flagForRepo(t repo.RepoType) string {
case repo.FullNode:
return "repo"
case repo.StorageMiner:
return "storagerepo"
return "miner-repo"
default:
panic(fmt.Sprintf("Unknown repo type: %v", t))
}
}
func envForRepo(t repo.RepoType) string {
switch t {
case repo.FullNode:
return "FULLNODE_API_INFO"
case repo.StorageMiner:
return "MINER_API_INFO"
default:
panic(fmt.Sprintf("Unknown repo type: %v", t))
}
}
// TODO remove after deprecation period
func envForRepoDeprecation(t repo.RepoType) string {
switch t {
case repo.FullNode:
return "FULLNODE_API_INFO"
@@ -90,14 +102,24 @@ func envForRepo(t repo.RepoType) string {
}
func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) {
if env, ok := os.LookupEnv(envForRepo(t)); ok {
envKey := envForRepo(t)
env, ok := os.LookupEnv(envKey)
if !ok {
// TODO remove after deprecation period
envKey = envForRepoDeprecation(t)
env, ok = os.LookupEnv(envKey)
if ok {
log.Warnf("Use deprecation env(%s) value, please use env(%s) instead.", envKey, envForRepo(t))
}
}
if ok {
sp := strings.SplitN(env, ":", 2)
if len(sp) != 2 {
log.Warnf("invalid env(%s) value, missing token or address", envForRepo(t))
log.Warnf("invalid env(%s) value, missing token or address", envKey)
} else {
ma, err := multiaddr.NewMultiaddr(sp[1])
if err != nil {
return APIInfo{}, xerrors.Errorf("could not parse multiaddr from env(%s): %w", envForRepo(t), err)
return APIInfo{}, xerrors.Errorf("could not parse multiaddr from env(%s): %w", envKey, err)
}
return APIInfo{
Addr: ma,
@@ -216,28 +238,28 @@ var CommonCommands = []*cli.Command{
logCmd,
waitApiCmd,
fetchParamCmd,
versionCmd,
VersionCmd,
}
var Commands = []*cli.Command{
withCategory("basic", sendCmd),
withCategory("basic", walletCmd),
withCategory("basic", clientCmd),
withCategory("basic", multisigCmd),
withCategory("basic", paychCmd),
withCategory("developer", authCmd),
withCategory("developer", mpoolCmd),
withCategory("developer", stateCmd),
withCategory("developer", chainCmd),
withCategory("developer", logCmd),
withCategory("developer", waitApiCmd),
withCategory("developer", fetchParamCmd),
withCategory("network", netCmd),
withCategory("network", syncCmd),
versionCmd,
WithCategory("basic", sendCmd),
WithCategory("basic", walletCmd),
WithCategory("basic", clientCmd),
WithCategory("basic", multisigCmd),
WithCategory("basic", paychCmd),
WithCategory("developer", authCmd),
WithCategory("developer", mpoolCmd),
WithCategory("developer", stateCmd),
WithCategory("developer", chainCmd),
WithCategory("developer", logCmd),
WithCategory("developer", waitApiCmd),
WithCategory("developer", fetchParamCmd),
WithCategory("network", netCmd),
WithCategory("network", syncCmd),
VersionCmd,
}
func withCategory(cat string, cmd *cli.Command) *cli.Command {
func WithCategory(cat string, cmd *cli.Command) *cli.Command {
cmd.Category = cat
return cmd
}
+46
View File
@@ -0,0 +1,46 @@
package cli
import (
"fmt"
"os"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
)
type PrintHelpErr struct {
Err error
Ctx *cli.Context
}
func (e *PrintHelpErr) Error() string {
return e.Err.Error()
}
func (e *PrintHelpErr) Unwrap() error {
return e.Err
}
func (e *PrintHelpErr) Is(o error) bool {
_, ok := o.(*PrintHelpErr)
return ok
}
func ShowHelp(cctx *cli.Context, err error) error {
return &PrintHelpErr{Err: err, Ctx: cctx}
}
func RunApp(app *cli.App) {
if err := app.Run(os.Args); err != nil {
if os.Getenv("LOTUS_DEV") != "" {
log.Warnf("%+v", err)
} else {
fmt.Printf("ERROR: %s\n\n", err)
}
var phe *PrintHelpErr
if xerrors.As(err, &phe) {
cli.ShowCommandHelp(phe.Ctx, phe.Ctx.Command.Name)
}
os.Exit(1)
}
}
+189 -2
View File
@@ -4,11 +4,13 @@ import (
"encoding/json"
"fmt"
"sort"
"strconv"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/lotus/chain/types"
)
@@ -20,6 +22,8 @@ var mpoolCmd = &cli.Command{
mpoolPending,
mpoolSub,
mpoolStat,
mpoolReplaceCmd,
mpoolFindCmd,
},
}
@@ -80,7 +84,7 @@ var mpoolPending = &cli.Command{
var mpoolSub = &cli.Command{
Name: "sub",
Usage: "Subscibe to mpool changes",
Usage: "Subscribe to mpool changes",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
@@ -221,10 +225,193 @@ var mpoolStat = &cli.Command{
return out[i].addr < out[j].addr
})
var total mpStat
for _, stat := range out {
fmt.Printf("%s, past: %d, cur: %d, future: %d\n", stat.addr, stat.past, stat.cur, stat.future)
total.past += stat.past
total.cur += stat.cur
total.future += stat.future
fmt.Printf("%s: past: %d, cur: %d, future: %d\n", stat.addr, stat.past, stat.cur, stat.future)
}
fmt.Println("-----")
fmt.Printf("total: past: %d, cur: %d, future: %d\n", total.past, total.cur, total.future)
return nil
},
}
var mpoolReplaceCmd = &cli.Command{
Name: "replace",
Usage: "replace a message in the mempool",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "gas-feecap",
Usage: "gas feecap for new message",
},
&cli.StringFlag{
Name: "gas-premium",
Usage: "gas price for new message",
},
&cli.Int64Flag{
Name: "gas-limit",
Usage: "gas price for new message",
},
},
ArgsUsage: "[from] [nonce]",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() < 2 {
return cli.ShowCommandHelp(cctx, cctx.Command.Name)
}
from, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
nonce, err := strconv.ParseUint(cctx.Args().Get(1), 10, 64)
if err != nil {
return err
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
ts, err := api.ChainHead(ctx)
if err != nil {
return xerrors.Errorf("getting chain head: %w", err)
}
pending, err := api.MpoolPending(ctx, ts.Key())
if err != nil {
return err
}
var found *types.SignedMessage
for _, p := range pending {
if p.Message.From == from && p.Message.Nonce == nonce {
found = p
break
}
}
if found == nil {
return fmt.Errorf("no pending message found from %s with nonce %d", from, nonce)
}
msg := found.Message
msg.GasLimit = cctx.Int64("gas-limit")
msg.GasPremium, err = types.BigFromString(cctx.String("gas-premium"))
if err != nil {
return fmt.Errorf("parsing gas-premium: %w", err)
}
// TODO: estiamte fee cap here
msg.GasFeeCap, err = types.BigFromString(cctx.String("gas-feecap"))
if err != nil {
return fmt.Errorf("parsing gas-feecap: %w", err)
}
smsg, err := api.WalletSignMessage(ctx, msg.From, &msg)
if err != nil {
return fmt.Errorf("failed to sign message: %w", err)
}
cid, err := api.MpoolPush(ctx, smsg)
if err != nil {
return fmt.Errorf("failed to push new message to mempool: %w", err)
}
fmt.Println("new message cid: ", cid)
return nil
},
}
var mpoolFindCmd = &cli.Command{
Name: "find",
Usage: "find a message in the mempool",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "from",
Usage: "search for messages with given 'from' address",
},
&cli.StringFlag{
Name: "to",
Usage: "search for messages with given 'to' address",
},
&cli.Int64Flag{
Name: "method",
Usage: "search for messages with given method",
},
},
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
pending, err := api.MpoolPending(ctx, types.EmptyTSK)
if err != nil {
return err
}
var toFilter, fromFilter address.Address
if cctx.IsSet("to") {
a, err := address.NewFromString(cctx.String("to"))
if err != nil {
return fmt.Errorf("'to' address was invalid: %w", err)
}
toFilter = a
}
if cctx.IsSet("from") {
a, err := address.NewFromString(cctx.String("from"))
if err != nil {
return fmt.Errorf("'from' address was invalid: %w", err)
}
fromFilter = a
}
var methodFilter *abi.MethodNum
if cctx.IsSet("method") {
m := abi.MethodNum(cctx.Int64("method"))
methodFilter = &m
}
var out []*types.SignedMessage
for _, m := range pending {
if toFilter != address.Undef && m.Message.To != toFilter {
continue
}
if fromFilter != address.Undef && m.Message.From != fromFilter {
continue
}
if methodFilter != nil && *methodFilter != m.Message.Method {
continue
}
out = append(out, m)
}
b, err := json.MarshalIndent(out, "", " ")
if err != nil {
return err
}
fmt.Println(string(b))
return nil
},
}
+313 -56
View File
@@ -11,14 +11,15 @@ import (
"strconv"
"text/tabwriter"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/filecoin-project/go-address"
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
samsig "github.com/filecoin-project/specs-actors/actors/builtin/multisig"
cid "github.com/ipfs/go-cid"
"github.com/ipfs/go-hamt-ipld"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/urfave/cli/v2"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/api"
@@ -30,17 +31,14 @@ import (
var multisigCmd = &cli.Command{
Name: "msig",
Usage: "Interact with a multisig wallet",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "source",
Usage: "specify the account to send propose from",
},
},
Subcommands: []*cli.Command{
msigCreateCmd,
msigInspectCmd,
msigProposeCmd,
msigApproveCmd,
msigSwapProposeCmd,
msigSwapApproveCmd,
msigSwapCancelCmd,
},
}
@@ -50,19 +48,29 @@ var msigCreateCmd = &cli.Command{
ArgsUsage: "[address1 address2 ...]",
Flags: []cli.Flag{
&cli.Int64Flag{
Name: "required",
Name: "required",
Usage: "number of required approvals (uses number of signers provided if omitted)",
},
&cli.StringFlag{
Name: "value",
Usage: "initial funds to give to multisig",
Value: "0",
},
&cli.StringFlag{
Name: "duration",
Usage: "length of the period over which funds unlock",
Value: "0",
},
&cli.StringFlag{
Name: "from",
Usage: "account to send the create message from",
},
},
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() < 1 {
return ShowHelp(cctx, fmt.Errorf("multisigs must have at least one signer"))
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
@@ -70,10 +78,6 @@ var msigCreateCmd = &cli.Command{
defer closer()
ctx := ReqContext(cctx)
if cctx.Args().Len() < 1 {
return fmt.Errorf("multisigs must have at least one signer")
}
var addrs []address.Address
for _, a := range cctx.Args().Slice() {
addr, err := address.NewFromString(a)
@@ -114,9 +118,11 @@ var msigCreateCmd = &cli.Command{
required = uint64(len(addrs))
}
d := abi.ChainEpoch(cctx.Uint64("duration"))
gp := types.NewInt(1)
msgCid, err := api.MsigCreate(ctx, required, addrs, intVal, sendAddr, gp)
msgCid, err := api.MsigCreate(ctx, required, addrs, d, intVal, sendAddr, gp)
if err != nil {
return err
}
@@ -150,8 +156,17 @@ var msigInspectCmd = &cli.Command{
Name: "inspect",
Usage: "Inspect a multisig wallet",
ArgsUsage: "[address]",
Flags: []cli.Flag{},
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "vesting",
Usage: "Include vesting details",
},
},
Action: func(cctx *cli.Context) error {
if !cctx.Args().Present() {
return ShowHelp(cctx, fmt.Errorf("must specify address of multisig to inspect"))
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
@@ -159,10 +174,6 @@ var msigInspectCmd = &cli.Command{
defer closer()
ctx := ReqContext(cctx)
if !cctx.Args().Present() {
return fmt.Errorf("must specify address of multisig to inspect")
}
maddr, err := address.NewFromString(cctx.Args().First())
if err != nil {
return err
@@ -178,12 +189,26 @@ var msigInspectCmd = &cli.Command{
return err
}
head, err := api.ChainHead(ctx)
if err != nil {
return err
}
var mstate samsig.State
if err := mstate.UnmarshalCBOR(bytes.NewReader(obj)); err != nil {
return err
}
fmt.Printf("Balance: %sfil\n", types.FIL(act.Balance))
locked := mstate.AmountLocked(head.Height() - mstate.StartEpoch)
fmt.Printf("Balance: %s\n", types.FIL(act.Balance))
fmt.Printf("Spendable: %s\n", types.FIL(types.BigSub(act.Balance, locked)))
if cctx.Bool("vesting") {
fmt.Printf("InitialBalance: %s\n", types.FIL(mstate.InitialBalance))
fmt.Printf("StartEpoch: %d\n", mstate.StartEpoch)
fmt.Printf("UnlockDuration: %d\n", mstate.UnlockDuration)
}
fmt.Printf("Threshold: %d / %d\n", mstate.NumApprovalsThreshold, len(mstate.Signers))
fmt.Println("Signers:")
for _, s := range mstate.Signers {
@@ -223,24 +248,20 @@ var msigInspectCmd = &cli.Command{
func GetMultisigPending(ctx context.Context, lapi api.FullNode, hroot cid.Cid) (map[int64]*samsig.Transaction, error) {
bs := apibstore.NewAPIBlockstore(lapi)
cst := cbor.NewCborStore(bs)
store := adt.WrapStore(ctx, cbor.NewCborStore(bs))
nd, err := hamt.LoadNode(ctx, cst, hroot, hamt.UseTreeBitWidth(5))
nd, err := adt.AsMap(store, hroot)
if err != nil {
return nil, err
}
txs := make(map[int64]*samsig.Transaction)
err = nd.ForEach(ctx, func(k string, val interface{}) error {
d := val.(*cbg.Deferred)
var tx samsig.Transaction
if err := tx.UnmarshalCBOR(bytes.NewReader(d.Raw)); err != nil {
return err
}
var tx samsig.Transaction
err = nd.ForEach(&tx, func(k string) error {
txid, _ := binary.Varint([]byte(k))
txs[txid] = &tx
cpy := tx // copy so we don't clobber on future iterations.
txs[txid] = &cpy
return nil
})
if err != nil {
@@ -268,11 +289,19 @@ var msigProposeCmd = &cli.Command{
ArgsUsage: "[multisigAddress destinationAddress value <methodId methodParams> (optional)]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "source",
Name: "from",
Usage: "account to send the propose message from",
},
},
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() < 3 {
return ShowHelp(cctx, fmt.Errorf("must pass at least multisig address, destination, and value"))
}
if cctx.Args().Len() > 3 && cctx.Args().Len() != 5 {
return ShowHelp(cctx, fmt.Errorf("must either pass three or five arguments"))
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
@@ -280,14 +309,6 @@ var msigProposeCmd = &cli.Command{
defer closer()
ctx := ReqContext(cctx)
if cctx.Args().Len() < 3 {
return fmt.Errorf("must pass multisig address, destination, and value")
}
if cctx.Args().Len() > 3 && cctx.Args().Len() != 5 {
return fmt.Errorf("usage: msig propose <msig addr> <desination> <value> [ <method> <params> ]")
}
msig, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
@@ -320,8 +341,8 @@ var msigProposeCmd = &cli.Command{
}
var from address.Address
if cctx.IsSet("source") {
f, err := address.NewFromString(cctx.String("source"))
if cctx.IsSet("from") {
f, err := address.NewFromString(cctx.String("from"))
if err != nil {
return err
}
@@ -350,12 +371,17 @@ var msigProposeCmd = &cli.Command{
return fmt.Errorf("proposal returned exit %d", wait.Receipt.ExitCode)
}
_, v, err := cbg.CborReadHeader(bytes.NewReader(wait.Receipt.Return))
if err != nil {
return err
var retval samsig.ProposeReturn
if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
return fmt.Errorf("failed to unmarshal propose return value: %w", err)
}
fmt.Printf("Transaction ID: %d\n", v)
fmt.Printf("Transaction ID: %d\n", retval.TxnID)
if retval.Applied {
fmt.Printf("Transaction was executed during propose\n")
fmt.Printf("Exit Code: %d\n", retval.Code)
fmt.Printf("Return Value: %x\n", retval.Ret)
}
return nil
},
@@ -367,11 +393,19 @@ var msigApproveCmd = &cli.Command{
ArgsUsage: "[multisigAddress messageId proposerAddress destination value <methodId methodParams> (optional)]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "source",
Name: "from",
Usage: "account to send the approve message from",
},
},
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() < 5 {
return ShowHelp(cctx, fmt.Errorf("must pass multisig address, message ID, proposer address, destination, and value"))
}
if cctx.Args().Len() > 5 && cctx.Args().Len() != 7 {
return ShowHelp(cctx, fmt.Errorf("usage: msig approve <msig addr> <message ID> <proposer address> <desination> <value> [ <method> <params> ]"))
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
@@ -379,14 +413,6 @@ var msigApproveCmd = &cli.Command{
defer closer()
ctx := ReqContext(cctx)
if cctx.Args().Len() < 5 {
return fmt.Errorf("must pass multisig address, message ID, proposer address, destination, and value")
}
if cctx.Args().Len() > 5 && cctx.Args().Len() != 7 {
return fmt.Errorf("usage: msig approve <msig addr> <message ID> <proposer address> <desination> <value> [ <method> <params> ]")
}
msig, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
@@ -436,8 +462,8 @@ var msigApproveCmd = &cli.Command{
}
var from address.Address
if cctx.IsSet("source") {
f, err := address.NewFromString(cctx.String("source"))
if cctx.IsSet("from") {
f, err := address.NewFromString(cctx.String("from"))
if err != nil {
return err
}
@@ -469,3 +495,234 @@ var msigApproveCmd = &cli.Command{
return nil
},
}
var msigSwapProposeCmd = &cli.Command{
Name: "swap-propose",
Usage: "Propose to swap signers",
ArgsUsage: "[multisigAddress oldAddress newAddress]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "from",
Usage: "account to send the approve message from",
},
},
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 3 {
return ShowHelp(cctx, fmt.Errorf("must pass multisig address, old signer address, new signer address"))
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
oldAdd, err := address.NewFromString(cctx.Args().Get(1))
if err != nil {
return err
}
newAdd, err := address.NewFromString(cctx.Args().Get(2))
if err != nil {
return err
}
var from address.Address
if cctx.IsSet("from") {
f, err := address.NewFromString(cctx.String("from"))
if err != nil {
return err
}
from = f
} else {
defaddr, err := api.WalletDefaultAddress(ctx)
if err != nil {
return err
}
from = defaddr
}
msgCid, err := api.MsigSwapPropose(ctx, msig, from, oldAdd, newAdd)
if err != nil {
return err
}
fmt.Println("sent swap proposal in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, build.MessageConfidence)
if err != nil {
return err
}
if wait.Receipt.ExitCode != 0 {
return fmt.Errorf("swap proposal returned exit %d", wait.Receipt.ExitCode)
}
return nil
},
}
var msigSwapApproveCmd = &cli.Command{
Name: "swap-approve",
Usage: "Approve a message to swap signers",
ArgsUsage: "[multisigAddress proposerAddress txId oldAddress newAddress]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "from",
Usage: "account to send the approve message from",
},
},
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 5 {
return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, transaction id, old signer address, new signer address"))
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
prop, err := address.NewFromString(cctx.Args().Get(1))
if err != nil {
return err
}
txid, err := strconv.ParseUint(cctx.Args().Get(2), 10, 64)
if err != nil {
return err
}
oldAdd, err := address.NewFromString(cctx.Args().Get(3))
if err != nil {
return err
}
newAdd, err := address.NewFromString(cctx.Args().Get(4))
if err != nil {
return err
}
var from address.Address
if cctx.IsSet("from") {
f, err := address.NewFromString(cctx.String("from"))
if err != nil {
return err
}
from = f
} else {
defaddr, err := api.WalletDefaultAddress(ctx)
if err != nil {
return err
}
from = defaddr
}
msgCid, err := api.MsigSwapApprove(ctx, msig, from, txid, prop, oldAdd, newAdd)
if err != nil {
return err
}
fmt.Println("sent swap approval in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, build.MessageConfidence)
if err != nil {
return err
}
if wait.Receipt.ExitCode != 0 {
return fmt.Errorf("swap approval returned exit %d", wait.Receipt.ExitCode)
}
return nil
},
}
var msigSwapCancelCmd = &cli.Command{
Name: "swap-cancel",
Usage: "Cancel a message to swap signers",
ArgsUsage: "[multisigAddress txId oldAddress newAddress]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "from",
Usage: "account to send the approve message from",
},
},
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 4 {
return ShowHelp(cctx, fmt.Errorf("must pass multisig address, transaction id, old signer address, new signer address"))
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
txid, err := strconv.ParseUint(cctx.Args().Get(1), 10, 64)
if err != nil {
return err
}
oldAdd, err := address.NewFromString(cctx.Args().Get(2))
if err != nil {
return err
}
newAdd, err := address.NewFromString(cctx.Args().Get(3))
if err != nil {
return err
}
var from address.Address
if cctx.IsSet("from") {
f, err := address.NewFromString(cctx.String("from"))
if err != nil {
return err
}
from = f
} else {
defaddr, err := api.WalletDefaultAddress(ctx)
if err != nil {
return err
}
from = defaddr
}
msgCid, err := api.MsigSwapCancel(ctx, msig, from, txid, oldAdd, newAdd)
if err != nil {
return err
}
fmt.Println("sent swap approval in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, build.MessageConfidence)
if err != nil {
return err
}
if wait.Receipt.ExitCode != 0 {
return fmt.Errorf("swap approval returned exit %d", wait.Receipt.ExitCode)
}
return nil
},
}
+26 -8
View File
@@ -1,7 +1,9 @@
package cli
import (
"encoding/json"
"fmt"
"os"
"sort"
"strings"
@@ -16,16 +18,16 @@ var netCmd = &cli.Command{
Name: "net",
Usage: "Manage P2P Network",
Subcommands: []*cli.Command{
netPeers,
NetPeers,
netConnect,
netListen,
netId,
NetListen,
NetId,
netFindPeer,
netScores,
},
}
var netPeers = &cli.Command{
var NetPeers = &cli.Command{
Name: "peers",
Usage: "Print peers",
Action: func(cctx *cli.Context) error {
@@ -55,6 +57,12 @@ var netPeers = &cli.Command{
var netScores = &cli.Command{
Name: "scores",
Usage: "Print peers' pubsub scores",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "extended",
Usage: "print extended peer scores in json",
},
},
Action: func(cctx *cli.Context) error {
api, closer, err := GetAPI(cctx)
if err != nil {
@@ -67,15 +75,25 @@ var netScores = &cli.Command{
return err
}
for _, peer := range scores {
fmt.Printf("%s, %f\n", peer.ID, peer.Score)
if cctx.Bool("extended") {
enc := json.NewEncoder(os.Stdout)
for _, peer := range scores {
err := enc.Encode(peer)
if err != nil {
return err
}
}
} else {
for _, peer := range scores {
fmt.Printf("%s, %f\n", peer.ID, peer.Score.Score)
}
}
return nil
},
}
var netListen = &cli.Command{
var NetListen = &cli.Command{
Name: "listen",
Usage: "List listen addresses",
Action: func(cctx *cli.Context) error {
@@ -129,7 +147,7 @@ var netConnect = &cli.Command{
},
}
var netId = &cli.Command{
var NetId = &cli.Command{
Name: "id",
Usage: "Get node identity",
Action: func(cctx *cli.Context) error {
+105 -14
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/base64"
"fmt"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/go-address"
@@ -20,31 +21,33 @@ var paychCmd = &cli.Command{
paychGetCmd,
paychListCmd,
paychVoucherCmd,
paychSettleCmd,
paychCloseCmd,
},
}
var paychGetCmd = &cli.Command{
Name: "get",
Usage: "Create a new payment channel or get existing one",
Usage: "Create a new payment channel or get existing one and add amount to it",
ArgsUsage: "[fromAddress toAddress amount]",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 3 {
return fmt.Errorf("must pass three arguments: <from> <to> <available funds>")
return ShowHelp(cctx, fmt.Errorf("must pass three arguments: <from> <to> <available funds>"))
}
from, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return fmt.Errorf("failed to parse from address: %s", err)
return ShowHelp(cctx, fmt.Errorf("failed to parse from address: %s", err))
}
to, err := address.NewFromString(cctx.Args().Get(1))
if err != nil {
return fmt.Errorf("failed to parse to address: %s", err)
return ShowHelp(cctx, fmt.Errorf("failed to parse to address: %s", err))
}
amt, err := types.BigFromString(cctx.Args().Get(2))
amt, err := types.ParseFIL(cctx.Args().Get(2))
if err != nil {
return fmt.Errorf("parsing amount failed: %s", err)
return ShowHelp(cctx, fmt.Errorf("parsing amount failed: %s", err))
}
api, closer, err := GetFullNodeAPI(cctx)
@@ -55,12 +58,20 @@ var paychGetCmd = &cli.Command{
ctx := ReqContext(cctx)
info, err := api.PaychGet(ctx, from, to, amt)
// Send a message to chain to create channel / add funds to existing
// channel
info, err := api.PaychGet(ctx, from, to, types.BigInt(amt))
if err != nil {
return err
}
fmt.Println(info.Channel.String())
// Wait for the message to be confirmed
chAddr, err := api.PaychGetWaitReady(ctx, info.WaitSentinel)
if err != nil {
return err
}
fmt.Println(chAddr)
return nil
},
}
@@ -89,6 +100,86 @@ var paychListCmd = &cli.Command{
},
}
var paychSettleCmd = &cli.Command{
Name: "settle",
Usage: "Settle a payment channel",
ArgsUsage: "[channelAddress]",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 1 {
return fmt.Errorf("must pass payment channel address")
}
ch, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return fmt.Errorf("failed to parse payment channel address: %s", err)
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
mcid, err := api.PaychSettle(ctx, ch)
if err != nil {
return err
}
mwait, err := api.StateWaitMsg(ctx, mcid, build.MessageConfidence)
if err != nil {
return nil
}
if mwait.Receipt.ExitCode != 0 {
return fmt.Errorf("settle message execution failed (exit code %d)", mwait.Receipt.ExitCode)
}
fmt.Printf("Settled channel %s\n", ch)
return nil
},
}
var paychCloseCmd = &cli.Command{
Name: "collect",
Usage: "Collect funds for a payment channel",
ArgsUsage: "[channelAddress]",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 1 {
return fmt.Errorf("must pass payment channel address")
}
ch, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return fmt.Errorf("failed to parse payment channel address: %s", err)
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
mcid, err := api.PaychCollect(ctx, ch)
if err != nil {
return err
}
mwait, err := api.StateWaitMsg(ctx, mcid, build.MessageConfidence)
if err != nil {
return nil
}
if mwait.Receipt.ExitCode != 0 {
return fmt.Errorf("collect message execution failed (exit code %d)", mwait.Receipt.ExitCode)
}
fmt.Printf("Collected funds for channel %s\n", ch)
return nil
},
}
var paychVoucherCmd = &cli.Command{
Name: "voucher",
Usage: "Interact with payment channel vouchers",
@@ -115,7 +206,7 @@ var paychVoucherCreateCmd = &cli.Command{
},
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 2 {
return fmt.Errorf("must pass two arguments: <channel> <amount>")
return ShowHelp(cctx, fmt.Errorf("must pass two arguments: <channel> <amount>"))
}
ch, err := address.NewFromString(cctx.Args().Get(0))
@@ -159,7 +250,7 @@ var paychVoucherCheckCmd = &cli.Command{
ArgsUsage: "[channelAddress voucher]",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 2 {
return fmt.Errorf("must pass payment channel address and voucher to validate")
return ShowHelp(cctx, fmt.Errorf("must pass payment channel address and voucher to validate"))
}
ch, err := address.NewFromString(cctx.Args().Get(0))
@@ -195,7 +286,7 @@ var paychVoucherAddCmd = &cli.Command{
ArgsUsage: "[channelAddress voucher]",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 2 {
return fmt.Errorf("must pass payment channel address and voucher")
return ShowHelp(cctx, fmt.Errorf("must pass payment channel address and voucher"))
}
ch, err := address.NewFromString(cctx.Args().Get(0))
@@ -237,7 +328,7 @@ var paychVoucherListCmd = &cli.Command{
},
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 1 {
return fmt.Errorf("must pass payment channel address")
return ShowHelp(cctx, fmt.Errorf("must pass payment channel address"))
}
ch, err := address.NewFromString(cctx.Args().Get(0))
@@ -281,7 +372,7 @@ var paychVoucherBestSpendableCmd = &cli.Command{
ArgsUsage: "[channelAddress]",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 1 {
return fmt.Errorf("must pass payment channel address")
return ShowHelp(cctx, fmt.Errorf("must pass payment channel address"))
}
ch, err := address.NewFromString(cctx.Args().Get(0))
@@ -336,7 +427,7 @@ var paychVoucherSubmitCmd = &cli.Command{
ArgsUsage: "[channelAddress voucher]",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 2 {
return fmt.Errorf("must pass payment channel address and voucher")
return ShowHelp(cctx, fmt.Errorf("must pass payment channel address and voucher"))
}
ch, err := address.NewFromString(cctx.Args().Get(0))
+94 -15
View File
@@ -1,11 +1,20 @@
package cli
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/urfave/cli/v2"
cbg "github.com/whyrusleeping/cbor-gen"
)
var sendCmd = &cli.Command{
@@ -14,21 +23,48 @@ var sendCmd = &cli.Command{
ArgsUsage: "[targetAddress] [amount]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "source",
Name: "from",
Usage: "optionally specify the account to send funds from",
},
&cli.StringFlag{
Name: "gas-price",
Name: "gas-premium",
Usage: "specify gas price to use in AttoFIL",
Value: "0",
},
&cli.StringFlag{
Name: "gas-feecap",
Usage: "specify gas fee cap to use in AttoFIL",
Value: "0",
},
&cli.Int64Flag{
Name: "gas-limit",
Usage: "specify gas limit",
Value: 0,
},
&cli.Int64Flag{
Name: "nonce",
Usage: "specify the nonce to use",
Value: -1,
},
&cli.Uint64Flag{
Name: "method",
Usage: "specify method to invoke",
Value: 0,
},
&cli.StringFlag{
Name: "params-json",
Usage: "specify invocation parameters in json",
},
&cli.StringFlag{
Name: "params-hex",
Usage: "specify invocation parameters in hex",
},
},
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 2 {
return ShowHelp(cctx, fmt.Errorf("'send' expects two arguments, target and amount"))
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
@@ -37,22 +73,18 @@ var sendCmd = &cli.Command{
ctx := ReqContext(cctx)
if cctx.Args().Len() != 2 {
return fmt.Errorf("'send' expects two arguments, target and amount")
}
toAddr, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
return ShowHelp(cctx, fmt.Errorf("failed to parse target address: %w", err))
}
val, err := types.ParseFIL(cctx.Args().Get(1))
if err != nil {
return err
return ShowHelp(cctx, fmt.Errorf("failed to parse amount: %w", err))
}
var fromAddr address.Address
if from := cctx.String("source"); from == "" {
if from := cctx.String("from"); from == "" {
defaddr, err := api.WalletDefaultAddress(ctx)
if err != nil {
return err
@@ -68,17 +100,45 @@ var sendCmd = &cli.Command{
fromAddr = addr
}
gp, err := types.BigFromString(cctx.String("gas-price"))
gp, err := types.BigFromString(cctx.String("gas-premium"))
if err != nil {
return err
}
gfc, err := types.BigFromString(cctx.String("gas-feecap"))
if err != nil {
return err
}
method := abi.MethodNum(cctx.Uint64("method"))
var params []byte
if cctx.IsSet("params-json") {
decparams, err := decodeTypedParams(ctx, api, toAddr, method, cctx.String("params-json"))
if err != nil {
return fmt.Errorf("failed to decode json params: %w", err)
}
params = decparams
}
if cctx.IsSet("params-hex") {
if params != nil {
return fmt.Errorf("can only specify one of 'params-json' and 'params-hex'")
}
decparams, err := hex.DecodeString(cctx.String("params-hex"))
if err != nil {
return fmt.Errorf("failed to decode hex params: %w", err)
}
params = decparams
}
msg := &types.Message{
From: fromAddr,
To: toAddr,
Value: types.BigInt(val),
GasLimit: 10000,
GasPrice: gp,
From: fromAddr,
To: toAddr,
Value: types.BigInt(val),
GasPremium: gp,
GasFeeCap: gfc,
GasLimit: cctx.Int64("gas-limit"),
Method: method,
Params: params,
}
if cctx.Int64("nonce") > 0 {
@@ -104,3 +164,22 @@ var sendCmd = &cli.Command{
return nil
},
}
func decodeTypedParams(ctx context.Context, fapi api.FullNode, to address.Address, method abi.MethodNum, paramstr string) ([]byte, error) {
act, err := fapi.StateGetActor(ctx, to, types.EmptyTSK)
if err != nil {
return nil, err
}
p := reflect.New(stmgr.MethodsMap[act.Code][method].Params.Elem()).Interface().(cbg.CBORMarshaler)
if err := json.Unmarshal([]byte(paramstr), p); err != nil {
return nil, fmt.Errorf("unmarshaling input into params type: %w", err)
}
buf := new(bytes.Buffer)
if err := p.MarshalCBOR(buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
+71 -26
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"html/template"
"io"
"os"
"reflect"
"sort"
@@ -13,6 +14,8 @@ import (
"strings"
"time"
"github.com/multiformats/go-multiaddr"
"github.com/ipfs/go-cid"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/multiformats/go-multihash"
@@ -34,7 +37,6 @@ import (
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/miner"
)
var stateCmd = &cli.Command{
@@ -49,10 +51,11 @@ var stateCmd = &cli.Command{
Subcommands: []*cli.Command{
statePowerCmd,
stateSectorsCmd,
stateProvingSetCmd,
stateActiveSectorsCmd,
statePledgeCollateralCmd,
stateListActorsCmd,
stateListMinersCmd,
stateCircSupplyCmd,
stateGetActorCmd,
stateLookupIDCmd,
stateReplaySetCmd,
@@ -104,6 +107,15 @@ var stateMinerInfo = &cli.Command{
fmt.Printf("Worker:\t%s\n", mi.Worker)
fmt.Printf("PeerID:\t%s\n", mi.PeerId)
fmt.Printf("SectorSize:\t%s (%d)\n", types.SizeStr(types.NewInt(uint64(mi.SectorSize))), mi.SectorSize)
fmt.Printf("Multiaddrs: \t")
for _, addr := range mi.Multiaddrs {
a, err := multiaddr.NewMultiaddrBytes(addr)
if err != nil {
return xerrors.Errorf("undecodable listen address: %w", err)
}
fmt.Printf("%s ", a)
}
fmt.Println()
return nil
},
@@ -130,7 +142,15 @@ func LoadTipSet(ctx context.Context, cctx *cli.Context, api api.FullNode) (*type
return nil, nil
}
return ParseTipSetRef(ctx, api, tss)
}
func ParseTipSetRef(ctx context.Context, api api.FullNode, tss string) (*types.TipSet, error) {
if tss[0] == '@' {
if tss == "@head" {
return api.ChainHead(ctx)
}
var h uint64
if _, err := fmt.Sscanf(tss, "@%d", &h); err != nil {
return nil, xerrors.Errorf("parsing height tipset ref: %w", err)
@@ -241,9 +261,9 @@ var stateSectorsCmd = &cli.Command{
},
}
var stateProvingSetCmd = &cli.Command{
Name: "proving",
Usage: "Query the proving set of a miner",
var stateActiveSectorsCmd = &cli.Command{
Name: "active-sectors",
Usage: "Query the active sector set of a miner",
ArgsUsage: "[minerAddress]",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
@@ -268,7 +288,7 @@ var stateProvingSetCmd = &cli.Command{
return err
}
sectors, err := api.StateMinerProvingSet(ctx, maddr, ts.Key())
sectors, err := api.StateMinerActiveSectors(ctx, maddr, ts.Key())
if err != nil {
return err
}
@@ -337,7 +357,11 @@ var stateReplaySetCmd = &cli.Command{
return xerrors.Errorf("finding message in chain: %w", err)
}
ts, err = fapi.ChainGetTipSet(ctx, r.TipSet.Parents())
childTs, err := fapi.ChainGetTipSet(ctx, r.TipSet)
if err != nil {
return xerrors.Errorf("loading tipset: %w", err)
}
ts, err = fapi.ChainGetTipSet(ctx, childTs.Parents())
}
if err != nil {
return err
@@ -834,12 +858,7 @@ var stateComputeStateCmd = &cli.Command{
var msgs []*types.Message
if cctx.Bool("apply-mpool-messages") {
pmsgs, err := api.MpoolPending(ctx, ts.Key())
if err != nil {
return err
}
pmsgs, err = miner.SelectMessages(ctx, api.StateGetActor, ts, pmsgs)
pmsgs, err := api.MpoolSelect(ctx, ts.Key(), 1)
if err != nil {
return err
}
@@ -870,7 +889,7 @@ var stateComputeStateCmd = &cli.Command{
return c.Code, nil
}
return computeStateHTMLTempl(ts, stout, getCode)
return ComputeStateHTMLTempl(os.Stdout, ts, stout, getCode)
}
fmt.Println("computed state cid: ", stout.Root)
@@ -919,7 +938,7 @@ var compStateTemplate = `
font-size: 12px;
border-collapse: collapse;
}
tr {
tr {
border-top: 1px solid black;
border-bottom: 1px solid black;
}
@@ -938,7 +957,7 @@ var compStateTemplate = `
/**
Checked State
**/
.ellipsis-toggle input:checked + .ellipsis {
display: none;
}
@@ -991,7 +1010,7 @@ var compStateMsg = `
<div><pre class="params">{{JsonParams ($code) (.Msg.Method) (.Msg.Params) | html}}</pre></div>
{{end}}
<div><span class="slow-{{IsSlow .Duration}}-{{IsVerySlow .Duration}}">Took {{.Duration}}</span>, <span class="exit{{IntExit .MsgRct.ExitCode}}">Exit: <b>{{.MsgRct.ExitCode}}</b></span>{{if gt (len .MsgRct.Return) 0}}, Return{{end}}</div>
{{if gt (len .MsgRct.Return) 0}}
<div><pre class="ret">{{JsonReturn ($code) (.Msg.Method) (.MsgRct.Return) | html}}</pre></div>
{{end}}
@@ -1077,7 +1096,7 @@ type compStateHTMLIn struct {
Comp *api.ComputeStateOutput
}
func computeStateHTMLTempl(ts *types.TipSet, o *api.ComputeStateOutput, getCode func(addr address.Address) (cid.Cid, error)) error {
func ComputeStateHTMLTempl(w io.Writer, ts *types.TipSet, o *api.ComputeStateOutput, getCode func(addr address.Address) (cid.Cid, error)) error {
t, err := template.New("compute_state").Funcs(map[string]interface{}{
"GetCode": getCode,
"GetMethod": getMethod,
@@ -1110,7 +1129,7 @@ func computeStateHTMLTempl(ts *types.TipSet, o *api.ComputeStateOutput, getCode
return err
}
return t.ExecuteTemplate(os.Stdout, "compute_state", &compStateHTMLIn{
return t.ExecuteTemplate(w, "compute_state", &compStateHTMLIn{
TipSet: ts,
Comp: o,
})
@@ -1366,13 +1385,11 @@ var stateCallCmd = &cli.Command{
}
ret, err := api.StateCall(ctx, &types.Message{
From: froma,
To: toa,
Value: types.BigInt(value),
GasLimit: 10000000000,
GasPrice: types.NewInt(0),
Method: abi.MethodNum(method),
Params: params,
From: froma,
To: toa,
Value: types.BigInt(value),
Method: abi.MethodNum(method),
Params: params,
}, ts.Key())
if err != nil {
return fmt.Errorf("state call failed: %s", err)
@@ -1502,3 +1519,31 @@ func parseParamsForMethod(act cid.Cid, method uint64, args []string) ([]byte, er
}
return buf.Bytes(), nil
}
var stateCircSupplyCmd = &cli.Command{
Name: "circulating-supply",
Usage: "Get the current circulating supply of filecoin",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
ts, err := LoadTipSet(ctx, cctx, api)
if err != nil {
return err
}
circ, err := api.StateCirculatingSupply(ctx, ts.Key())
if err != nil {
return err
}
fmt.Println(types.FIL(circ))
return nil
},
}
+1 -1
View File
@@ -195,7 +195,7 @@ func SyncWait(ctx context.Context, napi api.FullNode) error {
case <-ctx.Done():
fmt.Println("\nExit by user")
return nil
case <-time.After(1 * time.Second):
case <-build.Clock.After(1 * time.Second):
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"github.com/urfave/cli/v2"
)
var versionCmd = &cli.Command{
var VersionCmd = &cli.Command{
Name: "version",
Usage: "Print version",
Action: func(cctx *cli.Context) error {
+10
View File
@@ -215,6 +215,10 @@ var walletImport = &cli.Command{
Usage: "specify input format for key",
Value: "hex-lotus",
},
&cli.BoolFlag{
Name: "as-default",
Usage: "import the given key as your new default key",
},
},
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
@@ -287,6 +291,12 @@ var walletImport = &cli.Command{
return err
}
if cctx.Bool("as-default") {
if err := api.WalletSetDefault(ctx, addr); err != nil {
return fmt.Errorf("failed to set default key: %w", err)
}
}
fmt.Printf("imported key %s successfully!\n", addr)
return nil
},