是retrieval market
指为提供商协商交易以向客户提供存储数据的过程。应该强调的是,检索的协商过程主要发生在链下。它只是其中的一部分(主要与从支付渠道兑换代金券有关)涉及与区块链的交互。
主要成分如下:
支付渠道参与者
进行查询的协议
用于查询检索矿工和发起检索交易的数据传输子系统和协议
基于链的内容路由接口
一个客户端模块,用于查询检索矿工并发起检索交易
响应查询和交易建议的提供商模块
检索市场通过搭载数据传输系统和 Graphsync 来处理传输和验证,支持任意选择器并减少往返。检索市场可以支持在一个片段中发送任意有效负载 CID 和选择器。
数据传输系统相应地得到增强,以支持暂停/恢复和发送中间凭证以促进这一点。
检索市场的交易流程
用于提议和接受交易的 Filecoin Retrieval Market 协议的工作原理如下:
客户通过
FindProviders()
.客户端查询提供者以查看它是否满足其检索条件(通过查询协议)
客户安排
Data Transfer Pull Request
通过RetrievalDealProposal
作为凭证。提供者验证提案,如果无效则拒绝它。
如果提议有效,则提供者以接受消息响应并开始监视数据传输过程。
客户根据需要创建支付渠道和“通道”,并确保渠道中有足够的资金。
提供商会根据需要解封该扇区。
提供商在通过协议发送块时监控数据传输,直到需要付款。
当提供商需要付款时,它会暂停数据传输并发送付款请求作为中间凭证。
客户收到付款请求。
客户在链下创建并存储支付凭证。
客户端通过引用作为中间凭证发送的支付凭证(即,确认收到部分数据和通道或通道值)来响应提供者。
提供商验证客户发送的凭证并将其保存以供稍后在链上兑换
提供商继续发送数据并请求中间付款。
该过程一直持续到数据传输结束。
关于上述过程的一些额外注意事项如下:
支付渠道由客户创建。
支付渠道在提供商接受交易时创建,除非给定的客户和提供商之间已经存在开放的支付渠道。
代金券也由客户创建,并(这些代金券的参考/标识符)发送给提供商。
在客户和提供商之间创建和交换凭证时,凭证中指示的付款不会从支付渠道资金中扣除。
为了将钱转移到供应商的支付渠道方,供应商必须兑换代金券
为了将钱从支付渠道中取出,提供者必须在链上提交凭证和
Collect
资金。在数据传输过程中,兑换和收取凭证/资金都可以随时进行,但兑换凭证和收取资金涉及区块链,这进一步意味着它会产生gas费用。
数据传输完成后,客户端或提供商可以结算通道。然后有一个 12 小时的时间段,在此期间,提供者必须在链上提交兑换的代金券以收集资金。12 小时期限结束后,客户可能会从渠道中收取任何无人认领的资金,而提供商将失去他们未提交的凭证的资金。
提供商可以在开始解封数据之前要求在传输之前支付小额款项。付款旨在支持提供商解封第一块数据的计算成本(其中块是商定的逐步数据传输)。需要此过程以避免客户端执行 DoS 攻击,根据该攻击,他们会启动几笔交易并导致提供商使用大量计算资源。
引导信任
客户和提供者都没有任何特定的理由相互信任。因此,信任是通过对逐步完成的检索交易的支付间接建立的。这是通过在数据传输过程中发送凭证来实现的。
信托设立过程如下:
创建交易时,客户和提供商同意以字节为单位的“付款间隔”,这是提供商在每次所需增量之前将发送的最小数据量。
他们还同意“支付间隔增量”。这意味着随着客户和提供商之间信任的发展,每次成功转账和付款后,间隔将增加该值。
例子:
如果我的“支付间隔”是 1000,而我的“支付间隔增加”是 300,那么:
提供者必须发送至少 1000 个字节才能要求任何付款(由于块边界不均匀,他们最终可能会发送更多字节)。
客户必须为提供者请求付款时发送的所有字节付费(即签发凭证),前提是提供者已发送至少 1000 个字节。
提供商现在必须发送至少 1300 个字节才能再次请求付款。
当提供者请求付款时,客户必须为它尚未支付的所有字节支付(即发行后续凭证),假设自上次支付以来它已收到至少 1300 个字节。
该过程一直持续到检索结束,此时最后一次付款将仅用于剩余的字节。
检索市场中的数据表示
检索市场基于 Payload CID 工作。PayloadCID 是表示文件的 UnixFS 版本的 IPLD DAG 的根的哈希值。在这个阶段,该文件是具有 IPFS 样式表示的原始系统文件。为了让客户在检索市场下请求一些数据,他们必须知道 PayloadCID。重要的是要强调 PayloadCID 不会在链上存储或注册。
示例:检索市场 - 常见数据类型
package retrievalmarket
import (
"bytes"
"errors"
"fmt"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagcbor"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/protocol"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
datatransfer "github.com/filecoin-project/go-data-transfer"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
"github.com/filecoin-project/go-fil-markets/piecestore"
)
//go:generate cbor-gen-for --map-encoding Query QueryResponse DealProposal DealResponse Params QueryParams DealPayment ClientDealState ProviderDealState PaymentInfo RetrievalPeer Ask
// QueryProtocolID is the protocol for querying information about retrieval
// deal parameters
const QueryProtocolID = protocol.ID("/fil/retrieval/qry/1.0.0")
// OldQueryProtocolID is the old query protocol for tuple structs
const OldQueryProtocolID = protocol.ID("/fil/retrieval/qry/0.0.1")
// Unsubscribe is a function that unsubscribes a subscriber for either the
// client or the provider
type Unsubscribe func()
// PaymentInfo is the payment channel and lane for a deal, once it is setup
type PaymentInfo struct {
PayCh address.Address
Lane uint64
}
// ClientDealState is the current state of a deal from the point of view
// of a retrieval client
type ClientDealState struct {
DealProposal
StoreID *uint64
// Set when the data transfer is started
ChannelID *datatransfer.ChannelID
LastPaymentRequested bool
AllBlocksReceived bool
TotalFunds abi.TokenAmount
ClientWallet address.Address
MinerWallet address.Address
PaymentInfo *PaymentInfo
Status DealStatus
Sender peer.ID
TotalReceived uint64
Message string
BytesPaidFor uint64
CurrentInterval uint64
PaymentRequested abi.TokenAmount
FundsSpent abi.TokenAmount
UnsealFundsPaid abi.TokenAmount
WaitMsgCID *cid.Cid // the CID of any message the client deal is waiting for
VoucherShortfall abi.TokenAmount
LegacyProtocol bool
}
func (deal *ClientDealState) NextInterval() uint64 {
return deal.Params.NextInterval(deal.CurrentInterval)
}
// ProviderDealState is the current state of a deal from the point of view
// of a retrieval provider
type ProviderDealState struct {
DealProposal
StoreID uint64
ChannelID *datatransfer.ChannelID
PieceInfo *piecestore.PieceInfo
Status DealStatus
Receiver peer.ID
TotalSent uint64
FundsReceived abi.TokenAmount
Message string
CurrentInterval uint64
LegacyProtocol bool
}
func (deal *ProviderDealState) IntervalLowerBound() uint64 {
return deal.Params.IntervalLowerBound(deal.CurrentInterval)
}
func (deal *ProviderDealState) NextInterval() uint64 {
return deal.Params.NextInterval(deal.CurrentInterval)
}
// Identifier provides a unique id for this provider deal
func (pds ProviderDealState) Identifier() ProviderDealIdentifier {
return ProviderDealIdentifier{Receiver: pds.Receiver, DealID: pds.ID}
}
// ProviderDealIdentifier is a value that uniquely identifies a deal
type ProviderDealIdentifier struct {
Receiver peer.ID
DealID DealID
}
func (p ProviderDealIdentifier) String() string {
return fmt.Sprintf("%v/%v", p.Receiver, p.DealID)
}
// RetrievalPeer is a provider address/peer.ID pair (everything needed to make
// deals for with a miner)
type RetrievalPeer struct {
Address address.Address
ID peer.ID // optional
PieceCID *cid.Cid
}
// QueryResponseStatus indicates whether a queried piece is available
type QueryResponseStatus uint64
const (
// QueryResponseAvailable indicates a provider has a piece and is prepared to
// return it
QueryResponseAvailable QueryResponseStatus = iota
// QueryResponseUnavailable indicates a provider either does not have or cannot
// serve the queried piece to the client
QueryResponseUnavailable
// QueryResponseError indicates something went wrong generating a query response
QueryResponseError
)
// QueryItemStatus (V1) indicates whether the requested part of a piece (payload or selector)
// is available for retrieval
type QueryItemStatus uint64
const (
// QueryItemAvailable indicates requested part of the piece is available to be
// served
QueryItemAvailable QueryItemStatus = iota
// QueryItemUnavailable indicates the piece either does not contain the requested
// item or it cannot be served
QueryItemUnavailable
// QueryItemUnknown indicates the provider cannot determine if the given item
// is part of the requested piece (for example, if the piece is sealed and the
// miner does not maintain a payload CID index)
QueryItemUnknown
)
// QueryParams - V1 - indicate what specific information about a piece that a retrieval
// client is interested in, as well as specific parameters the client is seeking
// for the retrieval deal
type QueryParams struct {
PieceCID *cid.Cid // optional, query if miner has this cid in this piece. some miners may not be able to respond.
//Selector ipld.Node // optional, query if miner has this cid in this piece. some miners may not be able to respond.
//MaxPricePerByte abi.TokenAmount // optional, tell miner uninterested if more expensive than this
//MinPaymentInterval uint64 // optional, tell miner uninterested unless payment interval is greater than this
//MinPaymentIntervalIncrease uint64 // optional, tell miner uninterested unless payment interval increase is greater than this
}
// Query is a query to a given provider to determine information about a piece
// they may have available for retrieval
type Query struct {
PayloadCID cid.Cid // V0
QueryParams // V1
}
// QueryUndefined is a query with no values
var QueryUndefined = Query{}
// NewQueryV0 creates a V0 query (which only specifies a payload)
func NewQueryV0(payloadCID cid.Cid) Query {
return Query{PayloadCID: payloadCID}
}
// NewQueryV1 creates a V1 query (which has an optional pieceCID)
func NewQueryV1(payloadCID cid.Cid, pieceCID *cid.Cid) Query {
return Query{
PayloadCID: payloadCID,
QueryParams: QueryParams{
PieceCID: pieceCID,
},
}
}
// QueryResponse is a miners response to a given retrieval query
type QueryResponse struct {
Status QueryResponseStatus
PieceCIDFound QueryItemStatus // V1 - if a PieceCID was requested, the result
//SelectorFound QueryItemStatus // V1 - if a Selector was requested, the result
Size uint64 // Total size of piece in bytes
//ExpectedPayloadSize uint64 // V1 - optional, if PayloadCID + selector are specified and miner knows, can offer an expected size
PaymentAddress address.Address // address to send funds to -- may be different than miner addr
MinPricePerByte abi.TokenAmount
MaxPaymentInterval uint64
MaxPaymentIntervalIncrease uint64
Message string
UnsealPrice abi.TokenAmount
}
// QueryResponseUndefined is an empty QueryResponse
var QueryResponseUndefined = QueryResponse{}
// PieceRetrievalPrice is the total price to retrieve the piece (size * MinPricePerByte + UnsealedPrice)
func (qr QueryResponse) PieceRetrievalPrice() abi.TokenAmount {
return big.Add(big.Mul(qr.MinPricePerByte, abi.NewTokenAmount(int64(qr.Size))), qr.UnsealPrice)
}
// PayloadRetrievalPrice is the expected price to retrieve just the given payload
// & selector (V1)
//func (qr QueryResponse) PayloadRetrievalPrice() abi.TokenAmount {
// return types.BigMul(qr.MinPricePerByte, types.NewInt(qr.ExpectedPayloadSize))
//}
// IsTerminalError returns true if this status indicates processing of this deal
// is complete with an error
func IsTerminalError(status DealStatus) bool {
return status == DealStatusDealNotFound ||
status == DealStatusFailing ||
status == DealStatusRejected
}
// IsTerminalSuccess returns true if this status indicates processing of this deal
// is complete with a success
func IsTerminalSuccess(status DealStatus) bool {
return status == DealStatusCompleted
}
// IsTerminalStatus returns true if this status indicates processing of a deal is
// complete (either success or error)
func IsTerminalStatus(status DealStatus) bool {
return IsTerminalError(status) || IsTerminalSuccess(status)
}
// Params are the parameters requested for a retrieval deal proposal
type Params struct {
Selector *cbg.Deferred // V1
PieceCID *cid.Cid
PricePerByte abi.TokenAmount
PaymentInterval uint64 // when to request payment
PaymentIntervalIncrease uint64
UnsealPrice abi.TokenAmount
}
func (p Params) SelectorSpecified() bool {
return p.Selector != nil && !bytes.Equal(p.Selector.Raw, cbg.CborNull)
}
func (p Params) IntervalLowerBound(currentInterval uint64) uint64 {
intervalSize := p.PaymentInterval
var lowerBound uint64
var target uint64
for target < currentInterval {
lowerBound = target
target += intervalSize
intervalSize += p.PaymentIntervalIncrease
}
return lowerBound
}
func (p Params) NextInterval(currentInterval uint64) uint64 {
intervalSize := p.PaymentInterval
var nextInterval uint64
for nextInterval <= currentInterval {
nextInterval += intervalSize
intervalSize += p.PaymentIntervalIncrease
}
return nextInterval
}
// NewParamsV0 generates parameters for a retrieval deal, which is always a whole piece deal
func NewParamsV0(pricePerByte abi.TokenAmount, paymentInterval uint64, paymentIntervalIncrease uint64) Params {
return Params{
PricePerByte: pricePerByte,
PaymentInterval: paymentInterval,
PaymentIntervalIncrease: paymentIntervalIncrease,
UnsealPrice: big.Zero(),
}
}
// NewParamsV1 generates parameters for a retrieval deal, including a selector
func NewParamsV1(pricePerByte abi.TokenAmount, paymentInterval uint64, paymentIntervalIncrease uint64, sel ipld.Node, pieceCid *cid.Cid, unsealPrice abi.TokenAmount) (Params, error) {
var buffer bytes.Buffer
if sel == nil {
return Params{}, xerrors.New("selector required for NewParamsV1")
}
err := dagcbor.Encode(sel, &buffer)
if err != nil {
return Params{}, xerrors.Errorf("error encoding selector: %w", err)
}
return Params{
Selector: &cbg.Deferred{Raw: buffer.Bytes()},
PieceCID: pieceCid,
PricePerByte: pricePerByte,
PaymentInterval: paymentInterval,
PaymentIntervalIncrease: paymentIntervalIncrease,
UnsealPrice: unsealPrice,
}, nil
}
// DealID is an identifier for a retrieval deal (unique to a client)
type DealID uint64
func (d DealID) String() string {
return fmt.Sprintf("%d", d)
}
// DealProposal is a proposal for a new retrieval deal
type DealProposal struct {
PayloadCID cid.Cid
ID DealID
Params
}
// Type method makes DealProposal usable as a voucher
func (dp *DealProposal) Type() datatransfer.TypeIdentifier {
return "RetrievalDealProposal/1"
}
// DealProposalUndefined is an undefined deal proposal
var DealProposalUndefined = DealProposal{}
// DealResponse is a response to a retrieval deal proposal
type DealResponse struct {
Status DealStatus
ID DealID
// payment required to proceed
PaymentOwed abi.TokenAmount
Message string
}
// Type method makes DealResponse usable as a voucher result
func (dr *DealResponse) Type() datatransfer.TypeIdentifier {
return "RetrievalDealResponse/1"
}
// DealResponseUndefined is an undefined deal response
var DealResponseUndefined = DealResponse{}
// DealPayment is a payment for an in progress retrieval deal
type DealPayment struct {
ID DealID
PaymentChannel address.Address
PaymentVoucher *paych.SignedVoucher
}
// Type method makes DealPayment usable as a voucher
func (dr *DealPayment) Type() datatransfer.TypeIdentifier {
return "RetrievalDealPayment/1"
}
// DealPaymentUndefined is an undefined deal payment
var DealPaymentUndefined = DealPayment{}
var (
// ErrNotFound means a piece was not found during retrieval
ErrNotFound = errors.New("not found")
// ErrVerification means a retrieval contained a block response that did not verify
ErrVerification = errors.New("Error when verify data")
)
type Ask struct {
PricePerByte abi.TokenAmount
UnsealPrice abi.TokenAmount
PaymentInterval uint64
PaymentIntervalIncrease uint64
}
// ShortfallErorr is an error that indicates a short fall of funds
type ShortfallError struct {
shortfall abi.TokenAmount
}
// NewShortfallError returns a new error indicating a shortfall of funds
func NewShortfallError(shortfall abi.TokenAmount) error {
return ShortfallError{shortfall}
}
// Shortfall returns the numerical value of the shortfall
func (se ShortfallError) Shortfall() abi.TokenAmount {
return se.shortfall
}
func (se ShortfallError) Error() string {
return fmt.Sprintf("Inssufficient Funds. Shortfall: %s", se.shortfall.String())
}
// ChannelAvailableFunds provides information about funds in a channel
type ChannelAvailableFunds struct {
// ConfirmedAmt is the amount of funds that have been confirmed on-chain
// for the channel
ConfirmedAmt abi.TokenAmount
// PendingAmt is the amount of funds that are pending confirmation on-chain
PendingAmt abi.TokenAmount
// PendingWaitSentinel can be used with PaychGetWaitReady to wait for
// confirmation of pending funds
PendingWaitSentinel *cid.Cid
// QueuedAmt is the amount that is queued up behind a pending request
QueuedAmt abi.TokenAmount
// VoucherRedeemedAmt is the amount that is redeemed by vouchers on-chain
// and in the local datastore
VoucherReedeemedAmt abi.TokenAmount
}
// PricingInput provides input parameters required to price a retrieval deal.
type PricingInput struct {
// PayloadCID is the cid of the payload to retrieve.
PayloadCID cid.Cid
// PieceCID is the cid of the Piece from which the Payload will be retrieved.
PieceCID cid.Cid
// PieceSize is the size of the Piece from which the payload will be retrieved.
PieceSize abi.UnpaddedPieceSize
// Client is the peerID of the retrieval client.
Client peer.ID
// VerifiedDeal is true if there exists a verified storage deal for the PayloadCID.
VerifiedDeal bool
// Unsealed is true if there exists an unsealed sector from which we can retrieve the given payload.
Unsealed bool
// CurrentAsk is the current configured ask in the ask-store.
CurrentAsk Ask
}