github.com/onflow/flow-go@v0.33.17/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 pub fun main(account: Address): UFix64 { 180 let acct = getAccount(account) 181 let vaultRef = acct.getCapability(/public/flowTokenBalance) 182 .borrow<&FlowToken.Vault{FungibleToken.Balance}>() 183 ?? panic("Could not borrow Balance reference to the Vault") 184 return vaultRef.balance 185 } 186 `, sc.FungibleToken.Address.Hex(), sc.FlowToken.Address.Hex())) 187 188 bp.fusdScript = []byte(fmt.Sprintf(` 189 import FungibleToken from 0x%s 190 import FUSD from 0x%s 191 pub fun main(address: Address): UFix64 { 192 let account = getAccount(address) 193 let vaultRef = account.getCapability(/public/fusdBalance)! 194 .borrow<&FUSD.Vault{FungibleToken.Balance}>() 195 ?? panic("Could not borrow Balance reference to the Vault") 196 return vaultRef.balance 197 } 198 `, sc.FungibleToken.Address.Hex(), "3c5959b568896393")) 199 200 bp.momentsScript = []byte(` 201 import TopShot from 0x0b2a3299cc857e29 202 pub fun main(account: Address): Int { 203 let acct = getAccount(account) 204 let collectionRef = acct.getCapability(/public/MomentCollection) 205 .borrow<&{TopShot.MomentCollectionPublic}>()! 206 return collectionRef.getIDs().length 207 } 208 `) 209 210 return bp 211 } 212 213 func (c *balanceProcessor) reportAccountData(indx uint64) { 214 address, err := c.ctx.Chain.AddressAtIndex(indx) 215 if err != nil { 216 c.logger. 217 Err(err). 218 Uint64("index", indx). 219 Msgf("Error getting address") 220 return 221 } 222 223 runtimeAddress := common.MustBytesToAddress(address.Bytes()) 224 225 u, err := c.env.GetStorageUsed(runtimeAddress) 226 if err != nil { 227 c.logger. 228 Err(err). 229 Uint64("index", indx). 230 Str("address", address.String()). 231 Msgf("Error getting storage used for account") 232 return 233 } 234 235 balance, hasVault, err := c.balance(address) 236 if err != nil { 237 c.logger. 238 Err(err). 239 Uint64("index", indx). 240 Str("address", address.String()). 241 Msgf("Error getting balance for account") 242 return 243 } 244 fusdBalance, err := c.fusdBalance(address) 245 if err != nil { 246 c.logger. 247 Err(err). 248 Uint64("index", indx). 249 Str("address", address.String()). 250 Msgf("Error getting FUSD balance for account") 251 return 252 } 253 254 dapper, err := c.isDapper(address) 255 if err != nil { 256 c.logger. 257 Err(err). 258 Uint64("index", indx). 259 Str("address", address.String()). 260 Msgf("Error determining if account is dapper account") 261 return 262 } 263 if dapper { 264 m, err := c.moments(address) 265 if err != nil { 266 c.logger. 267 Err(err). 268 Uint64("index", indx). 269 Str("address", address.String()). 270 Msgf("Error getting moments for account") 271 return 272 } 273 c.rwm.Write(momentsRecord{ 274 Address: address.Hex(), 275 Moments: m, 276 }) 277 } 278 279 hasReceiver, err := c.hasReceiver(address) 280 if err != nil { 281 c.logger. 282 Err(err). 283 Uint64("index", indx). 284 Str("address", address.String()). 285 Msgf("Error checking if account has a receiver") 286 return 287 } 288 289 c.rwa.Write(accountRecord{ 290 Address: address.Hex(), 291 StorageUsed: u, 292 AccountBalance: balance, 293 FUSDBalance: fusdBalance, 294 HasVault: hasVault, 295 HasReceiver: hasReceiver, 296 IsDapper: dapper, 297 }) 298 299 contracts, err := c.env.GetAccountContractNames(runtimeAddress) 300 if err != nil { 301 c.logger. 302 Err(err). 303 Uint64("index", indx). 304 Str("address", address.String()). 305 Msgf("Error getting account contract names") 306 return 307 } 308 if len(contracts) == 0 { 309 return 310 } 311 for _, contract := range contracts { 312 c.rwc.Write(contractRecord{ 313 Address: address.Hex(), 314 Contract: contract, 315 }) 316 } 317 318 } 319 320 func (c *balanceProcessor) balance(address flow.Address) (uint64, bool, error) { 321 script := fvm.Script(c.balanceScript).WithArguments( 322 jsoncdc.MustEncode(cadence.NewAddress(address)), 323 ) 324 325 _, output, err := c.vm.Run(c.ctx, script, c.storageSnapshot) 326 if err != nil { 327 return 0, false, err 328 } 329 330 var balance uint64 331 var hasVault bool 332 if output.Err == nil && output.Value != nil { 333 balance = output.Value.ToGoValue().(uint64) 334 hasVault = true 335 } else { 336 hasVault = false 337 } 338 return balance, hasVault, nil 339 } 340 341 func (c *balanceProcessor) fusdBalance(address flow.Address) (uint64, error) { 342 script := fvm.Script(c.fusdScript).WithArguments( 343 jsoncdc.MustEncode(cadence.NewAddress(address)), 344 ) 345 346 _, output, err := c.vm.Run(c.ctx, script, c.storageSnapshot) 347 if err != nil { 348 return 0, err 349 } 350 351 var balance uint64 352 if output.Err == nil && output.Value != nil { 353 balance = output.Value.ToGoValue().(uint64) 354 } 355 return balance, nil 356 } 357 358 func (c *balanceProcessor) moments(address flow.Address) (int, error) { 359 script := fvm.Script(c.momentsScript).WithArguments( 360 jsoncdc.MustEncode(cadence.NewAddress(address)), 361 ) 362 363 _, output, err := c.vm.Run(c.ctx, script, c.storageSnapshot) 364 if err != nil { 365 return 0, err 366 } 367 368 var m int 369 if output.Err == nil && output.Value != nil { 370 m = output.Value.(cadence.Int).Int() 371 } 372 return m, nil 373 } 374 375 func (c *balanceProcessor) isDapper(address flow.Address) (bool, error) { 376 receiver, err := c.ReadStored(address, common.PathDomainPublic, "dapperUtilityCoinReceiver") 377 if err != nil { 378 return false, fmt.Errorf("could not load dapper receiver at %s: %w", address, err) 379 } 380 return receiver != nil, nil 381 } 382 383 func (c *balanceProcessor) hasReceiver(address flow.Address) (bool, error) { 384 receiver, err := c.ReadStored(address, common.PathDomainPublic, "flowTokenReceiver") 385 386 if err != nil { 387 return false, fmt.Errorf("could not load receiver at %s: %w", address, err) 388 } 389 return receiver != nil, nil 390 } 391 392 func (c *balanceProcessor) ReadStored(address flow.Address, domain common.PathDomain, id string) (cadence.Value, error) { 393 addr, err := common.BytesToAddress(address.Bytes()) 394 if err != nil { 395 return nil, err 396 } 397 398 rt := c.env.BorrowCadenceRuntime() 399 defer c.env.ReturnCadenceRuntime(rt) 400 401 receiver, err := rt.ReadStored( 402 addr, 403 cadence.Path{ 404 Domain: domain, 405 Identifier: id, 406 }, 407 ) 408 return receiver, err 409 }