Merge pull request #183 from filecoin-project/feat/stor-deal-payments

Payment integration in deals
This commit is contained in:
Łukasz Magiera 2019-09-09 22:09:46 +02:00 committed by GitHub
commit 2f03ac000e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 476 additions and 97 deletions

View File

@ -114,7 +114,7 @@ type FullNode interface {
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
PaychVoucherAdd(context.Context, address.Address, *types.SignedVoucher, []byte) error
PaychVoucherList(context.Context, address.Address) ([]*types.SignedVoucher, error)
PaychVoucherSubmit(context.Context, address.Address, *types.SignedVoucher) (cid.Cid, error)
}
@ -181,7 +181,18 @@ type ActorState struct {
State interface{}
}
type PaychStatus struct{}
type PCHDir int
const (
PCHUndef PCHDir = iota
PCHInbound
PCHOutbound
)
type PaychStatus struct {
ControlAddr address.Address
Direction PCHDir
}
type MinerPower struct {
MinerPower types.BigInt

View File

@ -86,7 +86,7 @@ type FullNodeStruct struct {
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"`
PaychVoucherAdd func(context.Context, address.Address, *types.SignedVoucher, []byte) 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"`
@ -303,8 +303,8 @@ func (c *FullNodeStruct) PaychVoucherCheckSpendable(ctx context.Context, addr ad
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) PaychVoucherAdd(ctx context.Context, addr address.Address, sv *types.SignedVoucher, proof []byte) error {
return c.Internal.PaychVoucherAdd(ctx, addr, sv, proof)
}
func (c *FullNodeStruct) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*types.SignedVoucher, error) {

View File

@ -7,6 +7,10 @@ const UnixfsLinksPerLevel = 1024
const SectorSize = 1024
const PaymentChannelClosingDelay = 6 * 60 * 2 // six hours
const DealVoucherSkewLimit = 10
const ForkLengthThreshold = 20
// TODO: Move other important consts here

View File

@ -5,14 +5,13 @@ import (
"context"
"encoding/binary"
"fmt"
"github.com/filecoin-project/go-lotus/chain/actors/aerrors"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/types"
"golang.org/x/xerrors"
"github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
"github.com/ipfs/go-hamt-ipld"
cbor "github.com/ipfs/go-ipld-cbor"
logging "github.com/ipfs/go-log"
mh "github.com/multiformats/go-multihash"
@ -211,7 +210,8 @@ func (ias *InitActorState) Lookup(cst *hamt.CborIpldStore, addr address.Address)
return address.Undef, err
}
val, err := amap.Find(context.TODO(), string(addr.Bytes()))
var val interface{}
err = amap.Find(context.TODO(), string(addr.Bytes()), &val)
if err != nil {
return address.Undef, err
}

View File

@ -2,15 +2,13 @@ package actors
import (
"context"
"github.com/filecoin-project/go-lotus/chain/actors/aerrors"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/types"
"github.com/filecoin-project/go-lotus/lib/sectorbuilder"
amt "github.com/filecoin-project/go-amt-ipld"
cid "github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
"github.com/filecoin-project/go-amt-ipld"
"github.com/ipfs/go-cid"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/libp2p/go-libp2p-core/peer"
"golang.org/x/xerrors"
@ -181,13 +179,14 @@ func (sma StorageMinerActor) StorageMinerConstructor(act *types.Actor, vmctx typ
}
var self StorageMinerActorState
nd := hamt.NewNode(vmctx.Ipld())
sectors, nerr := vmctx.Ipld().Put(context.TODO(), nd)
if nerr != nil {
return nil, aerrors.Escalate(nerr, "could not put in storage")
sectors := amt.NewAMT(types.WrapStorage(vmctx.Storage()))
scid, serr := sectors.Flush()
if serr != nil {
return nil, aerrors.Escalate(serr, "initializing AMT")
}
self.Sectors = sectors
self.ProvingSet = sectors
self.Sectors = scid
self.ProvingSet = scid
self.Info = minfocid
storage := vmctx.Storage()
@ -383,7 +382,7 @@ func GetFromSectorSet(ctx context.Context, s types.Storage, ss cid.Cid, sectorID
var comms [][]byte
err = ssr.Get(sectorID, &comms)
if err != nil {
if _, ok := err.(amt.ErrNotFound); ok {
if _, ok := err.(*amt.ErrNotFound); ok {
return false, nil, nil, nil
}
return false, nil, nil, aerrors.Escalate(err, "failed to find sector in sector set")
@ -559,7 +558,7 @@ func (sma StorageMinerActor) PaymentVerifyInclusion(act *types.Actor, vmctx type
return nil, aerrors.New(1, "miner does not have required sector")
}
ok, err := sectorbuilder.VerifyPieceInclusionProof(mi.SectorSize.Uint64(), voucherData.PieceSize.Uint64(), voucherData.CommP, commD, params.Proof)
ok, err := sectorbuilder.VerifyPieceInclusionProof(mi.SectorSize.Uint64(), voucherData.PieceSize.Uint64(), voucherData.CommP, commD, proof.Proof)
if err != nil {
return nil, aerrors.Escalate(err, "verify piece inclusion proof failed")
}

View File

@ -7,13 +7,12 @@ import (
"github.com/ipfs/go-cid"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/filecoin-project/go-lotus/build"
"github.com/filecoin-project/go-lotus/chain/actors/aerrors"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/types"
)
const ChannelClosingDelay = 6 * 60 * 2 // six hours
func init() {
cbor.RegisterCborType(PaymentChannelActorState{})
cbor.RegisterCborType(PCAConstructorParams{})
@ -29,7 +28,7 @@ type PaymentInfo struct {
Payer address.Address
ChannelMessage cid.Cid
Vouchers []types.SignedVoucher
Vouchers []*types.SignedVoucher
}
type LaneState struct {
@ -132,19 +131,19 @@ func (pca PaymentChannelActor) UpdateChannelState(act *types.Actor, vmctx types.
if sv.SecretPreimage != nil {
if !bytes.Equal(hash(params.Secret), sv.SecretPreimage) {
return nil, aerrors.New(3, "Incorrect secret!")
return nil, aerrors.New(3, "incorrect secret!")
}
}
if sv.Extra != nil {
encoded, err := SerializeParams([]interface{}{sv.Extra.Data, params.Proof})
encoded, err := SerializeParams(PaymentVerifyParams{sv.Extra.Data, params.Proof})
if err != nil {
return nil, err
}
_, err = vmctx.Send(sv.Extra.Actor, sv.Extra.Method, types.NewInt(0), encoded)
if err != nil {
return nil, aerrors.New(4, "spend voucher verification failed")
return nil, aerrors.Newf(4, "spend voucher verification failed: %s", err)
}
}
@ -231,7 +230,7 @@ func (pca PaymentChannelActor) Close(act *types.Actor, vmctx types.VMContext, pa
return nil, aerrors.New(2, "channel already closing")
}
self.ClosingAt = vmctx.BlockHeight() + ChannelClosingDelay
self.ClosingAt = vmctx.BlockHeight() + build.PaymentChannelClosingDelay
if self.ClosingAt < self.MinCloseHeight {
self.ClosingAt = self.MinCloseHeight
}

View File

@ -2,6 +2,7 @@ package deals
import (
"context"
"github.com/filecoin-project/go-lotus/chain/types"
"github.com/filecoin-project/go-lotus/storage/sectorblocks"
"math"
@ -34,6 +35,8 @@ type MinerDeal struct {
}
type Handler struct {
pricePerByteBlock types.BigInt // how much we want for storing one byte for one block
secst *sectorblocks.SectorBlocks
full api.FullNode
@ -74,6 +77,8 @@ func NewHandler(ds dtypes.MetadataDS, secst *sectorblocks.SectorBlocks, dag dtyp
dag: dag,
full: fullNode,
pricePerByteBlock: types.NewInt(3), // TODO: allow setting
conns: map[cid.Cid]inet.Stream{},
incoming: make(chan MinerDeal),

View File

@ -1,12 +1,17 @@
package deals
import (
"bytes"
"context"
"github.com/filecoin-project/go-lotus/build"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/ipfs/go-merkledag"
unixfile "github.com/ipfs/go-unixfs/file"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-lotus/chain/actors"
"github.com/filecoin-project/go-lotus/chain/types"
"github.com/filecoin-project/go-lotus/lib/sectorbuilder"
"github.com/filecoin-project/go-lotus/storage/sectorblocks"
)
@ -30,6 +35,68 @@ func (h *Handler) handle(ctx context.Context, deal MinerDeal, cb handlerFunc, ne
// ACCEPTED
func (h *Handler) validateVouchers(ctx context.Context, deal MinerDeal) error {
curHead, err := h.full.ChainHead(ctx)
if err != nil {
return err
}
for i, voucher := range deal.Proposal.Payment.Vouchers {
err := h.full.PaychVoucherCheckValid(ctx, deal.Proposal.Payment.PayChActor, voucher)
if err != nil {
return xerrors.Errorf("validating payment voucher %d: %w", i, err)
}
if voucher.Extra == nil {
return xerrors.Errorf("validating payment voucher %d: voucher.Extra not set")
}
if voucher.Extra.Actor != deal.Proposal.MinerAddress {
return xerrors.Errorf("validating payment voucher %d: extra params actor didn't match miner address in proposal: '%s' != '%s'", i, voucher.Extra.Actor, deal.Proposal.MinerAddress)
}
if voucher.Extra.Method != actors.MAMethods.PaymentVerifyInclusion {
return xerrors.Errorf("validating payment voucher %d: expected extra method %d, got %d", i, actors.MAMethods.PaymentVerifyInclusion, voucher.Extra.Method)
}
var inclChallenge actors.PieceInclVoucherData
if err := cbor.DecodeInto(voucher.Extra.Data, &inclChallenge); err != nil {
return xerrors.Errorf("validating payment voucher %d: failed to decode storage voucher data for verification: %w", i, err)
}
if inclChallenge.PieceSize.Uint64() != deal.Proposal.Size {
return xerrors.Errorf("validating payment voucher %d: paych challenge piece size didn't match deal proposal size: %d != %d", i, inclChallenge.PieceSize.Uint64(), deal.Proposal.Size)
}
if !bytes.Equal(inclChallenge.CommP, deal.Proposal.CommP) {
return xerrors.Errorf("validating payment voucher %d: paych challenge commP didn't match deal proposal", i)
}
maxClose := curHead.Height() + deal.Proposal.Duration + build.DealVoucherSkewLimit
if voucher.MinCloseHeight > maxClose {
return xerrors.Errorf("validating payment voucher %d: MinCloseHeight too high (%d), max expected: %d", i, voucher.MinCloseHeight, maxClose)
}
if voucher.TimeLock > maxClose {
return xerrors.Errorf("validating payment voucher %d: TimeLock too high (%d), max expected: %d", i, voucher.TimeLock, maxClose)
}
if len(voucher.Merges) > 0 {
return xerrors.Errorf("validating payment voucher %d: didn't expect any merges", i)
}
// TODO: make sure that current laneStatus.Amount == 0
if types.BigCmp(voucher.Amount, deal.Proposal.TotalPrice) < 0 {
return xerrors.Errorf("validating payment voucher %d: not enough funds in the voucher", i)
}
minPrice := types.BigMul(types.BigMul(h.pricePerByteBlock, types.NewInt(deal.Proposal.Size)), types.NewInt(deal.Proposal.Duration))
if types.BigCmp(minPrice, deal.Proposal.TotalPrice) > 0 {
return xerrors.Errorf("validating payment voucher %d: minimum price: %s", i, minPrice)
}
}
return nil
}
func (h *Handler) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
switch deal.Proposal.SerializationMode {
//case SerializationRaw:
@ -39,7 +106,15 @@ func (h *Handler) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal),
return nil, xerrors.Errorf("deal proposal with unsupported serialization: %s", deal.Proposal.SerializationMode)
}
// TODO: check payment
if err := h.validateVouchers(ctx, deal); err != nil {
return nil, err
}
for i, voucher := range deal.Proposal.Payment.Vouchers {
if err := h.full.PaychVoucherAdd(ctx, deal.Proposal.Payment.PayChActor, voucher, nil); err != nil {
return nil, xerrors.Errorf("consuming payment voucher %d: %w", i, err)
}
}
log.Info("fetching data for a deal")
err := h.sendSignedResponse(StorageDealResponse{
@ -140,6 +215,24 @@ func (h *Handler) sealing(ctx context.Context, deal MinerDeal) (func(*MinerDeal)
return nil, err
}
proof := &actors.InclusionProof{
Sector: deal.SectorID,
Proof: ip.ProofElements,
}
proofB, err := cbor.DumpObject(proof)
if err != nil {
return nil, err
}
// store proofs for channels
for i, v := range deal.Proposal.Payment.Vouchers {
if v.Extra.Method == actors.MAMethods.PaymentVerifyInclusion {
if err := h.full.PaychVoucherAdd(ctx, deal.Proposal.Payment.PayChActor, v, proofB); err != nil {
return nil, xerrors.Errorf("storing payment voucher %d proof: %w", i, err)
}
}
}
err = h.sendSignedResponse(StorageDealResponse{
State: Sealing,
Proposal: deal.ProposalCid,

View File

@ -2,12 +2,11 @@ package gen
import (
"context"
bls "github.com/filecoin-project/go-bls-sigs"
cid "github.com/ipfs/go-cid"
hamt "github.com/ipfs/go-hamt-ipld"
"github.com/filecoin-project/go-bls-sigs"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-hamt-ipld"
"github.com/pkg/errors"
sharray "github.com/whyrusleeping/sharray"
"github.com/whyrusleeping/sharray"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-lotus/chain/actors"

View File

@ -113,7 +113,7 @@ func (mp *MessagePool) Remove(from address.Address, nonce uint64) {
func (mp *MessagePool) Pending() []*types.SignedMessage {
mp.lk.Lock()
defer mp.lk.Unlock()
var out []*types.SignedMessage
out := make([]*types.SignedMessage, 0)
for _, mset := range mp.pending {
for i := mset.startNonce; true; i++ {
m, ok := mset.msgs[i]

View File

@ -100,7 +100,8 @@ func (st *StateTree) GetActor(addr address.Address) (*types.Actor, error) {
return cact, nil
}
thing, err := st.root.Find(context.TODO(), string(addr.Bytes()))
var thing interface{}
err := st.root.Find(context.TODO(), string(addr.Bytes()), &thing)
if err != nil {
if err == hamt.ErrNotFound {
return nil, types.ErrActorNotFound

View File

@ -440,7 +440,7 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err
if h.Timestamp < baseTs.MinTimestamp()+uint64(build.BlockDelay*len(h.Tickets)) {
log.Warn("timestamp funtimes: ", h.Timestamp, baseTs.MinTimestamp(), len(h.Tickets))
return xerrors.Errorf("block was generated too soon (timestamp < BLOCK_DELAY * len(tickets))")
return xerrors.Errorf("block was generated too soon (h.ts:%d < base.mints:%d + BLOCK_DELAY:%d * tkts.len:%d)", h.Timestamp, baseTs.MinTimestamp(), build.BlockDelay, len(h.Tickets))
}
if err := syncer.minerIsValid(ctx, h.Miner, baseTs); err != nil {

View File

@ -9,6 +9,7 @@ import (
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/polydawn/refmt/obj/atlas"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
)
const BigIntMaxSerializedLen = 128 // is this big enough? or too big?
@ -93,7 +94,7 @@ func (bi *BigInt) UnmarshalJSON(b []byte) error {
if string(s) == "<nil>" {
return nil
}
return fmt.Errorf("failed to parse bigint string")
return xerrors.Errorf("failed to parse bigint string: '%s'", string(b))
}
bi.Int = i

View File

@ -1,6 +1,7 @@
package types
import (
"bytes"
"encoding/base64"
"github.com/filecoin-project/go-lotus/chain/address"
@ -42,6 +43,24 @@ func (sv *SignedVoucher) EncodedString() (string, error) {
return base64.RawURLEncoding.EncodeToString(data), nil
}
func (sv *SignedVoucher) Equals(other *SignedVoucher) bool {
// TODO: make this less bad
selfB, err := cbor.DumpObject(sv)
if err != nil {
log.Errorf("SignedVoucher.Equals: dump self: %s", err)
return false
}
otherB, err := cbor.DumpObject(other)
if err != nil {
log.Errorf("SignedVoucher.Equals: dump other: %s", err)
return false
}
return bytes.Equal(selfB, otherB)
}
func DecodeSignedVoucher(s string) (*SignedVoucher, error) {
data, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {

View File

@ -202,7 +202,8 @@ var paychVoucherAddCmd = &cli.Command{
ctx := ReqContext(cctx)
if err := api.PaychVoucherAdd(ctx, ch, sv); err != nil {
// TODO: allow passing proof bytes
if err := api.PaychVoucherAdd(ctx, ch, sv, nil); err != nil {
return err
}
@ -213,6 +214,12 @@ var paychVoucherAddCmd = &cli.Command{
var paychVoucherListCmd = &cli.Command{
Name: "list",
Usage: "List stored vouchers for a given payment channel",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "export",
Usage: "Print export strings",
},
},
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 1 {
return fmt.Errorf("must pass payment channel address")
@ -236,7 +243,16 @@ var paychVoucherListCmd = &cli.Command{
}
for _, v := range vouchers {
fmt.Printf("Lane %d, Nonce %d: %s\n", v.Lane, v.Nonce, v.Amount.String())
if cctx.Bool("export") {
enc, err := v.EncodedString()
if err != nil {
return err
}
fmt.Printf("Lane %d, Nonce %d: %s; %s\n", v.Lane, v.Nonce, v.Amount.String(), enc)
} else {
fmt.Printf("Lane %d, Nonce %d: %s\n", v.Lane, v.Nonce, v.Amount.String())
}
}
return nil

View File

@ -179,7 +179,7 @@ func configureStorageMiner(ctx context.Context, api api.FullNode, addr address.A
Nonce: nonce,
Value: types.NewInt(0),
GasPrice: types.NewInt(0),
GasLimit: types.NewInt(1000),
GasLimit: types.NewInt(1000000),
}
smsg, err := api.WalletSignMessage(ctx, waddr, msg)

6
go.mod
View File

@ -22,7 +22,7 @@ require (
github.com/ipfs/go-ds-badger v0.0.5
github.com/ipfs/go-filestore v0.0.2
github.com/ipfs/go-fs-lock v0.0.1
github.com/ipfs/go-hamt-ipld v0.0.12-0.20190822003241-7ff276389cbf
github.com/ipfs/go-hamt-ipld v0.0.12-0.20190830015840-8aabc0c74ac6
github.com/ipfs/go-ipfs-blockstore v0.1.0
github.com/ipfs/go-ipfs-chunker v0.0.1
github.com/ipfs/go-ipfs-ds-help v0.0.1
@ -67,7 +67,7 @@ require (
github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a
github.com/stretchr/testify v1.4.0
github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba
github.com/whyrusleeping/cbor-gen v0.0.0-20190822231004-8db835b09a5a
github.com/whyrusleeping/cbor-gen v0.0.0-20190906235522-125fcd082c67
github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7
github.com/whyrusleeping/pubsub v0.0.0-20131020042734-02de8aa2db3d
github.com/whyrusleeping/sharray v0.0.0-20190718051354-e41931821e33
@ -76,6 +76,8 @@ require (
go.uber.org/fx v1.9.0
go.uber.org/goleak v0.10.0 // indirect
go4.org v0.0.0-20190313082347-94abd6928b1d // indirect
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 // indirect
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
google.golang.org/api v0.9.0 // indirect
gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8

11
go.sum
View File

@ -168,8 +168,8 @@ github.com/ipfs/go-filestore v0.0.2 h1:pcYwpjtXXwirtbjBXKVJM9CTa9F7/8v1EkfnDaHTO
github.com/ipfs/go-filestore v0.0.2/go.mod h1:KnZ41qJsCt2OX2mxZS0xsK3Psr0/oB93HMMssLujjVc=
github.com/ipfs/go-fs-lock v0.0.1 h1:XHX8uW4jQBYWHj59XXcjg7BHlHxV9ZOYs6Y43yb7/l0=
github.com/ipfs/go-fs-lock v0.0.1/go.mod h1:DNBekbboPKcxs1aukPSaOtFA3QfSdi5C855v0i9XJ8Y=
github.com/ipfs/go-hamt-ipld v0.0.12-0.20190822003241-7ff276389cbf h1:P9Kkd8YCG4gCvfi8O839HHK2c+p5sdtyXMHcc1rjp2M=
github.com/ipfs/go-hamt-ipld v0.0.12-0.20190822003241-7ff276389cbf/go.mod h1:gaK14QN1GOlYGgq+o+t5+WTExZZogkMt0k0IIBNjXsM=
github.com/ipfs/go-hamt-ipld v0.0.12-0.20190830015840-8aabc0c74ac6 h1:qVk+425ErvzJEz/9f38lhjPfmmu0GAj/BSNt56SW4xQ=
github.com/ipfs/go-hamt-ipld v0.0.12-0.20190830015840-8aabc0c74ac6/go.mod h1:UPmViPxLn1GGxxnllIww8yWUVO60qEYLFFfF9e3ojgo=
github.com/ipfs/go-ipfs-blockstore v0.0.1/go.mod h1:d3WClOmRQKFnJ0Jz/jj/zmksX0ma1gROTlovZKBmN08=
github.com/ipfs/go-ipfs-blockstore v0.0.2/go.mod h1:d3WClOmRQKFnJ0Jz/jj/zmksX0ma1gROTlovZKBmN08=
github.com/ipfs/go-ipfs-blockstore v0.1.0 h1:V1GZorHFUIB6YgTJQdq7mcaIpUfCM3fCyVi+MTo9O88=
@ -524,9 +524,10 @@ github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc h1:BCPnHtcboa
github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc/go.mod h1:r45hJU7yEoA81k6MWNhpMj/kms0n14dkzkxYHoB96UM=
github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba h1:X4n8JG2e2biEZZXdBKt9HX7DN3bYGFUqljqqy0DqgnY=
github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba/go.mod h1:CHQnYnQUEPydYCwuy8lmTHfGmdw9TKrhWV0xLx8l0oM=
github.com/whyrusleeping/cbor-gen v0.0.0-20190822002707-4e02357de5c1/go.mod h1:xdlJQaiqipF0HW+Mzpg7XRM3fWbGvfgFlcppuvlkIvY=
github.com/whyrusleeping/cbor-gen v0.0.0-20190822231004-8db835b09a5a h1:9oEQR9eq2H2JDmglMcrCa+TxUEYy3HKSiNoLIkPNy/U=
github.com/whyrusleeping/cbor-gen v0.0.0-20190822231004-8db835b09a5a/go.mod h1:xdlJQaiqipF0HW+Mzpg7XRM3fWbGvfgFlcppuvlkIvY=
github.com/whyrusleeping/cbor-gen v0.0.0-20190906235522-125fcd082c67 h1:1JDNlhJZDMCdB/8KH7w7Aq0yvBYRA8pVdhzPZluDBHU=
github.com/whyrusleeping/cbor-gen v0.0.0-20190906235522-125fcd082c67/go.mod h1:xdlJQaiqipF0HW+Mzpg7XRM3fWbGvfgFlcppuvlkIvY=
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E=
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8=
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k=
@ -584,6 +585,8 @@ golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM=
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -642,6 +645,8 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=

View File

@ -1,6 +1,7 @@
package main
import (
"crypto/rand"
"fmt"
"github.com/filecoin-project/go-lotus/lib/jsonrpc"
"io"
@ -203,6 +204,45 @@ func (api *api) SpawnStorage(fullNodeRepo string) (nodeInfo, error) {
return info, nil
}
func (api *api) FullID(id int32) (int32, error) {
api.runningLk.Lock()
defer api.runningLk.Unlock()
stor, ok := api.running[id]
if !ok {
return 0, xerrors.New("storage node not found")
}
if !stor.meta.Storage {
return 0, xerrors.New("node is not a storage node")
}
for id, n := range api.running {
if n.meta.Repo == stor.meta.FullNode {
return id, nil
}
}
return 0, xerrors.New("node not found")
}
func (api *api) CreateRandomFile(size int64) (string, error) {
tf, err := ioutil.TempFile(os.TempDir(), "pond-random-")
if err != nil {
return "", err
}
_, err = io.CopyN(tf, rand.Reader, size)
if err != nil {
return "", err
}
if err := tf.Close(); err != nil {
return "", err
}
return tf.Name(), nil
}
type client struct {
Nodes func() []nodeInfo
}

View File

@ -11,6 +11,9 @@ function truncAddr(addr, len) {
return addr
}
let sheet = document.createElement('style')
document.body.appendChild(sheet);
class Address extends React.Component {
constructor(props) {
super(props)
@ -32,6 +35,7 @@ class Address extends React.Component {
let actor = {}
let actorInfo
let minerInfo
let nonce
try {
balance = await this.props.client.call('Filecoin.WalletBalance', [this.props.addr])
@ -41,11 +45,14 @@ class Address extends React.Component {
if(this.props.miner) {
minerInfo = await this.props.client.call('Filecoin.StateMinerPower', [this.props.addr, this.props.ts || null])
}
if(this.props.nonce) {
nonce = await this.props.client.call('Filecoin.MpoolGetNonce', [this.props.addr])
}
} catch (err) {
console.log(err)
balance = -1
}
this.setState({balance, actor, actorInfo, minerInfo})
this.setState({balance, actor, actorInfo, minerInfo, nonce})
}
openState() {
@ -71,10 +78,17 @@ class Address extends React.Component {
return info
}
add10k = async () => {
[...Array(10).keys()].map(() => async () => await this.props.add1k(this.props.addr)).reduce(async (p, c) => [await p, await c()], Promise.resolve(null))
}
render() {
let add1k = <span/>
if(this.props.add1k) {
add1k = <span>&nbsp;<a href="#" onClick={() => this.props.add1k(this.props.addr)}>[+1k]</a></span>
if (this.props.add10k) {
add1k = <span>{add1k}&nbsp;<a href="#" onClick={this.add10k}>[+10k]</a></span>
}
}
let addr = truncAddr(this.props.addr, this.props.short ? 12 : 17)
@ -84,6 +98,16 @@ class Address extends React.Component {
addr = <a href="#" onClick={this.openState}>{addr}</a>
}
addr = <span className={`pondaddr-${this.props.addr}`}
onMouseEnter={() => sheet.sheet.insertRule(`.pondaddr-${this.props.addr}, .pondaddr-${this.props.addr} * { color: #11ee11; }`, 0)}
onMouseLeave={() => sheet.sheet.deleteRule(0)}
>{addr}</span>
let nonce = <span/>
if(this.props.nonce) {
nonce = <span>&nbsp;<abbr title={"Next nonce"}>Nc:{this.state.nonce}</abbr>{nonce}</span>
}
let balance = <span>:&nbsp;{this.state.balance}&nbsp;</span>
if(this.props.nobalance) {
balance = <span/>
@ -103,7 +127,7 @@ class Address extends React.Component {
minerInfo = <span>&nbsp;Power: {this.state.minerInfo.MinerPower} ({this.state.minerInfo.MinerPower/this.state.minerInfo.TotalPower*100}%)</span>
}
return <span>{addr}{balance}{actInfo}{add1k}{transfer}{minerInfo}</span>
return <span>{addr}{balance}{actInfo}{nonce}{add1k}{transfer}{minerInfo}</span>
}
}

View File

@ -58,6 +58,9 @@ class ChainExplorer extends React.Component {
}
async fetch(h, cache, msgcache) {
if (h < 0) {
return
}
const cids = cache[h + 1].Blocks.map(b => b.Parents).reduce((acc, val) => acc.concat(val), [])
const blocks = await Promise.all(cids.map(cid => this.props.client.call('Filecoin.ChainGetBlock', [cid])))

View File

@ -0,0 +1,46 @@
import React from 'react';
import Cristal from 'react-cristal'
class Client extends React.Component {
constructor(props) {
super(props)
this.state = {
kbs: 1,
blocks: 12,
total: 36000,
miner: "t0101"
}
}
update = (name) => (e) => this.setState({ [name]: e.target.value });
makeDeal = async () => {
let file = await this.props.pondClient.call('Pond.CreateRandomFile', [this.state.kbs * 1000]) // 1024 won't fit in 1k blocks :(
let cid = await this.props.client.call('Filecoin.ClientImport', [file])
let dealcid = await this.props.client.call('Filecoin.ClientStartDeal', [cid, this.state.miner, `${Math.round(this.state.total / this.state.blocks)}`, this.state.blocks])
console.log("deal cid: ", dealcid)
}
render() {
let ppb = Math.round(this.state.total / this.state.blocks * 100) / 100
let ppmbb = Math.round(ppb / (this.state.kbs / 1000) * 100) / 100
let dealMaker = <div>
<span>Make Deal: </span>
<select><option>t0101</option></select>
<abbr title="Data length">L:</abbr> <input placeholder="KBs" defaultValue={1} onChange={this.update("kbs")}/>
<abbr title="Deal duration">Dur:</abbr><input placeholder="blocks" defaultValue={12} onChange={this.update("blocks")}/>
Total: <input placeholder="total price" defaultValue={36000} onChange={this.update("total")}/>
<span><abbr title="Price per block">PpB:</abbr> {ppb} </span>
<span><abbr title="Price per block-MiB">PpMbB:</abbr> {ppmbb} </span>
<button onClick={this.makeDeal}>Deal!</button>
</div>
return <Cristal title={"Client - Node " + this.props.node.ID}>
<div>{dealMaker}</div>
</Cristal>
}
}
export default Client

View File

@ -1,10 +1,10 @@
import React from 'react';
import { Client } from 'rpc-websockets'
import Cristal from 'react-cristal'
import { BlockLinks } from "./BlockLink";
import StorageNodeInit from "./StorageNodeInit";
import Address from "./Address";
import ChainExplorer from "./ChainExplorer";
import Client from "./Client";
class FullNode extends React.Component {
constructor(props) {
@ -18,6 +18,7 @@ class FullNode extends React.Component {
this.startStorageMiner = this.startStorageMiner.bind(this)
this.add1k = this.add1k.bind(this)
this.explorer = this.explorer.bind(this)
this.client = this.client.bind(this)
this.loadInfo()
setInterval(this.loadInfo, 2050)
@ -46,12 +47,16 @@ class FullNode extends React.Component {
let minerList = await this.props.client.call('Filecoin.MinerAddresses', [])
let mpoolPending = (await this.props.client.call('Filecoin.MpoolPending', [tipset])).length
this.setState(() => ({
id: id,
version: version,
peers: peers.length,
tipset: tipset,
mpoolPending: mpoolPending,
addrs: addrs,
paychs: paychs,
vouchers: vouchers,
@ -74,8 +79,8 @@ class FullNode extends React.Component {
this.loadInfo()
}
async startStorageMiner() {
this.props.mountWindow((onClose) => <StorageNodeInit fullRepo={this.props.node.Repo} fullConn={this.props.client} pondClient={this.props.pondClient} onClose={onClose} mountWindow={this.props.mountWindow}/>)
startStorageMiner() {
this.props.spawnStorageNode(this.props.node.Repo, this.props.client)
}
async add1k(to) {
@ -86,6 +91,10 @@ class FullNode extends React.Component {
this.props.mountWindow((onClose) => <ChainExplorer onClose={onClose} ts={this.state.tipset} client={this.props.client} mountWindow={this.props.mountWindow}/>)
}
client() {
this.props.mountWindow((onClose) => <Client onClose={onClose} node={this.props.node} client={this.props.client} pondClient={this.props.pondClient} mountWindow={this.props.mountWindow}/>)
}
render() {
let runtime = <div></div>
@ -96,7 +105,7 @@ class FullNode extends React.Component {
<div>
Head: {
<BlockLinks cids={this.state.tipset.Cids} conn={this.props.client} mountWindow={this.props.mountWindow} />
} H:{this.state.tipset.Height} <a href="#" onClick={this.explorer}>[Explore]</a>
} H:{this.state.tipset.Height} Mp:{this.state.mpoolPending} <a href="#" onClick={this.explorer}>[Explore]</a> <a href="#" onClick={this.client}>[Client]</a>
</div>
)
}
@ -109,14 +118,14 @@ class FullNode extends React.Component {
let storageMine = <a href="#" onClick={this.startStorageMiner}>[Spawn Storage Miner]</a>
let addresses = this.state.addrs.map((addr) => {
let line = <Address client={this.props.client} add1k={this.add1k} addr={addr} mountWindow={this.props.mountWindow}/>
let line = <Address client={this.props.client} add1k={this.add1k} add10k={true} nonce={true} addr={addr} mountWindow={this.props.mountWindow}/>
if (this.state.defaultAddr === addr) {
line = <b>{line}</b>
}
return <div key={addr}>{line}</div>
})
let paychannels = this.state.paychs.map((addr, ak) => {
const line = <Address client={this.props.client} add1k={this.add1k} addr={addr} mountWindow={this.props.mountWindow}/>
const line = <Address client={this.props.client} add1k={this.add1k} add10k={true} addr={addr} mountWindow={this.props.mountWindow}/>
const vouchers = this.state.vouchers[ak].map(voucher => {
let extra = <span></span>
if(voucher.Extra) {

View File

@ -7,6 +7,7 @@ import StorageNode from "./StorageNode";
import {Client} from "rpc-websockets";
import pushMessage from "./chain/send";
import Logs from "./Logs";
import StorageNodeInit from "./StorageNodeInit";
class NodeList extends React.Component {
constructor(props) {
@ -21,6 +22,7 @@ class NodeList extends React.Component {
// This binding is necessary to make `this` work in the callback
this.spawnNode = this.spawnNode.bind(this)
this.spawnStorageNode = this.spawnStorageNode.bind(this)
this.connMgr = this.connMgr.bind(this)
this.consensus = this.consensus.bind(this)
this.transfer1kFrom1 = this.transfer1kFrom1.bind(this)
@ -49,12 +51,18 @@ class NodeList extends React.Component {
client={client}
pondClient={this.props.client}
give1k={this.transfer1kFrom1}
mountWindow={this.props.mountWindow}/>)
mountWindow={this.props.mountWindow}
spawnStorageNode={this.spawnStorageNode}
/>)
} else {
const fullId = await this.props.client.call('Pond.FullID', [node.ID])
this.props.mountWindow((onClose) =>
<StorageNode node={{...node}}
pondClient={this.props.client}
mountWindow={this.props.mountWindow}/>)
fullConn={this.state.nodes[fullId].conn}
mountWindow={this.props.mountWindow}
/>)
}
})
}
@ -81,7 +89,7 @@ class NodeList extends React.Component {
return [addr, balance]
}).reduce(async (c, n) => (await c)[1] > (await n)[1] ? await c : await n, Promise.resolve(['', -2]))
pushMessage(this.state.nodes[1].conn, bestaddr, {
await pushMessage(this.state.nodes[1].conn, bestaddr, {
To: to,
From: bestaddr,
Value: "1000",
@ -96,6 +104,16 @@ class NodeList extends React.Component {
this.setState(state => ({nodes: {...state.nodes, [node.ID]: node}}))
}
async spawnStorageNode(fullRepo, fullConn) {
let nodePromise = this.props.client.call('Pond.SpawnStorage', [fullRepo])
this.props.mountWindow((onClose) => <StorageNodeInit node={nodePromise} fullRepo={fullRepo} fullConn={fullConn} pondClient={this.props.client} onClose={onClose} mountWindow={this.props.mountWindow}/>)
let node = await nodePromise
await this.mountNode(node)
//this.setState(state => ({nodes: {...state.nodes, [node.ID]: node}}))
}
connMgr() {
this.setState({showConnMgr: true})
}

View File

@ -4,10 +4,10 @@ import StorageNode from "./StorageNode";
class StorageNodeInit extends React.Component {
async componentDidMount() {
const info = await this.props.pondClient.call('Pond.SpawnStorage', [this.props.fullRepo])
const info = await this.props.node
this.props.onClose()
this.props.mountWindow((onClose) => <StorageNode node={info} fullRepo={this.props.fullRepo} fullConn={this.props.fullConn} pondClient={this.props.pondClient} onClose={onClose} mountWindow={this.props.mountWindow}/>)
//this.props.mountWindow((onClose) => <StorageNode node={info} fullRepo={this.props.fullRepo} fullConn={this.props.fullConn} pondClient={this.props.pondClient} onClose={onClose} mountWindow={this.props.mountWindow}/>)
}
render() {

View File

@ -336,8 +336,10 @@ func (m *Miner) createBlock(base *MiningBase, ticket *types.Ticket, proof types.
msgs := m.selectMessages(pending)
uts := time.Now().Unix() // TODO: put smallest valid timestamp
// why even return this? that api call could just submit it for us
return m.api.MinerCreateBlock(context.TODO(), m.addresses[0], base.ts, append(base.tickets, ticket), proof, msgs, 0)
return m.api.MinerCreateBlock(context.TODO(), m.addresses[0], base.ts, append(base.tickets, ticket), proof, msgs, uint64(uts))
}
func (m *Miner) selectMessages(msgs []*types.SignedMessage) []*types.SignedMessage {

View File

@ -3,31 +3,32 @@ package full
import (
"context"
"errors"
"github.com/filecoin-project/go-lotus/build"
"github.com/filecoin-project/go-lotus/retrieval"
"github.com/filecoin-project/go-lotus/retrieval/discovery"
"github.com/ipfs/go-blockservice"
offline "github.com/ipfs/go-ipfs-exchange-offline"
"github.com/ipfs/go-merkledag"
"os"
"github.com/filecoin-project/go-lotus/api"
"github.com/filecoin-project/go-lotus/chain/actors"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/deals"
"github.com/filecoin-project/go-lotus/chain/types"
"github.com/filecoin-project/go-lotus/node/modules/dtypes"
"github.com/ipfs/go-blockservice"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-filestore"
chunker "github.com/ipfs/go-ipfs-chunker"
offline "github.com/ipfs/go-ipfs-exchange-offline"
files "github.com/ipfs/go-ipfs-files"
cbor "github.com/ipfs/go-ipld-cbor"
ipld "github.com/ipfs/go-ipld-format"
"github.com/ipfs/go-merkledag"
"github.com/ipfs/go-unixfs/importer/balanced"
ihelper "github.com/ipfs/go-unixfs/importer/helpers"
"github.com/libp2p/go-libp2p-core/peer"
"go.uber.org/fx"
"github.com/filecoin-project/go-lotus/api"
"github.com/filecoin-project/go-lotus/build"
"github.com/filecoin-project/go-lotus/chain/actors"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/deals"
"github.com/filecoin-project/go-lotus/chain/store"
"github.com/filecoin-project/go-lotus/chain/types"
"github.com/filecoin-project/go-lotus/node/modules/dtypes"
"github.com/filecoin-project/go-lotus/retrieval"
"github.com/filecoin-project/go-lotus/retrieval/discovery"
)
type ClientAPI struct {
@ -40,6 +41,7 @@ type ClientAPI struct {
DealClient *deals.Client
RetDiscovery discovery.PeerResolver
Retrieval *retrieval.Client
Chain *store.ChainStore
LocalDAG dtypes.ClientDAG
Blockstore dtypes.ClientBlockstore
@ -88,16 +90,18 @@ func (a *ClientAPI) ClientStartDeal(ctx context.Context, data cid.Cid, miner add
return nil, err
}
voucher := types.SignedVoucher{
// TimeLock: 0, // TODO: do we want to use this somehow?
head := a.Chain.GetHeaviestTipSet()
voucher := types.SignedVoucher{ // TODO: split into smaller payments
TimeLock: head.Height() + blocksDuration,
Extra: &types.ModVerifyParams{
Actor: miner,
Method: actors.MAMethods.PaymentVerifyInclusion,
Data: voucherData,
},
Lane: 0,
Lane: 0, // TODO: some api to make this easy
Amount: total,
MinCloseHeight: blocksDuration, // TODO: some way to start this after initial piece inclusion by actor? (also, at least add current height)
MinCloseHeight: head.Height() + blocksDuration, // TODO: some way to start this after initial piece inclusion by actor? Using actors.PieceInclVoucherData?
}
sv, err := a.paychVoucherCreate(ctx, paych, voucher)
@ -113,7 +117,7 @@ func (a *ClientAPI) ClientStartDeal(ctx context.Context, data cid.Cid, miner add
PayChActor: paych,
Payer: self,
ChannelMessage: paychMsg,
Vouchers: []types.SignedVoucher{*sv},
Vouchers: []*types.SignedVoucher{sv},
},
MinerAddress: miner,
ClientAddress: self,

View File

@ -53,7 +53,7 @@ func (a *PaychAPI) paychCreate(ctx context.Context, from, to address.Address, am
Nonce: nonce,
Method: actors.IAMethods.Exec,
Params: enc,
GasLimit: types.NewInt(1000),
GasLimit: types.NewInt(1000000),
GasPrice: types.NewInt(0),
}
@ -102,7 +102,14 @@ func (a *PaychAPI) PaychList(ctx context.Context) ([]address.Address, error) {
}
func (a *PaychAPI) PaychStatus(ctx context.Context, pch address.Address) (*api.PaychStatus, error) {
panic("nyi")
ci, err := a.PaychMgr.GetChannelInfo(pch)
if err != nil {
return nil, err
}
return &api.PaychStatus{
ControlAddr: ci.ControlAddr,
Direction: api.PCHDir(ci.Direction),
}, nil
}
func (a *PaychAPI) PaychClose(ctx context.Context, addr address.Address) (cid.Cid, error) {
@ -147,12 +154,14 @@ func (a *PaychAPI) PaychVoucherCheckSpendable(ctx context.Context, ch address.Ad
return a.PaychMgr.CheckVoucherSpendable(ctx, ch, sv, secret, proof)
}
func (a *PaychAPI) PaychVoucherAdd(ctx context.Context, ch address.Address, sv *types.SignedVoucher) error {
func (a *PaychAPI) PaychVoucherAdd(ctx context.Context, ch address.Address, sv *types.SignedVoucher, proof []byte) error {
_ = a.PaychMgr.TrackInboundChannel(ctx, ch) // TODO: expose those calls
if err := a.PaychVoucherCheckValid(ctx, ch, sv); err != nil {
return err
}
return a.PaychMgr.AddVoucher(ctx, ch, sv)
return a.PaychMgr.AddVoucher(ctx, ch, sv, proof)
}
// PaychVoucherCreate creates a new signed voucher on the given payment channel
@ -190,7 +199,7 @@ func (a *PaychAPI) paychVoucherCreate(ctx context.Context, pch address.Address,
sv.Signature = sig
if err := a.PaychMgr.AddVoucher(ctx, pch, sv); err != nil {
if err := a.PaychMgr.AddVoucher(ctx, pch, sv, nil); err != nil {
return nil, xerrors.Errorf("failed to persist voucher: %w", err)
}
@ -198,7 +207,17 @@ func (a *PaychAPI) paychVoucherCreate(ctx context.Context, pch address.Address,
}
func (a *PaychAPI) PaychVoucherList(ctx context.Context, pch address.Address) ([]*types.SignedVoucher, error) {
return a.PaychMgr.ListVouchers(ctx, pch)
vi, err := a.PaychMgr.ListVouchers(ctx, pch)
if err != nil {
return nil, err
}
out := make([]*types.SignedVoucher, len(vi))
for k, v := range vi {
out[k] = v.Voucher
}
return out, nil
}
func (a *PaychAPI) PaychVoucherSubmit(ctx context.Context, ch address.Address, sv *types.SignedVoucher) (cid.Cid, error) {

View File

@ -4,16 +4,19 @@ import (
"context"
"fmt"
hamt "github.com/ipfs/go-hamt-ipld"
logging "github.com/ipfs/go-log"
"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"
)
var log = logging.Logger("paych")
type Manager struct {
chain *store.ChainStore
store *Store
@ -118,6 +121,24 @@ func (pm *Manager) CheckVoucherSpendable(ctx context.Context, ch address.Address
return false, err
}
if sv.Extra != nil && proof == nil {
known, err := pm.ListVouchers(ctx, ch)
if err != nil {
return false, err
}
for _, v := range known {
if v.Proof != nil && v.Voucher.Equals(sv) {
log.Info("CheckVoucherSpendable: using stored proof")
proof = v.Proof
break
}
}
if proof == nil {
log.Warn("CheckVoucherSpendable: nil proof for voucher with validation")
}
}
enc, err := actors.SerializeParams(&actors.PCAUpdateChannelStateParams{
Sv: *sv,
Secret: secret,
@ -186,15 +207,15 @@ func (pm *Manager) getPaychOwner(ctx context.Context, ch address.Address) (addre
return address.NewFromBytes(ret.Return)
}
func (pm *Manager) AddVoucher(ctx context.Context, ch address.Address, sv *types.SignedVoucher) error {
func (pm *Manager) AddVoucher(ctx context.Context, ch address.Address, sv *types.SignedVoucher, proof []byte) error {
if err := pm.CheckVoucherValid(ctx, ch, sv); err != nil {
return err
}
return pm.store.AddVoucher(ch, sv)
return pm.store.AddVoucher(ch, sv, proof)
}
func (pm *Manager) ListVouchers(ctx context.Context, ch address.Address) ([]*types.SignedVoucher, error) {
func (pm *Manager) ListVouchers(ctx context.Context, ch address.Address) ([]*VoucherInfo, 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)
@ -208,9 +229,9 @@ func (pm *Manager) NextNonceForLane(ctx context.Context, ch address.Address, lan
var maxnonce uint64
for _, v := range vouchers {
if v.Lane == lane {
if v.Nonce > maxnonce {
maxnonce = v.Nonce
if v.Voucher.Lane == lane {
if v.Voucher.Nonce > maxnonce {
maxnonce = v.Voucher.Nonce
}
}
}

View File

@ -1,6 +1,8 @@
package paych
import (
"bytes"
"errors"
"fmt"
"strings"
@ -14,7 +16,10 @@ import (
"golang.org/x/xerrors"
)
var ErrChannelNotTracked = errors.New("channel not tracked")
func init() {
cbor.RegisterCborType(VoucherInfo{})
cbor.RegisterCborType(ChannelInfo{})
}
@ -34,11 +39,16 @@ const (
DirOutbound = 2
)
type VoucherInfo struct {
Voucher *types.SignedVoucher
Proof []byte
}
type ChannelInfo struct {
Channel address.Address
ControlAddr address.Address
Direction int
Vouchers []*types.SignedVoucher
Vouchers []*VoucherInfo
}
func dskeyForChannel(addr address.Address) datastore.Key {
@ -60,6 +70,9 @@ func (ps *Store) getChannelInfo(addr address.Address) (*ChannelInfo, error) {
k := dskeyForChannel(addr)
b, err := ps.ds.Get(k)
if err == datastore.ErrNotFound {
return nil, ErrChannelNotTracked
}
if err != nil {
return nil, err
}
@ -79,7 +92,7 @@ func (ps *Store) TrackChannel(ch *ChannelInfo) error {
return err
case nil:
return fmt.Errorf("already tracking channel: %s", ch.Channel)
case datastore.ErrNotFound:
case ErrChannelNotTracked:
return ps.putChannelInfo(ch)
}
}
@ -112,18 +125,44 @@ func (ps *Store) ListChannels() ([]address.Address, error) {
return out, nil
}
func (ps *Store) AddVoucher(ch address.Address, sv *types.SignedVoucher) error {
func (ps *Store) AddVoucher(ch address.Address, sv *types.SignedVoucher, proof []byte) error {
ci, err := ps.getChannelInfo(ch)
if err != nil {
return err
}
ci.Vouchers = append(ci.Vouchers, sv)
// look for duplicates
for i, v := range ci.Vouchers {
if !sv.Equals(v.Voucher) {
continue
}
if v.Proof != nil {
if !bytes.Equal(v.Proof, proof) {
log.Warnf("AddVoucher: multiple proofs for single voucher, storing both")
break
}
log.Warnf("AddVoucher: voucher re-added with matching proof")
return nil
}
log.Warnf("AddVoucher: adding proof to stored voucher")
ci.Vouchers[i] = &VoucherInfo{
Voucher: v.Voucher,
Proof: proof,
}
return ps.putChannelInfo(ci)
}
ci.Vouchers = append(ci.Vouchers, &VoucherInfo{
Voucher: sv,
Proof: proof,
})
return ps.putChannelInfo(ci)
}
func (ps *Store) VouchersForPaych(ch address.Address) ([]*types.SignedVoucher, error) {
func (ps *Store) VouchersForPaych(ch address.Address) ([]*VoucherInfo, error) {
ci, err := ps.getChannelInfo(ch)
if err != nil {
return nil, err

View File

@ -129,7 +129,7 @@ func (m *Miner) commitSector(ctx context.Context, sinfo sectorbuilder.SectorSeal
Method: actors.MAMethods.CommitSector,
Params: enc,
Value: types.NewInt(0), // TODO: need to ensure sufficient collateral
GasLimit: types.NewInt(1000 /* i dont know help */),
GasLimit: types.NewInt(100000 /* i dont know help */),
GasPrice: types.NewInt(1),
}