-
Notifications
You must be signed in to change notification settings - Fork 93
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
Ledger refactoring changes #870
Ledger refactoring changes #870
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #870 +/- ##
===========================================
+ Coverage 58.54% 59.63% +1.09%
===========================================
Files 37 38 +1
Lines 4407 4685 +278
===========================================
+ Hits 2580 2794 +214
- Misses 1514 1562 +48
- Partials 313 329 +16
Continue to review full report at Codecov.
|
b75d368
to
70b5450
Compare
// Make a copy of `AppLocalState` to avoid modifying ledger's storage. | ||
appLocalState := new(basics.AppLocalState) | ||
*appLocalState = *r.AppLocalState | ||
appLocalState.KeyValue = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why does global state and local state need to be set to nil?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
go-algorand code can return both empty and nil maps, but indexer code only returns nil maps. So here if the map is empty, we set it to nil, so that comparison doesn't fail.
} | ||
} | ||
|
||
func unconvertTrimmedLcAccountData(ba baseAccountData) ledgercore.AccountData { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe rename these to baseAccountToTrimmedAccount and trimmedLcAccountToBaseAccount?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current name is consistent with other similar functions here. But I could rename baseAccountData
.
TotalAssetParams uint64 `codec:"f"` | ||
TotalAssets uint64 `codec:"g"` | ||
TotalAppParams uint64 `codec:"h"` | ||
TotalAppLocalStates uint64 `codec:"i"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need to give better key names?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily. It's good for space efficiency, and this is how it's going to be done in go-algorand. https://github.com/algorand/go-algorand/blob/fcd06e94f60d2141df0cd11ff6e3fb0518483cdc/ledger/accountdb.go#L1079
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of these fields are being renamed (or maybe just auth-addr). Not sure if anyone is querying these directly, but it could potentially matter to some users. The fact that the names are equivalent to algod makes this seem a little better, but it does seem unusual that the data is basically unusable without serializing it to a go object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only auth-addr. Are you proposing anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while the space efficiency in go-algorand is impressive, I think it would be better for the indexer to have user friendly names for things.. these will show up as SQL query JSON names right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basically, shouldn't we have backwards compatibility with the codec names in basics.AccountData? onl, vote, voteFst, voteLst, etc..?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, for instance, it would show up in the postgres query log. In this PR that only applies to auth-addr
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And yes, if someone had their own query to get any of those other renamed fields, those queries would break (that's what I was trying to get at in my first comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that flipside crypto lets you query JSON keys inside the accountdata blob. names like onl, vote, voteFst, voteLst are being renamed here, more than auth-addr, right? @barnjamin
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes thats correct
func (l LedgerForEvaluator) LookupWithoutRewards(addresses map[basics.Address]struct{}) (map[basics.Address]*ledgercore.AccountData, error) { | ||
addressesArr := make([]basics.Address, 0, len(addresses)) | ||
for address := range addresses { | ||
addressesArr = append(addressesArr, address) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need to make a copy here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes because iterating over a map is non-determinstic. So, I first copy the addresses to an array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need to copy the address object into the array, since "address" is reused for each value in the range.
addressesArr := make([]basics.Address, len(addresses))
i := 0
for address := range addresses {
copy(addressesArr[i][:], address)
i++
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basics.Address
is a fixed size byte array. It is copied by value.
} | ||
err := row.Scan(&creatorAddr, ¶ms) | ||
if err == pgx.ErrNoRows { | ||
return basics.Address{}, basics.AssetParams{}, false, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return err here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the asset doesn't exist, there is no error. However, the third return value ("exists") is false
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this case get triggered when creating an asset/holding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you explain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering when you would expect to see ErrNoRows
. If it isn't expected it could be an error like Shiqi was suggesting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suppose the evaluator wants asset holding of an account. But it can only request asset holding together with asset params for the account. In many cases, asset params will not be created by the given account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't reviewed the migration code yet, but wanted to share my first round of comments.
Overall it seems to make sense, but I wasn't expecting the data structures to be so complicated. In a way, it feels like we're going to have the same sort of issues we had with the "apply" functions, but now it's complexity for running "eval".
I did have some specific requests in the comments.
@@ -1,14 +1,14 @@ | |||
# Import validation tool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this tool able to upgrade the DB from a previous release, or does it need to be restarted from the beginning?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can upgrade the DBs (indexer and go-algorand). It uses public "open db" functions that will run necessary migrations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool!
@@ -106,3 +106,29 @@ type specialAddresses struct { | |||
FeeSinkOverride crypto.Digest `codec:"FeeSink"` | |||
RewardsPoolOverride crypto.Digest `codec:"RewardsPool"` | |||
} | |||
|
|||
type baseOnlineAccountData struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a type that we can embed here?
The pattern used previously implicitly supports new fields. With this pattern, it would be easy to forget about a new field and accidentally drop that data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No unfortunately. The new approach in go-algorand is, ledgercore.AccountData
doesn't have codec tags, and there is baseAccountData
similar to this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we make them public in go-algorand?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hrmm. would it help to add codec tags to ledgercore.AccountData and ledgercore.VotingData that match the ones in basics.AccountData?
TotalAssetParams uint64 `codec:"f"` | ||
TotalAssets uint64 `codec:"g"` | ||
TotalAppParams uint64 `codec:"h"` | ||
TotalAppLocalStates uint64 `codec:"i"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of these fields are being renamed (or maybe just auth-addr). Not sure if anyone is querying these directly, but it could potentially matter to some users. The fact that the names are equivalent to algod makes this seem a little better, but it does seem unusual that the data is basically unusable without serializing it to a go object.
"WHERE index = $1 AND NOT deleted", | ||
appParamsStmtName: "SELECT creator, params FROM app WHERE index = $1 AND NOT deleted", | ||
appLocalStatesStmtName: "SELECT localstate FROM account_app " + | ||
"WHERE addr = $1 AND app = $2 AND NOT deleted", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just checking my understanding here. Previously we query for all of the holdings according to an address, now the eval function gives a list of required IDs and they are fetched one by one as needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the eval function gives a list of (address, creatable).
func (l LedgerForEvaluator) LookupWithoutRewards(addresses map[basics.Address]struct{}) (map[basics.Address]*ledgercore.AccountData, error) { | ||
addressesArr := make([]basics.Address, 0, len(addresses)) | ||
for address := range addresses { | ||
addressesArr = append(addressesArr, address) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need to copy the address object into the array, since "address" is reused for each value in the range.
addressesArr := make([]basics.Address, len(addresses))
i := 0
for address := range addresses {
copy(addressesArr[i][:], address)
i++
}
if accountData != nil { | ||
existingAddresses = append(existingAddresses, address) | ||
// LookupResources is part of go-algorand's indexerLedgerForEval interface. | ||
func (l LedgerForEvaluator) LookupResources(input map[basics.Address]map[ledger.Creatable]struct{}) (map[basics.Address]map[ledger.Creatable]ledgercore.AccountResource, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole function is very hard to follow with the large data structures mixed with SQL queries that lookup the data.
Could you update the function comment to summarize what's happening, and add some comments to the different sections?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
appLocalStatesReq := make([]AddrID, 0, len(input)) | ||
appParamsReq := make([]basics.CreatableIndex, 0, len(input)) | ||
appParamsToAddresses := make(map[basics.CreatableIndex]map[basics.Address]struct{}) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this whole loop / data structure created to facilitate the batch query and results processing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we need to construct arrays because iterating over maps is non-deterministic.
RewardedMicroAlgos: basics.MicroAlgos{Raw: 6}, | ||
AuthAddr: test.AccountA, | ||
}, | ||
VotingData: ledgercore.VotingData{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May as well add a state proof key in here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
idb/postgres/postgres.go
Outdated
// `resourcesReq` respectively for the given transaction. | ||
func addToAccountsResourcesRequest(stxnad *transactions.SignedTxnWithAD, assetCreators map[basics.AssetIndex]ledger.FoundAddress, appCreators map[basics.AppIndex]ledger.FoundAddress, addressesReq map[basics.Address]struct{}, resourcesReq map[basics.Address]map[ledger.Creatable]struct{}) { | ||
lookupResourcesReq := | ||
func(addr basics.Address) map[ledger.Creatable]struct{} { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This helper seems to be used identically everywhere:
lookupResourcesReq(txn.Sender)[creatable] = struct{}{}
How about changing the signature to setResourcesReq(txn.Sender, creatable)
and skip the map index / struct{}{}
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea, done
idb/postgres/postgres.go
Outdated
|
||
// Add requests for account data and account resources to `addressesReq` and | ||
// `resourcesReq` respectively for the given transaction. | ||
func addToAccountsResourcesRequest(stxnad *transactions.SignedTxnWithAD, assetCreators map[basics.AssetIndex]ledger.FoundAddress, appCreators map[basics.AppIndex]ledger.FoundAddress, addressesReq map[basics.Address]struct{}, resourcesReq map[basics.Address]map[ledger.Creatable]struct{}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are any of these helpers postgres specific, or is this pulling types out of transactions and building up accounting data structures?
We should pull all of the accounting logic into a separate package so that it's clear what is for the DB and what is dealing with go-algorand accounting details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved some of it to accounting/
.
70b5450
to
2f706ce
Compare
Thanks for the review! What complexity exactly are you referring to? |
Working with the map[addr]map[creatable]struct{} data structures. I can see how the existing patterns were applied pretty directly to the new data structures, but it's complicated. |
// Preload account data and account resources. | ||
func prepareAccountsResources(l *ledger_for_evaluator.LedgerForEvaluator, payset transactions.Payset, assetCreators map[basics.AssetIndex]ledger.FoundAddress, appCreators map[basics.AppIndex]ledger.FoundAddress) (map[basics.Address]*ledgercore.AccountData, map[basics.Address]map[ledger.Creatable]ledgercore.AccountResource, error) { | ||
addressesReq, resourcesReq := | ||
accounting.MakePreloadAccountsResourcesRequest(payset, assetCreators, appCreators) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ty, this makes it a lot easier to read the code 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
Adds new REST endpoints and "exclude" parameter for account information endpoint, similar to algorand/go-algorand#3542. Relies on #870 Update endpoint: GET /v2/accounts/{address} Supports new query parameter “exclude”, which can be a combination of all, created-apps, created-assets, apps-local-state, assets Removes empty “creator” string from AssetHolding type — was not implemented (removed associated TODO) Update endpoint: GET /v2/assets/{asset-id}/balances Remove "round" query parameter — I noticed support is not implemented, and the parameter was being ignored New endpoints: GET /v2/accounts/{address}/created-apps GET /v2/accounts/{address}/created-assets GET /v2/accounts/{address}/assets GET /v2/accounts/{address}/apps-local-state Supported query parameters: asset-id / application-id (look up a single asset), include-all (include deleted), limit, next (for pagination)
Summary
Ledger refactoring changes.
Closes https://github.com/algorand/go-algorand-internal/issues/1867.
Test Plan
Relying on existing tests. Some of them were changed. There are also tests for the migration.
The import validator caught up to the latest round on mainnet, testnet and betanet successfully.