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