Merge pull request #490 from filecoin-project/feat/gb-asks
deals: Ask prices per GiB
This commit is contained in:
commit
f36edc94bd
@ -102,7 +102,7 @@ type FullNode interface {
|
||||
|
||||
// ClientImport imports file under the specified path into filestore
|
||||
ClientImport(ctx context.Context, path string) (cid.Cid, error)
|
||||
ClientStartDeal(ctx context.Context, data cid.Cid, miner address.Address, price types.BigInt, blocksDuration uint64) (*cid.Cid, error)
|
||||
ClientStartDeal(ctx context.Context, data cid.Cid, miner address.Address, epochPrice types.BigInt, blocksDuration uint64) (*cid.Cid, error)
|
||||
ClientListDeals(ctx context.Context) ([]DealInfo, error)
|
||||
ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error)
|
||||
ClientFindData(ctx context.Context, root cid.Cid) ([]QueryOffer, error) // TODO: specify serialization mode we want (defaults to unixfs for now)
|
||||
@ -206,8 +206,8 @@ type DealInfo struct {
|
||||
PieceRef []byte // cid bytes
|
||||
Size uint64
|
||||
|
||||
TotalPrice types.BigInt
|
||||
Duration uint64
|
||||
PricePerEpoch types.BigInt
|
||||
Duration uint64
|
||||
}
|
||||
|
||||
type MsgWait struct {
|
||||
|
@ -77,20 +77,18 @@ type StorageDealProposal struct {
|
||||
Provider address.Address
|
||||
|
||||
ProposalExpiration uint64
|
||||
Duration uint64 // TODO: spec proposes 'DealExpiration', but that's awkward as it
|
||||
// doesn't tell when the deal actually starts, so the price per block is impossible to
|
||||
// calculate. It also doesn't incentivize the miner to seal / activate sooner, as he
|
||||
// still get's paid the full amount specified in the deal
|
||||
//
|
||||
// Changing to duration makes sure that the price-per-block is defined, and the miner
|
||||
// doesn't get paid when not storing the sector
|
||||
Duration uint64 // TODO: spec
|
||||
|
||||
StoragePrice types.BigInt
|
||||
StorageCollateral types.BigInt
|
||||
StoragePricePerEpoch types.BigInt
|
||||
StorageCollateral types.BigInt
|
||||
|
||||
ProposerSignature *types.Signature
|
||||
}
|
||||
|
||||
func (sdp *StorageDealProposal) TotalStoragePrice() types.BigInt {
|
||||
return types.BigMul(sdp.StoragePricePerEpoch, types.NewInt(sdp.Duration))
|
||||
}
|
||||
|
||||
type SignFunc = func(context.Context, []byte) (*types.Signature, error)
|
||||
|
||||
func (sdp *StorageDealProposal) Sign(ctx context.Context, sign SignFunc) error {
|
||||
@ -392,11 +390,13 @@ func (st *StorageMarketState) validateDeal(vmctx types.VMContext, deal StorageDe
|
||||
clientBalance := b[0]
|
||||
providerBalance := b[1]
|
||||
|
||||
if clientBalance.Available.LessThan(deal.Proposal.StoragePrice) {
|
||||
return aerrors.Newf(5, "client doesn't have enough available funds to cover StoragePrice; %d < %d", clientBalance.Available, deal.Proposal.StoragePrice)
|
||||
totalPrice := deal.Proposal.TotalStoragePrice()
|
||||
|
||||
if clientBalance.Available.LessThan(totalPrice) {
|
||||
return aerrors.Newf(5, "client doesn't have enough available funds to cover storage price; %d < %d", clientBalance.Available, totalPrice)
|
||||
}
|
||||
|
||||
clientBalance = lockFunds(clientBalance, deal.Proposal.StoragePrice)
|
||||
clientBalance = lockFunds(clientBalance, totalPrice)
|
||||
|
||||
// TODO: REVIEW: Not clear who pays for this
|
||||
if providerBalance.Available.LessThan(deal.Proposal.StorageCollateral) {
|
||||
@ -539,7 +539,7 @@ func (sma StorageMarketActor) ProcessStorageDealsPayment(act *types.Actor, vmctx
|
||||
|
||||
// todo: check math (written on a plane, also tired)
|
||||
// TODO: division is hard, this more than likely has some off-by-one issue
|
||||
toPay := types.BigDiv(types.BigMul(dealInfo.Deal.Proposal.StoragePrice, types.NewInt(build.ProvingPeriodDuration)), types.NewInt(dealInfo.Deal.Proposal.Duration))
|
||||
toPay := types.BigMul(dealInfo.Deal.Proposal.StoragePricePerEpoch, types.NewInt(build.ProvingPeriodDuration))
|
||||
|
||||
b, bnd, aerr := GetMarketBalances(vmctx.Context(), vmctx.Ipld(), self.Balances, dealInfo.Deal.Proposal.Client, providerWorker)
|
||||
if aerr != nil {
|
||||
|
@ -3051,8 +3051,8 @@ func (t *StorageDealProposal) MarshalCBOR(w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.t.StoragePrice (types.BigInt)
|
||||
if err := t.StoragePrice.MarshalCBOR(w); err != nil {
|
||||
// t.t.StoragePricePerEpoch (types.BigInt)
|
||||
if err := t.StoragePricePerEpoch.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -3158,11 +3158,11 @@ func (t *StorageDealProposal) UnmarshalCBOR(r io.Reader) error {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.Duration = extra
|
||||
// t.t.StoragePrice (types.BigInt)
|
||||
// t.t.StoragePricePerEpoch (types.BigInt)
|
||||
|
||||
{
|
||||
|
||||
if err := t.StoragePrice.UnmarshalCBOR(br); err != nil {
|
||||
if err := t.StoragePricePerEpoch.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -419,8 +419,8 @@ func (t *ClientDealProposal) MarshalCBOR(w io.Writer) error {
|
||||
return xerrors.Errorf("failed to write cid field t.Data: %w", err)
|
||||
}
|
||||
|
||||
// t.t.TotalPrice (types.BigInt)
|
||||
if err := t.TotalPrice.MarshalCBOR(w); err != nil {
|
||||
// t.t.PricePerEpoch (types.BigInt)
|
||||
if err := t.PricePerEpoch.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -481,11 +481,11 @@ func (t *ClientDealProposal) UnmarshalCBOR(r io.Reader) error {
|
||||
t.Data = c
|
||||
|
||||
}
|
||||
// t.t.TotalPrice (types.BigInt)
|
||||
// t.t.PricePerEpoch (types.BigInt)
|
||||
|
||||
{
|
||||
|
||||
if err := t.TotalPrice.UnmarshalCBOR(br); err != nil {
|
||||
if err := t.PricePerEpoch.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -159,7 +159,7 @@ func (c *Client) onUpdated(ctx context.Context, update clientDealUpdate) {
|
||||
type ClientDealProposal struct {
|
||||
Data cid.Cid
|
||||
|
||||
TotalPrice types.BigInt
|
||||
PricePerEpoch types.BigInt
|
||||
ProposalExpiration uint64
|
||||
Duration uint64
|
||||
|
||||
@ -175,13 +175,13 @@ func (c *Client) Start(ctx context.Context, p ClientDealProposal) (cid.Cid, erro
|
||||
return cid.Undef, xerrors.Errorf("getting client market balance failed: %w", err)
|
||||
}
|
||||
|
||||
if clientMarketBalance.Available.LessThan(p.TotalPrice) {
|
||||
if clientMarketBalance.Available.LessThan(types.BigMul(p.PricePerEpoch, types.NewInt(p.Duration))) {
|
||||
// TODO: move to a smarter market funds manager
|
||||
|
||||
smsg, err := c.mpool.MpoolPushMessage(ctx, &types.Message{
|
||||
To: actors.StorageMarketAddress,
|
||||
From: p.Client,
|
||||
Value: p.TotalPrice,
|
||||
Value: types.BigMul(p.PricePerEpoch, types.NewInt(p.Duration)),
|
||||
GasPrice: types.NewInt(0),
|
||||
GasLimit: types.NewInt(1000000),
|
||||
Method: actors.SMAMethods.AddBalance,
|
||||
@ -203,15 +203,15 @@ func (c *Client) Start(ctx context.Context, p ClientDealProposal) (cid.Cid, erro
|
||||
dataSize, err := c.dataSize(ctx, p.Data)
|
||||
|
||||
proposal := &actors.StorageDealProposal{
|
||||
PieceRef: p.Data.Bytes(),
|
||||
PieceSize: uint64(dataSize),
|
||||
PieceSerialization: actors.SerializationUnixFSv0,
|
||||
Client: p.Client,
|
||||
Provider: p.ProviderAddress,
|
||||
ProposalExpiration: p.ProposalExpiration,
|
||||
Duration: p.Duration,
|
||||
StoragePrice: p.TotalPrice,
|
||||
StorageCollateral: types.NewInt(uint64(dataSize)), // TODO: real calc
|
||||
PieceRef: p.Data.Bytes(),
|
||||
PieceSize: uint64(dataSize),
|
||||
PieceSerialization: actors.SerializationUnixFSv0,
|
||||
Client: p.Client,
|
||||
Provider: p.ProviderAddress,
|
||||
ProposalExpiration: p.ProposalExpiration,
|
||||
Duration: p.Duration,
|
||||
StoragePricePerEpoch: p.PricePerEpoch,
|
||||
StorageCollateral: types.NewInt(uint64(dataSize)), // TODO: real calc
|
||||
}
|
||||
|
||||
if err := api.SignWith(ctx, c.w.Sign, p.Client, proposal); err != nil {
|
||||
|
@ -109,7 +109,7 @@ func NewProvider(ds dtypes.MetadataDS, secst *sectorblocks.SectorBlocks, commt *
|
||||
if h.ask == nil {
|
||||
// TODO: we should be fine with this state, and just say it means 'not actively accepting deals'
|
||||
// for now... lets just set a price
|
||||
if err := h.SetPrice(types.NewInt(3), 1000000); err != nil {
|
||||
if err := h.SetPrice(types.NewInt(500_000_000), 1000000); err != nil {
|
||||
return nil, xerrors.Errorf("failed setting a default price: %w", err)
|
||||
}
|
||||
}
|
||||
|
@ -88,9 +88,9 @@ func (p *Provider) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal)
|
||||
// TODO: check StorageCollateral
|
||||
|
||||
// TODO:
|
||||
minPrice := types.BigMul(p.ask.Ask.Price, types.BigMul(types.NewInt(deal.Proposal.Duration), types.NewInt(deal.Proposal.PieceSize)))
|
||||
if deal.Proposal.StoragePrice.LessThan(minPrice) {
|
||||
return nil, xerrors.Errorf("storage price less than asking price: %s < %s", deal.Proposal.StoragePrice, minPrice)
|
||||
minPrice := types.BigDiv(types.BigMul(p.ask.Ask.Price, types.NewInt(deal.Proposal.PieceSize)), types.NewInt(1<<30))
|
||||
if deal.Proposal.StoragePricePerEpoch.LessThan(minPrice) {
|
||||
return nil, xerrors.Errorf("storage price per epoch less than asking price: %s < %s", deal.Proposal.StoragePricePerEpoch, minPrice)
|
||||
}
|
||||
|
||||
if deal.Proposal.PieceSize < p.ask.Ask.MinPieceSize {
|
||||
@ -105,7 +105,7 @@ func (p *Provider) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal)
|
||||
|
||||
// This doesn't guarantee that the client won't withdraw / lock those funds
|
||||
// but it's a decent first filter
|
||||
if clientMarketBalance.Available.LessThan(deal.Proposal.StoragePrice) {
|
||||
if clientMarketBalance.Available.LessThan(deal.Proposal.TotalStoragePrice()) {
|
||||
return nil, xerrors.New("clientMarketBalance.Available too small")
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,9 @@ type SignedStorageAsk struct {
|
||||
}
|
||||
|
||||
type StorageAsk struct {
|
||||
Price BigInt
|
||||
// Price per GiB / Epoch
|
||||
Price BigInt
|
||||
|
||||
MinPieceSize uint64
|
||||
Miner address.Address
|
||||
Timestamp uint64
|
||||
|
@ -488,7 +488,6 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet,
|
||||
return nil, xerrors.Errorf("getting block miner actor (%s) failed: %w", vm.blockMiner, err)
|
||||
}
|
||||
|
||||
|
||||
// TODO: support multiple blocks in a tipset
|
||||
// TODO: actually wire this up (miner is undef for now)
|
||||
gasReward := types.BigMul(msg.GasPrice, gasUsed)
|
||||
|
@ -101,8 +101,7 @@ var clientDealCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: parse bigint
|
||||
price, err := strconv.ParseInt(cctx.Args().Get(2), 10, 32)
|
||||
price, err := types.ParseFIL(cctx.Args().Get(2))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -112,7 +111,7 @@ var clientDealCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
proposal, err := api.ClientStartDeal(ctx, data, miner, types.NewInt(uint64(price)), uint64(dur))
|
||||
proposal, err := api.ClientStartDeal(ctx, data, miner, types.BigInt(price), uint64(dur))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -164,7 +163,7 @@ var clientFindCmd = &cli.Command{
|
||||
fmt.Printf("ERR %s@%s: %s\n", offer.Miner, offer.MinerPeerID, offer.Err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("RETRIEVAL %s@%s-%sfil-%db\n", offer.Miner, offer.MinerPeerID, offer.MinPrice, offer.Size)
|
||||
fmt.Printf("RETRIEVAL %s@%s-%sfil-%db\n", offer.Miner, offer.MinerPeerID, types.FIL(offer.MinPrice), offer.Size)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -308,19 +307,20 @@ var clientQueryAskCmd = &cli.Command{
|
||||
}
|
||||
|
||||
fmt.Printf("Ask: %s\n", maddr)
|
||||
fmt.Printf("Price per Byte: %s\n", ask.Ask.Price)
|
||||
fmt.Printf("Price per Byte: %s\n", types.FIL(ask.Ask.Price))
|
||||
|
||||
size := cctx.Int64("size")
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("Price per Block: %s\n", types.BigMul(ask.Ask.Price, types.NewInt(uint64(size))))
|
||||
perEpoch := types.BigDiv(types.BigMul(ask.Ask.Price, types.NewInt(uint64(size))), types.NewInt(1<<30))
|
||||
fmt.Printf("Price per Block: %s\n", types.FIL(perEpoch))
|
||||
|
||||
duration := cctx.Int64("duration")
|
||||
if duration == 0 {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("Total Price: %s\n", types.BigMul(types.BigMul(ask.Ask.Price, types.NewInt(uint64(size))), types.NewInt(uint64(duration))))
|
||||
fmt.Printf("Total Price: %s\n", types.FIL(types.BigMul(perEpoch, types.NewInt(uint64(duration)))))
|
||||
|
||||
return nil
|
||||
},
|
||||
|
@ -21,7 +21,7 @@ class Client extends React.Component {
|
||||
|
||||
this.state = {
|
||||
miners: ["t0101"],
|
||||
ask: {Price: "3"},
|
||||
ask: {Price: "500000000"},
|
||||
|
||||
kbs: 1,
|
||||
blocks: 12,
|
||||
@ -52,7 +52,7 @@ class Client extends React.Component {
|
||||
update = (name) => (e) => this.setState({ [name]: e.target.value });
|
||||
|
||||
makeDeal = async () => {
|
||||
let perBlk = this.state.ask.Price * this.state.kbs * 1000
|
||||
let perBlk = this.state.ask.Price * this.state.kbs * 1000 / (1 << 30)
|
||||
|
||||
let file = await this.props.pondClient.call('Pond.CreateRandomFile', [this.state.kbs * 1000]) // 1024 won't fit in 1k blocks :(
|
||||
let cid = await this.props.client.call('Filecoin.ClientImport', [file])
|
||||
|
@ -123,10 +123,10 @@ class MarketState extends React.Component {
|
||||
{Object.keys(this.state.deals).map(d => <tr>
|
||||
<td>{d}</td>
|
||||
<td>{this.state.deals[d].ActivationEpoch || "No"}</td>
|
||||
<td><Address short={true} addr={this.state.deals[d].Deal.Proposal.Provider} client={this.props.client} mountWindow={this.props.mountWindow}/></td>
|
||||
<td><Address short={true} addr={this.state.deals[d].Deal.Proposal.Client} client={this.props.client} mountWindow={this.props.mountWindow}/></td>
|
||||
<td><Address short={true} addr={this.state.deals[d].Deal.Proposal.Provider} client={this.props.client} mountWindow={this.props.mountWindow}/></td>
|
||||
<td>{this.state.deals[d].Deal.Proposal.PieceSize}B</td>
|
||||
<td>{this.state.deals[d].Deal.Proposal.StoragePrice}</td>
|
||||
<td>{this.state.deals[d].Deal.Proposal.StoragePricePerEpoch*this.state.deals[d].Deal.Proposal.Duration}</td>
|
||||
<td>{this.state.deals[d].Deal.Proposal.Duration}</td>
|
||||
</tr>)}
|
||||
</table>
|
||||
|
@ -53,7 +53,7 @@ type API struct {
|
||||
Filestore dtypes.ClientFilestore `optional:"true"`
|
||||
}
|
||||
|
||||
func (a *API) ClientStartDeal(ctx context.Context, data cid.Cid, miner address.Address, price types.BigInt, blocksDuration uint64) (*cid.Cid, error) {
|
||||
func (a *API) ClientStartDeal(ctx context.Context, data cid.Cid, miner address.Address, epochPrice types.BigInt, blocksDuration uint64) (*cid.Cid, error) {
|
||||
// TODO: make this a param
|
||||
self, err := a.WalletDefaultAddress(ctx)
|
||||
if err != nil {
|
||||
@ -76,12 +76,9 @@ func (a *API) ClientStartDeal(ctx context.Context, data cid.Cid, miner address.A
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// setup payments
|
||||
total := types.BigMul(price, types.NewInt(blocksDuration))
|
||||
|
||||
proposal := deals.ClientDealProposal{
|
||||
Data: data,
|
||||
TotalPrice: total,
|
||||
PricePerEpoch: epochPrice,
|
||||
ProposalExpiration: math.MaxUint64, // TODO: set something reasonable
|
||||
Duration: blocksDuration,
|
||||
ProviderAddress: miner,
|
||||
@ -110,8 +107,8 @@ func (a *API) ClientListDeals(ctx context.Context) ([]api.DealInfo, error) {
|
||||
PieceRef: v.Proposal.PieceRef,
|
||||
Size: v.Proposal.PieceSize,
|
||||
|
||||
TotalPrice: v.Proposal.StoragePrice,
|
||||
Duration: v.Proposal.Duration,
|
||||
PricePerEpoch: v.Proposal.StoragePricePerEpoch,
|
||||
Duration: v.Proposal.Duration,
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user