Skip to content

Commit b019536

Browse files
jasonpaulosgmalouf
andauthored
Add account min balance field (#1596)
* Add account min balance field * Update test resource boxes.json to expect min_balance field to be present/set. * Add unit test for minBalance calculation inspired by go-algorand. --------- Co-authored-by: Gary Malouf <982483+gmalouf@users.noreply.github.com>
1 parent 5fdba20 commit b019536

File tree

11 files changed

+657
-343
lines changed

11 files changed

+657
-343
lines changed

accounting/rewind.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ func AccountAtRound(ctx context.Context, account models.Account, round uint64, d
178178
acct.PendingRewards = 0
179179
acct.Amount = acct.AmountWithoutPendingRewards
180180

181+
// MinBalance is not supported.
182+
acct.MinBalance = 0
183+
181184
// TODO: Clear out the closed-at field as well. Like Rewards we cannot know this value for all accounts.
182185
//acct.ClosedAt = 0
183186

api/generated/common/routes.go

Lines changed: 138 additions & 137 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/generated/common/types.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/generated/v2/routes.go

Lines changed: 179 additions & 178 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/generated/v2/types.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/indexer.oas2.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,7 @@
10281028
"amount-without-pending-rewards",
10291029
"rewards",
10301030
"status",
1031+
"min-balance",
10311032
"total-apps-opted-in",
10321033
"total-assets-opted-in",
10331034
"total-box-bytes",
@@ -1044,6 +1045,10 @@
10441045
"description": "total number of MicroAlgos in the account",
10451046
"type": "integer"
10461047
},
1048+
"min-balance": {
1049+
"description": "MicroAlgo balance required by the account.\n\nThe requirement grows based on asset and application usage.",
1050+
"type": "integer"
1051+
},
10471052
"amount-without-pending-rewards": {
10481053
"description": "specifies the amount of MicroAlgos in the account, without the pending rewards.",
10491054
"type": "integer"

api/indexer.oas3.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,10 @@
767767
"description": "The round in which this account last proposed the block.",
768768
"type": "integer"
769769
},
770+
"min-balance": {
771+
"description": "MicroAlgo balance required by the account.\n\nThe requirement grows based on asset and application usage.",
772+
"type": "integer"
773+
},
770774
"participation": {
771775
"$ref": "#/components/schemas/AccountParticipation"
772776
},
@@ -828,6 +832,7 @@
828832
"address",
829833
"amount",
830834
"amount-without-pending-rewards",
835+
"min-balance",
831836
"pending-rewards",
832837
"rewards",
833838
"round",

api/test_resources/boxes.json

Lines changed: 45 additions & 20 deletions
Large diffs are not rendered by default.

idb/postgres/postgres.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,17 @@ func (db *IndexerDb) yieldAccountsThread(req *getAccountsRequest) {
996996
db.log.Warnf("long query %fs: %s", dt.Seconds(), req.query)
997997
}
998998
}()
999+
var proto config.ConsensusParams
1000+
{
1001+
var ok bool
1002+
// temporarily cast req.blockheader.CurrentProtocol(string) to protocol.ConsensusVersion
1003+
proto, ok = config.Consensus[protocol.ConsensusVersion(req.blockheader.CurrentProtocol)]
1004+
if !ok {
1005+
err := fmt.Errorf("get protocol err (%s)", req.blockheader.CurrentProtocol)
1006+
req.out <- idb.AccountRow{Error: err}
1007+
return
1008+
}
1009+
}
9991010
for req.rows.Next() {
10001011
var addr []byte
10011012
var microalgos uint64
@@ -1131,20 +1142,14 @@ func (db *IndexerDb) yieldAccountsThread(req *getAccountsRequest) {
11311142
account.IncentiveEligible = omitEmpty(accountData.IncentiveEligible)
11321143
account.LastHeartbeat = omitEmpty(uint64(accountData.LastHeartbeat))
11331144
account.LastProposed = omitEmpty(uint64(accountData.LastProposed))
1145+
1146+
account.MinBalance = itypes.AccountMinBalance(accountData, &proto)
11341147
}
11351148

11361149
if account.Status == "NotParticipating" {
11371150
account.PendingRewards = 0
11381151
} else {
11391152
// TODO: pending rewards calculation doesn't belong in database layer (this is just the most covenient place which has all the data)
1140-
// TODO: replace config.Consensus. config.Consensus map[protocol.ConsensusVersion]ConsensusParams
1141-
// temporarily cast req.blockheader.CurrentProtocol(string) to protocol.ConsensusVersion
1142-
proto, ok := config.Consensus[protocol.ConsensusVersion(req.blockheader.CurrentProtocol)]
1143-
if !ok {
1144-
err = fmt.Errorf("get protocol err (%s)", req.blockheader.CurrentProtocol)
1145-
req.out <- idb.AccountRow{Error: err}
1146-
break
1147-
}
11481153
rewardsUnits := uint64(0)
11491154
if proto.RewardUnit != 0 {
11501155
rewardsUnits = microalgos / proto.RewardUnit

types/min_balance.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package types
2+
3+
import (
4+
"github.com/algorand/go-algorand-sdk/v2/protocol/config"
5+
sdk "github.com/algorand/go-algorand-sdk/v2/types"
6+
)
7+
8+
// stateSchemaMinBalance computes the MinBalance requirements for a StateSchema
9+
// based on the consensus parameters
10+
func stateSchemaMinBalance(sm sdk.StateSchema, proto *config.ConsensusParams) uint64 {
11+
// Flat cost for each key/value pair
12+
flatCost := proto.SchemaMinBalancePerEntry * (sm.NumUint + sm.NumByteSlice)
13+
14+
// Cost for uints
15+
uintCost := proto.SchemaUintMinBalance * sm.NumUint
16+
17+
// Cost for byte slices
18+
bytesCost := proto.SchemaBytesMinBalance * sm.NumByteSlice
19+
20+
// Sum the separate costs
21+
return flatCost + uintCost + bytesCost
22+
}
23+
24+
// minBalance computes the minimum balance requirements for an account based on
25+
// some consensus parameters. MinBalance should correspond roughly to how much
26+
// storage the account is allowed to store on disk.
27+
func minBalance(
28+
proto *config.ConsensusParams,
29+
totalAssets uint64,
30+
totalAppSchema sdk.StateSchema,
31+
totalAppParams uint64, totalAppLocalStates uint64,
32+
totalExtraAppPages uint64,
33+
totalBoxes uint64, totalBoxBytes uint64,
34+
) uint64 {
35+
var min uint64
36+
37+
// First, base MinBalance
38+
min = proto.MinBalance
39+
40+
// MinBalance for each Asset
41+
assetCost := proto.MinBalance * totalAssets
42+
min += assetCost
43+
44+
// Base MinBalance for each created application
45+
appCreationCost := proto.AppFlatParamsMinBalance * totalAppParams
46+
min += appCreationCost
47+
48+
// Base MinBalance for each opted in application
49+
appOptInCost := proto.AppFlatOptInMinBalance * totalAppLocalStates
50+
min += appOptInCost
51+
52+
// MinBalance for state usage measured by LocalStateSchemas and
53+
// GlobalStateSchemas
54+
schemaCost := stateSchemaMinBalance(totalAppSchema, proto)
55+
min += schemaCost
56+
57+
// MinBalance for each extra app program page
58+
extraAppProgramLenCost := proto.AppFlatParamsMinBalance * totalExtraAppPages
59+
min += extraAppProgramLenCost
60+
61+
// Base MinBalance for each created box
62+
boxBaseCost := proto.BoxFlatMinBalance * totalBoxes
63+
min += boxBaseCost
64+
65+
// Per byte MinBalance for boxes
66+
boxByteCost := proto.BoxByteMinBalance * totalBoxBytes
67+
min += boxByteCost
68+
69+
return min
70+
}
71+
72+
// AccountMinBalance computes the minimum balance requirements for an account
73+
// based on some consensus parameters. MinBalance should correspond roughly to
74+
// how much storage the account is allowed to store on disk.
75+
func AccountMinBalance(account sdk.AccountData, proto *config.ConsensusParams) uint64 {
76+
return minBalance(
77+
proto,
78+
account.TotalAssets,
79+
account.TotalAppSchema,
80+
account.TotalAppParams, account.TotalAppLocalStates,
81+
uint64(account.TotalExtraAppPages),
82+
account.TotalBoxes, account.TotalBoxBytes,
83+
)
84+
}

0 commit comments

Comments
 (0)