Skip to content

Commit d388bae

Browse files
committed
refactor(platformvm): move validator metadata DB writes into validatorState
- Add AddValidatorMetadata to register and mark new entries for writing - Track pending deletes in DeleteValidatorMetadata for batch processing - Remove direct DB writes from writeCurrentStakers
1 parent a3eb8e9 commit d388bae

File tree

3 files changed

+197
-23
lines changed

3 files changed

+197
-23
lines changed

vms/platformvm/state/metadata_validator.go

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ type validatorState interface {
8888
metadata *validatorMetadata,
8989
)
9090

91+
// AddValidatorMetadata registers a new validator's [metadata] for vdrID
92+
// on [subnetID] and marks it for writing to disk on the next call to
93+
// WriteValidatorMetadata.
94+
AddValidatorMetadata(
95+
vdrID ids.NodeID,
96+
subnetID ids.ID,
97+
metadata *validatorMetadata,
98+
)
99+
91100
// GetUptime returns the current uptime measurements of [vdrID] on
92101
// [subnetID].
93102
GetUptime(
@@ -130,16 +139,24 @@ type validatorState interface {
130139
// WriteValidatorMetadata writes all staged updates from prior calls to
131140
// SetUptime or SetDelegateeReward.
132141
WriteValidatorMetadata(
133-
dbPrimary database.KeyValueWriter,
134-
dbSubnet database.KeyValueWriter,
142+
dbPrimary database.KeyValueWriterDeleter,
143+
dbSubnet database.KeyValueWriterDeleter,
135144
codecVersion uint16,
136145
) error
137146
}
138147

148+
type pendingDelete struct {
149+
subnetID ids.ID
150+
txID ids.ID
151+
}
152+
139153
type metadata struct {
140154
metadata map[ids.NodeID]map[ids.ID]*validatorMetadata // vdrID -> subnetID -> metadata
141155
// updatedMetadata tracks the updates since WriteValidatorMetadata was last called
142156
updatedMetadata map[ids.NodeID]set.Set[ids.ID] // vdrID -> subnetIDs
157+
// pendingDeletes tracks entries to be deleted from DB on the next
158+
// call to WriteValidatorMetadata
159+
pendingDeletes []pendingDelete
143160
}
144161

145162
func newValidatorState() validatorState {
@@ -162,6 +179,15 @@ func (m *metadata) LoadValidatorMetadata(
162179
subnetMetadata[subnetID] = uptime
163180
}
164181

182+
func (m *metadata) AddValidatorMetadata(
183+
vdrID ids.NodeID,
184+
subnetID ids.ID,
185+
md *validatorMetadata,
186+
) {
187+
m.LoadValidatorMetadata(vdrID, subnetID, md)
188+
m.addUpdatedMetadata(vdrID, subnetID)
189+
}
190+
165191
func (m *metadata) GetUptime(
166192
vdrID ids.NodeID,
167193
subnetID ids.ID,
@@ -217,6 +243,13 @@ func (m *metadata) SetDelegateeReward(
217243
}
218244

219245
func (m *metadata) DeleteValidatorMetadata(vdrID ids.NodeID, subnetID ids.ID) {
246+
if md, exists := m.metadata[vdrID][subnetID]; exists {
247+
m.pendingDeletes = append(m.pendingDeletes, pendingDelete{
248+
subnetID: subnetID,
249+
txID: md.txID,
250+
})
251+
}
252+
220253
subnetMetadata := m.metadata[vdrID]
221254
delete(subnetMetadata, subnetID)
222255
if len(subnetMetadata) == 0 {
@@ -231,10 +264,22 @@ func (m *metadata) DeleteValidatorMetadata(vdrID ids.NodeID, subnetID ids.ID) {
231264
}
232265

233266
func (m *metadata) WriteValidatorMetadata(
234-
dbPrimary database.KeyValueWriter,
235-
dbSubnet database.KeyValueWriter,
267+
dbPrimary database.KeyValueWriterDeleter,
268+
dbSubnet database.KeyValueWriterDeleter,
236269
codecVersion uint16,
237270
) error {
271+
// Deletes must be processed before puts to avoid removing a newly added entry.
272+
for _, del := range m.pendingDeletes {
273+
db := dbSubnet
274+
if del.subnetID == constants.PrimaryNetworkID {
275+
db = dbPrimary
276+
}
277+
if err := db.Delete(del.txID[:]); err != nil {
278+
return err
279+
}
280+
}
281+
m.pendingDeletes = m.pendingDeletes[:0]
282+
238283
for vdrID, updatedSubnets := range m.updatedMetadata {
239284
for subnetID := range updatedSubnets {
240285
metadata := m.metadata[vdrID][subnetID]

vms/platformvm/state/metadata_validator_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/ava-labs/avalanchego/database"
1414
"github.com/ava-labs/avalanchego/database/memdb"
1515
"github.com/ava-labs/avalanchego/ids"
16+
"github.com/ava-labs/avalanchego/utils/constants"
1617
"github.com/ava-labs/avalanchego/utils/wrappers"
1718
)
1819

@@ -171,6 +172,143 @@ func TestValidatorDelegateeRewards(t *testing.T) {
171172
require.ErrorIs(err, database.ErrNotFound)
172173
}
173174

175+
func TestAddValidatorMetadataAndWrite(t *testing.T) {
176+
require := require.New(t)
177+
state := newValidatorState()
178+
179+
primaryDB := memdb.New()
180+
subnetDB := memdb.New()
181+
182+
primaryNodeID := ids.GenerateTestNodeID()
183+
primaryTxID := ids.GenerateTestID()
184+
state.AddValidatorMetadata(primaryNodeID, constants.PrimaryNetworkID, &validatorMetadata{
185+
txID: primaryTxID,
186+
PotentialReward: 100,
187+
})
188+
189+
subnetNodeID := ids.GenerateTestNodeID()
190+
subnetID := ids.GenerateTestID()
191+
subnetTxID := ids.GenerateTestID()
192+
state.AddValidatorMetadata(subnetNodeID, subnetID, &validatorMetadata{
193+
txID: subnetTxID,
194+
PotentialReward: 200,
195+
})
196+
197+
require.NoError(state.WriteValidatorMetadata(primaryDB, subnetDB, CodecVersion1))
198+
199+
// Primary validator written to primaryDB
200+
has, err := primaryDB.Has(primaryTxID[:])
201+
require.NoError(err)
202+
require.True(has)
203+
204+
// Subnet validator written to subnetDB
205+
has, err = subnetDB.Has(subnetTxID[:])
206+
require.NoError(err)
207+
require.True(has)
208+
209+
// Verify the written primary metadata can be parsed back correctly
210+
metadataBytes, err := primaryDB.Get(primaryTxID[:])
211+
require.NoError(err)
212+
parsedMd := &validatorMetadata{}
213+
require.NoError(parseValidatorMetadata(metadataBytes, parsedMd))
214+
require.Equal(uint64(100), parsedMd.PotentialReward)
215+
}
216+
217+
func TestDeleteValidatorMetadataAndWrite(t *testing.T) {
218+
require := require.New(t)
219+
state := newValidatorState()
220+
221+
primaryDB := memdb.New()
222+
subnetDB := memdb.New()
223+
224+
nodeID := ids.GenerateTestNodeID()
225+
txID := ids.GenerateTestID()
226+
state.AddValidatorMetadata(nodeID, constants.PrimaryNetworkID, &validatorMetadata{
227+
txID: txID,
228+
PotentialReward: 100,
229+
})
230+
require.NoError(state.WriteValidatorMetadata(primaryDB, subnetDB, CodecVersion1))
231+
232+
has, err := primaryDB.Has(txID[:])
233+
require.NoError(err)
234+
require.True(has)
235+
236+
state.DeleteValidatorMetadata(nodeID, constants.PrimaryNetworkID)
237+
require.NoError(state.WriteValidatorMetadata(primaryDB, subnetDB, CodecVersion1))
238+
239+
has, err = primaryDB.Has(txID[:])
240+
require.NoError(err)
241+
require.False(has)
242+
243+
_, _, err = state.GetUptime(nodeID, constants.PrimaryNetworkID)
244+
require.ErrorIs(err, database.ErrNotFound)
245+
}
246+
247+
func TestAddThenDeleteValidatorMetadataAndWrite(t *testing.T) {
248+
require := require.New(t)
249+
state := newValidatorState()
250+
251+
primaryDB := memdb.New()
252+
subnetDB := memdb.New()
253+
254+
nodeID := ids.GenerateTestNodeID()
255+
txID := ids.GenerateTestID()
256+
257+
state.AddValidatorMetadata(nodeID, constants.PrimaryNetworkID, &validatorMetadata{
258+
txID: txID,
259+
PotentialReward: 100,
260+
})
261+
state.DeleteValidatorMetadata(nodeID, constants.PrimaryNetworkID)
262+
require.NoError(state.WriteValidatorMetadata(primaryDB, subnetDB, CodecVersion1))
263+
264+
has, err := primaryDB.Has(txID[:])
265+
require.NoError(err)
266+
require.False(has)
267+
268+
_, _, err = state.GetUptime(nodeID, constants.PrimaryNetworkID)
269+
require.ErrorIs(err, database.ErrNotFound)
270+
}
271+
272+
func TestDeleteThenReAddValidatorMetadataAndWrite(t *testing.T) {
273+
require := require.New(t)
274+
state := newValidatorState()
275+
276+
primaryDB := memdb.New()
277+
subnetDB := memdb.New()
278+
279+
nodeID := ids.GenerateTestNodeID()
280+
oldTxID := ids.GenerateTestID()
281+
282+
// save the initial validator metadata so we can delete it later
283+
state.AddValidatorMetadata(nodeID, constants.PrimaryNetworkID, &validatorMetadata{
284+
txID: oldTxID,
285+
PotentialReward: 100,
286+
})
287+
require.NoError(state.WriteValidatorMetadata(primaryDB, subnetDB, CodecVersion1))
288+
289+
state.DeleteValidatorMetadata(nodeID, constants.PrimaryNetworkID)
290+
newTxID := ids.GenerateTestID()
291+
state.AddValidatorMetadata(nodeID, constants.PrimaryNetworkID, &validatorMetadata{
292+
txID: newTxID,
293+
PotentialReward: 200,
294+
})
295+
require.NoError(state.WriteValidatorMetadata(primaryDB, subnetDB, CodecVersion1))
296+
297+
has, err := primaryDB.Has(oldTxID[:])
298+
require.NoError(err)
299+
require.False(has)
300+
301+
has, err = primaryDB.Has(newTxID[:])
302+
require.NoError(err)
303+
require.True(has)
304+
305+
metadataBytes, err := primaryDB.Get(newTxID[:])
306+
require.NoError(err)
307+
parsedMd := &validatorMetadata{}
308+
require.NoError(parseValidatorMetadata(metadataBytes, parsedMd))
309+
require.Equal(uint64(200), parsedMd.PotentialReward)
310+
}
311+
174312
func TestParseValidatorMetadata(t *testing.T) {
175313
type test struct {
176314
name string

vms/platformvm/state/state.go

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2249,7 +2249,6 @@ func (s *state) write(updateValidators bool, height uint64) error {
22492249
s.writeValidatorDiffs(height),
22502250
s.writeCurrentStakers(codecVersion),
22512251
s.writePendingStakers(),
2252-
s.WriteValidatorMetadata(s.currentValidatorList, s.currentSubnetValidatorList, codecVersion), // Must be called after writeCurrentStakers
22532252
s.writeL1Validators(),
22542253
s.writeTXs(),
22552254
s.writeRewardUTXOs(),
@@ -2797,15 +2796,11 @@ func getOrSetDefault[K comparable, V any](m map[K]*V, k K) *V {
27972796

27982797
func (s *state) writeCurrentStakers(codecVersion uint16) error {
27992798
for subnetID, validatorDiffs := range s.currentStakers.validatorDiffs {
2800-
// Select db to write to
2801-
validatorDB := s.currentSubnetValidatorList
28022799
delegatorDB := s.currentSubnetDelegatorList
28032800
if subnetID == constants.PrimaryNetworkID {
2804-
validatorDB = s.currentValidatorList
28052801
delegatorDB = s.currentDelegatorList
28062802
}
28072803

2808-
// Record the change in weight and/or public key for each validator.
28092804
for nodeID, validatorDiff := range validatorDiffs {
28102805
switch validatorDiff.validatorStatus {
28112806
case added:
@@ -2827,21 +2822,8 @@ func (s *state) writeCurrentStakers(codecVersion uint16) error {
28272822
PotentialDelegateeReward: 0,
28282823
}
28292824

2830-
metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata)
2831-
if err != nil {
2832-
return fmt.Errorf("failed to serialize current validator: %w", err)
2833-
}
2834-
2835-
if err = validatorDB.Put(staker.TxID[:], metadataBytes); err != nil {
2836-
return fmt.Errorf("failed to write current validator to list: %w", err)
2837-
}
2838-
2839-
s.validatorState.LoadValidatorMetadata(nodeID, subnetID, metadata)
2825+
s.validatorState.AddValidatorMetadata(nodeID, subnetID, metadata)
28402826
case deleted:
2841-
if err := validatorDB.Delete(validatorDiff.validator.TxID[:]); err != nil {
2842-
return fmt.Errorf("failed to delete current staker: %w", err)
2843-
}
2844-
28452827
s.validatorState.DeleteValidatorMetadata(nodeID, subnetID)
28462828
}
28472829

@@ -2855,6 +2837,15 @@ func (s *state) writeCurrentStakers(codecVersion uint16) error {
28552837
}
28562838
}
28572839
}
2840+
2841+
if err := s.validatorState.WriteValidatorMetadata(
2842+
s.currentValidatorList,
2843+
s.currentSubnetValidatorList,
2844+
codecVersion,
2845+
); err != nil {
2846+
return err
2847+
}
2848+
28582849
maps.Clear(s.currentStakers.validatorDiffs)
28592850
return nil
28602851
}

0 commit comments

Comments
 (0)