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