diff --git a/api/api.go b/api/api.go index 1c67c5c2d..0946e15d1 100644 --- a/api/api.go +++ b/api/api.go @@ -9,6 +9,7 @@ import ( "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" + "github.com/filecoin-project/go-lotus/chain/actors" "github.com/filecoin-project/go-lotus/chain/address" "github.com/filecoin-project/go-lotus/chain/store" "github.com/filecoin-project/go-lotus/chain/types" @@ -122,7 +123,8 @@ type FullNode interface { PaychStatus(context.Context, address.Address) (*PaychStatus, error) PaychClose(context.Context, address.Address) (cid.Cid, error) PaychAllocateLane(ctx context.Context, ch address.Address) (uint64, error) - PaychNewPayment(ctx context.Context, from, to address.Address, amount types.BigInt, extra *types.ModVerifyParams, tl uint64, minClose uint64) (*PaymentInfo, error) + PaychLaneState(ctx context.Context, ch address.Address, lane uint64) (actors.LaneState, error) + PaychNewPayment(ctx context.Context, from, to address.Address, vouchers []VoucherSpec) (*PaymentInfo, 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) @@ -229,7 +231,15 @@ type ChannelInfo struct { type PaymentInfo struct { Channel address.Address ChannelMessage *cid.Cid - Voucher *types.SignedVoucher + Vouchers []*types.SignedVoucher +} + +type VoucherSpec struct { + Amount types.BigInt + TimeLock uint64 + MinClose uint64 + + Extra *types.ModVerifyParams } type MinerPower struct { diff --git a/api/struct.go b/api/struct.go index e6db41d14..fd72a4fee 100644 --- a/api/struct.go +++ b/api/struct.go @@ -3,15 +3,15 @@ package api import ( "context" + sectorbuilder "github.com/filecoin-project/go-sectorbuilder" + "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/filecoin-project/go-lotus/chain/actors" "github.com/filecoin-project/go-lotus/chain/address" "github.com/filecoin-project/go-lotus/chain/store" "github.com/filecoin-project/go-lotus/chain/types" - sectorbuilder "github.com/filecoin-project/go-sectorbuilder" - - "github.com/ipfs/go-cid" - "github.com/libp2p/go-libp2p-core/peer" ) // All permissions are listed in permissioned.go @@ -86,19 +86,20 @@ type FullNodeStruct struct { StateGetActor func(context.Context, address.Address, *types.TipSet) (*types.Actor, error) `perm:"read"` StateReadState func(context.Context, *types.Actor, *types.TipSet) (*ActorState, error) `perm:"read"` - PaychGet func(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*ChannelInfo, 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"` - PaychAllocateLane func(context.Context, address.Address) (uint64, error) `perm:"sign"` - PaychNewPayment func(ctx context.Context, from, to address.Address, amount types.BigInt, extra *types.ModVerifyParams, tl uint64, minClose uint64) (*PaymentInfo, 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, []byte, types.BigInt) (types.BigInt, 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"` + PaychGet func(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*ChannelInfo, 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"` + PaychAllocateLane func(context.Context, address.Address) (uint64, error) `perm:"sign"` + PaychNewPayment func(ctx context.Context, from, to address.Address, vouchers []VoucherSpec) (*PaymentInfo, error) `perm:"sign"` + PaychLaneState func(ctx context.Context, ch address.Address, lane uint64) (actors.LaneState, error) `perm:"write"` + 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, []byte, types.BigInt) (types.BigInt, 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"` } } @@ -363,8 +364,12 @@ func (c *FullNodeStruct) PaychAllocateLane(ctx context.Context, ch address.Addre return c.Internal.PaychAllocateLane(ctx, ch) } -func (c *FullNodeStruct) PaychNewPayment(ctx context.Context, from, to address.Address, amount types.BigInt, extra *types.ModVerifyParams, tl uint64, minClose uint64) (*PaymentInfo, error) { - return c.Internal.PaychNewPayment(ctx, from, to, amount, extra, tl, minClose) +func (c *FullNodeStruct) PaychNewPayment(ctx context.Context, from, to address.Address, vouchers []VoucherSpec) (*PaymentInfo, error) { + return c.Internal.PaychNewPayment(ctx, from, to, vouchers) +} + +func (c *FullNodeStruct) PaychLaneState(ctx context.Context, ch address.Address, lane uint64) (actors.LaneState, error) { + return c.Internal.PaychLaneState(ctx, ch, lane) } func (c *FullNodeStruct) PaychVoucherSubmit(ctx context.Context, ch address.Address, sv *types.SignedVoucher) (cid.Cid, error) { diff --git a/build/params.go b/build/params.go index 7a5c577ba..19611fb43 100644 --- a/build/params.go +++ b/build/params.go @@ -19,6 +19,11 @@ const PaymentChannelClosingDelay = 6 * 60 * 2 // six hours // Blocks const DealVoucherSkewLimit = 10 +// Blocks +const MinDealVoucherIncrement = ProvingPeriodDuration + +const MaxVouchersPerDeal = 768 // roughly one voucher per 10h over a year + // ///// // Consensus / Network diff --git a/chain/deals/handler_states.go b/chain/deals/handler_states.go index 71d9d4583..b301df20d 100644 --- a/chain/deals/handler_states.go +++ b/chain/deals/handler_states.go @@ -3,16 +3,17 @@ package deals import ( "bytes" "context" - "github.com/filecoin-project/go-lotus/api" - "github.com/filecoin-project/go-lotus/build" - "github.com/filecoin-project/go-sectorbuilder/sealing_state" + "github.com/filecoin-project/go-sectorbuilder/sealing_state" 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/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/types" "github.com/filecoin-project/go-lotus/lib/sectorbuilder" "github.com/filecoin-project/go-lotus/storage/sectorblocks" @@ -42,11 +43,25 @@ func (h *Handler) handle(ctx context.Context, deal MinerDeal, cb minerHandlerFun // ACCEPTED -func (h *Handler) validateVouchers(ctx context.Context, deal MinerDeal) error { +func (h *Handler) validateVouchers(ctx context.Context, deal MinerDeal, paych address.Address) error { curHead, err := h.full.ChainHead(ctx) if err != nil { return err } + if len(deal.Proposal.Payment.Vouchers) == 0 { + return xerrors.Errorf("no payment vouchers for deal") + } + + increments := deal.Proposal.Duration / uint64(len(deal.Proposal.Payment.Vouchers)) + + startH := deal.Proposal.Payment.Vouchers[0].TimeLock - (deal.Proposal.Duration / increments) + if startH > curHead.Height()+build.DealVoucherSkewLimit { + return xerrors.Errorf("deal starts too far into the future") + } + + vspec := VoucherSpec(deal.Proposal.Duration, deal.Proposal.TotalPrice, startH, nil) + + lane := deal.Proposal.Payment.Vouchers[0].Lane for i, voucher := range deal.Proposal.Payment.Vouchers { err := h.full.PaychVoucherCheckValid(ctx, deal.Proposal.Payment.PayChActor, voucher) @@ -76,7 +91,7 @@ func (h *Handler) validateVouchers(ctx context.Context, deal MinerDeal) error { return xerrors.Errorf("validating payment voucher %d: paych challenge commP didn't match deal proposal", i) } - maxClose := curHead.Height() + deal.Proposal.Duration + build.DealVoucherSkewLimit + maxClose := curHead.Height() + (increments * uint64(i+1)) + build.DealVoucherSkewLimit if voucher.MinCloseHeight > maxClose { return xerrors.Errorf("validating payment voucher %d: MinCloseHeight too high (%d), max expected: %d", i, voucher.MinCloseHeight, maxClose) } @@ -88,17 +103,33 @@ func (h *Handler) validateVouchers(ctx context.Context, deal MinerDeal) error { 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 voucher.Amount.LessThan(deal.Proposal.TotalPrice) { - return xerrors.Errorf("validating payment voucher %d: not enough funds in the voucher", i) + if voucher.Amount.LessThan(vspec[i].Amount) { + return xerrors.Errorf("validating payment voucher %d: not enough funds in the voucher: %s < %s; vl=%d vsl=%d", i, voucher.Amount, vspec[i].Amount, len(deal.Proposal.Payment.Vouchers), len(vspec)) } + } - 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) - } + 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("minimum price: %s", minPrice) + } + + // TODO: This is mildly racy, we need a way to check lane and submit voucher + // atomically + laneState, err := h.full.PaychLaneState(ctx, paych, lane) + if err != nil { + return xerrors.Errorf("looking up payment channel lane: %w", err) + } + + if laneState.Closed { + return xerrors.New("lane closed") + } + + if laneState.Redeemed.GreaterThan(types.NewInt(0)) { + return xerrors.New("used lanes unsupported") + } + + if laneState.Nonce > 0 { + return xerrors.New("used lanes unsupported") } return nil @@ -120,7 +151,7 @@ func (h *Handler) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal), } } - if err := h.validateVouchers(ctx, deal); err != nil { + if err := h.validateVouchers(ctx, deal, deal.Proposal.Payment.PayChActor); err != nil { return nil, err } diff --git a/chain/deals/vouchers.go b/chain/deals/vouchers.go new file mode 100644 index 000000000..a8b3f33c8 --- /dev/null +++ b/chain/deals/vouchers.go @@ -0,0 +1,31 @@ +package deals + +import ( + "github.com/filecoin-project/go-lotus/api" + "github.com/filecoin-project/go-lotus/build" + "github.com/filecoin-project/go-lotus/chain/types" +) + +func VoucherSpec(blocksDuration uint64, price types.BigInt, start uint64, extra *types.ModVerifyParams) []api.VoucherSpec { + nVouchers := blocksDuration / build.MinDealVoucherIncrement + if nVouchers < 1 { + nVouchers = 1 + } + if nVouchers > build.MaxVouchersPerDeal { + nVouchers = build.MaxVouchersPerDeal + } + + hIncrements := blocksDuration / nVouchers + vouchers := make([]api.VoucherSpec, nVouchers) + + for i := uint64(0); i < nVouchers; i++ { + vouchers[i] = api.VoucherSpec{ + Amount: types.BigDiv(types.BigMul(price, types.NewInt(i+1)), types.NewInt(nVouchers)), + TimeLock: start + (hIncrements * (i + 1)), + MinClose: start + (hIncrements * (i + 1)), + Extra: extra, + } + } + + return vouchers +} diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index a2fae8c63..143524f39 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -3,12 +3,11 @@ package stmgr import ( "context" "fmt" - "github.com/filecoin-project/go-lotus/chain/actors" "github.com/filecoin-project/go-lotus/chain/types" "github.com/filecoin-project/go-lotus/chain/vm" - cid "github.com/ipfs/go-cid" + "github.com/ipfs/go-cid" "golang.org/x/xerrors" ) diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 1d1afd846..798637e00 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -182,5 +182,4 @@ func (sm *StateManager) LoadActorState(ctx context.Context, a address.Address, o } return act, nil - } diff --git a/node/impl/client/client.go b/node/impl/client/client.go index e03cda45d..2bc8128ee 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -96,8 +96,9 @@ func (a *API) ClientStartDeal(ctx context.Context, data cid.Cid, miner address.A } head := a.Chain.GetHeaviestTipSet() + vouchers := deals.VoucherSpec(blocksDuration, total, head.Height(), extra) - payment, err := a.PaychNewPayment(ctx, self, miner, total, extra, head.Height()+blocksDuration, head.Height()+blocksDuration) + payment, err := a.PaychNewPayment(ctx, self, miner, vouchers) if err != nil { return nil, err } @@ -110,7 +111,7 @@ func (a *API) ClientStartDeal(ctx context.Context, data cid.Cid, miner address.A PayChActor: payment.Channel, Payer: self, ChannelMessage: payment.ChannelMessage, - Vouchers: []*types.SignedVoucher{payment.Voucher}, + Vouchers: payment.Vouchers, }, MinerAddress: miner, ClientAddress: self, diff --git a/node/impl/paych/paych.go b/node/impl/paych/paych.go index 15c89befe..6a6880727 100644 --- a/node/impl/paych/paych.go +++ b/node/impl/paych/paych.go @@ -42,8 +42,11 @@ func (a *PaychAPI) PaychAllocateLane(ctx context.Context, ch address.Address) (u return a.PaychMgr.AllocateLane(ch) } -func (a *PaychAPI) PaychNewPayment(ctx context.Context, from, to address.Address, amount types.BigInt, extra *types.ModVerifyParams, tl uint64, minClose uint64) (*api.PaymentInfo, error) { +func (a *PaychAPI) PaychNewPayment(ctx context.Context, from, to address.Address, vouchers []api.VoucherSpec) (*api.PaymentInfo, error) { + amount := vouchers[len(vouchers)-1].Amount + // TODO: Fix free fund tracking in PaychGet + // TODO: validate voucher spec before locking funds ch, err := a.PaychGet(ctx, from, to, amount) if err != nil { return nil, err @@ -54,17 +57,24 @@ func (a *PaychAPI) PaychNewPayment(ctx context.Context, from, to address.Address return nil, err } - sv, err := a.paychVoucherCreate(ctx, ch.Channel, types.SignedVoucher{ - Amount: amount, - Lane: lane, + svs := make([]*types.SignedVoucher, len(vouchers)) - Extra: extra, - TimeLock: tl, - MinCloseHeight: minClose, - }) - if err != nil { - return nil, err + for i, v := range vouchers { + sv, err := a.paychVoucherCreate(ctx, ch.Channel, types.SignedVoucher{ + Amount: v.Amount, + Lane: lane, + + Extra: v.Extra, + TimeLock: v.TimeLock, + MinCloseHeight: v.MinClose, + }) + if err != nil { + return nil, err + } + + svs[i] = sv } + var pchCid *cid.Cid if ch.ChannelMessage != cid.Undef { pchCid = &ch.ChannelMessage @@ -73,7 +83,7 @@ func (a *PaychAPI) PaychNewPayment(ctx context.Context, from, to address.Address return &api.PaymentInfo{ Channel: ch.Channel, ChannelMessage: pchCid, - Voucher: sv, + Vouchers: svs, }, nil } @@ -126,6 +136,10 @@ func (a *PaychAPI) PaychClose(ctx context.Context, addr address.Address) (cid.Ci return smsg.Cid(), nil } +func (a *PaychAPI) PaychLaneState(ctx context.Context, ch address.Address, lane uint64) (actors.LaneState, error) { + return a.PaychMgr.LaneState(ctx, ch, lane) +} + func (a *PaychAPI) PaychVoucherCheckValid(ctx context.Context, ch address.Address, sv *types.SignedVoucher) error { return a.PaychMgr.CheckVoucherValid(ctx, ch, sv) } diff --git a/paych/paych.go b/paych/paych.go index 579c6cb63..302990ae6 100644 --- a/paych/paych.go +++ b/paych/paych.go @@ -221,16 +221,6 @@ func (pm *Manager) CheckVoucherSpendable(ctx context.Context, ch address.Address return true, nil } -func (pm *Manager) loadPaychState(ctx context.Context, ch address.Address) (*types.Actor, *actors.PaymentChannelActorState, error) { - var pcast actors.PaymentChannelActorState - act, err := pm.sm.LoadActorState(ctx, ch, &pcast, nil) - if 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 := pm.sm.Call(ctx, &types.Message{ From: ch, diff --git a/paych/state.go b/paych/state.go new file mode 100644 index 000000000..801eec237 --- /dev/null +++ b/paych/state.go @@ -0,0 +1,72 @@ +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/types" +) + +func (pm *Manager) loadPaychState(ctx context.Context, ch address.Address) (*types.Actor, *actors.PaymentChannelActorState, error) { + var pcast actors.PaymentChannelActorState + act, err := pm.sm.LoadActorState(ctx, ch, &pcast, nil) + if err != nil { + return nil, nil, err + } + + return act, &pcast, nil +} + +func (pm *Manager) LaneState(ctx context.Context, ch address.Address, lane uint64) (actors.LaneState, error) { + _, state, err := pm.loadPaychState(ctx, ch) + if err != nil { + return actors.LaneState{}, err + } + + // TODO: we probably want to call UpdateChannelState with all vouchers to be fully correct + // (but technically dont't need to) + // TODO: make sure this is correct + + ls, ok := state.LaneStates[fmt.Sprintf("%d", lane)] + if !ok { + ls = &actors.LaneState{ + Closed: false, + Redeemed: types.NewInt(0), + Nonce: 0, + } + } + + if ls.Closed { + return actors.LaneState{}, nil + } + + vouchers, err := pm.store.VouchersForPaych(ch) + if err != nil { + if err == ErrChannelNotTracked { + return *ls, nil + } + return actors.LaneState{}, err + } + + for _, v := range vouchers { + for range v.Voucher.Merges { + panic("merges todo") + } + + if v.Voucher.Lane != lane { + continue + } + + if v.Voucher.Nonce < ls.Nonce { + log.Warnf("Found outdated voucher: ch=%s, lane=%d, v.nonce=%d lane.nonce=%d", ch, lane, v.Voucher.Nonce, ls.Nonce) + continue + } + + ls.Nonce = v.Voucher.Nonce + ls.Redeemed = v.Voucher.Amount + } + + return *ls, nil +} diff --git a/retrieval/client.go b/retrieval/client.go index 611cad012..7a4d5b369 100644 --- a/retrieval/client.go +++ b/retrieval/client.go @@ -272,6 +272,6 @@ func (cst *clientStream) setupPayment(ctx context.Context, toSend types.BigInt) return api.PaymentInfo{ Channel: cst.paych, ChannelMessage: nil, - Voucher: sv, + Vouchers: []*types.SignedVoucher{sv}, }, nil } diff --git a/retrieval/miner.go b/retrieval/miner.go index 80234aad0..9bb038e42 100644 --- a/retrieval/miner.go +++ b/retrieval/miner.go @@ -122,8 +122,12 @@ func (hnd *handlerDeal) handleNext() (bool, error) { unixfs0 := deal.Params.Unixfs0 + if len(deal.Payment.Vouchers) != 1 { + return false, xerrors.Errorf("expected one signed voucher, got %d", len(deal.Payment.Vouchers)) + } + expPayment := types.BigMul(hnd.m.pricePerByte, types.NewInt(deal.Params.Unixfs0.Size)) - if _, err := hnd.m.full.PaychVoucherAdd(context.TODO(), deal.Payment.Channel, deal.Payment.Voucher, nil, expPayment); err != nil { + if _, err := hnd.m.full.PaychVoucherAdd(context.TODO(), deal.Payment.Channel, deal.Payment.Vouchers[0], nil, expPayment); err != nil { return false, xerrors.Errorf("processing retrieval payment: %w", err) }