Partial payment integration in deals
This commit is contained in:
parent
bc0bb913f1
commit
4acf6d9d47
13
api/api.go
13
api/api.go
@ -181,7 +181,18 @@ type ActorState struct {
|
|||||||
State interface{}
|
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 {
|
type MinerPower struct {
|
||||||
MinerPower types.BigInt
|
MinerPower types.BigInt
|
||||||
|
@ -29,7 +29,7 @@ type PaymentInfo struct {
|
|||||||
Payer address.Address
|
Payer address.Address
|
||||||
ChannelMessage cid.Cid
|
ChannelMessage cid.Cid
|
||||||
|
|
||||||
Vouchers []types.SignedVoucher
|
Vouchers []*types.SignedVoucher
|
||||||
}
|
}
|
||||||
|
|
||||||
type LaneState struct {
|
type LaneState struct {
|
||||||
|
@ -2,6 +2,7 @@ package deals
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/filecoin-project/go-lotus/chain/types"
|
||||||
"github.com/filecoin-project/go-lotus/storage/sectorblocks"
|
"github.com/filecoin-project/go-lotus/storage/sectorblocks"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
@ -34,6 +35,8 @@ type MinerDeal struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
pricePerByteBlock types.BigInt // how much we want for storing one byte for one block
|
||||||
|
|
||||||
secst *sectorblocks.SectorBlocks
|
secst *sectorblocks.SectorBlocks
|
||||||
full api.FullNode
|
full api.FullNode
|
||||||
|
|
||||||
@ -74,6 +77,8 @@ func NewHandler(ds dtypes.MetadataDS, secst *sectorblocks.SectorBlocks, dag dtyp
|
|||||||
dag: dag,
|
dag: dag,
|
||||||
full: fullNode,
|
full: fullNode,
|
||||||
|
|
||||||
|
pricePerByteBlock: types.NewInt(3), // TODO: allow setting
|
||||||
|
|
||||||
conns: map[cid.Cid]inet.Stream{},
|
conns: map[cid.Cid]inet.Stream{},
|
||||||
|
|
||||||
incoming: make(chan MinerDeal),
|
incoming: make(chan MinerDeal),
|
||||||
|
@ -7,6 +7,8 @@ import (
|
|||||||
unixfile "github.com/ipfs/go-unixfs/file"
|
unixfile "github.com/ipfs/go-unixfs/file"
|
||||||
"golang.org/x/xerrors"
|
"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/lib/sectorbuilder"
|
||||||
"github.com/filecoin-project/go-lotus/storage/sectorblocks"
|
"github.com/filecoin-project/go-lotus/storage/sectorblocks"
|
||||||
)
|
)
|
||||||
@ -39,10 +41,58 @@ func (h *Handler) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal),
|
|||||||
return nil, xerrors.Errorf("deal proposal with unsupported serialization: %s", deal.Proposal.SerializationMode)
|
return nil, xerrors.Errorf("deal proposal with unsupported serialization: %s", deal.Proposal.SerializationMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check payment
|
curHead, err := h.full.ChainHead(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, voucher := range deal.Proposal.Payment.Vouchers {
|
||||||
|
err := h.full.PaychVoucherCheckValid(ctx, deal.Proposal.Payment.PayChActor, voucher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("validating payment voucher %d: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if voucher.Extra != nil { // TODO: should we reject vouchers with no extra params?
|
||||||
|
if voucher.Extra.Actor != deal.Proposal.MinerAddress {
|
||||||
|
return nil, 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 nil, xerrors.Errorf("validating payment voucher %d: expected extra method %d, got %d", i, actors.MAMethods.PaymentVerifyInclusion, voucher.Extra.Method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if voucher.MinCloseHeight > curHead.Height() + deal.Proposal.Duration {
|
||||||
|
return nil, xerrors.Errorf("validating payment voucher %d: MinCloseHeight too high (%d), max expected: %d", i, voucher.MinCloseHeight, curHead.Height() + deal.Proposal.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
if voucher.TimeLock > curHead.Height() + deal.Proposal.Duration {
|
||||||
|
return nil, xerrors.Errorf("validating payment voucher %d: TimeLock too high (%d), max expected: %d", i, voucher.TimeLock, curHead.Height() + deal.Proposal.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(voucher.Merges) > 0 {
|
||||||
|
return nil, 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 nil, 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 nil, xerrors.Errorf("validating payment voucher %d: minimum price: %s", i, minPrice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, voucher := range deal.Proposal.Payment.Vouchers {
|
||||||
|
if err := h.full.PaychVoucherAdd(ctx, deal.Proposal.Payment.PayChActor, voucher); err != nil {
|
||||||
|
return nil, xerrors.Errorf("consuming payment voucher %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Info("fetching data for a deal")
|
log.Info("fetching data for a deal")
|
||||||
err := h.sendSignedResponse(StorageDealResponse{
|
err = h.sendSignedResponse(StorageDealResponse{
|
||||||
State: Accepted,
|
State: Accepted,
|
||||||
Message: "",
|
Message: "",
|
||||||
Proposal: deal.ProposalCid,
|
Proposal: deal.ProposalCid,
|
||||||
|
@ -3,31 +3,32 @@ package full
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"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"
|
"os"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-lotus/api"
|
"github.com/ipfs/go-blockservice"
|
||||||
"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-cid"
|
"github.com/ipfs/go-cid"
|
||||||
"github.com/ipfs/go-filestore"
|
"github.com/ipfs/go-filestore"
|
||||||
chunker "github.com/ipfs/go-ipfs-chunker"
|
chunker "github.com/ipfs/go-ipfs-chunker"
|
||||||
|
offline "github.com/ipfs/go-ipfs-exchange-offline"
|
||||||
files "github.com/ipfs/go-ipfs-files"
|
files "github.com/ipfs/go-ipfs-files"
|
||||||
cbor "github.com/ipfs/go-ipld-cbor"
|
cbor "github.com/ipfs/go-ipld-cbor"
|
||||||
ipld "github.com/ipfs/go-ipld-format"
|
ipld "github.com/ipfs/go-ipld-format"
|
||||||
|
"github.com/ipfs/go-merkledag"
|
||||||
"github.com/ipfs/go-unixfs/importer/balanced"
|
"github.com/ipfs/go-unixfs/importer/balanced"
|
||||||
ihelper "github.com/ipfs/go-unixfs/importer/helpers"
|
ihelper "github.com/ipfs/go-unixfs/importer/helpers"
|
||||||
"github.com/libp2p/go-libp2p-core/peer"
|
"github.com/libp2p/go-libp2p-core/peer"
|
||||||
"go.uber.org/fx"
|
"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 {
|
type ClientAPI struct {
|
||||||
@ -40,6 +41,7 @@ type ClientAPI struct {
|
|||||||
DealClient *deals.Client
|
DealClient *deals.Client
|
||||||
RetDiscovery discovery.PeerResolver
|
RetDiscovery discovery.PeerResolver
|
||||||
Retrieval *retrieval.Client
|
Retrieval *retrieval.Client
|
||||||
|
Chain *store.ChainStore
|
||||||
|
|
||||||
LocalDAG dtypes.ClientDAG
|
LocalDAG dtypes.ClientDAG
|
||||||
Blockstore dtypes.ClientBlockstore
|
Blockstore dtypes.ClientBlockstore
|
||||||
@ -88,16 +90,18 @@ func (a *ClientAPI) ClientStartDeal(ctx context.Context, data cid.Cid, miner add
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
voucher := types.SignedVoucher{
|
head := a.Chain.GetHeaviestTipSet()
|
||||||
// TimeLock: 0, // TODO: do we want to use this somehow?
|
|
||||||
|
voucher := types.SignedVoucher{ // TODO: split into smaller payments
|
||||||
|
TimeLock: head.Height() + blocksDuration,
|
||||||
Extra: &types.ModVerifyParams{
|
Extra: &types.ModVerifyParams{
|
||||||
Actor: miner,
|
Actor: miner,
|
||||||
Method: actors.MAMethods.PaymentVerifyInclusion,
|
Method: actors.MAMethods.PaymentVerifyInclusion,
|
||||||
Data: voucherData,
|
Data: voucherData,
|
||||||
},
|
},
|
||||||
Lane: 0,
|
Lane: 0, // TODO: some api to make this easy
|
||||||
Amount: total,
|
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)
|
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,
|
PayChActor: paych,
|
||||||
Payer: self,
|
Payer: self,
|
||||||
ChannelMessage: paychMsg,
|
ChannelMessage: paychMsg,
|
||||||
Vouchers: []types.SignedVoucher{*sv},
|
Vouchers: []*types.SignedVoucher{sv},
|
||||||
},
|
},
|
||||||
MinerAddress: miner,
|
MinerAddress: miner,
|
||||||
ClientAddress: self,
|
ClientAddress: self,
|
||||||
|
@ -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) {
|
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) {
|
func (a *PaychAPI) PaychClose(ctx context.Context, addr address.Address) (cid.Cid, error) {
|
||||||
@ -148,6 +155,8 @@ func (a *PaychAPI) PaychVoucherCheckSpendable(ctx context.Context, ch address.Ad
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) error {
|
||||||
|
_ = a.PaychMgr.TrackInboundChannel(ctx, ch)
|
||||||
|
|
||||||
if err := a.PaychVoucherCheckValid(ctx, ch, sv); err != nil {
|
if err := a.PaychVoucherCheckValid(ctx, ch, sv); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package paych
|
package paych
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -14,6 +15,8 @@ import (
|
|||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrChannelNotTracked = errors.New("channel not tracked")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cbor.RegisterCborType(ChannelInfo{})
|
cbor.RegisterCborType(ChannelInfo{})
|
||||||
}
|
}
|
||||||
@ -60,6 +63,9 @@ func (ps *Store) getChannelInfo(addr address.Address) (*ChannelInfo, error) {
|
|||||||
k := dskeyForChannel(addr)
|
k := dskeyForChannel(addr)
|
||||||
|
|
||||||
b, err := ps.ds.Get(k)
|
b, err := ps.ds.Get(k)
|
||||||
|
if err == datastore.ErrNotFound {
|
||||||
|
return nil, ErrChannelNotTracked
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -79,7 +85,7 @@ func (ps *Store) TrackChannel(ch *ChannelInfo) error {
|
|||||||
return err
|
return err
|
||||||
case nil:
|
case nil:
|
||||||
return fmt.Errorf("already tracking channel: %s", ch.Channel)
|
return fmt.Errorf("already tracking channel: %s", ch.Channel)
|
||||||
case datastore.ErrNotFound:
|
case ErrChannelNotTracked:
|
||||||
return ps.putChannelInfo(ch)
|
return ps.putChannelInfo(ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user