Skip to content

Commit

Permalink
Reject deals based on verifreg.DataCap for client (#307)
Browse files Browse the repository at this point in the history
  • Loading branch information
ingar authored Jul 2, 2020
1 parent 57ff8d6 commit f33b442
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 11 deletions.
37 changes: 26 additions & 11 deletions storagemarket/impl/providerstates/provider_states.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,44 +56,59 @@ func ValidateDealProposal(ctx fsm.Context, environment ProviderDealEnvironment,
return ctx.Trigger(storagemarket.ProviderEventDealRejected, xerrors.Errorf("verifying StorageDealProposal: %w", err))
}

if deal.Proposal.Provider != environment.Address() {
proposal := deal.Proposal

if proposal.Provider != environment.Address() {
return ctx.Trigger(storagemarket.ProviderEventDealRejected, xerrors.Errorf("incorrect provider for deal"))
}

if height > deal.Proposal.StartEpoch-environment.DealAcceptanceBuffer() {
if height > proposal.StartEpoch-environment.DealAcceptanceBuffer() {
return ctx.Trigger(storagemarket.ProviderEventDealRejected, xerrors.Errorf("deal start epoch is too soon or deal already expired"))
}

// TODO: check StorageCollateral

minPrice := big.Div(big.Mul(environment.Ask().Price, abi.NewTokenAmount(int64(deal.Proposal.PieceSize))), abi.NewTokenAmount(1<<30))
if deal.Proposal.StoragePricePerEpoch.LessThan(minPrice) {
minPrice := big.Div(big.Mul(environment.Ask().Price, abi.NewTokenAmount(int64(proposal.PieceSize))), abi.NewTokenAmount(1<<30))
if proposal.StoragePricePerEpoch.LessThan(minPrice) {
return ctx.Trigger(storagemarket.ProviderEventDealRejected,
xerrors.Errorf("storage price per epoch less than asking price: %s < %s", deal.Proposal.StoragePricePerEpoch, minPrice))
xerrors.Errorf("storage price per epoch less than asking price: %s < %s", proposal.StoragePricePerEpoch, minPrice))
}

if deal.Proposal.PieceSize < environment.Ask().MinPieceSize {
if proposal.PieceSize < environment.Ask().MinPieceSize {
return ctx.Trigger(storagemarket.ProviderEventDealRejected,
xerrors.Errorf("piece size less than minimum required size: %d < %d", deal.Proposal.PieceSize, environment.Ask().MinPieceSize))
xerrors.Errorf("piece size less than minimum required size: %d < %d", proposal.PieceSize, environment.Ask().MinPieceSize))
}

if deal.Proposal.PieceSize > environment.Ask().MaxPieceSize {
if proposal.PieceSize > environment.Ask().MaxPieceSize {
return ctx.Trigger(storagemarket.ProviderEventDealRejected,
xerrors.Errorf("piece size more than maximum allowed size: %d > %d", deal.Proposal.PieceSize, environment.Ask().MaxPieceSize))
xerrors.Errorf("piece size more than maximum allowed size: %d > %d", proposal.PieceSize, environment.Ask().MaxPieceSize))
}

// check market funds
clientMarketBalance, err := environment.Node().GetBalance(ctx.Context(), deal.Proposal.Client, tok)
clientMarketBalance, err := environment.Node().GetBalance(ctx.Context(), proposal.Client, tok)
if err != nil {
return ctx.Trigger(storagemarket.ProviderEventDealRejected, xerrors.Errorf("node error getting client market balance failed: %w", err))
}

// 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.TotalStorageFee()) {
if clientMarketBalance.Available.LessThan(proposal.TotalStorageFee()) {
return ctx.Trigger(storagemarket.ProviderEventDealRejected, xerrors.New("clientMarketBalance.Available too small"))
}

// Verified deal checks
if proposal.VerifiedDeal {
dataCap, err := environment.Node().GetDataCap(ctx.Context(), proposal.Client, tok)
if err != nil {
return ctx.Trigger(storagemarket.ProviderEventDealRejected, xerrors.Errorf("node error fetching verified data cap: %w", err))
}

pieceSize := big.NewIntUnsigned(uint64(proposal.PieceSize))
if dataCap.LessThan(pieceSize) {
return ctx.Trigger(storagemarket.ProviderEventDealRejected, xerrors.Errorf("verified deal DataCap too small for proposed piece size"))
}
}

return ctx.Trigger(storagemarket.ProviderEventDealDeciding)
}

Expand Down
46 changes: 46 additions & 0 deletions storagemarket/impl/providerstates/provider_states_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
"github.com/filecoin-project/go-statemachine/fsm"
fsmtest "github.com/filecoin-project/go-statemachine/fsm/testutil"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/builtin/market"
"github.com/filecoin-project/specs-actors/actors/builtin/verifreg"
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
Expand Down Expand Up @@ -130,6 +132,44 @@ func TestValidateDealProposal(t *testing.T) {
require.Equal(t, "deal rejected: clientMarketBalance.Available too small", deal.Message)
},
},
"verified deal succeeds": {
dealParams: dealParams{
VerifiedDeal: true,
},
nodeParams: nodeParams{
DataCap: big.NewIntUnsigned(uint64(defaultPieceSize)),
},
dealInspector: func(t *testing.T, deal storagemarket.MinerDeal, env *fakeEnvironment) {
require.True(t, deal.Proposal.VerifiedDeal)
tut.AssertDealState(t, storagemarket.StorageDealAcceptWait, deal.State)
},
},
"verified deal fails getting client data cap": {
dealParams: dealParams{
VerifiedDeal: true,
},
nodeParams: nodeParams{
GetDataCapError: xerrors.Errorf("failure getting data cap"),
},
dealInspector: func(t *testing.T, deal storagemarket.MinerDeal, env *fakeEnvironment) {
require.True(t, deal.Proposal.VerifiedDeal)
tut.AssertDealState(t, storagemarket.StorageDealRejecting, deal.State)
require.Equal(t, "deal rejected: node error fetching verified data cap: failure getting data cap", deal.Message)
},
},
"verified deal fails with insufficient data cap": {
dealParams: dealParams{
VerifiedDeal: true,
},
nodeParams: nodeParams{
DataCap: big.NewIntUnsigned(uint64(defaultPieceSize - 1)),
},
dealInspector: func(t *testing.T, deal storagemarket.MinerDeal, env *fakeEnvironment) {
require.True(t, deal.Proposal.VerifiedDeal)
tut.AssertDealState(t, storagemarket.StorageDealRejecting, deal.State)
require.Equal(t, "deal rejected: verified deal DataCap too small for proposed piece size", deal.Message)
},
},
}
for test, data := range tests {
t.Run(test, func(t *testing.T) {
Expand Down Expand Up @@ -819,6 +859,8 @@ type nodeParams struct {
OnDealExpiredError error
OnDealSlashedError error
OnDealSlashedEpoch abi.ChainEpoch
DataCap verifreg.DataCap
GetDataCapError error
}

type dealParams struct {
Expand All @@ -832,6 +874,7 @@ type dealParams struct {
StartEpoch abi.ChainEpoch
EndEpoch abi.ChainEpoch
FastRetrieval bool
VerifiedDeal bool
}

type environmentParams struct {
Expand Down Expand Up @@ -913,6 +956,8 @@ func makeExecutor(ctx context.Context,
PublishDealsError: nodeParams.PublishDealsError,
OnDealCompleteError: nodeParams.OnDealCompleteError,
LocatePieceForDealWithinSectorError: nodeParams.LocatePieceForDealWithinSectorError,
DataCap: nodeParams.DataCap,
GetDataCapErr: nodeParams.GetDataCapError,
}

if nodeParams.MinerAddr == address.Undef {
Expand Down Expand Up @@ -945,6 +990,7 @@ func makeExecutor(ctx context.Context,
if dealParams.PieceSize != abi.PaddedPieceSize(0) {
proposal.PieceSize = dealParams.PieceSize
}
proposal.VerifiedDeal = dealParams.VerifiedDeal
signedProposal := &market.ClientDealProposal{
Proposal: proposal,
ClientSignature: *tut.MakeTestSignature(),
Expand Down
4 changes: 4 additions & 0 deletions storagemarket/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"io"

"github.com/filecoin-project/specs-actors/actors/builtin/verifreg"
"github.com/ipfs/go-cid"

"github.com/filecoin-project/go-address"
Expand Down Expand Up @@ -74,6 +75,9 @@ type StorageProviderNode interface {

// LocatePieceForDealWithinSector looks up a given dealID in the miners sectors, and returns its sectorID and location
LocatePieceForDealWithinSector(ctx context.Context, dealID abi.DealID, tok shared.TipSetToken) (sectorID uint64, offset uint64, length uint64, err error)

// GetDataCap gets the current data cap for addr
GetDataCap(ctx context.Context, addr address.Address, tok shared.TipSetToken) (verifreg.DataCap, error)
}

// StorageClientNode are node dependencies for a StorageClient
Expand Down
8 changes: 8 additions & 0 deletions storagemarket/testnodes/testnodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/builtin/market"
"github.com/filecoin-project/specs-actors/actors/builtin/verifreg"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
"github.com/ipfs/go-cid"
Expand Down Expand Up @@ -280,6 +281,8 @@ type FakeProviderNode struct {
OnDealCompleteError error
OnDealCompleteCalls []storagemarket.MinerDeal
LocatePieceForDealWithinSectorError error
DataCap verifreg.DataCap
GetDataCapErr error
}

// PublishDeals simulates publishing a deal by adding it to the storage market state
Expand Down Expand Up @@ -324,4 +327,9 @@ func (n *FakeProviderNode) LocatePieceForDealWithinSector(ctx context.Context, d
return 0, 0, 0, n.LocatePieceForDealWithinSectorError
}

// GetDataCap gets the current data cap for addr
func (n *FakeProviderNode) GetDataCap(ctx context.Context, addr address.Address, tok shared.TipSetToken) (verifreg.DataCap, error) {
return n.DataCap, n.GetDataCapErr
}

var _ storagemarket.StorageProviderNode = (*FakeProviderNode)(nil)

0 comments on commit f33b442

Please sign in to comment.