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  }