Paych improvements; Retrieval payments
This commit is contained in:
@@ -1,257 +0,0 @@
|
||||
package full
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"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 {
|
||||
fx.In
|
||||
|
||||
ChainAPI
|
||||
StateAPI
|
||||
WalletAPI
|
||||
PaychAPI
|
||||
|
||||
DealClient *deals.Client
|
||||
RetDiscovery discovery.PeerResolver
|
||||
Retrieval *retrieval.Client
|
||||
Chain *store.ChainStore
|
||||
|
||||
LocalDAG dtypes.ClientDAG
|
||||
Blockstore dtypes.ClientBlockstore
|
||||
Filestore dtypes.ClientFilestore `optional:"true"`
|
||||
}
|
||||
|
||||
func (a *ClientAPI) ClientStartDeal(ctx context.Context, data cid.Cid, miner address.Address, price types.BigInt, blocksDuration uint64) (*cid.Cid, error) {
|
||||
// TODO: make this a param
|
||||
self, err := a.WalletDefaultAddress(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get miner peerID
|
||||
msg := &types.Message{
|
||||
To: miner,
|
||||
From: miner,
|
||||
Method: actors.MAMethods.GetPeerID,
|
||||
}
|
||||
|
||||
r, err := a.StateCall(ctx, msg, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pid, err := peer.IDFromBytes(r.Return)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vd, err := a.DealClient.VerifyParams(ctx, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
voucherData, err := cbor.DumpObject(vd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// setup payments
|
||||
total := types.BigMul(price, types.NewInt(blocksDuration))
|
||||
|
||||
// TODO: at least ping the miner before creating paych / locking the money
|
||||
extra := &types.ModVerifyParams{
|
||||
Actor: miner,
|
||||
Method: actors.MAMethods.PaymentVerifyInclusion,
|
||||
Data: voucherData,
|
||||
}
|
||||
|
||||
head := a.Chain.GetHeaviestTipSet()
|
||||
payment, err := a.PaychNewPayment(ctx, self, miner, total, extra, head.Height()+blocksDuration, head.Height()+blocksDuration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proposal := deals.ClientDealProposal{
|
||||
Data: data,
|
||||
TotalPrice: total,
|
||||
Duration: blocksDuration,
|
||||
Payment: actors.PaymentInfo{
|
||||
PayChActor: payment.Channel,
|
||||
Payer: self,
|
||||
ChannelMessage: payment.ChannelMessage,
|
||||
Vouchers: []*types.SignedVoucher{payment.Voucher},
|
||||
},
|
||||
MinerAddress: miner,
|
||||
ClientAddress: self,
|
||||
MinerID: pid,
|
||||
}
|
||||
|
||||
c, err := a.DealClient.Start(ctx, proposal, vd)
|
||||
// TODO: send updated voucher with PaymentVerifySector for cheaper validation (validate the sector the miner sent us first!)
|
||||
return &c, err
|
||||
}
|
||||
|
||||
func (a *ClientAPI) ClientListDeals(ctx context.Context) ([]api.DealInfo, error) {
|
||||
deals, err := a.DealClient.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make([]api.DealInfo, len(deals))
|
||||
for k, v := range deals {
|
||||
out[k] = api.DealInfo{
|
||||
ProposalCid: v.ProposalCid,
|
||||
State: v.State,
|
||||
Miner: v.Proposal.MinerAddress,
|
||||
|
||||
PieceRef: v.Proposal.PieceRef,
|
||||
CommP: v.Proposal.CommP,
|
||||
Size: v.Proposal.Size,
|
||||
|
||||
TotalPrice: v.Proposal.TotalPrice,
|
||||
Duration: v.Proposal.Duration,
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *ClientAPI) ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error) {
|
||||
// TODO: check if we have the ENTIRE dag
|
||||
|
||||
offExch := merkledag.NewDAGService(blockservice.New(a.Blockstore, offline.Exchange(a.Blockstore)))
|
||||
_, err := offExch.Get(ctx, root)
|
||||
if err == ipld.ErrNotFound {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *ClientAPI) ClientFindData(ctx context.Context, root cid.Cid) ([]api.QueryOffer, error) {
|
||||
peers, err := a.RetDiscovery.GetPeers(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make([]api.QueryOffer, len(peers))
|
||||
for k, p := range peers {
|
||||
out[k] = a.Retrieval.Query(ctx, p, root)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *ClientAPI) ClientImport(ctx context.Context, path string) (cid.Cid, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
file, err := files.NewReaderPathFile(path, f, stat)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
bufferedDS := ipld.NewBufferedDAG(ctx, a.LocalDAG)
|
||||
|
||||
params := ihelper.DagBuilderParams{
|
||||
Maxlinks: build.UnixfsLinksPerLevel,
|
||||
RawLeaves: true,
|
||||
CidBuilder: nil,
|
||||
Dagserv: bufferedDS,
|
||||
NoCopy: true,
|
||||
}
|
||||
|
||||
db, err := params.New(chunker.NewSizeSplitter(file, int64(build.UnixfsChunkSize)))
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
nd, err := balanced.Layout(db)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
return nd.Cid(), bufferedDS.Commit()
|
||||
}
|
||||
|
||||
func (a *ClientAPI) ClientListImports(ctx context.Context) ([]api.Import, error) {
|
||||
if a.Filestore == nil {
|
||||
return nil, errors.New("listing imports is not supported with in-memory dag yet")
|
||||
}
|
||||
next, err := filestore.ListAll(a.Filestore, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: make this less very bad by tracking root cids instead of using ListAll
|
||||
|
||||
out := make([]api.Import, 0)
|
||||
for {
|
||||
r := next()
|
||||
if r == nil {
|
||||
return out, nil
|
||||
}
|
||||
if r.Offset != 0 {
|
||||
continue
|
||||
}
|
||||
out = append(out, api.Import{
|
||||
Status: r.Status,
|
||||
Key: r.Key,
|
||||
FilePath: r.FilePath,
|
||||
Size: r.Size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ClientAPI) ClientRetrieve(ctx context.Context, order api.RetrievalOrder, path string) error {
|
||||
outFile, err := os.OpenFile(path, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = a.Retrieval.RetrieveUnixfs(ctx, order.Root, order.Size, order.MinerPeerID, order.Miner, outFile)
|
||||
if err != nil {
|
||||
_ = outFile.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return outFile.Close()
|
||||
}
|
||||
|
||||
func (a *ClientAPI) ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*types.SignedStorageAsk, error) {
|
||||
return a.DealClient.QueryAsk(ctx, p, miner)
|
||||
}
|
||||
@@ -1,334 +0,0 @@
|
||||
package full
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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/types"
|
||||
"github.com/filecoin-project/go-lotus/paych"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"go.uber.org/fx"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type PaychAPI struct {
|
||||
fx.In
|
||||
|
||||
MpoolAPI
|
||||
WalletAPI
|
||||
ChainAPI
|
||||
|
||||
PaychMgr *paych.Manager
|
||||
}
|
||||
|
||||
func (a *PaychAPI) PaychCreate(ctx context.Context, from, to address.Address, amt types.BigInt) (*api.ChannelInfo, error) {
|
||||
params, aerr := actors.SerializeParams(&actors.PCAConstructorParams{To: to})
|
||||
if aerr != nil {
|
||||
return nil, aerr
|
||||
}
|
||||
|
||||
nonce, err := a.MpoolGetNonce(ctx, from)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enc, err := actors.SerializeParams(&actors.ExecParams{
|
||||
Params: params,
|
||||
Code: actors.PaymentChannelActorCodeCid,
|
||||
})
|
||||
|
||||
msg := &types.Message{
|
||||
To: actors.InitActorAddress,
|
||||
From: from,
|
||||
Value: amt,
|
||||
Nonce: nonce,
|
||||
Method: actors.IAMethods.Exec,
|
||||
Params: enc,
|
||||
GasLimit: types.NewInt(1000000),
|
||||
GasPrice: types.NewInt(0),
|
||||
}
|
||||
|
||||
smsg, err := a.WalletSignMessage(ctx, from, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := a.MpoolPush(ctx, smsg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mcid := smsg.Cid()
|
||||
mwait, err := a.ChainWaitMsg(ctx, mcid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if mwait.Receipt.ExitCode != 0 {
|
||||
return nil, fmt.Errorf("payment channel creation failed (exit code %d)", mwait.Receipt.ExitCode)
|
||||
}
|
||||
|
||||
paychaddr, err := address.NewFromBytes(mwait.Receipt.Return)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := a.PaychMgr.TrackOutboundChannel(ctx, paychaddr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.ChannelInfo{
|
||||
Channel: paychaddr,
|
||||
ChannelMessage: mcid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *PaychAPI) PaychNewPayment(ctx context.Context, from, to address.Address, amount types.BigInt, extra *types.ModVerifyParams, tl uint64, minClose uint64) (*api.PaymentInfo, error) {
|
||||
ch, err := a.PaychMgr.OutboundChanTo(from, to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var chMsg *cid.Cid
|
||||
if ch == address.Undef {
|
||||
// don't have matching channel, open new
|
||||
|
||||
// TODO: this should be more atomic
|
||||
chInfo, err := a.PaychCreate(ctx, from, to, amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ch = chInfo.Channel
|
||||
chMsg = &chInfo.ChannelMessage
|
||||
} else {
|
||||
// already have chanel to the destination, add funds, and open a new lane
|
||||
// TODO: track free funds in channel
|
||||
|
||||
nonce, err := a.MpoolGetNonce(ctx, from)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := &types.Message{
|
||||
To: ch,
|
||||
From: from,
|
||||
Value: amount,
|
||||
Nonce: nonce,
|
||||
Method: 0,
|
||||
GasLimit: types.NewInt(1000000),
|
||||
GasPrice: types.NewInt(0),
|
||||
}
|
||||
|
||||
smsg, err := a.WalletSignMessage(ctx, from, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := a.MpoolPush(ctx, smsg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mwait, err := a.ChainWaitMsg(ctx, smsg.Cid())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if mwait.Receipt.ExitCode != 0 {
|
||||
return nil, fmt.Errorf("voucher channel creation failed: adding funds (exit code %d)", mwait.Receipt.ExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
lane, err := a.PaychMgr.AllocateLane(ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sv, err := a.paychVoucherCreate(ctx, ch, types.SignedVoucher{
|
||||
Amount: amount,
|
||||
Lane: lane,
|
||||
|
||||
Extra: extra,
|
||||
TimeLock: tl,
|
||||
MinCloseHeight: minClose,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.PaymentInfo{
|
||||
Channel: ch,
|
||||
ChannelMessage: chMsg,
|
||||
Voucher: sv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *PaychAPI) PaychList(ctx context.Context) ([]address.Address, error) {
|
||||
return a.PaychMgr.ListChannels()
|
||||
}
|
||||
|
||||
func (a *PaychAPI) PaychStatus(ctx context.Context, pch address.Address) (*api.PaychStatus, error) {
|
||||
ci, err := a.PaychMgr.GetChannelInfo(pch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &api.PaychStatus{
|
||||
ControlAddr: ci.Control,
|
||||
Direction: api.PCHDir(ci.Direction),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *PaychAPI) 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.Control)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
msg := &types.Message{
|
||||
To: addr,
|
||||
From: ci.Control,
|
||||
Value: types.NewInt(0),
|
||||
Method: actors.PCAMethods.Close,
|
||||
Nonce: nonce,
|
||||
|
||||
GasLimit: types.NewInt(500),
|
||||
GasPrice: types.NewInt(0),
|
||||
}
|
||||
|
||||
smsg, err := a.WalletSignMessage(ctx, ci.Control, 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 *PaychAPI) PaychVoucherCheckValid(ctx context.Context, ch address.Address, sv *types.SignedVoucher) error {
|
||||
return a.PaychMgr.CheckVoucherValid(ctx, ch, sv)
|
||||
}
|
||||
|
||||
func (a *PaychAPI) 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 *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, proof)
|
||||
}
|
||||
|
||||
// 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 *PaychAPI) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*types.SignedVoucher, error) {
|
||||
return a.paychVoucherCreate(ctx, pch, types.SignedVoucher{Amount: amt, Lane: lane})
|
||||
}
|
||||
|
||||
func (a *PaychAPI) paychVoucherCreate(ctx context.Context, pch address.Address, voucher types.SignedVoucher) (*types.SignedVoucher, error) {
|
||||
ci, err := a.PaychMgr.GetChannelInfo(pch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce, err := a.PaychMgr.NextNonceForLane(ctx, pch, voucher.Lane)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sv := &voucher
|
||||
sv.Nonce = nonce
|
||||
|
||||
vb, err := sv.SigningBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig, err := a.WalletSign(ctx, ci.Control, vb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sv.Signature = sig
|
||||
|
||||
if err := a.PaychMgr.AddVoucher(ctx, pch, sv, nil); err != nil {
|
||||
return nil, xerrors.Errorf("failed to persist voucher: %w", err)
|
||||
}
|
||||
|
||||
return sv, nil
|
||||
}
|
||||
|
||||
func (a *PaychAPI) PaychVoucherList(ctx context.Context, pch address.Address) ([]*types.SignedVoucher, error) {
|
||||
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) {
|
||||
ci, err := a.PaychMgr.GetChannelInfo(ch)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
nonce, err := a.MpoolGetNonce(ctx, ci.Control)
|
||||
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.Control,
|
||||
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.Control, 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
|
||||
}
|
||||
Reference in New Issue
Block a user