Skip to content

Commit 1fe8c81

Browse files
committed
scrutinizer: add custom big Int implementation
The JSON Marshaler for the standard type big.Int optputs an integer number instead of an string (such as "123456789") or an hexString (such as "0x123456). This behavior creates a problem when importing the JSON data to a different implementation (i.e Javascript) since it forces to read the field as an integer (instead of bigInt). To fix this we introduce a new custom big.Int implementation which Marshals always to a bigInt string. Note that this fix is widely used by projects such as go-ethereum (common/hexutil) Signed-off-by: p4u <[email protected]>
1 parent 11c9180 commit 1fe8c81

File tree

8 files changed

+144
-30
lines changed

8 files changed

+144
-30
lines changed

types/big.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package types
2+
3+
import (
4+
"fmt"
5+
"math/big"
6+
)
7+
8+
// BigInt is a big.Int wrapper which marshals JSON to a string representation of the big number
9+
type BigInt big.Int
10+
11+
func (i BigInt) MarshalText() ([]byte, error) {
12+
return []byte((*big.Int)(&i).String()), nil
13+
}
14+
15+
func (i *BigInt) UnmarshalText(data []byte) error {
16+
i2, ok := new(big.Int).SetString(string(data), 0)
17+
if !ok {
18+
return fmt.Errorf("wrong format for bigInt")
19+
}
20+
*i = (BigInt)(*i2)
21+
return nil
22+
}
23+
24+
func (i *BigInt) GobEncode() ([]byte, error) {
25+
return i.ToInt().GobEncode()
26+
}
27+
28+
func (i *BigInt) GobDecode(buf []byte) error {
29+
return i.ToInt().GobDecode(buf)
30+
}
31+
32+
// String returns the string representation of the big number
33+
func (i *BigInt) String() string {
34+
return (*big.Int)(i).String()
35+
}
36+
37+
// SetBytes interprets buf as big-endian unsigned integer
38+
func (i *BigInt) SetBytes(buf []byte) *BigInt {
39+
return (*BigInt)(i.ToInt().SetBytes(buf))
40+
}
41+
42+
// Bytes returns the bytes representation of the big number
43+
func (i *BigInt) Bytes() []byte {
44+
return (*big.Int)(i).Bytes()
45+
}
46+
47+
// ToInt converts b to a big.Int.
48+
func (i *BigInt) ToInt() *big.Int {
49+
return (*big.Int)(i)
50+
}
51+
52+
// Add sum x+y
53+
func (i *BigInt) Add(x *BigInt, y *BigInt) *BigInt {
54+
return (*BigInt)(i.ToInt().Add(x.ToInt(), y.ToInt()))
55+
}
56+
57+
// Mul multiplies x*y
58+
func (i *BigInt) Mul(x *BigInt, y *BigInt) *BigInt {
59+
return (*BigInt)(i.ToInt().Mul(x.ToInt(), y.ToInt()))
60+
}
61+
62+
// SetUint64 sets the value of x to the big number
63+
func (i *BigInt) SetUint64(x uint64) *BigInt {
64+
return (*BigInt)(i.ToInt().SetUint64(x))
65+
}

types/big_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package types
2+
3+
import (
4+
"encoding/json"
5+
"math/big"
6+
"testing"
7+
8+
qt "github.com/frankban/quicktest"
9+
)
10+
11+
func TestBigInt(t *testing.T) {
12+
// Test basic
13+
a := new(BigInt).SetUint64(100)
14+
b := new(BigInt).SetUint64(200)
15+
16+
a.Add(a, b)
17+
qt.Assert(t, a.String(), qt.DeepEquals, "300")
18+
19+
a.ToInt().Add(a.ToInt(), b.ToInt())
20+
qt.Assert(t, a.String(), qt.DeepEquals, "500")
21+
22+
// Test single text Marshaling
23+
j, err := a.MarshalText()
24+
qt.Assert(t, err, qt.IsNil)
25+
qt.Assert(t, string(j), qt.DeepEquals, "500")
26+
27+
c := new(BigInt)
28+
err = c.UnmarshalText([]byte("123"))
29+
qt.Assert(t, err, qt.IsNil)
30+
qt.Assert(t, c.String(), qt.DeepEquals, "123")
31+
32+
// Test text Marshaling inside a struct
33+
js := &jsonStructTest{
34+
Name: "first",
35+
BigInt: new(BigInt).SetUint64(12312312312312312312),
36+
}
37+
data, err := json.Marshal(js)
38+
qt.Assert(t, err, qt.IsNil)
39+
qt.Assert(t, string(data), qt.DeepEquals,
40+
`{"name":"first","number":"12312312312312312312"}`)
41+
42+
// Test bytes compatibility with standard library
43+
d := new(big.Int).SetInt64(456).Bytes()
44+
c.SetBytes(d)
45+
qt.Assert(t, c.String(), qt.DeepEquals, "456")
46+
}
47+
48+
type jsonStructTest struct {
49+
Name string `json:"name"`
50+
BigInt *BigInt `json:"number"`
51+
}

vochain/scrutinizer/indexertypes/results.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ const (
2020
// Results holds the final results and relevant process info for a vochain process
2121
type Results struct {
2222
ProcessID types.HexBytes `badgerholdKey:"ProcessID" json:"processId"`
23-
Votes [][]*big.Int `json:"votes"`
24-
Weight *big.Int `json:"weight"`
23+
Votes [][]*types.BigInt `json:"votes"`
24+
Weight *types.BigInt `json:"weight"`
2525
EnvelopeHeight uint64 `json:"envelopeHeight"`
2626
EnvelopeType *models.EnvelopeType `json:"envelopeType"`
2727
VoteOpts *models.ProcessVoteOptions `json:"voteOptions"`
@@ -147,7 +147,7 @@ func (r *Results) AddVote(voteValues []int, weight *big.Int, mutex *sync.Mutex)
147147
}
148148

149149
// Add the Election weight (tells how much voting power have already been processed)
150-
r.Weight.Add(r.Weight, weight)
150+
r.Weight.Add(r.Weight, (*types.BigInt)(weight))
151151
if len(r.Votes) == 0 {
152152
r.Votes = NewEmptyVotes(int(r.VoteOpts.MaxCount), int(r.VoteOpts.MaxValue)+1)
153153
}
@@ -174,31 +174,31 @@ func (r *Results) AddVote(voteValues []int, weight *big.Int, mutex *sync.Mutex)
174174
for q, value := range voteValues {
175175
r.Votes[q][0].Add(
176176
r.Votes[q][0],
177-
new(big.Int).Mul(
178-
new(big.Int).SetUint64(uint64(value)),
179-
weight),
177+
new(types.BigInt).Mul(
178+
new(types.BigInt).SetUint64(uint64(value)),
179+
(*types.BigInt)(weight)),
180180
)
181181
}
182182
} else {
183183
// For the other cases, we use the results matrix index weighted
184184
// as described in the Ballot Protocol.
185185
for q, opt := range voteValues {
186-
r.Votes[q][opt].Add(r.Votes[q][opt], weight)
186+
r.Votes[q][opt].Add(r.Votes[q][opt], (*types.BigInt)(weight))
187187
}
188188
}
189189
return nil
190190
}
191191

192192
// NewEmptyVotes creates a new results struct with the given number of questions and options
193-
func NewEmptyVotes(questions, options int) [][]*big.Int {
193+
func NewEmptyVotes(questions, options int) [][]*types.BigInt {
194194
if questions == 0 || options == 0 {
195195
return nil
196196
}
197-
results := [][]*big.Int{}
197+
results := [][]*types.BigInt{}
198198
for i := 0; i < questions; i++ {
199-
question := []*big.Int{}
199+
question := []*types.BigInt{}
200200
for j := 0; j < options; j++ {
201-
question = append(question, big.NewInt(0))
201+
question = append(question, new(types.BigInt).SetUint64(0))
202202
}
203203
results = append(results, question)
204204
}

vochain/scrutinizer/indexertypes/types.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package indexertypes
33
import (
44
"encoding/json"
55
"fmt"
6-
"math/big"
76
"reflect"
87
"strings"
98
"time"
@@ -81,7 +80,7 @@ type VoteReference struct {
8180
Nullifier types.HexBytes `badgerholdKey:"Nullifier"`
8281
ProcessID types.HexBytes `badgerholdIndex:"ProcessID"`
8382
Height uint32
84-
Weight *big.Int
83+
Weight *types.BigInt
8584
TxIndex int32
8685
CreationTime time.Time
8786
}

vochain/scrutinizer/process.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package scrutinizer
33
import (
44
"encoding/hex"
55
"fmt"
6-
"math/big"
76
"strings"
87
"time"
98

@@ -267,7 +266,7 @@ func (s *Scrutinizer) newEmptyProcess(pid []byte) error {
267266
ProcessID: pid,
268267
// MaxValue requires +1 since 0 is also an option
269268
Votes: indexertypes.NewEmptyVotes(int(options.MaxCount), int(options.MaxValue)+1),
270-
Weight: new(big.Int).SetUint64(0),
269+
Weight: new(types.BigInt).SetUint64(0),
271270
Signatures: []types.HexBytes{},
272271
VoteOpts: p.GetVoteOptions(),
273272
EnvelopeType: p.GetEnvelopeType(),
@@ -392,7 +391,7 @@ func (s *Scrutinizer) updateProcess(pid []byte) error {
392391
return fmt.Errorf("record isn't the correct type! Wanted Result, got %T", record)
393392
}
394393
// On cancelled process, remove all results except for envelope height, weight, pid
395-
results.Votes = [][]*big.Int{}
394+
results.Votes = [][]*types.BigInt{}
396395
results.EnvelopeType = &models.EnvelopeType{}
397396
results.VoteOpts = &models.ProcessVoteOptions{}
398397
results.Signatures = []types.HexBytes{}

vochain/scrutinizer/scrutinizer.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ func (s *Scrutinizer) AfterSyncBootstrap() {
257257
ProcessID: p,
258258
// MaxValue requires +1 since 0 is also an option
259259
Votes: indexertypes.NewEmptyVotes(int(options.MaxCount), int(options.MaxValue)+1),
260-
Weight: new(big.Int).SetUint64(0),
260+
Weight: new(types.BigInt).SetUint64(0),
261261
VoteOpts: options,
262262
EnvelopeType: process.GetEnvelopeType(),
263263
Signatures: []types.HexBytes{},
@@ -269,7 +269,7 @@ func (s *Scrutinizer) AfterSyncBootstrap() {
269269

270270
// Count the votes, add them to results (in memory, without any db transaction)
271271
results := &indexertypes.Results{
272-
Weight: new(big.Int).SetUint64(0),
272+
Weight: new(types.BigInt).SetUint64(0),
273273
VoteOpts: options,
274274
EnvelopeType: process.EnvelopeType,
275275
}
@@ -383,7 +383,7 @@ func (s *Scrutinizer) Commit(height uint32) error {
383383
// This is a temporary "results" for computing votes
384384
// of a single processId for the current block.
385385
results := &indexertypes.Results{
386-
Weight: new(big.Int).SetUint64(0),
386+
Weight: new(types.BigInt).SetUint64(0),
387387
VoteOpts: proc.VoteOpts,
388388
EnvelopeType: proc.Envelope,
389389
}
@@ -559,7 +559,7 @@ func (s *Scrutinizer) OnProcessResults(pid []byte, results *models.ProcessResult
559559
}
560560

561561
// GetFriendlyResults translates votes into a matrix of strings
562-
func GetFriendlyResults(votes [][]*big.Int) [][]string {
562+
func GetFriendlyResults(votes [][]*types.BigInt) [][]string {
563563
r := [][]string{}
564564
for i := range votes {
565565
r = append(r, []string{})

vochain/scrutinizer/scrutinizer_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ func TestResults(t *testing.T) {
534534
if qi > 3 {
535535
t.Fatalf("found more questions that expected")
536536
}
537-
value = result.Votes[q][qi]
537+
value = result.Votes[q][qi].ToInt()
538538
if qi != 1 && value.Cmp(v0) != 0 {
539539
t.Fatalf("result is not correct, %d is not 0 as expected", value.Uint64())
540540
}
@@ -592,7 +592,7 @@ func TestLiveResults(t *testing.T) {
592592
qt.Assert(t, err, qt.IsNil)
593593
r := &indexertypes.Results{
594594
Votes: indexertypes.NewEmptyVotes(3, 100),
595-
Weight: new(big.Int).SetUint64(0),
595+
Weight: new(types.BigInt).SetUint64(0),
596596
VoteOpts: &models.ProcessVoteOptions{MaxCount: 3, MaxValue: 100},
597597
EnvelopeType: &models.EnvelopeType{},
598598
}
@@ -623,7 +623,7 @@ func TestLiveResults(t *testing.T) {
623623
if qi > 100 {
624624
t.Fatalf("found more questions that expected")
625625
}
626-
value = result.Votes[q][qi]
626+
value = result.Votes[q][qi].ToInt()
627627
if qi == 0 && value.Cmp(v0) != 0 {
628628
t.Fatalf("result is not correct, %d is not 0 as expected", value.Uint64())
629629
}
@@ -733,7 +733,7 @@ var vote = func(v []int, sc *Scrutinizer, pid []byte, weight *big.Int) error {
733733
ProcessID: pid,
734734
Votes: indexertypes.NewEmptyVotes(
735735
int(proc.VoteOpts.MaxCount), int(proc.VoteOpts.MaxValue)+1),
736-
Weight: new(big.Int).SetUint64(0),
736+
Weight: new(types.BigInt).SetUint64(0),
737737
Signatures: []types.HexBytes{},
738738
VoteOpts: proc.VoteOpts,
739739
EnvelopeType: proc.Envelope,

vochain/scrutinizer/vote.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func (s *Scrutinizer) WalkEnvelopes(processId []byte, async bool,
111111
log.Errorf("transaction is not an Envelope")
112112
return
113113
}
114-
callback(envelope, txRef.Weight)
114+
callback(envelope, txRef.Weight.ToInt())
115115
}
116116
if async {
117117
go func() {
@@ -323,7 +323,7 @@ func (s *Scrutinizer) GetResultsWeight(processID []byte) (*big.Int, error) {
323323
if err := s.db.FindOne(results, badgerhold.Where(badgerhold.Key).Eq(processID)); err != nil {
324324
return nil, err
325325
}
326-
return results.Weight, nil
326+
return results.Weight.ToInt(), nil
327327
}
328328

329329
// unmarshalVote decodes the base64 payload to a VotePackage struct type.
@@ -377,7 +377,7 @@ func (s *Scrutinizer) addLiveVote(pid []byte, VotePackage []byte, weight *big.In
377377
}
378378
} else {
379379
// If encrypted, just add the weight
380-
results.Weight.Add(results.Weight, weight)
380+
results.Weight.Add(results.Weight, (*types.BigInt)(weight))
381381
results.EnvelopeHeight++
382382
}
383383
return nil
@@ -393,7 +393,7 @@ func (s *Scrutinizer) addVoteIndex(nullifier, pid []byte, blockHeight uint32,
393393
Nullifier: nullifier,
394394
ProcessID: pid,
395395
Height: blockHeight,
396-
Weight: new(big.Int).SetBytes(weight),
396+
Weight: new(types.BigInt).SetBytes(weight),
397397
TxIndex: txIndex,
398398
CreationTime: time.Now(),
399399
})
@@ -403,7 +403,7 @@ func (s *Scrutinizer) addVoteIndex(nullifier, pid []byte, blockHeight uint32,
403403
Nullifier: nullifier,
404404
ProcessID: pid,
405405
Height: blockHeight,
406-
Weight: new(big.Int).SetBytes(weight),
406+
Weight: new(types.BigInt).SetBytes(weight),
407407
TxIndex: txIndex,
408408
CreationTime: time.Now(),
409409
})
@@ -480,7 +480,7 @@ func (s *Scrutinizer) computeFinalResults(p *indexertypes.Process) (*indexertype
480480
results := &indexertypes.Results{
481481
Votes: indexertypes.NewEmptyVotes(int(p.VoteOpts.MaxCount), int(p.VoteOpts.MaxValue)+1),
482482
ProcessID: p.ID,
483-
Weight: new(big.Int).SetUint64(0),
483+
Weight: new(types.BigInt).SetUint64(0),
484484
Final: true,
485485
VoteOpts: p.VoteOpts,
486486
EnvelopeType: p.Envelope,

0 commit comments

Comments
 (0)