Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate client's DataCap for verified deals #307

Merged
merged 1 commit into from
Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)