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