Skip to content

Commit

Permalink
services/horizon: Include contract asset balances in asset stats (#4805)
Browse files Browse the repository at this point in the history
This PR a follow up to #4782 where we update asset stats ingestion to keep track of how many contracts hold an asset and the amount of an asset held by all contracts in total.

This is implemented by ingesting balance contract data ledger entries which are updated by the stellar asset contract. The balance contract data ledger entries are like trustlines but for contract holders of an asset.
  • Loading branch information
tamirms authored Mar 16, 2023
1 parent f8ba8f1 commit cd3a221
Show file tree
Hide file tree
Showing 17 changed files with 1,412 additions and 236 deletions.
2 changes: 2 additions & 0 deletions protocols/horizon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,13 @@ type AssetStat struct {
NumAccounts int32 `json:"num_accounts"`
NumClaimableBalances int32 `json:"num_claimable_balances"`
NumLiquidityPools int32 `json:"num_liquidity_pools"`
NumContracts int32 `json:"num_contracts"`
// Action needed in release: horizon-v3.0.0: deprecated field
Amount string `json:"amount"`
Accounts AssetStatAccounts `json:"accounts"`
ClaimableBalancesAmount string `json:"claimable_balances_amount"`
LiquidityPoolsAmount string `json:"liquidity_pools_amount"`
ContractsAmount string `json:"contracts_amount"`
Balances AssetStatBalances `json:"balances"`
Flags AccountFlags `json:"flags"`
}
Expand Down
5 changes: 5 additions & 0 deletions services/horizon/internal/actions/asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ func TestAssetStats(t *testing.T) {
LiquidityPoolsAmount: "0.0000020",
Amount: "0.0000001",
NumAccounts: usdAssetStat.NumAccounts,
ContractsAmount: "0.0000000",
Asset: base.Asset{
Type: "credit_alphanum4",
Code: usdAssetStat.AssetCode,
Expand Down Expand Up @@ -202,6 +203,7 @@ func TestAssetStats(t *testing.T) {
},
ClaimableBalancesAmount: "0.0000000",
LiquidityPoolsAmount: "0.0000000",
ContractsAmount: "0.0000000",
Amount: "0.0000023",
NumAccounts: etherAssetStat.NumAccounts,
Asset: base.Asset{
Expand Down Expand Up @@ -248,6 +250,7 @@ func TestAssetStats(t *testing.T) {
ClaimableBalancesAmount: "0.0000000",
LiquidityPoolsAmount: "0.0000000",
Amount: "0.0000001",
ContractsAmount: "0.0000000",
NumAccounts: otherUSDAssetStat.NumAccounts,
Asset: base.Asset{
Type: "credit_alphanum4",
Expand Down Expand Up @@ -295,6 +298,7 @@ func TestAssetStats(t *testing.T) {
ClaimableBalancesAmount: "0.0000000",
LiquidityPoolsAmount: "0.0000000",
Amount: "0.0000111",
ContractsAmount: "0.0000000",
NumAccounts: eurAssetStat.NumAccounts,
Asset: base.Asset{
Type: "credit_alphanum4",
Expand Down Expand Up @@ -471,6 +475,7 @@ func TestAssetStatsIssuerDoesNotExist(t *testing.T) {
ClaimableBalancesAmount: "0.0000000",
LiquidityPoolsAmount: "0.0000000",
Amount: "0.0000001",
ContractsAmount: "0.0000000",
NumAccounts: usdAssetStat.NumAccounts,
Asset: base.Asset{
Type: "credit_alphanum4",
Expand Down
14 changes: 0 additions & 14 deletions services/horizon/internal/db2/history/asset_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,20 +126,6 @@ func (q *Q) GetAssetStatByContracts(ctx context.Context, contractIDs [][32]byte)
return assetStats, err
}

// CountContractIDs counts all rows in the asset stats table which have a contract id set.
// CountContractIDs is used by the state verification routine.
func (q *Q) CountContractIDs(ctx context.Context) (int, error) {
sql := sq.Select("count(*)").From("exp_asset_stats").
Where("contract_id IS NOT NULL")

var count int
if err := q.Get(ctx, &count, sql); err != nil {
return 0, errors.Wrap(err, "could not run select query")
}

return count, nil
}

func parseAssetStatsCursor(cursor string) (string, string, error) {
parts := strings.SplitN(cursor, "_", 3)
if len(parts) != 3 {
Expand Down
48 changes: 37 additions & 11 deletions services/horizon/internal/db2/history/asset_stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ func TestAssetStatContracts(t *testing.T) {
test.ResetHorizonDB(t, tt.HorizonDB)
q := &Q{tt.HorizonSession()}

// asset stats is empty so count should be 0
count, err := q.CountContractIDs(tt.Ctx)
tt.Assert.NoError(err)
tt.Assert.Equal(0, count)

assetStats := []ExpAssetStat{
{
AssetType: xdr.AssetTypeAssetTypeNative,
Expand All @@ -30,13 +25,15 @@ func TestAssetStatContracts(t *testing.T) {
ClaimableBalances: 0,
LiquidityPools: 0,
Unauthorized: 0,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "0",
AuthorizedToMaintainLiabilities: "0",
ClaimableBalances: "0",
LiquidityPools: "0",
Unauthorized: "0",
Contracts: "0",
},
Amount: "0",
NumAccounts: 0,
Expand All @@ -49,13 +46,15 @@ func TestAssetStatContracts(t *testing.T) {
Authorized: 1,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 7,
},
Balances: ExpAssetStatBalances{
Authorized: "23",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "4",
LiquidityPools: "5",
Contracts: "60",
},
Amount: "23",
NumAccounts: 1,
Expand All @@ -68,13 +67,15 @@ func TestAssetStatContracts(t *testing.T) {
Authorized: 2,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 8,
},
Balances: ExpAssetStatBalances{
Authorized: "1",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "4",
LiquidityPools: "5",
Contracts: "90",
},
Amount: "1",
NumAccounts: 2,
Expand All @@ -87,14 +88,10 @@ func TestAssetStatContracts(t *testing.T) {
}
tt.Assert.NoError(q.InsertAssetStats(tt.Ctx, assetStats, 1))

count, err = q.CountContractIDs(tt.Ctx)
tt.Assert.NoError(err)
tt.Assert.Equal(2, count)

contractID[0] = 0
for i := 0; i < 2; i++ {
var assetStat ExpAssetStat
assetStat, err = q.GetAssetStatByContract(tt.Ctx, contractID)
assetStat, err := q.GetAssetStatByContract(tt.Ctx, contractID)
tt.Assert.NoError(err)
tt.Assert.True(assetStat.Equals(assetStats[i]))
contractID[0]++
Expand Down Expand Up @@ -162,13 +159,15 @@ func TestInsertAssetStats(t *testing.T) {
Authorized: 2,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "1",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "4",
LiquidityPools: "5",
Contracts: "0",
},
Amount: "1",
NumAccounts: 2,
Expand All @@ -181,13 +180,15 @@ func TestInsertAssetStats(t *testing.T) {
Authorized: 1,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "23",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "4",
LiquidityPools: "5",
Contracts: "0",
},
Amount: "23",
NumAccounts: 1,
Expand Down Expand Up @@ -217,13 +218,15 @@ func TestInsertAssetStat(t *testing.T) {
Authorized: 2,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "1",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "4",
LiquidityPools: "5",
Contracts: "0",
},
Amount: "1",
NumAccounts: 2,
Expand All @@ -236,13 +239,15 @@ func TestInsertAssetStat(t *testing.T) {
Authorized: 1,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "23",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "4",
LiquidityPools: "5",
Contracts: "0",
},
Amount: "23",
NumAccounts: 1,
Expand Down Expand Up @@ -274,13 +279,15 @@ func TestInsertAssetStatAlreadyExistsError(t *testing.T) {
Authorized: 2,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "1",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "4",
LiquidityPools: "5",
Contracts: "0",
},
Amount: "1",
NumAccounts: 2,
Expand Down Expand Up @@ -321,13 +328,15 @@ func TestUpdateAssetStatDoesNotExistsError(t *testing.T) {
Authorized: 2,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "1",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "4",
LiquidityPools: "5",
Contracts: "0",
},
Amount: "1",
NumAccounts: 2,
Expand Down Expand Up @@ -356,13 +365,15 @@ func TestUpdateStat(t *testing.T) {
Authorized: 2,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "1",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "4",
LiquidityPools: "5",
Contracts: "0",
},
Amount: "1",
NumAccounts: 2,
Expand All @@ -377,15 +388,18 @@ func TestUpdateStat(t *testing.T) {
tt.Assert.Equal(got, assetStat)

assetStat.NumAccounts = 50
assetStat.Accounts.Contracts = 4
assetStat.Amount = "23"
assetStat.Balances.Contracts = "56"
assetStat.SetContractID([32]byte{23})

numChanged, err = q.UpdateAssetStat(tt.Ctx, assetStat)
tt.Assert.Nil(err)
tt.Assert.Equal(numChanged, int64(1))

got, err = q.GetAssetStat(tt.Ctx, assetStat.AssetType, assetStat.AssetCode, assetStat.AssetIssuer)
tt.Assert.NoError(err)
tt.Assert.Equal(got, assetStat)
tt.Assert.True(got.Equals(assetStat))
}

func TestGetAssetStatDoesNotExist(t *testing.T) {
Expand All @@ -402,13 +416,15 @@ func TestGetAssetStatDoesNotExist(t *testing.T) {
Authorized: 2,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "1",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "4",
LiquidityPools: "5",
Contracts: "0",
},
Amount: "1",
NumAccounts: 2,
Expand All @@ -433,13 +449,15 @@ func TestRemoveAssetStat(t *testing.T) {
Authorized: 2,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "1",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "4",
LiquidityPools: "5",
Contracts: "0",
},
Amount: "1",
NumAccounts: 2,
Expand Down Expand Up @@ -569,13 +587,15 @@ func TestGetAssetStatsFiltersAndCursor(t *testing.T) {
Authorized: 2,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "1",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "0",
LiquidityPools: "0",
Contracts: "0",
},
Amount: "1",
NumAccounts: 2,
Expand All @@ -588,13 +608,15 @@ func TestGetAssetStatsFiltersAndCursor(t *testing.T) {
Authorized: 1,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "23",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "0",
LiquidityPools: "0",
Contracts: "0",
},
Amount: "23",
NumAccounts: 1,
Expand All @@ -607,13 +629,15 @@ func TestGetAssetStatsFiltersAndCursor(t *testing.T) {
Authorized: 2,
AuthorizedToMaintainLiabilities: 3,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "1",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "0",
LiquidityPools: "0",
Contracts: "0",
},
Amount: "1",
NumAccounts: 2,
Expand All @@ -626,13 +650,15 @@ func TestGetAssetStatsFiltersAndCursor(t *testing.T) {
Authorized: 3,
AuthorizedToMaintainLiabilities: 2,
Unauthorized: 4,
Contracts: 0,
},
Balances: ExpAssetStatBalances{
Authorized: "111",
AuthorizedToMaintainLiabilities: "2",
Unauthorized: "3",
ClaimableBalances: "1",
LiquidityPools: "2",
Contracts: "0",
},
Amount: "111",
NumAccounts: 3,
Expand Down
Loading

0 comments on commit cd3a221

Please sign in to comment.