Skip to content

Commit 3136ad3

Browse files
committed
Make account endpoint produce deterministic output
1 parent 7529897 commit 3136ad3

File tree

2 files changed

+77
-52
lines changed

2 files changed

+77
-52
lines changed

daemon/algod/api/server/v2/account.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package v2
1919
import (
2020
"encoding/base64"
2121
"errors"
22+
"sort"
2223

2324
"github.com/algorand/go-algorand/crypto"
2425
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated"
@@ -45,12 +46,18 @@ func AccountDataToAccount(
4546

4647
assets = append(assets, holding)
4748
}
49+
sort.Slice(assets, func(i, j int) bool {
50+
return assets[i].AssetId < assets[j].AssetId
51+
})
4852

4953
createdAssets := make([]generated.Asset, 0, len(record.AssetParams))
5054
for idx, params := range record.AssetParams {
5155
asset := AssetParamsToAsset(address, idx, &params)
5256
createdAssets = append(createdAssets, asset)
5357
}
58+
sort.Slice(createdAssets, func(i, j int) bool {
59+
return createdAssets[i].Index < createdAssets[j].Index
60+
})
5461

5562
var apiParticipation *generated.AccountParticipation
5663
if record.VoteID != (crypto.OneTimeSignatureVerifier{}) {
@@ -68,6 +75,9 @@ func AccountDataToAccount(
6875
app := AppParamsToApplication(address, appIdx, &appParams)
6976
createdApps = append(createdApps, app)
7077
}
78+
sort.Slice(createdApps, func(i, j int) bool {
79+
return createdApps[i].Id < createdApps[j].Id
80+
})
7181

7282
appsLocalState := make([]generated.ApplicationLocalState, 0, len(record.AppLocalStates))
7383
for appIdx, state := range record.AppLocalStates {
@@ -81,6 +91,9 @@ func AccountDataToAccount(
8191
},
8292
})
8393
}
94+
sort.Slice(appsLocalState, func(i, j int) bool {
95+
return appsLocalState[i].Id < appsLocalState[j].Id
96+
})
8497

8598
totalAppSchema := generated.ApplicationStateSchema{
8699
NumByteSlice: record.TotalAppSchema.NumByteSlice,
@@ -118,7 +131,8 @@ func convertTKVToGenerated(tkv *basics.TealKeyValue) *generated.TealKeyValueStor
118131
return nil
119132
}
120133

121-
var converted generated.TealKeyValueStore
134+
converted := make(generated.TealKeyValueStore, 0, len(*tkv))
135+
rawKeyBytes := make([]string, 0, len(*tkv))
122136
for k, v := range *tkv {
123137
converted = append(converted, generated.TealKeyValue{
124138
Key: base64.StdEncoding.EncodeToString([]byte(k)),
@@ -128,7 +142,11 @@ func convertTKVToGenerated(tkv *basics.TealKeyValue) *generated.TealKeyValueStor
128142
Uint: v.Uint,
129143
},
130144
})
145+
rawKeyBytes = append(rawKeyBytes, k)
131146
}
147+
sort.Slice(converted, func(i, j int) bool {
148+
return rawKeyBytes[i] < rawKeyBytes[j]
149+
})
132150
return &converted
133151
}
134152

daemon/algod/api/server/v2/account_test.go

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func TestAccount(t *testing.T) {
4040
ApprovalProgram: []byte{1},
4141
StateSchemas: basics.StateSchemas{
4242
GlobalStateSchema: basics.StateSchema{NumUint: 1},
43+
LocalStateSchema: basics.StateSchema{NumByteSlice: 5},
4344
},
4445
}
4546
appParams2 := basics.AppParams{
@@ -52,11 +53,14 @@ func TestAccount(t *testing.T) {
5253
Total: 100,
5354
DefaultFrozen: false,
5455
UnitName: "unit1",
56+
Decimals: 0,
5557
}
5658
assetParams2 := basics.AssetParams{
5759
Total: 200,
5860
DefaultFrozen: true,
5961
UnitName: "unit2",
62+
Decimals: 6,
63+
MetadataHash: [32]byte{1},
6064
}
6165
copy(assetParams2.MetadataHash[:], []byte("test2"))
6266
a := basics.AccountData{
@@ -88,28 +92,24 @@ func TestAccount(t *testing.T) {
8892
addr := basics.Address{}.String()
8993
conv, err := AccountDataToAccount(addr, &b, map[basics.AssetIndex]string{}, round, a.MicroAlgos)
9094
require.NoError(t, err)
91-
require.Equal(t, conv.Address, addr)
92-
require.Equal(t, conv.Amount, b.MicroAlgos.Raw)
93-
require.Equal(t, conv.AmountWithoutPendingRewards, a.MicroAlgos.Raw)
95+
require.Equal(t, addr, conv.Address)
96+
require.Equal(t, b.MicroAlgos.Raw, conv.Amount)
97+
require.Equal(t, a.MicroAlgos.Raw, conv.AmountWithoutPendingRewards)
98+
99+
verifyCreatedApp := func(index int, appIdx basics.AppIndex, params basics.AppParams) {
100+
require.Equal(t, uint64(appIdx), (*conv.CreatedApps)[index].Id)
101+
require.Equal(t, params.ApprovalProgram, (*conv.CreatedApps)[index].Params.ApprovalProgram)
102+
require.Equal(t, params.GlobalStateSchema.NumUint, (*conv.CreatedApps)[index].Params.GlobalStateSchema.NumUint)
103+
require.Equal(t, params.GlobalStateSchema.NumByteSlice, (*conv.CreatedApps)[index].Params.GlobalStateSchema.NumByteSlice)
104+
require.Equal(t, params.LocalStateSchema.NumUint, (*conv.CreatedApps)[index].Params.LocalStateSchema.NumUint)
105+
require.Equal(t, params.LocalStateSchema.NumByteSlice, (*conv.CreatedApps)[index].Params.LocalStateSchema.NumByteSlice)
106+
}
94107

95108
require.NotNil(t, conv.CreatedApps)
96109
require.Equal(t, 2, len(*conv.CreatedApps))
97-
for _, app := range *conv.CreatedApps {
98-
var params basics.AppParams
99-
if app.Id == uint64(appIdx1) {
100-
params = appParams1
101-
} else if app.Id == uint64(appIdx2) {
102-
params = appParams2
103-
} else {
104-
require.Fail(t, fmt.Sprintf("app idx %d not in [%d, %d]", app.Id, appIdx1, appIdx2))
105-
}
106-
require.Equal(t, params.ApprovalProgram, app.Params.ApprovalProgram)
107-
require.Equal(t, params.GlobalStateSchema.NumUint, app.Params.GlobalStateSchema.NumUint)
108-
require.Equal(t, params.GlobalStateSchema.NumByteSlice, app.Params.GlobalStateSchema.NumByteSlice)
109-
}
110+
verifyCreatedApp(0, appIdx1, appParams1)
111+
verifyCreatedApp(1, appIdx2, appParams2)
110112

111-
require.NotNil(t, conv.AppsLocalState)
112-
require.Equal(t, 2, len(*conv.AppsLocalState))
113113
makeTKV := func(k string, v interface{}) generated.TealKeyValue {
114114
value := generated.TealValue{}
115115
switch v.(type) {
@@ -127,47 +127,54 @@ func TestAccount(t *testing.T) {
127127
Value: value,
128128
}
129129
}
130-
for _, ls := range *conv.AppsLocalState {
131-
require.Equal(t, uint64(10), ls.Schema.NumUint)
132-
require.Equal(t, uint64(0), ls.Schema.NumByteSlice)
133-
require.Equal(t, 2, len(*ls.KeyValue))
134-
var value1 generated.TealKeyValue
135-
var value2 generated.TealKeyValue
136-
if ls.Id == uint64(appIdx1) {
137-
value1 = makeTKV("uint", 1)
138-
value2 = makeTKV("bytes", "value1")
139-
} else if ls.Id == uint64(appIdx2) {
140-
value1 = makeTKV("uint", 2)
141-
value2 = makeTKV("bytes", "value2")
142-
} else {
143-
require.Fail(t, fmt.Sprintf("local state app idx %d not in [%d, %d]", ls.Id, appIdx1, appIdx2))
130+
131+
verifyAppLocalState := func(index int, appIdx basics.AppIndex, numUints, numByteSlices uint64, keyValues generated.TealKeyValueStore) {
132+
require.Equal(t, uint64(appIdx), (*conv.AppsLocalState)[index].Id)
133+
require.Equal(t, numUints, (*conv.AppsLocalState)[index].Schema.NumUint)
134+
require.Equal(t, numByteSlices, (*conv.AppsLocalState)[index].Schema.NumByteSlice)
135+
require.Equal(t, len(keyValues), len(*(*conv.AppsLocalState)[index].KeyValue))
136+
for i, keyValue := range keyValues {
137+
require.Equal(t, keyValue, (*(*conv.AppsLocalState)[index].KeyValue)[i])
144138
}
145-
require.Contains(t, *ls.KeyValue, value1)
146-
require.Contains(t, *ls.KeyValue, value2)
147139
}
148140

149-
require.NotNil(t, conv.CreatedAssets)
150-
require.Equal(t, 2, len(*conv.CreatedAssets))
151-
for _, asset := range *conv.CreatedAssets {
152-
var params basics.AssetParams
153-
if asset.Index == uint64(assetIdx1) {
154-
params = assetParams1
155-
} else if asset.Index == uint64(assetIdx2) {
156-
params = assetParams2
141+
require.NotNil(t, conv.AppsLocalState)
142+
require.Equal(t, 2, len(*conv.AppsLocalState))
143+
verifyAppLocalState(0, appIdx1, 10, 0, generated.TealKeyValueStore{makeTKV("bytes", "value1"), makeTKV("uint", 1)})
144+
verifyAppLocalState(1, appIdx2, 10, 0, generated.TealKeyValueStore{makeTKV("bytes", "value2"), makeTKV("uint", 2)})
145+
146+
verifyCreatedAsset := func(index int, assetIdx basics.AssetIndex, params basics.AssetParams) {
147+
require.Equal(t, uint64(assetIdx), (*conv.CreatedAssets)[index].Index)
148+
require.Equal(t, params.Total, (*conv.CreatedAssets)[index].Params.Total)
149+
require.NotNil(t, (*conv.CreatedAssets)[index].Params.DefaultFrozen)
150+
require.Equal(t, params.DefaultFrozen, *(*conv.CreatedAssets)[index].Params.DefaultFrozen)
151+
require.NotNil(t, (*conv.CreatedAssets)[index].Params.UnitName)
152+
require.Equal(t, params.UnitName, *(*conv.CreatedAssets)[index].Params.UnitName)
153+
if params.MetadataHash == ([32]byte{}) {
154+
require.Nil(t, (*conv.CreatedAssets)[index].Params.MetadataHash)
157155
} else {
158-
require.Fail(t, fmt.Sprintf("asset idx %d not in [%d, %d]", asset.Index, assetIdx1, assetIdx2))
159-
}
160-
require.Equal(t, params.Total, asset.Params.Total)
161-
require.NotNil(t, asset.Params.DefaultFrozen)
162-
require.Equal(t, params.DefaultFrozen, *asset.Params.DefaultFrozen)
163-
require.NotNil(t, asset.Params.UnitName)
164-
require.Equal(t, params.UnitName, *asset.Params.UnitName)
165-
if asset.Params.MetadataHash != nil {
166-
require.Equal(t, params.MetadataHash[:], *asset.Params.MetadataHash)
156+
require.NotNil(t, (*conv.CreatedAssets)[index].Params.MetadataHash)
157+
require.Equal(t, params.MetadataHash[:], *(*conv.CreatedAssets)[index].Params.MetadataHash)
167158
}
168159
}
169160

161+
require.NotNil(t, conv.CreatedAssets)
162+
require.Equal(t, 2, len(*conv.CreatedAssets))
163+
verifyCreatedAsset(0, assetIdx1, assetParams1)
164+
verifyCreatedAsset(1, assetIdx2, assetParams2)
165+
170166
c, err := AccountToAccountData(&conv)
171167
require.NoError(t, err)
172168
require.Equal(t, b, c)
169+
170+
t.Run("IsDeterministic", func(t *testing.T) {
171+
// convert the same account a few more times to make sure we always
172+
// produce the same generated.Account
173+
for i := 0; i < 10; i++ {
174+
anotherConv, err := AccountDataToAccount(addr, &b, map[basics.AssetIndex]string{}, round, a.MicroAlgos)
175+
require.NoError(t, err)
176+
177+
require.Equal(t, protocol.EncodeJSON(conv), protocol.EncodeJSON(anotherConv))
178+
}
179+
})
173180
}

0 commit comments

Comments
 (0)