github.com/koko1123/flow-go-1@v0.29.6/cmd/util/ledger/reporters/account_reporter.go (about)

     1  package reporters
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	goRuntime "runtime"
     7  	"sync"
     8  
     9  	"github.com/rs/zerolog"
    10  	"github.com/schollz/progressbar/v3"
    11  
    12  	"github.com/onflow/cadence"
    13  	jsoncdc "github.com/onflow/cadence/encoding/json"
    14  	"github.com/onflow/cadence/runtime/common"
    15  
    16  	"github.com/koko1123/flow-go-1/fvm"
    17  	"github.com/koko1123/flow-go-1/fvm/derived"
    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  // AccountReporter iterates through registers keeping a map of register sizes
    26  // reports on storage metrics
    27  type AccountReporter struct {
    28  	Log   zerolog.Logger
    29  	RWF   ReportWriterFactory
    30  	Chain flow.Chain
    31  }
    32  
    33  var _ ledger.Reporter = &AccountReporter{}
    34  
    35  func (r *AccountReporter) Name() string {
    36  	return "Account Reporter"
    37  }
    38  
    39  type accountRecord struct {
    40  	Address        string `json:"address"`
    41  	StorageUsed    uint64 `json:"storageUsed"`
    42  	AccountBalance uint64 `json:"accountBalance"`
    43  	FUSDBalance    uint64 `json:"fusdBalance"`
    44  	HasVault       bool   `json:"hasVault"`
    45  	HasReceiver    bool   `json:"hasReceiver"`
    46  	IsDapper       bool   `json:"isDapper"`
    47  }
    48  
    49  type contractRecord struct {
    50  	Address  string `json:"address"`
    51  	Contract string `json:"contract"`
    52  }
    53  
    54  type momentsRecord struct {
    55  	Address string `json:"address"`
    56  	Moments int    `json:"moments"`
    57  }
    58  
    59  func (r *AccountReporter) Report(payload []ledger.Payload, commit ledger.State) error {
    60  	rwa := r.RWF.ReportWriter("account_report")
    61  	rwc := r.RWF.ReportWriter("contract_report")
    62  	rwm := r.RWF.ReportWriter("moments_report")
    63  	defer rwa.Close()
    64  	defer rwc.Close()
    65  	defer rwm.Close()
    66  
    67  	l := utils.NewSimpleViewFromPayloads(payload)
    68  	txnState := state.NewTransactionState(l, state.DefaultParameters())
    69  	gen := environment.NewAddressGenerator(txnState, r.Chain)
    70  
    71  	progress := progressbar.Default(int64(gen.AddressCount()), "Processing:")
    72  
    73  	workerCount := goRuntime.NumCPU() / 2
    74  	if workerCount == 0 {
    75  		workerCount = 1
    76  	}
    77  
    78  	addressIndexes := make(chan uint64, workerCount)
    79  	defer close(addressIndexes)
    80  
    81  	// create multiple workers to generate account data report concurrently
    82  	wg := &sync.WaitGroup{}
    83  	for i := 0; i < workerCount; i++ {
    84  		go func() {
    85  			adp := newAccountDataProcessor(r.Log, rwa, rwc, rwm, r.Chain, l)
    86  			for indx := range addressIndexes {
    87  				adp.reportAccountData(indx)
    88  				wg.Done()
    89  			}
    90  		}()
    91  	}
    92  
    93  	addressCount := gen.AddressCount()
    94  	// produce jobs for workers to process
    95  	for i := uint64(1); i <= addressCount; i++ {
    96  		addressIndexes <- i
    97  
    98  		wg.Add(1)
    99  
   100  		err := progress.Add(1)
   101  		if err != nil {
   102  			panic(fmt.Errorf("progress.Add(1): %w", err))
   103  		}
   104  	}
   105  
   106  	// wait until all jobs are done
   107  	wg.Wait()
   108  
   109  	err := progress.Finish()
   110  	if err != nil {
   111  		panic(fmt.Errorf("progress.Finish(): %w", err))
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  type balanceProcessor struct {
   118  	vm            fvm.VM
   119  	ctx           fvm.Context
   120  	view          state.View
   121  	env           environment.Environment
   122  	balanceScript []byte
   123  	momentsScript []byte
   124  
   125  	accounts environment.Accounts
   126  
   127  	rwa        ReportWriter
   128  	rwc        ReportWriter
   129  	logger     zerolog.Logger
   130  	rwm        ReportWriter
   131  	fusdScript []byte
   132  }
   133  
   134  func NewBalanceReporter(chain flow.Chain, view state.View) *balanceProcessor {
   135  	vm := fvm.NewVirtualMachine()
   136  	derivedBlockData := derived.NewEmptyDerivedBlockData()
   137  	ctx := fvm.NewContext(
   138  		fvm.WithChain(chain),
   139  		fvm.WithMemoryAndInteractionLimitsDisabled(),
   140  		fvm.WithDerivedBlockData(derivedBlockData))
   141  
   142  	v := view.NewChild()
   143  	txnState := state.NewTransactionState(v, state.DefaultParameters())
   144  	accounts := environment.NewAccounts(txnState)
   145  
   146  	derivedTxnData, err := derivedBlockData.NewSnapshotReadDerivedTransactionData(0, 0)
   147  	if err != nil {
   148  		panic(err)
   149  	}
   150  
   151  	env := environment.NewScriptEnvironment(
   152  		context.Background(),
   153  		ctx.TracerSpan,
   154  		ctx.EnvironmentParams,
   155  		txnState,
   156  		derivedTxnData)
   157  
   158  	return &balanceProcessor{
   159  		vm:       vm,
   160  		ctx:      ctx,
   161  		view:     v,
   162  		accounts: accounts,
   163  		env:      env,
   164  	}
   165  }
   166  
   167  func newAccountDataProcessor(logger zerolog.Logger, rwa ReportWriter, rwc ReportWriter, rwm ReportWriter, chain flow.Chain, view state.View) *balanceProcessor {
   168  	bp := NewBalanceReporter(chain, view)
   169  
   170  	bp.logger = logger
   171  	bp.rwa = rwa
   172  	bp.rwc = rwc
   173  	bp.rwm = rwm
   174  	bp.balanceScript = []byte(fmt.Sprintf(`
   175  				import FungibleToken from 0x%s
   176  				import FlowToken from 0x%s
   177  				pub fun main(account: Address): UFix64 {
   178  					let acct = getAccount(account)
   179  					let vaultRef = acct.getCapability(/public/flowTokenBalance)
   180  						.borrow<&FlowToken.Vault{FungibleToken.Balance}>()
   181  						?? panic("Could not borrow Balance reference to the Vault")
   182  					return vaultRef.balance
   183  				}
   184  			`, fvm.FungibleTokenAddress(bp.ctx.Chain), fvm.FlowTokenAddress(bp.ctx.Chain)))
   185  
   186  	bp.fusdScript = []byte(fmt.Sprintf(`
   187  			import FungibleToken from 0x%s
   188  			import FUSD from 0x%s
   189  			pub fun main(address: Address): UFix64 {
   190  				let account = getAccount(address)
   191  				let vaultRef = account.getCapability(/public/fusdBalance)!
   192  					.borrow<&FUSD.Vault{FungibleToken.Balance}>()
   193  					?? panic("Could not borrow Balance reference to the Vault")
   194  				return vaultRef.balance
   195  			}
   196  			`, fvm.FungibleTokenAddress(bp.ctx.Chain), "3c5959b568896393"))
   197  
   198  	bp.momentsScript = []byte(`
   199  			import TopShot from 0x0b2a3299cc857e29
   200  			pub fun main(account: Address): Int {
   201  				let acct = getAccount(account)
   202  				let collectionRef = acct.getCapability(/public/MomentCollection)
   203  										.borrow<&{TopShot.MomentCollectionPublic}>()!
   204  				return collectionRef.getIDs().length
   205  			}
   206  			`)
   207  
   208  	return bp
   209  }
   210  
   211  func (c *balanceProcessor) reportAccountData(indx uint64) {
   212  	address, err := c.ctx.Chain.AddressAtIndex(indx)
   213  	if err != nil {
   214  		c.logger.
   215  			Err(err).
   216  			Uint64("index", indx).
   217  			Msgf("Error getting address")
   218  		return
   219  	}
   220  
   221  	u, err := c.storageUsed(address)
   222  	if err != nil {
   223  		c.logger.
   224  			Err(err).
   225  			Uint64("index", indx).
   226  			Str("address", address.String()).
   227  			Msgf("Error getting storage used for account")
   228  		return
   229  	}
   230  
   231  	balance, hasVault, err := c.balance(address)
   232  	if err != nil {
   233  		c.logger.
   234  			Err(err).
   235  			Uint64("index", indx).
   236  			Str("address", address.String()).
   237  			Msgf("Error getting balance for account")
   238  		return
   239  	}
   240  	fusdBalance, err := c.fusdBalance(address)
   241  	if err != nil {
   242  		c.logger.
   243  			Err(err).
   244  			Uint64("index", indx).
   245  			Str("address", address.String()).
   246  			Msgf("Error getting FUSD balance for account")
   247  		return
   248  	}
   249  
   250  	dapper, err := c.isDapper(address)
   251  	if err != nil {
   252  		c.logger.
   253  			Err(err).
   254  			Uint64("index", indx).
   255  			Str("address", address.String()).
   256  			Msgf("Error determining if account is dapper account")
   257  		return
   258  	}
   259  	if dapper {
   260  		m, err := c.moments(address)
   261  		if err != nil {
   262  			c.logger.
   263  				Err(err).
   264  				Uint64("index", indx).
   265  				Str("address", address.String()).
   266  				Msgf("Error getting moments for account")
   267  			return
   268  		}
   269  		c.rwm.Write(momentsRecord{
   270  			Address: address.Hex(),
   271  			Moments: m,
   272  		})
   273  	}
   274  
   275  	hasReceiver, err := c.hasReceiver(address)
   276  	if err != nil {
   277  		c.logger.
   278  			Err(err).
   279  			Uint64("index", indx).
   280  			Str("address", address.String()).
   281  			Msgf("Error checking if account has a receiver")
   282  		return
   283  	}
   284  
   285  	c.rwa.Write(accountRecord{
   286  		Address:        address.Hex(),
   287  		StorageUsed:    u,
   288  		AccountBalance: balance,
   289  		FUSDBalance:    fusdBalance,
   290  		HasVault:       hasVault,
   291  		HasReceiver:    hasReceiver,
   292  		IsDapper:       dapper,
   293  	})
   294  
   295  	contracts, err := c.accounts.GetContractNames(address)
   296  	if err != nil {
   297  		c.logger.
   298  			Err(err).
   299  			Uint64("index", indx).
   300  			Str("address", address.String()).
   301  			Msgf("Error getting account contract names")
   302  		return
   303  	}
   304  	if len(contracts) == 0 {
   305  		return
   306  	}
   307  	for _, contract := range contracts {
   308  		c.rwc.Write(contractRecord{
   309  			Address:  address.Hex(),
   310  			Contract: contract,
   311  		})
   312  	}
   313  
   314  }
   315  
   316  func (c *balanceProcessor) balance(address flow.Address) (uint64, bool, error) {
   317  	script := fvm.Script(c.balanceScript).WithArguments(
   318  		jsoncdc.MustEncode(cadence.NewAddress(address)),
   319  	)
   320  
   321  	err := c.vm.Run(c.ctx, script, c.view)
   322  	if err != nil {
   323  		return 0, false, err
   324  	}
   325  
   326  	var balance uint64
   327  	var hasVault bool
   328  	if script.Err == nil && script.Value != nil {
   329  		balance = script.Value.ToGoValue().(uint64)
   330  		hasVault = true
   331  	} else {
   332  		hasVault = false
   333  	}
   334  	return balance, hasVault, nil
   335  }
   336  
   337  func (c *balanceProcessor) fusdBalance(address flow.Address) (uint64, error) {
   338  	script := fvm.Script(c.fusdScript).WithArguments(
   339  		jsoncdc.MustEncode(cadence.NewAddress(address)),
   340  	)
   341  
   342  	err := c.vm.Run(c.ctx, script, c.view)
   343  	if err != nil {
   344  		return 0, err
   345  	}
   346  
   347  	var balance uint64
   348  	if script.Err == nil && script.Value != nil {
   349  		balance = script.Value.ToGoValue().(uint64)
   350  	}
   351  	return balance, nil
   352  }
   353  
   354  func (c *balanceProcessor) moments(address flow.Address) (int, error) {
   355  	script := fvm.Script(c.momentsScript).WithArguments(
   356  		jsoncdc.MustEncode(cadence.NewAddress(address)),
   357  	)
   358  
   359  	err := c.vm.Run(c.ctx, script, c.view)
   360  	if err != nil {
   361  		return 0, err
   362  	}
   363  
   364  	var m int
   365  	if script.Err == nil && script.Value != nil {
   366  		m = script.Value.(cadence.Int).Int()
   367  	}
   368  	return m, nil
   369  }
   370  
   371  func (c *balanceProcessor) storageUsed(address flow.Address) (uint64, error) {
   372  	return c.accounts.GetStorageUsed(address)
   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.Identifier(),
   405  			Identifier: id,
   406  		},
   407  	)
   408  	return receiver, err
   409  }