github.com/filecoin-project/specs-actors/v4@v4.0.2/support/agent/deal_client_agent.go (about)

     1  package agent
     2  
     3  import (
     4  	"crypto/sha256"
     5  	mh "github.com/multiformats/go-multihash"
     6  	"math/bits"
     7  	"math/rand"
     8  	"strconv"
     9  
    10  	"github.com/filecoin-project/go-address"
    11  	"github.com/filecoin-project/go-state-types/abi"
    12  	"github.com/filecoin-project/go-state-types/big"
    13  	"github.com/filecoin-project/go-state-types/cbor"
    14  	"github.com/filecoin-project/go-state-types/crypto"
    15  	"github.com/filecoin-project/specs-actors/v4/actors/builtin"
    16  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/market"
    17  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/power"
    18  	"github.com/filecoin-project/specs-actors/v4/actors/builtin/reward"
    19  	"github.com/ipfs/go-cid"
    20  )
    21  
    22  type DealClientConfig struct {
    23  	DealRate         float64         // deals made per epoch
    24  	MinPieceSize     uint64          // minimum piece size (actual piece size be rounded up to power of 2)
    25  	MaxPieceSize     uint64          // maximum piece size
    26  	MinStoragePrice  abi.TokenAmount // minimum price per epoch a client will pay for storage (may be zero)
    27  	MaxStoragePrice  abi.TokenAmount // maximum price per epoch a client will pay for storage
    28  	MinMarketBalance abi.TokenAmount // balance below which client will top up funds in market actor
    29  	MaxMarketBalance abi.TokenAmount // balance to which client will top up funds in market actor
    30  }
    31  
    32  type DealClientAgent struct {
    33  	DealCount int
    34  
    35  	account    address.Address
    36  	config     DealClientConfig
    37  	dealEvents *RateIterator
    38  	rnd        *rand.Rand
    39  
    40  	// tracks funds expected to be locked for client deal payment
    41  	expectedMarketBalance abi.TokenAmount
    42  }
    43  
    44  func AddDealClientsForAccounts(s SimState, accounts []address.Address, seed int64, config DealClientConfig) []*DealClientAgent {
    45  	rnd := rand.New(rand.NewSource(seed))
    46  	var agents []*DealClientAgent
    47  	for _, account := range accounts {
    48  		agent := NewDealClientAgent(account, rnd.Int63(), config)
    49  		agents = append(agents, agent)
    50  		s.AddAgent(agent)
    51  	}
    52  	return agents
    53  }
    54  
    55  func NewDealClientAgent(account address.Address, seed int64, config DealClientConfig) *DealClientAgent {
    56  	rnd := rand.New(rand.NewSource(seed))
    57  	return &DealClientAgent{
    58  		account:               account,
    59  		config:                config,
    60  		rnd:                   rnd,
    61  		expectedMarketBalance: big.Zero(),
    62  		dealEvents:            NewRateIterator(config.DealRate, rnd.Int63()),
    63  	}
    64  }
    65  
    66  func (dca *DealClientAgent) Tick(s SimState) ([]message, error) {
    67  	// aggregate all deals into one message
    68  	if err := dca.dealEvents.Tick(func() error {
    69  		provider := s.ChooseDealProvider()
    70  
    71  		// provider will be nil if called before any miners are added to system
    72  		if provider == nil {
    73  			return nil
    74  		}
    75  
    76  		if err := dca.createDeal(s, provider); err != nil {
    77  			return err
    78  		}
    79  		return nil
    80  	}); err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	// add message to update market balance if necessary
    85  	messages := dca.updateMarketBalance()
    86  
    87  	return messages, nil
    88  }
    89  
    90  func (dca *DealClientAgent) updateMarketBalance() []message {
    91  	if dca.expectedMarketBalance.GreaterThanEqual(dca.config.MinMarketBalance) {
    92  		return []message{}
    93  	}
    94  
    95  	balanceToAdd := big.Sub(dca.config.MaxMarketBalance, dca.expectedMarketBalance)
    96  
    97  	return []message{{
    98  		From:   dca.account,
    99  		To:     builtin.StorageMarketActorAddr,
   100  		Value:  balanceToAdd,
   101  		Method: builtin.MethodsMarket.AddBalance,
   102  		Params: &dca.account,
   103  		ReturnHandler: func(v SimState, msg message, ret cbor.Marshaler) error {
   104  			dca.expectedMarketBalance = dca.config.MaxMarketBalance
   105  			return nil
   106  		},
   107  	}}
   108  }
   109  
   110  // Create a proposal
   111  // Return false if provider if deal can't be performed because client or provider lacks funds
   112  func (dca *DealClientAgent) createDeal(s SimState, provider DealProvider) error {
   113  	pieceCid, err := dca.generatePieceCID()
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	pieceSize := dca.config.MinPieceSize +
   119  		uint64(dca.rnd.Int63n(int64(dca.config.MaxPieceSize-dca.config.MinPieceSize)))
   120  
   121  	// round to next power of two
   122  	if bits.OnesCount64(pieceSize) > 1 {
   123  		pieceSize = 1 << bits.Len64(pieceSize)
   124  	}
   125  
   126  	providerCollateral, err := calculateProviderCollateral(s, abi.PaddedPieceSize(pieceSize))
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	// if provider does not have enough collateral, just skip this deal
   132  	if provider.AvailableCollateral().LessThan(providerCollateral) {
   133  		return nil
   134  	}
   135  
   136  	// storage price is uniformly distributed between min an max
   137  	price := big.Add(dca.config.MinStoragePrice,
   138  		big.NewInt(dca.rnd.Int63n(int64(big.Sub(dca.config.MaxStoragePrice, dca.config.MinStoragePrice).Uint64()))))
   139  
   140  	// deal start is earliest possible epoch, deal end is uniformly distributed between start and max.
   141  	dealStart, maxDealEnd := provider.DealRange(s.GetEpoch())
   142  	dealEnd := dealStart + abi.ChainEpoch(dca.rnd.Int63n(int64(maxDealEnd-dealStart)))
   143  	if dealEnd-dealStart < market.DealMinDuration {
   144  		dealEnd = dealStart + market.DealMinDuration
   145  	}
   146  
   147  	// lower expected balance in anticipation of market actor locking storage fee
   148  	storageFee := big.Mul(big.NewInt(int64(dealEnd-dealStart)), price)
   149  
   150  	// if this client does not have enough balance for storage fee, just skip this deal
   151  	if dca.expectedMarketBalance.LessThan(storageFee) {
   152  		return nil
   153  	}
   154  
   155  	dca.expectedMarketBalance = big.Sub(dca.expectedMarketBalance, storageFee)
   156  
   157  	proposal := market.DealProposal{
   158  		PieceCID:             pieceCid,
   159  		PieceSize:            abi.PaddedPieceSize(pieceSize),
   160  		VerifiedDeal:         false,
   161  		Client:               dca.account,
   162  		Provider:             provider.Address(),
   163  		Label:                dca.account.String() + ":" + strconv.Itoa(dca.DealCount),
   164  		StartEpoch:           dealStart,
   165  		EndEpoch:             dealEnd,
   166  		StoragePricePerEpoch: price,
   167  		ProviderCollateral:   providerCollateral,
   168  		ClientCollateral:     big.Zero(),
   169  	}
   170  
   171  	provider.CreateDeal(market.ClientDealProposal{
   172  		Proposal:        proposal,
   173  		ClientSignature: crypto.Signature{},
   174  	})
   175  	dca.DealCount++
   176  	return nil
   177  }
   178  
   179  func (dca *DealClientAgent) generatePieceCID() (cid.Cid, error) {
   180  	data := make([]byte, 10)
   181  	if _, err := dca.rnd.Read(data); err != nil {
   182  		return cid.Cid{}, err
   183  	}
   184  
   185  	sum := sha256.Sum256(data)
   186  	hash, err := mh.Encode(sum[:], market.PieceCIDPrefix.MhType)
   187  	if err != nil {
   188  		panic(err)
   189  	}
   190  	return cid.NewCidV1(market.PieceCIDPrefix.Codec, hash), nil
   191  }
   192  
   193  // Always choose the minimum collateral. This appears to be realistic, and there's is not an obvious way to model a
   194  // more complex distribution.
   195  func calculateProviderCollateral(s SimState, pieceSize abi.PaddedPieceSize) (abi.TokenAmount, error) {
   196  	var powerSt power.State
   197  	if err := s.GetState(builtin.StoragePowerActorAddr, &powerSt); err != nil {
   198  		return big.Zero(), err
   199  	}
   200  
   201  	var rewardSt reward.State
   202  	if err := s.GetState(builtin.RewardActorAddr, &rewardSt); err != nil {
   203  		return big.Zero(), err
   204  	}
   205  
   206  	min, _ := market.DealProviderCollateralBounds(pieceSize, false, powerSt.TotalRawBytePower,
   207  		powerSt.TotalQualityAdjPower, rewardSt.ThisEpochBaselinePower, s.NetworkCirculatingSupply())
   208  	return min, nil
   209  }