github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/cmd/util/ledger/reporters/fungible_token_tracker.go (about) 1 package reporters 2 3 import ( 4 "fmt" 5 "runtime" 6 "strings" 7 "sync" 8 9 "github.com/rs/zerolog" 10 "github.com/schollz/progressbar/v3" 11 12 cadenceRuntime "github.com/onflow/cadence/runtime" 13 "github.com/onflow/cadence/runtime/common" 14 "github.com/onflow/cadence/runtime/interpreter" 15 16 "github.com/onflow/flow-go/cmd/util/ledger/util" 17 "github.com/onflow/flow-go/fvm/environment" 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 const FungibleTokenTrackerReportPrefix = "fungible_token_report" 25 26 var domains = []string{ 27 common.PathDomainPublic.Identifier(), 28 common.PathDomainPrivate.Identifier(), 29 common.PathDomainStorage.Identifier(), 30 } 31 32 // FungibleTokenTracker iterates through stored cadence values over all accounts and check for any 33 // value with the given resource typeID 34 type FungibleTokenTracker struct { 35 log zerolog.Logger 36 chain flow.Chain 37 rwf ReportWriterFactory 38 rw ReportWriter 39 progress *progressbar.ProgressBar 40 vaultTypeIDs map[string]bool 41 } 42 43 func FlowTokenTypeID(chain flow.Chain) string { 44 sc := systemcontracts.SystemContractsForChain(chain.ChainID()) 45 return fmt.Sprintf("A.%s.FlowToken.Vault", sc.FlowToken.Address.Hex()) 46 } 47 48 func NewFungibleTokenTracker(logger zerolog.Logger, rwf ReportWriterFactory, chain flow.Chain, vaultTypeIDs []string) *FungibleTokenTracker { 49 ftt := &FungibleTokenTracker{ 50 log: logger, 51 rwf: rwf, 52 chain: chain, 53 vaultTypeIDs: make(map[string]bool), 54 } 55 for _, vt := range vaultTypeIDs { 56 ftt.vaultTypeIDs[vt] = true 57 } 58 return ftt 59 } 60 61 func (r *FungibleTokenTracker) Name() string { 62 return "Resource Tracker" 63 } 64 65 type trace []string 66 67 func (t trace) String() string { 68 return strings.Join(t, "/") 69 } 70 71 type TokenDataPoint struct { 72 // Path is the storage path of the composite the vault was found in 73 Path string `json:"path"` 74 // Address is the owner of the composite the vault was found in 75 Address string `json:"address"` 76 // Balance is the balance of the flow vault 77 Balance uint64 `json:"balance"` 78 // token type 79 TypeID string `json:"type_id"` 80 } 81 82 type job struct { 83 owner flow.Address 84 payloads []ledger.Payload 85 } 86 87 // Report creates a fungible_token_report_*.json file that contains data on all fungible token Vaults in the state commitment. 88 // I recommend using gojq to browse through the data, because of the large uint64 numbers which jq won't be able to handle. 89 func (r *FungibleTokenTracker) Report(payloads []ledger.Payload, commit ledger.State) error { 90 r.rw = r.rwf.ReportWriter(FungibleTokenTrackerReportPrefix) 91 defer r.rw.Close() 92 93 wg := &sync.WaitGroup{} 94 95 // we need to shard by owner, otherwise ledger won't be thread-safe 96 addressCount := 0 97 payloadsByOwner := make(map[flow.Address][]ledger.Payload) 98 99 for _, pay := range payloads { 100 k, err := pay.Key() 101 if err != nil { 102 return nil 103 } 104 owner := flow.BytesToAddress(k.KeyParts[0].Value) 105 if len(owner) > 0 { // ignoring payloads without ownership (fvm ones) 106 m, ok := payloadsByOwner[owner] 107 if !ok { 108 payloadsByOwner[owner] = make([]ledger.Payload, 0) 109 addressCount++ 110 } 111 payloadsByOwner[owner] = append(m, pay) 112 } 113 } 114 115 jobs := make(chan job, addressCount) 116 r.progress = progressbar.Default(int64(addressCount), "Processing:") 117 118 for k, v := range payloadsByOwner { 119 jobs <- job{k, v} 120 } 121 122 close(jobs) 123 124 workerCount := runtime.NumCPU() 125 for i := 0; i < workerCount; i++ { 126 wg.Add(1) 127 go r.worker(jobs, wg) 128 } 129 130 wg.Wait() 131 132 err := r.progress.Finish() 133 if err != nil { 134 panic(err) 135 } 136 137 return nil 138 } 139 140 func (r *FungibleTokenTracker) worker( 141 jobs <-chan job, 142 wg *sync.WaitGroup) { 143 for j := range jobs { 144 145 txnState := state.NewTransactionState( 146 NewStorageSnapshotFromPayload(j.payloads), 147 state.DefaultParameters()) 148 accounts := environment.NewAccounts(txnState) 149 storage := cadenceRuntime.NewStorage( 150 &util.AccountsAtreeLedger{Accounts: accounts}, 151 nil, 152 ) 153 154 owner, err := common.BytesToAddress(j.owner[:]) 155 if err != nil { 156 panic(err) 157 } 158 159 inter, err := interpreter.NewInterpreter(nil, nil, &interpreter.Config{}) 160 if err != nil { 161 panic(err) 162 } 163 164 for _, domain := range domains { 165 storageMap := storage.GetStorageMap(owner, domain, true) 166 itr := storageMap.Iterator(inter) 167 key, value := itr.Next() 168 for value != nil { 169 identifier := string(key.(interpreter.StringAtreeValue)) 170 r.iterateChildren(append([]string{domain}, identifier), j.owner, value) 171 key, value = itr.Next() 172 } 173 } 174 175 err = r.progress.Add(1) 176 if err != nil { 177 panic(err) 178 } 179 } 180 181 wg.Done() 182 } 183 184 func (r *FungibleTokenTracker) iterateChildren(tr trace, addr flow.Address, value interpreter.Value) { 185 186 compValue, ok := value.(*interpreter.CompositeValue) 187 if !ok { 188 return 189 } 190 191 // because compValue.Kind == common.CompositeKindResource 192 // we could pass nil to the IsResourceKinded method 193 inter, err := interpreter.NewInterpreter(nil, nil, &interpreter.Config{}) 194 if err != nil { 195 panic(err) 196 } 197 if compValue.IsResourceKinded(nil) { 198 typeIDStr := string(compValue.TypeID()) 199 if _, ok := r.vaultTypeIDs[typeIDStr]; ok { 200 b := uint64(compValue.GetField( 201 inter, 202 interpreter.EmptyLocationRange, 203 "balance", 204 ).(interpreter.UFix64Value)) 205 if b > 0 { 206 r.rw.Write(TokenDataPoint{ 207 Path: tr.String(), 208 Address: addr.Hex(), 209 Balance: b, 210 TypeID: string(compValue.TypeID()), 211 }) 212 } 213 } 214 215 // iterate over fields of the composite value (skip the ones that are not resource typed) 216 compValue.ForEachField(inter, 217 func(key string, value interpreter.Value) (resume bool) { 218 r.iterateChildren(append(tr, key), addr, value) 219 220 // continue iteration 221 return true 222 }, 223 interpreter.EmptyLocationRange, 224 ) 225 } 226 }