Skip to content

Commit 8b0ac77

Browse files
committed
WIP: Commitments storage added to state. Basic functions implemented.
Commitments serialization implemented and tested. Checks of CommitToGeneration transaction against storage of commitments added to transaction checker. Tests on new checks implemented. New setting MaxGenerators added to networks configurations. StageNet settings updated.
1 parent 9619261 commit 8b0ac77

File tree

11 files changed

+385
-7
lines changed

11 files changed

+385
-7
lines changed

pkg/settings/blockchain_settings.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ type FunctionalitySettings struct {
106106
LightNodeBlockFieldsAbsenceInterval uint64 `json:"light_node_block_fields_absence_interval"`
107107

108108
GenerationPeriod uint64 `json:"generation_period"`
109+
MaxGenerators int `json:"max_generators"`
109110
}
110111

111112
func (f *FunctionalitySettings) VotesForFeatureElection(height uint64) uint64 {

pkg/settings/embedded/mainnet.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"min_update_asset_info_interval": 100000,
4848
"light_node_block_fields_absence_interval": 1000,
4949
"generation_period": 10000,
50+
"max_generators": 128,
5051
"type": 0,
5152
"genesis": {
5253
"version": 1,

pkg/settings/embedded/stagenet.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@
6060
"block_reward_boost_period": 1000,
6161
"min_update_asset_info_interval": 10,
6262
"light_node_block_fields_absence_interval": 1000,
63-
"generation_period": 100,
63+
"generation_period": 1000,
64+
"max_generators": 32,
6465
"type": 2,
6566
"genesis": {
6667
"version": 1,

pkg/settings/embedded/testnet.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"min_update_asset_info_interval": 100000,
4848
"light_node_block_fields_absence_interval": 1000,
4949
"generation_period": 3000,
50+
"max_generators": 64,
5051
"type": 1,
5152
"genesis": {
5253
"version": 1,

pkg/state/commitments.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package state
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
8+
"github.com/fxamacker/cbor/v2"
9+
10+
"github.com/wavesplatform/gowaves/pkg/crypto"
11+
"github.com/wavesplatform/gowaves/pkg/crypto/bls"
12+
"github.com/wavesplatform/gowaves/pkg/keyvalue"
13+
"github.com/wavesplatform/gowaves/pkg/proto"
14+
)
15+
16+
// commitmentItem represents a single commitment made by a block generator.
17+
// It links the generator's Waves public key to its corresponding BLS endorser public key.
18+
type commitmentItem struct {
19+
GeneratorPK crypto.PublicKey `cbor:"0,keyasint,omitempty"`
20+
EndorserPK bls.PublicKey `cbor:"1,keyasint,omitempty"`
21+
}
22+
23+
// commitmentsRecord holds all generator commitments for a specific generation period.
24+
type commitmentsRecord struct {
25+
Commitments []commitmentItem `cbor:"0,keyasint,omitempty"`
26+
}
27+
28+
func (cr *commitmentsRecord) append(generatorPK crypto.PublicKey, endorserPK bls.PublicKey) {
29+
cr.Commitments = append(cr.Commitments, commitmentItem{
30+
GeneratorPK: generatorPK,
31+
EndorserPK: endorserPK,
32+
})
33+
}
34+
func (cr *commitmentsRecord) marshalBinary() ([]byte, error) { return cbor.Marshal(cr) }
35+
36+
func (cr *commitmentsRecord) unmarshalBinary(data []byte) error { return cbor.Unmarshal(data, cr) }
37+
38+
// commitments manages the storage and retrieval of generator commitments.
39+
type commitments struct {
40+
db keyvalue.IterableKeyVal
41+
dbBatch keyvalue.Batch
42+
hs *historyStorage
43+
44+
scheme proto.Scheme
45+
calculateHashes bool
46+
hasher *stateHasher
47+
}
48+
49+
func newCommitments(hs *historyStorage, scheme proto.Scheme, calcHashes bool) *commitments {
50+
return &commitments{
51+
db: hs.db,
52+
dbBatch: hs.dbBatch,
53+
hs: hs,
54+
scheme: scheme,
55+
calculateHashes: calcHashes,
56+
hasher: newStateHasher(),
57+
}
58+
}
59+
60+
func (c *commitments) store(
61+
periodStart uint32, generatorPK crypto.PublicKey, endorserPK bls.PublicKey, blockID proto.BlockID,
62+
) error {
63+
key := commitmentKey{periodStart: periodStart}
64+
data, err := c.hs.newestTopEntryData(key.bytes())
65+
if err != nil && !errors.Is(err, keyvalue.ErrNotFound) {
66+
return fmt.Errorf("failed to retrieve commitments record: %w", err)
67+
}
68+
var rec commitmentsRecord
69+
if data != nil {
70+
if umErr := rec.unmarshalBinary(data); umErr != nil {
71+
return fmt.Errorf("failed to unmarshal commitments record: %w", umErr)
72+
}
73+
}
74+
rec.append(generatorPK, endorserPK)
75+
newData, mErr := rec.marshalBinary()
76+
if mErr != nil {
77+
return fmt.Errorf("failed to marshal commitments record: %w", mErr)
78+
}
79+
if addErr := c.hs.addNewEntry(commitment, key.bytes(), newData, blockID); addErr != nil {
80+
return fmt.Errorf("failed to add commitment record: %w", addErr)
81+
}
82+
return nil
83+
}
84+
85+
// exists checks if a commitment exists for the given period start and generator public key.
86+
func (c *commitments) exists(periodStart uint32, generatorPK crypto.PublicKey) (bool, error) {
87+
key := commitmentKey{periodStart: periodStart}
88+
data, err := c.hs.newestTopEntryData(key.bytes())
89+
if err != nil {
90+
if errors.Is(err, keyvalue.ErrNotFound) {
91+
return false, nil
92+
}
93+
return false, fmt.Errorf("failed to retrieve commitment record: %w", err)
94+
}
95+
var rec commitmentsRecord
96+
if umErr := rec.unmarshalBinary(data); umErr != nil {
97+
return false, fmt.Errorf("failed to unmarshal commitment record: %w", umErr)
98+
}
99+
pkb := generatorPK.Bytes()
100+
for _, cm := range rec.Commitments {
101+
if bytes.Equal(pkb, cm.GeneratorPK.Bytes()) {
102+
return true, nil
103+
}
104+
}
105+
return false, nil
106+
}
107+
108+
// size returns the number of commitments for the given period start.
109+
func (c *commitments) size(periodStart uint32) (int, error) {
110+
key := commitmentKey{periodStart: periodStart}
111+
data, err := c.hs.newestTopEntryData(key.bytes())
112+
if err != nil {
113+
if errors.Is(err, keyvalue.ErrNotFound) {
114+
return 0, nil
115+
}
116+
return 0, fmt.Errorf("failed to retrieve commitment record: %w", err)
117+
}
118+
var rec commitmentsRecord
119+
if umErr := rec.unmarshalBinary(data); umErr != nil {
120+
return 0, fmt.Errorf("failed to unmarshal commitment record: %w", umErr)
121+
}
122+
return len(rec.Commitments), nil
123+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package state
2+
3+
import (
4+
"crypto/rand"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/wavesplatform/gowaves/pkg/crypto"
12+
"github.com/wavesplatform/gowaves/pkg/crypto/bls"
13+
"github.com/wavesplatform/gowaves/pkg/proto"
14+
)
15+
16+
func TestCommitmentsRecordRoundTrip(t *testing.T) {
17+
for i, test := range []int{
18+
1, 8, 16, 32, 64, 128, 256, 512, 1024,
19+
} {
20+
t.Run(fmt.Sprintf("%d", i+1), func(t *testing.T) {
21+
rec := commitmentsRecord{Commitments: generateCommitments(t, test)}
22+
23+
data, err := rec.marshalBinary()
24+
require.NoError(t, err)
25+
assert.NotNil(t, data)
26+
27+
var decoded commitmentsRecord
28+
err = decoded.unmarshalBinary(data)
29+
require.NoError(t, err)
30+
assert.Equal(t, rec, decoded)
31+
for i, cm := range rec.Commitments {
32+
assert.Equal(t, cm.GeneratorPK, decoded.Commitments[i].GeneratorPK)
33+
assert.Equal(t, cm.EndorserPK, decoded.Commitments[i].EndorserPK)
34+
}
35+
})
36+
}
37+
}
38+
39+
func BenchmarkCommitmentsRecordMarshalling(b *testing.B) {
40+
for _, n := range []int{
41+
1, 8, 16, 32, 64, 128, 256, 512, 1024,
42+
} {
43+
b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
44+
rec := commitmentsRecord{Commitments: generateCommitments(b, n)}
45+
b.ResetTimer()
46+
for i := 0; i < b.N; i++ {
47+
_, err := rec.marshalBinary()
48+
if err != nil {
49+
b.Fatal(err)
50+
}
51+
}
52+
})
53+
}
54+
}
55+
56+
func BenchmarkCommitmentsRecordUnmarshalling(b *testing.B) {
57+
for _, n := range []int{
58+
1, 8, 16, 32, 64, 128, 256, 512, 1024,
59+
} {
60+
b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
61+
rec := commitmentsRecord{Commitments: generateCommitments(b, n)}
62+
data, err := rec.marshalBinary()
63+
if err != nil {
64+
b.Fatal(err)
65+
}
66+
b.ResetTimer()
67+
for i := 0; i < b.N; i++ {
68+
var decoded commitmentsRecord
69+
err = decoded.unmarshalBinary(data)
70+
if err != nil {
71+
b.Fatal(err)
72+
}
73+
}
74+
})
75+
}
76+
}
77+
78+
func TestCommitments_Exists(t *testing.T) {
79+
for i, test := range []struct {
80+
periodStart uint32
81+
n int
82+
}{
83+
{periodStart: 1_000_000, n: 1},
84+
{periodStart: 2_000_000, n: 32},
85+
{periodStart: 3_000_000, n: 64},
86+
{periodStart: 4_000_000, n: 128},
87+
} {
88+
t.Run(fmt.Sprintf("%d", i+1), func(t *testing.T) {
89+
to := createStorageObjects(t, true)
90+
cms := generateCommitments(t, test.n+1)
91+
for j := range test.n {
92+
blockID := generateRandomBlockID(t)
93+
to.addBlock(t, blockID)
94+
err := to.entities.commitments.store(test.periodStart, cms[j].GeneratorPK, cms[j].EndorserPK, blockID)
95+
require.NoError(t, err)
96+
to.flush(t)
97+
98+
// Check that all added commitments exist.
99+
for k := range j {
100+
ok, eErr := to.entities.commitments.exists(test.periodStart, cms[k].GeneratorPK)
101+
require.NoError(t, eErr)
102+
assert.True(t, ok)
103+
}
104+
105+
// Check that non-existing commitment does not exist.
106+
ok, err := to.entities.commitments.exists(test.periodStart, cms[test.n].GeneratorPK)
107+
require.NoError(t, err)
108+
assert.False(t, ok)
109+
}
110+
})
111+
}
112+
}
113+
114+
func TestCommitments_Size(t *testing.T) {
115+
for i, test := range []struct {
116+
periodStart uint32
117+
n int
118+
}{
119+
{periodStart: 1_000_000, n: 1},
120+
{periodStart: 2_000_000, n: 32},
121+
{periodStart: 3_000_000, n: 64},
122+
{periodStart: 4_000_000, n: 128},
123+
} {
124+
t.Run(fmt.Sprintf("%d", i+1), func(t *testing.T) {
125+
to := createStorageObjects(t, true)
126+
cms := generateCommitments(t, test.n+1)
127+
for j := range test.n {
128+
blockID := generateRandomBlockID(t)
129+
to.addBlock(t, blockID)
130+
err := to.entities.commitments.store(test.periodStart, cms[j].GeneratorPK, cms[j].EndorserPK, blockID)
131+
require.NoError(t, err)
132+
to.flush(t)
133+
134+
s, err := to.entities.commitments.size(test.periodStart)
135+
require.NoError(t, err)
136+
assert.Equal(t, j+1, s)
137+
}
138+
})
139+
}
140+
}
141+
142+
func generateCommitments(t testing.TB, n int) []commitmentItem {
143+
r := make([]commitmentItem, n)
144+
for i := 0; i < n; i++ {
145+
_, wpk, err := crypto.GenerateKeyPair([]byte(fmt.Sprintf("WAVES_%d", i)))
146+
require.NoError(t, err)
147+
bsk, err := bls.GenerateSecretKey([]byte(fmt.Sprintf("BLS_%d", i)))
148+
require.NoError(t, err)
149+
bpk, err := bsk.PublicKey()
150+
require.NoError(t, err)
151+
r[i] = commitmentItem{
152+
GeneratorPK: wpk,
153+
EndorserPK: bpk,
154+
}
155+
}
156+
return r
157+
}
158+
159+
func generateRandomBlockID(t testing.TB) proto.BlockID {
160+
var sig crypto.Signature
161+
_, err := rand.Read(sig[:])
162+
require.NoError(t, err)
163+
return proto.NewBlockIDFromSignature(sig)
164+
}

pkg/state/history_storage.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const (
4545
snapshots
4646
patches
4747
challengedAddress
48+
commitment
4849
)
4950

5051
type blockchainEntityProperties struct {
@@ -212,6 +213,11 @@ var properties = map[blockchainEntity]blockchainEntityProperties{
212213
needToCut: true,
213214
fixedSize: false,
214215
},
216+
commitment: {
217+
needToFilter: true,
218+
needToCut: true,
219+
fixedSize: false,
220+
},
215221
}
216222

217223
type historyEntry struct {

pkg/state/keys.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,10 @@ const (
133133
snapshotsKeyPrefix
134134
// Blockchain patches storage.
135135
patchKeyPrefix
136-
136+
// Key to store and retrieve challenged addresses.
137137
challengedAddressKeyPrefix
138+
// Key to store and retrieve generator's commitments for a specific generation period.
139+
commitmentKeyPrefix
138140
)
139141

140142
var (
@@ -761,3 +763,14 @@ func (k *challengedAddressKey) bytes() []byte {
761763
copy(buf[1:], k.address[:])
762764
return buf
763765
}
766+
767+
type commitmentKey struct {
768+
periodStart uint32
769+
}
770+
771+
func (k *commitmentKey) bytes() []byte {
772+
buf := make([]byte, 1+uint32Size)
773+
buf[0] = commitmentKeyPrefix
774+
binary.BigEndian.PutUint32(buf[1:], k.periodStart)
775+
return buf
776+
}

pkg/state/state.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ type blockchainEntitiesStorage struct {
6868
hitSources *hitSources
6969
snapshots *snapshotsAtHeight
7070
patches *patchesStorage
71+
commitments *commitments
7172
calculateHashes bool
7273
}
7374

@@ -102,6 +103,7 @@ func newBlockchainEntitiesStorage(hs *historyStorage, sets *settings.BlockchainS
102103
newHitSources(hs),
103104
newSnapshotsAtHeight(hs, sets.AddressSchemeCharacter),
104105
newPatchesStorage(hs, sets.AddressSchemeCharacter),
106+
newCommitments(hs, sets.AddressSchemeCharacter, calcHashes),
105107
calcHashes,
106108
}, nil
107109
}

0 commit comments

Comments
 (0)