diff --git a/api/api.go b/api/api.go index 6d460f0b2..9610ec674 100644 --- a/api/api.go +++ b/api/api.go @@ -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 diff --git a/chain/actors/actor_paych.go b/chain/actors/actor_paych.go index f6ca71763..f4f1514be 100644 --- a/chain/actors/actor_paych.go +++ b/chain/actors/actor_paych.go @@ -29,7 +29,7 @@ type PaymentInfo struct { Payer address.Address ChannelMessage cid.Cid - Vouchers []types.SignedVoucher + Vouchers []*types.SignedVoucher } type LaneState struct { diff --git a/chain/deals/handler.go b/chain/deals/handler.go index f4e33c6b6..602ccc15f 100644 --- a/chain/deals/handler.go +++ b/chain/deals/handler.go @@ -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), diff --git a/chain/deals/handler_states.go b/chain/deals/handler_states.go index 12ca2d002..66153add4 100644 --- a/chain/deals/handler_states.go +++ b/chain/deals/handler_states.go @@ -7,6 +7,8 @@ import ( 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" ) @@ -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) } - // 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") - err := h.sendSignedResponse(StorageDealResponse{ + err = h.sendSignedResponse(StorageDealResponse{ State: Accepted, Message: "", Proposal: deal.ProposalCid, diff --git a/node/impl/full/client.go b/node/impl/full/client.go index f70fa21ad..fc5ec53f5 100644 --- a/node/impl/full/client.go +++ b/node/impl/full/client.go @@ -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, diff --git a/node/impl/full/paych.go b/node/impl/full/paych.go index eed7564c7..eebd91350 100644 --- a/node/impl/full/paych.go +++ b/node/impl/full/paych.go @@ -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) { @@ -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 { + _ = a.PaychMgr.TrackInboundChannel(ctx, ch) + if err := a.PaychVoucherCheckValid(ctx, ch, sv); err != nil { return err } diff --git a/paych/store.go b/paych/store.go index 9198208f9..4356a429e 100644 --- a/paych/store.go +++ b/paych/store.go @@ -1,6 +1,7 @@ package paych import ( + "errors" "fmt" "strings" @@ -14,6 +15,8 @@ import ( "golang.org/x/xerrors" ) +var ErrChannelNotTracked = errors.New("channel not tracked") + func init() { cbor.RegisterCborType(ChannelInfo{}) } @@ -60,6 +63,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 +85,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) } }