github.com/koko1123/flow-go-1@v0.29.6/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/koko1123/flow-go-1/cmd/util/ledger/migrations" 17 "github.com/koko1123/flow-go-1/fvm" 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 const FungibleTokenTrackerReportPrefix = "fungible_token_report" 26 27 var domains = []string{ 28 common.PathDomainPublic.Identifier(), 29 common.PathDomainPrivate.Identifier(), 30 common.PathDomainStorage.Identifier(), 31 } 32 33 // FungibleTokenTracker iterates through stored cadence values over all accounts and check for any 34 // value with the given resource typeID 35 type FungibleTokenTracker struct { 36 log zerolog.Logger 37 chain flow.Chain 38 rwf ReportWriterFactory 39 rw ReportWriter 40 progress *progressbar.ProgressBar 41 vaultTypeIDs map[string]bool 42 } 43 44 func FlowTokenTypeID(chain flow.Chain) string { 45 return fmt.Sprintf("A.%s.FlowToken.Vault", fvm.FlowTokenAddress(chain).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 view := utils.NewSimpleViewFromPayloads(j.payloads) 146 txnState := state.NewTransactionState(view, state.DefaultParameters()) 147 accounts := environment.NewAccounts(txnState) 148 storage := cadenceRuntime.NewStorage( 149 &migrations.AccountsAtreeLedger{Accounts: accounts}, 150 nil, 151 ) 152 153 owner, err := common.BytesToAddress(j.owner[:]) 154 if err != nil { 155 panic(err) 156 } 157 158 inter, err := interpreter.NewInterpreter(nil, nil, &interpreter.Config{}) 159 if err != nil { 160 panic(err) 161 } 162 163 for _, domain := range domains { 164 storageMap := storage.GetStorageMap(owner, domain, true) 165 itr := storageMap.Iterator(inter) 166 key, value := itr.Next() 167 for value != nil { 168 r.iterateChildren(append([]string{domain}, key), j.owner, value) 169 key, value = itr.Next() 170 } 171 } 172 173 err = r.progress.Add(1) 174 if err != nil { 175 panic(err) 176 } 177 } 178 179 wg.Done() 180 } 181 182 func (r *FungibleTokenTracker) iterateChildren(tr trace, addr flow.Address, value interpreter.Value) { 183 184 compValue, ok := value.(*interpreter.CompositeValue) 185 if !ok { 186 return 187 } 188 189 // because compValue.Kind == common.CompositeKindResource 190 // we could pass nil to the IsResourceKinded method 191 inter, err := interpreter.NewInterpreter(nil, nil, &interpreter.Config{}) 192 if err != nil { 193 panic(err) 194 } 195 if compValue.IsResourceKinded(nil) { 196 typeIDStr := string(compValue.TypeID()) 197 if _, ok := r.vaultTypeIDs[typeIDStr]; ok { 198 b := uint64(compValue.GetField( 199 inter, 200 interpreter.EmptyLocationRange, 201 "balance", 202 ).(interpreter.UFix64Value)) 203 if b > 0 { 204 r.rw.Write(TokenDataPoint{ 205 Path: tr.String(), 206 Address: addr.Hex(), 207 Balance: b, 208 TypeID: string(compValue.TypeID()), 209 }) 210 } 211 } 212 213 // iterate over fields of the composite value (skip the ones that are not resource typed) 214 compValue.ForEachField(inter, 215 func(key string, value interpreter.Value) { 216 r.iterateChildren(append(tr, key), addr, value) 217 }) 218 } 219 }