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