package retrievaladapter import ( "context" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/shared" "github.com/filecoin-project/go-state-types/abi" "github.com/ipfs/go-cid" "github.com/multiformats/go-multiaddr" mh "github.com/multiformats/go-multihash" "golang.org/x/xerrors" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/node/impl/full" payapi "github.com/filecoin-project/lotus/node/impl/paych" ) func mkPaychReusedCid(addr address.Address) cid.Cid { c, err := cid.V1Builder{Codec: cid.Raw, MhType: mh.IDENTITY}.Sum(addr.Bytes()) if err != nil { panic(err) } return c } func extractPaychReusedCid(c cid.Cid) (address.Address, error) { if c.Prefix().Codec != cid.Raw { return address.Undef, nil } h, err := mh.Decode(c.Hash()) if err != nil { return address.Address{}, err } return address.NewFromBytes(h.Digest) } type retrievalClientNode struct { forceOffChain bool chainAPI full.ChainAPI payAPI payapi.PaychAPI stateAPI full.StateAPI } // NewRetrievalClientNode returns a new node adapter for a retrieval client that talks to the // Lotus Node func NewRetrievalClientNode(forceOffChain bool, payAPI payapi.PaychAPI, chainAPI full.ChainAPI, stateAPI full.StateAPI) retrievalmarket.RetrievalClientNode { return &retrievalClientNode{ forceOffChain: forceOffChain, chainAPI: chainAPI, payAPI: payAPI, stateAPI: stateAPI, } } // GetOrCreatePaymentChannel sets up a new payment channel if one does not exist // between a client and a miner and ensures the client has the given amount of // funds available in the channel. func (rcn *retrievalClientNode) GetOrCreatePaymentChannel(ctx context.Context, clientAddress address.Address, minerAddress address.Address, clientFundsAvailable abi.TokenAmount, tok shared.TipSetToken) (address.Address, cid.Cid, error) { // TODO: respect the provided TipSetToken (a serialized TipSetKey) when // querying the chain ci, err := rcn.payAPI.PaychGet(ctx, clientAddress, minerAddress, clientFundsAvailable, api.PaychGetOpts{ Reserve: true, OffChain: rcn.forceOffChain, }) if err != nil { log.Errorw("paych get failed", "error", err) return address.Undef, cid.Undef, err } if ci.WaitSentinel == cid.Undef { return ci.Channel, mkPaychReusedCid(ci.Channel), nil } return ci.Channel, ci.WaitSentinel, nil } // Allocate late creates a lane within a payment channel so that calls to // CreatePaymentVoucher will automatically make vouchers only for the difference // in total func (rcn *retrievalClientNode) AllocateLane(ctx context.Context, paymentChannel address.Address) (uint64, error) { return rcn.payAPI.PaychAllocateLane(ctx, paymentChannel) } // CreatePaymentVoucher creates a new payment voucher in the given lane for a // given payment channel so that all the payment vouchers in the lane add up // to the given amount (so the payment voucher will be for the difference) func (rcn *retrievalClientNode) CreatePaymentVoucher(ctx context.Context, paymentChannel address.Address, amount abi.TokenAmount, lane uint64, tok shared.TipSetToken) (*paych.SignedVoucher, error) { // TODO: respect the provided TipSetToken (a serialized TipSetKey) when // querying the chain voucher, err := rcn.payAPI.PaychVoucherCreate(ctx, paymentChannel, amount, lane) if err != nil { return nil, err } if voucher.Voucher == nil { return nil, retrievalmarket.NewShortfallError(voucher.Shortfall) } return voucher.Voucher, nil } func (rcn *retrievalClientNode) GetChainHead(ctx context.Context) (shared.TipSetToken, abi.ChainEpoch, error) { head, err := rcn.chainAPI.ChainHead(ctx) if err != nil { return nil, 0, err } return head.Key().Bytes(), head.Height(), nil } func (rcn *retrievalClientNode) WaitForPaymentChannelReady(ctx context.Context, messageCID cid.Cid) (address.Address, error) { maybeAddr, err := extractPaychReusedCid(messageCID) if err != nil { return address.Address{}, xerrors.Errorf("extract paych reused CID: %w", err) } if maybeAddr != address.Undef { return maybeAddr, nil } return rcn.payAPI.PaychGetWaitReady(ctx, messageCID) } func (rcn *retrievalClientNode) CheckAvailableFunds(ctx context.Context, paymentChannel address.Address) (retrievalmarket.ChannelAvailableFunds, error) { channelAvailableFunds, err := rcn.payAPI.PaychAvailableFunds(ctx, paymentChannel) if err != nil { return retrievalmarket.ChannelAvailableFunds{}, err } return retrievalmarket.ChannelAvailableFunds{ ConfirmedAmt: channelAvailableFunds.ConfirmedAmt, PendingAmt: channelAvailableFunds.PendingAmt, PendingWaitSentinel: channelAvailableFunds.PendingWaitSentinel, QueuedAmt: channelAvailableFunds.QueuedAmt, VoucherReedeemedAmt: channelAvailableFunds.VoucherReedeemedAmt, }, nil } func (rcn *retrievalClientNode) GetKnownAddresses(ctx context.Context, p retrievalmarket.RetrievalPeer, encodedTs shared.TipSetToken) ([]multiaddr.Multiaddr, error) { tsk, err := types.TipSetKeyFromBytes(encodedTs) if err != nil { return nil, err } mi, err := rcn.stateAPI.StateMinerInfo(ctx, p.Address, tsk) if err != nil { return nil, err } multiaddrs := make([]multiaddr.Multiaddr, 0, len(mi.Multiaddrs)) for _, a := range mi.Multiaddrs { maddr, err := multiaddr.NewMultiaddrBytes(a) if err != nil { return nil, err } multiaddrs = append(multiaddrs, maddr) } return multiaddrs, nil }