Cardano-db-sync: Account balance example

Created on 25 Mar 2021  ·  10Comments  ·  Source: input-output-hk/cardano-db-sync

I might be a tad late to the party, but would you mind sharing how one would track a balance of an account, for example?

Calculating outputs + reward + reserve + treasury - inputs - withdrawal won't do the trick and there doesn't seem to be a nice and easy way around this.

A good candidate could be stake1uyluup0rh6r2cc7kcw8nudqz990ezf5ltagxmw3u8deukvqwq7etq (rewards account for an already retired pool). Different explorers show this in a different manner, but I think it's currently not possible without any kind of workaround / additional cumbersome computing. Things become even more blurry when taking edge cases into account. With all that in mind, wouldn't it be beneficial to explicitely track reap (in addition to deposit) info along with account and epoch in the dbsync?

_Originally posted by @1000101 in https://github.com/input-output-hk/cardano-db-sync/issues/474#issuecomment-804793203_

Most helpful comment

I agree with this. There are also numerous edge cases each "user" of cardano-db-sync would have to handle. Both Adalite and Yoroi are confused when they see an account which was previously a reward account of a retired pool:
image
image

Also, AFAIK only cardanoscan handles the remaining rewards correctly, and they do not use db-sync.

There should be an easy relation which would be updated the same way as rewards are automatically added. Let's take a pool with multiple deregistrations as an example, since that is actually pretty common and it's hard to handle the reward balance of its owner account.

There are 9 retirement certificates to it
image

And 15 registration certificates
image

There are numerous edge cases and vague rules of retirement overruling by new certificaes.

@erikd, if you insist on sufficient connection between 500 tx deposit and the retirements, how would you write a query for just the returned deposits of one of its owner accounts - e127e9e94eaa9287680e877832700a1724255e9688609497e09a6c440f? If not, can we add this explicitly to db-sync? Let's say, as a new table, named pool_refunds, with addr_id, amount (currently should be a multiple of 500) and epoch_no - so that this is handled correctly, and in a single place.

All 10 comments

Thanks for opening this issue on my behalf! Just to clarify a bit more - it's not just about the account per se, but mainly about tracking reap somewhere in the db so it could be easily used in computation of account balance (which is used everywhere, e.g. pool live stake size, pool live pledge, etc.).

@1000101 What is "reap" ? There is no table or column in db-sync called "reap".

Account balance is not trivial.

Addresses in the Shelley and later eras have two components; a payment credential and a staking credential. The stake address (eg stake1uyluup0rh6r2cc7kcw8nudqz990ezf5ltagxmw3u8deukvqwq7etq) is derived from the later. However it is possible to construct a valid address which does not contain any staking credential.

So while it would be possible to get the account balance for a particular stake address, there is no way to get the account balance of a wallet unless every address in that wallet includes the same staking credential.

@1000101 What is "reap" ? There is no table or column in db-sync called "reap".

Oh, I'm sorry for not clarifying this enough. What I meant is described in https://github.com/input-output-hk/cardano-db-sync/issues/474 - i.e. what I'm saying is exactly that there is no column for this but it would be great if there would be.

There is currently no way of easily computing balance of an account, when this account has been used in pool registration (as a reward account) and later on the pool was retired, thus regaining the pool deposit. The "payment" of pool deposit is tracked in dbsync, but not its "refund" when a pool is retired.

I agree with this. There are also numerous edge cases each "user" of cardano-db-sync would have to handle. Both Adalite and Yoroi are confused when they see an account which was previously a reward account of a retired pool:
image
image

Also, AFAIK only cardanoscan handles the remaining rewards correctly, and they do not use db-sync.

There should be an easy relation which would be updated the same way as rewards are automatically added. Let's take a pool with multiple deregistrations as an example, since that is actually pretty common and it's hard to handle the reward balance of its owner account.

There are 9 retirement certificates to it
image

And 15 registration certificates
image

There are numerous edge cases and vague rules of retirement overruling by new certificaes.

@erikd, if you insist on sufficient connection between 500 tx deposit and the retirements, how would you write a query for just the returned deposits of one of its owner accounts - e127e9e94eaa9287680e877832700a1724255e9688609497e09a6c440f? If not, can we add this explicitly to db-sync? Let's say, as a new table, named pool_refunds, with addr_id, amount (currently should be a multiple of 500) and epoch_no - so that this is handled correctly, and in a single place.

Currently db-sync extracts data from the ledger state and inserts in in the database. It discards some information, but does not do any aggregations. What is inserted into the database is simply a reflection of what ledger state provides.

Lets look at the relevant pool_hash.id:

> select id, hash_raw from pool_hash
    where hash_raw = '\x9c8e59ea7004a51f953642653d70a94d066359b9dd6e6416a5430ff3' ;
  id  |                          hash_raw                          
------+------------------------------------------------------------
 5022 | \x9c8e59ea7004a51f953642653d70a94d066359b9dd6e6416a5430ff3
(1 row)

The pool was registered (extra joins to put them in order of block_no ascending) as follows:

> select pool_update.id, hash_id, cert_index, active_epoch_no, block.block_no, block.epoch_no
    from pool_update
      inner join tx on tx.id = pool_update.registered_tx_id
      inner join block on tx.block_id = block.id
    where pool_update.hash_id = 5022
    order by block.block_no asc; 
  id  | hash_id | cert_index | active_epoch_no | block_no | epoch_no 
------+---------+------------+-----------------+----------+----------
 5022 |    5022 |          0 |             220 |  4717004 |      218
 5023 |    5022 |          0 |             220 |  4717035 |      218
 5024 |    5022 |          0 |             220 |  4717057 |      218
 5029 |    5022 |          0 |             220 |  4717140 |      218
 5030 |    5022 |          0 |             220 |  4717146 |      218
 5031 |    5022 |          0 |             220 |  4717169 |      218
 5032 |    5022 |          0 |             220 |  4717189 |      218
 5033 |    5022 |          0 |             220 |  4717216 |      218
 5034 |    5022 |          0 |             220 |  4717234 |      218
 5035 |    5022 |          0 |             220 |  4717241 |      218
 5037 |    5022 |          0 |             220 |  4717296 |      218
 5038 |    5022 |          0 |             220 |  4717425 |      218
 5061 |    5022 |          0 |             220 |  4721000 |      218
 5080 |    5022 |          0 |             221 |  4725394 |      219
 5081 |    5022 |          0 |             221 |  4725435 |      219
(15 rows)

and deregistered in:

> select pool_retire.id, hash_id, cert_index, retiring_epoch, block.block_no, block.epoch_no
    from pool_retire
      inner join tx on tx.id = pool_retire.announced_tx_id
      inner join block on tx.block_id = block.id
    where hash_id = 5022
    order by block.block_no asc ; 
 id  | hash_id | cert_index | retiring_epoch | block_no | epoch_no 
-----+---------+------------+----------------+----------+----------
 180 |    5022 |          0 |            219 |  4717395 |      218
 181 |    5022 |          0 |            219 |  4717397 |      218
 182 |    5022 |          0 |            219 |  4717401 |      218
 183 |    5022 |          0 |            219 |  4717403 |      218
 184 |    5022 |          0 |            219 |  4717405 |      218
 185 |    5022 |          0 |            219 |  4717417 |      218
 186 |    5022 |          0 |            219 |  4717430 |      218
 187 |    5022 |          0 |            220 |  4725366 |      219
 188 |    5022 |          0 |            220 |  4725717 |      219
(9 rows)

De-intrerleaving updates and deregistrations:

action   | block_no
---------+------------
register | 4717004
update   | 4717035
update   | 4717057
update   | 4717140
update   | 4717146
update   | 4717169
update   | 4717189
update   | 4717216
update   | 4717234
update   | 4717241
update   | 4717296
retire   | 4717395
retire   | 4717397
retire   | 4717401
retire   | 4717403
retire   | 4717405
retire   | 4717417
update   | 4717425
retire   | 4717430
update   | 4721000
retire   | 4725366
update   | 4725394
update   | 4725435
retire   | 4725717

To be continued ....

This is definitely too much of a pain in the neck. Talking with ledger-specs people to try to come up with a solution.

Spoken to the ledger-specs people who suggested a quick and dirty way to fudge it for now while waiting for a better solution.

The idea would be to have something like a pool_registration_refund table containing the stake address and the amount.

That'd be great!

Spoken to the ledger-specs people who suggested a quick and dirty way to fudge it for now while waiting for a better solution.

The idea would be to have something like a pool_registration_refund table containing the stake address and the amount.

Awesome! I mean, we do have a workaround in place for now so we won't hit the negative balance @xdzurman shared, but it's around 100 lines of not-so-pretty:tm: SQL which has to be computed per every account. This would definitely help a ton. Thanks a mil!!!

Was this page helpful?
0 / 5 - 0 ratings