github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/cmd/util/ledger/reporters/account_reporter.go (about) 1 package reporters 2 3 import ( 4 "fmt" 5 goRuntime "runtime" 6 "sync" 7 8 "github.com/rs/zerolog" 9 "github.com/schollz/progressbar/v3" 10 11 "github.com/onflow/cadence" 12 jsoncdc "github.com/onflow/cadence/encoding/json" 13 "github.com/onflow/cadence/runtime/common" 14 15 "github.com/onflow/flow-go/fvm" 16 "github.com/onflow/flow-go/fvm/environment" 17 "github.com/onflow/flow-go/fvm/storage/snapshot" 18 "github.com/onflow/flow-go/fvm/storage/state" 19 "github.com/onflow/flow-go/fvm/systemcontracts" 20 "github.com/onflow/flow-go/ledger" 21 "github.com/onflow/flow-go/model/flow" 22 ) 23 24 // AccountReporter iterates through registers keeping a map of register sizes 25 // reports on storage metrics 26 type AccountReporter struct { 27 Log zerolog.Logger 28 RWF ReportWriterFactory 29 Chain flow.Chain 30 } 31 32 var _ ledger.Reporter = &AccountReporter{} 33 34 func (r *AccountReporter) Name() string { 35 return "Account Reporter" 36 } 37 38 type accountRecord struct { 39 Address string `json:"address"` 40 StorageUsed uint64 `json:"storageUsed"` 41 AccountBalance uint64 `json:"accountBalance"` 42 FUSDBalance uint64 `json:"fusdBalance"` 43 HasVault bool `json:"hasVault"` 44 HasReceiver bool `json:"hasReceiver"` 45 IsDapper bool `json:"isDapper"` 46 } 47 48 type contractRecord struct { 49 Address string `json:"address"` 50 Contract string `json:"contract"` 51 } 52 53 type momentsRecord struct { 54 Address string `json:"address"` 55 Moments int `json:"moments"` 56 } 57 58 func (r *AccountReporter) Report(payload []ledger.Payload, commit ledger.State) error { 59 rwa := r.RWF.ReportWriter("account_report") 60 rwc := r.RWF.ReportWriter("contract_report") 61 rwm := r.RWF.ReportWriter("moments_report") 62 defer rwa.Close() 63 defer rwc.Close() 64 defer rwm.Close() 65 66 snapshot := NewStorageSnapshotFromPayload(payload) 67 68 workerCount := goRuntime.NumCPU() / 2 69 if workerCount == 0 { 70 workerCount = 1 71 } 72 73 addressIndexes := make(chan uint64, workerCount) 74 defer close(addressIndexes) 75 76 // create multiple workers to generate account data report concurrently 77 wg := &sync.WaitGroup{} 78 for i := 0; i < workerCount; i++ { 79 go func() { 80 adp := newAccountDataProcessor( 81 r.Log, 82 rwa, 83 rwc, 84 rwm, 85 r.Chain, 86 snapshot) 87 for indx := range addressIndexes { 88 adp.reportAccountData(indx) 89 wg.Done() 90 } 91 }() 92 } 93 94 txnState := state.NewTransactionState( 95 snapshot, 96 state.DefaultParameters()) 97 gen := environment.NewAddressGenerator(txnState, r.Chain) 98 addressCount := gen.AddressCount() 99 100 progress := progressbar.Default(int64(addressCount), "Processing:") 101 102 // produce jobs for workers to process 103 for i := uint64(1); i <= addressCount; i++ { 104 addressIndexes <- i 105 106 wg.Add(1) 107 108 err := progress.Add(1) 109 if err != nil { 110 panic(fmt.Errorf("progress.Add(1): %w", err)) 111 } 112 } 113 114 // wait until all jobs are done 115 wg.Wait() 116 117 err := progress.Finish() 118 if err != nil { 119 panic(fmt.Errorf("progress.Finish(): %w", err)) 120 } 121 122 return nil 123 } 124 125 type balanceProcessor struct { 126 vm fvm.VM 127 ctx fvm.Context 128 storageSnapshot snapshot.StorageSnapshot 129 env environment.Environment 130 balanceScript []byte 131 momentsScript []byte 132 133 rwa ReportWriter 134 rwc ReportWriter 135 logger zerolog.Logger 136 rwm ReportWriter 137 fusdScript []byte 138 } 139 140 func NewBalanceReporter( 141 chain flow.Chain, 142 snapshot snapshot.StorageSnapshot, 143 ) *balanceProcessor { 144 vm := fvm.NewVirtualMachine() 145 ctx := fvm.NewContext( 146 fvm.WithChain(chain), 147 fvm.WithMemoryAndInteractionLimitsDisabled()) 148 149 env := environment.NewScriptEnvironmentFromStorageSnapshot( 150 ctx.EnvironmentParams, 151 snapshot) 152 153 return &balanceProcessor{ 154 vm: vm, 155 ctx: ctx, 156 storageSnapshot: snapshot, 157 env: env, 158 } 159 } 160 161 func newAccountDataProcessor( 162 logger zerolog.Logger, 163 rwa ReportWriter, 164 rwc ReportWriter, 165 rwm ReportWriter, 166 chain flow.Chain, 167 snapshot snapshot.StorageSnapshot, 168 ) *balanceProcessor { 169 bp := NewBalanceReporter(chain, snapshot) 170 sc := systemcontracts.SystemContractsForChain(bp.ctx.Chain.ChainID()) 171 172 bp.logger = logger 173 bp.rwa = rwa 174 bp.rwc = rwc 175 bp.rwm = rwm 176 bp.balanceScript = []byte(fmt.Sprintf(` 177 import FungibleToken from 0x%s 178 import FlowToken from 0x%s 179 access(all) fun main(account: Address): UFix64 { 180 let acct = getAccount(account) 181 let vaultRef = acct.capabilities.borrow<&FlowToken.Vault>(/public/flowTokenBalance) 182 ?? panic("Could not borrow Balance reference to the Vault") 183 return vaultRef.balance 184 } 185 `, sc.FungibleToken.Address.Hex(), sc.FlowToken.Address.Hex())) 186 187 bp.fusdScript = []byte(fmt.Sprintf(` 188 import FungibleToken from 0x%s 189 import FUSD from 0x%s 190 access(all) fun main(address: Address): UFix64 { 191 let account = getAccount(address) 192 let vaultRef = account.capabilities.borrow<&FUSD.Vault>(/public/fusdBalance) 193 ?? panic("Could not borrow Balance reference to the Vault") 194 return vaultRef.balance 195 } 196 `, sc.FungibleToken.Address.Hex(), "3c5959b568896393")) 197 198 bp.momentsScript = []byte(` 199 import TopShot from 0x0b2a3299cc857e29 200 access(all) fun main(account: Address): Int { 201 let acct = getAccount(account) 202 let collectionRef = acct.capabilities.borrow<&{TopShot.MomentCollectionPublic}>(/public/MomentCollection)! 203 return collectionRef.getIDs().length 204 } 205 `) 206 207 return bp 208 } 209 210 func (c *balanceProcessor) reportAccountData(indx uint64) { 211 address, err := c.ctx.Chain.AddressAtIndex(indx) 212 if err != nil { 213 c.logger. 214 Err(err). 215 Uint64("index", indx). 216 Msgf("Error getting address") 217 return 218 } 219 220 runtimeAddress := common.MustBytesToAddress(address.Bytes()) 221 222 u, err := c.env.GetStorageUsed(runtimeAddress) 223 if err != nil { 224 c.logger. 225 Err(err). 226 Uint64("index", indx). 227 Str("address", address.String()). 228 Msgf("Error getting storage used for account") 229 return 230 } 231 232 balance, hasVault, err := c.balance(address) 233 if err != nil { 234 c.logger. 235 Err(err). 236 Uint64("index", indx). 237 Str("address", address.String()). 238 Msgf("Error getting balance for account") 239 return 240 } 241 fusdBalance, err := c.fusdBalance(address) 242 if err != nil { 243 c.logger. 244 Err(err). 245 Uint64("index", indx). 246 Str("address", address.String()). 247 Msgf("Error getting FUSD balance for account") 248 return 249 } 250 251 dapper, err := c.isDapper(address) 252 if err != nil { 253 c.logger. 254 Err(err). 255 Uint64("index", indx). 256 Str("address", address.String()). 257 Msgf("Error determining if account is dapper account") 258 return 259 } 260 if dapper { 261 m, err := c.moments(address) 262 if err != nil { 263 c.logger. 264 Err(err). 265 Uint64("index", indx). 266 Str("address", address.String()). 267 Msgf("Error getting moments for account") 268 return 269 } 270 c.rwm.Write(momentsRecord{ 271 Address: address.Hex(), 272 Moments: m, 273 }) 274 } 275 276 hasReceiver, err := c.hasReceiver(address) 277 if err != nil { 278 c.logger. 279 Err(err). 280 Uint64("index", indx). 281 Str("address", address.String()). 282 Msgf("Error checking if account has a receiver") 283 return 284 } 285 286 c.rwa.Write(accountRecord{ 287 Address: address.Hex(), 288 StorageUsed: u, 289 AccountBalance: balance, 290 FUSDBalance: fusdBalance, 291 HasVault: hasVault, 292 HasReceiver: hasReceiver, 293 IsDapper: dapper, 294 }) 295 296 contracts, err := c.env.GetAccountContractNames(runtimeAddress) 297 if err != nil { 298 c.logger. 299 Err(err). 300 Uint64("index", indx). 301 Str("address", address.String()). 302 Msgf("Error getting account contract names") 303 return 304 } 305 if len(contracts) == 0 { 306 return 307 } 308 for _, contract := range contracts { 309 c.rwc.Write(contractRecord{ 310 Address: address.Hex(), 311 Contract: contract, 312 }) 313 } 314 315 } 316 317 func (c *balanceProcessor) balance(address flow.Address) (uint64, bool, error) { 318 script := fvm.Script(c.balanceScript).WithArguments( 319 jsoncdc.MustEncode(cadence.NewAddress(address)), 320 ) 321 322 _, output, err := c.vm.Run(c.ctx, script, c.storageSnapshot) 323 if err != nil { 324 return 0, false, err 325 } 326 327 var balance uint64 328 var hasVault bool 329 if output.Err == nil && output.Value != nil { 330 balance = uint64(output.Value.(cadence.UFix64)) 331 hasVault = true 332 } else { 333 hasVault = false 334 } 335 return balance, hasVault, nil 336 } 337 338 func (c *balanceProcessor) fusdBalance(address flow.Address) (uint64, error) { 339 script := fvm.Script(c.fusdScript).WithArguments( 340 jsoncdc.MustEncode(cadence.NewAddress(address)), 341 ) 342 343 _, output, err := c.vm.Run(c.ctx, script, c.storageSnapshot) 344 if err != nil { 345 return 0, err 346 } 347 348 var balance uint64 349 if output.Err == nil && output.Value != nil { 350 balance = uint64(output.Value.(cadence.UFix64)) 351 } 352 return balance, nil 353 } 354 355 func (c *balanceProcessor) moments(address flow.Address) (int, error) { 356 script := fvm.Script(c.momentsScript).WithArguments( 357 jsoncdc.MustEncode(cadence.NewAddress(address)), 358 ) 359 360 _, output, err := c.vm.Run(c.ctx, script, c.storageSnapshot) 361 if err != nil { 362 return 0, err 363 } 364 365 var m int 366 if output.Err == nil && output.Value != nil { 367 m = output.Value.(cadence.Int).Int() 368 } 369 return m, nil 370 } 371 372 func (c *balanceProcessor) isDapper(address flow.Address) (bool, error) { 373 receiver, err := c.ReadStored(address, common.PathDomainPublic, "dapperUtilityCoinReceiver") 374 if err != nil { 375 return false, fmt.Errorf("could not load dapper receiver at %s: %w", address, err) 376 } 377 return receiver != nil, nil 378 } 379 380 func (c *balanceProcessor) hasReceiver(address flow.Address) (bool, error) { 381 receiver, err := c.ReadStored(address, common.PathDomainPublic, "flowTokenReceiver") 382 383 if err != nil { 384 return false, fmt.Errorf("could not load receiver at %s: %w", address, err) 385 } 386 return receiver != nil, nil 387 } 388 389 func (c *balanceProcessor) ReadStored(address flow.Address, domain common.PathDomain, id string) (cadence.Value, error) { 390 addr, err := common.BytesToAddress(address.Bytes()) 391 if err != nil { 392 return nil, err 393 } 394 395 rt := c.env.BorrowCadenceRuntime() 396 defer c.env.ReturnCadenceRuntime(rt) 397 398 receiver, err := rt.ReadStored( 399 addr, 400 cadence.Path{ 401 Domain: domain, 402 Identifier: id, 403 }, 404 ) 405 return receiver, err 406 }