Merge pull request #4421 from filecoin-project/fix/lite-msig-inspect
Ensure msig inspect cli works with lotus-lite
This commit is contained in:
commit
c56ef260d5
@ -10,9 +10,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type GatewayAPI interface {
|
type GatewayAPI interface {
|
||||||
|
ChainHasObj(context.Context, cid.Cid) (bool, error)
|
||||||
ChainHead(ctx context.Context) (*types.TipSet, error)
|
ChainHead(ctx context.Context) (*types.TipSet, error)
|
||||||
ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)
|
ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)
|
||||||
ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error)
|
ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error)
|
||||||
|
ChainReadObj(context.Context, cid.Cid) ([]byte, error)
|
||||||
GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *MessageSendSpec, tsk types.TipSetKey) (*types.Message, error)
|
GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *MessageSendSpec, tsk types.TipSetKey) (*types.Message, error)
|
||||||
MpoolPush(ctx context.Context, sm *types.SignedMessage) (cid.Cid, error)
|
MpoolPush(ctx context.Context, sm *types.SignedMessage) (cid.Cid, error)
|
||||||
MsigGetAvailableBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (types.BigInt, error)
|
MsigGetAvailableBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (types.BigInt, error)
|
||||||
|
@ -371,9 +371,11 @@ type WorkerStruct struct {
|
|||||||
type GatewayStruct struct {
|
type GatewayStruct struct {
|
||||||
Internal struct {
|
Internal struct {
|
||||||
// TODO: does the gateway need perms?
|
// TODO: does the gateway need perms?
|
||||||
|
ChainHasObj func(context.Context, cid.Cid) (bool, error)
|
||||||
ChainGetTipSet func(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)
|
ChainGetTipSet func(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)
|
||||||
ChainGetTipSetByHeight func(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error)
|
ChainGetTipSetByHeight func(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error)
|
||||||
ChainHead func(ctx context.Context) (*types.TipSet, error)
|
ChainHead func(ctx context.Context) (*types.TipSet, error)
|
||||||
|
ChainReadObj func(context.Context, cid.Cid) ([]byte, error)
|
||||||
GasEstimateMessageGas func(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error)
|
GasEstimateMessageGas func(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error)
|
||||||
MpoolPush func(ctx context.Context, sm *types.SignedMessage) (cid.Cid, error)
|
MpoolPush func(ctx context.Context, sm *types.SignedMessage) (cid.Cid, error)
|
||||||
MsigGetAvailableBalance func(ctx context.Context, addr address.Address, tsk types.TipSetKey) (types.BigInt, error)
|
MsigGetAvailableBalance func(ctx context.Context, addr address.Address, tsk types.TipSetKey) (types.BigInt, error)
|
||||||
@ -1432,6 +1434,10 @@ func (w *WorkerStruct) Closing(ctx context.Context) (<-chan struct{}, error) {
|
|||||||
return w.Internal.Closing(ctx)
|
return w.Internal.Closing(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g GatewayStruct) ChainHasObj(ctx context.Context, c cid.Cid) (bool, error) {
|
||||||
|
return g.Internal.ChainHasObj(ctx, c)
|
||||||
|
}
|
||||||
|
|
||||||
func (g GatewayStruct) ChainHead(ctx context.Context) (*types.TipSet, error) {
|
func (g GatewayStruct) ChainHead(ctx context.Context) (*types.TipSet, error) {
|
||||||
return g.Internal.ChainHead(ctx)
|
return g.Internal.ChainHead(ctx)
|
||||||
}
|
}
|
||||||
@ -1444,6 +1450,10 @@ func (g GatewayStruct) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEp
|
|||||||
return g.Internal.ChainGetTipSetByHeight(ctx, h, tsk)
|
return g.Internal.ChainGetTipSetByHeight(ctx, h, tsk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g GatewayStruct) ChainReadObj(ctx context.Context, c cid.Cid) ([]byte, error) {
|
||||||
|
return g.Internal.ChainReadObj(ctx, c)
|
||||||
|
}
|
||||||
|
|
||||||
func (g GatewayStruct) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) {
|
func (g GatewayStruct) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) {
|
||||||
return g.Internal.GasEstimateMessageGas(ctx, msg, spec, tsk)
|
return g.Internal.GasEstimateMessageGas(ctx, msg, spec, tsk)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -162,7 +161,7 @@ var msigCreateCmd = &cli.Command{
|
|||||||
|
|
||||||
// check it executed successfully
|
// check it executed successfully
|
||||||
if wait.Receipt.ExitCode != 0 {
|
if wait.Receipt.ExitCode != 0 {
|
||||||
fmt.Println("actor creation failed!")
|
fmt.Fprintln(cctx.App.Writer, "actor creation failed!")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +171,7 @@ var msigCreateCmd = &cli.Command{
|
|||||||
if err := execreturn.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
|
if err := execreturn.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("Created new multisig: ", execreturn.IDAddress, execreturn.RobustAddress)
|
fmt.Fprintln(cctx.App.Writer, "Created new multisig: ", execreturn.IDAddress, execreturn.RobustAddress)
|
||||||
|
|
||||||
// TODO: maybe register this somewhere
|
// TODO: maybe register this somewhere
|
||||||
return nil
|
return nil
|
||||||
@ -236,25 +235,25 @@ var msigInspectCmd = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Balance: %s\n", types.FIL(act.Balance))
|
fmt.Fprintf(cctx.App.Writer, "Balance: %s\n", types.FIL(act.Balance))
|
||||||
fmt.Printf("Spendable: %s\n", types.FIL(types.BigSub(act.Balance, locked)))
|
fmt.Fprintf(cctx.App.Writer, "Spendable: %s\n", types.FIL(types.BigSub(act.Balance, locked)))
|
||||||
|
|
||||||
if cctx.Bool("vesting") {
|
if cctx.Bool("vesting") {
|
||||||
ib, err := mstate.InitialBalance()
|
ib, err := mstate.InitialBalance()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("InitialBalance: %s\n", types.FIL(ib))
|
fmt.Fprintf(cctx.App.Writer, "InitialBalance: %s\n", types.FIL(ib))
|
||||||
se, err := mstate.StartEpoch()
|
se, err := mstate.StartEpoch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("StartEpoch: %d\n", se)
|
fmt.Fprintf(cctx.App.Writer, "StartEpoch: %d\n", se)
|
||||||
ud, err := mstate.UnlockDuration()
|
ud, err := mstate.UnlockDuration()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("UnlockDuration: %d\n", ud)
|
fmt.Fprintf(cctx.App.Writer, "UnlockDuration: %d\n", ud)
|
||||||
}
|
}
|
||||||
|
|
||||||
signers, err := mstate.Signers()
|
signers, err := mstate.Signers()
|
||||||
@ -265,10 +264,10 @@ var msigInspectCmd = &cli.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("Threshold: %d / %d\n", threshold, len(signers))
|
fmt.Fprintf(cctx.App.Writer, "Threshold: %d / %d\n", threshold, len(signers))
|
||||||
fmt.Println("Signers:")
|
fmt.Fprintln(cctx.App.Writer, "Signers:")
|
||||||
for _, s := range signers {
|
for _, s := range signers {
|
||||||
fmt.Printf("\t%s\n", s)
|
fmt.Fprintf(cctx.App.Writer, "\t%s\n", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
pending := make(map[int64]multisig.Transaction)
|
pending := make(map[int64]multisig.Transaction)
|
||||||
@ -280,7 +279,7 @@ var msigInspectCmd = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
decParams := cctx.Bool("decode-params")
|
decParams := cctx.Bool("decode-params")
|
||||||
fmt.Println("Transactions: ", len(pending))
|
fmt.Fprintln(cctx.App.Writer, "Transactions: ", len(pending))
|
||||||
if len(pending) > 0 {
|
if len(pending) > 0 {
|
||||||
var txids []int64
|
var txids []int64
|
||||||
for txid := range pending {
|
for txid := range pending {
|
||||||
@ -290,7 +289,7 @@ var msigInspectCmd = &cli.Command{
|
|||||||
return txids[i] < txids[j]
|
return txids[i] < txids[j]
|
||||||
})
|
})
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 8, 4, 2, ' ', 0)
|
w := tabwriter.NewWriter(cctx.App.Writer, 8, 4, 2, ' ', 0)
|
||||||
fmt.Fprintf(w, "ID\tState\tApprovals\tTo\tValue\tMethod\tParams\n")
|
fmt.Fprintf(w, "ID\tState\tApprovals\tTo\tValue\tMethod\tParams\n")
|
||||||
for _, txid := range txids {
|
for _, txid := range txids {
|
||||||
tx := pending[txid]
|
tx := pending[txid]
|
||||||
@ -699,7 +698,7 @@ var msigAddProposeCmd = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("sent add proposal in message: ", msgCid)
|
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")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
55
cli/multisig_test.go
Normal file
55
cli/multisig_test.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-address"
|
||||||
|
"github.com/filecoin-project/lotus/api/test"
|
||||||
|
clitest "github.com/filecoin-project/lotus/cli/test"
|
||||||
|
builder "github.com/filecoin-project/lotus/node/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMultisig does a basic test to exercise the multisig CLI
|
||||||
|
// commands
|
||||||
|
func TestMultisig(t *testing.T) {
|
||||||
|
_ = os.Setenv("BELLMAN_NO_GPU", "1")
|
||||||
|
|
||||||
|
blocktime := 5 * time.Millisecond
|
||||||
|
ctx := context.Background()
|
||||||
|
nodes, _ := startNodes(ctx, t, blocktime)
|
||||||
|
clientNode := nodes[0]
|
||||||
|
clitest.RunMultisigTest(t, Commands, clientNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startNodes(ctx context.Context, t *testing.T, blocktime time.Duration) ([]test.TestNode, []address.Address) {
|
||||||
|
n, sn := builder.RPCMockSbBuilder(t, test.OneFull, test.OneMiner)
|
||||||
|
|
||||||
|
full := n[0]
|
||||||
|
miner := sn[0]
|
||||||
|
|
||||||
|
// Get everyone connected
|
||||||
|
addrs, err := full.NetAddrsListen(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := miner.NetConnect(ctx, addrs); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start mining blocks
|
||||||
|
bm := test.NewBlockMiner(ctx, t, miner, blocktime)
|
||||||
|
bm.MineBlocks()
|
||||||
|
|
||||||
|
// Get the creator's address
|
||||||
|
creatorAddr, err := full.WalletDefaultAddress(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mock CLI
|
||||||
|
return n, []address.Address{creatorAddr}
|
||||||
|
}
|
@ -437,6 +437,7 @@ type mockCLI struct {
|
|||||||
out *bytes.Buffer
|
out *bytes.Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: refactor to use the methods in cli/test/mockcli.go
|
||||||
func newMockCLI(t *testing.T) *mockCLI {
|
func newMockCLI(t *testing.T) *mockCLI {
|
||||||
// Create a CLI App with an --api-url flag so that we can specify which node
|
// Create a CLI App with an --api-url flag so that we can specify which node
|
||||||
// the command should be executed against
|
// the command should be executed against
|
||||||
|
124
cli/test/mockcli.go
Normal file
124
cli/test/mockcli.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/multiformats/go-multiaddr"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
lcli "github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockCLI struct {
|
||||||
|
t *testing.T
|
||||||
|
cmds []*lcli.Command
|
||||||
|
cctx *lcli.Context
|
||||||
|
out *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockCLI(t *testing.T, cmds []*lcli.Command) *mockCLI {
|
||||||
|
// Create a CLI App with an --api-url flag so that we can specify which node
|
||||||
|
// the command should be executed against
|
||||||
|
app := &lcli.App{
|
||||||
|
Flags: []lcli.Flag{
|
||||||
|
&lcli.StringFlag{
|
||||||
|
Name: "api-url",
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Commands: cmds,
|
||||||
|
}
|
||||||
|
|
||||||
|
var out bytes.Buffer
|
||||||
|
app.Writer = &out
|
||||||
|
app.Setup()
|
||||||
|
|
||||||
|
cctx := lcli.NewContext(app, &flag.FlagSet{}, nil)
|
||||||
|
return &mockCLI{t: t, cmds: cmds, cctx: cctx, out: &out}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockCLI) client(addr multiaddr.Multiaddr) *mockCLIClient {
|
||||||
|
return &mockCLIClient{t: c.t, cmds: c.cmds, addr: addr, cctx: c.cctx, out: c.out}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mockCLIClient runs commands against a particular node
|
||||||
|
type mockCLIClient struct {
|
||||||
|
t *testing.T
|
||||||
|
cmds []*lcli.Command
|
||||||
|
addr multiaddr.Multiaddr
|
||||||
|
cctx *lcli.Context
|
||||||
|
out *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockCLIClient) run(cmd []string, params []string, args []string) string {
|
||||||
|
// Add parameter --api-url=<node api listener address>
|
||||||
|
apiFlag := "--api-url=" + c.addr.String()
|
||||||
|
params = append([]string{apiFlag}, params...)
|
||||||
|
|
||||||
|
err := c.cctx.App.Run(append(append(cmd, params...), args...))
|
||||||
|
require.NoError(c.t, err)
|
||||||
|
|
||||||
|
// Get the output
|
||||||
|
str := strings.TrimSpace(c.out.String())
|
||||||
|
c.out.Reset()
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockCLIClient) runCmd(input []string) string {
|
||||||
|
cmd := c.cmdByNameSub(input[0], input[1])
|
||||||
|
out, err := c.runCmdRaw(cmd, input[2:])
|
||||||
|
require.NoError(c.t, err)
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockCLIClient) cmdByNameSub(name string, sub string) *lcli.Command {
|
||||||
|
for _, c := range c.cmds {
|
||||||
|
if c.Name == name {
|
||||||
|
for _, s := range c.Subcommands {
|
||||||
|
if s.Name == sub {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockCLIClient) runCmdRaw(cmd *lcli.Command, input []string) (string, error) {
|
||||||
|
// prepend --api-url=<node api listener address>
|
||||||
|
apiFlag := "--api-url=" + c.addr.String()
|
||||||
|
input = append([]string{apiFlag}, input...)
|
||||||
|
|
||||||
|
fs := c.flagSet(cmd)
|
||||||
|
err := fs.Parse(input)
|
||||||
|
require.NoError(c.t, err)
|
||||||
|
|
||||||
|
err = cmd.Action(lcli.NewContext(c.cctx.App, fs, c.cctx))
|
||||||
|
|
||||||
|
// Get the output
|
||||||
|
str := strings.TrimSpace(c.out.String())
|
||||||
|
c.out.Reset()
|
||||||
|
return str, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockCLIClient) flagSet(cmd *lcli.Command) *flag.FlagSet {
|
||||||
|
// Apply app level flags (so we can process --api-url flag)
|
||||||
|
fs := &flag.FlagSet{}
|
||||||
|
for _, f := range c.cctx.App.Flags {
|
||||||
|
err := f.Apply(fs)
|
||||||
|
if err != nil {
|
||||||
|
c.t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Apply command level flags
|
||||||
|
for _, f := range cmd.Flags {
|
||||||
|
err := f.Apply(fs)
|
||||||
|
if err != nil {
|
||||||
|
c.t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fs
|
||||||
|
}
|
110
cli/test/multisig.go
Normal file
110
cli/test/multisig.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-address"
|
||||||
|
"github.com/filecoin-project/lotus/api/test"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
logging "github.com/ipfs/go-log/v2"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
lcli "github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func QuietMiningLogs() {
|
||||||
|
logging.SetLogLevel("miner", "ERROR")
|
||||||
|
logging.SetLogLevel("chainstore", "ERROR")
|
||||||
|
logging.SetLogLevel("chain", "ERROR")
|
||||||
|
logging.SetLogLevel("sub", "ERROR")
|
||||||
|
logging.SetLogLevel("storageminer", "ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunMultisigTest(t *testing.T, cmds []*lcli.Command, clientNode test.TestNode) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Create mock CLI
|
||||||
|
mockCLI := newMockCLI(t, cmds)
|
||||||
|
clientCLI := mockCLI.client(clientNode.ListenAddr)
|
||||||
|
|
||||||
|
// Create some wallets on the node to use for testing multisig
|
||||||
|
var walletAddrs []address.Address
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
addr, err := clientNode.WalletNew(ctx, types.KTSecp256k1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
walletAddrs = append(walletAddrs, addr)
|
||||||
|
|
||||||
|
test.SendFunds(ctx, t, clientNode, addr, types.NewInt(1e15))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an msig with three of the addresses and threshold of two sigs
|
||||||
|
// msig create --required=2 --duration=50 --value=1000attofil <addr1> <addr2> <addr3>
|
||||||
|
amtAtto := types.NewInt(1000)
|
||||||
|
threshold := 2
|
||||||
|
paramDuration := "--duration=50"
|
||||||
|
paramRequired := fmt.Sprintf("--required=%d", threshold)
|
||||||
|
paramValue := fmt.Sprintf("--value=%dattofil", amtAtto)
|
||||||
|
cmd := []string{
|
||||||
|
"msig", "create",
|
||||||
|
paramRequired,
|
||||||
|
paramDuration,
|
||||||
|
paramValue,
|
||||||
|
walletAddrs[0].String(),
|
||||||
|
walletAddrs[1].String(),
|
||||||
|
walletAddrs[2].String(),
|
||||||
|
}
|
||||||
|
out := clientCLI.runCmd(cmd)
|
||||||
|
fmt.Println(out)
|
||||||
|
|
||||||
|
// Extract msig robust address from output
|
||||||
|
expCreateOutPrefix := "Created new multisig:"
|
||||||
|
require.Regexp(t, regexp.MustCompile(expCreateOutPrefix), out)
|
||||||
|
parts := strings.Split(strings.TrimSpace(strings.Replace(out, expCreateOutPrefix, "", -1)), " ")
|
||||||
|
require.Len(t, parts, 2)
|
||||||
|
msigRobustAddr := parts[1]
|
||||||
|
fmt.Println("msig robust address:", msigRobustAddr)
|
||||||
|
|
||||||
|
// Propose to add a new address to the msig
|
||||||
|
// msig add-propose --from=<addr> <msig> <addr>
|
||||||
|
paramFrom := fmt.Sprintf("--from=%s", walletAddrs[0])
|
||||||
|
cmd = []string{
|
||||||
|
"msig", "add-propose",
|
||||||
|
paramFrom,
|
||||||
|
msigRobustAddr,
|
||||||
|
walletAddrs[3].String(),
|
||||||
|
}
|
||||||
|
out = clientCLI.runCmd(cmd)
|
||||||
|
fmt.Println(out)
|
||||||
|
|
||||||
|
// msig inspect <msig>
|
||||||
|
cmd = []string{"msig", "inspect", "--vesting", "--decode-params", msigRobustAddr}
|
||||||
|
out = clientCLI.runCmd(cmd)
|
||||||
|
fmt.Println(out)
|
||||||
|
|
||||||
|
// Expect correct balance
|
||||||
|
require.Regexp(t, regexp.MustCompile("Balance: 0.000000000000001 FIL"), out)
|
||||||
|
// Expect 1 transaction
|
||||||
|
require.Regexp(t, regexp.MustCompile(`Transactions:\s*1`), out)
|
||||||
|
// Expect transaction to be "AddSigner"
|
||||||
|
require.Regexp(t, regexp.MustCompile(`AddSigner`), out)
|
||||||
|
|
||||||
|
// Approve adding the new address
|
||||||
|
// msig add-approve --from=<addr> <msig> <addr> 0 <addr> false
|
||||||
|
txnID := "0"
|
||||||
|
paramFrom = fmt.Sprintf("--from=%s", walletAddrs[1])
|
||||||
|
cmd = []string{
|
||||||
|
"msig", "add-approve",
|
||||||
|
paramFrom,
|
||||||
|
msigRobustAddr,
|
||||||
|
walletAddrs[0].String(),
|
||||||
|
txnID,
|
||||||
|
walletAddrs[3].String(),
|
||||||
|
"false",
|
||||||
|
}
|
||||||
|
out = clientCLI.runCmd(cmd)
|
||||||
|
fmt.Println(out)
|
||||||
|
}
|
@ -26,9 +26,11 @@ var (
|
|||||||
// gatewayDepsAPI defines the API methods that the GatewayAPI depends on
|
// gatewayDepsAPI defines the API methods that the GatewayAPI depends on
|
||||||
// (to make it easy to mock for tests)
|
// (to make it easy to mock for tests)
|
||||||
type gatewayDepsAPI interface {
|
type gatewayDepsAPI interface {
|
||||||
|
ChainHasObj(context.Context, cid.Cid) (bool, error)
|
||||||
ChainHead(ctx context.Context) (*types.TipSet, error)
|
ChainHead(ctx context.Context) (*types.TipSet, error)
|
||||||
ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)
|
ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)
|
||||||
ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error)
|
ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error)
|
||||||
|
ChainReadObj(context.Context, cid.Cid) ([]byte, error)
|
||||||
GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error)
|
GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error)
|
||||||
MpoolPushUntrusted(ctx context.Context, sm *types.SignedMessage) (cid.Cid, error)
|
MpoolPushUntrusted(ctx context.Context, sm *types.SignedMessage) (cid.Cid, error)
|
||||||
MsigGetAvailableBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (types.BigInt, error)
|
MsigGetAvailableBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (types.BigInt, error)
|
||||||
@ -40,7 +42,18 @@ type gatewayDepsAPI interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GatewayAPI struct {
|
type GatewayAPI struct {
|
||||||
api gatewayDepsAPI
|
api gatewayDepsAPI
|
||||||
|
lookbackCap time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGatewayAPI creates a new GatewayAPI with the default lookback cap
|
||||||
|
func NewGatewayAPI(api gatewayDepsAPI) *GatewayAPI {
|
||||||
|
return newGatewayAPI(api, LookbackCap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// used by the tests
|
||||||
|
func newGatewayAPI(api gatewayDepsAPI, lookbackCap time.Duration) *GatewayAPI {
|
||||||
|
return &GatewayAPI{api: api, lookbackCap: lookbackCap}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *GatewayAPI) checkTipsetKey(ctx context.Context, tsk types.TipSetKey) error {
|
func (a *GatewayAPI) checkTipsetKey(ctx context.Context, tsk types.TipSetKey) error {
|
||||||
@ -76,13 +89,17 @@ func (a *GatewayAPI) checkTipsetHeight(ts *types.TipSet, h abi.ChainEpoch) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *GatewayAPI) checkTimestamp(at time.Time) error {
|
func (a *GatewayAPI) checkTimestamp(at time.Time) error {
|
||||||
if time.Since(at) > LookbackCap {
|
if time.Since(at) > a.lookbackCap {
|
||||||
return ErrLookbackTooLong
|
return ErrLookbackTooLong
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *GatewayAPI) ChainHasObj(ctx context.Context, c cid.Cid) (bool, error) {
|
||||||
|
return a.api.ChainHasObj(ctx, c)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *GatewayAPI) ChainHead(ctx context.Context) (*types.TipSet, error) {
|
func (a *GatewayAPI) ChainHead(ctx context.Context) (*types.TipSet, error) {
|
||||||
// TODO: cache and invalidate cache when timestamp is up (or have internal ChainNotify)
|
// TODO: cache and invalidate cache when timestamp is up (or have internal ChainNotify)
|
||||||
|
|
||||||
@ -112,6 +129,10 @@ func (a *GatewayAPI) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoc
|
|||||||
return a.api.ChainGetTipSetByHeight(ctx, h, tsk)
|
return a.api.ChainGetTipSetByHeight(ctx, h, tsk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *GatewayAPI) ChainReadObj(ctx context.Context, c cid.Cid) ([]byte, error) {
|
||||||
|
return a.api.ChainReadObj(ctx, c)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *GatewayAPI) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) {
|
func (a *GatewayAPI) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) {
|
||||||
if err := a.checkTipsetKey(ctx, tsk); err != nil {
|
if err := a.checkTipsetKey(ctx, tsk); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -88,7 +88,7 @@ func TestGatewayAPIChainGetTipSetByHeight(t *testing.T) {
|
|||||||
tt := tt
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
mock := &mockGatewayDepsAPI{}
|
mock := &mockGatewayDepsAPI{}
|
||||||
a := &GatewayAPI{api: mock}
|
a := NewGatewayAPI(mock)
|
||||||
|
|
||||||
// Create tipsets from genesis up to tskh and return the highest
|
// Create tipsets from genesis up to tskh and return the highest
|
||||||
ts := mock.createTipSets(tt.args.tskh, tt.args.genesisTS)
|
ts := mock.createTipSets(tt.args.tskh, tt.args.genesisTS)
|
||||||
@ -109,6 +109,10 @@ type mockGatewayDepsAPI struct {
|
|||||||
tipsets []*types.TipSet
|
tipsets []*types.TipSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockGatewayDepsAPI) ChainHasObj(context.Context, cid.Cid) (bool, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockGatewayDepsAPI) ChainHead(ctx context.Context) (*types.TipSet, error) {
|
func (m *mockGatewayDepsAPI) ChainHead(ctx context.Context) (*types.TipSet, error) {
|
||||||
m.lk.RLock()
|
m.lk.RLock()
|
||||||
defer m.lk.RUnlock()
|
defer m.lk.RUnlock()
|
||||||
@ -158,6 +162,10 @@ func (m *mockGatewayDepsAPI) ChainGetTipSetByHeight(ctx context.Context, h abi.C
|
|||||||
return m.tipsets[h], nil
|
return m.tipsets[h], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockGatewayDepsAPI) ChainReadObj(ctx context.Context, c cid.Cid) ([]byte, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockGatewayDepsAPI) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) {
|
func (m *mockGatewayDepsAPI) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,14 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/cli"
|
||||||
|
clitest "github.com/filecoin-project/lotus/cli/test"
|
||||||
|
|
||||||
init0 "github.com/filecoin-project/specs-actors/actors/builtin/init"
|
init0 "github.com/filecoin-project/specs-actors/actors/builtin/init"
|
||||||
"github.com/filecoin-project/specs-actors/actors/builtin/multisig"
|
"github.com/filecoin-project/specs-actors/actors/builtin/multisig"
|
||||||
|
|
||||||
@ -26,20 +30,22 @@ import (
|
|||||||
builder "github.com/filecoin-project/lotus/node/test"
|
builder "github.com/filecoin-project/lotus/node/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const maxLookbackCap = time.Duration(math.MaxInt64)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1)
|
policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1)
|
||||||
policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048))
|
policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048))
|
||||||
policy.SetMinVerifiedDealSize(abi.NewStoragePower(256))
|
policy.SetMinVerifiedDealSize(abi.NewStoragePower(256))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestEndToEnd tests that API calls can be made on a lite node that is
|
// TestEndToEndWalletMsig tests that wallet and msig API calls can be made
|
||||||
// connected through a gateway to a full API node
|
// on a lite node that is connected through a gateway to a full API node
|
||||||
func TestEndToEnd(t *testing.T) {
|
func TestEndToEndWalletMsig(t *testing.T) {
|
||||||
_ = os.Setenv("BELLMAN_NO_GPU", "1")
|
_ = os.Setenv("BELLMAN_NO_GPU", "1")
|
||||||
|
|
||||||
blocktime := 5 * time.Millisecond
|
blocktime := 5 * time.Millisecond
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
full, lite, closer := startNodes(ctx, t, blocktime)
|
full, lite, closer := startNodes(ctx, t, blocktime, maxLookbackCap)
|
||||||
defer closer()
|
defer closer()
|
||||||
|
|
||||||
// The full node starts with a wallet
|
// The full node starts with a wallet
|
||||||
@ -56,11 +62,11 @@ func TestEndToEnd(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Send some funds from the full node to the lite node
|
// Send some funds from the full node to the lite node
|
||||||
err = sendFunds(ctx, t, full, fullWalletAddr, liteWalletAddr, types.NewInt(1e18))
|
err = sendFunds(ctx, full, fullWalletAddr, liteWalletAddr, types.NewInt(1e18))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Send some funds from the lite node back to the full node
|
// Send some funds from the lite node back to the full node
|
||||||
err = sendFunds(ctx, t, lite, liteWalletAddr, fullWalletAddr, types.NewInt(100))
|
err = sendFunds(ctx, lite, liteWalletAddr, fullWalletAddr, types.NewInt(100))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Sign some data with the lite node wallet address
|
// Sign some data with the lite node wallet address
|
||||||
@ -81,7 +87,7 @@ func TestEndToEnd(t *testing.T) {
|
|||||||
|
|
||||||
walletAddrs = append(walletAddrs, addr)
|
walletAddrs = append(walletAddrs, addr)
|
||||||
|
|
||||||
err = sendFunds(ctx, t, lite, liteWalletAddr, addr, types.NewInt(1e15))
|
err = sendFunds(ctx, lite, liteWalletAddr, addr, types.NewInt(1e15))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +141,33 @@ func TestEndToEnd(t *testing.T) {
|
|||||||
require.True(t, approveReturn.Applied)
|
require.True(t, approveReturn.Applied)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendFunds(ctx context.Context, t *testing.T, fromNode test.TestNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error {
|
// TestEndToEndMsigCLI tests that msig CLI calls can be made
|
||||||
|
// on a lite node that is connected through a gateway to a full API node
|
||||||
|
func TestEndToEndMsigCLI(t *testing.T) {
|
||||||
|
_ = os.Setenv("BELLMAN_NO_GPU", "1")
|
||||||
|
clitest.QuietMiningLogs()
|
||||||
|
|
||||||
|
blocktime := 5 * time.Millisecond
|
||||||
|
ctx := context.Background()
|
||||||
|
full, lite, closer := startNodes(ctx, t, blocktime, maxLookbackCap)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
// The full node starts with a wallet
|
||||||
|
fullWalletAddr, err := full.WalletDefaultAddress(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Create a wallet on the lite node
|
||||||
|
liteWalletAddr, err := lite.WalletNew(ctx, types.KTSecp256k1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Send some funds from the full node to the lite node
|
||||||
|
err = sendFunds(ctx, full, fullWalletAddr, liteWalletAddr, types.NewInt(1e18))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
clitest.RunMultisigTest(t, cli.Commands, lite)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendFunds(ctx context.Context, fromNode test.TestNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error {
|
||||||
msg := &types.Message{
|
msg := &types.Message{
|
||||||
From: fromAddr,
|
From: fromAddr,
|
||||||
To: toAddr,
|
To: toAddr,
|
||||||
@ -158,7 +190,7 @@ func sendFunds(ctx context.Context, t *testing.T, fromNode test.TestNode, fromAd
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startNodes(ctx context.Context, t *testing.T, blocktime time.Duration) (test.TestNode, test.TestNode, jsonrpc.ClientCloser) {
|
func startNodes(ctx context.Context, t *testing.T, blocktime time.Duration, lookbackCap time.Duration) (test.TestNode, test.TestNode, jsonrpc.ClientCloser) {
|
||||||
var closer jsonrpc.ClientCloser
|
var closer jsonrpc.ClientCloser
|
||||||
|
|
||||||
// Create one miner and two full nodes.
|
// Create one miner and two full nodes.
|
||||||
@ -175,7 +207,7 @@ func startNodes(ctx context.Context, t *testing.T, blocktime time.Duration) (tes
|
|||||||
fullNode := nodes[0]
|
fullNode := nodes[0]
|
||||||
|
|
||||||
// Create a gateway server in front of the full node
|
// Create a gateway server in front of the full node
|
||||||
_, addr, err := builder.CreateRPCServer(&GatewayAPI{api: fullNode})
|
_, addr, err := builder.CreateRPCServer(newGatewayAPI(fullNode, lookbackCap))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Create a gateway client API that connects to the gateway server
|
// Create a gateway client API that connects to the gateway server
|
||||||
|
@ -76,7 +76,7 @@ var runCmd = &cli.Command{
|
|||||||
log.Info("Setting up API endpoint at " + address)
|
log.Info("Setting up API endpoint at " + address)
|
||||||
|
|
||||||
rpcServer := jsonrpc.NewServer()
|
rpcServer := jsonrpc.NewServer()
|
||||||
rpcServer.Register("Filecoin", &GatewayAPI{api: api})
|
rpcServer.Register("Filecoin", NewGatewayAPI(api))
|
||||||
|
|
||||||
mux.Handle("/rpc/v0", rpcServer)
|
mux.Handle("/rpc/v0", rpcServer)
|
||||||
mux.PathPrefix("/").Handler(http.DefaultServeMux)
|
mux.PathPrefix("/").Handler(http.DefaultServeMux)
|
||||||
|
@ -40,9 +40,11 @@ import (
|
|||||||
var log = logging.Logger("fullnode")
|
var log = logging.Logger("fullnode")
|
||||||
|
|
||||||
type ChainModuleAPI interface {
|
type ChainModuleAPI interface {
|
||||||
|
ChainHasObj(context.Context, cid.Cid) (bool, error)
|
||||||
ChainHead(context.Context) (*types.TipSet, error)
|
ChainHead(context.Context) (*types.TipSet, error)
|
||||||
ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)
|
ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)
|
||||||
ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error)
|
ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error)
|
||||||
|
ChainReadObj(context.Context, cid.Cid) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChainModule provides a default implementation of ChainModuleAPI.
|
// ChainModule provides a default implementation of ChainModuleAPI.
|
||||||
@ -206,8 +208,8 @@ func (m *ChainModule) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpo
|
|||||||
return m.Chain.GetTipsetByHeight(ctx, h, ts, true)
|
return m.Chain.GetTipsetByHeight(ctx, h, ts, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ChainAPI) ChainReadObj(ctx context.Context, obj cid.Cid) ([]byte, error) {
|
func (m *ChainModule) ChainReadObj(ctx context.Context, obj cid.Cid) ([]byte, error) {
|
||||||
blk, err := a.Chain.Blockstore().Get(obj)
|
blk, err := m.Chain.Blockstore().Get(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("blockstore get: %w", err)
|
return nil, xerrors.Errorf("blockstore get: %w", err)
|
||||||
}
|
}
|
||||||
@ -219,8 +221,8 @@ func (a *ChainAPI) ChainDeleteObj(ctx context.Context, obj cid.Cid) error {
|
|||||||
return a.Chain.Blockstore().DeleteBlock(obj)
|
return a.Chain.Blockstore().DeleteBlock(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ChainAPI) ChainHasObj(ctx context.Context, obj cid.Cid) (bool, error) {
|
func (m *ChainModule) ChainHasObj(ctx context.Context, obj cid.Cid) (bool, error) {
|
||||||
return a.Chain.Blockstore().Has(obj)
|
return m.Chain.Blockstore().Has(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ChainAPI) ChainStatObj(ctx context.Context, obj cid.Cid, base cid.Cid) (api.ObjStat, error) {
|
func (a *ChainAPI) ChainStatObj(ctx context.Context, obj cid.Cid, base cid.Cid) (api.ObjStat, error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user