Add mpool manage
command
Signed-off-by: Jakub Sztandera <kubuxu@protocol.ai>
This commit is contained in:
parent
87df73a455
commit
7535c5bb53
@ -360,7 +360,7 @@ func (mp *MessagePool) checkMessages(msgs []*types.Message, interned bool) (resu
|
||||
}
|
||||
|
||||
if m.GasFeeCap.LessThan(baseFeeUpperBound) {
|
||||
check.OK = false
|
||||
check.OK = true // on purpose, the checks is more of a warning
|
||||
check.Err = "GasFeeCap less than base fee upper bound for inclusion in next 20 epochs"
|
||||
} else {
|
||||
check.OK = true
|
||||
|
@ -34,6 +34,7 @@ var MpoolCmd = &cli.Command{
|
||||
MpoolFindCmd,
|
||||
MpoolConfig,
|
||||
MpoolGasPerfCmd,
|
||||
mpoolManage,
|
||||
},
|
||||
}
|
||||
|
||||
|
356
cli/mpool_manage.go
Normal file
356
cli/mpool_manage.go
Normal file
@ -0,0 +1,356 @@
|
||||
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()
|
||||
ctx := ReqContext(cctx)
|
||||
|
||||
_, localAddr, err := srv.LocalAddresses(ctx)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
22
cli/send.go
22
cli/send.go
@ -58,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"))
|
||||
}
|
||||
@ -145,21 +149,13 @@ var sendCmd = &cli.Command{
|
||||
if err != nil {
|
||||
return xerrors.Errorf("creating message prototype: %w", err)
|
||||
}
|
||||
msg, checks, err := srv.PublishMessage(ctx, proto, cctx.Bool("force"))
|
||||
if xerrors.Is(err, ErrCheckFailed) {
|
||||
proto, err := resolveChecks(ctx, srv, cctx.App.Writer, proto, checks, true)
|
||||
|
||||
c, err := InteractiveSend(ctx, cctx, srv, proto)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("from UI: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
msg, _, err = srv.PublishMessage(ctx, proto, true) //nolint
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return xerrors.Errorf("publishing message: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(cctx.App.Writer, "%s\n", msg.Cid())
|
||||
fmt.Fprintf(cctx.App.Writer, "%s\n", c)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
110
cli/send_test.go
110
cli/send_test.go
@ -1,6 +1,17 @@
|
||||
package cli
|
||||
|
||||
/*
|
||||
import (
|
||||
"bytes"
|
||||
"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"
|
||||
"github.com/stretchr/testify/assert"
|
||||
ucli "github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var arbtCid = (&types.Message{
|
||||
From: mustAddr(address.NewIDAddress(2)),
|
||||
@ -37,81 +48,26 @@ func TestSendCLI(t *testing.T) {
|
||||
app, mockSrvcs, buf, done := newMockApp(t, sendCmd)
|
||||
defer done()
|
||||
|
||||
gomock.InOrder(
|
||||
mockSrvcs.EXPECT().Send(gomock.Any(), SendParams{
|
||||
arbtProto := &api.MessagePrototype{
|
||||
Message: types.Message{
|
||||
From: mustAddr(address.NewIDAddress(1)),
|
||||
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{
|
||||
To: mustAddr(address.NewIDAddress(1)),
|
||||
Nonce: &zero,
|
||||
Val: oneFil,
|
||||
}).Return(arbtCid, nil),
|
||||
mockSrvcs.EXPECT().Close(),
|
||||
)
|
||||
err := app.Run([]string{"lotus", "send", "--nonce=0", "t01", "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, arbtCid.String()+"\n", buf.String())
|
||||
})
|
||||
|
||||
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", "t01", "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, sigMsg.Cid().String()+"\n", buf.String())
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
@ -14,8 +14,35 @@ import (
|
||||
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) (cid.Cid, 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 cid.Undef, xerrors.Errorf("from UI: %w", err)
|
||||
}
|
||||
|
||||
msg, _, err = srv.PublishMessage(ctx, proto, true)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return cid.Undef, xerrors.Errorf("publishing message: %w", err)
|
||||
}
|
||||
|
||||
return msg.Cid(), nil
|
||||
}
|
||||
|
||||
var interactiveSolves = map[api.CheckStatusCode]bool{
|
||||
api.CheckStatusMessageBaseFee: true,
|
||||
api.CheckStatusMessageBaseFeeLowerBound: true,
|
||||
@ -42,15 +69,11 @@ func baseFeeFromHints(hint map[string]interface{}) big.Int {
|
||||
|
||||
func resolveChecks(ctx context.Context, s ServicesAPI, printer io.Writer,
|
||||
proto *api.MessagePrototype, checkGroups [][]api.MessageCheckStatus,
|
||||
interactive bool) (*api.MessagePrototype, error) {
|
||||
) (*api.MessagePrototype, error) {
|
||||
|
||||
fmt.Fprintf(printer, "Following checks have failed:\n")
|
||||
printChecks(printer, checkGroups, proto.Message.Cid())
|
||||
if !interactive {
|
||||
return nil, ErrCheckFailed
|
||||
}
|
||||
|
||||
if interactive {
|
||||
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) {
|
||||
@ -71,7 +94,6 @@ func resolveChecks(ctx context.Context, s ServicesAPI, printer io.Writer,
|
||||
if !askUser(printer, "Do you wish to send this message? [yes/No]: ", false) {
|
||||
return nil, ErrAbortedByUser
|
||||
}
|
||||
}
|
||||
return proto, nil
|
||||
}
|
||||
|
||||
@ -88,7 +110,7 @@ func printChecks(printer io.Writer, checkGroups [][]api.MessageCheckStatus, prot
|
||||
if !aboutProto {
|
||||
msgName = c.Cid.String()
|
||||
}
|
||||
fmt.Fprintf(printer, "%s message failed a check: %s\n", msgName, c.Err)
|
||||
fmt.Fprintf(printer, "%s message failed a check %s: %s\n", msgName, c.Code, c.Err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -133,7 +155,7 @@ func runFeeCapAdjustmentUI(proto *api.MessagePrototype, baseFee abi.TokenAmount)
|
||||
|
||||
maxFee := big.Mul(proto.Message.GasFeeCap, big.NewInt(proto.Message.GasLimit))
|
||||
send := false
|
||||
t.SetScene(ui(baseFee, proto.Message.GasLimit, &maxFee, &send))
|
||||
t.PushScene(feeUI(baseFee, proto.Message.GasLimit, &maxFee, &send))
|
||||
|
||||
err = t.Run()
|
||||
if err != nil {
|
||||
@ -148,7 +170,7 @@ func runFeeCapAdjustmentUI(proto *api.MessagePrototype, baseFee abi.TokenAmount)
|
||||
return proto, nil
|
||||
}
|
||||
|
||||
func ui(baseFee abi.TokenAmount, gasLimit int64, maxFee *abi.TokenAmount, send *bool) func(*imtui.Tui) error {
|
||||
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))
|
||||
@ -180,7 +202,8 @@ func ui(baseFee abi.TokenAmount, gasLimit int64, maxFee *abi.TokenAmount, send *
|
||||
|
||||
if t.CurrentKey.Key() == tcell.KeyEnter {
|
||||
*send = true
|
||||
return imtui.ErrNormalExit
|
||||
t.PopScene()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,12 @@ type ServicesAPI interface {
|
||||
// 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, interactive bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error)
|
||||
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
|
||||
@ -240,3 +245,41 @@ func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (*
|
||||
}
|
||||
return prototype, 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
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/big"
|
||||
"github.com/filecoin-project/go-state-types/crypto"
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
mocks "github.com/filecoin-project/lotus/api/v0api/v0mocks"
|
||||
types "github.com/filecoin-project/lotus/chain/types"
|
||||
@ -60,12 +61,12 @@ func setupMockSrvcs(t *testing.T) (*ServicesImpl, *mocks.MockFullNode) {
|
||||
}
|
||||
|
||||
// linter doesn't like dead code, so these are commented out.
|
||||
// func fakeSign(msg *types.Message) *types.SignedMessage {
|
||||
// return &types.SignedMessage{
|
||||
// Message: *msg,
|
||||
// Signature: crypto.Signature{Type: crypto.SigTypeSecp256k1, Data: make([]byte, 32)},
|
||||
// }
|
||||
// }
|
||||
func fakeSign(msg *types.Message) *types.SignedMessage {
|
||||
return &types.SignedMessage{
|
||||
Message: *msg,
|
||||
Signature: crypto.Signature{Type: crypto.SigTypeSecp256k1, Data: make([]byte, 32)},
|
||||
}
|
||||
}
|
||||
|
||||
//func makeMessageSigner() (*cid.Cid, interface{}) {
|
||||
//smCid := cid.Undef
|
||||
|
@ -96,6 +96,22 @@ func (mr *MockServicesAPIMockRecorder) GetBaseFee(arg0 interface{}) *gomock.Call
|
||||
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()
|
||||
@ -111,6 +127,36 @@ func (mr *MockServicesAPIMockRecorder) MessageForSend(arg0, arg1 interface{}) *g
|
||||
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()
|
||||
|
@ -2,7 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.opencensus.io/trace"
|
||||
|
||||
@ -52,6 +54,8 @@ func main() {
|
||||
ctx, span := trace.StartSpan(context.Background(), "/cli")
|
||||
defer span.End()
|
||||
|
||||
interactiveDef := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
|
||||
|
||||
app := &cli.App{
|
||||
Name: "lotus",
|
||||
Usage: "Filecoin decentralized storage network client",
|
||||
@ -64,10 +68,20 @@ func main() {
|
||||
Hidden: true,
|
||||
Value: "~/.lotus", // TODO: Consider XDG_DATA_HOME
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "interactive",
|
||||
Usage: "setting to false will disable interactive functionality of commands",
|
||||
Value: interactiveDef,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "force-send",
|
||||
Usage: "if true, will ignore pre-send checks",
|
||||
},
|
||||
},
|
||||
|
||||
Commands: append(local, lcli.Commands...),
|
||||
}
|
||||
|
||||
app.Setup()
|
||||
app.Metadata["traceContext"] = ctx
|
||||
app.Metadata["repoType"] = repo.FullNode
|
||||
|
3
go.mod
3
go.mod
@ -8,7 +8,7 @@ require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/GeertJohan/go.rice v1.0.0
|
||||
github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee
|
||||
github.com/Kubuxu/imtui v0.0.0-20210323145256-9fdaecfdf6b7
|
||||
github.com/Kubuxu/imtui v0.0.0-20210401140320-41663d68d0fa
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921
|
||||
@ -118,6 +118,7 @@ require (
|
||||
github.com/libp2p/go-libp2p-yamux v0.4.1
|
||||
github.com/libp2p/go-maddr-filter v0.1.0
|
||||
github.com/mattn/go-colorable v0.1.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/multiformats/go-base32 v0.0.3
|
||||
|
4
go.sum
4
go.sum
@ -42,11 +42,11 @@ github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee h1:8doiS7ib3zi6/K1
|
||||
github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee/go.mod h1:W0GbEAA4uFNYOGG2cJpmFJ04E6SD1NLELPYZB57/7AY=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y=
|
||||
github.com/Kubuxu/imtui v0.0.0-20210401140320-41663d68d0fa h1:1PPxEyGdIGVkX/kqMvLJ95a1dGS1Sz7tpNEgehEYYt0=
|
||||
github.com/Kubuxu/imtui v0.0.0-20210401140320-41663d68d0fa/go.mod h1:WUmMvh9wMtqj1Xhf1hf3kp9RvL+y6odtdYxpyZjb90U=
|
||||
github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
|
||||
github.com/Kubuxu/imtui v0.0.0-20210323145256-9fdaecfdf6b7 h1:oaKenk0p5Pg7k2YRflJtiai4weJN+VsABO3zSaUVU6w=
|
||||
github.com/Kubuxu/imtui v0.0.0-20210323145256-9fdaecfdf6b7/go.mod h1:WUmMvh9wMtqj1Xhf1hf3kp9RvL+y6odtdYxpyZjb90U=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
|
Loading…
Reference in New Issue
Block a user