Merge pull request #132 from filecoin-project/feat/paych-manager
Implement payment channel manager
This commit is contained in:
commit
e050d56594
@ -100,11 +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
|
||||
PaychVoucherCheck(context.Context, *types.SignedVoucher) 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, *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
|
||||
|
@ -73,14 +73,17 @@ type FullNodeStruct struct {
|
||||
StateMinerSectors func(context.Context, address.Address) ([]*SectorInfo, error) `perm:"read"`
|
||||
StateMinerProvingSet func(context.Context, address.Address) ([]*SectorInfo, error) `perm:"read"`
|
||||
|
||||
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"`
|
||||
PaychVoucherCheck func(context.Context, *types.SignedVoucher) error `perm:"read"`
|
||||
PaychVoucherAdd func(context.Context, *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"`
|
||||
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) (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"`
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,12 +263,16 @@ func (c *FullNodeStruct) PaychStatus(ctx context.Context, pch address.Address) (
|
||||
return c.Internal.PaychStatus(ctx, pch)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) PaychVoucherCheck(ctx context.Context, sv *types.SignedVoucher) error {
|
||||
return c.Internal.PaychVoucherCheck(ctx, sv)
|
||||
func (c *FullNodeStruct) PaychVoucherCheckValid(ctx context.Context, addr address.Address, sv *types.SignedVoucher) error {
|
||||
return c.Internal.PaychVoucherCheckValid(ctx, addr, sv)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) PaychVoucherAdd(ctx context.Context, sv *types.SignedVoucher) error {
|
||||
return c.Internal.PaychVoucherAdd(ctx, sv)
|
||||
func (c *FullNodeStruct) PaychVoucherCheckSpendable(ctx context.Context, addr address.Address, sv *types.SignedVoucher, secret []byte, proof []byte) (bool, error) {
|
||||
return c.Internal.PaychVoucherCheckSpendable(ctx, addr, sv, secret, proof)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) PaychVoucherAdd(ctx context.Context, addr address.Address, sv *types.SignedVoucher) error {
|
||||
return c.Internal.PaychVoucherAdd(ctx, addr, sv)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*types.SignedVoucher, error) {
|
||||
@ -276,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)
|
||||
}
|
||||
|
@ -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
|
||||
|
59
chain/vm/call.go
Normal file
59
chain/vm/call.go
Normal file
@ -0,0 +1,59 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/filecoin-project/go-lotus/chain/actors"
|
||||
"github.com/filecoin-project/go-lotus/chain/store"
|
||||
"github.com/filecoin-project/go-lotus/chain/types"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func Call(ctx context.Context, cs *store.ChainStore, msg *types.Message, ts *types.TipSet) (*types.MessageReceipt, error) {
|
||||
if ts == nil {
|
||||
ts = cs.GetHeaviestTipSet()
|
||||
}
|
||||
state, err := cs.TipSetState(ts.Cids())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if msg.GasLimit == types.EmptyInt {
|
||||
msg.GasLimit = types.NewInt(10000000000)
|
||||
}
|
||||
if msg.GasPrice == types.EmptyInt {
|
||||
msg.GasPrice = types.NewInt(0)
|
||||
}
|
||||
if msg.Value == types.EmptyInt {
|
||||
msg.Value = types.NewInt(0)
|
||||
}
|
||||
if msg.Params == nil {
|
||||
msg.Params, err = actors.SerializeParams(struct{}{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
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, nil
|
||||
}
|
@ -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
342
cli/paych.go
Normal 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
|
||||
},
|
||||
}
|
1
go.mod
1
go.mod
@ -61,6 +61,7 @@ require (
|
||||
github.com/multiformats/go-multihash v0.0.6
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a
|
||||
github.com/prometheus/common v0.2.0
|
||||
github.com/smartystreets/assertions v1.0.1 // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect
|
||||
github.com/stretchr/testify v1.3.0
|
||||
|
3
go.sum
3
go.sum
@ -19,7 +19,9 @@ github.com/Stebalien/go-bitfield v0.0.0-20180330043415-076a62f9ce6e/go.mod h1:3o
|
||||
github.com/Stebalien/go-bitfield v0.0.1 h1:X3kbSSPUaJK60wV2hjOPZwmpljr6VGCqdq4cBLhbQBo=
|
||||
github.com/Stebalien/go-bitfield v0.0.1/go.mod h1:GNjFpasyUVkHMsfEOk8EFLJ9syQ6SI+XWrX9Wf2XH0s=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/apache/thrift v0.12.0 h1:pODnxUFNcjP9UTLZGTdeh+j16A8lJbRvD3rOtrk/7bs=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
@ -549,6 +551,7 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
|
@ -33,6 +33,7 @@ import (
|
||||
"github.com/filecoin-project/go-lotus/node/modules/lp2p"
|
||||
"github.com/filecoin-project/go-lotus/node/modules/testing"
|
||||
"github.com/filecoin-project/go-lotus/node/repo"
|
||||
"github.com/filecoin-project/go-lotus/paych"
|
||||
"github.com/filecoin-project/go-lotus/storage"
|
||||
)
|
||||
|
||||
@ -214,6 +215,9 @@ func Online() Option {
|
||||
|
||||
Override(new(*deals.Client), deals.NewClient),
|
||||
Override(RunDealClientKey, modules.RunDealClient),
|
||||
|
||||
Override(new(*paych.Store), modules.PaychStore),
|
||||
Override(new(*paych.Manager), modules.PaymentChannelManager),
|
||||
),
|
||||
|
||||
// Storage miner
|
||||
|
@ -3,9 +3,10 @@ package impl
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/filecoin-project/go-lotus/lib/bufbstore"
|
||||
"strconv"
|
||||
|
||||
"github.com/filecoin-project/go-lotus/lib/bufbstore"
|
||||
|
||||
"github.com/filecoin-project/go-lotus/api"
|
||||
"github.com/filecoin-project/go-lotus/chain"
|
||||
"github.com/filecoin-project/go-lotus/chain/actors"
|
||||
@ -19,6 +20,7 @@ import (
|
||||
"github.com/filecoin-project/go-lotus/chain/wallet"
|
||||
"github.com/filecoin-project/go-lotus/miner"
|
||||
"github.com/filecoin-project/go-lotus/node/client"
|
||||
"github.com/filecoin-project/go-lotus/paych"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-hamt-ipld"
|
||||
@ -41,6 +43,7 @@ type FullNodeAPI struct {
|
||||
PubSub *pubsub.PubSub
|
||||
Mpool *chain.MessagePool
|
||||
Wallet *wallet.Wallet
|
||||
PaychMgr *paych.Manager
|
||||
}
|
||||
|
||||
func (a *FullNodeAPI) ClientStartDeal(ctx context.Context, data cid.Cid, miner address.Address, price types.BigInt, blocksDuration uint64) (*cid.Cid, error) {
|
||||
@ -155,41 +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) {
|
||||
if ts == nil {
|
||||
ts = a.Chain.GetHeaviestTipSet()
|
||||
}
|
||||
state, err := a.Chain.TipSetState(ts.Cids())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vmi, err := vm.NewVM(state, ts.Height(), ts.Blocks()[0].Miner, a.Chain)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to set up vm: %w", err)
|
||||
}
|
||||
|
||||
if msg.GasLimit == types.EmptyInt {
|
||||
msg.GasLimit = types.NewInt(10000000000)
|
||||
}
|
||||
if msg.GasPrice == types.EmptyInt {
|
||||
msg.GasPrice = types.NewInt(0)
|
||||
}
|
||||
if msg.Value == types.EmptyInt {
|
||||
msg.Value = types.NewInt(0)
|
||||
}
|
||||
if msg.Params == nil {
|
||||
msg.Params, err = actors.SerializeParams(struct{}{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe just use the invoker directly?
|
||||
ret, err := vmi.ApplyMessage(ctx, msg)
|
||||
if ret.ActorErr != nil {
|
||||
log.Warnf("chain call failed: %s", ret.ActorErr)
|
||||
}
|
||||
return &ret.MessageReceipt, err
|
||||
return vm.Call(ctx, a.Chain, msg, ts)
|
||||
}
|
||||
|
||||
func (a *FullNodeAPI) stateForTs(ts *types.TipSet) (*state.StateTree, error) {
|
||||
@ -511,36 +480,160 @@ 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
|
||||
}
|
||||
|
||||
func (a *FullNodeAPI) PaychList(ctx context.Context) ([]address.Address, error) {
|
||||
panic("nyi")
|
||||
return a.PaychMgr.ListChannels()
|
||||
}
|
||||
|
||||
func (a *FullNodeAPI) PaychStatus(ctx context.Context, pch address.Address) (*api.PaychStatus, error) {
|
||||
panic("nyi")
|
||||
}
|
||||
|
||||
func (a *FullNodeAPI) PaychClose(ctx context.Context, addr address.Address) error {
|
||||
panic("nyi")
|
||||
func (a *FullNodeAPI) PaychClose(ctx context.Context, addr address.Address) (cid.Cid, error) {
|
||||
ci, err := a.PaychMgr.GetChannelInfo(addr)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
nonce, err := a.MpoolGetNonce(ctx, ci.ControlAddr)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
msg := &types.Message{
|
||||
To: addr,
|
||||
From: ci.ControlAddr,
|
||||
Value: types.NewInt(0),
|
||||
Method: actors.PCAMethods.Close,
|
||||
Nonce: nonce,
|
||||
|
||||
GasLimit: types.NewInt(500),
|
||||
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
|
||||
}
|
||||
|
||||
return smsg.Cid(), nil
|
||||
}
|
||||
|
||||
func (a *FullNodeAPI) PaychVoucherCheck(ctx context.Context, sv *types.SignedVoucher) error {
|
||||
panic("nyi")
|
||||
func (a *FullNodeAPI) PaychVoucherCheckValid(ctx context.Context, ch address.Address, sv *types.SignedVoucher) error {
|
||||
return a.PaychMgr.CheckVoucherValid(ctx, ch, sv)
|
||||
}
|
||||
|
||||
func (a *FullNodeAPI) PaychVoucherAdd(ctx context.Context, sv *types.SignedVoucher) error {
|
||||
panic("nyi")
|
||||
func (a *FullNodeAPI) PaychVoucherCheckSpendable(ctx context.Context, ch address.Address, sv *types.SignedVoucher, secret []byte, proof []byte) (bool, error) {
|
||||
return a.PaychMgr.CheckVoucherSpendable(ctx, ch, sv, secret, proof)
|
||||
}
|
||||
|
||||
func (a *FullNodeAPI) PaychVoucherAdd(ctx context.Context, ch address.Address, sv *types.SignedVoucher) error {
|
||||
if err := a.PaychVoucherCheckValid(ctx, ch, sv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.PaychMgr.AddVoucher(ctx, ch, sv)
|
||||
}
|
||||
|
||||
// PaychVoucherCreate creates a new signed voucher on the given payment channel
|
||||
// with the given lane and amount. The value passed in is exactly the value
|
||||
// that will be used to create the voucher, so if previous vouchers exist, the
|
||||
// actual additional value of this voucher will only be the difference between
|
||||
// the two.
|
||||
func (a *FullNodeAPI) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*types.SignedVoucher, error) {
|
||||
panic("nyi")
|
||||
ci, err := a.PaychMgr.GetChannelInfo(pch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce, err := a.PaychMgr.NextNonceForLane(ctx, pch, lane)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sv := &types.SignedVoucher{
|
||||
Lane: lane,
|
||||
Nonce: nonce,
|
||||
Amount: amt,
|
||||
}
|
||||
|
||||
vb, err := sv.SigningBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig, err := a.WalletSign(ctx, ci.ControlAddr, vb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sv.Signature = sig
|
||||
|
||||
if err := a.PaychMgr.AddVoucher(ctx, pch, sv); err != nil {
|
||||
return nil, xerrors.Errorf("failed to persist voucher: %w", err)
|
||||
}
|
||||
|
||||
return sv, nil
|
||||
}
|
||||
|
||||
func (a *FullNodeAPI) PaychVoucherList(ctx context.Context, pch address.Address) ([]*types.SignedVoucher, error) {
|
||||
panic("nyi")
|
||||
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{}
|
||||
|
15
node/modules/paych.go
Normal file
15
node/modules/paych.go
Normal file
@ -0,0 +1,15 @@
|
||||
package modules
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-lotus/chain/store"
|
||||
"github.com/filecoin-project/go-lotus/node/modules/dtypes"
|
||||
"github.com/filecoin-project/go-lotus/paych"
|
||||
)
|
||||
|
||||
func PaychStore(ds dtypes.MetadataDS) *paych.Store {
|
||||
return paych.NewStore(ds)
|
||||
}
|
||||
|
||||
func PaymentChannelManager(chain *store.ChainStore, store *paych.Store) (*paych.Manager, error) {
|
||||
return paych.NewManager(chain, store), nil
|
||||
}
|
219
paych/paych.go
Normal file
219
paych/paych.go
Normal file
@ -0,0 +1,219 @@
|
||||
package paych
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
chain *store.ChainStore
|
||||
store *Store
|
||||
}
|
||||
|
||||
func NewManager(chain *store.ChainStore, pchstore *Store) *Manager {
|
||||
return &Manager{
|
||||
chain: chain,
|
||||
store: pchstore,
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *Manager) TrackInboundChannel(ctx context.Context, ch address.Address) error {
|
||||
_, st, err := pm.loadPaychState(ctx, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return pm.store.TrackChannel(&ChannelInfo{
|
||||
Channel: ch,
|
||||
Direction: DirInbound,
|
||||
ControlAddr: st.To,
|
||||
})
|
||||
}
|
||||
|
||||
func (pm *Manager) TrackOutboundChannel(ctx context.Context, ch address.Address) error {
|
||||
_, st, err := pm.loadPaychState(ctx, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return pm.store.TrackChannel(&ChannelInfo{
|
||||
Channel: ch,
|
||||
Direction: DirOutbound,
|
||||
ControlAddr: st.From,
|
||||
})
|
||||
}
|
||||
|
||||
func (pm *Manager) ListChannels() ([]address.Address, error) {
|
||||
return pm.store.ListChannels()
|
||||
}
|
||||
|
||||
func (pm *Manager) GetChannelInfo(addr address.Address) (*ChannelInfo, error) {
|
||||
return pm.store.getChannelInfo(addr)
|
||||
}
|
||||
|
||||
// checks if the given voucher is valid (is or could become spendable at some point)
|
||||
func (pm *Manager) CheckVoucherValid(ctx context.Context, ch address.Address, sv *types.SignedVoucher) error {
|
||||
act, pca, err := pm.loadPaychState(ctx, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// verify signature
|
||||
vb, err := sv.SigningBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: technically, either party may create and sign a voucher.
|
||||
// However, for now, we only accept them from the channel creator.
|
||||
// More complex handling logic can be added later
|
||||
if err := sv.Signature.Verify(pca.From, vb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sendAmount := sv.Amount
|
||||
|
||||
// now check the lane state
|
||||
// TODO: should check against vouchers in our local store too
|
||||
// there might be something conflicting
|
||||
ls, ok := pca.LaneStates[fmt.Sprint(sv.Lane)]
|
||||
if !ok {
|
||||
} else {
|
||||
if ls.Closed {
|
||||
return fmt.Errorf("voucher is on a closed lane")
|
||||
}
|
||||
if ls.Nonce >= sv.Nonce {
|
||||
return fmt.Errorf("nonce too low")
|
||||
}
|
||||
|
||||
sendAmount = types.BigSub(sv.Amount, ls.Redeemed)
|
||||
}
|
||||
|
||||
// TODO: also account for vouchers on other lanes we've received
|
||||
newTotal := types.BigAdd(sendAmount, pca.ToSend)
|
||||
if types.BigCmp(act.Balance, newTotal) < 0 {
|
||||
return fmt.Errorf("not enough funds in channel to cover voucher")
|
||||
}
|
||||
|
||||
if len(sv.Merges) != 0 {
|
||||
return fmt.Errorf("dont currently support paych lane merges")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checks if the given voucher is currently spendable
|
||||
func (pm *Manager) CheckVoucherSpendable(ctx context.Context, ch address.Address, sv *types.SignedVoucher, secret []byte, proof []byte) (bool, error) {
|
||||
owner, err := pm.getPaychOwner(ctx, ch)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
enc, err := actors.SerializeParams(&actors.PCAUpdateChannelStateParams{
|
||||
Sv: *sv,
|
||||
Secret: secret,
|
||||
Proof: proof,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
ret, err := vm.Call(ctx, pm.chain, &types.Message{
|
||||
From: owner,
|
||||
To: ch,
|
||||
Method: actors.PCAMethods.UpdateChannelState,
|
||||
Params: enc,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if ret.ExitCode != 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (pm *Manager) loadPaychState(ctx context.Context, ch address.Address) (*types.Actor, *actors.PaymentChannelActorState, error) {
|
||||
st, err := pm.chain.TipSetState(pm.chain.GetHeaviestTipSet().Cids())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cst := hamt.CSTFromBstore(pm.chain.Blockstore())
|
||||
tree, err := state.LoadStateTree(cst, st)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
act, err := tree.GetActor(ch)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var pcast actors.PaymentChannelActorState
|
||||
if err := cst.Get(ctx, act.Head, &pcast); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return act, &pcast, nil
|
||||
}
|
||||
|
||||
func (pm *Manager) getPaychOwner(ctx context.Context, ch address.Address) (address.Address, error) {
|
||||
ret, err := vm.Call(ctx, pm.chain, &types.Message{
|
||||
From: ch,
|
||||
To: ch,
|
||||
Method: actors.PCAMethods.GetOwner,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return address.Undef, err
|
||||
}
|
||||
|
||||
if ret.ExitCode != 0 {
|
||||
return address.Undef, fmt.Errorf("failed to get payment channel owner (exit code %d)", ret.ExitCode)
|
||||
}
|
||||
|
||||
return address.NewFromBytes(ret.Return)
|
||||
}
|
||||
|
||||
func (pm *Manager) AddVoucher(ctx context.Context, ch address.Address, sv *types.SignedVoucher) error {
|
||||
if err := pm.CheckVoucherValid(ctx, ch, sv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return pm.store.AddVoucher(ch, sv)
|
||||
}
|
||||
|
||||
func (pm *Manager) ListVouchers(ctx context.Context, ch address.Address) ([]*types.SignedVoucher, error) {
|
||||
// TODO: just having a passthrough method like this feels odd. Seems like
|
||||
// there should be some filtering we're doing here
|
||||
return pm.store.VouchersForPaych(ch)
|
||||
}
|
||||
|
||||
func (pm *Manager) NextNonceForLane(ctx context.Context, ch address.Address, lane uint64) (uint64, error) {
|
||||
vouchers, err := pm.store.VouchersForPaych(ch)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var maxnonce uint64
|
||||
for _, v := range vouchers {
|
||||
if v.Lane == lane {
|
||||
if v.Nonce > maxnonce {
|
||||
maxnonce = v.Nonce
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return maxnonce + 1, nil
|
||||
}
|
133
paych/store.go
Normal file
133
paych/store.go
Normal file
@ -0,0 +1,133 @@
|
||||
package paych
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/filecoin-project/go-lotus/chain/address"
|
||||
"github.com/filecoin-project/go-lotus/chain/types"
|
||||
"github.com/ipfs/go-datastore"
|
||||
"github.com/ipfs/go-datastore/namespace"
|
||||
dsq "github.com/ipfs/go-datastore/query"
|
||||
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cbor.RegisterCborType(ChannelInfo{})
|
||||
}
|
||||
|
||||
type Store struct {
|
||||
ds datastore.Batching
|
||||
}
|
||||
|
||||
func NewStore(ds datastore.Batching) *Store {
|
||||
ds = namespace.Wrap(ds, datastore.NewKey("/paych/"))
|
||||
return &Store{
|
||||
ds: ds,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
DirInbound = 1
|
||||
DirOutbound = 2
|
||||
)
|
||||
|
||||
type ChannelInfo struct {
|
||||
Channel address.Address
|
||||
ControlAddr address.Address
|
||||
Direction int
|
||||
Vouchers []*types.SignedVoucher
|
||||
}
|
||||
|
||||
func dskeyForChannel(addr address.Address) datastore.Key {
|
||||
return datastore.NewKey(addr.String())
|
||||
}
|
||||
|
||||
func (ps *Store) putChannelInfo(ci *ChannelInfo) error {
|
||||
k := dskeyForChannel(ci.Channel)
|
||||
|
||||
b, err := cbor.DumpObject(ci)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ps.ds.Put(k, b)
|
||||
}
|
||||
|
||||
func (ps *Store) getChannelInfo(addr address.Address) (*ChannelInfo, error) {
|
||||
k := dskeyForChannel(addr)
|
||||
|
||||
b, err := ps.ds.Get(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ci ChannelInfo
|
||||
if err := cbor.DecodeInto(b, &ci); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ci, nil
|
||||
}
|
||||
|
||||
func (ps *Store) TrackChannel(ch *ChannelInfo) error {
|
||||
_, err := ps.getChannelInfo(ch.Channel)
|
||||
switch err {
|
||||
default:
|
||||
return err
|
||||
case nil:
|
||||
return fmt.Errorf("already tracking channel: %s", ch.Channel)
|
||||
case datastore.ErrNotFound:
|
||||
return ps.putChannelInfo(ch)
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *Store) ListChannels() ([]address.Address, error) {
|
||||
res, err := ps.ds.Query(dsq.Query{KeysOnly: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out []address.Address
|
||||
for {
|
||||
res, ok := res.NextSync()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (ps *Store) AddVoucher(ch address.Address, sv *types.SignedVoucher) error {
|
||||
ci, err := ps.getChannelInfo(ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ci.Vouchers = append(ci.Vouchers, sv)
|
||||
|
||||
return ps.putChannelInfo(ci)
|
||||
}
|
||||
|
||||
func (ps *Store) VouchersForPaych(ch address.Address) ([]*types.SignedVoucher, error) {
|
||||
ci, err := ps.getChannelInfo(ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ci.Vouchers, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user