Merge master into feat/nv13

This commit is contained in:
Łukasz Magiera
2021-05-27 12:28:20 +02:00
350 changed files with 28302 additions and 3610 deletions
+7 -7
View File
@@ -8,7 +8,7 @@ import (
"github.com/filecoin-project/go-jsonrpc/auth"
"github.com/filecoin-project/lotus/api/apistruct"
"github.com/filecoin-project/lotus/api"
cliutil "github.com/filecoin-project/lotus/cli/util"
"github.com/filecoin-project/lotus/node/repo"
)
@@ -47,18 +47,18 @@ var AuthCreateAdminToken = &cli.Command{
perm := cctx.String("perm")
idx := 0
for i, p := range apistruct.AllPermissions {
for i, p := range api.AllPermissions {
if auth.Permission(perm) == p {
idx = i + 1
}
}
if idx == 0 {
return fmt.Errorf("--perm flag has to be one of: %s", apistruct.AllPermissions)
return fmt.Errorf("--perm flag has to be one of: %s", api.AllPermissions)
}
// slice on [:idx] so for example: 'sign' gives you [read, write, sign]
token, err := napi.AuthNew(ctx, apistruct.AllPermissions[:idx])
token, err := napi.AuthNew(ctx, api.AllPermissions[:idx])
if err != nil {
return err
}
@@ -95,18 +95,18 @@ var AuthApiInfoToken = &cli.Command{
perm := cctx.String("perm")
idx := 0
for i, p := range apistruct.AllPermissions {
for i, p := range api.AllPermissions {
if auth.Permission(perm) == p {
idx = i + 1
}
}
if idx == 0 {
return fmt.Errorf("--perm flag has to be one of: %s", apistruct.AllPermissions)
return fmt.Errorf("--perm flag has to be one of: %s", api.AllPermissions)
}
// slice on [:idx] so for example: 'sign' gives you [read, write, sign]
token, err := napi.AuthNew(ctx, apistruct.AllPermissions[:idx])
token, err := napi.AuthNew(ctx, api.AllPermissions[:idx])
if err != nil {
return err
}
+23 -23
View File
@@ -31,7 +31,9 @@ import (
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/api"
lapi "github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/api/v0api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/stmgr"
@@ -722,12 +724,6 @@ var ChainGetCmd = &cli.Command{
return err
}
if ts == nil {
ts, err = api.ChainHead(ctx)
if err != nil {
return err
}
}
p = "/ipfs/" + ts.ParentState().String() + p
if cctx.Bool("verbose") {
fmt.Println(p)
@@ -806,7 +802,7 @@ var ChainGetCmd = &cli.Command{
type apiIpldStore struct {
ctx context.Context
api lapi.FullNode
api v0api.FullNode
}
func (ht *apiIpldStore) Context() context.Context {
@@ -834,7 +830,7 @@ func (ht *apiIpldStore) Put(ctx context.Context, v interface{}) (cid.Cid, error)
panic("No mutations allowed")
}
func handleAmt(ctx context.Context, api lapi.FullNode, r cid.Cid) error {
func handleAmt(ctx context.Context, api v0api.FullNode, r cid.Cid) error {
s := &apiIpldStore{ctx, api}
mp, err := adt.AsArray(s, r)
if err != nil {
@@ -847,7 +843,7 @@ func handleAmt(ctx context.Context, api lapi.FullNode, r cid.Cid) error {
})
}
func handleHamtEpoch(ctx context.Context, api lapi.FullNode, r cid.Cid) error {
func handleHamtEpoch(ctx context.Context, api v0api.FullNode, r cid.Cid) error {
s := &apiIpldStore{ctx, api}
mp, err := adt.AsMap(s, r)
if err != nil {
@@ -865,7 +861,7 @@ func handleHamtEpoch(ctx context.Context, api lapi.FullNode, r cid.Cid) error {
})
}
func handleHamtAddress(ctx context.Context, api lapi.FullNode, r cid.Cid) error {
func handleHamtAddress(ctx context.Context, api v0api.FullNode, r cid.Cid) error {
s := &apiIpldStore{ctx, api}
mp, err := adt.AsMap(s, r)
if err != nil {
@@ -1121,11 +1117,13 @@ var SlashConsensusFault = &cli.Command{
},
},
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
a := srv.FullNodeAPI()
ctx := ReqContext(cctx)
c1, err := cid.Parse(cctx.Args().Get(0))
@@ -1133,7 +1131,7 @@ var SlashConsensusFault = &cli.Command{
return xerrors.Errorf("parsing cid 1: %w", err)
}
b1, err := api.ChainGetBlock(ctx, c1)
b1, err := a.ChainGetBlock(ctx, c1)
if err != nil {
return xerrors.Errorf("getting block 1: %w", err)
}
@@ -1143,7 +1141,7 @@ var SlashConsensusFault = &cli.Command{
return xerrors.Errorf("parsing cid 2: %w", err)
}
b2, err := api.ChainGetBlock(ctx, c2)
b2, err := a.ChainGetBlock(ctx, c2)
if err != nil {
return xerrors.Errorf("getting block 2: %w", err)
}
@@ -1154,7 +1152,7 @@ var SlashConsensusFault = &cli.Command{
var fromAddr address.Address
if from := cctx.String("from"); from == "" {
defaddr, err := api.WalletDefaultAddress(ctx)
defaddr, err := a.WalletDefaultAddress(ctx)
if err != nil {
return err
}
@@ -1190,7 +1188,7 @@ var SlashConsensusFault = &cli.Command{
return xerrors.Errorf("parsing cid extra: %w", err)
}
bExtra, err := api.ChainGetBlock(ctx, cExtra)
bExtra, err := a.ChainGetBlock(ctx, cExtra)
if err != nil {
return xerrors.Errorf("getting block extra: %w", err)
}
@@ -1208,15 +1206,17 @@ var SlashConsensusFault = &cli.Command{
return err
}
msg := &types.Message{
To: b2.Miner,
From: fromAddr,
Value: types.NewInt(0),
Method: builtin.MethodsMiner.ReportConsensusFault,
Params: enc,
proto := &api.MessagePrototype{
Message: types.Message{
To: b2.Miner,
From: fromAddr,
Value: types.NewInt(0),
Method: builtin.MethodsMiner.ReportConsensusFault,
Params: enc,
},
}
smsg, err := api.MpoolPushMessage(ctx, msg, nil)
smsg, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
+27 -10
View File
@@ -40,6 +40,7 @@ import (
"github.com/filecoin-project/lotus/api"
lapi "github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/api/v0api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors/builtin"
"github.com/filecoin-project/lotus/chain/actors/builtin/market"
@@ -321,6 +322,10 @@ The minimum value is 518400 (6 months).`,
Name: "manual-piece-size",
Usage: "if manually specifying piece cid, used to specify size (dataCid must be to a car file)",
},
&cli.BoolFlag{
Name: "manual-stateless-deal",
Usage: "instructs the node to send an offline deal without registering it with the deallist/fsm",
},
&cli.StringFlag{
Name: "from",
Usage: "specify address to fund the deal with",
@@ -460,7 +465,7 @@ The minimum value is 518400 (6 months).`,
isVerified = verifiedDealParam
}
proposal, err := api.ClientStartDeal(ctx, &lapi.StartDealParams{
sdParams := &lapi.StartDealParams{
Data: ref,
Wallet: a,
Miner: miner,
@@ -470,7 +475,18 @@ The minimum value is 518400 (6 months).`,
FastRetrieval: cctx.Bool("fast-retrieval"),
VerifiedDeal: isVerified,
ProviderCollateral: provCol,
})
}
var proposal *cid.Cid
if cctx.Bool("manual-stateless-deal") {
if ref.TransferType != storagemarket.TTManual || price.Int64() != 0 {
return xerrors.New("when manual-stateless-deal is enabled, you must also provide a 'price' of 0 and specify 'manual-piece-cid' and 'manual-piece-size'")
}
proposal, err = api.ClientStatelessDeal(ctx, sdParams)
} else {
proposal, err = api.ClientStartDeal(ctx, sdParams)
}
if err != nil {
return err
}
@@ -1295,7 +1311,8 @@ var clientListAsksCmd = &cli.Command{
Usage: "List asks for top miners",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "by-ping",
Name: "by-ping",
Usage: "sort by ping",
},
&cli.StringFlag{
Name: "output-format",
@@ -1348,7 +1365,7 @@ type QueriedAsk struct {
Ping time.Duration
}
func GetAsks(ctx context.Context, api lapi.FullNode) ([]QueriedAsk, error) {
func GetAsks(ctx context.Context, api v0api.FullNode) ([]QueriedAsk, error) {
isTTY := true
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 {
isTTY = false
@@ -1450,17 +1467,17 @@ loop:
}
rt := time.Now()
_, err = api.ClientQueryAsk(ctx, *mi.PeerId, miner)
if err != nil {
return
}
pingDuration := time.Now().Sub(rt)
atomic.AddInt64(&got, 1)
lk.Lock()
asks = append(asks, QueriedAsk{
Ask: ask,
Ping: time.Now().Sub(rt),
Ping: pingDuration,
})
lk.Unlock()
}(miner)
@@ -1655,7 +1672,7 @@ var clientListDeals = &cli.Command{
},
}
func dealFromDealInfo(ctx context.Context, full api.FullNode, head *types.TipSet, v api.DealInfo) deal {
func dealFromDealInfo(ctx context.Context, full v0api.FullNode, head *types.TipSet, v api.DealInfo) deal {
if v.DealID == 0 {
return deal{
LocalDeal: v,
@@ -1674,7 +1691,7 @@ func dealFromDealInfo(ctx context.Context, full api.FullNode, head *types.TipSet
}
}
func outputStorageDeals(ctx context.Context, out io.Writer, full lapi.FullNode, localDeals []lapi.DealInfo, verbose bool, color bool, showFailed bool) error {
func outputStorageDeals(ctx context.Context, out io.Writer, full v0api.FullNode, localDeals []lapi.DealInfo, verbose bool, color bool, showFailed bool) error {
sort.Slice(localDeals, func(i, j int) bool {
return localDeals[i].CreationTime.Before(localDeals[j].CreationTime)
})
@@ -2293,7 +2310,7 @@ func ellipsis(s string, length int) string {
return s
}
func inspectDealCmd(ctx context.Context, api lapi.FullNode, proposalCid string, dealId int) error {
func inspectDealCmd(ctx context.Context, api v0api.FullNode, proposalCid string, dealId int) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
@@ -2346,7 +2363,7 @@ func renderDeal(di *lapi.DealInfo) {
}
for _, stg := range di.DealStages.Stages {
msg := fmt.Sprintf("%s %s: %s (%s)", color.BlueString("Stage:"), color.BlueString(strings.TrimPrefix(stg.Name, "StorageDeal")), stg.Description, color.GreenString(stg.ExpectedDuration))
msg := fmt.Sprintf("%s %s: %s (expected duration: %s)", color.BlueString("Stage:"), color.BlueString(strings.TrimPrefix(stg.Name, "StorageDeal")), stg.Description, color.GreenString(stg.ExpectedDuration))
if stg.UpdatedTime.Time().IsZero() {
msg = color.YellowString(msg)
}
+4 -1
View File
@@ -34,7 +34,7 @@ func GetFullNodeServices(ctx *cli.Context) (ServicesAPI, error) {
return tn.(ServicesAPI), nil
}
api, c, err := GetFullNodeAPI(ctx)
api, c, err := GetFullNodeAPIV1(ctx)
if err != nil {
return nil, err
}
@@ -50,6 +50,7 @@ var DaemonContext = cliutil.DaemonContext
var ReqContext = cliutil.ReqContext
var GetFullNodeAPI = cliutil.GetFullNodeAPI
var GetFullNodeAPIV1 = cliutil.GetFullNodeAPIV1
var GetGatewayAPI = cliutil.GetGatewayAPI
var GetStorageMinerAPI = cliutil.GetStorageMinerAPI
@@ -70,6 +71,7 @@ var Commands = []*cli.Command{
WithCategory("basic", walletCmd),
WithCategory("basic", clientCmd),
WithCategory("basic", multisigCmd),
WithCategory("basic", verifRegCmd),
WithCategory("basic", paychCmd),
WithCategory("developer", AuthCmd),
WithCategory("developer", MpoolCmd),
@@ -80,6 +82,7 @@ var Commands = []*cli.Command{
WithCategory("developer", FetchParamCmd),
WithCategory("network", NetCmd),
WithCategory("network", SyncCmd),
WithCategory("status", StatusCmd),
PprofCmd,
VersionCmd,
}
+13 -4
View File
@@ -22,6 +22,7 @@ import (
logging "github.com/ipfs/go-log/v2"
"github.com/filecoin-project/lotus/api/v0api"
"github.com/filecoin-project/lotus/chain/store"
"github.com/urfave/cli/v2"
)
@@ -237,6 +238,9 @@ var disputerStartCmd = &cli.Command{
dpmsgs := make([]*types.Message, 0)
startTime := time.Now()
proofsChecked := uint64(0)
// TODO: Parallelizeable
for _, dl := range dls {
fullDeadlines, err := api.StateMinerDeadlines(ctx, dl.miner, tsk)
@@ -248,7 +252,10 @@ var disputerStartCmd = &cli.Command{
return xerrors.Errorf("deadline index %d not found in deadlines", dl.index)
}
ms, err := makeDisputeWindowedPosts(ctx, api, dl, fullDeadlines[dl.index].DisputableProofCount, fromAddr)
disputableProofs := fullDeadlines[dl.index].DisputableProofCount
proofsChecked += disputableProofs
ms, err := makeDisputeWindowedPosts(ctx, api, dl, disputableProofs, fromAddr)
if err != nil {
return xerrors.Errorf("failed to check for disputes: %w", err)
}
@@ -263,6 +270,8 @@ var disputerStartCmd = &cli.Command{
deadlineMap[dClose+Confidence] = append(deadlineMap[dClose+Confidence], *dl)
}
disputeLog.Infow("checked proofs", "count", proofsChecked, "duration", time.Since(startTime))
// TODO: Parallelizeable / can be integrated into the previous deadline-iterating for loop
for _, dpmsg := range dpmsgs {
disputeLog.Infow("disputing a PoSt", "miner", dpmsg.To)
@@ -356,7 +365,7 @@ var disputerStartCmd = &cli.Command{
// for a given miner, index, and maxPostIndex, tries to dispute posts from 0...postsSnapshotted-1
// returns a list of DisputeWindowedPoSt msgs that are expected to succeed if sent
func makeDisputeWindowedPosts(ctx context.Context, api lapi.FullNode, dl minerDeadline, postsSnapshotted uint64, sender address.Address) ([]*types.Message, error) {
func makeDisputeWindowedPosts(ctx context.Context, api v0api.FullNode, dl minerDeadline, postsSnapshotted uint64, sender address.Address) ([]*types.Message, error) {
disputes := make([]*types.Message, 0)
for i := uint64(0); i < postsSnapshotted; i++ {
@@ -388,7 +397,7 @@ func makeDisputeWindowedPosts(ctx context.Context, api lapi.FullNode, dl minerDe
return disputes, nil
}
func makeMinerDeadline(ctx context.Context, api lapi.FullNode, mAddr address.Address) (abi.ChainEpoch, *minerDeadline, error) {
func makeMinerDeadline(ctx context.Context, api v0api.FullNode, mAddr address.Address) (abi.ChainEpoch, *minerDeadline, error) {
dl, err := api.StateMinerProvingDeadline(ctx, mAddr, types.EmptyTSK)
if err != nil {
return -1, nil, xerrors.Errorf("getting proving index list: %w", err)
@@ -400,7 +409,7 @@ func makeMinerDeadline(ctx context.Context, api lapi.FullNode, mAddr address.Add
}, nil
}
func getSender(ctx context.Context, api lapi.FullNode, fromStr string) (address.Address, error) {
func getSender(ctx context.Context, api v0api.FullNode, fromStr string) (address.Address, error) {
if fromStr == "" {
return api.WalletDefaultAddress(ctx)
}
+9
View File
@@ -0,0 +1,9 @@
package cli
import (
logging "github.com/ipfs/go-log/v2"
)
func init() {
logging.SetLogLevel("watchdog", "ERROR")
}
+1
View File
@@ -34,6 +34,7 @@ var MpoolCmd = &cli.Command{
MpoolFindCmd,
MpoolConfig,
MpoolGasPerfCmd,
mpoolManage,
},
}
+360
View File
@@ -0,0 +1,360 @@
package cli
import (
"context"
"fmt"
"sort"
"github.com/Kubuxu/imtui"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors/builtin"
"github.com/filecoin-project/lotus/chain/messagepool"
types "github.com/filecoin-project/lotus/chain/types"
"github.com/gdamore/tcell/v2"
cid "github.com/ipfs/go-cid"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
)
var mpoolManage = &cli.Command{
Name: "manage",
Action: func(cctx *cli.Context) error {
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer srv.Close() //nolint:errcheck
ctx := ReqContext(cctx)
_, localAddr, err := srv.LocalAddresses(ctx)
if err != nil {
return xerrors.Errorf("getting local addresses: %w", err)
}
msgs, err := srv.MpoolPendingFilter(ctx, func(sm *types.SignedMessage) bool {
if sm.Message.From.Empty() {
return false
}
for _, a := range localAddr {
if a == sm.Message.From {
return true
}
}
return false
}, types.EmptyTSK)
if err != nil {
return err
}
t, err := imtui.NewTui()
if err != nil {
panic(err)
}
mm := &mmUI{
ctx: ctx,
srv: srv,
addrs: localAddr,
messages: msgs,
}
sort.Slice(mm.addrs, func(i, j int) bool {
return mm.addrs[i].String() < mm.addrs[j].String()
})
t.PushScene(mm.addrSelect())
err = t.Run()
if err != nil {
panic(err)
}
return nil
},
}
type mmUI struct {
ctx context.Context
srv ServicesAPI
addrs []address.Address
messages []*types.SignedMessage
}
func (mm *mmUI) addrSelect() func(*imtui.Tui) error {
rows := [][]string{{"Address", "No. Messages"}}
mCount := map[address.Address]int{}
for _, sm := range mm.messages {
mCount[sm.Message.From]++
}
for _, a := range mm.addrs {
rows = append(rows, []string{a.String(), fmt.Sprintf("%d", mCount[a])})
}
flex := []int{4, 1}
sel := 0
scroll := 0
return func(t *imtui.Tui) error {
if t.CurrentKey != nil && t.CurrentKey.Key() == tcell.KeyEnter {
if sel > 0 {
t.ReplaceScene(mm.messageLising(mm.addrs[sel-1]))
}
}
t.FlexTable(0, 0, 0, &sel, &scroll, rows, flex, true)
return nil
}
}
func errUI(err error) func(*imtui.Tui) error {
return func(t *imtui.Tui) error {
return err
}
}
type msgInfo struct {
sm *types.SignedMessage
checks []api.MessageCheckStatus
}
func (mi *msgInfo) Row() []string {
cidStr := mi.sm.Cid().String()
failedChecks := 0
for _, c := range mi.checks {
if !c.OK {
failedChecks++
}
}
shortAddr := mi.sm.Message.To.String()
if len(shortAddr) > 16 {
shortAddr = "…" + shortAddr[len(shortAddr)-16:]
}
var fCk string
if failedChecks == 0 {
fCk = "[:green:]OK"
} else {
fCk = "[:orange:]" + fmt.Sprintf("%d", failedChecks)
}
return []string{"…" + cidStr[len(cidStr)-32:], shortAddr,
fmt.Sprintf("%d", mi.sm.Message.Nonce), types.FIL(mi.sm.Message.Value).String(),
fmt.Sprintf("%d", mi.sm.Message.Method), fCk}
}
func (mm *mmUI) messageLising(a address.Address) func(*imtui.Tui) error {
genMsgInfos := func() ([]msgInfo, error) {
msgs, err := mm.srv.MpoolPendingFilter(mm.ctx, func(sm *types.SignedMessage) bool {
if sm.Message.From.Empty() {
return false
}
if a == sm.Message.From {
return true
}
return false
}, types.EmptyTSK)
if err != nil {
return nil, xerrors.Errorf("getting pending: %w", err)
}
msgIdx := map[cid.Cid]*types.SignedMessage{}
for _, sm := range msgs {
if sm.Message.From == a {
msgIdx[sm.Message.Cid()] = sm
msgIdx[sm.Cid()] = sm
}
}
checks, err := mm.srv.MpoolCheckPendingMessages(mm.ctx, a)
if err != nil {
return nil, xerrors.Errorf("checking pending: %w", err)
}
msgInfos := make([]msgInfo, 0, len(checks))
for _, msgChecks := range checks {
failingChecks := []api.MessageCheckStatus{}
for _, c := range msgChecks {
if !c.OK {
failingChecks = append(failingChecks, c)
}
}
msgInfos = append(msgInfos, msgInfo{
sm: msgIdx[msgChecks[0].Cid],
checks: failingChecks,
})
}
return msgInfos, nil
}
sel := 0
scroll := 0
var msgInfos []msgInfo
var rows [][]string
flex := []int{3, 2, 1, 1, 1, 1}
refresh := true
return func(t *imtui.Tui) error {
if refresh {
var err error
msgInfos, err = genMsgInfos()
if err != nil {
return xerrors.Errorf("getting msgInfos: %w", err)
}
rows = [][]string{{"Message Cid", "To", "Nonce", "Value", "Method", "Checks"}}
for _, mi := range msgInfos {
rows = append(rows, mi.Row())
}
refresh = false
}
if t.CurrentKey != nil && t.CurrentKey.Key() == tcell.KeyEnter {
if sel > 0 {
t.PushScene(mm.messageDetail(msgInfos[sel-1]))
refresh = true
return nil
}
}
t.Label(0, 0, fmt.Sprintf("Address: %s", a), tcell.StyleDefault)
t.FlexTable(1, 0, 0, &sel, &scroll, rows, flex, true)
return nil
}
}
func (mm *mmUI) messageDetail(mi msgInfo) func(*imtui.Tui) error {
baseFee, err := mm.srv.GetBaseFee(mm.ctx)
if err != nil {
return errUI(err)
}
_ = baseFee
m := mi.sm.Message
maxFee := big.Mul(m.GasFeeCap, big.NewInt(m.GasLimit))
issues := [][]string{}
for _, c := range mi.checks {
issues = append(issues, []string{c.Code.String(), c.Err})
}
issuesFlex := []int{1, 3}
var sel, scroll int
executeReprice := false
executeNoop := false
return func(t *imtui.Tui) error {
if executeReprice {
m.GasFeeCap = big.Div(maxFee, big.NewInt(m.GasLimit))
m.GasPremium = messagepool.ComputeMinRBF(m.GasPremium)
m.GasFeeCap = big.Max(m.GasFeeCap, m.GasPremium)
_, _, err := mm.srv.PublishMessage(mm.ctx, &api.MessagePrototype{
Message: m,
ValidNonce: true,
}, true)
if err != nil {
return err
}
t.PopScene()
return nil
}
if executeNoop {
nop := types.Message{
To: builtin.BurntFundsActorAddr,
From: m.From,
Nonce: m.Nonce,
Value: big.Zero(),
}
nop.GasPremium = messagepool.ComputeMinRBF(m.GasPremium)
_, _, err := mm.srv.PublishMessage(mm.ctx, &api.MessagePrototype{
Message: nop,
ValidNonce: true,
}, true)
if err != nil {
return xerrors.Errorf("publishing noop message: %w", err)
}
t.PopScene()
return nil
}
if t.CurrentKey != nil {
if t.CurrentKey.Key() == tcell.KeyLeft {
t.PopScene()
return nil
}
if t.CurrentKey.Key() == tcell.KeyRune {
switch t.CurrentKey.Rune() {
case 'R', 'r':
t.PushScene(feeUI(baseFee, m.GasLimit, &maxFee, &executeReprice))
return nil
case 'N', 'n':
t.PushScene(confirmationScene(
&executeNoop,
"Are you sure you want to cancel the message by",
"replacing it with a message with no effects?"))
return nil
}
}
}
row := 0
defS := tcell.StyleDefault
display := func(f string, args ...interface{}) {
t.Label(0, row, fmt.Sprintf(f, args...), defS)
row++
}
display("Message CID: %s", m.Cid())
display("Signed Message CID: %s", mi.sm.Cid())
row++
display("From: %s", m.From)
display("To: %s", m.To)
row++
display("Nonce: %d", m.Nonce)
display("Value: %s", types.FIL(m.Value))
row++
display("GasLimit: %d", m.GasLimit)
display("GasPremium: %s", types.FIL(m.GasPremium).Short())
display("GasFeeCap %s", types.FIL(m.GasFeeCap).Short())
row++
display("Press R to reprice this message")
display("Press N to replace this message with no-operation message")
row++
t.FlexTable(row, 0, 0, &sel, &scroll, issues, issuesFlex, false)
return nil
}
}
func confirmationScene(yes *bool, ask ...string) func(*imtui.Tui) error {
return func(t *imtui.Tui) error {
row := 0
defS := tcell.StyleDefault
display := func(f string, args ...interface{}) {
t.Label(0, row, fmt.Sprintf(f, args...), defS)
row++
}
for _, a := range ask {
display(a)
}
row++
display("Enter to confirm")
display("Esc to cancel")
if t.CurrentKey != nil {
if t.CurrentKey.Key() == tcell.KeyEnter {
*yes = true
t.PopScene()
return nil
}
}
return nil
}
}
+190 -57
View File
@@ -95,11 +95,13 @@ var msigCreateCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("multisigs must have at least one signer"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
var addrs []address.Address
@@ -146,13 +148,20 @@ var msigCreateCmd = &cli.Command{
gp := types.NewInt(1)
msgCid, err := api.MsigCreate(ctx, required, addrs, d, intVal, sendAddr, gp)
proto, err := api.MsigCreate(ctx, required, addrs, d, intVal, sendAddr, gp)
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
// wait for it to get mined into a block
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -364,11 +373,13 @@ var msigProposeCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("must either pass three or five arguments"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -426,14 +437,21 @@ var msigProposeCmd = &cli.Command{
return fmt.Errorf("actor %s is not a multisig actor", msig)
}
msgCid, err := api.MsigPropose(ctx, msig, dest, types.BigInt(value), from, method, params)
proto, err := api.MsigPropose(ctx, msig, dest, types.BigInt(value), from, method, params)
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
fmt.Println("send proposal in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -481,11 +499,13 @@ var msigApproveCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("usage: msig approve <msig addr> <message ID> <proposer address> <desination> <value> [ <method> <params> ]"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -515,10 +535,17 @@ var msigApproveCmd = &cli.Command{
var msgCid cid.Cid
if cctx.Args().Len() == 2 {
msgCid, err = api.MsigApprove(ctx, msig, txid, from)
proto, err := api.MsigApprove(ctx, msig, txid, from)
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid = sm.Cid()
} else {
proposer, err := address.NewFromString(cctx.Args().Get(2))
if err != nil {
@@ -558,15 +585,22 @@ var msigApproveCmd = &cli.Command{
params = p
}
msgCid, err = api.MsigApproveTxnHash(ctx, msig, txid, proposer, dest, types.BigInt(value), from, method, params)
proto, err := api.MsigApproveTxnHash(ctx, msig, txid, proposer, dest, types.BigInt(value), from, method, params)
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid = sm.Cid()
}
fmt.Println("sent approval in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -598,11 +632,13 @@ var msigRemoveProposeCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("must pass multisig address and signer address"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -630,14 +666,21 @@ var msigRemoveProposeCmd = &cli.Command{
from = defaddr
}
msgCid, err := api.MsigRemoveSigner(ctx, msig, from, addr, cctx.Bool("decrease-threshold"))
proto, err := api.MsigRemoveSigner(ctx, msig, from, addr, cctx.Bool("decrease-threshold"))
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
fmt.Println("sent remove proposal in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -676,11 +719,13 @@ var msigAddProposeCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("must pass multisig address and signer address"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -708,14 +753,21 @@ var msigAddProposeCmd = &cli.Command{
from = defaddr
}
msgCid, err := api.MsigAddPropose(ctx, msig, from, addr, cctx.Bool("increase-threshold"))
proto, err := api.MsigAddPropose(ctx, msig, from, addr, cctx.Bool("increase-threshold"))
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
fmt.Fprintln(cctx.App.Writer, "sent add proposal in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -743,11 +795,13 @@ var msigAddApproveCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, transaction id, new signer address, whether to increase threshold"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -790,14 +844,21 @@ var msigAddApproveCmd = &cli.Command{
from = defaddr
}
msgCid, err := api.MsigAddApprove(ctx, msig, from, txid, prop, newAdd, inc)
proto, err := api.MsigAddApprove(ctx, msig, from, txid, prop, newAdd, inc)
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
fmt.Println("sent add approval in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -825,11 +886,13 @@ var msigAddCancelCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("must pass multisig address, transaction id, new signer address, whether to increase threshold"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -867,14 +930,21 @@ var msigAddCancelCmd = &cli.Command{
from = defaddr
}
msgCid, err := api.MsigAddCancel(ctx, msig, from, txid, newAdd, inc)
proto, err := api.MsigAddCancel(ctx, msig, from, txid, newAdd, inc)
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
fmt.Println("sent add cancellation in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -902,11 +972,13 @@ var msigSwapProposeCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("must pass multisig address, old signer address, new signer address"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -939,14 +1011,21 @@ var msigSwapProposeCmd = &cli.Command{
from = defaddr
}
msgCid, err := api.MsigSwapPropose(ctx, msig, from, oldAdd, newAdd)
proto, err := api.MsigSwapPropose(ctx, msig, from, oldAdd, newAdd)
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
fmt.Println("sent swap proposal in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -974,11 +1053,13 @@ var msigSwapApproveCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, transaction id, old signer address, new signer address"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -1021,14 +1102,21 @@ var msigSwapApproveCmd = &cli.Command{
from = defaddr
}
msgCid, err := api.MsigSwapApprove(ctx, msig, from, txid, prop, oldAdd, newAdd)
proto, err := api.MsigSwapApprove(ctx, msig, from, txid, prop, oldAdd, newAdd)
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
fmt.Println("sent swap approval in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -1056,11 +1144,13 @@ var msigSwapCancelCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("must pass multisig address, transaction id, old signer address, new signer address"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -1098,14 +1188,21 @@ var msigSwapCancelCmd = &cli.Command{
from = defaddr
}
msgCid, err := api.MsigSwapCancel(ctx, msig, from, txid, oldAdd, newAdd)
proto, err := api.MsigSwapCancel(ctx, msig, from, txid, oldAdd, newAdd)
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
fmt.Println("sent swap cancellation in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -1133,11 +1230,13 @@ var msigLockProposeCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("must pass multisig address, start epoch, unlock duration, and amount"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -1185,14 +1284,21 @@ var msigLockProposeCmd = &cli.Command{
return actErr
}
msgCid, err := api.MsigPropose(ctx, msig, msig, big.Zero(), from, uint64(multisig.Methods.LockBalance), params)
proto, err := api.MsigPropose(ctx, msig, msig, big.Zero(), from, uint64(multisig.Methods.LockBalance), params)
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
fmt.Println("sent lock proposal in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -1220,11 +1326,13 @@ var msigLockApproveCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, tx id, start epoch, unlock duration, and amount"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -1282,14 +1390,21 @@ var msigLockApproveCmd = &cli.Command{
return actErr
}
msgCid, err := api.MsigApproveTxnHash(ctx, msig, txid, prop, msig, big.Zero(), from, uint64(multisig.Methods.LockBalance), params)
proto, err := api.MsigApproveTxnHash(ctx, msig, txid, prop, msig, big.Zero(), from, uint64(multisig.Methods.LockBalance), params)
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
fmt.Println("sent lock approval in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -1317,11 +1432,13 @@ var msigLockCancelCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("must pass multisig address, tx id, start epoch, unlock duration, and amount"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -1374,14 +1491,21 @@ var msigLockCancelCmd = &cli.Command{
return actErr
}
msgCid, err := api.MsigCancel(ctx, msig, txid, msig, big.Zero(), from, uint64(multisig.Methods.LockBalance), params)
proto, err := api.MsigCancel(ctx, msig, txid, msig, big.Zero(), from, uint64(multisig.Methods.LockBalance), params)
if err != nil {
return err
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
fmt.Println("sent lock cancellation in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
@@ -1471,11 +1595,13 @@ var msigProposeThresholdCmd = &cli.Command{
return ShowHelp(cctx, fmt.Errorf("must pass multisig address and new threshold value"))
}
api, closer, err := GetFullNodeAPI(cctx)
srv, err := GetFullNodeServices(cctx)
if err != nil {
return err
}
defer closer()
defer srv.Close() //nolint:errcheck
api := srv.FullNodeAPI()
ctx := ReqContext(cctx)
msig, err := address.NewFromString(cctx.Args().Get(0))
@@ -1511,14 +1637,21 @@ var msigProposeThresholdCmd = &cli.Command{
return actErr
}
msgCid, err := api.MsigPropose(ctx, msig, msig, types.NewInt(0), from, uint64(multisig.Methods.ChangeNumApprovalsThreshold), params)
proto, err := api.MsigPropose(ctx, msig, msig, types.NewInt(0), from, uint64(multisig.Methods.ChangeNumApprovalsThreshold), params)
if err != nil {
return fmt.Errorf("failed to propose change of threshold: %w", err)
}
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
msgCid := sm.Cid()
fmt.Println("sent change threshold proposal in message: ", msgCid)
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")))
wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true)
if err != nil {
return err
}
+13 -11
View File
@@ -2,7 +2,6 @@ package cli
import (
"encoding/hex"
"errors"
"fmt"
"github.com/urfave/cli/v2"
@@ -59,10 +58,14 @@ var sendCmd = &cli.Command{
},
&cli.BoolFlag{
Name: "force",
Usage: "must be specified for the action to take effect if maybe SysErrInsufficientFunds etc",
Usage: "Deprecated: use global 'force-send'",
},
},
Action: func(cctx *cli.Context) error {
if cctx.IsSet("force") {
fmt.Println("'force' flag is deprecated, use global flag 'force-send'")
}
if cctx.Args().Len() != 2 {
return ShowHelp(cctx, fmt.Errorf("'send' expects two arguments, target and amount"))
}
@@ -137,23 +140,22 @@ var sendCmd = &cli.Command{
params.Params = decparams
}
params.Force = cctx.Bool("force")
if cctx.IsSet("nonce") {
n := cctx.Uint64("nonce")
params.Nonce = &n
}
msgCid, err := srv.Send(ctx, params)
proto, err := srv.MessageForSend(ctx, params)
if err != nil {
if errors.Is(err, ErrSendBalanceTooLow) {
return fmt.Errorf("--force must be specified for this action to have an effect; you have been warned: %w", err)
}
return xerrors.Errorf("executing send: %w", err)
return xerrors.Errorf("creating message prototype: %w", err)
}
fmt.Fprintf(cctx.App.Writer, "%s\n", msgCid)
sm, err := InteractiveSend(ctx, cctx, srv, proto)
if err != nil {
return err
}
fmt.Fprintf(cctx.App.Writer, "%s\n", sm.Cid())
return nil
},
}
+18 -79
View File
@@ -2,24 +2,17 @@ package cli
import (
"bytes"
"errors"
"testing"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/api"
types "github.com/filecoin-project/lotus/chain/types"
gomock "github.com/golang/mock/gomock"
cid "github.com/ipfs/go-cid"
"github.com/stretchr/testify/assert"
ucli "github.com/urfave/cli/v2"
)
var arbtCid = (&types.Message{
From: mustAddr(address.NewIDAddress(2)),
To: mustAddr(address.NewIDAddress(1)),
Value: types.NewInt(1000),
}).Cid()
func mustAddr(a address.Address, err error) address.Address {
if err != nil {
panic(err)
@@ -49,80 +42,26 @@ func TestSendCLI(t *testing.T) {
app, mockSrvcs, buf, done := newMockApp(t, sendCmd)
defer done()
gomock.InOrder(
mockSrvcs.EXPECT().Send(gomock.Any(), SendParams{
To: mustAddr(address.NewIDAddress(1)),
Val: oneFil,
}).Return(arbtCid, nil),
mockSrvcs.EXPECT().Close(),
)
err := app.Run([]string{"lotus", "send", "t01", "1"})
assert.NoError(t, err)
assert.EqualValues(t, arbtCid.String()+"\n", buf.String())
})
t.Run("ErrSendBalanceTooLow", func(t *testing.T) {
app, mockSrvcs, _, done := newMockApp(t, sendCmd)
defer done()
gomock.InOrder(
mockSrvcs.EXPECT().Send(gomock.Any(), SendParams{
To: mustAddr(address.NewIDAddress(1)),
Val: oneFil,
}).Return(cid.Undef, ErrSendBalanceTooLow),
mockSrvcs.EXPECT().Close(),
)
err := app.Run([]string{"lotus", "send", "t01", "1"})
assert.ErrorIs(t, err, ErrSendBalanceTooLow)
})
t.Run("generic-err-is-forwarded", func(t *testing.T) {
app, mockSrvcs, _, done := newMockApp(t, sendCmd)
defer done()
errMark := errors.New("something")
gomock.InOrder(
mockSrvcs.EXPECT().Send(gomock.Any(), SendParams{
To: mustAddr(address.NewIDAddress(1)),
Val: oneFil,
}).Return(cid.Undef, errMark),
mockSrvcs.EXPECT().Close(),
)
err := app.Run([]string{"lotus", "send", "t01", "1"})
assert.ErrorIs(t, err, errMark)
})
t.Run("from-specific", func(t *testing.T) {
app, mockSrvcs, buf, done := newMockApp(t, sendCmd)
defer done()
gomock.InOrder(
mockSrvcs.EXPECT().Send(gomock.Any(), SendParams{
To: mustAddr(address.NewIDAddress(1)),
From: mustAddr(address.NewIDAddress(2)),
Val: oneFil,
}).Return(arbtCid, nil),
mockSrvcs.EXPECT().Close(),
)
err := app.Run([]string{"lotus", "send", "--from=t02", "t01", "1"})
assert.NoError(t, err)
assert.EqualValues(t, arbtCid.String()+"\n", buf.String())
})
t.Run("nonce-specific", func(t *testing.T) {
app, mockSrvcs, buf, done := newMockApp(t, sendCmd)
defer done()
zero := uint64(0)
gomock.InOrder(
mockSrvcs.EXPECT().Send(gomock.Any(), SendParams{
arbtProto := &api.MessagePrototype{
Message: types.Message{
From: mustAddr(address.NewIDAddress(1)),
To: mustAddr(address.NewIDAddress(1)),
Nonce: &zero,
Val: oneFil,
}).Return(arbtCid, nil),
Value: oneFil,
},
}
sigMsg := fakeSign(&arbtProto.Message)
gomock.InOrder(
mockSrvcs.EXPECT().MessageForSend(gomock.Any(), SendParams{
To: mustAddr(address.NewIDAddress(1)),
Val: oneFil,
}).Return(arbtProto, nil),
mockSrvcs.EXPECT().PublishMessage(gomock.Any(), arbtProto, false).
Return(sigMsg, nil, nil),
mockSrvcs.EXPECT().Close(),
)
err := app.Run([]string{"lotus", "send", "--nonce=0", "t01", "1"})
err := app.Run([]string{"lotus", "send", "t01", "1"})
assert.NoError(t, err)
assert.EqualValues(t, arbtCid.String()+"\n", buf.String())
assert.EqualValues(t, sigMsg.Cid().String()+"\n", buf.String())
})
}
+264
View File
@@ -0,0 +1,264 @@
package cli
import (
"context"
"errors"
"fmt"
"io"
"strings"
"github.com/Kubuxu/imtui"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
types "github.com/filecoin-project/lotus/chain/types"
"github.com/gdamore/tcell/v2"
cid "github.com/ipfs/go-cid"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
)
func InteractiveSend(ctx context.Context, cctx *cli.Context, srv ServicesAPI,
proto *api.MessagePrototype) (*types.SignedMessage, error) {
msg, checks, err := srv.PublishMessage(ctx, proto, cctx.Bool("force") || cctx.Bool("force-send"))
printer := cctx.App.Writer
if xerrors.Is(err, ErrCheckFailed) {
if !cctx.Bool("interactive") {
fmt.Fprintf(printer, "Following checks have failed:\n")
printChecks(printer, checks, proto.Message.Cid())
} else {
proto, err = resolveChecks(ctx, srv, cctx.App.Writer, proto, checks)
if err != nil {
return nil, xerrors.Errorf("from UI: %w", err)
}
msg, _, err = srv.PublishMessage(ctx, proto, true)
}
}
if err != nil {
return nil, xerrors.Errorf("publishing message: %w", err)
}
return msg, nil
}
var interactiveSolves = map[api.CheckStatusCode]bool{
api.CheckStatusMessageMinBaseFee: true,
api.CheckStatusMessageBaseFee: true,
api.CheckStatusMessageBaseFeeLowerBound: true,
api.CheckStatusMessageBaseFeeUpperBound: true,
}
func baseFeeFromHints(hint map[string]interface{}) big.Int {
bHint, ok := hint["baseFee"]
if !ok {
return big.Zero()
}
bHintS, ok := bHint.(string)
if !ok {
return big.Zero()
}
var err error
baseFee, err := big.FromString(bHintS)
if err != nil {
return big.Zero()
}
return baseFee
}
func resolveChecks(ctx context.Context, s ServicesAPI, printer io.Writer,
proto *api.MessagePrototype, checkGroups [][]api.MessageCheckStatus,
) (*api.MessagePrototype, error) {
fmt.Fprintf(printer, "Following checks have failed:\n")
printChecks(printer, checkGroups, proto.Message.Cid())
if feeCapBad, baseFee := isFeeCapProblem(checkGroups, proto.Message.Cid()); feeCapBad {
fmt.Fprintf(printer, "Fee of the message can be adjusted\n")
if askUser(printer, "Do you wish to do that? [Yes/no]: ", true) {
var err error
proto, err = runFeeCapAdjustmentUI(proto, baseFee)
if err != nil {
return nil, err
}
}
checks, err := s.RunChecksForPrototype(ctx, proto)
if err != nil {
return nil, err
}
fmt.Fprintf(printer, "Following checks still failed:\n")
printChecks(printer, checks, proto.Message.Cid())
}
if !askUser(printer, "Do you wish to send this message? [yes/No]: ", false) {
return nil, ErrAbortedByUser
}
return proto, nil
}
var ErrAbortedByUser = errors.New("aborted by user")
func printChecks(printer io.Writer, checkGroups [][]api.MessageCheckStatus, protoCid cid.Cid) {
for _, checks := range checkGroups {
for _, c := range checks {
if c.OK {
continue
}
aboutProto := c.Cid.Equals(protoCid)
msgName := "current"
if !aboutProto {
msgName = c.Cid.String()
}
fmt.Fprintf(printer, "%s message failed a check %s: %s\n", msgName, c.Code, c.Err)
}
}
}
func askUser(printer io.Writer, q string, def bool) bool {
var resp string
fmt.Fprint(printer, q)
fmt.Scanln(&resp)
resp = strings.ToLower(resp)
if len(resp) == 0 {
return def
}
return resp[0] == 'y'
}
func isFeeCapProblem(checkGroups [][]api.MessageCheckStatus, protoCid cid.Cid) (bool, big.Int) {
baseFee := big.Zero()
yes := false
for _, checks := range checkGroups {
for _, c := range checks {
if c.OK {
continue
}
aboutProto := c.Cid.Equals(protoCid)
if aboutProto && interactiveSolves[c.Code] {
yes = true
if baseFee.IsZero() {
baseFee = baseFeeFromHints(c.Hint)
}
}
}
}
if baseFee.IsZero() {
// this will only be the case if failing check is: MessageMinBaseFee
baseFee = big.NewInt(build.MinimumBaseFee)
}
return yes, baseFee
}
func runFeeCapAdjustmentUI(proto *api.MessagePrototype, baseFee abi.TokenAmount) (*api.MessagePrototype, error) {
t, err := imtui.NewTui()
if err != nil {
return nil, err
}
maxFee := big.Mul(proto.Message.GasFeeCap, big.NewInt(proto.Message.GasLimit))
send := false
t.PushScene(feeUI(baseFee, proto.Message.GasLimit, &maxFee, &send))
err = t.Run()
if err != nil {
return nil, err
}
if !send {
return nil, fmt.Errorf("aborted by user")
}
proto.Message.GasFeeCap = big.Div(maxFee, big.NewInt(proto.Message.GasLimit))
return proto, nil
}
func feeUI(baseFee abi.TokenAmount, gasLimit int64, maxFee *abi.TokenAmount, send *bool) func(*imtui.Tui) error {
orignalMaxFee := *maxFee
required := big.Mul(baseFee, big.NewInt(gasLimit))
safe := big.Mul(required, big.NewInt(10))
price := fmt.Sprintf("%s", types.FIL(*maxFee).Unitless())
return func(t *imtui.Tui) error {
if t.CurrentKey != nil {
if t.CurrentKey.Key() == tcell.KeyRune {
pF, err := types.ParseFIL(price)
switch t.CurrentKey.Rune() {
case 's', 'S':
price = types.FIL(safe).Unitless()
case '+':
if err == nil {
p := big.Mul(big.Int(pF), types.NewInt(11))
p = big.Div(p, types.NewInt(10))
price = fmt.Sprintf("%s", types.FIL(p).Unitless())
}
case '-':
if err == nil {
p := big.Mul(big.Int(pF), types.NewInt(10))
p = big.Div(p, types.NewInt(11))
price = fmt.Sprintf("%s", types.FIL(p).Unitless())
}
default:
}
}
if t.CurrentKey.Key() == tcell.KeyEnter {
*send = true
t.PopScene()
return nil
}
}
defS := tcell.StyleDefault
row := 0
t.Label(0, row, "Fee of the message is too low.", defS)
row++
t.Label(0, row, fmt.Sprintf("Your configured maximum fee is: %s FIL",
types.FIL(orignalMaxFee).Unitless()), defS)
row++
t.Label(0, row, fmt.Sprintf("Required maximum fee for the message: %s FIL",
types.FIL(required).Unitless()), defS)
row++
w := t.Label(0, row, fmt.Sprintf("Safe maximum fee for the message: %s FIL",
types.FIL(safe).Unitless()), defS)
t.Label(w, row, " Press S to use it", defS)
row++
w = t.Label(0, row, "Current Maximum Fee: ", defS)
w += t.EditFieldFiltered(w, row, 14, &price, imtui.FilterDecimal, defS.Foreground(tcell.ColorWhite).Background(tcell.ColorBlack))
w += t.Label(w, row, " FIL", defS)
pF, err := types.ParseFIL(price)
*maxFee = abi.TokenAmount(pF)
if err != nil {
w += t.Label(w, row, " invalid price", defS.Foreground(tcell.ColorMaroon).Bold(true))
} else if maxFee.GreaterThanEqual(safe) {
w += t.Label(w, row, " SAFE", defS.Foreground(tcell.ColorDarkGreen).Bold(true))
} else if maxFee.GreaterThanEqual(required) {
w += t.Label(w, row, " low", defS.Foreground(tcell.ColorYellow).Bold(true))
over := big.Div(big.Mul(*maxFee, big.NewInt(100)), required)
w += t.Label(w, row,
fmt.Sprintf(" %.1fx over the minimum", float64(over.Int64())/100.0), defS)
} else {
w += t.Label(w, row, " too low", defS.Foreground(tcell.ColorRed).Bold(true))
}
row += 2
t.Label(0, row, fmt.Sprintf("Current Base Fee is: %s", types.FIL(baseFee).Nano()), defS)
row++
t.Label(0, row, fmt.Sprintf("Resulting FeeCap is: %s",
types.FIL(big.Div(*maxFee, big.NewInt(gasLimit))).Nano()), defS)
row++
t.Label(0, row, "You can use '+' and '-' to adjust the fee.", defS)
return nil
}
}
+154 -44
View File
@@ -4,13 +4,13 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"reflect"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/stmgr"
types "github.com/filecoin-project/lotus/chain/types"
@@ -22,12 +22,30 @@ import (
//go:generate go run github.com/golang/mock/mockgen -destination=servicesmock_test.go -package=cli -self_package github.com/filecoin-project/lotus/cli . ServicesAPI
type ServicesAPI interface {
// Sends executes a send given SendParams
Send(ctx context.Context, params SendParams) (cid.Cid, error)
FullNodeAPI() api.FullNode
GetBaseFee(ctx context.Context) (abi.TokenAmount, error)
// MessageForSend creates a prototype of a message based on SendParams
MessageForSend(ctx context.Context, params SendParams) (*api.MessagePrototype, error)
// DecodeTypedParamsFromJSON takes in information needed to identify a method and converts JSON
// parameters to bytes of their CBOR encoding
DecodeTypedParamsFromJSON(ctx context.Context, to address.Address, method abi.MethodNum, paramstr string) ([]byte, error)
RunChecksForPrototype(ctx context.Context, prototype *api.MessagePrototype) ([][]api.MessageCheckStatus, error)
// PublishMessage takes in a message prototype and publishes it
// before publishing the message, it runs checks on the node, message and mpool to verify that
// message is valid and won't be stuck.
// if `force` is true, it skips the checks
PublishMessage(ctx context.Context, prototype *api.MessagePrototype, force bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error)
LocalAddresses(ctx context.Context) (address.Address, []address.Address, error)
MpoolPendingFilter(ctx context.Context, filter func(*types.SignedMessage) bool, tsk types.TipSetKey) ([]*types.SignedMessage, error)
MpoolCheckPendingMessages(ctx context.Context, a address.Address) ([][]api.MessageCheckStatus, error)
// Close ends the session of services and disconnects from RPC, using Services after Close is called
// most likely will result in an error
// Should not be called concurrently
@@ -39,6 +57,10 @@ type ServicesImpl struct {
closer jsonrpc.ClientCloser
}
func (s *ServicesImpl) FullNodeAPI() api.FullNode {
return s.api
}
func (s *ServicesImpl) Close() error {
if s.closer == nil {
return xerrors.Errorf("Services already closed")
@@ -48,6 +70,16 @@ func (s *ServicesImpl) Close() error {
return nil
}
func (s *ServicesImpl) GetBaseFee(ctx context.Context) (abi.TokenAmount, error) {
// not used but useful
ts, err := s.api.ChainHead(ctx)
if err != nil {
return big.Zero(), xerrors.Errorf("getting head: %w", err)
}
return ts.MinTicketBlock().ParentBaseFee, nil
}
func (s *ServicesImpl) DecodeTypedParamsFromJSON(ctx context.Context, to address.Address, method abi.MethodNum, paramstr string) ([]byte, error) {
act, err := s.api.StateGetActor(ctx, to, types.EmptyTSK)
if err != nil {
@@ -72,6 +104,79 @@ func (s *ServicesImpl) DecodeTypedParamsFromJSON(ctx context.Context, to address
return buf.Bytes(), nil
}
type CheckInfo struct {
MessageTie cid.Cid
CurrentMessageTie bool
Check api.MessageCheckStatus
}
var ErrCheckFailed = fmt.Errorf("check has failed")
func (s *ServicesImpl) RunChecksForPrototype(ctx context.Context, prototype *api.MessagePrototype) ([][]api.MessageCheckStatus, error) {
var outChecks [][]api.MessageCheckStatus
checks, err := s.api.MpoolCheckMessages(ctx, []*api.MessagePrototype{prototype})
if err != nil {
return nil, xerrors.Errorf("message check: %w", err)
}
outChecks = append(outChecks, checks...)
checks, err = s.api.MpoolCheckPendingMessages(ctx, prototype.Message.From)
if err != nil {
return nil, xerrors.Errorf("pending mpool check: %w", err)
}
outChecks = append(outChecks, checks...)
return outChecks, nil
}
// PublishMessage modifies prototype to include gas estimation
// Errors with ErrCheckFailed if any of the checks fail
// First group of checks is related to the message prototype
func (s *ServicesImpl) PublishMessage(ctx context.Context,
prototype *api.MessagePrototype, force bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) {
gasedMsg, err := s.api.GasEstimateMessageGas(ctx, &prototype.Message, nil, types.EmptyTSK)
if err != nil {
return nil, nil, xerrors.Errorf("estimating gas: %w", err)
}
prototype.Message = *gasedMsg
if !force {
checks, err := s.RunChecksForPrototype(ctx, prototype)
if err != nil {
return nil, nil, xerrors.Errorf("running checks: %w", err)
}
for _, chks := range checks {
for _, c := range chks {
if !c.OK {
return nil, checks, ErrCheckFailed
}
}
}
}
if prototype.ValidNonce {
sm, err := s.api.WalletSignMessage(ctx, prototype.Message.From, &prototype.Message)
if err != nil {
return nil, nil, err
}
_, err = s.api.MpoolPush(ctx, sm)
if err != nil {
return nil, nil, err
}
return sm, nil, nil
}
sm, err := s.api.MpoolPushMessage(ctx, &prototype.Message, nil)
if err != nil {
return nil, nil, err
}
return sm, nil, nil
}
type SendParams struct {
To address.Address
From address.Address
@@ -84,26 +189,18 @@ type SendParams struct {
Nonce *uint64
Method abi.MethodNum
Params []byte
Force bool
}
// This is specialised Send for Send command
// There might be room for generic Send that other commands can use to send their messages
// We will see
var ErrSendBalanceTooLow = errors.New("balance too low")
func (s *ServicesImpl) Send(ctx context.Context, params SendParams) (cid.Cid, error) {
func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (*api.MessagePrototype, error) {
if params.From == address.Undef {
defaddr, err := s.api.WalletDefaultAddress(ctx)
if err != nil {
return cid.Undef, err
return nil, err
}
params.From = defaddr
}
msg := &types.Message{
msg := types.Message{
From: params.From,
To: params.To,
Value: params.Val,
@@ -127,40 +224,53 @@ func (s *ServicesImpl) Send(ctx context.Context, params SendParams) (cid.Cid, er
} else {
msg.GasLimit = 0
}
if !params.Force {
// Funds insufficient check
fromBalance, err := s.api.WalletBalance(ctx, msg.From)
if err != nil {
return cid.Undef, err
}
totalCost := types.BigAdd(types.BigMul(msg.GasFeeCap, types.NewInt(uint64(msg.GasLimit))), msg.Value)
if fromBalance.LessThan(totalCost) {
return cid.Undef, xerrors.Errorf("From balance %s less than total cost %s: %w", types.FIL(fromBalance), types.FIL(totalCost), ErrSendBalanceTooLow)
}
}
validNonce := false
if params.Nonce != nil {
msg.Nonce = *params.Nonce
sm, err := s.api.WalletSignMessage(ctx, params.From, msg)
if err != nil {
return cid.Undef, err
}
_, err = s.api.MpoolPush(ctx, sm)
if err != nil {
return cid.Undef, err
}
return sm.Cid(), nil
validNonce = true
}
sm, err := s.api.MpoolPushMessage(ctx, msg, nil)
if err != nil {
return cid.Undef, err
prototype := &api.MessagePrototype{
Message: msg,
ValidNonce: validNonce,
}
return prototype, nil
}
return sm.Cid(), nil
func (s *ServicesImpl) MpoolPendingFilter(ctx context.Context, filter func(*types.SignedMessage) bool,
tsk types.TipSetKey) ([]*types.SignedMessage, error) {
msgs, err := s.api.MpoolPending(ctx, types.EmptyTSK)
if err != nil {
return nil, xerrors.Errorf("getting pending messages: %w", err)
}
out := []*types.SignedMessage{}
for _, sm := range msgs {
if filter(sm) {
out = append(out, sm)
}
}
return out, nil
}
func (s *ServicesImpl) LocalAddresses(ctx context.Context) (address.Address, []address.Address, error) {
def, err := s.api.WalletDefaultAddress(ctx)
if err != nil {
return address.Undef, nil, xerrors.Errorf("getting default addr: %w", err)
}
all, err := s.api.WalletList(ctx)
if err != nil {
return address.Undef, nil, xerrors.Errorf("getting list of addrs: %w", err)
}
return def, all, nil
}
func (s *ServicesImpl) MpoolCheckPendingMessages(ctx context.Context, a address.Address) ([][]api.MessageCheckStatus, error) {
checks, err := s.api.MpoolCheckPendingMessages(ctx, a)
if err != nil {
return nil, xerrors.Errorf("pending mpool check: %w", err)
}
return checks, nil
}
+30 -81
View File
@@ -9,10 +9,9 @@ import (
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/api/mocks"
mocks "github.com/filecoin-project/lotus/api/mocks"
types "github.com/filecoin-project/lotus/chain/types"
gomock "github.com/golang/mock/gomock"
cid "github.com/ipfs/go-cid"
"github.com/stretchr/testify/assert"
)
@@ -61,6 +60,7 @@ func setupMockSrvcs(t *testing.T) (*ServicesImpl, *mocks.MockFullNode) {
return srvcs, mockApi
}
// linter doesn't like dead code, so these are commented out.
func fakeSign(msg *types.Message) *types.SignedMessage {
return &types.SignedMessage{
Message: *msg,
@@ -68,15 +68,15 @@ func fakeSign(msg *types.Message) *types.SignedMessage {
}
}
func makeMessageSigner() (*cid.Cid, interface{}) {
smCid := cid.Undef
return &smCid,
func(_ context.Context, msg *types.Message, _ *api.MessageSendSpec) (*types.SignedMessage, error) {
sm := fakeSign(msg)
smCid = sm.Cid()
return sm, nil
}
}
//func makeMessageSigner() (*cid.Cid, interface{}) {
//smCid := cid.Undef
//return &smCid,
//func(_ context.Context, msg *types.Message, _ *api.MessageSendSpec) (*types.SignedMessage, error) {
//sm := fakeSign(msg)
//smCid = sm.Cid()
//return sm, nil
//}
//}
type MessageMatcher SendParams
@@ -84,11 +84,13 @@ var _ gomock.Matcher = MessageMatcher{}
// Matches returns whether x is a match.
func (mm MessageMatcher) Matches(x interface{}) bool {
m, ok := x.(*types.Message)
proto, ok := x.(*api.MessagePrototype)
if !ok {
return false
}
m := &proto.Message
if mm.From != address.Undef && mm.From != m.From {
return false
}
@@ -151,47 +153,12 @@ func TestSendService(t *testing.T) {
t.Run("happy", func(t *testing.T) {
params := params
srvcs, mockApi := setupMockSrvcs(t)
srvcs, _ := setupMockSrvcs(t)
defer srvcs.Close() //nolint:errcheck
msgCid, sign := makeMessageSigner()
gomock.InOrder(
mockApi.EXPECT().WalletBalance(ctxM, params.From).Return(types.NewInt(balance), nil),
mockApi.EXPECT().MpoolPushMessage(ctxM, MessageMatcher(params), nil).DoAndReturn(sign),
)
c, err := srvcs.Send(ctx, params)
proto, err := srvcs.MessageForSend(ctx, params)
assert.NoError(t, err)
assert.Equal(t, *msgCid, c)
})
t.Run("balance-too-low", func(t *testing.T) {
params := params
srvcs, mockApi := setupMockSrvcs(t)
defer srvcs.Close() //nolint:errcheck
gomock.InOrder(
mockApi.EXPECT().WalletBalance(ctxM, a1).Return(types.NewInt(balance-200), nil),
// no MpoolPushMessage
)
c, err := srvcs.Send(ctx, params)
assert.Equal(t, c, cid.Undef)
assert.ErrorIs(t, err, ErrSendBalanceTooLow)
})
t.Run("force", func(t *testing.T) {
params := params
params.Force = true
srvcs, mockApi := setupMockSrvcs(t)
defer srvcs.Close() //nolint:errcheck
msgCid, sign := makeMessageSigner()
gomock.InOrder(
mockApi.EXPECT().WalletBalance(ctxM, a1).Return(types.NewInt(balance-200), nil).AnyTimes(),
mockApi.EXPECT().MpoolPushMessage(ctxM, MessageMatcher(params), nil).DoAndReturn(sign),
)
c, err := srvcs.Send(ctx, params)
assert.NoError(t, err)
assert.Equal(t, *msgCid, c)
assert.True(t, MessageMatcher(params).Matches(proto))
})
t.Run("default-from", func(t *testing.T) {
@@ -202,16 +169,14 @@ func TestSendService(t *testing.T) {
srvcs, mockApi := setupMockSrvcs(t)
defer srvcs.Close() //nolint:errcheck
msgCid, sign := makeMessageSigner()
gomock.InOrder(
mockApi.EXPECT().WalletDefaultAddress(ctxM).Return(a1, nil),
mockApi.EXPECT().WalletBalance(ctxM, a1).Return(types.NewInt(balance), nil),
mockApi.EXPECT().MpoolPushMessage(ctxM, mm, nil).DoAndReturn(sign),
)
c, err := srvcs.Send(ctx, params)
proto, err := srvcs.MessageForSend(ctx, params)
assert.NoError(t, err)
assert.Equal(t, *msgCid, c)
assert.True(t, mm.Matches(proto))
})
t.Run("set-nonce", func(t *testing.T) {
@@ -220,26 +185,12 @@ func TestSendService(t *testing.T) {
params.Nonce = &n
mm := MessageMatcher(params)
srvcs, mockApi := setupMockSrvcs(t)
srvcs, _ := setupMockSrvcs(t)
defer srvcs.Close() //nolint:errcheck
_, _ = mm, mockApi
var sm *types.SignedMessage
gomock.InOrder(
mockApi.EXPECT().WalletBalance(ctxM, a1).Return(types.NewInt(balance), nil),
mockApi.EXPECT().WalletSignMessage(ctxM, a1, mm).DoAndReturn(
func(_ context.Context, _ address.Address, msg *types.Message) (*types.SignedMessage, error) {
sm = fakeSign(msg)
// now we expect MpoolPush with that SignedMessage
mockApi.EXPECT().MpoolPush(ctxM, sm).Return(sm.Cid(), nil)
return sm, nil
}),
)
c, err := srvcs.Send(ctx, params)
proto, err := srvcs.MessageForSend(ctx, params)
assert.NoError(t, err)
assert.Equal(t, sm.Cid(), c)
assert.True(t, mm.Matches(proto))
})
t.Run("gas-params", func(t *testing.T) {
@@ -251,16 +202,14 @@ func TestSendService(t *testing.T) {
gp := big.NewInt(10)
params.GasPremium = &gp
srvcs, mockApi := setupMockSrvcs(t)
defer srvcs.Close() //nolint:errcheck
msgCid, sign := makeMessageSigner()
gomock.InOrder(
mockApi.EXPECT().WalletBalance(ctxM, params.From).Return(types.NewInt(balance), nil),
mockApi.EXPECT().MpoolPushMessage(ctxM, MessageMatcher(params), nil).DoAndReturn(sign),
)
mm := MessageMatcher(params)
c, err := srvcs.Send(ctx, params)
srvcs, _ := setupMockSrvcs(t)
defer srvcs.Close() //nolint:errcheck
proto, err := srvcs.MessageForSend(ctx, params)
assert.NoError(t, err)
assert.Equal(t, *msgCid, c)
assert.True(t, mm.Matches(proto))
})
}
+116 -8
View File
@@ -8,8 +8,10 @@ import (
context "context"
go_address "github.com/filecoin-project/go-address"
abi "github.com/filecoin-project/go-state-types/abi"
big "github.com/filecoin-project/go-state-types/big"
api "github.com/filecoin-project/lotus/api"
types "github.com/filecoin-project/lotus/chain/types"
gomock "github.com/golang/mock/gomock"
go_cid "github.com/ipfs/go-cid"
reflect "reflect"
)
@@ -65,17 +67,123 @@ func (mr *MockServicesAPIMockRecorder) DecodeTypedParamsFromJSON(arg0, arg1, arg
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeTypedParamsFromJSON", reflect.TypeOf((*MockServicesAPI)(nil).DecodeTypedParamsFromJSON), arg0, arg1, arg2, arg3)
}
// Send mocks base method
func (m *MockServicesAPI) Send(arg0 context.Context, arg1 SendParams) (go_cid.Cid, error) {
// FullNodeAPI mocks base method
func (m *MockServicesAPI) FullNodeAPI() api.FullNode {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Send", arg0, arg1)
ret0, _ := ret[0].(go_cid.Cid)
ret := m.ctrl.Call(m, "FullNodeAPI")
ret0, _ := ret[0].(api.FullNode)
return ret0
}
// FullNodeAPI indicates an expected call of FullNodeAPI
func (mr *MockServicesAPIMockRecorder) FullNodeAPI() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullNodeAPI", reflect.TypeOf((*MockServicesAPI)(nil).FullNodeAPI))
}
// GetBaseFee mocks base method
func (m *MockServicesAPI) GetBaseFee(arg0 context.Context) (big.Int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBaseFee", arg0)
ret0, _ := ret[0].(big.Int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Send indicates an expected call of Send
func (mr *MockServicesAPIMockRecorder) Send(arg0, arg1 interface{}) *gomock.Call {
// GetBaseFee indicates an expected call of GetBaseFee
func (mr *MockServicesAPIMockRecorder) GetBaseFee(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockServicesAPI)(nil).Send), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBaseFee", reflect.TypeOf((*MockServicesAPI)(nil).GetBaseFee), arg0)
}
// LocalAddresses mocks base method
func (m *MockServicesAPI) LocalAddresses(arg0 context.Context) (go_address.Address, []go_address.Address, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LocalAddresses", arg0)
ret0, _ := ret[0].(go_address.Address)
ret1, _ := ret[1].([]go_address.Address)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// LocalAddresses indicates an expected call of LocalAddresses
func (mr *MockServicesAPIMockRecorder) LocalAddresses(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddresses", reflect.TypeOf((*MockServicesAPI)(nil).LocalAddresses), arg0)
}
// MessageForSend mocks base method
func (m *MockServicesAPI) MessageForSend(arg0 context.Context, arg1 SendParams) (*api.MessagePrototype, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MessageForSend", arg0, arg1)
ret0, _ := ret[0].(*api.MessagePrototype)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// MessageForSend indicates an expected call of MessageForSend
func (mr *MockServicesAPIMockRecorder) MessageForSend(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MessageForSend", reflect.TypeOf((*MockServicesAPI)(nil).MessageForSend), arg0, arg1)
}
// MpoolCheckPendingMessages mocks base method
func (m *MockServicesAPI) MpoolCheckPendingMessages(arg0 context.Context, arg1 go_address.Address) ([][]api.MessageCheckStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MpoolCheckPendingMessages", arg0, arg1)
ret0, _ := ret[0].([][]api.MessageCheckStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// MpoolCheckPendingMessages indicates an expected call of MpoolCheckPendingMessages
func (mr *MockServicesAPIMockRecorder) MpoolCheckPendingMessages(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolCheckPendingMessages", reflect.TypeOf((*MockServicesAPI)(nil).MpoolCheckPendingMessages), arg0, arg1)
}
// MpoolPendingFilter mocks base method
func (m *MockServicesAPI) MpoolPendingFilter(arg0 context.Context, arg1 func(*types.SignedMessage) bool, arg2 types.TipSetKey) ([]*types.SignedMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MpoolPendingFilter", arg0, arg1, arg2)
ret0, _ := ret[0].([]*types.SignedMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// MpoolPendingFilter indicates an expected call of MpoolPendingFilter
func (mr *MockServicesAPIMockRecorder) MpoolPendingFilter(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolPendingFilter", reflect.TypeOf((*MockServicesAPI)(nil).MpoolPendingFilter), arg0, arg1, arg2)
}
// PublishMessage mocks base method
func (m *MockServicesAPI) PublishMessage(arg0 context.Context, arg1 *api.MessagePrototype, arg2 bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PublishMessage", arg0, arg1, arg2)
ret0, _ := ret[0].(*types.SignedMessage)
ret1, _ := ret[1].([][]api.MessageCheckStatus)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// PublishMessage indicates an expected call of PublishMessage
func (mr *MockServicesAPIMockRecorder) PublishMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishMessage", reflect.TypeOf((*MockServicesAPI)(nil).PublishMessage), arg0, arg1, arg2)
}
// RunChecksForPrototype mocks base method
func (m *MockServicesAPI) RunChecksForPrototype(arg0 context.Context, arg1 *api.MessagePrototype) ([][]api.MessageCheckStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RunChecksForPrototype", arg0, arg1)
ret0, _ := ret[0].([][]api.MessageCheckStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RunChecksForPrototype indicates an expected call of RunChecksForPrototype
func (mr *MockServicesAPIMockRecorder) RunChecksForPrototype(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunChecksForPrototype", reflect.TypeOf((*MockServicesAPI)(nil).RunChecksForPrototype), arg0, arg1)
}
+55 -29
View File
@@ -15,6 +15,8 @@ import (
"strings"
"time"
"github.com/filecoin-project/lotus/api/v0api"
"github.com/fatih/color"
"github.com/filecoin-project/lotus/chain/actors/builtin"
@@ -73,6 +75,50 @@ var StateCmd = &cli.Command{
StateMarketCmd,
StateExecTraceCmd,
StateNtwkVersionCmd,
StateMinerProvingDeadlineCmd,
},
}
var StateMinerProvingDeadlineCmd = &cli.Command{
Name: "miner-proving-deadline",
Usage: "Retrieve information about a given miner's proving deadline",
ArgsUsage: "[minerAddress]",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
if !cctx.Args().Present() {
return fmt.Errorf("must specify miner to get information for")
}
addr, err := address.NewFromString(cctx.Args().First())
if err != nil {
return err
}
ts, err := LoadTipSet(ctx, cctx, api)
if err != nil {
return err
}
cd, err := api.StateMinerProvingDeadline(ctx, addr, ts.Key())
if err != nil {
return xerrors.Errorf("getting miner info: %w", err)
}
fmt.Printf("Period Start:\t%s\n", cd.PeriodStart)
fmt.Printf("Index:\t\t%d\n", cd.Index)
fmt.Printf("Open:\t\t%s\n", cd.Open)
fmt.Printf("Close:\t\t%s\n", cd.Close)
fmt.Printf("Challenge:\t%s\n", cd.Challenge)
fmt.Printf("FaultCutoff:\t%s\n", cd.FaultCutoff)
return nil
},
}
@@ -178,16 +224,19 @@ func ParseTipSetString(ts string) ([]cid.Cid, error) {
return cids, nil
}
func LoadTipSet(ctx context.Context, cctx *cli.Context, api api.FullNode) (*types.TipSet, error) {
// LoadTipSet gets the tipset from the context, or the head from the API.
//
// It always gets the head from the API so commands use a consistent tipset even if time pases.
func LoadTipSet(ctx context.Context, cctx *cli.Context, api v0api.FullNode) (*types.TipSet, error) {
tss := cctx.String("tipset")
if tss == "" {
return nil, nil
return api.ChainHead(ctx)
}
return ParseTipSetRef(ctx, api, tss)
}
func ParseTipSetRef(ctx context.Context, api api.FullNode, tss string) (*types.TipSet, error) {
func ParseTipSetRef(ctx context.Context, api v0api.FullNode, tss string) (*types.TipSet, error) {
if tss[0] == '@' {
if tss == "@head" {
return api.ChainHead(ctx)
@@ -574,7 +623,7 @@ var StateListMinersCmd = &cli.Command{
},
}
func getDealsCounts(ctx context.Context, lapi api.FullNode) (map[address.Address]int, error) {
func getDealsCounts(ctx context.Context, lapi v0api.FullNode) (map[address.Address]int, error) {
allDeals, err := lapi.StateMarketDeals(ctx, types.EmptyTSK)
if err != nil {
return nil, err
@@ -848,14 +897,6 @@ var StateListMessagesCmd = &cli.Command{
return err
}
if ts == nil {
head, err := api.ChainHead(ctx)
if err != nil {
return err
}
ts = head
}
windowSize := abi.ChainEpoch(100)
cur := ts
@@ -955,13 +996,6 @@ var StateComputeStateCmd = &cli.Command{
}
h := abi.ChainEpoch(cctx.Uint64("vm-height"))
if ts == nil {
head, err := api.ChainHead(ctx)
if err != nil {
return err
}
ts = head
}
if h == 0 {
h = ts.Height()
}
@@ -1443,7 +1477,7 @@ var StateSearchMsgCmd = &cli.Command{
},
}
func printReceiptReturn(ctx context.Context, api api.FullNode, m *types.Message, r types.MessageReceipt) error {
func printReceiptReturn(ctx context.Context, api v0api.FullNode, m *types.Message, r types.MessageReceipt) error {
if len(r.Return) == 0 {
return nil
}
@@ -1463,8 +1497,7 @@ func printReceiptReturn(ctx context.Context, api api.FullNode, m *types.Message,
return nil
}
func printMsg(ctx context.Context, api api.FullNode, msg cid.Cid, mw *lapi.MsgLookup, m *types.Message) error {
func printMsg(ctx context.Context, api v0api.FullNode, msg cid.Cid, mw *lapi.MsgLookup, m *types.Message) error {
if mw == nil {
fmt.Println("message was not found on chain")
return nil
@@ -1764,13 +1797,6 @@ var StateSectorCmd = &cli.Command{
return err
}
if ts == nil {
ts, err = api.ChainHead(ctx)
if err != nil {
return err
}
}
maddr, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
+60
View File
@@ -0,0 +1,60 @@
package cli
import (
"fmt"
"github.com/urfave/cli/v2"
"github.com/filecoin-project/lotus/build"
)
var StatusCmd = &cli.Command{
Name: "status",
Usage: "Check node status",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "chain",
Usage: "include chain health status",
},
},
Action: func(cctx *cli.Context) error {
apic, closer, err := GetFullNodeAPIV1(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
inclChainStatus := cctx.Bool("chain")
status, err := apic.NodeStatus(ctx, inclChainStatus)
if err != nil {
return err
}
fmt.Printf("Sync Epoch: %d\n", status.SyncStatus.Epoch)
fmt.Printf("Epochs Behind: %d\n", status.SyncStatus.Behind)
fmt.Printf("Peers to Publish Messages: %d\n", status.PeerStatus.PeersToPublishMsgs)
fmt.Printf("Peers to Publish Blocks: %d\n", status.PeerStatus.PeersToPublishBlocks)
if inclChainStatus && status.SyncStatus.Epoch > uint64(build.Finality) {
var ok100, okFin string
if status.ChainStatus.BlocksPerTipsetLast100 >= 4.75 {
ok100 = "[OK]"
} else {
ok100 = "[UNHEALTHY]"
}
if status.ChainStatus.BlocksPerTipsetLastFinality >= 4.75 {
okFin = "[OK]"
} else {
okFin = "[UNHEALTHY]"
}
fmt.Printf("Blocks per TipSet in last 100 epochs: %f %s\n", status.ChainStatus.BlocksPerTipsetLast100, ok100)
fmt.Printf("Blocks per TipSet in last finality: %f %s\n", status.ChainStatus.BlocksPerTipsetLastFinality, okFin)
}
return nil
},
}
+2 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/urfave/cli/v2"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/api/v0api"
"github.com/filecoin-project/lotus/build"
)
@@ -240,7 +241,7 @@ var SyncCheckpointCmd = &cli.Command{
},
}
func SyncWait(ctx context.Context, napi api.FullNode, watch bool) error {
func SyncWait(ctx context.Context, napi v0api.FullNode, watch bool) error {
tick := time.Second / 4
lastLines := 0
+1 -1
View File
@@ -56,7 +56,7 @@ type MockCLIClient struct {
func (c *MockCLIClient) RunCmd(input ...string) string {
out, err := c.RunCmdRaw(input...)
require.NoError(c.t, err)
require.NoError(c.t, err, "output:\n%s", out)
return out
}
+2
View File
@@ -9,4 +9,6 @@ func QuietMiningLogs() {
_ = log.SetLogLevel("sub", "ERROR")
_ = log.SetLogLevel("storageminer", "ERROR")
_ = log.SetLogLevel("pubsub", "ERROR")
_ = log.SetLogLevel("gen", "ERROR")
_ = log.SetLogLevel("dht/RtRefreshManager", "ERROR")
}
+2 -2
View File
@@ -10,12 +10,12 @@ import (
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/api/v0api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/types"
)
func parseTipSet(ctx context.Context, api api.FullNode, vals []string) (*types.TipSet, error) {
func parseTipSet(ctx context.Context, api v0api.FullNode, vals []string) (*types.TipSet, error) {
var headers []*types.BlockHeader
for _, c := range vals {
blkc, err := cid.Decode(c)
+38 -14
View File
@@ -18,6 +18,8 @@ import (
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/api/client"
"github.com/filecoin-project/lotus/api/v0api"
"github.com/filecoin-project/lotus/api/v1api"
"github.com/filecoin-project/lotus/node/repo"
)
@@ -133,13 +135,13 @@ func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) {
}, nil
}
func GetRawAPI(ctx *cli.Context, t repo.RepoType) (string, http.Header, error) {
func GetRawAPI(ctx *cli.Context, t repo.RepoType, version string) (string, http.Header, error) {
ainfo, err := GetAPIInfo(ctx, t)
if err != nil {
return "", nil, xerrors.Errorf("could not get API info: %w", err)
}
addr, err := ainfo.DialArgs()
addr, err := ainfo.DialArgs(version)
if err != nil {
return "", nil, xerrors.Errorf("could not get DialArgs: %w", err)
}
@@ -165,25 +167,38 @@ func GetAPI(ctx *cli.Context) (api.Common, jsonrpc.ClientCloser, error) {
return tn.(api.FullNode), func() {}, nil
}
addr, headers, err := GetRawAPI(ctx, t)
addr, headers, err := GetRawAPI(ctx, t, "v0")
if err != nil {
return nil, nil, err
}
return client.NewCommonRPC(ctx.Context, addr, headers)
return client.NewCommonRPCV0(ctx.Context, addr, headers)
}
func GetFullNodeAPI(ctx *cli.Context) (api.FullNode, jsonrpc.ClientCloser, error) {
func GetFullNodeAPI(ctx *cli.Context) (v0api.FullNode, jsonrpc.ClientCloser, error) {
if tn, ok := ctx.App.Metadata["testnode-full"]; ok {
return tn.(api.FullNode), func() {}, nil
return &v0api.WrapperV1Full{FullNode: tn.(v1api.FullNode)}, func() {}, nil
}
addr, headers, err := GetRawAPI(ctx, repo.FullNode)
addr, headers, err := GetRawAPI(ctx, repo.FullNode, "v0")
if err != nil {
return nil, nil, err
}
return client.NewFullNodeRPC(ctx.Context, addr, headers)
return client.NewFullNodeRPCV0(ctx.Context, addr, headers)
}
func GetFullNodeAPIV1(ctx *cli.Context) (v1api.FullNode, jsonrpc.ClientCloser, error) {
if tn, ok := ctx.App.Metadata["testnode-full"]; ok {
return tn.(v1api.FullNode), func() {}, nil
}
addr, headers, err := GetRawAPI(ctx, repo.FullNode, "v1")
if err != nil {
return nil, nil, err
}
return client.NewFullNodeRPCV1(ctx.Context, addr, headers)
}
type GetStorageMinerOptions struct {
@@ -206,7 +221,7 @@ func GetStorageMinerAPI(ctx *cli.Context, opts ...GetStorageMinerOption) (api.St
return tn.(api.StorageMiner), func() {}, nil
}
addr, headers, err := GetRawAPI(ctx, repo.StorageMiner)
addr, headers, err := GetRawAPI(ctx, repo.StorageMiner, "v0")
if err != nil {
return nil, nil, err
}
@@ -227,25 +242,34 @@ func GetStorageMinerAPI(ctx *cli.Context, opts ...GetStorageMinerOption) (api.St
addr = u.String()
}
return client.NewStorageMinerRPC(ctx.Context, addr, headers)
return client.NewStorageMinerRPCV0(ctx.Context, addr, headers)
}
func GetWorkerAPI(ctx *cli.Context) (api.Worker, jsonrpc.ClientCloser, error) {
addr, headers, err := GetRawAPI(ctx, repo.Worker)
addr, headers, err := GetRawAPI(ctx, repo.Worker, "v0")
if err != nil {
return nil, nil, err
}
return client.NewWorkerRPC(ctx.Context, addr, headers)
return client.NewWorkerRPCV0(ctx.Context, addr, headers)
}
func GetGatewayAPI(ctx *cli.Context) (api.Gateway, jsonrpc.ClientCloser, error) {
addr, headers, err := GetRawAPI(ctx, repo.FullNode)
addr, headers, err := GetRawAPI(ctx, repo.FullNode, "v1")
if err != nil {
return nil, nil, err
}
return client.NewGatewayRPC(ctx.Context, addr, headers)
return client.NewGatewayRPCV1(ctx.Context, addr, headers)
}
func GetGatewayAPIV0(ctx *cli.Context) (v0api.Gateway, jsonrpc.ClientCloser, error) {
addr, headers, err := GetRawAPI(ctx, repo.FullNode, "v0")
if err != nil {
return nil, nil, err
}
return client.NewGatewayRPCV0(ctx.Context, addr, headers)
}
func DaemonContext(cctx *cli.Context) context.Context {
+3 -3
View File
@@ -36,7 +36,7 @@ func ParseApiInfo(s string) APIInfo {
}
}
func (a APIInfo) DialArgs() (string, error) {
func (a APIInfo) DialArgs(version string) (string, error) {
ma, err := multiaddr.NewMultiaddr(a.Addr)
if err == nil {
_, addr, err := manet.DialArgs(ma)
@@ -44,14 +44,14 @@ func (a APIInfo) DialArgs() (string, error) {
return "", err
}
return "ws://" + addr + "/rpc/v0", nil
return "ws://" + addr + "/rpc/" + version, nil
}
_, err = url.Parse(a.Addr)
if err != nil {
return "", err
}
return a.Addr + "/rpc/v0", nil
return a.Addr + "/rpc/" + version, nil
}
func (a APIInfo) Host() (string, error) {
+276
View File
@@ -0,0 +1,276 @@
package cli
import (
"context"
"fmt"
verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/lotus/api/v0api"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/blockstore"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/actors/adt"
"github.com/filecoin-project/lotus/chain/actors/builtin/verifreg"
"github.com/filecoin-project/lotus/chain/types"
cbor "github.com/ipfs/go-ipld-cbor"
)
var verifRegCmd = &cli.Command{
Name: "verifreg",
Usage: "Interact with the verified registry actor",
Flags: []cli.Flag{},
Subcommands: []*cli.Command{
verifRegVerifyClientCmd,
verifRegListVerifiersCmd,
verifRegListClientsCmd,
verifRegCheckClientCmd,
verifRegCheckVerifierCmd,
},
}
var verifRegVerifyClientCmd = &cli.Command{
Name: "verify-client",
Usage: "give allowance to the specified verified client address",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "from",
Usage: "specify your verifier address to send the message from",
Required: true,
},
},
Action: func(cctx *cli.Context) error {
froms := cctx.String("from")
if froms == "" {
return fmt.Errorf("must specify from address with --from")
}
fromk, err := address.NewFromString(froms)
if err != nil {
return err
}
if cctx.Args().Len() != 2 {
return fmt.Errorf("must specify two arguments: address and allowance")
}
target, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
allowance, err := types.BigFromString(cctx.Args().Get(1))
if err != nil {
return err
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
found, dcap, err := checkVerifier(ctx, api, fromk)
if err != nil {
return err
}
if !found {
return xerrors.New("sender address must be a verifier")
}
if dcap.Cmp(allowance.Int) < 0 {
return xerrors.Errorf("cannot allot more allowance than verifier data cap: %s < %s", dcap, allowance)
}
// TODO: This should be abstracted over actor versions
params, err := actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: target, Allowance: allowance})
if err != nil {
return err
}
msg := &types.Message{
To: verifreg.Address,
From: fromk,
Method: verifreg.Methods.AddVerifiedClient,
Params: params,
}
smsg, err := api.MpoolPushMessage(ctx, msg, nil)
if err != nil {
return err
}
fmt.Printf("message sent, now waiting on cid: %s\n", smsg.Cid())
mwait, err := api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence)
if err != nil {
return err
}
if mwait.Receipt.ExitCode != 0 {
return fmt.Errorf("failed to add verified client: %d", mwait.Receipt.ExitCode)
}
return nil
},
}
var verifRegListVerifiersCmd = &cli.Command{
Name: "list-verifiers",
Usage: "list all verifiers",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
act, err := api.StateGetActor(ctx, verifreg.Address, types.EmptyTSK)
if err != nil {
return err
}
apibs := blockstore.NewAPIBlockstore(api)
store := adt.WrapStore(ctx, cbor.NewCborStore(apibs))
st, err := verifreg.Load(store, act)
if err != nil {
return err
}
return st.ForEachVerifier(func(addr address.Address, dcap abi.StoragePower) error {
_, err := fmt.Printf("%s: %s\n", addr, dcap)
return err
})
},
}
var verifRegListClientsCmd = &cli.Command{
Name: "list-clients",
Usage: "list all verified clients",
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
act, err := api.StateGetActor(ctx, verifreg.Address, types.EmptyTSK)
if err != nil {
return err
}
apibs := blockstore.NewAPIBlockstore(api)
store := adt.WrapStore(ctx, cbor.NewCborStore(apibs))
st, err := verifreg.Load(store, act)
if err != nil {
return err
}
return st.ForEachClient(func(addr address.Address, dcap abi.StoragePower) error {
_, err := fmt.Printf("%s: %s\n", addr, dcap)
return err
})
},
}
var verifRegCheckClientCmd = &cli.Command{
Name: "check-client",
Usage: "check verified client remaining bytes",
Action: func(cctx *cli.Context) error {
if !cctx.Args().Present() {
return fmt.Errorf("must specify client address to check")
}
caddr, err := address.NewFromString(cctx.Args().First())
if err != nil {
return err
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
dcap, err := api.StateVerifiedClientStatus(ctx, caddr, types.EmptyTSK)
if err != nil {
return err
}
if dcap == nil {
return xerrors.Errorf("client %s is not a verified client", err)
}
fmt.Println(*dcap)
return nil
},
}
var verifRegCheckVerifierCmd = &cli.Command{
Name: "check-verifier",
Usage: "check verifiers remaining bytes",
Action: func(cctx *cli.Context) error {
if !cctx.Args().Present() {
return fmt.Errorf("must specify verifier address to check")
}
vaddr, err := address.NewFromString(cctx.Args().First())
if err != nil {
return err
}
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
found, dcap, err := checkVerifier(ctx, api, vaddr)
if err != nil {
return err
}
if !found {
return fmt.Errorf("not found")
}
fmt.Println(dcap)
return nil
},
}
func checkVerifier(ctx context.Context, api v0api.FullNode, vaddr address.Address) (bool, abi.StoragePower, error) {
vid, err := api.StateLookupID(ctx, vaddr, types.EmptyTSK)
if err != nil {
return false, big.Zero(), err
}
act, err := api.StateGetActor(ctx, verifreg.Address, types.EmptyTSK)
if err != nil {
return false, big.Zero(), err
}
apibs := blockstore.NewAPIBlockstore(api)
store := adt.WrapStore(ctx, cbor.NewCborStore(apibs))
st, err := verifreg.Load(store, act)
if err != nil {
return false, big.Zero(), err
}
return st.VerifierDataCap(vid)
}
+1 -1
View File
@@ -12,7 +12,7 @@ var WaitApiCmd = &cli.Command{
Usage: "Wait for lotus api to come online",
Action: func(cctx *cli.Context) error {
for i := 0; i < 30; i++ {
api, closer, err := GetFullNodeAPI(cctx)
api, closer, err := GetAPI(cctx)
if err != nil {
fmt.Printf("Not online yet... (%s)\n", err)
time.Sleep(time.Second)