Skip to content

Commit 56d2cec

Browse files
committed
update roundtrip.Check with more features and use in a bunch of places
1 parent 19a679f commit 56d2cec

File tree

6 files changed

+267
-13
lines changed

6 files changed

+267
-13
lines changed

crypto/onetimesig_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import (
2020
"fmt"
2121
"testing"
2222

23+
"github.com/stretchr/testify/require"
24+
25+
"github.com/algorand/go-algorand/data/basics/testing/roundtrip"
2326
"github.com/algorand/go-algorand/test/partitiontest"
2427
)
2528

@@ -140,6 +143,28 @@ func testOneTimeSignVerifyNewStyle(t *testing.T, c *OneTimeSignatureSecrets, c2
140143
}
141144
}
142145

146+
func TestHeartbeatProofRoundTrip(t *testing.T) {
147+
partitiontest.PartitionTest(t)
148+
149+
toOTS := func(h HeartbeatProof) OneTimeSignature { return h.ToOneTimeSignature() }
150+
toProof := func(ots OneTimeSignature) HeartbeatProof { return ots.ToHeartbeatProof() }
151+
152+
proofs := []HeartbeatProof{{}}
153+
for i := 0; i < 5; i++ {
154+
var p HeartbeatProof
155+
RandBytes(p.Sig[:])
156+
RandBytes(p.PK[:])
157+
RandBytes(p.PK2[:])
158+
RandBytes(p.PK1Sig[:])
159+
RandBytes(p.PK2Sig[:])
160+
proofs = append(proofs, p)
161+
}
162+
163+
for _, proof := range proofs {
164+
require.True(t, roundtrip.Check(t, proof, toOTS, toProof), proof)
165+
}
166+
}
167+
143168
func BenchmarkOneTimeSigBatchVerification(b *testing.B) {
144169
for _, enabled := range []bool{false, true} {
145170
b.Run(fmt.Sprintf("batch=%v", enabled), func(b *testing.B) {

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

Lines changed: 123 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,31 @@ import (
2525
"github.com/algorand/go-algorand/config"
2626
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
2727
"github.com/algorand/go-algorand/data/basics"
28+
"github.com/algorand/go-algorand/data/basics/testing/roundtrip"
2829
ledgertesting "github.com/algorand/go-algorand/ledger/testing"
2930
"github.com/algorand/go-algorand/protocol"
3031
"github.com/algorand/go-algorand/test/partitiontest"
3132
)
3233

34+
// makeAccountConverters creates conversion functions for round-trip testing between
35+
// basics.AccountData and model.Account.
36+
func makeAccountConverters(t *testing.T, addrStr string, round basics.Round, proto *config.ConsensusParams, withoutRewards basics.MicroAlgos) (
37+
toModel func(basics.AccountData) model.Account,
38+
toBasics func(model.Account) basics.AccountData,
39+
) {
40+
toModel = func(ad basics.AccountData) model.Account {
41+
converted, err := AccountDataToAccount(addrStr, &ad, round, proto, withoutRewards)
42+
require.NoError(t, err)
43+
return converted
44+
}
45+
toBasics = func(acc model.Account) basics.AccountData {
46+
converted, err := AccountToAccountData(&acc)
47+
require.NoError(t, err)
48+
return converted
49+
}
50+
return toModel, toBasics
51+
}
52+
3353
func TestAccount(t *testing.T) {
3454
partitiontest.PartitionTest(t)
3555
t.Parallel()
@@ -106,8 +126,9 @@ func TestAccount(t *testing.T) {
106126
b := a.WithUpdatedRewards(proto.RewardUnit, 100)
107127

108128
addr := basics.Address{}.String()
109-
conv, err := AccountDataToAccount(addr, &b, round, &proto, a.MicroAlgos)
110-
require.NoError(t, err)
129+
toModel, toBasics := makeAccountConverters(t, addr, round, &proto, a.MicroAlgos)
130+
131+
conv := toModel(b)
111132
require.Equal(t, addr, conv.Address)
112133
require.Equal(t, b.MicroAlgos.Raw, conv.Amount)
113134
require.Equal(t, a.MicroAlgos.Raw, conv.AmountWithoutPendingRewards)
@@ -145,6 +166,21 @@ func TestAccount(t *testing.T) {
145166
verifyCreatedApp(0, appIdx1, appParams1)
146167
verifyCreatedApp(1, appIdx2, appParams2)
147168

169+
appRoundTrip := func(idx basics.AppIndex, params basics.AppParams) {
170+
require.True(t, roundtrip.Check(t, params,
171+
func(ap basics.AppParams) model.Application {
172+
return AppParamsToApplication(addr, idx, &ap)
173+
},
174+
func(app model.Application) basics.AppParams {
175+
converted, err := ApplicationParamsToAppParams(&app.Params)
176+
require.NoError(t, err)
177+
return converted
178+
}))
179+
}
180+
181+
appRoundTrip(appIdx1, appParams1)
182+
appRoundTrip(appIdx2, appParams2)
183+
148184
makeTKV := func(k string, v interface{}) model.TealKeyValue {
149185
value := model.TealValue{}
150186
switch v.(type) {
@@ -198,9 +234,7 @@ func TestAccount(t *testing.T) {
198234
verifyCreatedAsset(0, assetIdx1, assetParams1)
199235
verifyCreatedAsset(1, assetIdx2, assetParams2)
200236

201-
c, err := AccountToAccountData(&conv)
202-
require.NoError(t, err)
203-
require.Equal(t, b, c)
237+
require.True(t, roundtrip.Check(t, b, toModel, toBasics))
204238

205239
t.Run("IsDeterministic", func(t *testing.T) {
206240
// convert the same account a few more times to make sure we always
@@ -223,11 +257,91 @@ func TestAccountRandomRoundTrip(t *testing.T) {
223257
for addr, acct := range accts {
224258
round := basics.Round(2)
225259
proto := config.Consensus[protocol.ConsensusFuture]
226-
conv, err := AccountDataToAccount(addr.String(), &acct, round, &proto, acct.MicroAlgos)
227-
require.NoError(t, err)
228-
c, err := AccountToAccountData(&conv)
260+
toModel, toBasics := makeAccountConverters(t, addr.String(), round, &proto, acct.MicroAlgos)
261+
// AccountData has constraints (Status field must be valid), and this test
262+
// already uses RandomAccounts to generate valid random accounts
263+
require.True(t, roundtrip.Check(t, acct, toModel, toBasics, roundtrip.NoRandomCases()))
264+
}
265+
}
266+
}
267+
268+
func TestConvertTealKeyValueRoundTrip(t *testing.T) {
269+
partitiontest.PartitionTest(t)
270+
t.Parallel()
271+
272+
t.Run("nil input", func(t *testing.T) {
273+
require.Nil(t, convertTKVToGenerated(nil))
274+
result, err := convertGeneratedTKV(nil)
275+
require.NoError(t, err)
276+
require.Nil(t, result)
277+
})
278+
279+
t.Run("empty map treated as nil", func(t *testing.T) {
280+
empty := basics.TealKeyValue{}
281+
require.Nil(t, convertTKVToGenerated(&empty))
282+
result, err := convertGeneratedTKV(convertTKVToGenerated(&empty))
283+
require.NoError(t, err)
284+
require.Nil(t, result)
285+
})
286+
287+
t.Run("round-trip non-empty map", func(t *testing.T) {
288+
kv := basics.TealKeyValue{
289+
"alpha": {Type: basics.TealUintType, Uint: 17},
290+
"beta": {Type: basics.TealBytesType, Bytes: "\x00\x01binary"},
291+
}
292+
293+
toGenerated := func(val basics.TealKeyValue) *model.TealKeyValueStore {
294+
return convertTKVToGenerated(&val)
295+
}
296+
toBasics := func(store *model.TealKeyValueStore) basics.TealKeyValue {
297+
converted, err := convertGeneratedTKV(store)
229298
require.NoError(t, err)
230-
require.Equal(t, acct, c)
299+
return converted
231300
}
301+
302+
require.True(t, roundtrip.Check(t, kv, toGenerated, toBasics))
303+
})
304+
}
305+
306+
func TestAppLocalStateRoundTrip(t *testing.T) {
307+
partitiontest.PartitionTest(t)
308+
t.Parallel()
309+
310+
appIdx := basics.AppIndex(42)
311+
cases := map[string]basics.AppLocalState{
312+
"empty kv": {
313+
Schema: basics.StateSchema{NumUint: 1, NumByteSlice: 0},
314+
KeyValue: nil,
315+
},
316+
"mixed kv": {
317+
Schema: basics.StateSchema{NumUint: 2, NumByteSlice: 3},
318+
KeyValue: basics.TealKeyValue{
319+
"counter": {Type: basics.TealUintType, Uint: 99},
320+
"note": {Type: basics.TealBytesType, Bytes: "hello world"},
321+
},
322+
},
323+
}
324+
325+
for name, state := range cases {
326+
state := state
327+
t.Run(name, func(t *testing.T) {
328+
modelState := AppLocalState(state, appIdx)
329+
modelStates := []model.ApplicationLocalState{modelState}
330+
331+
acc := model.Account{
332+
Status: basics.Offline.String(),
333+
Amount: 0,
334+
AppsLocalState: &modelStates,
335+
}
336+
337+
ad, err := AccountToAccountData(&acc)
338+
require.NoError(t, err)
339+
340+
require.NotNil(t, ad.AppLocalStates)
341+
got, ok := ad.AppLocalStates[appIdx]
342+
require.True(t, ok)
343+
require.Equal(t, state.Schema, got.Schema)
344+
require.Equal(t, state.KeyValue, got.KeyValue)
345+
})
232346
}
233347
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/algorand/go-algorand/crypto"
3333
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
3434
"github.com/algorand/go-algorand/data/basics"
35+
"github.com/algorand/go-algorand/data/basics/testing/roundtrip"
3536
"github.com/algorand/go-algorand/data/transactions"
3637
"github.com/algorand/go-algorand/data/transactions/logic"
3738
"github.com/algorand/go-algorand/data/txntest"
@@ -1087,6 +1088,30 @@ func TestStateDeltaToStateDelta(t *testing.T) {
10871088
gsd := globalDeltaToStateDelta(sd)
10881089
require.Equal(t, 3, len(gsd))
10891090

1091+
decode := func(msd model.StateDelta) basics.StateDelta {
1092+
if len(msd) == 0 {
1093+
return nil
1094+
}
1095+
result := make(basics.StateDelta, len(msd))
1096+
for _, kv := range msd {
1097+
keyBytes, err := base64.StdEncoding.DecodeString(kv.Key)
1098+
require.NoError(t, err)
1099+
vd := basics.ValueDelta{Action: basics.DeltaAction(kv.Value.Action)}
1100+
if kv.Value.Bytes != nil {
1101+
decoded, err := base64.StdEncoding.DecodeString(*kv.Value.Bytes)
1102+
require.NoError(t, err)
1103+
vd.Bytes = string(decoded)
1104+
}
1105+
if kv.Value.Uint != nil {
1106+
vd.Uint = *kv.Value.Uint
1107+
}
1108+
result[string(keyBytes)] = vd
1109+
}
1110+
return result
1111+
}
1112+
1113+
require.True(t, roundtrip.Check(t, sd, globalDeltaToStateDelta, decode))
1114+
10901115
var keys []string
10911116
// test with a loop because sd is a map and iteration order is random
10921117
for _, item := range gsd {

data/basics/teal_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,37 @@ import (
2020
"testing"
2121

2222
"github.com/stretchr/testify/require"
23+
"pgregory.net/rapid"
2324

25+
"github.com/algorand/go-algorand/data/basics/testing/roundtrip"
2426
"github.com/algorand/go-algorand/test/partitiontest"
2527
)
2628

29+
// genTealValue generates a valid TealValue with proper Type/field correspondence.
30+
func genTealValue() *rapid.Generator[TealValue] {
31+
return rapid.Custom(func(t *rapid.T) TealValue {
32+
tealType := rapid.OneOf(rapid.Just(TealUintType), rapid.Just(TealBytesType)).Draw(t, "type")
33+
34+
if tealType == TealUintType {
35+
return TealValue{Type: TealUintType, Uint: rapid.Uint64().Draw(t, "uint")}
36+
}
37+
return TealValue{Type: TealBytesType, Bytes: rapid.String().Draw(t, "bytes")}
38+
})
39+
}
40+
41+
// genValueDelta generates a valid ValueDelta with proper Action/field correspondence.
42+
// Note: DeleteAction is excluded as it doesn't round-trip to TealValue.
43+
func genValueDelta() *rapid.Generator[ValueDelta] {
44+
return rapid.Custom(func(t *rapid.T) ValueDelta {
45+
action := rapid.OneOf(rapid.Just(SetUintAction), rapid.Just(SetBytesAction)).Draw(t, "action")
46+
47+
if action == SetUintAction {
48+
return ValueDelta{Action: SetUintAction, Uint: rapid.Uint64().Draw(t, "uint")}
49+
}
50+
return ValueDelta{Action: SetBytesAction, Bytes: rapid.String().Draw(t, "bytes")}
51+
})
52+
}
53+
2754
func TestStateDeltaEqual(t *testing.T) {
2855
partitiontest.PartitionTest(t)
2956

@@ -60,3 +87,45 @@ func TestStateDeltaEqual(t *testing.T) {
6087
d2 = StateDelta{"test": {Action: SetBytesAction, Bytes: "val1"}}
6188
a.False(d1.Equal(d2))
6289
}
90+
91+
func TestTealValueRoundTrip(t *testing.T) {
92+
partitiontest.PartitionTest(t)
93+
94+
// Test with a simple example value
95+
example := TealValue{Type: TealUintType, Uint: 17}
96+
97+
// Use roundtrip.Check with WithRapid for property-based testing
98+
require.True(t, roundtrip.Check(t, example,
99+
func(tv TealValue) ValueDelta { return tv.ToValueDelta() },
100+
func(vd ValueDelta) TealValue {
101+
tv, ok := vd.ToTealValue()
102+
require.True(t, ok)
103+
return tv
104+
},
105+
roundtrip.WithRapid(genTealValue())))
106+
}
107+
108+
func TestValueDeltaRoundTrip(t *testing.T) {
109+
partitiontest.PartitionTest(t)
110+
111+
// Test with a simple example value
112+
example := ValueDelta{Action: SetUintAction, Uint: 42}
113+
114+
// Use roundtrip.Check with WithRapid for property-based testing
115+
require.True(t, roundtrip.Check(t, example,
116+
func(vd ValueDelta) TealValue {
117+
tv, ok := vd.ToTealValue()
118+
require.True(t, ok)
119+
return tv
120+
},
121+
func(tv TealValue) ValueDelta { return tv.ToValueDelta() },
122+
roundtrip.WithRapid(genValueDelta())))
123+
}
124+
125+
func TestValueDeltaDeleteDoesNotRoundTrip(t *testing.T) {
126+
partitiontest.PartitionTest(t)
127+
128+
vd := ValueDelta{Action: DeleteAction}
129+
_, ok := vd.ToTealValue()
130+
require.False(t, ok)
131+
}

ledger/store/trackerdb/data_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/algorand/go-algorand/crypto"
2828
"github.com/algorand/go-algorand/data/basics"
2929
basics_testing "github.com/algorand/go-algorand/data/basics/testing"
30+
"github.com/algorand/go-algorand/data/basics/testing/roundtrip"
3031
"github.com/algorand/go-algorand/ledger/ledgercore"
3132
ledgertesting "github.com/algorand/go-algorand/ledger/testing"
3233
"github.com/algorand/go-algorand/protocol"
@@ -1292,7 +1293,7 @@ func TestCopyFunctions(t *testing.T) {
12921293
return rd.GetAssetParams()
12931294
}
12941295
for _, nz := range basics_testing.NearZeros(t, basics.AssetParams{}) {
1295-
assert.True(t, basics_testing.RoundTrip(t, nz, assetToRD, rdToAsset), nz)
1296+
assert.True(t, roundtrip.Check(t, nz, assetToRD, rdToAsset), nz)
12961297
}
12971298

12981299
// Asset holdings are copied into and out of ResourceData losslessly
@@ -1305,7 +1306,7 @@ func TestCopyFunctions(t *testing.T) {
13051306
return rd.GetAssetHolding()
13061307
}
13071308
for _, nz := range basics_testing.NearZeros(t, basics.AssetHolding{}) {
1308-
assert.True(t, basics_testing.RoundTrip(t, nz, holdingToRD, rdToHolding), nz)
1309+
assert.True(t, roundtrip.Check(t, nz, holdingToRD, rdToHolding), nz)
13091310
}
13101311

13111312
// AppParams are copied into and out of ResourceData losslessly
@@ -1318,7 +1319,7 @@ func TestCopyFunctions(t *testing.T) {
13181319
return rd.GetAppParams()
13191320
}
13201321
for _, nz := range basics_testing.NearZeros(t, basics.AppParams{}) {
1321-
assert.True(t, basics_testing.RoundTrip(t, nz, apToRD, rdToAP), nz)
1322+
assert.True(t, roundtrip.Check(t, nz, apToRD, rdToAP), nz)
13221323
}
13231324

13241325
// AppLocalStates are copied into and out of ResourceData losslessly
@@ -1331,7 +1332,7 @@ func TestCopyFunctions(t *testing.T) {
13311332
return rd.GetAppLocalState()
13321333
}
13331334
for _, nz := range basics_testing.NearZeros(t, basics.AppLocalState{}) {
1334-
assert.True(t, basics_testing.RoundTrip(t, nz, localsToRD, rdToLocals), nz)
1335+
assert.True(t, roundtrip.Check(t, nz, localsToRD, rdToLocals), nz)
13351336
}
13361337

13371338
}

0 commit comments

Comments
 (0)