Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement queries for historical balances (state) #4371

Merged
merged 31 commits into from
Aug 22, 2022

Conversation

andreibancioiu
Copy link
Collaborator

@andreibancioiu andreibancioiu commented Aug 12, 2022

Description of the reasoning behind the pull request (what feature was missing / how the problem was manifesting itself / what was the motive behind the refactoring)

  • Node API does not support historical balances lookup yet. That is, it does not support queries such as address/erd1alice?blockNonce=12345.
  • Rosetta API would greatly benefit from historical balances (e.g. improved robustness, improved debugging)

Proposed Changes

  • At the API controllers level (i.e., in addressGroup), allow one to provide extra URL parameters (query parameters) for account-related endpoints. These parameters are: blockNonce, blockHash, blockRootHash. They are mutually exclusive.
  • At a lower level, in the node object, handle the new query options and appropriately delegate the queries to the accountsRepository. The accountsRepository relies on a new component (see below) to resolve historical queries.
  • In addition to accountsDBApi, add a new implementation of AccountsAdapterAPI: accountsDBApiWithHistory. This implementation is able to provide historical state (balance etc.) for the queried account, by recreating the trie at a specified rootHash.
  • The re-creation of the trie at an older rootHash is possible thanks to: Enable snapshot even if pruning is disabled #4365.
  • Historical balances lookup require the following node configuration: AccountsStatePruningEnabled = false. The implementation is also subject to (affected by): NumActivePersisters = K.

Testing procedure

  • Run Rosetta's check:data validation flow, with historical balances enabled, on local testnet, then on devnet.
  • On Node API, perform a large number of account queries such as: /address/erd1alice?blockNonce=..., /address/erd1alice?blockHash=..., /address/erd1alice?blockRootHash=.... On the response, also check the blockInfo field. It should match the provided coordinates.
  • For the queries above, also provide bad parameters (e.g. missing blocks).
  • For sender = Alice, broadcast a transaction that transfers some value. Say this transaction is added in block N. Then, query address/erd1alice?blockNonce=(N-1), address/erd1alice?blockNonce=(N) and address/erd1alice?blockNonce=(N+1). Check output.
  • On internal testnets, make sure to set AccountsStatePruningEnabled = false and NumActivePersisters = K, where K should be at least 3 (3 is fine).

@andreibancioiu andreibancioiu changed the base branch from master to rc/2022-july August 12, 2022 12:38
@andreibancioiu andreibancioiu self-assigned this Aug 12, 2022
@codecov-commenter
Copy link

codecov-commenter commented Aug 12, 2022

Codecov Report

Merging #4371 (5b41b3f) into rc/2022-july (311f2d1) will increase coverage by 0.04%.
The diff coverage is 87.16%.

@@               Coverage Diff                @@
##           rc/2022-july    #4371      +/-   ##
================================================
+ Coverage         73.75%   73.80%   +0.04%     
================================================
  Files               671      675       +4     
  Lines             86488    86769     +281     
================================================
+ Hits              63793    64039     +246     
- Misses            17919    17938      +19     
- Partials           4776     4792      +16     
Impacted Files Coverage Δ
api/groups/addressGroup.go 71.53% <54.54%> (-0.91%) ⬇️
factory/stateComponents.go 54.79% <66.66%> (-0.14%) ⬇️
node/node.go 73.36% <66.66%> (-0.24%) ⬇️
common/holders/rootHashHolder.go 80.00% <80.00%> (ø)
state/accountsRepository.go 88.23% <80.95%> (-11.77%) ⬇️
state/factory/accountsAdapterAPICreator.go 80.00% <85.71%> (-20.00%) ⬇️
api/groups/addressGroupOptions.go 86.95% <86.95%> (ø)
node/nodeLoadAccounts.go 88.04% <87.50%> (-1.25%) ⬇️
api/groups/urlParams.go 93.10% <90.90%> (-6.90%) ⬇️
state/accountsDBApiWithHistory.go 94.79% <94.79%> (ø)
... and 7 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@andreibancioiu andreibancioiu marked this pull request as ready for review August 17, 2022 16:50
@@ -9,7 +9,7 @@ require (
github.com/ElrondNetwork/concurrent-map v0.1.3
github.com/ElrondNetwork/covalent-indexer-go v1.0.6
github.com/ElrondNetwork/elastic-indexer-go v1.2.38
github.com/ElrondNetwork/elrond-go-core v1.1.18
github.com/ElrondNetwork/elrond-go-core v1.1.19-0.20220813193723-a7d8c4aeaa87
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


func (n *Node) getBlockRootHash(headerHash []byte, header data.HeaderHandler) []byte {
blockRootHash, err := n.processComponents.ScheduledTxsExecutionHandler().GetScheduledRootHashForHeader(headerHash)
if err != nil {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, we have to swallow this error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps compare it with some well-known errors, then log.Warn if necessary?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is ok like this. We either return the roothash (that we can find on disk) as the function states.
I think we have a similar approach in the block processors process/block/shardblock.go L1208 + L1250-L1253

numSpecifiedBlockCoordinates++
}

if numSpecifiedBlockCoordinates > 1 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

elegant implementation 👍

@@ -15,11 +17,44 @@ func parseBoolUrlParam(c *gin.Context, name string) (bool, error) {
return strconv.ParseBool(param)
}

func parseUintUrlParam(c *gin.Context, name string) (uint64, error) {
func parseUint32UrlParam(c *gin.Context, name string) (core.OptionalUint32, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return core.OptionalUint32{Value: uint32(value), HasValue: true}, nil
}

func parseUint64UrlParam(c *gin.Context, name string) (core.OptionalUint64, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

common/holders/getAccountStateOptions.go Outdated Show resolved Hide resolved
common/holders/getAccountStateOptions.go Outdated Show resolved Hide resolved
state/accountsDBApi.go Outdated Show resolved Hide resolved
state/accountsDBApi.go Outdated Show resolved Hide resolved
state/accountsDBApiWithHistory.go Outdated Show resolved Hide resolved
state/accountsDBApiWithHistory.go Outdated Show resolved Hide resolved
state/accountsDBApiWithHistory.go Show resolved Hide resolved
@@ -30,6 +31,13 @@ func (n *Node) loadUserAccountHandlerByPubKey(pubKey []byte, options api.Account

account, blockInfo, err := repository.GetAccountWithBlockInfo(pubKey, options)
if err != nil {
blockInfo, ok := extractBlockInfoIfErrAccountNotFoundAtBlock(err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting approach for error handling/passing/extracting data out of it

accountsDB.mutRecreateAndGet.RLock()
if !accountsDB.shouldRecreateTrieUnprotected(rootHash) {
// Re-creation is not necessary, but make sure no other routine re-creates it.
defer accountsDB.mutRecreateAndGet.RUnlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 yup, this should stay on defer

for j := 0; j < numCallsPerRootHash; j++ {
wg.Add(1)

go func(rootHashAsInt int) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering how these numbers numRootHashes = 128 and numCallsPerRootHash = 128 do not crash the race detector since it can handle a maximum 8192 go routines

Copy link
Collaborator

@gabi-vuls gabi-vuls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System test passed.

@andreibancioiu andreibancioiu merged commit 4d64e3d into rc/2022-july Aug 22, 2022
@andreibancioiu andreibancioiu deleted the historical-balances-08-12 branch August 22, 2022 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants