add payment channel CLI and finish up commands

This commit is contained in:
whyrusleeping 2019-08-12 21:27:54 -07:00
parent c986267a2e
commit 12acee5242
9 changed files with 466 additions and 33 deletions

View File

@ -100,12 +100,13 @@ type FullNode interface {
PaychCreate(ctx context.Context, from, to address.Address, amt types.BigInt) (address.Address, error)
PaychList(context.Context) ([]address.Address, error)
PaychStatus(context.Context, address.Address) (*PaychStatus, error)
PaychClose(context.Context, address.Address) error
PaychClose(context.Context, address.Address) (cid.Cid, error)
PaychVoucherCheckValid(context.Context, address.Address, *types.SignedVoucher) error
PaychVoucherCheckSpendable(context.Context, address.Address, *types.SignedVoucher, []byte, []byte) (bool, error)
PaychVoucherCreate(context.Context, address.Address, types.BigInt, uint64) (*types.SignedVoucher, error)
PaychVoucherAdd(context.Context, address.Address, *types.SignedVoucher) error
PaychVoucherList(context.Context, address.Address) ([]*types.SignedVoucher, error)
PaychVoucherSubmit(context.Context, address.Address, *types.SignedVoucher) (cid.Cid, error)
}
// Full API is a low-level interface to the Filecoin network storage miner node

View File

@ -76,13 +76,14 @@ type FullNodeStruct struct {
PaychCreate func(ctx context.Context, from, to address.Address, amt types.BigInt) (address.Address, error) `perm:"sign"`
PaychList func(context.Context) ([]address.Address, error) `perm:"read"`
PaychStatus func(context.Context, address.Address) (*PaychStatus, error) `perm:"read"`
PaychClose func(context.Context, address.Address) error `perm:"sign"`
PaychClose func(context.Context, address.Address) (cid.Cid, error) `perm:"sign"`
PaychVoucherCheck func(context.Context, *types.SignedVoucher) error `perm:"read"`
PaychVoucherCheckValid func(context.Context, address.Address, *types.SignedVoucher) error `perm:"read"`
PaychVoucherCheckSpendable func(context.Context, address.Address, *types.SignedVoucher, []byte, []byte) (bool, error) `perm:"read"`
PaychVoucherAdd func(context.Context, address.Address, *types.SignedVoucher) error `perm:"write"`
PaychVoucherCreate func(context.Context, address.Address, types.BigInt, uint64) (*types.SignedVoucher, error) `perm:"sign"`
PaychVoucherList func(context.Context, address.Address) ([]*types.SignedVoucher, error) `perm:"write"`
PaychVoucherSubmit func(context.Context, address.Address, *types.SignedVoucher) (cid.Cid, error) `perm:"sign"`
}
}
@ -282,10 +283,14 @@ func (c *FullNodeStruct) PaychVoucherList(ctx context.Context, pch address.Addre
return c.Internal.PaychVoucherList(ctx, pch)
}
func (c *FullNodeStruct) PaychClose(ctx context.Context, a address.Address) error {
func (c *FullNodeStruct) PaychClose(ctx context.Context, a address.Address) (cid.Cid, error) {
return c.Internal.PaychClose(ctx, a)
}
func (c *FullNodeStruct) PaychVoucherSubmit(ctx context.Context, ch address.Address, sv *types.SignedVoucher) (cid.Cid, error) {
return c.Internal.PaychVoucherSubmit(ctx, ch, sv)
}
func (c *StorageMinerStruct) ActorAddresses(ctx context.Context) ([]address.Address, error) {
return c.Internal.ActorAddresses(ctx)
}

View File

@ -1,6 +1,8 @@
package types
import (
"encoding/base64"
"github.com/filecoin-project/go-lotus/chain/address"
cbor "github.com/ipfs/go-ipld-cbor"
)
@ -31,6 +33,29 @@ func (sv *SignedVoucher) SigningBytes() ([]byte, error) {
return cbor.DumpObject(osv)
}
func (sv *SignedVoucher) EncodedString() (string, error) {
data, err := cbor.DumpObject(sv)
if err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(data), nil
}
func DecodeSignedVoucher(s string) (*SignedVoucher, error) {
data, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
return nil, err
}
var sv SignedVoucher
if err := cbor.DecodeInto(data, &sv); err != nil {
return nil, err
}
return &sv, nil
}
type Merge struct {
Lane uint64
Nonce uint64

View File

@ -1,4 +1,4 @@
package chain
package vm
import (
"context"
@ -6,7 +6,6 @@ import (
"github.com/filecoin-project/go-lotus/chain/actors"
"github.com/filecoin-project/go-lotus/chain/store"
"github.com/filecoin-project/go-lotus/chain/types"
"github.com/filecoin-project/go-lotus/chain/vm"
"golang.org/x/xerrors"
)
@ -19,7 +18,7 @@ func Call(ctx context.Context, cs *store.ChainStore, msg *types.Message, ts *typ
return nil, err
}
vmi, err := vm.NewVM(state, ts.Height(), ts.Blocks()[0].Miner, cs)
vmi, err := NewVM(state, ts.Height(), ts.Blocks()[0].Miner, cs)
if err != nil {
return nil, xerrors.Errorf("failed to set up vm: %w", err)
}
@ -40,10 +39,21 @@ func Call(ctx context.Context, cs *store.ChainStore, msg *types.Message, ts *typ
}
}
fromActor, err := vmi.cstate.GetActor(msg.From)
if err != nil {
return nil, err
}
msg.Nonce = fromActor.Nonce
// TODO: maybe just use the invoker directly?
ret, err := vmi.ApplyMessage(ctx, msg)
if err != nil {
return nil, xerrors.Errorf("apply message failed: %w", err)
}
if ret.ActorErr != nil {
log.Warnf("chain call failed: %s", ret.ActorErr)
}
return &ret.MessageReceipt, err
return &ret.MessageReceipt, nil
}

View File

@ -116,12 +116,13 @@ func ReqContext(cctx *cli.Context) context.Context {
var Commands = []*cli.Command{
chainCmd,
clientCmd,
createMinerCmd,
minerCmd,
mpoolCmd,
netCmd,
paychCmd,
sendCmd,
stateCmd,
versionCmd,
walletCmd,
createMinerCmd,
stateCmd,
sendCmd,
}

342
cli/paych.go Normal file
View File

@ -0,0 +1,342 @@
package cli
import (
"fmt"
"github.com/filecoin-project/go-lotus/chain/address"
types "github.com/filecoin-project/go-lotus/chain/types"
"gopkg.in/urfave/cli.v2"
)
var paychCmd = &cli.Command{
Name: "paych",
Usage: "Manage payment channels",
Subcommands: []*cli.Command{
paychCreateCmd,
paychListCmd,
paychVoucherCmd,
},
}
var paychCreateCmd = &cli.Command{
Name: "create",
Usage: "Create a new payment channel",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 3 {
return fmt.Errorf("must pass three arguments: <from> <to> <amount>")
}
from, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return fmt.Errorf("failed to parse from address: %s", err)
}
to, err := address.NewFromString(cctx.Args().Get(1))
if err != nil {
return fmt.Errorf("failed to parse to address: %s", err)
}
amt, err := types.BigFromString(cctx.Args().Get(2))
if err != nil {
return fmt.Errorf("parsing amount failed: %s", err)
}
api, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
ctx := ReqContext(cctx)
addr, err := api.PaychCreate(ctx, from, to, amt)
if err != nil {
return err
}
fmt.Println(addr.String())
return nil
},
}
var paychListCmd = &cli.Command{
Name: "list",
Usage: "List all locally registered payment channels",
Action: func(cctx *cli.Context) error {
api, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
ctx := ReqContext(cctx)
chs, err := api.PaychList(ctx)
if err != nil {
return err
}
for _, v := range chs {
fmt.Println(v.String())
}
return nil
},
}
var paychVoucherCmd = &cli.Command{
Name: "voucher",
Usage: "Interact with payment channel vouchers",
Subcommands: []*cli.Command{
paychVoucherCreateCmd,
paychVoucherCheckCmd,
paychVoucherAddCmd,
paychVoucherListCmd,
paychVoucherBestSpendableCmd,
paychVoucherSubmitCmd,
},
}
var paychVoucherCreateCmd = &cli.Command{
Name: "create",
Usage: "Create a signed payment channel voucher",
Flags: []cli.Flag{
&cli.IntFlag{
Name: "lane",
Value: 0,
Usage: "specify payment channel lane to use",
},
},
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 2 {
return fmt.Errorf("must pass two arguments: <channel> <amount>")
}
ch, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
amt, err := types.BigFromString(cctx.Args().Get(1))
if err != nil {
return err
}
lane := cctx.Int("lane")
api, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
ctx := ReqContext(cctx)
sv, err := api.PaychVoucherCreate(ctx, ch, amt, uint64(lane))
if err != nil {
return err
}
enc, err := sv.EncodedString()
if err != nil {
return err
}
fmt.Println(enc)
return nil
},
}
var paychVoucherCheckCmd = &cli.Command{
Name: "check",
Usage: "Check validity of payment channel voucher",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 2 {
return fmt.Errorf("must pass payment channel address and voucher to validate")
}
ch, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
sv, err := types.DecodeSignedVoucher(cctx.Args().Get(1))
if err != nil {
return err
}
api, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
ctx := ReqContext(cctx)
if err := api.PaychVoucherCheckValid(ctx, ch, sv); err != nil {
return err
}
fmt.Println("voucher is valid")
return nil
},
}
var paychVoucherAddCmd = &cli.Command{
Name: "add",
Usage: "Add payment channel voucher to local datastore",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 2 {
return fmt.Errorf("must pass payment channel address and voucher")
}
ch, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
sv, err := types.DecodeSignedVoucher(cctx.Args().Get(1))
if err != nil {
return err
}
api, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
ctx := ReqContext(cctx)
if err := api.PaychVoucherAdd(ctx, ch, sv); err != nil {
return err
}
return nil
},
}
var paychVoucherListCmd = &cli.Command{
Name: "list",
Usage: "List stored vouchers for a given payment channel",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 1 {
return fmt.Errorf("must pass payment channel address")
}
ch, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
api, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
ctx := ReqContext(cctx)
vouchers, err := api.PaychVoucherList(ctx, ch)
if err != nil {
return err
}
for _, v := range vouchers {
fmt.Printf("Lane %d, Nonce %d: %s\n", v.Lane, v.Nonce, v.Amount.String())
}
return nil
},
}
var paychVoucherBestSpendableCmd = &cli.Command{
Name: "best-spendable",
Usage: "Print voucher with highest value that is currently spendable",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 1 {
return fmt.Errorf("must pass payment channel address")
}
ch, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
api, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
ctx := ReqContext(cctx)
vouchers, err := api.PaychVoucherList(ctx, ch)
if err != nil {
return err
}
var best *types.SignedVoucher
for _, v := range vouchers {
spendable, err := api.PaychVoucherCheckSpendable(ctx, ch, v, nil, nil)
if err != nil {
return err
}
if spendable {
if best == nil || types.BigCmp(v.Amount, best.Amount) > 0 {
best = v
}
}
}
if best == nil {
return fmt.Errorf("No spendable vouchers for that channel")
}
enc, err := best.EncodedString()
if err != nil {
return err
}
fmt.Println(enc)
fmt.Printf("Amount: %s\n", best.Amount)
return nil
},
}
var paychVoucherSubmitCmd = &cli.Command{
Name: "submit",
Usage: "Submit voucher to chain to update payment channel state",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 2 {
return fmt.Errorf("must pass payment channel address and voucher")
}
ch, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
sv, err := types.DecodeSignedVoucher(cctx.Args().Get(1))
if err != nil {
return err
}
api, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
ctx := ReqContext(cctx)
mcid, err := api.PaychVoucherSubmit(ctx, ch, sv)
if err != nil {
return err
}
mwait, err := api.ChainWaitMsg(ctx, mcid)
if err != nil {
return err
}
if mwait.Receipt.ExitCode != 0 {
return fmt.Errorf("message execution failed (exit code %d)", mwait.Receipt.ExitCode)
}
fmt.Println("channel updated succesfully")
return nil
},
}

View File

@ -16,6 +16,7 @@ import (
"github.com/filecoin-project/go-lotus/chain/state"
"github.com/filecoin-project/go-lotus/chain/store"
"github.com/filecoin-project/go-lotus/chain/types"
"github.com/filecoin-project/go-lotus/chain/vm"
"github.com/filecoin-project/go-lotus/chain/wallet"
"github.com/filecoin-project/go-lotus/miner"
"github.com/filecoin-project/go-lotus/node/client"
@ -157,7 +158,7 @@ func (a *FullNodeAPI) ChainGetBlockReceipts(ctx context.Context, bcid cid.Cid) (
}
func (a *FullNodeAPI) ChainCall(ctx context.Context, msg *types.Message, ts *types.TipSet) (*types.MessageReceipt, error) {
return chain.Call(ctx, a.Chain, msg, ts)
return vm.Call(ctx, a.Chain, msg, ts)
}
func (a *FullNodeAPI) stateForTs(ts *types.TipSet) (*state.StateTree, error) {
@ -479,7 +480,10 @@ func (a *FullNodeAPI) PaychCreate(ctx context.Context, from, to address.Address,
return address.Undef, err
}
// TODO: track this somewhere?
if err := a.PaychMgr.TrackOutboundChannel(ctx, paychaddr); err != nil {
return address.Undef, err
}
return paychaddr, nil
}
@ -491,15 +495,15 @@ func (a *FullNodeAPI) PaychStatus(ctx context.Context, pch address.Address) (*ap
panic("nyi")
}
func (a *FullNodeAPI) PaychClose(ctx context.Context, addr address.Address) error {
func (a *FullNodeAPI) PaychClose(ctx context.Context, addr address.Address) (cid.Cid, error) {
ci, err := a.PaychMgr.GetChannelInfo(addr)
if err != nil {
return err
return cid.Undef, err
}
nonce, err := a.MpoolGetNonce(ctx, ci.ControlAddr)
if err != nil {
return err
return cid.Undef, err
}
msg := &types.Message{
@ -513,23 +517,16 @@ func (a *FullNodeAPI) PaychClose(ctx context.Context, addr address.Address) erro
GasPrice: types.NewInt(0),
}
b, err := msg.Serialize()
smsg, err := a.WalletSignMessage(ctx, ci.ControlAddr, msg)
if err != nil {
return err
return cid.Undef, err
}
sig, err := a.WalletSign(ctx, ci.ControlAddr, b)
if err != nil {
return err
if err := a.MpoolPush(ctx, smsg); err != nil {
return cid.Undef, err
}
smsg := &types.SignedMessage{
Message: *msg,
Signature: *sig,
}
// TODO: should this block and wait?
return a.MpoolPush(ctx, smsg)
return smsg.Cid(), nil
}
func (a *FullNodeAPI) PaychVoucherCheckValid(ctx context.Context, ch address.Address, sv *types.SignedVoucher) error {
@ -591,4 +588,50 @@ func (a *FullNodeAPI) PaychVoucherList(ctx context.Context, pch address.Address)
return a.PaychMgr.ListVouchers(ctx, pch)
}
func (a *FullNodeAPI) PaychVoucherSubmit(ctx context.Context, ch address.Address, sv *types.SignedVoucher) (cid.Cid, error) {
ci, err := a.PaychMgr.GetChannelInfo(ch)
if err != nil {
return cid.Undef, err
}
nonce, err := a.MpoolGetNonce(ctx, ci.ControlAddr)
if err != nil {
return cid.Undef, err
}
if sv.Extra != nil || len(sv.SecretPreimage) > 0 {
return cid.Undef, fmt.Errorf("cant handle more advanced payment channel stuff yet")
}
enc, err := actors.SerializeParams(&actors.PCAUpdateChannelStateParams{
Sv: *sv,
})
if err != nil {
return cid.Undef, err
}
msg := &types.Message{
From: ci.ControlAddr,
To: ch,
Value: types.NewInt(0),
Nonce: nonce,
Method: actors.PCAMethods.UpdateChannelState,
Params: enc,
GasLimit: types.NewInt(100000),
GasPrice: types.NewInt(0),
}
smsg, err := a.WalletSignMessage(ctx, ci.ControlAddr, msg)
if err != nil {
return cid.Undef, err
}
if err := a.MpoolPush(ctx, smsg); err != nil {
return cid.Undef, err
}
// TODO: should we wait for it...?
return smsg.Cid(), nil
}
var _ api.FullNode = &FullNodeAPI{}

View File

@ -4,12 +4,12 @@ import (
"context"
"fmt"
"github.com/filecoin-project/go-lotus/chain"
"github.com/filecoin-project/go-lotus/chain/actors"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/state"
"github.com/filecoin-project/go-lotus/chain/store"
"github.com/filecoin-project/go-lotus/chain/types"
"github.com/filecoin-project/go-lotus/chain/vm"
hamt "github.com/ipfs/go-hamt-ipld"
)
@ -123,7 +123,7 @@ func (pm *Manager) CheckVoucherSpendable(ctx context.Context, ch address.Address
return false, err
}
ret, err := chain.Call(ctx, pm.chain, &types.Message{
ret, err := vm.Call(ctx, pm.chain, &types.Message{
From: owner,
To: ch,
Method: actors.PCAMethods.UpdateChannelState,
@ -166,7 +166,7 @@ func (pm *Manager) loadPaychState(ctx context.Context, ch address.Address) (*typ
}
func (pm *Manager) getPaychOwner(ctx context.Context, ch address.Address) (address.Address, error) {
ret, err := chain.Call(ctx, pm.chain, &types.Message{
ret, err := vm.Call(ctx, pm.chain, &types.Message{
From: ch,
To: ch,
Method: actors.PCAMethods.GetOwner,
@ -211,5 +211,5 @@ func (pm *Manager) NextNonceForLane(ctx context.Context, ch address.Address, lan
}
}
return maxnonce, nil
return maxnonce + 1, nil
}

View File

@ -2,6 +2,7 @@ package paych
import (
"fmt"
"strings"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/types"
@ -10,6 +11,7 @@ import (
dsq "github.com/ipfs/go-datastore/query"
cbor "github.com/ipfs/go-ipld-cbor"
"golang.org/x/xerrors"
)
func init() {
@ -95,11 +97,15 @@ func (ps *Store) ListChannels() ([]address.Address, error) {
break
}
addr, err := address.NewFromString(res.Key)
if err != nil {
if res.Error != nil {
return nil, err
}
addr, err := address.NewFromString(strings.TrimPrefix(res.Key, "/"))
if err != nil {
return nil, xerrors.Errorf("failed reading paych key (%q) from datastore: %w", res.Key, err)
}
out = append(out, addr)
}