github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/rpc/get_state.go (about)

     1  package rpc
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
     9  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc/query"
    10  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    11  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/walk"
    12  )
    13  
    14  type StateFilters struct {
    15  	BalanceCheck func(address base.Address, balance *base.Wei) bool
    16  }
    17  
    18  // GetState returns account state (search: FromRpc)
    19  func (conn *Connection) GetState(fieldBits types.StatePart, address base.Address, blockNumber base.Blknum, filters StateFilters) (*types.State, error) {
    20  	blockTs := base.Timestamp(0)
    21  	if conn.StoreReadable() {
    22  		// walk.Cache_State
    23  		state := &types.State{
    24  			BlockNumber: blockNumber,
    25  			Address:     address,
    26  		}
    27  		if err := conn.Store.Read(state, nil); err == nil {
    28  			if state.Parts&fieldBits == fieldBits {
    29  				// we have what we need
    30  				return state, nil
    31  			}
    32  		}
    33  		fieldBits |= state.Parts // preserve what's there
    34  		blockTs = conn.GetBlockTimestamp(blockNumber)
    35  	}
    36  
    37  	// We always ask for balance even if we dont' need it. Not sure why.
    38  	rpcPayload := []query.BatchPayload{
    39  		{
    40  			Key: "balance",
    41  			Payload: &query.Payload{
    42  				Method: "eth_getBalance",
    43  				Params: query.Params{
    44  					address,
    45  					fmt.Sprintf("0x%x", blockNumber),
    46  				},
    47  			},
    48  		},
    49  	}
    50  
    51  	if (fieldBits & types.Nonce) != 0 {
    52  		rpcPayload = append(rpcPayload, query.BatchPayload{
    53  			Key: "nonce",
    54  			Payload: &query.Payload{
    55  				Method: "eth_getTransactionCount",
    56  				Params: query.Params{
    57  					address,
    58  					fmt.Sprintf("0x%x", blockNumber),
    59  				},
    60  			},
    61  		})
    62  	}
    63  
    64  	if (fieldBits & types.Code) != 0 {
    65  		rpcPayload = append(rpcPayload, query.BatchPayload{
    66  			Key: "code",
    67  			Payload: &query.Payload{
    68  				Method: "eth_getCode",
    69  				Params: query.Params{
    70  					address,
    71  					fmt.Sprintf("0x%x", blockNumber),
    72  				},
    73  			},
    74  		})
    75  	}
    76  
    77  	queryResults, err := query.QueryBatch[string](conn.Chain, rpcPayload)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	value := queryResults["balance"]
    83  	balance := base.NewWei(0)
    84  	balance.SetString(*value, 0)
    85  
    86  	state := &types.State{
    87  		Address:     address,
    88  		BlockNumber: blockNumber,
    89  		Deployed:    base.NOPOSN,
    90  		Timestamp:   blockTs,
    91  		Parts:       fieldBits,
    92  	}
    93  
    94  	if (fieldBits & types.Balance) != 0 {
    95  		state.Balance = *balance
    96  	}
    97  
    98  	if (fieldBits & types.Nonce) != 0 {
    99  		if value, ok := queryResults["nonce"]; ok {
   100  			state.Nonce = base.MustParseValue(*value)
   101  		}
   102  	}
   103  
   104  	if (fieldBits & types.Code) != 0 {
   105  		if value, ok := queryResults["code"]; ok {
   106  			code := *value
   107  			if code != "0x" {
   108  				state.Code = code
   109  			}
   110  		}
   111  	}
   112  
   113  	if (fieldBits & types.Deployed) != 0 {
   114  		block, err := conn.GetContractDeployBlock(address)
   115  		if err != nil && !errors.Is(err, ErrNotAContract) {
   116  			return nil, err
   117  		}
   118  		// If err is ErrNotAContract, then we'll use the default value
   119  		if err == nil {
   120  			state.Deployed = block
   121  		}
   122  	}
   123  
   124  	var proxy base.Address
   125  
   126  	if (fieldBits&types.Proxy) != 0 || (fieldBits&types.Type) != 0 {
   127  		proxy, err = conn.GetContractProxyAt(address, blockNumber)
   128  		if err != nil {
   129  			return nil, err
   130  		}
   131  		if (fieldBits & types.Proxy) != 0 {
   132  			state.Proxy = proxy
   133  		}
   134  	}
   135  
   136  	if (fieldBits & types.Type) != 0 {
   137  		if !proxy.IsZero() {
   138  			state.AccountType = "Proxy"
   139  		} else {
   140  			state.AccountType = conn.getTypeNonProxy(address, blockNumber)
   141  		}
   142  	}
   143  
   144  	isFinal := base.IsFinal(conn.LatestBlockTimestamp, blockTs)
   145  	if isFinal && conn.StoreWritable() && conn.EnabledMap[walk.Cache_State] {
   146  		_ = conn.Store.Write(state, nil)
   147  	}
   148  
   149  	if filters.BalanceCheck != nil {
   150  		if !filters.BalanceCheck(address, &state.Balance) {
   151  			return nil, nil
   152  		}
   153  	}
   154  
   155  	return state, nil
   156  }
   157  
   158  // GetBalanceAt returns a balance for an address at a block
   159  func (conn *Connection) GetBalanceAt(addr base.Address, bn base.Blknum) (*base.Wei, error) {
   160  	if ec, err := conn.getClient(); err != nil {
   161  		var zero base.Wei
   162  		return &zero, err
   163  	} else {
   164  		defer ec.Close()
   165  		ret, err := ec.BalanceAt(context.Background(), addr.Common(), base.BiFromBn(bn))
   166  		return (*base.Wei)(ret), err
   167  	}
   168  }
   169  
   170  func (conn *Connection) getTypeNonProxy(address base.Address, bn base.Blknum) string {
   171  	isContractErr := conn.IsContractAt(address, bn)
   172  	if errors.Is(isContractErr, ErrNotAContract) {
   173  		return "EOA"
   174  	}
   175  	return "Contract"
   176  }