Skip to content
This repository has been archived by the owner on Jun 6, 2023. It is now read-only.

Commit

Permalink
Market state: Change Label to be a Union of strings and bytes
Browse files Browse the repository at this point in the history
  • Loading branch information
arajasek committed Mar 12, 2022
1 parent a2b8075 commit c755c04
Show file tree
Hide file tree
Showing 8 changed files with 655 additions and 65 deletions.
393 changes: 393 additions & 0 deletions actors/builtin/market/cbor_gen.go

Large diffs are not rendered by default.

228 changes: 200 additions & 28 deletions actors/builtin/market/deal.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
package market

import (
"bytes"
"fmt"
addr "github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
acrypto "github.com/filecoin-project/go-state-types/crypto"
market0 "github.com/filecoin-project/specs-actors/actors/builtin/market"
"github.com/ipfs/go-cid"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
"io"
)

//var PieceCIDPrefix = cid.Prefix{
Expand All @@ -12,6 +22,147 @@ import (
//}
var PieceCIDPrefix = market0.PieceCIDPrefix

// The DealLabel is a "kinded union" -- can either be a String or a byte slice, but not both
// TODO: What represents the empty label and how do we marshall it?
type DealLabel struct {
labelString string
labelBytes []byte
}

var EmptyDealLabel = DealLabel{}

func NewDealLabelFromString(s string) (DealLabel, error) {
if len(s) > DealMaxLabelSize {
return EmptyDealLabel, xerrors.Errorf("provided string is too large to be a label (%d), max length (%d)", len(s), DealMaxLabelSize)
}
// TODO: Check UTF-8
return DealLabel{
labelString: s,
}, nil
}

func NewDealLabelFromBytes(b []byte) (DealLabel, error) {
if len(b) > DealMaxLabelSize {
return EmptyDealLabel, xerrors.Errorf("provided bytes are too large to be a label (%d), max length (%d)", len(b), DealMaxLabelSize)
}
// TODO: nilcheck? See note about emptiness.
return DealLabel{
labelBytes: b,
}, nil
}

func (label DealLabel) IsStringSet() bool {
return label.labelString == ""
}

func (label DealLabel) IsBytesSet() bool {
return len(label.labelBytes) != 0
}

func (label DealLabel) ToString() (string, error) {
if label.IsBytesSet() {
return "", xerrors.Errorf("label has bytes set")
}

return label.labelString, nil
}

func (label DealLabel) ToBytes() ([]byte, error) {
if label.IsStringSet() {
return nil, xerrors.Errorf("label has string set")
}

return label.labelBytes, nil
}

func (label DealLabel) Length() int {
if label.IsStringSet() {
return len(label.labelString)
}

return len(label.labelBytes)
}
func (l DealLabel) Equals(o DealLabel) bool {
return l.labelString == o.labelString && bytes.Equal(l.labelBytes, o.labelBytes)
}

func (label *DealLabel) MarshalCBOR(w io.Writer) error {
// TODO: Whait if nil?
if label.IsStringSet() && label.IsBytesSet() {
return fmt.Errorf("dealLabel cannot have both string and bytes set")
}

scratch := make([]byte, 9)

if label.IsBytesSet() {
if len(label.labelBytes) > cbg.ByteArrayMaxLen {
return xerrors.Errorf("labelBytes is too long to marshal (%d), max allowed (%d)", len(label.labelBytes), cbg.ByteArrayMaxLen)
}

if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(label.labelBytes))); err != nil {
return err
}

if _, err := w.Write(label.labelBytes[:]); err != nil {
return err
}
} else {
// "Empty" labels (empty strings and nil bytes) get marshaled as empty strings
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(label.labelString))); err != nil {
return err
}
if _, err := io.WriteString(w, label.labelString); err != nil {
return err
}
}

return nil
}

func (label *DealLabel) UnmarshalCBOR(br io.Reader) error {
// TODO: Whait if nil?

scratch := make([]byte, 8)

maj, length, err := cbg.CborReadHeaderBuf(br, scratch)
if err != nil {
return err
}

if maj == cbg.MajTextString {
label.labelBytes = nil
if length > cbg.MaxLength {
return fmt.Errorf("label string was too long (%d), max allowed (%d)", length, cbg.MaxLength)
}

buf := make([]byte, length)
_, err = io.ReadAtLeast(br, buf, int(length))
if err != nil {
return err
}

// TODO: Check UTF-8 here too
label.labelString = string(buf)
} else if maj == cbg.MajByteString {
label.labelString = ""
if length > cbg.ByteArrayMaxLen {
return fmt.Errorf("label bytes was too long (%d), max allowed (%d)", length, cbg.ByteArrayMaxLen)
}

if length > 0 {
label.labelBytes = make([]uint8, length)
}

if _, err := io.ReadFull(br, label.labelBytes[:]); err != nil {
return err
}
} else {
return fmt.Errorf("unexpected major tag (%d) when unmarshaling DealLabel: only textString (%d) or byteString (%d) expected", maj, cbg.MajTextString, cbg.MajByteString)
}

return nil
}

// Note: Deal Collateral is only released and returned to clients and miners
// when the storage deal stops counting towards power. In the current iteration,
// it will be released when the sector containing the storage deals expires,
Expand All @@ -20,33 +171,54 @@ var PieceCIDPrefix = market0.PieceCIDPrefix
// minimal deals that last for a long time.
// Note: ClientCollateralPerEpoch may not be needed and removed pending future confirmation.
// There will be a Minimum value for both client and provider deal collateral.
//type DealProposal struct {
// PieceCID cid.Cid `checked:"true"` // Checked in validateDeal, CommP
// PieceSize abi.PaddedPieceSize
// VerifiedDeal bool
// Client addr.Address
// Provider addr.Address
//
// // Label is an arbitrary client chosen label to apply to the deal
// // TODO: Limit the size of this: https://github.com/filecoin-project/specs-actors/issues/897
// Label string
//
// // Nominal start epoch. Deal payment is linear between StartEpoch and EndEpoch,
// // with total amount StoragePricePerEpoch * (EndEpoch - StartEpoch).
// // Storage deal must appear in a sealed (proven) sector no later than StartEpoch,
// // otherwise it is invalid.
// StartEpoch abi.ChainEpoch
// EndEpoch abi.ChainEpoch
// StoragePricePerEpoch abi.TokenAmount
//
// ProviderCollateral abi.TokenAmount
// ClientCollateral abi.TokenAmount
//}
type DealProposal = market0.DealProposal
type DealProposal struct {
PieceCID cid.Cid `checked:"true"` // Checked in validateDeal, CommP
PieceSize abi.PaddedPieceSize
VerifiedDeal bool
Client addr.Address
Provider addr.Address

// Label is an arbitrary client chosen label to apply to the deal
Label DealLabel

// Nominal start epoch. Deal payment is linear between StartEpoch and EndEpoch,
// with total amount StoragePricePerEpoch * (EndEpoch - StartEpoch).
// Storage deal must appear in a sealed (proven) sector no later than StartEpoch,
// otherwise it is invalid.
StartEpoch abi.ChainEpoch
EndEpoch abi.ChainEpoch
StoragePricePerEpoch abi.TokenAmount

ProviderCollateral abi.TokenAmount
ClientCollateral abi.TokenAmount
}

// ClientDealProposal is a DealProposal signed by a client
// type ClientDealProposal struct {
// Proposal DealProposal
// ClientSignature crypto.Signature
// }
type ClientDealProposal = market0.ClientDealProposal
type ClientDealProposal struct {
Proposal DealProposal
ClientSignature acrypto.Signature
}

func (p *DealProposal) Duration() abi.ChainEpoch {
return p.EndEpoch - p.StartEpoch
}

func (p *DealProposal) TotalStorageFee() abi.TokenAmount {
return big.Mul(p.StoragePricePerEpoch, big.NewInt(int64(p.Duration())))
}

func (p *DealProposal) ClientBalanceRequirement() abi.TokenAmount {
return big.Add(p.ClientCollateral, p.TotalStorageFee())
}

func (p *DealProposal) ProviderBalanceRequirement() abi.TokenAmount {
return p.ProviderCollateral
}

func (p *DealProposal) Cid() (cid.Cid, error) {
buf := new(bytes.Buffer)
if err := p.MarshalCBOR(buf); err != nil {
return cid.Undef, err
}
return abi.CidBuilder.Sum(buf.Bytes())
}
11 changes: 5 additions & 6 deletions actors/builtin/market/market_actor.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,9 @@ func (a Actor) AddBalance(rt Runtime, providerOrClientAddress *addr.Address) *ab
return nil
}

// type PublishStorageDealsParams struct {
// Deals []ClientDealProposal
// }
type PublishStorageDealsParams = market0.PublishStorageDealsParams
type PublishStorageDealsParams struct {
Deals []ClientDealProposal
}

type PublishStorageDealsReturn struct {
IDs []abi.DealID
Expand Down Expand Up @@ -789,8 +788,8 @@ func validateDeal(rt Runtime, deal ClientDealProposal, networkRawPower, networkQ

proposal := deal.Proposal

if len(proposal.Label) > DealMaxLabelSize {
return xerrors.Errorf("deal label can be at most %d bytes, is %d", DealMaxLabelSize, len(proposal.Label))
if proposal.Label.Length() > DealMaxLabelSize {
return xerrors.Errorf("deal label can be at most %d bytes, is %d", DealMaxLabelSize, proposal.Label.Length())
}

if err := proposal.PieceSize.Validate(); err != nil {
Expand Down
60 changes: 38 additions & 22 deletions actors/builtin/market/market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2415,8 +2415,10 @@ func TestMarketActorDeals(t *testing.T) {
rt.Verify()
}

dealProposal.Label = "foo"
label, err := market.NewDealLabelFromString("foo")
assert.NoError(t, err)

dealProposal.Label = label
// Same deal with a different label should work
{
rt.SetCaller(worker, builtin.AccountActorCodeID)
Expand All @@ -2443,31 +2445,38 @@ func TestMaxDealLabelSize(t *testing.T) {
actor.addParticipantFunds(rt, client, abi.NewTokenAmount(20000000))

dealProposal := generateDealProposal(client, provider, abi.ChainEpoch(1), abi.ChainEpoch(200*builtin.EpochsInDay))
dealProposal.Label = string(make([]byte, market.DealMaxLabelSize))
params := &market.PublishStorageDealsParams{Deals: []market.ClientDealProposal{{Proposal: dealProposal}}}

// Label at max size should work.
{
rt.SetCaller(worker, builtin.AccountActorCodeID)
actor.publishDeals(rt, minerAddrs, publishDealReq{deal: dealProposal})
}
label, err := market.NewDealLabelFromString(string(make([]byte, market.DealMaxLabelSize)))
assert.NoError(t, err)

dealProposal.Label = string(make([]byte, market.DealMaxLabelSize+1))
dealProposal.Label = label
//params := &market.PublishStorageDealsParams{Deals: []market.ClientDealProposal{{Proposal: dealProposal}}}

// Label greater than max size should fail.
// DealLabel at max size should work.
{
rt.ExpectValidateCallerType(builtin.AccountActorCodeID, builtin.MultisigActorCodeID)
rt.ExpectSend(provider, builtin.MethodsMiner.ControlAddresses, nil, abi.NewTokenAmount(0), &miner.GetControlAddressesReturn{Worker: worker, Owner: owner}, 0)
expectQueryNetworkInfo(rt, actor)
rt.ExpectVerifySignature(crypto.Signature{}, client, mustCbor(&params.Deals[0].Proposal), nil)
rt.SetCaller(worker, builtin.AccountActorCodeID)
rt.ExpectAbort(exitcode.ErrIllegalArgument, func() {
rt.Call(actor.PublishStorageDeals, params)
})

rt.Verify()
actor.publishDeals(rt, minerAddrs, publishDealReq{deal: dealProposal})
}
actor.checkState(rt)

//label, err = market.NewDealLabelFromString(string(make([]byte, market.DealMaxLabelSize+1)))
//assert.NoError(t, err)
//
//dealProposal.Label = label

// DealLabel greater than max size should fail.
//{
// rt.ExpectValidateCallerType(builtin.AccountActorCodeID, builtin.MultisigActorCodeID)
// rt.ExpectSend(provider, builtin.MethodsMiner.ControlAddresses, nil, abi.NewTokenAmount(0), &miner.GetControlAddressesReturn{Worker: worker, Owner: owner}, 0)
// expectQueryNetworkInfo(rt, actor)
// rt.ExpectVerifySignature(crypto.Signature{}, client, mustCbor(&params.Deals[0].Proposal), nil)
// rt.SetCaller(worker, builtin.AccountActorCodeID)
// rt.ExpectAbort(exitcode.ErrIllegalArgument, func() {
// rt.Call(actor.PublishStorageDeals, params)
// })
//
// rt.Verify()
//}
//actor.checkState(rt)
}

func TestComputeDataCommitment(t *testing.T) {
Expand Down Expand Up @@ -3266,7 +3275,10 @@ func (h *marketActorTestHarness) generateAndPublishDealForPiece(rt *mock.Runtime
clientCollateral := big.NewInt(10)
providerCollateral := big.NewInt(10)

deal := market.DealProposal{PieceCID: pieceCID, PieceSize: pieceSize, Client: client, Provider: minerAddrs.provider, Label: "label", StartEpoch: startEpoch,
label, err := market.NewDealLabelFromString("label")
assert.NoError(h.t, err)

deal := market.DealProposal{PieceCID: pieceCID, PieceSize: pieceSize, Client: client, Provider: minerAddrs.provider, Label: label, StartEpoch: startEpoch,
EndEpoch: endEpoch, StoragePricePerEpoch: storagePerEpoch, ProviderCollateral: providerCollateral, ClientCollateral: clientCollateral}

// add funds
Expand Down Expand Up @@ -3317,8 +3329,12 @@ func generateDealProposalWithCollateral(client, provider address.Address, provid
pieceCid := tutil.MakeCID("1", &market.PieceCIDPrefix)
pieceSize := abi.PaddedPieceSize(2048)
storagePerEpoch := big.NewInt(10)
label, err := market.NewDealLabelFromString("label")
if err != nil {
panic(err)
}

return market.DealProposal{PieceCID: pieceCid, PieceSize: pieceSize, Client: client, Provider: provider, Label: "label", StartEpoch: startEpoch,
return market.DealProposal{PieceCID: pieceCid, PieceSize: pieceSize, Client: client, Provider: provider, Label: label, StartEpoch: startEpoch,
EndEpoch: endEpoch, StoragePricePerEpoch: storagePerEpoch, ProviderCollateral: providerCollateral, ClientCollateral: clientCollateral}
}

Expand Down
Loading

0 comments on commit c755c04

Please sign in to comment.