From 8f2ba5c8d87098ac0308839faa5738e8af62d703 Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Fri, 28 Apr 2023 11:19:58 -0400 Subject: [PATCH 01/22] Add epoch data type. --- lib/block_view.go | 9 +++ lib/block_view_stake.go | 16 ++-- lib/block_view_types.go | 5 +- lib/db_utils.go | 6 +- lib/pos_epoch.go | 159 ++++++++++++++++++++++++++++++++++++++++ lib/pos_epoch_test.go | 75 +++++++++++++++++++ 6 files changed, 260 insertions(+), 10 deletions(-) create mode 100644 lib/pos_epoch.go create mode 100644 lib/pos_epoch_test.go diff --git a/lib/block_view.go b/lib/block_view.go index 2e1841849..92250eb0a 100644 --- a/lib/block_view.go +++ b/lib/block_view.go @@ -126,6 +126,9 @@ type UtxoView struct { // Locked stake mappings LockedStakeMapKeyToLockedStakeEntry map[LockedStakeMapKey]*LockedStakeEntry + // Current EpochEntry + CurrentEpochEntry *EpochEntry + // The hash of the tip the view is currently referencing. Mainly used // for error-checking when doing a bulk operation on the view. TipHash *BlockHash @@ -228,6 +231,9 @@ func (bav *UtxoView) _ResetViewMappingsAfterFlush() { // LockedStakeEntries bav.LockedStakeMapKeyToLockedStakeEntry = make(map[LockedStakeMapKey]*LockedStakeEntry) + + // CurrentEpochEntry + bav.CurrentEpochEntry = nil } func (bav *UtxoView) CopyUtxoView() (*UtxoView, error) { @@ -505,6 +511,9 @@ func (bav *UtxoView) CopyUtxoView() (*UtxoView, error) { newView.LockedStakeMapKeyToLockedStakeEntry[entryKey] = entry.Copy() } + // Copy the CurrentEpochEntry + newView.CurrentEpochEntry = bav.CurrentEpochEntry.Copy() + return newView, nil } diff --git a/lib/block_view_stake.go b/lib/block_view_stake.go index 737df7bfa..245debc2e 100644 --- a/lib/block_view_stake.go +++ b/lib/block_view_stake.go @@ -576,14 +576,14 @@ func DBGetStakeEntryWithTxn( if err == badger.ErrKeyNotFound { return nil, nil } - return nil, errors.Wrapf(err, "DBGetStakeByValidatorByStaker: problem retrieving StakeEntry: ") + return nil, errors.Wrapf(err, "DBGetStakeEntry: problem retrieving StakeEntry: ") } // Decode StakeEntry from bytes. rr := bytes.NewReader(stakeEntryBytes) stakeEntry, err := DecodeDeSoEncoder(&StakeEntry{}, rr) if err != nil { - return nil, errors.Wrapf(err, "DBGetStakeByValidatorByStaker: problem decoding StakeEntry: ") + return nil, errors.Wrapf(err, "DBGetStakeEntry: problem decoding StakeEntry: ") } return stakeEntry, nil } @@ -649,7 +649,7 @@ func DBGetLockedStakeEntryWithTxn( return nil, nil } return nil, errors.Wrapf( - err, "DBGetLockedStakeByValidatorByStakerByLockedAt: problem retrieving LockedStakeEntry: ", + err, "DBGetLockedStakeEntry: problem retrieving LockedStakeEntry: ", ) } @@ -658,7 +658,7 @@ func DBGetLockedStakeEntryWithTxn( lockedStakeEntry, err := DecodeDeSoEncoder(&LockedStakeEntry{}, rr) if err != nil { return nil, errors.Wrapf( - err, "DBGetLockedStakeByValidatorByStakerByLockedAt: problem decoding LockedStakeEntry: ", + err, "DBGetLockedStakeEntry: problem decoding LockedStakeEntry: ", ) } return lockedStakeEntry, nil @@ -755,7 +755,7 @@ func DBPutStakeEntryWithTxn( key := DBKeyForStakeByValidatorByStaker(stakeEntry) if err := DBSetWithTxn(txn, snap, key, EncodeToBytes(blockHeight, stakeEntry)); err != nil { return errors.Wrapf( - err, "DBPutStakeWithTxn: problem storing StakeEntry in index PrefixStakeByValidatorByStaker", + err, "DBPutStakeEntryWithTxn: problem storing StakeEntry in index PrefixStakeByValidatorByStaker: ", ) } @@ -776,7 +776,7 @@ func DBPutLockedStakeEntryWithTxn( key := DBKeyForLockedStakeByValidatorByStakerByLockedAt(lockedStakeEntry) if err := DBSetWithTxn(txn, snap, key, EncodeToBytes(blockHeight, lockedStakeEntry)); err != nil { return errors.Wrapf( - err, "DBPutLockedStakeWithTxn: problem storing LockedStakeEntry in index PrefixLockedStakeByValidatorByStakerByLockedAt", + err, "DBPutLockedStakeEntryWithTxn: problem storing LockedStakeEntry in index PrefixLockedStakeByValidatorByStakerByLockedAt: ", ) } @@ -797,7 +797,7 @@ func DBDeleteStakeEntryWithTxn( key := DBKeyForStakeByValidatorByStaker(stakeEntry) if err := DBDeleteWithTxn(txn, snap, key); err != nil { return errors.Wrapf( - err, "DBDeleteStakeWithTxn: problem deleting StakeEntry from index PrefixStakeByValidatorByStaker", + err, "DBDeleteStakeEntryWithTxn: problem deleting StakeEntry from index PrefixStakeByValidatorByStaker: ", ) } @@ -818,7 +818,7 @@ func DBDeleteLockedStakeEntryWithTxn( key := DBKeyForLockedStakeByValidatorByStakerByLockedAt(lockedStakeEntry) if err := DBDeleteWithTxn(txn, snap, key); err != nil { return errors.Wrapf( - err, "DBDeleteLockedStakeWithTxn: problem deleting StakeEntry from index PrefixLockedStakeByValidatorByStakerByLockedAt", + err, "DBDeleteLockedStakeEntryWithTxn: problem deleting StakeEntry from index PrefixLockedStakeByValidatorByStakerByLockedAt: ", ) } diff --git a/lib/block_view_types.go b/lib/block_view_types.go index 4c6e3fd2f..679d737ec 100644 --- a/lib/block_view_types.go +++ b/lib/block_view_types.go @@ -113,9 +113,10 @@ const ( EncoderTypeValidatorEntry EncoderType = 40 EncoderTypeStakeEntry EncoderType = 41 EncoderTypeLockedStakeEntry EncoderType = 42 + EncoderTypeEpochEntry EncoderType = 43 // EncoderTypeEndBlockView encoder type should be at the end and is used for automated tests. - EncoderTypeEndBlockView EncoderType = 43 + EncoderTypeEndBlockView EncoderType = 44 ) // Txindex encoder types. @@ -250,6 +251,8 @@ func (encoderType EncoderType) New() DeSoEncoder { return &StakeEntry{} case EncoderTypeLockedStakeEntry: return &LockedStakeEntry{} + case EncoderTypeEpochEntry: + return &EpochEntry{} } // Txindex encoder types diff --git a/lib/db_utils.go b/lib/db_utils.go index 1657ff25a..260a78be9 100644 --- a/lib/db_utils.go +++ b/lib/db_utils.go @@ -502,7 +502,11 @@ type DBPrefixes struct { // Prefix, ValidatorPKID, StakerPKID, LockedAtEpochNumber -> LockedStakeEntry PrefixLockedStakeByValidatorByStakerByLockedAt []byte `prefix_id:"[82]" is_state:"true"` - // NEXT_TAG: 83 + // PrefixCurrentEpoch: Retrieve the current EpochEntry. + // Prefix -> EpochEntry + PrefixCurrentEpoch []byte `prefix_id:"[83]" is_state:"true"` + + // NEXT_TAG: 84 } // StatePrefixToDeSoEncoder maps each state prefix to a DeSoEncoder type that is stored under that prefix. diff --git a/lib/pos_epoch.go b/lib/pos_epoch.go new file mode 100644 index 000000000..2e721dc82 --- /dev/null +++ b/lib/pos_epoch.go @@ -0,0 +1,159 @@ +package lib + +import ( + "bytes" + "github.com/dgraph-io/badger/v3" + "github.com/pkg/errors" +) + +// +// TYPE +// + +type EpochEntry struct { + EpochNumber uint64 + LastBlockHeightInEpoch uint64 +} + +func (epochEntry *EpochEntry) Copy() *EpochEntry { + return &EpochEntry{ + EpochNumber: epochEntry.EpochNumber, + LastBlockHeightInEpoch: epochEntry.LastBlockHeightInEpoch, + } +} + +func (epochEntry *EpochEntry) RawEncodeWithoutMetadata(blockHeight uint64, skipMetadata ...bool) []byte { + var data []byte + data = append(data, UintToBuf(epochEntry.EpochNumber)...) + data = append(data, UintToBuf(epochEntry.LastBlockHeightInEpoch)...) + return data +} + +func (epochEntry *EpochEntry) RawDecodeWithoutMetadata(blockHeight uint64, rr *bytes.Reader) error { + var err error + + // EpochNumber + epochEntry.EpochNumber, err = ReadUvarint(rr) + if err != nil { + return errors.Wrapf(err, "EpochEntry.Decode: Problem reading EpochNumber: ") + } + + // LastBlockHeightInEpoch + epochEntry.LastBlockHeightInEpoch, err = ReadUvarint(rr) + if err != nil { + return errors.Wrapf(err, "EpochEntry.Decode: Problem reading LastBlockHeightInEpoch: ") + } + + return err +} + +func (epochEntry *EpochEntry) GetVersionByte(blockHeight uint64) byte { + return 0 +} + +func (epochEntry *EpochEntry) GetEncoderType() EncoderType { + return EncoderTypeEpochEntry +} + +// +// UTXO VIEW UTILS +// + +func (bav *UtxoView) GetCurrentEpoch() (*EpochEntry, error) { + var epochEntry *EpochEntry + var err error + + // First, check the UtxoView. + epochEntry = bav.CurrentEpochEntry + if epochEntry != nil { + return epochEntry.Copy(), nil + } + + // If not found, check the database. + epochEntry, err = DBGetCurrentEpoch(bav.Handle, bav.Snapshot) + if err != nil { + return nil, errors.Wrapf(err, "UtxoView.GetCurrentEpoch: problem retrieving EpochEntry from db: ") + } + if epochEntry != nil { + // Cache in the UtxoView. + bav.CurrentEpochEntry = epochEntry.Copy() + } + return epochEntry, nil +} + +func (bav *UtxoView) SetCurrentEpoch(epochEntry *EpochEntry, blockHeight uint64) error { + // This function should only ever be called from the OnEpochEnd hook. + if epochEntry == nil { + return errors.New("UtxoView.SetCurrentEpoch: called with nil EpochEntry") + } + + // Set the current EpochEntry in the db. + if err := DBPutCurrentEpoch(bav.Handle, bav.Snapshot, epochEntry, blockHeight); err != nil { + return errors.Wrapf(err, "UtxoView.SetCurrentEpoch: problem setting EpochEntry in db: ") + } + + // Set the current EpochEntry in the UtxoView. + bav.CurrentEpochEntry = epochEntry.Copy() + return nil +} + +func (bav *UtxoView) DeleteCurrentEpoch() { + // This function should only ever be called from the OnEpochEnd hook. + bav.CurrentEpochEntry = nil +} + +// +// DB UTILS +// + +func DBKeyForCurrentEpoch() []byte { + return append([]byte{}, Prefixes.PrefixCurrentEpoch...) +} + +func DBGetCurrentEpoch(handle *badger.DB, snap *Snapshot) (*EpochEntry, error) { + var ret *EpochEntry + err := handle.View(func(txn *badger.Txn) error { + var innerErr error + ret, innerErr = DBGetCurrentEpochWithTxn(txn, snap) + return innerErr + }) + return ret, err +} + +func DBGetCurrentEpochWithTxn(txn *badger.Txn, snap *Snapshot) (*EpochEntry, error) { + // Retrieve StakeEntry from db. + key := DBKeyForCurrentEpoch() + epochEntryBytes, err := DBGetWithTxn(txn, snap, key) + if err != nil { + // We don't want to error if the key isn't found. Instead, return nil. + if err == badger.ErrKeyNotFound { + return nil, nil + } + return nil, errors.Wrapf(err, "DBGetCurrentEpoch: problem retrieving EpochEntry: ") + } + + // Decode EpochEntry from bytes. + rr := bytes.NewReader(epochEntryBytes) + epochEntry, err := DecodeDeSoEncoder(&EpochEntry{}, rr) + if err != nil { + return nil, errors.Wrapf(err, "DBGetCurrentEpoch: problem decoding EpochEntry: ") + } + return epochEntry, nil +} + +func DBPutCurrentEpoch(handle *badger.DB, snap *Snapshot, epochEntry *EpochEntry, blockHeight uint64) error { + return handle.Update(func(txn *badger.Txn) error { + return DBPutCurrentEpochWithTxn(txn, snap, epochEntry, blockHeight) + }) +} + +func DBPutCurrentEpochWithTxn(txn *badger.Txn, snap *Snapshot, epochEntry *EpochEntry, blockHeight uint64) error { + // Set EpochEntry in PrefixCurrentEpoch. + key := DBKeyForCurrentEpoch() + if err := DBSetWithTxn(txn, snap, key, EncodeToBytes(blockHeight, epochEntry)); err != nil { + return errors.Wrapf( + err, "DBPutCurrentEpoch: problem storing EpochEntry in index PrefixCurrentEpoch: ", + ) + } + return nil +} diff --git a/lib/pos_epoch_test.go b/lib/pos_epoch_test.go new file mode 100644 index 000000000..fd37a65e6 --- /dev/null +++ b/lib/pos_epoch_test.go @@ -0,0 +1,75 @@ +package lib + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestCurrentEpoch(t *testing.T) { + var epochEntry *EpochEntry + var err error + + // Initialize blockchain. + chain, params, db := NewLowDifficultyBlockchain(t) + blockHeight := uint64(chain.blockTip().Height) + 1 + utxoView, err := NewUtxoView(db, params, chain.postgres, chain.snapshot) + require.NoError(t, err) + + // Test that the CurrentEpoch is nil in the db. + epochEntry, err = DBGetCurrentEpoch(db, utxoView.Snapshot) + require.NoError(t, err) + require.Nil(t, epochEntry) + + // Test that the CurrentEpoch is nil in the UtxoView. + require.Nil(t, utxoView.CurrentEpochEntry) + + // Test GetCurrentEpoch(). + epochEntry, err = utxoView.GetCurrentEpoch() + require.NoError(t, err) + require.Nil(t, epochEntry) + + // Set the CurrentEpoch. + epochEntry = &EpochEntry{ + EpochNumber: 1, + LastBlockHeightInEpoch: blockHeight + 5, + } + err = utxoView.SetCurrentEpoch(epochEntry, blockHeight) + require.NoError(t, err) + + // Test that the CurrentEpoch is set in the db. + epochEntry, err = DBGetCurrentEpoch(db, utxoView.Snapshot) + require.NoError(t, err) + require.NotNil(t, epochEntry) + require.Equal(t, epochEntry.EpochNumber, uint64(1)) + require.Equal(t, epochEntry.LastBlockHeightInEpoch, blockHeight+5) + + // Test that the CurrentEpoch is set in the UtxoView. + epochEntry = utxoView.CurrentEpochEntry + require.NotNil(t, epochEntry) + require.Equal(t, epochEntry.EpochNumber, uint64(1)) + require.Equal(t, epochEntry.LastBlockHeightInEpoch, blockHeight+5) + + // Test GetCurrentEpoch(). + epochEntry, err = utxoView.GetCurrentEpoch() + require.NoError(t, err) + require.NotNil(t, epochEntry) + require.Equal(t, epochEntry.EpochNumber, uint64(1)) + require.Equal(t, epochEntry.LastBlockHeightInEpoch, blockHeight+5) + + // Delete CurrentEpoch from the UtxoView. + utxoView.DeleteCurrentEpoch() + require.Nil(t, utxoView.CurrentEpochEntry) + + // CurrentEpoch still exists in the db. + epochEntry, err = DBGetCurrentEpoch(db, utxoView.Snapshot) + require.NoError(t, err) + require.NotNil(t, epochEntry) + + // GetCurrentEpoch() should return the CurrentEpoch from the db. + epochEntry, err = utxoView.GetCurrentEpoch() + require.NoError(t, err) + require.NotNil(t, epochEntry) + + // CurrentEpoch gets cached in the UtxoView. + require.NotNil(t, utxoView.CurrentEpochEntry) +} From ce42544763508d2ca265b49221bb5030742a05d2 Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Fri, 28 Apr 2023 11:31:55 -0400 Subject: [PATCH 02/22] Update func naming. --- lib/pos_epoch.go | 43 +++++++++++++++++++++++++++---------------- lib/pos_epoch_test.go | 19 +++++++++++-------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/lib/pos_epoch.go b/lib/pos_epoch.go index 2e721dc82..0cffc2f81 100644 --- a/lib/pos_epoch.go +++ b/lib/pos_epoch.go @@ -59,7 +59,7 @@ func (epochEntry *EpochEntry) GetEncoderType() EncoderType { // UTXO VIEW UTILS // -func (bav *UtxoView) GetCurrentEpoch() (*EpochEntry, error) { +func (bav *UtxoView) GetCurrentEpochEntry() (*EpochEntry, error) { var epochEntry *EpochEntry var err error @@ -70,7 +70,7 @@ func (bav *UtxoView) GetCurrentEpoch() (*EpochEntry, error) { } // If not found, check the database. - epochEntry, err = DBGetCurrentEpoch(bav.Handle, bav.Snapshot) + epochEntry, err = DBGetCurrentEpochEntry(bav.Handle, bav.Snapshot) if err != nil { return nil, errors.Wrapf(err, "UtxoView.GetCurrentEpoch: problem retrieving EpochEntry from db: ") } @@ -81,15 +81,26 @@ func (bav *UtxoView) GetCurrentEpoch() (*EpochEntry, error) { return epochEntry, nil } -func (bav *UtxoView) SetCurrentEpoch(epochEntry *EpochEntry, blockHeight uint64) error { +func (bav *UtxoView) GetCurrentEpochNumber() (uint64, error) { + epochEntry, err := bav.GetCurrentEpochEntry() + if err != nil { + return 0, errors.Wrapf(err, "UtxoView.GetCurrentEpochNumber: ") + } + if epochEntry == nil { + return 0, errors.New("UtxoView.GetCurrentEpochNumber: no CurrentEpochEntry found") + } + return epochEntry.EpochNumber, nil +} + +func (bav *UtxoView) SetCurrentEpochEntry(epochEntry *EpochEntry, blockHeight uint64) error { // This function should only ever be called from the OnEpochEnd hook. if epochEntry == nil { - return errors.New("UtxoView.SetCurrentEpoch: called with nil EpochEntry") + return errors.New("UtxoView.SetCurrentEpochEntry: called with nil EpochEntry") } // Set the current EpochEntry in the db. - if err := DBPutCurrentEpoch(bav.Handle, bav.Snapshot, epochEntry, blockHeight); err != nil { - return errors.Wrapf(err, "UtxoView.SetCurrentEpoch: problem setting EpochEntry in db: ") + if err := DBPutCurrentEpochEntry(bav.Handle, bav.Snapshot, epochEntry, blockHeight); err != nil { + return errors.Wrapf(err, "UtxoView.SetCurrentEpochEntry: problem setting EpochEntry in db: ") } // Set the current EpochEntry in the UtxoView. @@ -97,7 +108,7 @@ func (bav *UtxoView) SetCurrentEpoch(epochEntry *EpochEntry, blockHeight uint64) return nil } -func (bav *UtxoView) DeleteCurrentEpoch() { +func (bav *UtxoView) DeleteCurrentEpochEntry() { // This function should only ever be called from the OnEpochEnd hook. bav.CurrentEpochEntry = nil } @@ -110,17 +121,17 @@ func DBKeyForCurrentEpoch() []byte { return append([]byte{}, Prefixes.PrefixCurrentEpoch...) } -func DBGetCurrentEpoch(handle *badger.DB, snap *Snapshot) (*EpochEntry, error) { +func DBGetCurrentEpochEntry(handle *badger.DB, snap *Snapshot) (*EpochEntry, error) { var ret *EpochEntry err := handle.View(func(txn *badger.Txn) error { var innerErr error - ret, innerErr = DBGetCurrentEpochWithTxn(txn, snap) + ret, innerErr = DBGetCurrentEpochEntryWithTxn(txn, snap) return innerErr }) return ret, err } -func DBGetCurrentEpochWithTxn(txn *badger.Txn, snap *Snapshot) (*EpochEntry, error) { +func DBGetCurrentEpochEntryWithTxn(txn *badger.Txn, snap *Snapshot) (*EpochEntry, error) { // Retrieve StakeEntry from db. key := DBKeyForCurrentEpoch() epochEntryBytes, err := DBGetWithTxn(txn, snap, key) @@ -129,30 +140,30 @@ func DBGetCurrentEpochWithTxn(txn *badger.Txn, snap *Snapshot) (*EpochEntry, err if err == badger.ErrKeyNotFound { return nil, nil } - return nil, errors.Wrapf(err, "DBGetCurrentEpoch: problem retrieving EpochEntry: ") + return nil, errors.Wrapf(err, "DBGetCurrentEpochEntry: problem retrieving EpochEntry: ") } // Decode EpochEntry from bytes. rr := bytes.NewReader(epochEntryBytes) epochEntry, err := DecodeDeSoEncoder(&EpochEntry{}, rr) if err != nil { - return nil, errors.Wrapf(err, "DBGetCurrentEpoch: problem decoding EpochEntry: ") + return nil, errors.Wrapf(err, "DBGetCurrentEpochEntry: problem decoding EpochEntry: ") } return epochEntry, nil } -func DBPutCurrentEpoch(handle *badger.DB, snap *Snapshot, epochEntry *EpochEntry, blockHeight uint64) error { +func DBPutCurrentEpochEntry(handle *badger.DB, snap *Snapshot, epochEntry *EpochEntry, blockHeight uint64) error { return handle.Update(func(txn *badger.Txn) error { - return DBPutCurrentEpochWithTxn(txn, snap, epochEntry, blockHeight) + return DBPutCurrentEpochEntryWithTxn(txn, snap, epochEntry, blockHeight) }) } -func DBPutCurrentEpochWithTxn(txn *badger.Txn, snap *Snapshot, epochEntry *EpochEntry, blockHeight uint64) error { +func DBPutCurrentEpochEntryWithTxn(txn *badger.Txn, snap *Snapshot, epochEntry *EpochEntry, blockHeight uint64) error { // Set EpochEntry in PrefixCurrentEpoch. key := DBKeyForCurrentEpoch() if err := DBSetWithTxn(txn, snap, key, EncodeToBytes(blockHeight, epochEntry)); err != nil { return errors.Wrapf( - err, "DBPutCurrentEpoch: problem storing EpochEntry in index PrefixCurrentEpoch: ", + err, "DBPutCurrentEpochEntry: problem storing EpochEntry in index PrefixCurrentEpoch: ", ) } return nil diff --git a/lib/pos_epoch_test.go b/lib/pos_epoch_test.go index fd37a65e6..02ee6bf64 100644 --- a/lib/pos_epoch_test.go +++ b/lib/pos_epoch_test.go @@ -16,7 +16,7 @@ func TestCurrentEpoch(t *testing.T) { require.NoError(t, err) // Test that the CurrentEpoch is nil in the db. - epochEntry, err = DBGetCurrentEpoch(db, utxoView.Snapshot) + epochEntry, err = DBGetCurrentEpochEntry(db, utxoView.Snapshot) require.NoError(t, err) require.Nil(t, epochEntry) @@ -24,7 +24,7 @@ func TestCurrentEpoch(t *testing.T) { require.Nil(t, utxoView.CurrentEpochEntry) // Test GetCurrentEpoch(). - epochEntry, err = utxoView.GetCurrentEpoch() + epochEntry, err = utxoView.GetCurrentEpochEntry() require.NoError(t, err) require.Nil(t, epochEntry) @@ -33,11 +33,11 @@ func TestCurrentEpoch(t *testing.T) { EpochNumber: 1, LastBlockHeightInEpoch: blockHeight + 5, } - err = utxoView.SetCurrentEpoch(epochEntry, blockHeight) + err = utxoView.SetCurrentEpochEntry(epochEntry, blockHeight) require.NoError(t, err) // Test that the CurrentEpoch is set in the db. - epochEntry, err = DBGetCurrentEpoch(db, utxoView.Snapshot) + epochEntry, err = DBGetCurrentEpochEntry(db, utxoView.Snapshot) require.NoError(t, err) require.NotNil(t, epochEntry) require.Equal(t, epochEntry.EpochNumber, uint64(1)) @@ -50,26 +50,29 @@ func TestCurrentEpoch(t *testing.T) { require.Equal(t, epochEntry.LastBlockHeightInEpoch, blockHeight+5) // Test GetCurrentEpoch(). - epochEntry, err = utxoView.GetCurrentEpoch() + epochEntry, err = utxoView.GetCurrentEpochEntry() require.NoError(t, err) require.NotNil(t, epochEntry) require.Equal(t, epochEntry.EpochNumber, uint64(1)) require.Equal(t, epochEntry.LastBlockHeightInEpoch, blockHeight+5) // Delete CurrentEpoch from the UtxoView. - utxoView.DeleteCurrentEpoch() + utxoView.DeleteCurrentEpochEntry() require.Nil(t, utxoView.CurrentEpochEntry) // CurrentEpoch still exists in the db. - epochEntry, err = DBGetCurrentEpoch(db, utxoView.Snapshot) + epochEntry, err = DBGetCurrentEpochEntry(db, utxoView.Snapshot) require.NoError(t, err) require.NotNil(t, epochEntry) // GetCurrentEpoch() should return the CurrentEpoch from the db. - epochEntry, err = utxoView.GetCurrentEpoch() + epochEntry, err = utxoView.GetCurrentEpochEntry() require.NoError(t, err) require.NotNil(t, epochEntry) // CurrentEpoch gets cached in the UtxoView. require.NotNil(t, utxoView.CurrentEpochEntry) + + // Test GetCurrentEpochNumber(). + require.Equal(t, utxoView.CurrentEpochEntry.EpochNumber, uint64(1)) } From 21272d5003485fb8a5f0e8320313aef9a9a023cb Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Fri, 28 Apr 2023 11:33:21 -0400 Subject: [PATCH 03/22] Fix nil error. --- lib/block_view.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/block_view.go b/lib/block_view.go index 92250eb0a..597f0605b 100644 --- a/lib/block_view.go +++ b/lib/block_view.go @@ -512,7 +512,9 @@ func (bav *UtxoView) CopyUtxoView() (*UtxoView, error) { } // Copy the CurrentEpochEntry - newView.CurrentEpochEntry = bav.CurrentEpochEntry.Copy() + if bav.CurrentEpochEntry != nil { + newView.CurrentEpochEntry = bav.CurrentEpochEntry.Copy() + } return newView, nil } From d590dbe9e65fa4c89b9bb6275d0a4ef1e045cb3a Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Fri, 28 Apr 2023 11:49:35 -0400 Subject: [PATCH 04/22] Add prefix encoder db util. --- lib/db_utils.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/db_utils.go b/lib/db_utils.go index 260a78be9..4ca3b736c 100644 --- a/lib/db_utils.go +++ b/lib/db_utils.go @@ -723,6 +723,9 @@ func StatePrefixToDeSoEncoder(prefix []byte) (_isEncoder bool, _encoder DeSoEnco } else if bytes.Equal(prefix, Prefixes.PrefixLockedStakeByValidatorByStakerByLockedAt) { // prefix_id:"[82]" return true, &LockedStakeEntry{} + } else if bytes.Equal(prefix, Prefixes.PrefixCurrentEpoch) { + // prefix_id:"[83]" + return true, &EpochEntry{} } return true, nil From aa34085213937f053e9a5126165840b0e44c6b47 Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Fri, 28 Apr 2023 13:41:21 -0400 Subject: [PATCH 05/22] Rename field to FinalBlockHeight. --- lib/pos_epoch.go | 16 ++++++++-------- lib/pos_epoch_test.go | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/pos_epoch.go b/lib/pos_epoch.go index 0cffc2f81..37867b4f2 100644 --- a/lib/pos_epoch.go +++ b/lib/pos_epoch.go @@ -11,21 +11,21 @@ import ( // type EpochEntry struct { - EpochNumber uint64 - LastBlockHeightInEpoch uint64 + EpochNumber uint64 + FinalBlockHeight uint64 } func (epochEntry *EpochEntry) Copy() *EpochEntry { return &EpochEntry{ - EpochNumber: epochEntry.EpochNumber, - LastBlockHeightInEpoch: epochEntry.LastBlockHeightInEpoch, + EpochNumber: epochEntry.EpochNumber, + FinalBlockHeight: epochEntry.FinalBlockHeight, } } func (epochEntry *EpochEntry) RawEncodeWithoutMetadata(blockHeight uint64, skipMetadata ...bool) []byte { var data []byte data = append(data, UintToBuf(epochEntry.EpochNumber)...) - data = append(data, UintToBuf(epochEntry.LastBlockHeightInEpoch)...) + data = append(data, UintToBuf(epochEntry.FinalBlockHeight)...) return data } @@ -38,10 +38,10 @@ func (epochEntry *EpochEntry) RawDecodeWithoutMetadata(blockHeight uint64, rr *b return errors.Wrapf(err, "EpochEntry.Decode: Problem reading EpochNumber: ") } - // LastBlockHeightInEpoch - epochEntry.LastBlockHeightInEpoch, err = ReadUvarint(rr) + // FinalBlockHeight + epochEntry.FinalBlockHeight, err = ReadUvarint(rr) if err != nil { - return errors.Wrapf(err, "EpochEntry.Decode: Problem reading LastBlockHeightInEpoch: ") + return errors.Wrapf(err, "EpochEntry.Decode: Problem reading FinalBlockHeight: ") } return err diff --git a/lib/pos_epoch_test.go b/lib/pos_epoch_test.go index 02ee6bf64..559efac63 100644 --- a/lib/pos_epoch_test.go +++ b/lib/pos_epoch_test.go @@ -30,8 +30,8 @@ func TestCurrentEpoch(t *testing.T) { // Set the CurrentEpoch. epochEntry = &EpochEntry{ - EpochNumber: 1, - LastBlockHeightInEpoch: blockHeight + 5, + EpochNumber: 1, + FinalBlockHeight: blockHeight + 5, } err = utxoView.SetCurrentEpochEntry(epochEntry, blockHeight) require.NoError(t, err) @@ -41,20 +41,20 @@ func TestCurrentEpoch(t *testing.T) { require.NoError(t, err) require.NotNil(t, epochEntry) require.Equal(t, epochEntry.EpochNumber, uint64(1)) - require.Equal(t, epochEntry.LastBlockHeightInEpoch, blockHeight+5) + require.Equal(t, epochEntry.FinalBlockHeight, blockHeight+5) // Test that the CurrentEpoch is set in the UtxoView. epochEntry = utxoView.CurrentEpochEntry require.NotNil(t, epochEntry) require.Equal(t, epochEntry.EpochNumber, uint64(1)) - require.Equal(t, epochEntry.LastBlockHeightInEpoch, blockHeight+5) + require.Equal(t, epochEntry.FinalBlockHeight, blockHeight+5) // Test GetCurrentEpoch(). epochEntry, err = utxoView.GetCurrentEpochEntry() require.NoError(t, err) require.NotNil(t, epochEntry) require.Equal(t, epochEntry.EpochNumber, uint64(1)) - require.Equal(t, epochEntry.LastBlockHeightInEpoch, blockHeight+5) + require.Equal(t, epochEntry.FinalBlockHeight, blockHeight+5) // Delete CurrentEpoch from the UtxoView. utxoView.DeleteCurrentEpochEntry() From b2beb9e0de67d5e214ef8210d904d8b4829bb6d3 Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Fri, 28 Apr 2023 13:49:07 -0400 Subject: [PATCH 06/22] Avoid snapshotting issue for now. --- lib/pos_epoch_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pos_epoch_test.go b/lib/pos_epoch_test.go index 559efac63..2e5675820 100644 --- a/lib/pos_epoch_test.go +++ b/lib/pos_epoch_test.go @@ -14,6 +14,7 @@ func TestCurrentEpoch(t *testing.T) { blockHeight := uint64(chain.blockTip().Height) + 1 utxoView, err := NewUtxoView(db, params, chain.postgres, chain.snapshot) require.NoError(t, err) + chain.snapshot = nil // Test that the CurrentEpoch is nil in the db. epochEntry, err = DBGetCurrentEpochEntry(db, utxoView.Snapshot) From 0f5f9496294cf834fe1db6ab83270fe53203adec Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Fri, 28 Apr 2023 14:13:12 -0400 Subject: [PATCH 07/22] Update ancestral record before setting in db. --- lib/pos_epoch.go | 12 ++++++++++++ lib/pos_epoch_test.go | 1 - 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/pos_epoch.go b/lib/pos_epoch.go index 37867b4f2..87d1ef606 100644 --- a/lib/pos_epoch.go +++ b/lib/pos_epoch.go @@ -98,6 +98,18 @@ func (bav *UtxoView) SetCurrentEpochEntry(epochEntry *EpochEntry, blockHeight ui return errors.New("UtxoView.SetCurrentEpochEntry: called with nil EpochEntry") } + // TODO: is this what we should do here? + if bav.Snapshot != nil { + // We're about to set a record to the db, so we initiate a snapshot update. + // This function prepares the data structures in the snapshot. + bav.Snapshot.PrepareAncestralRecordsFlush() + + // When we finish setting to the db, we'll also flush to ancestral records. + // This happens concurrently, which is why we have the 2-phase prepare-flush + // happening for snapshot. + defer bav.Snapshot.StartAncestralRecordsFlush(true) + } + // Set the current EpochEntry in the db. if err := DBPutCurrentEpochEntry(bav.Handle, bav.Snapshot, epochEntry, blockHeight); err != nil { return errors.Wrapf(err, "UtxoView.SetCurrentEpochEntry: problem setting EpochEntry in db: ") diff --git a/lib/pos_epoch_test.go b/lib/pos_epoch_test.go index 2e5675820..559efac63 100644 --- a/lib/pos_epoch_test.go +++ b/lib/pos_epoch_test.go @@ -14,7 +14,6 @@ func TestCurrentEpoch(t *testing.T) { blockHeight := uint64(chain.blockTip().Height) + 1 utxoView, err := NewUtxoView(db, params, chain.postgres, chain.snapshot) require.NoError(t, err) - chain.snapshot = nil // Test that the CurrentEpoch is nil in the db. epochEntry, err = DBGetCurrentEpochEntry(db, utxoView.Snapshot) From 6d1ebf5c4c5f4dbe44acd68259fb008d986edc4c Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Fri, 28 Apr 2023 14:17:31 -0400 Subject: [PATCH 08/22] Incorporate epoch number into PoS txns. --- lib/block_view_stake.go | 14 +++++- lib/block_view_stake_test.go | 82 +++++++++++++++++--------------- lib/block_view_validator.go | 16 +++++-- lib/block_view_validator_test.go | 29 +++++++++-- 4 files changed, 91 insertions(+), 50 deletions(-) diff --git a/lib/block_view_stake.go b/lib/block_view_stake.go index 245debc2e..eeb4bfcf0 100644 --- a/lib/block_view_stake.go +++ b/lib/block_view_stake.go @@ -1427,8 +1427,13 @@ func (bav *UtxoView) _connectUnstake( // 2. Set the new GlobalStakeAmountNanos. bav._setGlobalStakeAmountNanos(globalStakeAmountNanos) + // Retrieve the CurrentEpochNumber. + currentEpochNumber, err := bav.GetCurrentEpochNumber() + if err != nil { + return 0, 0, nil, errors.Wrapf(err, "_connectUnstake: error retrieving CurrentEpochNumber: ") + } + // Update the LockedStakeEntry, if exists. Create if not. - currentEpochNumber := uint64(0) // TODO: set this // 1. Retrieve the PrevLockedStakeEntry. This will be restored if we disconnect this txn. prevLockedStakeEntry, err := bav.GetLockedStakeEntry( prevValidatorEntry.ValidatorPKID, transactorPKIDEntry.PKID, currentEpochNumber, @@ -1555,10 +1560,15 @@ func (bav *UtxoView) _disconnectUnstake( // Restore the PrevGlobalStakeAmountNanos. bav._setGlobalStakeAmountNanos(operationData.PrevGlobalStakeAmountNanos) + // Retrieve the CurrentEpochNumber. + currentEpochNumber, err := bav.GetCurrentEpochNumber() + if err != nil { + return errors.Wrapf(err, "_disconnectUnstake: error retrieving CurrentEpochNumber: ") + } + // Restore the PrevLockedStakeEntry, if exists. The PrevLockedStakeEntry will exist if the // transactor has previously unstaked stake assigned to this validator within the same epoch. // The PrevLockedStakeEntry will not exist otherwise. - currentEpochNumber := uint64(0) // TODO: set this // 1. Retrieve the CurrentLockedStakeEntry. currentLockedStakeEntry, err := bav.GetLockedStakeEntry( prevValidatorEntry.ValidatorPKID, transactorPKIDEntry.PKID, currentEpochNumber, diff --git a/lib/block_view_stake_test.go b/lib/block_view_stake_test.go index fedf50442..40c9b16b3 100644 --- a/lib/block_view_stake_test.go +++ b/lib/block_view_stake_test.go @@ -67,6 +67,12 @@ func _testStaking(t *testing.T, flushToDB bool) { return desoBalanceNanos } + // Seed a CurrentEpochEntry. + err = utxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}, blockHeight) + require.NoError(t, err) + currentEpochNumber, err := utxoView().GetCurrentEpochNumber() + require.NoError(t, err) + { // Param Updater set min fee rate to 101 nanos per KB params.ExtraRegtestParamUpdaterKeys[MakePkMapKey(paramUpdaterPkBytes)] = true @@ -368,7 +374,6 @@ func _testStaking(t *testing.T, flushToDB bool) { require.Equal(t, globalStakeAmountNanos, uint256.NewInt().SetUint64(110)) // Verify LockedStakeEntry.UnstakeAmountNanos. - currentEpochNumber := uint64(0) // TODO: get epoch number from db. lockedStakeEntry, err := utxoView().GetLockedStakeEntry(m0PKID, m1PKID, currentEpochNumber) require.NoError(t, err) require.Equal(t, lockedStakeEntry.LockedAmountNanos, uint256.NewInt().SetUint64(40)) @@ -407,7 +412,6 @@ func _testStaking(t *testing.T, flushToDB bool) { require.Equal(t, globalStakeAmountNanos, uint256.NewInt().SetUint64(80)) // Verify LockedStakeEntry.UnstakeAmountNanos. - currentEpochNumber := uint64(0) // TODO: get epoch number from db. lockedStakeEntry, err := utxoView().GetLockedStakeEntry(m0PKID, m1PKID, currentEpochNumber) require.NoError(t, err) require.Equal(t, lockedStakeEntry.LockedAmountNanos, uint256.NewInt().SetUint64(70)) @@ -445,7 +449,6 @@ func _testStaking(t *testing.T, flushToDB bool) { require.Equal(t, globalStakeAmountNanos, uint256.NewInt()) // Verify LockedStakeEntry.UnstakeAmountNanos. - currentEpochNumber := uint64(0) // TODO: get epoch number from db. lockedStakeEntry, err := utxoView().GetLockedStakeEntry(m0PKID, m1PKID, currentEpochNumber) require.NoError(t, err) require.Equal(t, lockedStakeEntry.LockedAmountNanos, uint256.NewInt().SetUint64(150)) @@ -466,8 +469,8 @@ func _testStaking(t *testing.T, flushToDB bool) { unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: 0, - EndEpochNumber: 0, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitUnlockStakeTxn( testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, @@ -483,8 +486,8 @@ func _testStaking(t *testing.T, flushToDB bool) { // RuleErrorInvalidValidatorPKID unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m2PkBytes), - StartEpochNumber: 0, - EndEpochNumber: 0, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitUnlockStakeTxn( testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, @@ -507,7 +510,9 @@ func _testStaking(t *testing.T, flushToDB bool) { } { // m1 unlocks stake that was assigned to m0. - lockedStakeEntries, err := utxoView().GetLockedStakeEntriesInRange(m0PKID, m1PKID, 0, 0) + lockedStakeEntries, err := utxoView().GetLockedStakeEntriesInRange( + m0PKID, m1PKID, currentEpochNumber, currentEpochNumber, + ) require.NoError(t, err) require.Equal(t, len(lockedStakeEntries), 1) require.Equal(t, lockedStakeEntries[0].LockedAmountNanos, uint256.NewInt().SetUint64(150)) @@ -515,8 +520,8 @@ func _testStaking(t *testing.T, flushToDB bool) { m1OldDESOBalanceNanos := getDESOBalanceNanos(m1PkBytes) unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: 0, - EndEpochNumber: 0, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } feeNanos, err := _submitUnlockStakeTxn( testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, @@ -539,7 +544,6 @@ func _testStaking(t *testing.T, flushToDB bool) { require.Equal(t, globalStakeAmountNanos, uint256.NewInt()) // Verify LockedStakeEntry.isDeleted. - currentEpochNumber := uint64(0) // TODO: get epoch number from db. lockedStakeEntry, err := utxoView().GetLockedStakeEntry(m0PKID, m1PKID, currentEpochNumber) require.NoError(t, err) require.Nil(t, lockedStakeEntry) @@ -916,6 +920,12 @@ func _testStakingWithDerivedKey(t *testing.T) { return fees, nil } + // Seed a CurrentEpochEntry. + err = newUtxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}, blockHeight) + require.NoError(t, err) + currentEpochNumber, err := newUtxoView().GetCurrentEpochNumber() + require.NoError(t, err) + { // ParamUpdater set min fee rate params.ExtraRegtestParamUpdaterKeys[MakePkMapKey(paramUpdaterPkBytes)] = true @@ -1182,8 +1192,7 @@ func _testStakingWithDerivedKey(t *testing.T) { require.Equal(t, stakeEntry.StakeAmountNanos, uint256.NewInt().SetUint64(50)) // LockedStakeEntry was created. - epochNumber := uint64(0) // TODO: get epoch number from db. - lockedStakeEntry, err := newUtxoView().GetLockedStakeEntry(m0PKID, senderPKID, epochNumber) + lockedStakeEntry, err := newUtxoView().GetLockedStakeEntry(m0PKID, senderPKID, currentEpochNumber) require.NoError(t, err) require.NotNil(t, lockedStakeEntry) require.Equal(t, lockedStakeEntry.LockedAmountNanos, uint256.NewInt().SetUint64(50)) @@ -1204,11 +1213,10 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender tries to unlock all stake from m1 using the DerivedKey. Errors. - epochNumber := uint64(0) // TODO: get epoch number from db. unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: epochNumber, - EndEpochNumber: epochNumber, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1229,8 +1237,8 @@ func _testStakingWithDerivedKey(t *testing.T) { // sender tries to unlock all stake from m1 using the DerivedKey. Errors. unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: epochNumber, - EndEpochNumber: epochNumber, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1242,8 +1250,8 @@ func _testStakingWithDerivedKey(t *testing.T) { senderOldDESOBalanceNanos := getDESOBalanceNanos(senderPkBytes) unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: epochNumber, - EndEpochNumber: epochNumber, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } feeNanos, err := _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1251,7 +1259,7 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // LockedStakeEntry was deleted. - lockedStakeEntry, err := newUtxoView().GetLockedStakeEntry(m0PKID, senderPKID, epochNumber) + lockedStakeEntry, err := newUtxoView().GetLockedStakeEntry(m0PKID, senderPKID, currentEpochNumber) require.NoError(t, err) require.Nil(t, lockedStakeEntry) @@ -1280,8 +1288,8 @@ func _testStakingWithDerivedKey(t *testing.T) { // sender tries to unlock all stake from m0 using the DerivedKey. Errors. unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: epochNumber, - EndEpochNumber: epochNumber, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1347,11 +1355,10 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender unlocks stake from m0 using the DerivedKey. - epochNumber := uint64(0) // TODO: get epoch number from db. unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: epochNumber, - EndEpochNumber: epochNumber, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1361,8 +1368,8 @@ func _testStakingWithDerivedKey(t *testing.T) { // sender unlocks stake from m1 using the DerivedKey. unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: epochNumber, - EndEpochNumber: epochNumber, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1421,11 +1428,10 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender unlocks stake from m0 using the DerivedKey. - epochNumber := uint64(0) // TODO: get epoch number from db. unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: epochNumber, - EndEpochNumber: epochNumber, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1435,8 +1441,8 @@ func _testStakingWithDerivedKey(t *testing.T) { // sender unlocks stake from m1 using the DerivedKey. unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: epochNumber, - EndEpochNumber: epochNumber, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1490,11 +1496,10 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender unlocks stake from m1 using the scoped TransactionSpendingLimit. - epochNumber := uint64(0) // TODO: get epoch number from db. unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: epochNumber, - EndEpochNumber: epochNumber, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1522,11 +1527,10 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender unlocks stake from m1 using the global TransactionSpendingLimit. - epochNumber = uint64(0) // TODO: get epoch number from db. unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: epochNumber, - EndEpochNumber: epochNumber, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, diff --git a/lib/block_view_validator.go b/lib/block_view_validator.go index fb9ecbdc5..163f12e7a 100644 --- a/lib/block_view_validator.go +++ b/lib/block_view_validator.go @@ -1056,13 +1056,17 @@ func (bav *UtxoView) _connectUnregisterAsValidator( return 0, 0, nil, errors.Wrapf(err, "_connectUnregisterAsValidator: error retrieving StakeEntries: ") } + // Retrieve the CurrentEpochNumber. + currentEpochNumber, err := bav.GetCurrentEpochNumber() + if err != nil { + return 0, 0, nil, errors.Wrapf(err, "_connectUnregisterAsValidator: error retrieving CurrentEpochNumber: ") + } + // Delete each StakeEntry and create or update the corresponding LockedStakeEntry. // Track TotalUnstakedAmountNanos and PrevLockedStakeEntries. totalUnstakedAmountNanos := uint256.NewInt() var prevLockedStakeEntries []*LockedStakeEntry - currentEpochNumber := uint64(0) // TODO: Retrieve this from the db. - for _, prevStakeEntry := range prevStakeEntries { // Add the UnstakedAmountNanos to the TotalUnstakedAmountNanos. totalUnstakedAmountNanos, err = SafeUint256().Add( @@ -1223,9 +1227,13 @@ func (bav *UtxoView) _disconnectUnregisterAsValidator( bav._setStakeEntryMappings(prevStakeEntry) } - // Restore the PrevLockedStakeEntries, if any. - currentEpochNumber := uint64(0) // TODO: Retrieve this from the db. + // Retrieve the CurrentEpochNumber. + currentEpochNumber, err := bav.GetCurrentEpochNumber() + if err != nil { + return errors.Wrapf(err, "_disconnectUnregisterAsValidator: error retrieving CurrentEpochNumber: ") + } + // Restore the PrevLockedStakeEntries, if any. for _, prevLockedStakeEntry := range operationData.PrevLockedStakeEntries { // Delete the CurrentLockedStakeEntry. currentLockedStakeEntry, err := bav.GetLockedStakeEntry( diff --git a/lib/block_view_validator_test.go b/lib/block_view_validator_test.go index 889b80c64..fc9493314 100644 --- a/lib/block_view_validator_test.go +++ b/lib/block_view_validator_test.go @@ -62,6 +62,10 @@ func _testValidatorRegistration(t *testing.T, flushToDB bool) { m0PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m0PkBytes).PKID + // Seed a CurrentEpochEntry. + err = utxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}, blockHeight) + require.NoError(t, err) + { // ParamUpdater set min fee rate params.ExtraRegtestParamUpdaterKeys[MakePkMapKey(paramUpdaterPkBytes)] = true @@ -412,9 +416,14 @@ func _testValidatorRegistrationWithDerivedKey(t *testing.T) { senderPrivKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), senderPrivBytes) senderPKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, senderPkBytes).PKID - _submitAuthorizeDerivedKeyTxn := func(txnType TxnType, count uint64) (string, error) { + newUtxoView := func() *UtxoView { utxoView, err := NewUtxoView(db, params, chain.postgres, chain.snapshot) require.NoError(t, err) + return utxoView + } + + _submitAuthorizeDerivedKeyTxn := func(txnType TxnType, count uint64) (string, error) { + utxoView := newUtxoView() txnSpendingLimit := &TransactionSpendingLimit{ GlobalDESOLimit: NanosPerUnit, // 1 $DESO spending limit @@ -463,8 +472,7 @@ func _testValidatorRegistrationWithDerivedKey(t *testing.T) { _submitValidatorTxnWithDerivedKey := func( transactorPkBytes []byte, derivedKeyPrivBase58Check string, inputTxn MsgDeSoTxn, ) error { - utxoView, err := NewUtxoView(db, params, chain.postgres, chain.snapshot) - require.NoError(t, err) + utxoView := newUtxoView() var txn *MsgDeSoTxn switch inputTxn.TxnMeta.GetTxnType() { @@ -519,6 +527,10 @@ func _testValidatorRegistrationWithDerivedKey(t *testing.T) { return nil } + // Seed a CurrentEpochEntry. + err = newUtxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}, blockHeight) + require.NoError(t, err) + { // ParamUpdater set min fee rate params.ExtraRegtestParamUpdaterKeys[MakePkMapKey(paramUpdaterPkBytes)] = true @@ -677,6 +689,10 @@ func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { m1PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m1PkBytes).PKID m2PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m2PkBytes).PKID + // Seed a CurrentEpochEntry. + err = utxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}, blockHeight) + require.NoError(t, err) + { // ParamUpdater set min fee rate params.ExtraRegtestParamUpdaterKeys[MakePkMapKey(paramUpdaterPkBytes)] = true @@ -1210,8 +1226,11 @@ func _testUnregisterAsValidator(t *testing.T, flushToDB bool) { m0PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m0PkBytes).PKID m1PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m1PkBytes).PKID - currentEpochNumber := uint64(0) // TODO: Retrieve this from the db. - _ = currentEpochNumber + // Seed a CurrentEpochEntry. + err = utxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}, blockHeight) + require.NoError(t, err) + currentEpochNumber, err := utxoView().GetCurrentEpochNumber() + require.NoError(t, err) { // ParamUpdater set min fee rate From c7b02688a25680434025c74424b55eabffe34f86 Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Mon, 1 May 2023 13:26:07 -0400 Subject: [PATCH 09/22] Validate stake lockup period. --- lib/block_view_stake.go | 16 ++++- lib/block_view_stake_test.go | 136 +++++++++++++++++++++++------------ 2 files changed, 106 insertions(+), 46 deletions(-) diff --git a/lib/block_view_stake.go b/lib/block_view_stake.go index eeb4bfcf0..56c70fb76 100644 --- a/lib/block_view_stake.go +++ b/lib/block_view_stake.go @@ -1870,7 +1870,17 @@ func (bav *UtxoView) IsValidUnlockStakeMetadata(transactorPkBytes []byte, metada if metadata.StartEpochNumber > metadata.EndEpochNumber { return errors.Wrapf(RuleErrorInvalidUnlockStakeEpochRange, "UtxoView.IsValidUnlockStakeMetadata: ") } - // TODO: validate EndEpochNumber is <= CurrentEpochNumber - 2 + + // Retrieve CurrentEpochNumber. + currentEpochNumber, err := bav.GetCurrentEpochNumber() + if err != nil { + return errors.Wrapf(err, "UtxoView.IsValidUnlockStakeMetadata: error retrieving CurrentEpochNumber: ") + } + + // Validate EndEpochNumber + StakeLockupEpochDuration <= CurrentEpochNumber. + if metadata.EndEpochNumber+StakeLockupEpochDuration > currentEpochNumber { + return errors.Wrapf(RuleErrorInvalidUnlockStakeMustWaitLockupDuration, "UtxoView.IsValidUnlockStakeMetadata: ") + } // Validate LockedStakeEntries exist. lockedStakeEntries, err := bav.GetLockedStakeEntriesInRange( @@ -2572,6 +2582,7 @@ const RuleErrorInvalidUnstakeNoStakeFound RuleError = "RuleErrorInvalidUnstakeNo const RuleErrorInvalidUnstakeAmountNanos RuleError = "RuleErrorInvalidUnstakeAmountNanos" const RuleErrorInvalidUnstakeInsufficientStakeFound RuleError = "RuleErrorInvalidUnstakeInsufficientStakeFound" const RuleErrorInvalidUnlockStakeEpochRange RuleError = "RuleErrorInvalidUnlockStakeEpochRange" +const RuleErrorInvalidUnlockStakeMustWaitLockupDuration RuleError = "RuleErrorInvalidUnlockStakeMustWaitLockupDuration" const RuleErrorInvalidUnlockStakeNoUnlockableStakeFound RuleError = "RuleErrorInvalidUnlockStakeNoUnlockableStakeFound" const RuleErrorInvalidUnlockStakeUnlockableStakeOverflowsUint64 RuleError = "RuleErrorInvalidUnlockStakeUnlockableStakeOverflowsUint64" const RuleErrorStakeTransactionSpendingLimitNotFound RuleError = "RuleErrorStakeTransactionSpendingLimitNotFound" @@ -2582,3 +2593,6 @@ const RuleErrorUnlockStakeTransactionSpendingLimitNotFound RuleError = "RuleErro const RuleErrorTransactionSpendingLimitInvalidStaker RuleError = "RuleErrorTransactionSpendingLimitInvalidStaker" const RuleErrorTransactionSpendingLimitInvalidValidator RuleError = "RuleErrorTransactionSpendingLimitInvalidValidator" const RuleErrorTransactionSpendingLimitValidatorDisabledDelegatedStake RuleError = "RuleErrorTransactionSpendingLimitValidatorDisabledDelegatedStake" + +// TODO: move this to a field that can be updated by ParamUpdater. +const StakeLockupEpochDuration = uint64(2) diff --git a/lib/block_view_stake_test.go b/lib/block_view_stake_test.go index 40c9b16b3..cd2caeb52 100644 --- a/lib/block_view_stake_test.go +++ b/lib/block_view_stake_test.go @@ -68,7 +68,7 @@ func _testStaking(t *testing.T, flushToDB bool) { } // Seed a CurrentEpochEntry. - err = utxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}, blockHeight) + err = utxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 5, FinalBlockHeight: blockHeight + 10}, blockHeight) require.NoError(t, err) currentEpochNumber, err := utxoView().GetCurrentEpochNumber() require.NoError(t, err) @@ -461,6 +461,35 @@ func _testStaking(t *testing.T, flushToDB bool) { // // UNLOCK STAKE // + { + // RuleErrorInvalidUnlockStakeMustWaitLockupDuration + unlockStakeMetadata := &UnlockStakeMetadata{ + ValidatorPublicKey: NewPublicKey(m0PkBytes), + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, + } + _, err = _submitUnlockStakeTxn( + testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, + ) + require.Error(t, err) + require.Contains(t, err.Error(), RuleErrorInvalidUnlockStakeMustWaitLockupDuration) + } + { + // Simulate two epochs passing by seeding a new CurrentEpochEntry. + + // We have to manually delete the CurrentEpochEntry from the UniversalUtxoView + // and ReadOnlyUtxoView just for these tests since utxoView() returns a copy + // and these UtxoViews will return the old cached CurrentEpochEntry otherwise. + mempool.universalUtxoView.DeleteCurrentEpochEntry() + mempool.readOnlyUtxoView.DeleteCurrentEpochEntry() + + err = utxoView().SetCurrentEpochEntry( + &EpochEntry{EpochNumber: currentEpochNumber + 2, FinalBlockHeight: blockHeight + 10}, + blockHeight, + ) + currentEpochNumber, err = utxoView().GetCurrentEpochNumber() + require.NoError(t, err) + } { // RuleErrorProofOfStakeTxnBeforeBlockHeight params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight = math.MaxUint32 @@ -469,8 +498,8 @@ func _testStaking(t *testing.T, flushToDB bool) { unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, } _, err = _submitUnlockStakeTxn( testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, @@ -486,8 +515,8 @@ func _testStaking(t *testing.T, flushToDB bool) { // RuleErrorInvalidValidatorPKID unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m2PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, } _, err = _submitUnlockStakeTxn( testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, @@ -508,10 +537,23 @@ func _testStaking(t *testing.T, flushToDB bool) { require.Error(t, err) require.Contains(t, err.Error(), RuleErrorInvalidUnlockStakeEpochRange) } + { + // RuleErrorInvalidUnlockStakeNoUnlockableStakeFound + unlockStakeMetadata := &UnlockStakeMetadata{ + ValidatorPublicKey: NewPublicKey(m0PkBytes), + StartEpochNumber: 0, + EndEpochNumber: 0, + } + _, err = _submitUnlockStakeTxn( + testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, + ) + require.Error(t, err) + require.Contains(t, err.Error(), RuleErrorInvalidUnlockStakeNoUnlockableStakeFound) + } { // m1 unlocks stake that was assigned to m0. lockedStakeEntries, err := utxoView().GetLockedStakeEntriesInRange( - m0PKID, m1PKID, currentEpochNumber, currentEpochNumber, + m0PKID, m1PKID, currentEpochNumber-2, currentEpochNumber-2, ) require.NoError(t, err) require.Equal(t, len(lockedStakeEntries), 1) @@ -520,8 +562,8 @@ func _testStaking(t *testing.T, flushToDB bool) { m1OldDESOBalanceNanos := getDESOBalanceNanos(m1PkBytes) unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, } feeNanos, err := _submitUnlockStakeTxn( testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, @@ -552,19 +594,6 @@ func _testStaking(t *testing.T, flushToDB bool) { m1NewDESOBalanceNanos := getDESOBalanceNanos(m1PkBytes) require.Equal(t, m1OldDESOBalanceNanos-feeNanos+uint64(150), m1NewDESOBalanceNanos) } - { - // RuleErrorInvalidUnlockStakeNoUnlockableStakeFound - unlockStakeMetadata := &UnlockStakeMetadata{ - ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: 0, - EndEpochNumber: 0, - } - _, err = _submitUnlockStakeTxn( - testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, - ) - require.Error(t, err) - require.Contains(t, err.Error(), RuleErrorInvalidUnlockStakeNoUnlockableStakeFound) - } // Flush mempool to the db and test rollbacks. require.NoError(t, mempool.universalUtxoView.FlushToDb(blockHeight)) @@ -921,10 +950,20 @@ func _testStakingWithDerivedKey(t *testing.T) { } // Seed a CurrentEpochEntry. - err = newUtxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}, blockHeight) - require.NoError(t, err) - currentEpochNumber, err := newUtxoView().GetCurrentEpochNumber() - require.NoError(t, err) + currentEpochNumber := uint64(5) + + _simulateEpochsPassing := func(numEpochs uint64) { + // Simulate epochs passing by seeding a new CurrentEpochEntry. + err = newUtxoView().SetCurrentEpochEntry( + &EpochEntry{EpochNumber: currentEpochNumber + numEpochs, FinalBlockHeight: blockHeight + 10}, + blockHeight, + ) + require.NoError(t, err) + currentEpochNumber, err = newUtxoView().GetCurrentEpochNumber() + require.NoError(t, err) + } + + _simulateEpochsPassing(0) { // ParamUpdater set min fee rate @@ -1199,6 +1238,7 @@ func _testStakingWithDerivedKey(t *testing.T) { } { // sender unlocks stake using a DerivedKey. + _simulateEpochsPassing(2) // sender creates a DerivedKey to perform 1 unlock stake operation with m0. stakeLimitKey := MakeStakeLimitKey(m0PKID, senderPKID) @@ -1215,8 +1255,8 @@ func _testStakingWithDerivedKey(t *testing.T) { // sender tries to unlock all stake from m1 using the DerivedKey. Errors. unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1235,10 +1275,11 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender tries to unlock all stake from m1 using the DerivedKey. Errors. + _simulateEpochsPassing(2) unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1250,8 +1291,8 @@ func _testStakingWithDerivedKey(t *testing.T) { senderOldDESOBalanceNanos := getDESOBalanceNanos(senderPkBytes) unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 4, + EndEpochNumber: currentEpochNumber - 4, } feeNanos, err := _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1286,10 +1327,11 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender tries to unlock all stake from m0 using the DerivedKey. Errors. + _simulateEpochsPassing(2) unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1355,10 +1397,11 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender unlocks stake from m0 using the DerivedKey. + _simulateEpochsPassing(2) unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1368,8 +1411,8 @@ func _testStakingWithDerivedKey(t *testing.T) { // sender unlocks stake from m1 using the DerivedKey. unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1428,10 +1471,11 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender unlocks stake from m0 using the DerivedKey. + _simulateEpochsPassing(2) unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1441,8 +1485,8 @@ func _testStakingWithDerivedKey(t *testing.T) { // sender unlocks stake from m1 using the DerivedKey. unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1496,10 +1540,11 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender unlocks stake from m1 using the scoped TransactionSpendingLimit. + _simulateEpochsPassing(2) unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1527,10 +1572,11 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender unlocks stake from m1 using the global TransactionSpendingLimit. + _simulateEpochsPassing(2) unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, From d2bb3aa674aee1ebfd38207c577e2421cbee3ba6 Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Mon, 1 May 2023 16:48:53 -0400 Subject: [PATCH 10/22] Validate stake lockup period. --- lib/block_view_stake.go | 25 ++- lib/block_view_stake_test.go | 296 +++++++++++++++++++++++++---------- lib/block_view_types.go | 14 ++ lib/constants.go | 10 ++ 4 files changed, 244 insertions(+), 101 deletions(-) diff --git a/lib/block_view_stake.go b/lib/block_view_stake.go index 56c70fb76..16f1a6059 100644 --- a/lib/block_view_stake.go +++ b/lib/block_view_stake.go @@ -1134,12 +1134,14 @@ func (bav *UtxoView) _connectStake( // Check if there is an existing StakeEntry that will be updated. // The existing StakeEntry will be restored if we disconnect this transaction. + var prevStakeEntries []*StakeEntry prevStakeEntry, err := bav.GetStakeEntry(prevValidatorEntry.ValidatorPKID, transactorPKIDEntry.PKID) if err != nil { return 0, 0, nil, errors.Wrapf(err, "_connectStake: ") } // Delete the existing StakeEntry, if exists. if prevStakeEntry != nil { + prevStakeEntries = append(prevStakeEntries, prevStakeEntry) bav._deleteStakeEntryMappings(prevStakeEntry) } @@ -1217,7 +1219,7 @@ func (bav *UtxoView) _connectStake( Type: OperationTypeStake, PrevValidatorEntry: prevValidatorEntry, PrevGlobalStakeAmountNanos: prevGlobalStakeAmountNanos, - PrevStakeEntries: []*StakeEntry{prevStakeEntry}, + PrevStakeEntries: prevStakeEntries, }) return totalInput, totalOutput, utxoOpsForTxn, nil } @@ -1378,6 +1380,7 @@ func (bav *UtxoView) _connectUnstake( if prevStakeEntry.StakeAmountNanos.Cmp(txMeta.UnstakeAmountNanos) < 0 { return 0, 0, nil, errors.Wrapf(RuleErrorInvalidUnstakeInsufficientStakeFound, "_connectUnstake: ") } + prevStakeEntries := []*StakeEntry{prevStakeEntry} // Update the StakeEntry, decreasing the StakeAmountNanos. // 1. Calculate the updated StakeAmountNanos. @@ -1442,6 +1445,7 @@ func (bav *UtxoView) _connectUnstake( return 0, 0, nil, errors.Wrapf(err, "_connectUnstake: ") } // 2. Create a CurrrentLockedStakeEntry. + var prevLockedStakeEntries []*LockedStakeEntry var currentLockedStakeEntry *LockedStakeEntry if prevLockedStakeEntry != nil { // Update the existing LockedStakeEntry. @@ -1466,6 +1470,7 @@ func (bav *UtxoView) _connectUnstake( } // 3. Delete the PrevLockedStakeEntry, if exists. if prevLockedStakeEntry != nil { + prevLockedStakeEntries = append(prevLockedStakeEntries, prevLockedStakeEntry) bav._deleteLockedStakeEntryMappings(prevLockedStakeEntry) } // 4. Set the CurrentLockedStakeEntry. @@ -1476,8 +1481,9 @@ func (bav *UtxoView) _connectUnstake( Type: OperationTypeUnstake, PrevValidatorEntry: prevValidatorEntry, PrevGlobalStakeAmountNanos: prevGlobalStakeAmountNanos, - PrevStakeEntries: []*StakeEntry{prevStakeEntry}, - PrevLockedStakeEntries: []*LockedStakeEntry{prevLockedStakeEntry}, + PrevStakeEntries: prevStakeEntries, + PrevLockedStakeEntries: prevLockedStakeEntries, + PrevEpochNumber: currentEpochNumber, }) return totalInput, totalOutput, utxoOpsForTxn, nil } @@ -1560,18 +1566,12 @@ func (bav *UtxoView) _disconnectUnstake( // Restore the PrevGlobalStakeAmountNanos. bav._setGlobalStakeAmountNanos(operationData.PrevGlobalStakeAmountNanos) - // Retrieve the CurrentEpochNumber. - currentEpochNumber, err := bav.GetCurrentEpochNumber() - if err != nil { - return errors.Wrapf(err, "_disconnectUnstake: error retrieving CurrentEpochNumber: ") - } - // Restore the PrevLockedStakeEntry, if exists. The PrevLockedStakeEntry will exist if the // transactor has previously unstaked stake assigned to this validator within the same epoch. // The PrevLockedStakeEntry will not exist otherwise. // 1. Retrieve the CurrentLockedStakeEntry. currentLockedStakeEntry, err := bav.GetLockedStakeEntry( - prevValidatorEntry.ValidatorPKID, transactorPKIDEntry.PKID, currentEpochNumber, + prevValidatorEntry.ValidatorPKID, transactorPKIDEntry.PKID, operationData.PrevEpochNumber, ) if err != nil { return errors.Wrapf(err, "_disconnectUnstake: ") @@ -1878,7 +1878,7 @@ func (bav *UtxoView) IsValidUnlockStakeMetadata(transactorPkBytes []byte, metada } // Validate EndEpochNumber + StakeLockupEpochDuration <= CurrentEpochNumber. - if metadata.EndEpochNumber+StakeLockupEpochDuration > currentEpochNumber { + if metadata.EndEpochNumber+bav.Params.StakeLockupEpochDuration > currentEpochNumber { return errors.Wrapf(RuleErrorInvalidUnlockStakeMustWaitLockupDuration, "UtxoView.IsValidUnlockStakeMetadata: ") } @@ -2593,6 +2593,3 @@ const RuleErrorUnlockStakeTransactionSpendingLimitNotFound RuleError = "RuleErro const RuleErrorTransactionSpendingLimitInvalidStaker RuleError = "RuleErrorTransactionSpendingLimitInvalidStaker" const RuleErrorTransactionSpendingLimitInvalidValidator RuleError = "RuleErrorTransactionSpendingLimitInvalidValidator" const RuleErrorTransactionSpendingLimitValidatorDisabledDelegatedStake RuleError = "RuleErrorTransactionSpendingLimitValidatorDisabledDelegatedStake" - -// TODO: move this to a field that can be updated by ParamUpdater. -const StakeLockupEpochDuration = uint64(2) diff --git a/lib/block_view_stake_test.go b/lib/block_view_stake_test.go index cd2caeb52..03cdef4ae 100644 --- a/lib/block_view_stake_test.go +++ b/lib/block_view_stake_test.go @@ -28,6 +28,10 @@ func _testStaking(t *testing.T, flushToDB bool) { mempool, miner := NewTestMiner(t, chain, params, true) chain.snapshot = nil + // For these tests, we set StakeLockupEpochDuration to zero. + // We test the lockup logic in a separate test. + params.StakeLockupEpochDuration = 0 + // Mine a few blocks to give the senderPkString some money. for ii := 0; ii < 10; ii++ { _, err = miner.MineAndProcessSingleBlock(0, mempool) @@ -461,35 +465,6 @@ func _testStaking(t *testing.T, flushToDB bool) { // // UNLOCK STAKE // - { - // RuleErrorInvalidUnlockStakeMustWaitLockupDuration - unlockStakeMetadata := &UnlockStakeMetadata{ - ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber, - EndEpochNumber: currentEpochNumber, - } - _, err = _submitUnlockStakeTxn( - testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, - ) - require.Error(t, err) - require.Contains(t, err.Error(), RuleErrorInvalidUnlockStakeMustWaitLockupDuration) - } - { - // Simulate two epochs passing by seeding a new CurrentEpochEntry. - - // We have to manually delete the CurrentEpochEntry from the UniversalUtxoView - // and ReadOnlyUtxoView just for these tests since utxoView() returns a copy - // and these UtxoViews will return the old cached CurrentEpochEntry otherwise. - mempool.universalUtxoView.DeleteCurrentEpochEntry() - mempool.readOnlyUtxoView.DeleteCurrentEpochEntry() - - err = utxoView().SetCurrentEpochEntry( - &EpochEntry{EpochNumber: currentEpochNumber + 2, FinalBlockHeight: blockHeight + 10}, - blockHeight, - ) - currentEpochNumber, err = utxoView().GetCurrentEpochNumber() - require.NoError(t, err) - } { // RuleErrorProofOfStakeTxnBeforeBlockHeight params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight = math.MaxUint32 @@ -498,8 +473,8 @@ func _testStaking(t *testing.T, flushToDB bool) { unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitUnlockStakeTxn( testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, @@ -515,8 +490,8 @@ func _testStaking(t *testing.T, flushToDB bool) { // RuleErrorInvalidValidatorPKID unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m2PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitUnlockStakeTxn( testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, @@ -553,7 +528,7 @@ func _testStaking(t *testing.T, flushToDB bool) { { // m1 unlocks stake that was assigned to m0. lockedStakeEntries, err := utxoView().GetLockedStakeEntriesInRange( - m0PKID, m1PKID, currentEpochNumber-2, currentEpochNumber-2, + m0PKID, m1PKID, currentEpochNumber, currentEpochNumber, ) require.NoError(t, err) require.Equal(t, len(lockedStakeEntries), 1) @@ -562,8 +537,8 @@ func _testStaking(t *testing.T, flushToDB bool) { m1OldDESOBalanceNanos := getDESOBalanceNanos(m1PkBytes) unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } feeNanos, err := _submitUnlockStakeTxn( testMeta, m1Pub, m1Priv, unlockStakeMetadata, nil, flushToDB, @@ -797,6 +772,10 @@ func _testStakingWithDerivedKey(t *testing.T) { GlobalDeSoParams.EncoderMigrationHeightsList = GetEncoderMigrationHeightsList(¶ms.ForkHeights) chain.snapshot = nil + // For these tests, we set StakeLockupEpochDuration to zero. + // We test the lockup logic in a separate test. + params.StakeLockupEpochDuration = 0 + // Mine a few blocks to give the senderPkString some money. for ii := 0; ii < 10; ii++ { _, err = miner.MineAndProcessSingleBlock(0, mempool) @@ -950,20 +929,10 @@ func _testStakingWithDerivedKey(t *testing.T) { } // Seed a CurrentEpochEntry. - currentEpochNumber := uint64(5) - - _simulateEpochsPassing := func(numEpochs uint64) { - // Simulate epochs passing by seeding a new CurrentEpochEntry. - err = newUtxoView().SetCurrentEpochEntry( - &EpochEntry{EpochNumber: currentEpochNumber + numEpochs, FinalBlockHeight: blockHeight + 10}, - blockHeight, - ) - require.NoError(t, err) - currentEpochNumber, err = newUtxoView().GetCurrentEpochNumber() - require.NoError(t, err) - } - - _simulateEpochsPassing(0) + err = newUtxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 5, FinalBlockHeight: blockHeight + 10}, blockHeight) + require.NoError(t, err) + currentEpochNumber, err := newUtxoView().GetCurrentEpochNumber() + require.NoError(t, err) { // ParamUpdater set min fee rate @@ -1238,8 +1207,6 @@ func _testStakingWithDerivedKey(t *testing.T) { } { // sender unlocks stake using a DerivedKey. - _simulateEpochsPassing(2) - // sender creates a DerivedKey to perform 1 unlock stake operation with m0. stakeLimitKey := MakeStakeLimitKey(m0PKID, senderPKID) txnSpendingLimit := &TransactionSpendingLimit{ @@ -1255,8 +1222,8 @@ func _testStakingWithDerivedKey(t *testing.T) { // sender tries to unlock all stake from m1 using the DerivedKey. Errors. unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1275,11 +1242,10 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender tries to unlock all stake from m1 using the DerivedKey. Errors. - _simulateEpochsPassing(2) unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1291,8 +1257,8 @@ func _testStakingWithDerivedKey(t *testing.T) { senderOldDESOBalanceNanos := getDESOBalanceNanos(senderPkBytes) unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber - 4, - EndEpochNumber: currentEpochNumber - 4, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } feeNanos, err := _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1327,11 +1293,10 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender tries to unlock all stake from m0 using the DerivedKey. Errors. - _simulateEpochsPassing(2) unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1397,11 +1362,10 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender unlocks stake from m0 using the DerivedKey. - _simulateEpochsPassing(2) unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1411,8 +1375,8 @@ func _testStakingWithDerivedKey(t *testing.T) { // sender unlocks stake from m1 using the DerivedKey. unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1460,6 +1424,17 @@ func _testStakingWithDerivedKey(t *testing.T) { ) require.NoError(t, err) + // sender unlocks stake from m0 using the DerivedKey. + unlockStakeMetadata := &UnlockStakeMetadata{ + ValidatorPublicKey: NewPublicKey(m0PkBytes), + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, + } + _, err = _submitStakeTxnWithDerivedKey( + senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, + ) + require.NoError(t, err) + // sender unstakes from m1 using the DerivedKey. unstakeMetadata = &UnstakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), @@ -1470,23 +1445,11 @@ func _testStakingWithDerivedKey(t *testing.T) { ) require.NoError(t, err) - // sender unlocks stake from m0 using the DerivedKey. - _simulateEpochsPassing(2) - unlockStakeMetadata := &UnlockStakeMetadata{ - ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, - } - _, err = _submitStakeTxnWithDerivedKey( - senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, - ) - require.NoError(t, err) - // sender unlocks stake from m1 using the DerivedKey. unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1540,11 +1503,10 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender unlocks stake from m1 using the scoped TransactionSpendingLimit. - _simulateEpochsPassing(2) unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1572,11 +1534,10 @@ func _testStakingWithDerivedKey(t *testing.T) { require.NoError(t, err) // sender unlocks stake from m1 using the global TransactionSpendingLimit. - _simulateEpochsPassing(2) unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, } _, err = _submitStakeTxnWithDerivedKey( senderPkBytes, derivedKeyPriv, MsgDeSoTxn{TxnMeta: unlockStakeMetadata}, @@ -1798,3 +1759,164 @@ func TestGetLockedStakeEntriesInRange(t *testing.T) { require.NoError(t, err) require.Empty(t, lockedStakeEntries) } + +func TestStakeLockupEpochDuration(t *testing.T) { + var err error + + // Initialize balance model fork heights. + setBalanceModelBlockHeights() + defer resetBalanceModelBlockHeights() + + // Initialize test chain and miner. + chain, params, db := NewLowDifficultyBlockchain(t) + mempool, miner := NewTestMiner(t, chain, params, true) + + // Initialize fork heights. + params.ForkHeights.DeSoUnlimitedDerivedKeysBlockHeight = uint32(0) + params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight = uint32(1) + GlobalDeSoParams.EncoderMigrationHeights = GetEncoderMigrationHeights(¶ms.ForkHeights) + GlobalDeSoParams.EncoderMigrationHeightsList = GetEncoderMigrationHeightsList(¶ms.ForkHeights) + chain.snapshot = nil + + // For these tests, we set StakeLockupEpochDuration to 2. + // We test the lockup logic in a separate test. + params.StakeLockupEpochDuration = 2 + + // Mine a few blocks to give the senderPkString some money. + for ii := 0; ii < 10; ii++ { + _, err = miner.MineAndProcessSingleBlock(0, mempool) + require.NoError(t, err) + } + + // We build the testMeta obj after mining blocks so that we save the correct block height. + blockHeight := uint64(chain.blockTip().Height) + 1 + testMeta := &TestMeta{ + t: t, + chain: chain, + params: params, + db: db, + mempool: mempool, + miner: miner, + savedHeight: uint32(blockHeight), + feeRateNanosPerKb: uint64(101), + } + + _registerOrTransferWithTestMeta(testMeta, "m0", senderPkString, m0Pub, senderPrivString, 1e3) + _registerOrTransferWithTestMeta(testMeta, "", senderPkString, paramUpdaterPub, senderPrivString, 1e3) + + m0PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m0PkBytes).PKID + + newUtxoView := func() *UtxoView { + utxoView, err := NewUtxoView(db, params, chain.postgres, chain.snapshot) + require.NoError(t, err) + return utxoView + } + + // Seed a CurrentEpochEntry. + err = newUtxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 5, FinalBlockHeight: blockHeight + 10}, blockHeight) + require.NoError(t, err) + currentEpochNumber, err := newUtxoView().GetCurrentEpochNumber() + require.NoError(t, err) + + { + // ParamUpdater set min fee rate + params.ExtraRegtestParamUpdaterKeys[MakePkMapKey(paramUpdaterPkBytes)] = true + _updateGlobalParamsEntryWithTestMeta( + testMeta, + testMeta.feeRateNanosPerKb, + paramUpdaterPub, + paramUpdaterPriv, + -1, + int64(testMeta.feeRateNanosPerKb), + -1, + -1, + -1, + ) + } + { + // m0 registers as a validator. + registerMetadata := &RegisterAsValidatorMetadata{ + Domains: [][]byte{[]byte("https://m1.com")}, + } + _, _, _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, true) + require.NoError(t, err) + + validatorEntry, err := newUtxoView().GetValidatorByPKID(m0PKID) + require.NoError(t, err) + require.NotNil(t, validatorEntry) + } + { + // m0 stakes with himself. + stakeMetadata := &StakeMetadata{ + ValidatorPublicKey: NewPublicKey(m0PkBytes), + StakeAmountNanos: uint256.NewInt().SetUint64(100), + } + _, err = _submitStakeTxn(testMeta, m0Pub, m0Priv, stakeMetadata, nil, true) + require.NoError(t, err) + + stakeEntry, err := newUtxoView().GetStakeEntry(m0PKID, m0PKID) + require.NoError(t, err) + require.NotNil(t, stakeEntry) + require.Equal(t, stakeEntry.StakeAmountNanos, uint256.NewInt().SetUint64(100)) + } + { + // m0 unstakes from himself. + unstakeMetadata := &UnstakeMetadata{ + ValidatorPublicKey: NewPublicKey(m0PkBytes), + UnstakeAmountNanos: uint256.NewInt().SetUint64(100), + } + _, err = _submitUnstakeTxn(testMeta, m0Pub, m0Priv, unstakeMetadata, nil, true) + require.NoError(t, err) + + stakeEntry, err := newUtxoView().GetStakeEntry(m0PKID, m0PKID) + require.NoError(t, err) + require.Nil(t, stakeEntry) + + lockedStakeEntry, err := newUtxoView().GetLockedStakeEntry(m0PKID, m0PKID, currentEpochNumber) + require.NoError(t, err) + require.NotNil(t, lockedStakeEntry) + require.Equal(t, lockedStakeEntry.LockedAmountNanos, uint256.NewInt().SetUint64(100)) + } + { + // RuleErrorInvalidUnlockStakeMustWaitLockupDuration + unlockStakeMetadata := &UnlockStakeMetadata{ + ValidatorPublicKey: NewPublicKey(m0PkBytes), + StartEpochNumber: currentEpochNumber, + EndEpochNumber: currentEpochNumber, + } + _, err = _submitUnlockStakeTxn(testMeta, m0Pub, m0Priv, unlockStakeMetadata, nil, true) + require.Error(t, err) + require.Contains(t, err.Error(), RuleErrorInvalidUnlockStakeMustWaitLockupDuration) + } + { + // Simulate two epochs passing by seeding a new CurrentEpochEntry. + err = newUtxoView().SetCurrentEpochEntry( + &EpochEntry{EpochNumber: currentEpochNumber + 2, FinalBlockHeight: blockHeight + 10}, + blockHeight, + ) + require.NoError(t, err) + currentEpochNumber, err = newUtxoView().GetCurrentEpochNumber() + require.NoError(t, err) + } + { + // m0 unlocks his stake. + oldDesoBalanceNanos, err := newUtxoView().GetDeSoBalanceNanosForPublicKey(m0PkBytes) + require.NoError(t, err) + + unlockStakeMetadata := &UnlockStakeMetadata{ + ValidatorPublicKey: NewPublicKey(m0PkBytes), + StartEpochNumber: currentEpochNumber - 2, + EndEpochNumber: currentEpochNumber - 2, + } + feeNanos, err := _submitUnlockStakeTxn(testMeta, m0Pub, m0Priv, unlockStakeMetadata, nil, true) + require.NoError(t, err) + + lockedStakeEntry, err := newUtxoView().GetLockedStakeEntry(m0PKID, m0PKID, currentEpochNumber-2) + require.NoError(t, err) + require.Nil(t, lockedStakeEntry) + + newDesoBalanceNanos, err := newUtxoView().GetDeSoBalanceNanosForPublicKey(m0PkBytes) + require.NoError(t, err) + require.Equal(t, oldDesoBalanceNanos-feeNanos+uint64(100), newDesoBalanceNanos) + } +} diff --git a/lib/block_view_types.go b/lib/block_view_types.go index 679d737ec..1b7e44ecf 100644 --- a/lib/block_view_types.go +++ b/lib/block_view_types.go @@ -918,6 +918,10 @@ type UtxoOperation struct { // PrevLockedStakeEntries is a slice of LockedStakeEntries // prior to a unstake or unlock stake txn. PrevLockedStakeEntries []*LockedStakeEntry + + // PrevEpochNumber is the CurrentEpochNumber during the connect logic. This should be used + // during disconnect logic instead of the CurrentEpochNumber which may have changed. + PrevEpochNumber uint64 } func (op *UtxoOperation) RawEncodeWithoutMetadata(blockHeight uint64, skipMetadata ...bool) []byte { @@ -1247,6 +1251,9 @@ func (op *UtxoOperation) RawEncodeWithoutMetadata(blockHeight uint64, skipMetada // PrevLockedStakeEntries data = append(data, EncodeDeSoEncoderSlice(op.PrevLockedStakeEntries, blockHeight, skipMetadata...)...) + + // PrevEpochNumber + data = append(data, UintToBuf(op.PrevEpochNumber)...) } return data @@ -1886,6 +1893,13 @@ func (op *UtxoOperation) RawDecodeWithoutMetadata(blockHeight uint64, rr *bytes. if op.PrevLockedStakeEntries, err = DecodeDeSoEncoderSlice[*LockedStakeEntry](rr); err != nil { return errors.Wrapf(err, "UtxoOperation.Decode: Problem reading PrevLockedStakeEntries: ") } + + // PrevEpochNumber + op.PrevEpochNumber, err = ReadUvarint(rr) + if err != nil { + return errors.Wrapf(err, "UtxoOperation.Decode: Problem reading PrevEpochNumber: ") + } + } return nil diff --git a/lib/constants.go b/lib/constants.go index 53f1c34c6..b029860b7 100644 --- a/lib/constants.go +++ b/lib/constants.go @@ -596,6 +596,10 @@ type DeSoParams struct { // attack the bancor curve to any meaningful measure. CreatorCoinAutoSellThresholdNanos uint64 + // StakeLockupEpochDuration is the number of epochs that a + // user must wait before unlocking their unstaked stake. + StakeLockupEpochDuration uint64 + ForkHeights ForkHeights EncoderMigrationHeights *EncoderMigrationHeights @@ -968,6 +972,9 @@ var DeSoMainnetParams = DeSoParams{ // reserve ratios. CreatorCoinAutoSellThresholdNanos: uint64(10), + // Unstaked stake can be unlocked after a minimum of two elapsed epochs. + StakeLockupEpochDuration: uint64(2), + ForkHeights: MainnetForkHeights, EncoderMigrationHeights: GetEncoderMigrationHeights(&MainnetForkHeights), EncoderMigrationHeightsList: GetEncoderMigrationHeightsList(&MainnetForkHeights), @@ -1196,6 +1203,9 @@ var DeSoTestnetParams = DeSoParams{ // reserve ratios. CreatorCoinAutoSellThresholdNanos: uint64(10), + // Unstaked stake can be unlocked after a minimum of two elapsed epochs. + StakeLockupEpochDuration: uint64(2), + ForkHeights: TestnetForkHeights, EncoderMigrationHeights: GetEncoderMigrationHeights(&TestnetForkHeights), EncoderMigrationHeightsList: GetEncoderMigrationHeightsList(&TestnetForkHeights), From f916c0ca7e80dae678784d4e3b6adb8fa74a2052 Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Mon, 1 May 2023 17:04:54 -0400 Subject: [PATCH 11/22] Clean up test file changes. --- lib/block_view_stake_test.go | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/block_view_stake_test.go b/lib/block_view_stake_test.go index 03cdef4ae..3f82d6384 100644 --- a/lib/block_view_stake_test.go +++ b/lib/block_view_stake_test.go @@ -72,7 +72,7 @@ func _testStaking(t *testing.T, flushToDB bool) { } // Seed a CurrentEpochEntry. - err = utxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 5, FinalBlockHeight: blockHeight + 10}, blockHeight) + err = utxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}, blockHeight) require.NoError(t, err) currentEpochNumber, err := utxoView().GetCurrentEpochNumber() require.NoError(t, err) @@ -929,7 +929,7 @@ func _testStakingWithDerivedKey(t *testing.T) { } // Seed a CurrentEpochEntry. - err = newUtxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 5, FinalBlockHeight: blockHeight + 10}, blockHeight) + err = newUtxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}, blockHeight) require.NoError(t, err) currentEpochNumber, err := newUtxoView().GetCurrentEpochNumber() require.NoError(t, err) @@ -1207,6 +1207,7 @@ func _testStakingWithDerivedKey(t *testing.T) { } { // sender unlocks stake using a DerivedKey. + // sender creates a DerivedKey to perform 1 unlock stake operation with m0. stakeLimitKey := MakeStakeLimitKey(m0PKID, senderPKID) txnSpendingLimit := &TransactionSpendingLimit{ @@ -1424,6 +1425,16 @@ func _testStakingWithDerivedKey(t *testing.T) { ) require.NoError(t, err) + // sender unstakes from m1 using the DerivedKey. + unstakeMetadata = &UnstakeMetadata{ + ValidatorPublicKey: NewPublicKey(m1PkBytes), + UnstakeAmountNanos: uint256.NewInt().SetUint64(25), + } + _, err = _submitUnstakeTxn( + testMeta, senderPkString, senderPrivString, unstakeMetadata, nil, true, + ) + require.NoError(t, err) + // sender unlocks stake from m0 using the DerivedKey. unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), @@ -1435,16 +1446,6 @@ func _testStakingWithDerivedKey(t *testing.T) { ) require.NoError(t, err) - // sender unstakes from m1 using the DerivedKey. - unstakeMetadata = &UnstakeMetadata{ - ValidatorPublicKey: NewPublicKey(m1PkBytes), - UnstakeAmountNanos: uint256.NewInt().SetUint64(25), - } - _, err = _submitUnstakeTxn( - testMeta, senderPkString, senderPrivString, unstakeMetadata, nil, true, - ) - require.NoError(t, err) - // sender unlocks stake from m1 using the DerivedKey. unlockStakeMetadata = &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m1PkBytes), @@ -1890,6 +1891,8 @@ func TestStakeLockupEpochDuration(t *testing.T) { } { // Simulate two epochs passing by seeding a new CurrentEpochEntry. + // Note that we can't test the disconnect logic after these tests + // since we have updated the CurrentEpochNumber. err = newUtxoView().SetCurrentEpochEntry( &EpochEntry{EpochNumber: currentEpochNumber + 2, FinalBlockHeight: blockHeight + 10}, blockHeight, From e4bfe2466b086eb27e8de0c79c935549333420ae Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Tue, 2 May 2023 12:47:46 -0400 Subject: [PATCH 12/22] Add jailed status to validator entries. --- lib/block_view_validator.go | 80 ++++++++++++--- lib/block_view_validator_test.go | 169 ++++++++++++++++++++++--------- lib/db_utils.go | 19 ++++ lib/db_utils_test.go | 20 ++++ 4 files changed, 228 insertions(+), 60 deletions(-) diff --git a/lib/block_view_validator.go b/lib/block_view_validator.go index fb9ecbdc5..a7d0baa24 100644 --- a/lib/block_view_validator.go +++ b/lib/block_view_validator.go @@ -30,10 +30,20 @@ type ValidatorEntry struct { VotingSignatureBlockHeight uint64 TotalStakeAmountNanos *uint256.Int RegisteredAtBlockHeight uint64 + Status ValidatorStatus + LastActiveEpochNumber uint64 ExtraData map[string][]byte isDeleted bool } +type ValidatorStatus uint8 + +const ( + ValidatorStatusInvalid ValidatorStatus = 0 + ValidatorStatusActive ValidatorStatus = 1 + ValidatorStatusJailed ValidatorStatus = 2 +) + type ValidatorMapKey struct { // The MapKey has to contain all fields that are used in Badger keys. // Otherwise, an update to the UtxoView will not be able to update or @@ -41,6 +51,7 @@ type ValidatorMapKey struct { ValidatorPKID PKID TotalStakeAmountNanos uint256.Int RegisteredAtBlockHeight uint64 + Status ValidatorStatus } func (validatorEntry *ValidatorEntry) Copy() *ValidatorEntry { @@ -61,6 +72,8 @@ func (validatorEntry *ValidatorEntry) Copy() *ValidatorEntry { VotingSignatureBlockHeight: validatorEntry.VotingSignatureBlockHeight, TotalStakeAmountNanos: validatorEntry.TotalStakeAmountNanos.Clone(), RegisteredAtBlockHeight: validatorEntry.RegisteredAtBlockHeight, + Status: validatorEntry.Status, + LastActiveEpochNumber: validatorEntry.LastActiveEpochNumber, ExtraData: copyExtraData(validatorEntry.ExtraData), isDeleted: validatorEntry.isDeleted, } @@ -71,6 +84,7 @@ func (validatorEntry *ValidatorEntry) ToMapKey() ValidatorMapKey { ValidatorPKID: *validatorEntry.ValidatorPKID, TotalStakeAmountNanos: *validatorEntry.TotalStakeAmountNanos, RegisteredAtBlockHeight: validatorEntry.RegisteredAtBlockHeight, + Status: validatorEntry.Status, } } @@ -91,6 +105,8 @@ func (validatorEntry *ValidatorEntry) RawEncodeWithoutMetadata(blockHeight uint6 data = append(data, UintToBuf(validatorEntry.VotingSignatureBlockHeight)...) data = append(data, EncodeUint256(validatorEntry.TotalStakeAmountNanos)...) data = append(data, UintToBuf(validatorEntry.RegisteredAtBlockHeight)...) + data = append(data, EncodeUint16(uint16(validatorEntry.Status))...) + data = append(data, UintToBuf(validatorEntry.LastActiveEpochNumber)...) data = append(data, EncodeExtraData(validatorEntry.ExtraData)...) return data } @@ -163,6 +179,22 @@ func (validatorEntry *ValidatorEntry) RawDecodeWithoutMetadata(blockHeight uint6 return errors.Wrapf(err, "ValidatorEntry.Decode: Problem reading RegisteredAtBlockHeight: ") } + // Status + status, err := ReadUint16(rr) + if err != nil { + return errors.Wrapf(err, "ValidatorEntry.Decode: Problem reading Status: ") + } + if status > math.MaxUint8 { + return fmt.Errorf("ValidatorEntry.Decode: ValidatorEntry.Status overflows uint8: %d", status) + } + validatorEntry.Status = ValidatorStatus(uint8(status)) + + // LastActiveEpochNumber + validatorEntry.LastActiveEpochNumber, err = ReadUvarint(rr) + if err != nil { + return errors.Wrapf(err, "ValidatorEntry.Decode: Problem reading LastActiveEpochNumber: ") + } + // ExtraData validatorEntry.ExtraData, err = DecodeExtraData(rr) if err != nil { @@ -475,6 +507,7 @@ func DBKeyForValidatorByPKID(validatorEntry *ValidatorEntry) []byte { func DBKeyForValidatorByStake(validatorEntry *ValidatorEntry) []byte { key := append([]byte{}, Prefixes.PrefixValidatorByStake...) + key = append(key, EncodeUint16(uint16(validatorEntry.Status))...) // TotalStakeAmountNanos will never be nil here, but EncodeOptionalUint256 // is used because it provides a fixed-width encoding of uint256.Ints. key = append(key, EncodeOptionalUint256(validatorEntry.TotalStakeAmountNanos)...) // Highest stake first @@ -518,7 +551,7 @@ func DBGetValidatorByPKIDWithTxn(txn *badger.Txn, snap *Snapshot, pkid *PKID) (* return validatorEntry, nil } -func DBGetTopValidatorsByStake( +func DBGetTopActiveValidatorsByStake( handle *badger.DB, snap *Snapshot, limit int, @@ -532,13 +565,14 @@ func DBGetTopValidatorsByStake( validatorKeysToSkip.Add(string(DBKeyForValidatorByStake(validatorEntryToSkip))) } - // Retrieve top N ValidatorEntry PKIDs by stake. + // Retrieve top N active ValidatorEntry PKIDs by stake. key := append([]byte{}, Prefixes.PrefixValidatorByStake...) + key = append(key, EncodeUint16(uint16(ValidatorStatusActive))...) _, validatorPKIDsBytes, err := EnumerateKeysForPrefixWithLimitOffsetOrder( handle, key, limit, nil, true, validatorKeysToSkip, ) if err != nil { - return nil, errors.Wrapf(err, "DBGetTopValidatorsByStake: problem retrieving top validators: ") + return nil, errors.Wrapf(err, "DBGetTopActiveValidatorsByStake: problem retrieving top validators: ") } // For each PKID, retrieve the ValidatorEntry by PKID. @@ -547,12 +581,12 @@ func DBGetTopValidatorsByStake( validatorPKID := &PKID{} exists, err := DecodeFromBytes(validatorPKID, bytes.NewReader(validatorPKIDBytes)) if !exists || err != nil { - return nil, errors.Wrapf(err, "DBGetTopValidatorsByStake: problem reading ValidatorPKID: ") + return nil, errors.Wrapf(err, "DBGetTopActiveValidatorsByStake: problem reading ValidatorPKID: ") } // Retrieve ValidatorEntry by PKID. validatorEntry, err := DBGetValidatorByPKID(handle, snap, validatorPKID) if err != nil { - return nil, errors.Wrapf(err, "DBGetTopValidatorsByStake: problem retrieving validator by PKID: ") + return nil, errors.Wrapf(err, "DBGetTopActiveValidatorsByStake: problem retrieving validator by PKID: ") } validatorEntries = append(validatorEntries, validatorEntry) } @@ -902,12 +936,31 @@ func (bav *UtxoView) _connectRegisterAsValidator( totalStakeAmountNanos = prevValidatorEntry.TotalStakeAmountNanos.Clone() } - // Set RegisteredAtBlockHeight only if this is a new ValidatorEntry. + // Set RegisteredAtBlockHeight to CurrentBlockHeight if this is a new ValidatorEntry. + // Otherwise, retain the existing RegisteredAtBlockHeight. registeredAtBlockHeight := uint64(blockHeight) if prevValidatorEntry != nil { registeredAtBlockHeight = prevValidatorEntry.RegisteredAtBlockHeight } + // Set Status to Active if this is a new ValidatorEntry. + // Otherwise, retain the existing Status. + status := ValidatorStatusActive + if prevValidatorEntry != nil { + status = prevValidatorEntry.Status + } + + // Set LastActiveEpochNumber to CurrentEpochNumber if this is a new ValidatorEntry. + // Otherwise, retain the existing LastActiveEpochNumber. + var lastActiveEpochNumber uint64 + if prevValidatorEntry != nil { + lastActiveEpochNumber = prevValidatorEntry.LastActiveEpochNumber + } else { + // Retrieve the CurrentEpochNumber. + currentEpochNumber := uint64(0) // TODO: update + lastActiveEpochNumber = currentEpochNumber + } + // Retrieve existing ExtraData to merge with any new ExtraData. var prevExtraData map[string][]byte if prevValidatorEntry != nil { @@ -927,6 +980,8 @@ func (bav *UtxoView) _connectRegisterAsValidator( VotingSignatureBlockHeight: txMeta.VotingSignatureBlockHeight, TotalStakeAmountNanos: totalStakeAmountNanos, RegisteredAtBlockHeight: registeredAtBlockHeight, + Status: status, + LastActiveEpochNumber: lastActiveEpochNumber, ExtraData: mergeExtraData(prevExtraData, txn.ExtraData), } // Set the ValidatorEntry. @@ -1131,8 +1186,7 @@ func (bav *UtxoView) _connectUnregisterAsValidator( if err != nil { return 0, 0, nil, errors.Wrapf(err, "_connectUnregisterAsValidator: ") } - // Note that we don't need to check isDeleted because the Get returns nil if isDeleted=true. - if prevValidatorEntry == nil { + if prevValidatorEntry == nil || prevValidatorEntry.isDeleted { return 0, 0, nil, errors.Wrapf(RuleErrorValidatorNotFound, "_connectUnregisterAsValidator: ") } bav._deleteValidatorEntryMappings(prevValidatorEntry) @@ -1388,7 +1442,7 @@ func (bav *UtxoView) GetValidatorByPublicKey(validatorPublicKey *PublicKey) (*Va return validatorEntry, nil } -func (bav *UtxoView) GetTopValidatorsByStake(limit int) ([]*ValidatorEntry, error) { +func (bav *UtxoView) GetTopActiveValidatorsByStake(limit int) ([]*ValidatorEntry, error) { // Validate limit param. if limit <= 0 { return []*ValidatorEntry{}, nil @@ -1404,13 +1458,13 @@ func (bav *UtxoView) GetTopValidatorsByStake(limit int) ([]*ValidatorEntry, erro utxoViewValidatorEntries = append(utxoViewValidatorEntries, validatorEntry) } // Pull top N ValidatorEntries from the database (not present in the UtxoView). - validatorEntries, err := DBGetTopValidatorsByStake(bav.Handle, bav.Snapshot, limit, utxoViewValidatorEntries) + validatorEntries, err := DBGetTopActiveValidatorsByStake(bav.Handle, bav.Snapshot, limit, utxoViewValidatorEntries) if err != nil { - return nil, errors.Wrapf(err, "UtxoView.GetTopValidatorsByStake: error retrieving entries from db: ") + return nil, errors.Wrapf(err, "UtxoView.GetTopActiveValidatorsByStake: error retrieving entries from db: ") } - // Add !isDeleted ValidatorEntries from the UtxoView to the ValidatorEntries from the db. + // Add !isDeleted, active ValidatorEntries from the UtxoView to the ValidatorEntries from the db. for _, validatorEntry := range utxoViewValidatorEntries { - if !validatorEntry.isDeleted { + if !validatorEntry.isDeleted && validatorEntry.Status == ValidatorStatusActive { validatorEntries = append(validatorEntries, validatorEntry) } } diff --git a/lib/block_view_validator_test.go b/lib/block_view_validator_test.go index 889b80c64..e5c2ff069 100644 --- a/lib/block_view_validator_test.go +++ b/lib/block_view_validator_test.go @@ -172,12 +172,12 @@ func _testValidatorRegistration(t *testing.T, flushToDB bool) { require.Equal(t, string(validatorEntry.ExtraData["TestKey"]), "TestValue1") } { - // Query: retrieve top ValidatorEntries by stake - validatorEntries, err = utxoView().GetTopValidatorsByStake(0) + // Query: retrieve top active ValidatorEntries by stake + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(0) require.NoError(t, err) require.Empty(t, validatorEntries) - validatorEntries, err = utxoView().GetTopValidatorsByStake(2) + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(2) require.NoError(t, err) require.Len(t, validatorEntries, 1) require.Equal(t, validatorEntries[0].ValidatorPKID, m0PKID) @@ -235,8 +235,8 @@ func _testValidatorRegistration(t *testing.T, flushToDB bool) { require.Nil(t, validatorEntry) } { - // Query: retrieve top ValidatorEntries by stake - validatorEntries, err = utxoView().GetTopValidatorsByStake(1) + // Query: retrieve top active ValidatorEntries by stake + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(1) require.NoError(t, err) require.Empty(t, validatorEntries) } @@ -619,12 +619,12 @@ func _testValidatorRegistrationWithDerivedKey(t *testing.T) { _executeAllTestRollbackAndFlush(testMeta) } -func TestGetTopValidatorsByStake(t *testing.T) { - _testGetTopValidatorsByStake(t, false) - _testGetTopValidatorsByStake(t, true) +func TestGetTopActiveValidatorsByStake(t *testing.T) { + _testGetTopActiveValidatorsByStake(t, false) + _testGetTopActiveValidatorsByStake(t, true) } -func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { +func _testGetTopActiveValidatorsByStake(t *testing.T, flushToDB bool) { var validatorEntries []*ValidatorEntry var err error @@ -703,7 +703,7 @@ func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { require.NoError(t, err) // Verify top validators. - validatorEntries, err = utxoView().GetTopValidatorsByStake(10) + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(10) require.NoError(t, err) require.Len(t, validatorEntries, 1) require.Equal(t, validatorEntries[0].ValidatorPKID, m0PKID) @@ -720,7 +720,7 @@ func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { require.NoError(t, err) // Verify top validators. - validatorEntries, err = utxoView().GetTopValidatorsByStake(10) + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(10) require.NoError(t, err) require.Len(t, validatorEntries, 2) } @@ -735,7 +735,7 @@ func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { require.NoError(t, err) // Verify top validators. - validatorEntries, err = utxoView().GetTopValidatorsByStake(10) + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(10) require.NoError(t, err) require.Len(t, validatorEntries, 3) } @@ -765,7 +765,7 @@ func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { require.NoError(t, err) // Verify top validators. - validatorEntries, err = utxoView().GetTopValidatorsByStake(10) + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(10) require.NoError(t, err) require.Len(t, validatorEntries, 3) require.Equal(t, validatorEntries[0].ValidatorPKID, m2PKID) @@ -784,7 +784,7 @@ func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { _, err = _submitUnstakeTxn(testMeta, m3Pub, m3Priv, unstakeMetadata, nil, flushToDB) // Verify top validators. - validatorEntries, err = utxoView().GetTopValidatorsByStake(10) + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(10) require.NoError(t, err) require.Len(t, validatorEntries, 3) require.Equal(t, validatorEntries[0].ValidatorPKID, m2PKID) @@ -803,7 +803,7 @@ func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { _, err = _submitUnstakeTxn(testMeta, m3Pub, m3Priv, unstakeMetadata, nil, flushToDB) // Verify top validators. - validatorEntries, err = utxoView().GetTopValidatorsByStake(10) + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(10) require.NoError(t, err) require.Len(t, validatorEntries, 3) require.Equal(t, validatorEntries[0].ValidatorPKID, m2PKID) @@ -819,7 +819,7 @@ func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { require.NoError(t, err) // Verify top validators. - validatorEntries, err = utxoView().GetTopValidatorsByStake(10) + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(10) require.NoError(t, err) require.Len(t, validatorEntries, 2) require.Equal(t, validatorEntries[0].ValidatorPKID, m0PKID) @@ -837,7 +837,7 @@ func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { require.NoError(t, err) // Verify top validators. - validatorEntries, err = utxoView().GetTopValidatorsByStake(10) + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(10) require.NoError(t, err) require.Len(t, validatorEntries, 2) require.Equal(t, validatorEntries[0].ValidatorPKID, m1PKID) @@ -855,7 +855,7 @@ func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { require.NoError(t, err) // Verify top validators. - validatorEntries, err = utxoView().GetTopValidatorsByStake(10) + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(10) require.NoError(t, err) require.Len(t, validatorEntries, 2) require.Equal(t, validatorEntries[0].ValidatorPKID, m1PKID) @@ -865,7 +865,7 @@ func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { } { // Verify top validators with LIMIT. - validatorEntries, err = utxoView().GetTopValidatorsByStake(1) + validatorEntries, err = utxoView().GetTopActiveValidatorsByStake(1) require.NoError(t, err) require.Len(t, validatorEntries, 1) require.Equal(t, validatorEntries[0].ValidatorPKID, m1PKID) @@ -877,7 +877,7 @@ func _testGetTopValidatorsByStake(t *testing.T, flushToDB bool) { _executeAllTestRollbackAndFlush(testMeta) } -func TestGetTopValidatorsByStakeMergingDbAndUtxoView(t *testing.T) { +func TestGetTopActiveValidatorsByStakeMergingDbAndUtxoView(t *testing.T) { // For this test, we manually place ValidatorEntries in the database and // UtxoView to test merging the two to determine the TopValidatorsByStake. @@ -887,14 +887,24 @@ func TestGetTopValidatorsByStakeMergingDbAndUtxoView(t *testing.T) { require.NoError(t, err) blockHeight := uint64(chain.blockTip().Height + 1) + // m0 will be stored in the db with Stake=100. m0PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m0PkBytes).PKID + // m1 will be stored in the db with Stake=400 and Status=Jailed. m1PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m1PkBytes).PKID + // m2 will be stored in the db and UtxoView with Stake=300. m2PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m2PkBytes).PKID + // m3 will be stored in the db and UtxoView with Stake=600 and isDeleted=true. + m3PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m3PkBytes).PKID + // m4 will be stored in the UtxoView only with Stake=50. + m4PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m4PkBytes).PKID + // m5 will be stored in the UtxoView only with Stake=500 and Status=Jailed. + m5PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m5PkBytes).PKID // Store m0's ValidatorEntry in the db with TotalStake = 100 nanos. validatorEntry := &ValidatorEntry{ ValidatorPKID: m0PKID, TotalStakeAmountNanos: uint256.NewInt().SetUint64(100), + Status: ValidatorStatusActive, } utxoView._setValidatorEntryMappings(validatorEntry) require.NoError(t, utxoView.FlushToDb(blockHeight)) @@ -908,65 +918,130 @@ func TestGetTopValidatorsByStakeMergingDbAndUtxoView(t *testing.T) { // Verify m0 is not stored in the UtxoView. require.Empty(t, utxoView.ValidatorMapKeyToValidatorEntry) - // Store m1's ValidatorEntry in the database with TotalStake = 200 nanos. + // Store m1's jailed ValidatorEntry in the db with TotalStake = 400 nanos. validatorEntry = &ValidatorEntry{ ValidatorPKID: m1PKID, - TotalStakeAmountNanos: uint256.NewInt().SetUint64(200), + TotalStakeAmountNanos: uint256.NewInt().SetUint64(400), + Status: ValidatorStatusJailed, } utxoView._setValidatorEntryMappings(validatorEntry) require.NoError(t, utxoView.FlushToDb(blockHeight)) - // Fetch m1 so it is also cached in the UtxoView. - validatorEntry, err = utxoView.GetValidatorByPKID(m1PKID) + // Verify m1 is stored in the db. + validatorEntry, err = DBGetValidatorByPKID(db, chain.snapshot, m1PKID) require.NoError(t, err) require.NotNil(t, validatorEntry) + require.Equal(t, validatorEntry.TotalStakeAmountNanos, uint256.NewInt().SetUint64(400)) + require.Equal(t, validatorEntry.Status, ValidatorStatusJailed) - // Verify m1 is stored in the db. - validatorEntry, err = DBGetValidatorByPKID(db, chain.snapshot, m1PKID) + // Store m2's ValidatorEntry in the db with TotalStake = 300 nanos. + m2ValidatorEntry := &ValidatorEntry{ + ValidatorPKID: m2PKID, + TotalStakeAmountNanos: uint256.NewInt().SetUint64(300), + Status: ValidatorStatusActive, + } + utxoView._setValidatorEntryMappings(m2ValidatorEntry) + require.NoError(t, utxoView.FlushToDb(blockHeight)) + + // Verify m2 is stored in the db. + validatorEntry, err = DBGetValidatorByPKID(db, chain.snapshot, m2PKID) require.NoError(t, err) require.NotNil(t, validatorEntry) - require.Equal(t, validatorEntry.TotalStakeAmountNanos, uint256.NewInt().SetUint64(200)) + require.Equal(t, validatorEntry.TotalStakeAmountNanos, uint256.NewInt().SetUint64(300)) - // Verify m1 is also stored in the UtxoView. + // Store m3's ValidatorEntry in the db with TotalStake = 600 nanos. + m3ValidatorEntry := &ValidatorEntry{ + ValidatorPKID: m3PKID, + TotalStakeAmountNanos: uint256.NewInt().SetUint64(600), + Status: ValidatorStatusActive, + } + utxoView._setValidatorEntryMappings(m3ValidatorEntry) + require.NoError(t, utxoView.FlushToDb(blockHeight)) + + // Verify m3 is stored in the db. + validatorEntry, err = DBGetValidatorByPKID(db, chain.snapshot, m3PKID) + require.NoError(t, err) + require.NotNil(t, validatorEntry) + require.Equal(t, validatorEntry.TotalStakeAmountNanos, uint256.NewInt().SetUint64(600)) + + // Fetch m2 so it is also cached in the UtxoView. + validatorEntry, err = utxoView.GetValidatorByPKID(m2PKID) + require.NoError(t, err) + require.NotNil(t, validatorEntry) + + // Verify m2 is also stored in the UtxoView. require.Len(t, utxoView.ValidatorMapKeyToValidatorEntry, 1) - require.Equal(t, utxoView.ValidatorMapKeyToValidatorEntry[validatorEntry.ToMapKey()].ValidatorPKID, m1PKID) + require.Equal(t, utxoView.ValidatorMapKeyToValidatorEntry[m2ValidatorEntry.ToMapKey()].ValidatorPKID, m2PKID) require.Equal( t, - utxoView.ValidatorMapKeyToValidatorEntry[validatorEntry.ToMapKey()].TotalStakeAmountNanos, - uint256.NewInt().SetUint64(200), + utxoView.ValidatorMapKeyToValidatorEntry[m2ValidatorEntry.ToMapKey()].TotalStakeAmountNanos, + uint256.NewInt().SetUint64(300), ) - // Store m2's ValidatorEntry in the UtxoView. - m2ValidatorEntry := &ValidatorEntry{ - ValidatorPKID: m2PKID, + // Store m3's ValidatorEntry in the UtxoView with isDeleted=true. + m3ValidatorEntry.isDeleted = true + utxoView._setValidatorEntryMappings(m3ValidatorEntry) + + // Verify m3 is stored in the UtxoView with isDeleted=true. + require.Equal(t, utxoView.ValidatorMapKeyToValidatorEntry[m3ValidatorEntry.ToMapKey()].ValidatorPKID, m3PKID) + require.True(t, utxoView.ValidatorMapKeyToValidatorEntry[m3ValidatorEntry.ToMapKey()].isDeleted) + + // Store m4's ValidatorEntry in the UtxoView with TotalStake = 50 nanos. + m4ValidatorEntry := &ValidatorEntry{ + ValidatorPKID: m4PKID, TotalStakeAmountNanos: uint256.NewInt().SetUint64(50), + Status: ValidatorStatusActive, } - utxoView._setValidatorEntryMappings(m2ValidatorEntry) + utxoView._setValidatorEntryMappings(m4ValidatorEntry) - // Verify m2 is not stored in the db. - validatorEntry, err = DBGetValidatorByPKID(db, chain.snapshot, m2PKID) + // Verify m4 is not stored in the db. + validatorEntry, err = DBGetValidatorByPKID(db, chain.snapshot, m4PKID) require.NoError(t, err) require.Nil(t, validatorEntry) - // Verify m2 is stored in the UtxoView. - require.Len(t, utxoView.ValidatorMapKeyToValidatorEntry, 2) - - require.Equal(t, utxoView.ValidatorMapKeyToValidatorEntry[m2ValidatorEntry.ToMapKey()].ValidatorPKID, m2PKID) + // Verify m4 is stored in the UtxoView. + require.Len(t, utxoView.ValidatorMapKeyToValidatorEntry, 3) + require.Equal(t, utxoView.ValidatorMapKeyToValidatorEntry[m4ValidatorEntry.ToMapKey()].ValidatorPKID, m4PKID) require.Equal( t, - utxoView.ValidatorMapKeyToValidatorEntry[m2ValidatorEntry.ToMapKey()].TotalStakeAmountNanos, + utxoView.ValidatorMapKeyToValidatorEntry[m4ValidatorEntry.ToMapKey()].TotalStakeAmountNanos, uint256.NewInt().SetUint64(50), ) - // Fetch TopValidatorsByStake merging ValidatorEntries from the db and UtxoView. - validatorEntries, err := utxoView.GetTopValidatorsByStake(3) + // Store m5's jailed ValidatorEntry in the UtxoView with TotalStake = 500 nanos. + m5ValidatorEntry := &ValidatorEntry{ + ValidatorPKID: m5PKID, + TotalStakeAmountNanos: uint256.NewInt().SetUint64(500), + Status: ValidatorStatusJailed, + } + utxoView._setValidatorEntryMappings(m5ValidatorEntry) + + // Verify m5 is not stored in the db. + validatorEntry, err = DBGetValidatorByPKID(db, chain.snapshot, m5PKID) + require.NoError(t, err) + require.Nil(t, validatorEntry) + + // Verify m5 is stored in the UtxoView. + require.Len(t, utxoView.ValidatorMapKeyToValidatorEntry, 4) + require.Equal(t, utxoView.ValidatorMapKeyToValidatorEntry[m5ValidatorEntry.ToMapKey()].ValidatorPKID, m5PKID) + require.Equal( + t, + utxoView.ValidatorMapKeyToValidatorEntry[m5ValidatorEntry.ToMapKey()].TotalStakeAmountNanos, + uint256.NewInt().SetUint64(500), + ) + require.Equal( + t, utxoView.ValidatorMapKeyToValidatorEntry[m5ValidatorEntry.ToMapKey()].Status, ValidatorStatusJailed, + ) + + // Fetch TopActiveValidatorsByStake merging ValidatorEntries from the db and UtxoView. + validatorEntries, err := utxoView.GetTopActiveValidatorsByStake(6) require.NoError(t, err) require.Len(t, validatorEntries, 3) - require.Equal(t, validatorEntries[0].ValidatorPKID, m1PKID) - require.Equal(t, validatorEntries[0].TotalStakeAmountNanos, uint256.NewInt().SetUint64(200)) + require.Equal(t, validatorEntries[0].ValidatorPKID, m2PKID) + require.Equal(t, validatorEntries[0].TotalStakeAmountNanos, uint256.NewInt().SetUint64(300)) require.Equal(t, validatorEntries[1].ValidatorPKID, m0PKID) require.Equal(t, validatorEntries[1].TotalStakeAmountNanos, uint256.NewInt().SetUint64(100)) - require.Equal(t, validatorEntries[2].ValidatorPKID, m2PKID) + require.Equal(t, validatorEntries[2].ValidatorPKID, m4PKID) require.Equal(t, validatorEntries[2].TotalStakeAmountNanos, uint256.NewInt().SetUint64(50)) } diff --git a/lib/db_utils.go b/lib/db_utils.go index 1657ff25a..500dd359c 100644 --- a/lib/db_utils.go +++ b/lib/db_utils.go @@ -4216,6 +4216,25 @@ func DecodeUint64(scoreBytes []byte) uint64 { return binary.BigEndian.Uint64(scoreBytes) } +func EncodeUint16(num uint16) []byte { + numBytes := make([]byte, 2) + binary.BigEndian.PutUint16(numBytes, num) + return numBytes +} + +func DecodeUint16(numBytes []byte) uint16 { + return binary.BigEndian.Uint16(numBytes) +} + +func ReadUint16(rr *bytes.Reader) (uint16, error) { + var numBytes [2]byte + _, err := io.ReadFull(rr, numBytes[:]) + if err != nil { + return 0, err + } + return DecodeUint16(numBytes[:]), nil +} + func DbPutNanosPurchasedWithTxn(txn *badger.Txn, snap *Snapshot, nanosPurchased uint64) error { return DBSetWithTxn(txn, snap, Prefixes.PrefixNanosPurchased, EncodeUint64(nanosPurchased)) } diff --git a/lib/db_utils_test.go b/lib/db_utils_test.go index b9e9acef8..e36335914 100644 --- a/lib/db_utils_test.go +++ b/lib/db_utils_test.go @@ -1,8 +1,10 @@ package lib import ( + "bytes" "io/ioutil" "log" + "math" "math/big" "testing" "time" @@ -665,3 +667,21 @@ func TestDeleteExpiredTransactorNonceEntries(t *testing.T) { } } + +func TestEncodeUint16(t *testing.T) { + for _, num := range []uint16{0, 95, math.MaxUint16} { + // Encode to bytes. + encoded := EncodeUint16(num) + require.Len(t, encoded, 2) + + // Decode from bytes. + decoded := DecodeUint16(encoded) + require.Equal(t, num, decoded) + + // Read from bytes. + rr := bytes.NewReader(encoded) + decoded2, err := ReadUint16(rr) + require.NoError(t, err) + require.Equal(t, num, decoded2) + } +} From 38e00002eb93780082166f305287038613fcbec3 Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Tue, 2 May 2023 13:08:53 -0400 Subject: [PATCH 13/22] Set stake lockup period to 3 epochs. --- lib/block_view_stake_test.go | 12 ++++++------ lib/constants.go | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/block_view_stake_test.go b/lib/block_view_stake_test.go index 3f82d6384..516ae9541 100644 --- a/lib/block_view_stake_test.go +++ b/lib/block_view_stake_test.go @@ -1779,9 +1779,9 @@ func TestStakeLockupEpochDuration(t *testing.T) { GlobalDeSoParams.EncoderMigrationHeightsList = GetEncoderMigrationHeightsList(¶ms.ForkHeights) chain.snapshot = nil - // For these tests, we set StakeLockupEpochDuration to 2. + // For these tests, we set StakeLockupEpochDuration to 3. // We test the lockup logic in a separate test. - params.StakeLockupEpochDuration = 2 + params.StakeLockupEpochDuration = 3 // Mine a few blocks to give the senderPkString some money. for ii := 0; ii < 10; ii++ { @@ -1890,11 +1890,11 @@ func TestStakeLockupEpochDuration(t *testing.T) { require.Contains(t, err.Error(), RuleErrorInvalidUnlockStakeMustWaitLockupDuration) } { - // Simulate two epochs passing by seeding a new CurrentEpochEntry. + // Simulate three epochs passing by seeding a new CurrentEpochEntry. // Note that we can't test the disconnect logic after these tests // since we have updated the CurrentEpochNumber. err = newUtxoView().SetCurrentEpochEntry( - &EpochEntry{EpochNumber: currentEpochNumber + 2, FinalBlockHeight: blockHeight + 10}, + &EpochEntry{EpochNumber: currentEpochNumber + 3, FinalBlockHeight: blockHeight + 10}, blockHeight, ) require.NoError(t, err) @@ -1908,8 +1908,8 @@ func TestStakeLockupEpochDuration(t *testing.T) { unlockStakeMetadata := &UnlockStakeMetadata{ ValidatorPublicKey: NewPublicKey(m0PkBytes), - StartEpochNumber: currentEpochNumber - 2, - EndEpochNumber: currentEpochNumber - 2, + StartEpochNumber: currentEpochNumber - 3, + EndEpochNumber: currentEpochNumber - 3, } feeNanos, err := _submitUnlockStakeTxn(testMeta, m0Pub, m0Priv, unlockStakeMetadata, nil, true) require.NoError(t, err) diff --git a/lib/constants.go b/lib/constants.go index b029860b7..31fbe97c2 100644 --- a/lib/constants.go +++ b/lib/constants.go @@ -972,8 +972,8 @@ var DeSoMainnetParams = DeSoParams{ // reserve ratios. CreatorCoinAutoSellThresholdNanos: uint64(10), - // Unstaked stake can be unlocked after a minimum of two elapsed epochs. - StakeLockupEpochDuration: uint64(2), + // Unstaked stake can be unlocked after a minimum of N elapsed epochs. + StakeLockupEpochDuration: uint64(3), ForkHeights: MainnetForkHeights, EncoderMigrationHeights: GetEncoderMigrationHeights(&MainnetForkHeights), @@ -1203,8 +1203,8 @@ var DeSoTestnetParams = DeSoParams{ // reserve ratios. CreatorCoinAutoSellThresholdNanos: uint64(10), - // Unstaked stake can be unlocked after a minimum of two elapsed epochs. - StakeLockupEpochDuration: uint64(2), + // Unstaked stake can be unlocked after a minimum of N elapsed epochs. + StakeLockupEpochDuration: uint64(3), ForkHeights: TestnetForkHeights, EncoderMigrationHeights: GetEncoderMigrationHeights(&TestnetForkHeights), From 4d572e1302d4b6d30fde080379c824e4ce918ebd Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Tue, 2 May 2023 14:56:54 -0400 Subject: [PATCH 14/22] Set LastActiveEpochNumber to CurrentEpochNumber. --- lib/block_view_validator.go | 7 ++++++- lib/block_view_validator_test.go | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/block_view_validator.go b/lib/block_view_validator.go index 33887110b..bbaabe255 100644 --- a/lib/block_view_validator.go +++ b/lib/block_view_validator.go @@ -954,10 +954,15 @@ func (bav *UtxoView) _connectRegisterAsValidator( // Otherwise, retain the existing LastActiveEpochNumber. var lastActiveEpochNumber uint64 if prevValidatorEntry != nil { + // Retain the existing LastActiveEpochNumber. lastActiveEpochNumber = prevValidatorEntry.LastActiveEpochNumber } else { // Retrieve the CurrentEpochNumber. - currentEpochNumber := uint64(0) // TODO: update + currentEpochNumber, err := bav.GetCurrentEpochNumber() + if err != nil { + return 0, 0, nil, errors.Wrapf(err, "_connectRegisterAsValidator: error retrieving CurrentEpochNumber: ") + } + // Set LastActiveEpochNumber to CurrentEpochNumber. lastActiveEpochNumber = currentEpochNumber } diff --git a/lib/block_view_validator_test.go b/lib/block_view_validator_test.go index 7fb535964..9a9056aed 100644 --- a/lib/block_view_validator_test.go +++ b/lib/block_view_validator_test.go @@ -1115,6 +1115,10 @@ func _testUpdatingValidatorDisableDelegatedStake(t *testing.T, flushToDB bool) { m0PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m0PkBytes).PKID + // Seed a CurrentEpochEntry. + err = utxoView().SetCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}, blockHeight) + require.NoError(t, err) + { // ParamUpdater set min fee rate params.ExtraRegtestParamUpdaterKeys[MakePkMapKey(paramUpdaterPkBytes)] = true From 299bbf01358a012149124d53042439a943ac600e Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Tue, 2 May 2023 16:12:12 -0400 Subject: [PATCH 15/22] Add unjail validator txn type. --- lib/block_view.go | 7 + lib/block_view_types.go | 10 +- lib/block_view_validator.go | 339 ++++++++++++++++++++++++++++++- lib/block_view_validator_test.go | 9 + lib/constants.go | 10 + lib/db_utils.go | 17 +- lib/mempool.go | 4 + lib/network.go | 12 +- 8 files changed, 397 insertions(+), 11 deletions(-) diff --git a/lib/block_view.go b/lib/block_view.go index 597f0605b..2b63c1fbf 100644 --- a/lib/block_view.go +++ b/lib/block_view.go @@ -1373,6 +1373,10 @@ func (bav *UtxoView) DisconnectTransaction(currentTxn *MsgDeSoTxn, txnHash *Bloc case TxnTypeUnlockStake: return bav._disconnectUnlockStake( OperationTypeUnlockStake, currentTxn, txnHash, utxoOpsForTxn, blockHeight) + + case TxnTypeUnjailValidator: + return bav._disconnectUnjailValidator( + OperationTypeUnjailValidator, currentTxn, txnHash, utxoOpsForTxn, blockHeight) } return fmt.Errorf("DisconnectBlock: Unimplemented txn type %v", currentTxn.TxnMeta.GetTxnType().String()) @@ -3309,6 +3313,9 @@ func (bav *UtxoView) _connectTransaction(txn *MsgDeSoTxn, txHash *BlockHash, case TxnTypeUnlockStake: totalInput, totalOutput, utxoOpsForTxn, err = bav._connectUnlockStake(txn, txHash, blockHeight, verifySignatures) + case TxnTypeUnjailValidator: + totalInput, totalOutput, utxoOpsForTxn, err = bav._connectUnjailValidator(txn, txHash, blockHeight, verifySignatures) + default: err = fmt.Errorf("ConnectTransaction: Unimplemented txn type %v", txn.TxnMeta.GetTxnType().String()) } diff --git a/lib/block_view_types.go b/lib/block_view_types.go index 1b7e44ecf..edec5a303 100644 --- a/lib/block_view_types.go +++ b/lib/block_view_types.go @@ -156,9 +156,10 @@ const ( EncoderTypeStakeTxindexMetadata EncoderType = 1000032 EncoderTypeUnstakeTxindexMetadata EncoderType = 1000033 EncoderTypeUnlockStakeTxindexMetadata EncoderType = 1000034 + EncoderTypeUnjailValidatorTxindexMetadata EncoderType = 1000035 // EncoderTypeEndTxIndex encoder type should be at the end and is used for automated tests. - EncoderTypeEndTxIndex EncoderType = 1000035 + EncoderTypeEndTxIndex EncoderType = 1000036 ) // This function translates the EncoderType into an empty DeSoEncoder struct. @@ -327,6 +328,8 @@ func (encoderType EncoderType) New() DeSoEncoder { return &UnstakeTxindexMetadata{} case EncoderTypeUnlockStakeTxindexMetadata: return &UnlockStakeTxindexMetadata{} + case EncoderTypeUnjailValidatorTxindexMetadata: + return &UnjailValidatorTxindexMetadata{} default: return nil } @@ -626,8 +629,9 @@ const ( OperationTypeStake OperationType = 41 OperationTypeUnstake OperationType = 42 OperationTypeUnlockStake OperationType = 43 + OperationTypeUnjailValidator OperationType = 44 - // NEXT_TAG = 44 + // NEXT_TAG = 45 ) func (op OperationType) String() string { @@ -718,6 +722,8 @@ func (op OperationType) String() string { return "OperationTypeUnstake" case OperationTypeUnlockStake: return "OperationTypeUnlockStake" + case OperationTypeUnjailValidator: + return "OperationTypeUnjailValidator" } return "OperationTypeUNKNOWN" } diff --git a/lib/block_view_validator.go b/lib/block_view_validator.go index bbaabe255..d4b9870fb 100644 --- a/lib/block_view_validator.go +++ b/lib/block_view_validator.go @@ -313,6 +313,28 @@ func (txnData *UnregisterAsValidatorMetadata) New() DeSoTxnMetadata { return &UnregisterAsValidatorMetadata{} } +// +// TYPES: UnjailValidatorMetadata +// + +type UnjailValidatorMetadata struct{} + +func (txnData *UnjailValidatorMetadata) GetTxnType() TxnType { + return TxnTypeUnjailValidator +} + +func (txnData *UnjailValidatorMetadata) ToBytes(preSignature bool) ([]byte, error) { + return []byte{}, nil +} + +func (txnData *UnjailValidatorMetadata) FromBytes(data []byte) error { + return nil +} + +func (txnData *UnjailValidatorMetadata) New() DeSoTxnMetadata { + return &UnjailValidatorMetadata{} +} + // // TYPES: RegisterAsValidatorTxindexMetadata // @@ -495,6 +517,41 @@ func (txindexMetadata *UnregisterAsValidatorTxindexMetadata) GetEncoderType() En return EncoderTypeUnregisterAsValidatorTxindexMetadata } +// +// TYPES: UnjailValidatorTxindexMetadata +// + +type UnjailValidatorTxindexMetadata struct { + ValidatorPublicKeyBase58Check string +} + +func (txindexMetadata *UnjailValidatorTxindexMetadata) RawEncodeWithoutMetadata(blockHeight uint64, skipMetadata ...bool) []byte { + var data []byte + data = append(data, EncodeByteArray([]byte(txindexMetadata.ValidatorPublicKeyBase58Check))...) + return data +} + +func (txindexMetadata *UnjailValidatorTxindexMetadata) RawDecodeWithoutMetadata(blockHeight uint64, rr *bytes.Reader) error { + var err error + + // ValidatorPublicKeyBase58Check + validatorPublicKeyBase58CheckBytes, err := DecodeByteArray(rr) + if err != nil { + return errors.Wrapf(err, "UnjailValidatorTxindexMetadata.Decode: Problem reading ValidatorPublicKeyBase58Check: ") + } + txindexMetadata.ValidatorPublicKeyBase58Check = string(validatorPublicKeyBase58CheckBytes) + + return nil +} + +func (txindexMetadata *UnjailValidatorTxindexMetadata) GetVersionByte(blockHeight uint64) byte { + return 0 +} + +func (txindexMetadata *UnjailValidatorTxindexMetadata) GetEncoderType() EncoderType { + return EncoderTypeUnjailValidatorTxindexMetadata +} + // // DB UTILS // @@ -855,6 +912,82 @@ func (bc *Blockchain) CreateUnregisterAsValidatorTxn( return txn, totalInput, changeAmount, fees, nil } +func (bc *Blockchain) CreateUnjailValidatorTxn( + transactorPublicKey []byte, + metadata *UnjailValidatorMetadata, + extraData map[string][]byte, + minFeeRateNanosPerKB uint64, + mempool *DeSoMempool, + additionalOutputs []*DeSoOutput, +) ( + _txn *MsgDeSoTxn, + _totalInput uint64, + _changeAmount uint64, + _fees uint64, + _err error, +) { + // Create a txn containing the UnjailValidator fields. + txn := &MsgDeSoTxn{ + PublicKey: transactorPublicKey, + TxnMeta: metadata, + TxOutputs: additionalOutputs, + ExtraData: extraData, + // We wait to compute the signature until + // we've added all the inputs and change. + } + + // Create a new UtxoView. If we have access to a mempool object, use + // it to get an augmented view that factors in pending transactions. + utxoView, err := NewUtxoView(bc.db, bc.params, bc.postgres, bc.snapshot) + if err != nil { + return nil, 0, 0, 0, errors.Wrap( + err, "Blockchain.CreateUnjailValidatorTxn: problem creating new utxo view: ", + ) + } + if mempool != nil { + utxoView, err = mempool.GetAugmentedUniversalView() + if err != nil { + return nil, 0, 0, 0, errors.Wrapf( + err, "Blockchain.CreateUnjailValidatorTxn: problem getting augmented utxo view from mempool: ", + ) + } + } + + // Validate txn metadata. + if err = utxoView.IsValidUnjailValidatorMetadata(transactorPublicKey, metadata); err != nil { + return nil, 0, 0, 0, errors.Wrapf( + err, "Blockchain.CreateUnjailValidatorTxn: invalid txn metadata: ", + ) + } + + // We don't need to make any tweaks to the amount because + // it's basically a standard "pay per kilobyte" transaction. + totalInput, spendAmount, changeAmount, fees, err := bc.AddInputsAndChangeToTransaction( + txn, minFeeRateNanosPerKB, mempool, + ) + if err != nil { + return nil, 0, 0, 0, errors.Wrapf( + err, "Blockchain.CreateUnjailValidatorTxn: problem adding inputs: ", + ) + } + + // Validate that the transaction has at least one input, even if it all goes + // to change. This ensures that the transaction will not be "replayable." + if len(txn.TxInputs) == 0 && bc.blockTip().Height+1 < bc.params.ForkHeights.BalanceModelBlockHeight { + return nil, 0, 0, 0, errors.New( + "Blockchain.CreateUnjailValidatorTxn: txn has zero inputs, try increasing the fee rate", + ) + } + + // Sanity-check that the spendAmount is zero. + if spendAmount != 0 { + return nil, 0, 0, 0, fmt.Errorf( + "Blockchain.CreateUnjailValidatorTxn: spend amount is non-zero: %d", spendAmount, + ) + } + return txn, totalInput, changeAmount, fees, nil +} + // // UTXO VIEW UTILS // @@ -1316,6 +1449,157 @@ func (bav *UtxoView) _disconnectUnregisterAsValidator( ) } +func (bav *UtxoView) _connectUnjailValidator( + txn *MsgDeSoTxn, + txHash *BlockHash, + blockHeight uint32, + verifySignatures bool, +) ( + _totalInput uint64, + _totalOutput uint64, + _utxoOps []*UtxoOperation, + _err error, +) { + // Validate the starting block height. + if blockHeight < bav.Params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight { + return 0, 0, nil, errors.Wrapf(RuleErrorProofofStakeTxnBeforeBlockHeight, "_connectUnjailValidator: ") + } + + // Validate the txn TxnType. + if txn.TxnMeta.GetTxnType() != TxnTypeUnjailValidator { + return 0, 0, nil, fmt.Errorf( + "_connectUnjailValidator: called with bad TxnType %s", txn.TxnMeta.GetTxnType().String(), + ) + } + + // Connect a basic transfer to get the total input and the + // total output without considering the txn metadata. + totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer( + txn, txHash, blockHeight, verifySignatures, + ) + if err != nil { + return 0, 0, nil, errors.Wrapf(err, "_connectUnjailValidator: ") + } + if verifySignatures { + // _connectBasicTransfer has already checked that the txn is signed + // by the top-level public key, which we take to be the sender's + // public key so there is no need to verify anything further. + } + + // Grab the txn metadata. + txMeta := txn.TxnMeta.(*UnjailValidatorMetadata) + + // Validate the txn metadata. + if err = bav.IsValidUnjailValidatorMetadata(txn.PublicKey, txMeta); err != nil { + return 0, 0, nil, errors.Wrapf(err, "_connectUnjailValidator: ") + } + + // Convert TransactorPublicKey to TransactorPKID. + transactorPKIDEntry := bav.GetPKIDForPublicKey(txn.PublicKey) + if transactorPKIDEntry == nil || transactorPKIDEntry.isDeleted { + return 0, 0, nil, errors.Wrapf(RuleErrorInvalidValidatorPKID, "_connectUnjailValidator: ") + } + + // Retrieve the existing ValidatorEntry that will be overwritten. + // This ValidatorEntry will be restored if we disconnect this txn. + prevValidatorEntry, err := bav.GetValidatorByPKID(transactorPKIDEntry.PKID) + if err != nil { + return 0, 0, nil, errors.Wrapf(err, "_connectUnjailValidator: ") + } + if prevValidatorEntry == nil || prevValidatorEntry.isDeleted { + return 0, 0, nil, errors.Wrapf(RuleErrorValidatorNotFound, "_connectUnjailValidator: ") + } + if prevValidatorEntry.Status != ValidatorStatusJailed { + return 0, 0, nil, errors.Wrapf(RuleErrorUnjailingNonjailedValidator, "_connectUnjailValidator: ") + } + + // Copy the existing ValidatorEntry. + currentValidatorEntry := prevValidatorEntry.Copy() + + // Update Status to Active. + currentValidatorEntry.Status = ValidatorStatusActive + + // Retrieve the CurrentEpochNumber. + currentEpochNumber, err := bav.GetCurrentEpochNumber() + if err != nil { + return 0, 0, nil, errors.Wrapf(err, "_connectUnjailValidator: error retrieving CurrentEpochNumber: ") + } + + // Update LastActiveEpochNumber to CurrentEpochNumber. + currentValidatorEntry.LastActiveEpochNumber = currentEpochNumber + + // Merge ExtraData with existing ExtraData. + currentValidatorEntry.ExtraData = mergeExtraData(prevValidatorEntry.ExtraData, txn.ExtraData) + + // Delete the PrevValidatorEntry. + bav._deleteValidatorEntryMappings(prevValidatorEntry) + + // Set the CurrentValidatorEntry. + bav._setValidatorEntryMappings(currentValidatorEntry) + + // Add a UTXO operation + utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{ + Type: OperationTypeUnjailValidator, + PrevValidatorEntry: prevValidatorEntry, + }) + return totalInput, totalOutput, utxoOpsForTxn, nil +} + +func (bav *UtxoView) _disconnectUnjailValidator( + operationType OperationType, + currentTxn *MsgDeSoTxn, + txHash *BlockHash, + utxoOpsForTxn []*UtxoOperation, + blockHeight uint32, +) error { + // Validate the starting block height. + if blockHeight < bav.Params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight { + return errors.Wrapf(RuleErrorProofofStakeTxnBeforeBlockHeight, "_disconnectUnjailValidator: ") + } + + // Validate the last operation is an UnjailValidator operation. + if len(utxoOpsForTxn) == 0 { + return fmt.Errorf("_disconnectUnjailValidator: utxoOperations are missing") + } + operationIndex := len(utxoOpsForTxn) - 1 + operationData := utxoOpsForTxn[operationIndex] + if operationData.Type != OperationTypeUnjailValidator { + return fmt.Errorf( + "_disconnectUnjailValidator: trying to revert %v but found %v", + OperationTypeUnjailValidator, + operationData.Type, + ) + } + + // Convert TransactorPublicKey to TransactorPKID. + transactorPKIDEntry := bav.GetPKIDForPublicKey(currentTxn.PublicKey) + if transactorPKIDEntry == nil || transactorPKIDEntry.isDeleted { + return errors.Wrapf(RuleErrorInvalidValidatorPKID, "_disconnectUnjailValidator: ") + } + + // Delete the current ValidatorEntry. + currentValidatorEntry, err := bav.GetValidatorByPKID(transactorPKIDEntry.PKID) + if err != nil { + return errors.Wrapf(err, "_disconnectUnjailValidator: ") + } + if currentValidatorEntry == nil || currentValidatorEntry.isDeleted { + return errors.Wrapf(RuleErrorValidatorNotFound, "_disconnectUnjailValidator: ") + } + bav._deleteValidatorEntryMappings(currentValidatorEntry) + + // Restore the PrevValidatorEntry. + prevValidatorEntry := operationData.PrevValidatorEntry + if prevValidatorEntry == nil { + return errors.New("_disconnectUnjailValidator: PrevValidatorEntry is nil") + } + bav._setValidatorEntryMappings(prevValidatorEntry) + + // Disconnect the BasicTransfer. + return bav._disconnectBasicTransfer( + currentTxn, txHash, utxoOpsForTxn[:operationIndex], blockHeight, + ) +} + func (bav *UtxoView) IsValidRegisterAsValidatorMetadata(transactorPublicKey []byte, metadata *RegisterAsValidatorMetadata) error { // Validate ValidatorPKID. transactorPKIDEntry := bav.GetPKIDForPublicKey(transactorPublicKey) @@ -1385,13 +1669,39 @@ func (bav *UtxoView) IsValidUnregisterAsValidatorMetadata(transactorPublicKey [] if err != nil { return errors.Wrapf(err, "UtxoView.IsValidUnregisterAsValidatorMetadata: ") } - if validatorEntry == nil { + if validatorEntry == nil || validatorEntry.isDeleted { return errors.Wrapf(RuleErrorValidatorNotFound, "UtxoView.IsValidUnregisterAsValidatorMetadata: ") } return nil } +func (bav *UtxoView) IsValidUnjailValidatorMetadata(transactorPublicKey []byte, metadata *UnjailValidatorMetadata) error { + // Validate ValidatorPKID. + transactorPKIDEntry := bav.GetPKIDForPublicKey(transactorPublicKey) + if transactorPKIDEntry == nil || transactorPKIDEntry.isDeleted { + return errors.Wrapf(RuleErrorInvalidValidatorPKID, "UtxoView.IsValidUnjailValidatorMetadata: ") + } + + // Validate ValidatorEntry exists. + validatorEntry, err := bav.GetValidatorByPKID(transactorPKIDEntry.PKID) + if err != nil { + return errors.Wrapf(err, "UtxoView.IsValidUnjailValidatorMetadata: ") + } + if validatorEntry == nil || validatorEntry.isDeleted { + return errors.Wrapf(RuleErrorValidatorNotFound, "UtxoView.IsValidUnjailValidatorMetadata: ") + } + + // Validate ValidatorEntry is jailed. + if validatorEntry.Status != ValidatorStatusJailed { + return errors.Wrapf(RuleErrorUnjailingNonjailedValidator, "UtxoView.IsValidUnjailValidatorMetadata: ") + } + + // TODO: Validate enough epochs have elapsed for validator to be unjailed. + + return nil +} + func (bav *UtxoView) GetValidatorByPKID(pkid *PKID) (*ValidatorEntry, error) { // First check the UtxoView. @@ -1686,6 +1996,32 @@ func (bav *UtxoView) CreateUnregisterAsValidatorTxindexMetadata( return txindexMetadata, affectedPublicKeys } +func (bav *UtxoView) CreateUnjailValidatorTxindexMetadata( + utxoOp *UtxoOperation, + txn *MsgDeSoTxn, +) ( + *UnjailValidatorTxindexMetadata, + []*AffectedPublicKey, +) { + // Cast ValidatorPublicKey to ValidatorPublicKeyBase58Check. + validatorPublicKeyBase58Check := PkToString(txn.PublicKey, bav.Params) + + // Construct TxindexMetadata. + txindexMetadata := &UnjailValidatorTxindexMetadata{ + ValidatorPublicKeyBase58Check: validatorPublicKeyBase58Check, + } + + // Construct AffectedPublicKeys. + affectedPublicKeys := []*AffectedPublicKey{ + { + PublicKeyBase58Check: validatorPublicKeyBase58Check, + Metadata: "UnjailedValidatorPublicKeyBase58Check", + }, + } + + return txindexMetadata, affectedPublicKeys +} + // // CONSTANTS // @@ -1698,5 +2034,6 @@ const RuleErrorValidatorInvalidDomain RuleError = "RuleErrorValidatorInvalidDoma const RuleErrorValidatorDuplicateDomains RuleError = "RuleErrorValidatorDuplicateDomains" const RuleErrorValidatorNotFound RuleError = "RuleErrorValidatorNotFound" const RuleErrorValidatorDisablingExistingDelegatedStakers RuleError = "RuleErrorValidatorDisablingExistingDelegatedStakers" +const RuleErrorUnjailingNonjailedValidator RuleError = "RuleErrorUnjailingNonjailedValidator" const MaxValidatorNumDomains int = 12 diff --git a/lib/block_view_validator_test.go b/lib/block_view_validator_test.go index 9a9056aed..bd08b9748 100644 --- a/lib/block_view_validator_test.go +++ b/lib/block_view_validator_test.go @@ -1449,3 +1449,12 @@ func _testUnregisterAsValidator(t *testing.T, flushToDB bool) { require.NoError(t, mempool.universalUtxoView.FlushToDb(blockHeight)) _executeAllTestRollbackAndFlush(testMeta) } + +func TestUnjailValidator(t *testing.T) { + _testUnjailValidator(t, false) + _testUnjailValidator(t, true) +} + +func _testUnjailValidator(t *testing.T, flushToDB bool) { + // TODO +} diff --git a/lib/constants.go b/lib/constants.go index 31fbe97c2..010a1b42f 100644 --- a/lib/constants.go +++ b/lib/constants.go @@ -600,6 +600,10 @@ type DeSoParams struct { // user must wait before unlocking their unstaked stake. StakeLockupEpochDuration uint64 + // ValidatorJailEpochDuration is the number of epochs that a validator must + // wait after being jailed before submitting an UnjailValidator txn. + ValidatorJailEpochDuration uint64 + ForkHeights ForkHeights EncoderMigrationHeights *EncoderMigrationHeights @@ -975,6 +979,9 @@ var DeSoMainnetParams = DeSoParams{ // Unstaked stake can be unlocked after a minimum of N elapsed epochs. StakeLockupEpochDuration: uint64(3), + // Jailed validators can be unjailed after a minimum of N elapsed epochs. + ValidatorJailEpochDuration: uint64(3), + ForkHeights: MainnetForkHeights, EncoderMigrationHeights: GetEncoderMigrationHeights(&MainnetForkHeights), EncoderMigrationHeightsList: GetEncoderMigrationHeightsList(&MainnetForkHeights), @@ -1206,6 +1213,9 @@ var DeSoTestnetParams = DeSoParams{ // Unstaked stake can be unlocked after a minimum of N elapsed epochs. StakeLockupEpochDuration: uint64(3), + // Jailed validators can be unjailed after a minimum of N elapsed epochs. + ValidatorJailEpochDuration: uint64(3), + ForkHeights: TestnetForkHeights, EncoderMigrationHeights: GetEncoderMigrationHeights(&TestnetForkHeights), EncoderMigrationHeightsList: GetEncoderMigrationHeightsList(&TestnetForkHeights), diff --git a/lib/db_utils.go b/lib/db_utils.go index 414342b23..001055fbf 100644 --- a/lib/db_utils.go +++ b/lib/db_utils.go @@ -6806,6 +6806,7 @@ type TransactionMetadata struct { StakeTxindexMetadata *StakeTxindexMetadata `json:",omitempty"` UnstakeTxindexMetadata *UnstakeTxindexMetadata `json:",omitempty"` UnlockStakeTxindexMetadata *UnlockStakeTxindexMetadata `json:",omitempty"` + UnjailValidatorTxindexMetadata *UnjailValidatorTxindexMetadata `json:",omitempty"` } func (txnMeta *TransactionMetadata) RawEncodeWithoutMetadata(blockHeight uint64, skipMetadata ...bool) []byte { @@ -6898,6 +6899,8 @@ func (txnMeta *TransactionMetadata) RawEncodeWithoutMetadata(blockHeight uint64, data = append(data, EncodeToBytes(blockHeight, txnMeta.UnstakeTxindexMetadata, skipMetadata...)...) // encoding UnlockStakeTxindexMetadata data = append(data, EncodeToBytes(blockHeight, txnMeta.UnlockStakeTxindexMetadata, skipMetadata...)...) + // encoding UnjailValidatorTxindexMetadata + data = append(data, EncodeToBytes(blockHeight, txnMeta.UnjailValidatorTxindexMetadata, skipMetadata...)...) } return data @@ -7152,23 +7155,27 @@ func (txnMeta *TransactionMetadata) RawDecodeWithoutMetadata(blockHeight uint64, if MigrationTriggered(blockHeight, ProofOfStakeNewTxnTypesMigration) { // decoding RegisterAsValidatorTxindexMetadata if txnMeta.RegisterAsValidatorTxindexMetadata, err = DecodeDeSoEncoder(&RegisterAsValidatorTxindexMetadata{}, rr); err != nil { - return errors.Wrapf(err, "TransactionMetadata.Decode: Problem reading RegisterAsValidatorTxindexMetadata") + return errors.Wrapf(err, "TransactionMetadata.Decode: Problem reading RegisterAsValidatorTxindexMetadata: ") } // decoding UnregisterAsValidatorTxindexMetadata if txnMeta.UnregisterAsValidatorTxindexMetadata, err = DecodeDeSoEncoder(&UnregisterAsValidatorTxindexMetadata{}, rr); err != nil { - return errors.Wrapf(err, "TransactionMetadata.Decode: Problem reading UnregisterAsValidatorTxindexMetadata") + return errors.Wrapf(err, "TransactionMetadata.Decode: Problem reading UnregisterAsValidatorTxindexMetadata: ") } // decoding StakeTxindexMetadata if txnMeta.StakeTxindexMetadata, err = DecodeDeSoEncoder(&StakeTxindexMetadata{}, rr); err != nil { - return errors.Wrapf(err, "TransactionMetadata.Decode: Problem reading StakeTxindexMetadata") + return errors.Wrapf(err, "TransactionMetadata.Decode: Problem reading StakeTxindexMetadata: ") } // decoding UnstakeTxindexMetadata if txnMeta.UnstakeTxindexMetadata, err = DecodeDeSoEncoder(&UnstakeTxindexMetadata{}, rr); err != nil { - return errors.Wrapf(err, "TransactionMetadata.Decode: Problem reading UnstakeTxindexMetadata") + return errors.Wrapf(err, "TransactionMetadata.Decode: Problem reading UnstakeTxindexMetadata: ") } // decoding UnlockStakeTxindexMetadata if txnMeta.UnlockStakeTxindexMetadata, err = DecodeDeSoEncoder(&UnlockStakeTxindexMetadata{}, rr); err != nil { - return errors.Wrapf(err, "TransactionMetadata.Decode: Problem reading UnlockStakeTxindexMetadata") + return errors.Wrapf(err, "TransactionMetadata.Decode: Problem reading UnlockStakeTxindexMetadata: ") + } + // decoding UnjailValidatorTxindexMetadata + if txnMeta.UnjailValidatorTxindexMetadata, err = DecodeDeSoEncoder(&UnjailValidatorTxindexMetadata{}, rr); err != nil { + return errors.Wrapf(err, "TransactionMetadata.Decode: Problem reading UnjailValidatorTxindexMetadata: ") } } diff --git a/lib/mempool.go b/lib/mempool.go index c5d3626d4..6e7373986 100644 --- a/lib/mempool.go +++ b/lib/mempool.go @@ -1961,6 +1961,10 @@ func ComputeTransactionMetadata(txn *MsgDeSoTxn, utxoView *UtxoView, blockHash * txindexMetadata, affectedPublicKeys := utxoView.CreateUnlockStakeTxindexMetadata(utxoOps[len(utxoOps)-1], txn) txnMeta.UnlockStakeTxindexMetadata = txindexMetadata txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, affectedPublicKeys...) + case TxnTypeUnjailValidator: + txindexMetadata, affectedPublicKeys := utxoView.CreateUnjailValidatorTxindexMetadata(utxoOps[len(utxoOps)-1], txn) + txnMeta.UnjailValidatorTxindexMetadata = txindexMetadata + txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, affectedPublicKeys...) } return txnMeta } diff --git a/lib/network.go b/lib/network.go index ddaa1e342..339b5ee59 100644 --- a/lib/network.go +++ b/lib/network.go @@ -244,8 +244,9 @@ const ( TxnTypeStake TxnType = 36 TxnTypeUnstake TxnType = 37 TxnTypeUnlockStake TxnType = 38 + TxnTypeUnjailValidator TxnType = 39 - // NEXT_ID = 39 + // NEXT_ID = 40 ) type TxnString string @@ -290,6 +291,7 @@ const ( TxnStringStake TxnString = "STAKE" TxnStringUnstake TxnString = "UNSTAKE" TxnStringUnlockStake TxnString = "UNLOCK_STAKE" + TxnStringUnjailValidator TxnString = "UNJAIL_VALIDATOR" ) var ( @@ -302,7 +304,7 @@ var ( TxnTypeDAOCoin, TxnTypeDAOCoinTransfer, TxnTypeDAOCoinLimitOrder, TxnTypeCreateUserAssociation, TxnTypeDeleteUserAssociation, TxnTypeCreatePostAssociation, TxnTypeDeletePostAssociation, TxnTypeAccessGroup, TxnTypeAccessGroupMembers, TxnTypeNewMessage, TxnTypeRegisterAsValidator, - TxnTypeUnregisterAsValidator, TxnTypeStake, TxnTypeUnstake, TxnTypeUnlockStake, + TxnTypeUnregisterAsValidator, TxnTypeStake, TxnTypeUnstake, TxnTypeUnlockStake, TxnTypeUnjailValidator, } AllTxnString = []TxnString{ TxnStringUnset, TxnStringBlockReward, TxnStringBasicTransfer, TxnStringBitcoinExchange, TxnStringPrivateMessage, @@ -313,7 +315,7 @@ var ( TxnStringDAOCoin, TxnStringDAOCoinTransfer, TxnStringDAOCoinLimitOrder, TxnStringCreateUserAssociation, TxnStringDeleteUserAssociation, TxnStringCreatePostAssociation, TxnStringDeletePostAssociation, TxnStringAccessGroup, TxnStringAccessGroupMembers, TxnStringNewMessage, TxnStringRegisterAsValidator, - TxnStringUnregisterAsValidator, TxnStringStake, TxnStringUnstake, TxnStringUnlockStake, + TxnStringUnregisterAsValidator, TxnStringStake, TxnStringUnstake, TxnStringUnlockStake, TxnStringUnjailValidator, } ) @@ -403,6 +405,8 @@ func (txnType TxnType) GetTxnString() TxnString { return TxnStringUnstake case TxnTypeUnlockStake: return TxnStringUnlockStake + case TxnTypeUnjailValidator: + return TxnStringUnjailValidator default: return TxnStringUndefined } @@ -486,6 +490,8 @@ func GetTxnTypeFromString(txnString TxnString) TxnType { return TxnTypeUnstake case TxnStringUnlockStake: return TxnTypeUnlockStake + case TxnStringUnjailValidator: + return TxnTypeUnjailValidator default: // TxnTypeUnset means we couldn't find a matching txn type return TxnTypeUnset From 6a08b23a037c2fa6c71ccc6cb13a8987915d9835 Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Tue, 2 May 2023 16:32:18 -0400 Subject: [PATCH 16/22] Add ValidatorEntry.JailedAtEpochNumber field. --- lib/block_view_validator.go | 83 ++++++++++++++++++-------------- lib/block_view_validator_test.go | 12 ++--- 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/lib/block_view_validator.go b/lib/block_view_validator.go index bbaabe255..b36561f11 100644 --- a/lib/block_view_validator.go +++ b/lib/block_view_validator.go @@ -30,12 +30,27 @@ type ValidatorEntry struct { VotingSignatureBlockHeight uint64 TotalStakeAmountNanos *uint256.Int RegisteredAtBlockHeight uint64 - Status ValidatorStatus - LastActiveEpochNumber uint64 + LastActiveAtEpochNumber uint64 + JailedAtEpochNumber uint64 ExtraData map[string][]byte isDeleted bool } +func (validatorEntry *ValidatorEntry) Status() ValidatorStatus { + // ValidatorEntry.Status() is a virtual/derived field that is not stored in + // the database, but instead constructed from other ValidatorEntry fields. + // No sense in storing duplicative data twice. This saves memory and ensures + // that e.g. the ValidatorEntry.JailedAtEpochNumber field and the + // ValidatorEntry.Status() return value will never get out of sync. + // + // Make sure that any fields referenced here are included in the ValidatorMapKey + // since the ValidatorEntry.Status() value is used as a field in a Badger index. + if validatorEntry.JailedAtEpochNumber > uint64(0) { + return ValidatorStatusJailed + } + return ValidatorStatusActive +} + type ValidatorStatus uint8 const ( @@ -51,7 +66,7 @@ type ValidatorMapKey struct { ValidatorPKID PKID TotalStakeAmountNanos uint256.Int RegisteredAtBlockHeight uint64 - Status ValidatorStatus + JailedAtEpochNumber uint64 } func (validatorEntry *ValidatorEntry) Copy() *ValidatorEntry { @@ -72,8 +87,8 @@ func (validatorEntry *ValidatorEntry) Copy() *ValidatorEntry { VotingSignatureBlockHeight: validatorEntry.VotingSignatureBlockHeight, TotalStakeAmountNanos: validatorEntry.TotalStakeAmountNanos.Clone(), RegisteredAtBlockHeight: validatorEntry.RegisteredAtBlockHeight, - Status: validatorEntry.Status, - LastActiveEpochNumber: validatorEntry.LastActiveEpochNumber, + LastActiveAtEpochNumber: validatorEntry.LastActiveAtEpochNumber, + JailedAtEpochNumber: validatorEntry.JailedAtEpochNumber, ExtraData: copyExtraData(validatorEntry.ExtraData), isDeleted: validatorEntry.isDeleted, } @@ -84,7 +99,7 @@ func (validatorEntry *ValidatorEntry) ToMapKey() ValidatorMapKey { ValidatorPKID: *validatorEntry.ValidatorPKID, TotalStakeAmountNanos: *validatorEntry.TotalStakeAmountNanos, RegisteredAtBlockHeight: validatorEntry.RegisteredAtBlockHeight, - Status: validatorEntry.Status, + JailedAtEpochNumber: validatorEntry.JailedAtEpochNumber, } } @@ -105,8 +120,8 @@ func (validatorEntry *ValidatorEntry) RawEncodeWithoutMetadata(blockHeight uint6 data = append(data, UintToBuf(validatorEntry.VotingSignatureBlockHeight)...) data = append(data, EncodeUint256(validatorEntry.TotalStakeAmountNanos)...) data = append(data, UintToBuf(validatorEntry.RegisteredAtBlockHeight)...) - data = append(data, EncodeUint16(uint16(validatorEntry.Status))...) - data = append(data, UintToBuf(validatorEntry.LastActiveEpochNumber)...) + data = append(data, UintToBuf(validatorEntry.LastActiveAtEpochNumber)...) + data = append(data, UintToBuf(validatorEntry.JailedAtEpochNumber)...) data = append(data, EncodeExtraData(validatorEntry.ExtraData)...) return data } @@ -179,20 +194,16 @@ func (validatorEntry *ValidatorEntry) RawDecodeWithoutMetadata(blockHeight uint6 return errors.Wrapf(err, "ValidatorEntry.Decode: Problem reading RegisteredAtBlockHeight: ") } - // Status - status, err := ReadUint16(rr) + // LastActiveAtEpochNumber + validatorEntry.LastActiveAtEpochNumber, err = ReadUvarint(rr) if err != nil { - return errors.Wrapf(err, "ValidatorEntry.Decode: Problem reading Status: ") - } - if status > math.MaxUint8 { - return fmt.Errorf("ValidatorEntry.Decode: ValidatorEntry.Status overflows uint8: %d", status) + return errors.Wrapf(err, "ValidatorEntry.Decode: Problem reading LastActiveAtEpochNumber: ") } - validatorEntry.Status = ValidatorStatus(uint8(status)) - // LastActiveEpochNumber - validatorEntry.LastActiveEpochNumber, err = ReadUvarint(rr) + // JailedAtEpochNumber + validatorEntry.JailedAtEpochNumber, err = ReadUvarint(rr) if err != nil { - return errors.Wrapf(err, "ValidatorEntry.Decode: Problem reading LastActiveEpochNumber: ") + return errors.Wrapf(err, "ValidatorEntry.Decode: Problem reading JailedAtEpochNumber: ") } // ExtraData @@ -507,7 +518,7 @@ func DBKeyForValidatorByPKID(validatorEntry *ValidatorEntry) []byte { func DBKeyForValidatorByStake(validatorEntry *ValidatorEntry) []byte { key := append([]byte{}, Prefixes.PrefixValidatorByStake...) - key = append(key, EncodeUint16(uint16(validatorEntry.Status))...) + key = append(key, EncodeUint16(uint16(validatorEntry.Status()))...) // TotalStakeAmountNanos will never be nil here, but EncodeOptionalUint256 // is used because it provides a fixed-width encoding of uint256.Ints. key = append(key, EncodeOptionalUint256(validatorEntry.TotalStakeAmountNanos)...) // Highest stake first @@ -943,27 +954,27 @@ func (bav *UtxoView) _connectRegisterAsValidator( registeredAtBlockHeight = prevValidatorEntry.RegisteredAtBlockHeight } - // Set Status to Active if this is a new ValidatorEntry. - // Otherwise, retain the existing Status. - status := ValidatorStatusActive + // Set LastActiveAtEpochNumber to CurrentEpochNumber if this is a new ValidatorEntry. + // Otherwise, retain the existing LastActiveAtEpochNumber. + var lastActiveAtEpochNumber uint64 if prevValidatorEntry != nil { - status = prevValidatorEntry.Status - } - - // Set LastActiveEpochNumber to CurrentEpochNumber if this is a new ValidatorEntry. - // Otherwise, retain the existing LastActiveEpochNumber. - var lastActiveEpochNumber uint64 - if prevValidatorEntry != nil { - // Retain the existing LastActiveEpochNumber. - lastActiveEpochNumber = prevValidatorEntry.LastActiveEpochNumber + // Retain the existing LastActiveAtEpochNumber. + lastActiveAtEpochNumber = prevValidatorEntry.LastActiveAtEpochNumber } else { // Retrieve the CurrentEpochNumber. currentEpochNumber, err := bav.GetCurrentEpochNumber() if err != nil { return 0, 0, nil, errors.Wrapf(err, "_connectRegisterAsValidator: error retrieving CurrentEpochNumber: ") } - // Set LastActiveEpochNumber to CurrentEpochNumber. - lastActiveEpochNumber = currentEpochNumber + // Set LastActiveAtEpochNumber to CurrentEpochNumber. + lastActiveAtEpochNumber = currentEpochNumber + } + + // Set JailedAtEpochNumber to zero if this is a new ValidatorEntry. + // Otherwise, retain the existing JailedAtEpochNumber. + jailedAtEpochNumber := uint64(0) + if prevValidatorEntry != nil { + jailedAtEpochNumber = prevValidatorEntry.JailedAtEpochNumber } // Retrieve existing ExtraData to merge with any new ExtraData. @@ -985,8 +996,8 @@ func (bav *UtxoView) _connectRegisterAsValidator( VotingSignatureBlockHeight: txMeta.VotingSignatureBlockHeight, TotalStakeAmountNanos: totalStakeAmountNanos, RegisteredAtBlockHeight: registeredAtBlockHeight, - Status: status, - LastActiveEpochNumber: lastActiveEpochNumber, + LastActiveAtEpochNumber: lastActiveAtEpochNumber, + JailedAtEpochNumber: jailedAtEpochNumber, ExtraData: mergeExtraData(prevExtraData, txn.ExtraData), } // Set the ValidatorEntry. @@ -1477,7 +1488,7 @@ func (bav *UtxoView) GetTopActiveValidatorsByStake(limit int) ([]*ValidatorEntry } // Add !isDeleted, active ValidatorEntries from the UtxoView to the ValidatorEntries from the db. for _, validatorEntry := range utxoViewValidatorEntries { - if !validatorEntry.isDeleted && validatorEntry.Status == ValidatorStatusActive { + if !validatorEntry.isDeleted && validatorEntry.Status() == ValidatorStatusActive { validatorEntries = append(validatorEntries, validatorEntry) } } diff --git a/lib/block_view_validator_test.go b/lib/block_view_validator_test.go index 9a9056aed..a5adcdce6 100644 --- a/lib/block_view_validator_test.go +++ b/lib/block_view_validator_test.go @@ -920,7 +920,6 @@ func TestGetTopActiveValidatorsByStakeMergingDbAndUtxoView(t *testing.T) { validatorEntry := &ValidatorEntry{ ValidatorPKID: m0PKID, TotalStakeAmountNanos: uint256.NewInt().SetUint64(100), - Status: ValidatorStatusActive, } utxoView._setValidatorEntryMappings(validatorEntry) require.NoError(t, utxoView.FlushToDb(blockHeight)) @@ -938,7 +937,7 @@ func TestGetTopActiveValidatorsByStakeMergingDbAndUtxoView(t *testing.T) { validatorEntry = &ValidatorEntry{ ValidatorPKID: m1PKID, TotalStakeAmountNanos: uint256.NewInt().SetUint64(400), - Status: ValidatorStatusJailed, + JailedAtEpochNumber: 1, } utxoView._setValidatorEntryMappings(validatorEntry) require.NoError(t, utxoView.FlushToDb(blockHeight)) @@ -948,13 +947,12 @@ func TestGetTopActiveValidatorsByStakeMergingDbAndUtxoView(t *testing.T) { require.NoError(t, err) require.NotNil(t, validatorEntry) require.Equal(t, validatorEntry.TotalStakeAmountNanos, uint256.NewInt().SetUint64(400)) - require.Equal(t, validatorEntry.Status, ValidatorStatusJailed) + require.Equal(t, validatorEntry.Status(), ValidatorStatusJailed) // Store m2's ValidatorEntry in the db with TotalStake = 300 nanos. m2ValidatorEntry := &ValidatorEntry{ ValidatorPKID: m2PKID, TotalStakeAmountNanos: uint256.NewInt().SetUint64(300), - Status: ValidatorStatusActive, } utxoView._setValidatorEntryMappings(m2ValidatorEntry) require.NoError(t, utxoView.FlushToDb(blockHeight)) @@ -969,7 +967,6 @@ func TestGetTopActiveValidatorsByStakeMergingDbAndUtxoView(t *testing.T) { m3ValidatorEntry := &ValidatorEntry{ ValidatorPKID: m3PKID, TotalStakeAmountNanos: uint256.NewInt().SetUint64(600), - Status: ValidatorStatusActive, } utxoView._setValidatorEntryMappings(m3ValidatorEntry) require.NoError(t, utxoView.FlushToDb(blockHeight)) @@ -1006,7 +1003,6 @@ func TestGetTopActiveValidatorsByStakeMergingDbAndUtxoView(t *testing.T) { m4ValidatorEntry := &ValidatorEntry{ ValidatorPKID: m4PKID, TotalStakeAmountNanos: uint256.NewInt().SetUint64(50), - Status: ValidatorStatusActive, } utxoView._setValidatorEntryMappings(m4ValidatorEntry) @@ -1028,7 +1024,7 @@ func TestGetTopActiveValidatorsByStakeMergingDbAndUtxoView(t *testing.T) { m5ValidatorEntry := &ValidatorEntry{ ValidatorPKID: m5PKID, TotalStakeAmountNanos: uint256.NewInt().SetUint64(500), - Status: ValidatorStatusJailed, + JailedAtEpochNumber: 1, } utxoView._setValidatorEntryMappings(m5ValidatorEntry) @@ -1046,7 +1042,7 @@ func TestGetTopActiveValidatorsByStakeMergingDbAndUtxoView(t *testing.T) { uint256.NewInt().SetUint64(500), ) require.Equal( - t, utxoView.ValidatorMapKeyToValidatorEntry[m5ValidatorEntry.ToMapKey()].Status, ValidatorStatusJailed, + t, utxoView.ValidatorMapKeyToValidatorEntry[m5ValidatorEntry.ToMapKey()].Status(), ValidatorStatusJailed, ) // Fetch TopActiveValidatorsByStake merging ValidatorEntries from the db and UtxoView. From e0f7457745e5df39ae0c09806ac5b180548a714f Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Tue, 2 May 2023 16:45:27 -0400 Subject: [PATCH 17/22] Validate sufficient num epochs have passed to unjail. --- lib/block_view_validator.go | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/block_view_validator.go b/lib/block_view_validator.go index 71c896979..532b51cca 100644 --- a/lib/block_view_validator.go +++ b/lib/block_view_validator.go @@ -1505,6 +1505,11 @@ func (bav *UtxoView) _connectUnjailValidator( return 0, 0, nil, errors.Wrapf(err, "_connectUnjailValidator: ") } + // At this point, we have validated in IsValidUnjailValidatorMetadata() + // that the ValidatorEntry exists, belongs to the transactor, is jailed, + // and a sufficient number of epochs have elapsed for this validator to + // be unjailed. + // Convert TransactorPublicKey to TransactorPKID. transactorPKIDEntry := bav.GetPKIDForPublicKey(txn.PublicKey) if transactorPKIDEntry == nil || transactorPKIDEntry.isDeleted { @@ -1520,24 +1525,21 @@ func (bav *UtxoView) _connectUnjailValidator( if prevValidatorEntry == nil || prevValidatorEntry.isDeleted { return 0, 0, nil, errors.Wrapf(RuleErrorValidatorNotFound, "_connectUnjailValidator: ") } - if prevValidatorEntry.Status != ValidatorStatusJailed { - return 0, 0, nil, errors.Wrapf(RuleErrorUnjailingNonjailedValidator, "_connectUnjailValidator: ") - } // Copy the existing ValidatorEntry. currentValidatorEntry := prevValidatorEntry.Copy() - // Update Status to Active. - currentValidatorEntry.Status = ValidatorStatusActive - // Retrieve the CurrentEpochNumber. currentEpochNumber, err := bav.GetCurrentEpochNumber() if err != nil { return 0, 0, nil, errors.Wrapf(err, "_connectUnjailValidator: error retrieving CurrentEpochNumber: ") } - // Update LastActiveEpochNumber to CurrentEpochNumber. - currentValidatorEntry.LastActiveEpochNumber = currentEpochNumber + // Update LastActiveAtEpochNumber to CurrentEpochNumber. + currentValidatorEntry.LastActiveAtEpochNumber = currentEpochNumber + + // Reset JailedAtEpochNumber to zero. + currentValidatorEntry.JailedAtEpochNumber = 0 // Merge ExtraData with existing ExtraData. currentValidatorEntry.ExtraData = mergeExtraData(prevValidatorEntry.ExtraData, txn.ExtraData) @@ -1704,11 +1706,20 @@ func (bav *UtxoView) IsValidUnjailValidatorMetadata(transactorPublicKey []byte, } // Validate ValidatorEntry is jailed. - if validatorEntry.Status != ValidatorStatusJailed { + if validatorEntry.Status() != ValidatorStatusJailed { return errors.Wrapf(RuleErrorUnjailingNonjailedValidator, "UtxoView.IsValidUnjailValidatorMetadata: ") } - // TODO: Validate enough epochs have elapsed for validator to be unjailed. + // Retrieve CurrentEpochNumber. + currentEpochNumber, err := bav.GetCurrentEpochNumber() + if err != nil { + return errors.Wrapf(err, "UtxoView.IsValidUnjailValidatorMetadata: error retrieving CurrentEpochNumber: ") + } + + // Validate sufficient epochs have elapsed for validator to be unjailed. + if validatorEntry.JailedAtEpochNumber+bav.Params.ValidatorJailEpochDuration < currentEpochNumber { + return errors.Wrapf(RuleErrorUnjailingValidatorTooEarly, "UtxoView.IsValidUnjailValidatorMetadata: ") + } return nil } @@ -2046,5 +2057,6 @@ const RuleErrorValidatorDuplicateDomains RuleError = "RuleErrorValidatorDuplicat const RuleErrorValidatorNotFound RuleError = "RuleErrorValidatorNotFound" const RuleErrorValidatorDisablingExistingDelegatedStakers RuleError = "RuleErrorValidatorDisablingExistingDelegatedStakers" const RuleErrorUnjailingNonjailedValidator RuleError = "RuleErrorUnjailingNonjailedValidator" +const RuleErrorUnjailingValidatorTooEarly RuleError = "RuleErrorUnjailingValidatorTooEarly" const MaxValidatorNumDomains int = 12 From 36e837bf69bf3717801f9bdff4008fed119f7106 Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Thu, 4 May 2023 11:42:43 -0400 Subject: [PATCH 18/22] Resolve merge conflicts more. --- lib/block_view_types.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/block_view_types.go b/lib/block_view_types.go index edec5a303..9aa041678 100644 --- a/lib/block_view_types.go +++ b/lib/block_view_types.go @@ -924,10 +924,6 @@ type UtxoOperation struct { // PrevLockedStakeEntries is a slice of LockedStakeEntries // prior to a unstake or unlock stake txn. PrevLockedStakeEntries []*LockedStakeEntry - - // PrevEpochNumber is the CurrentEpochNumber during the connect logic. This should be used - // during disconnect logic instead of the CurrentEpochNumber which may have changed. - PrevEpochNumber uint64 } func (op *UtxoOperation) RawEncodeWithoutMetadata(blockHeight uint64, skipMetadata ...bool) []byte { @@ -1257,9 +1253,6 @@ func (op *UtxoOperation) RawEncodeWithoutMetadata(blockHeight uint64, skipMetada // PrevLockedStakeEntries data = append(data, EncodeDeSoEncoderSlice(op.PrevLockedStakeEntries, blockHeight, skipMetadata...)...) - - // PrevEpochNumber - data = append(data, UintToBuf(op.PrevEpochNumber)...) } return data @@ -1899,13 +1892,6 @@ func (op *UtxoOperation) RawDecodeWithoutMetadata(blockHeight uint64, rr *bytes. if op.PrevLockedStakeEntries, err = DecodeDeSoEncoderSlice[*LockedStakeEntry](rr); err != nil { return errors.Wrapf(err, "UtxoOperation.Decode: Problem reading PrevLockedStakeEntries: ") } - - // PrevEpochNumber - op.PrevEpochNumber, err = ReadUvarint(rr) - if err != nil { - return errors.Wrapf(err, "UtxoOperation.Decode: Problem reading PrevEpochNumber: ") - } - } return nil From 8f95ac73a86b3dea13e440954a03e467769e1930 Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Thu, 4 May 2023 13:26:15 -0400 Subject: [PATCH 19/22] Start adding tests for unjail connect logic. --- lib/block_view_stake_test.go | 18 +- lib/block_view_validator.go | 2 +- lib/block_view_validator_test.go | 348 +++++++++++++++++++++++++------ 3 files changed, 294 insertions(+), 74 deletions(-) diff --git a/lib/block_view_stake_test.go b/lib/block_view_stake_test.go index 8cb71fbc8..fc8360997 100644 --- a/lib/block_view_stake_test.go +++ b/lib/block_view_stake_test.go @@ -12,7 +12,6 @@ import ( func TestStaking(t *testing.T) { _testStaking(t, false) _testStaking(t, true) - _testStakingWithDerivedKey(t) } func _testStaking(t *testing.T, flushToDB bool) { @@ -103,9 +102,7 @@ func _testStaking(t *testing.T, flushToDB bool) { registerAsValidatorMetadata := &RegisterAsValidatorMetadata{ Domains: [][]byte{[]byte("https://example.com")}, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerAsValidatorMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerAsValidatorMetadata, nil, flushToDB) require.NoError(t, err) validatorEntry, err := utxoView().GetValidatorByPKID(m0PKID) @@ -755,7 +752,7 @@ func _submitUnlockStakeTxn( return fees, nil } -func _testStakingWithDerivedKey(t *testing.T) { +func TestStakingWithDerivedKey(t *testing.T) { var derivedKeyPriv string var err error @@ -957,9 +954,7 @@ func _testStakingWithDerivedKey(t *testing.T) { registerAsValidatorMetadata := &RegisterAsValidatorMetadata{ Domains: [][]byte{[]byte("https://example1.com")}, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerAsValidatorMetadata, nil, true, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerAsValidatorMetadata, nil, true) require.NoError(t, err) } { @@ -967,9 +962,7 @@ func _testStakingWithDerivedKey(t *testing.T) { registerAsValidatorMetadata := &RegisterAsValidatorMetadata{ Domains: [][]byte{[]byte("https://example2.com")}, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m1Pub, m1Priv, registerAsValidatorMetadata, nil, true, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m1Pub, m1Priv, registerAsValidatorMetadata, nil, true) require.NoError(t, err) } { @@ -1783,7 +1776,6 @@ func TestStakeLockupEpochDuration(t *testing.T) { chain.snapshot = nil // For these tests, we set StakeLockupEpochDuration to 3. - // We test the lockup logic in a separate test. params.StakeLockupEpochDuration = 3 // Mine a few blocks to give the senderPkString some money. @@ -1843,7 +1835,7 @@ func TestStakeLockupEpochDuration(t *testing.T) { registerMetadata := &RegisterAsValidatorMetadata{ Domains: [][]byte{[]byte("https://m1.com")}, } - _, _, _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, true) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, true) require.NoError(t, err) validatorEntry, err := newUtxoView().GetValidatorByPKID(m0PKID) diff --git a/lib/block_view_validator.go b/lib/block_view_validator.go index 8ebf83760..1d70ef6db 100644 --- a/lib/block_view_validator.go +++ b/lib/block_view_validator.go @@ -1717,7 +1717,7 @@ func (bav *UtxoView) IsValidUnjailValidatorMetadata(transactorPublicKey []byte, } // Validate sufficient epochs have elapsed for validator to be unjailed. - if validatorEntry.JailedAtEpochNumber+bav.Params.ValidatorJailEpochDuration < currentEpochNumber { + if validatorEntry.JailedAtEpochNumber+bav.Params.ValidatorJailEpochDuration > currentEpochNumber { return errors.Wrapf(RuleErrorUnjailingValidatorTooEarly, "UtxoView.IsValidUnjailValidatorMetadata: ") } diff --git a/lib/block_view_validator_test.go b/lib/block_view_validator_test.go index 9cf525c79..bd4e8ce5a 100644 --- a/lib/block_view_validator_test.go +++ b/lib/block_view_validator_test.go @@ -13,7 +13,6 @@ import ( func TestValidatorRegistration(t *testing.T) { _testValidatorRegistration(t, false) _testValidatorRegistration(t, true) - _testValidatorRegistrationWithDerivedKey(t) } func _testValidatorRegistration(t *testing.T, flushToDB bool) { @@ -93,9 +92,7 @@ func _testValidatorRegistration(t *testing.T, flushToDB bool) { Domains: [][]byte{[]byte("https://example.com")}, DisableDelegatedStake: false, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) require.Error(t, err) require.Contains(t, err.Error(), RuleErrorProofofStakeTxnBeforeBlockHeight) @@ -109,9 +106,7 @@ func _testValidatorRegistration(t *testing.T, flushToDB bool) { Domains: [][]byte{}, DisableDelegatedStake: false, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) require.Error(t, err) require.Contains(t, err.Error(), RuleErrorValidatorNoDomains) } @@ -125,9 +120,7 @@ func _testValidatorRegistration(t *testing.T, flushToDB bool) { Domains: domains, DisableDelegatedStake: false, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) require.Error(t, err) require.Contains(t, err.Error(), RuleErrorValidatorTooManyDomains) } @@ -137,9 +130,7 @@ func _testValidatorRegistration(t *testing.T, flushToDB bool) { Domains: [][]byte{[]byte("InvalidURL")}, DisableDelegatedStake: false, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) require.Error(t, err) require.Contains(t, err.Error(), RuleErrorValidatorInvalidDomain) } @@ -149,9 +140,7 @@ func _testValidatorRegistration(t *testing.T, flushToDB bool) { Domains: [][]byte{[]byte("https://example.com"), []byte("https://example.com")}, DisableDelegatedStake: false, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) require.Error(t, err) require.Contains(t, err.Error(), RuleErrorValidatorDuplicateDomains) } @@ -162,9 +151,7 @@ func _testValidatorRegistration(t *testing.T, flushToDB bool) { DisableDelegatedStake: false, } extraData := map[string][]byte{"TestKey": []byte("TestValue1")} - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, extraData, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, extraData, flushToDB) require.NoError(t, err) } { @@ -201,9 +188,7 @@ func _testValidatorRegistration(t *testing.T, flushToDB bool) { DisableDelegatedStake: false, } extraData := map[string][]byte{"TestKey": []byte("TestValue2")} - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, extraData, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, extraData, flushToDB) require.NoError(t, err) } { @@ -219,18 +204,18 @@ func _testValidatorRegistration(t *testing.T, flushToDB bool) { } { // Sad path: unregister validator that doesn't exist - _, _, _, err = _submitUnregisterAsValidatorTxn(testMeta, m1Pub, m1Priv, flushToDB) + _, err = _submitUnregisterAsValidatorTxn(testMeta, m1Pub, m1Priv, flushToDB) require.Error(t, err) require.Contains(t, err.Error(), RuleErrorValidatorNotFound) } { // Happy path: unregister validator - _, _, _, err = _submitUnregisterAsValidatorTxn(testMeta, m0Pub, m0Priv, flushToDB) + _, err = _submitUnregisterAsValidatorTxn(testMeta, m0Pub, m0Priv, flushToDB) require.NoError(t, err) } { // Sad path: unregister validator that doesn't exist - _, _, _, err = _submitUnregisterAsValidatorTxn(testMeta, m0Pub, m0Priv, flushToDB) + _, err = _submitUnregisterAsValidatorTxn(testMeta, m0Pub, m0Priv, flushToDB) require.Error(t, err) require.Contains(t, err.Error(), RuleErrorValidatorNotFound) } @@ -265,7 +250,7 @@ func _submitRegisterAsValidatorTxn( metadata *RegisterAsValidatorMetadata, extraData map[string][]byte, flushToDB bool, -) (_utxoOps []*UtxoOperation, _txn *MsgDeSoTxn, _height uint32, _err error) { +) (_fees uint64, _err error) { // Record transactor's prevBalance. prevBalance := _getBalance(testMeta.t, testMeta.chain, testMeta.mempool, transactorPublicKeyBase58Check) @@ -283,7 +268,7 @@ func _submitRegisterAsValidatorTxn( []*DeSoOutput{}, ) if err != nil { - return nil, nil, 0, err + return 0, err } require.Equal(testMeta.t, totalInputMake, changeAmountMake+feesMake) @@ -300,7 +285,7 @@ func _submitRegisterAsValidatorTxn( false, ) if err != nil { - return nil, nil, 0, err + return 0, err } require.Equal(testMeta.t, totalInput, totalOutput+fees) require.Equal(testMeta.t, totalInput, totalInputMake) @@ -314,7 +299,7 @@ func _submitRegisterAsValidatorTxn( testMeta.expectedSenderBalances = append(testMeta.expectedSenderBalances, prevBalance) testMeta.txnOps = append(testMeta.txnOps, utxoOps) testMeta.txns = append(testMeta.txns, txn) - return utxoOps, txn, testMeta.savedHeight, nil + return fees, nil } func _submitUnregisterAsValidatorTxn( @@ -322,7 +307,7 @@ func _submitUnregisterAsValidatorTxn( transactorPublicKeyBase58Check string, transactorPrivateKeyBase58Check string, flushToDB bool, -) (_utxoOps []*UtxoOperation, _txn *MsgDeSoTxn, _height uint32, _err error) { +) (_fees uint64, _err error) { // Record transactor's prevBalance. prevBalance := _getBalance(testMeta.t, testMeta.chain, testMeta.mempool, transactorPublicKeyBase58Check) @@ -340,7 +325,7 @@ func _submitUnregisterAsValidatorTxn( []*DeSoOutput{}, ) if err != nil { - return nil, nil, 0, err + return 0, err } require.Equal(testMeta.t, totalInputMake, changeAmountMake+feesMake) @@ -357,7 +342,7 @@ func _submitUnregisterAsValidatorTxn( false, ) if err != nil { - return nil, nil, 0, err + return 0, err } require.Equal(testMeta.t, totalInput, totalOutput+fees) require.Equal(testMeta.t, totalInput, totalInputMake) @@ -371,10 +356,10 @@ func _submitUnregisterAsValidatorTxn( testMeta.expectedSenderBalances = append(testMeta.expectedSenderBalances, prevBalance) testMeta.txnOps = append(testMeta.txnOps, utxoOps) testMeta.txns = append(testMeta.txns, txn) - return utxoOps, txn, testMeta.savedHeight, nil + return fees, nil } -func _testValidatorRegistrationWithDerivedKey(t *testing.T) { +func TestValidatorRegistrationWithDerivedKey(t *testing.T) { var err error // Initialize balance model fork heights. @@ -719,9 +704,7 @@ func _testGetTopActiveValidatorsByStake(t *testing.T, flushToDB bool) { registerMetadata := &RegisterAsValidatorMetadata{ Domains: [][]byte{[]byte("https://m0.com")}, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) require.NoError(t, err) // Verify top validators. @@ -736,9 +719,7 @@ func _testGetTopActiveValidatorsByStake(t *testing.T, flushToDB bool) { registerMetadata := &RegisterAsValidatorMetadata{ Domains: [][]byte{[]byte("https://m1.com")}, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m1Pub, m1Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m1Pub, m1Priv, registerMetadata, nil, flushToDB) require.NoError(t, err) // Verify top validators. @@ -751,9 +732,7 @@ func _testGetTopActiveValidatorsByStake(t *testing.T, flushToDB bool) { registerMetadata := &RegisterAsValidatorMetadata{ Domains: [][]byte{[]byte("https://m2.com")}, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m2Pub, m2Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m2Pub, m2Priv, registerMetadata, nil, flushToDB) require.NoError(t, err) // Verify top validators. @@ -837,7 +816,7 @@ func _testGetTopActiveValidatorsByStake(t *testing.T, flushToDB bool) { } { // m2 unregisters as validator. - _, _, _, err = _submitUnregisterAsValidatorTxn(testMeta, m2Pub, m2Priv, flushToDB) + _, err = _submitUnregisterAsValidatorTxn(testMeta, m2Pub, m2Priv, flushToDB) require.NoError(t, err) // Verify top validators. @@ -1143,9 +1122,7 @@ func _testUpdatingValidatorDisableDelegatedStake(t *testing.T, flushToDB bool) { Domains: [][]byte{[]byte("https://m0.com")}, DisableDelegatedStake: false, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) require.NoError(t, err) validatorEntry, err = utxoView().GetValidatorByPKID(m0PKID) @@ -1163,9 +1140,7 @@ func _testUpdatingValidatorDisableDelegatedStake(t *testing.T, flushToDB bool) { Domains: [][]byte{[]byte("https://m0.com")}, DisableDelegatedStake: true, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) require.NoError(t, err) validatorEntry, err = utxoView().GetValidatorByPKID(m0PKID) @@ -1207,9 +1182,7 @@ func _testUpdatingValidatorDisableDelegatedStake(t *testing.T, flushToDB bool) { Domains: [][]byte{[]byte("https://m0.com")}, DisableDelegatedStake: false, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) require.NoError(t, err) validatorEntry, err = utxoView().GetValidatorByPKID(m0PKID) @@ -1238,9 +1211,7 @@ func _testUpdatingValidatorDisableDelegatedStake(t *testing.T, flushToDB bool) { Domains: [][]byte{[]byte("https://m0.com")}, DisableDelegatedStake: true, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) require.Error(t, err) require.Contains(t, err.Error(), RuleErrorValidatorDisablingExistingDelegatedStakers) } @@ -1336,9 +1307,7 @@ func _testUnregisterAsValidator(t *testing.T, flushToDB bool) { registerMetadata := &RegisterAsValidatorMetadata{ Domains: [][]byte{[]byte("https://m0.com")}, } - _, _, _, err = _submitRegisterAsValidatorTxn( - testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB, - ) + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) require.NoError(t, err) validatorEntry, err = utxoView().GetValidatorByPKID(m0PKID) @@ -1414,7 +1383,7 @@ func _testUnregisterAsValidator(t *testing.T, flushToDB bool) { } { // m0 unregisters as a validator. - _, _, _, err = _submitUnregisterAsValidatorTxn(testMeta, m0Pub, m0Priv, flushToDB) + _, err = _submitUnregisterAsValidatorTxn(testMeta, m0Pub, m0Priv, flushToDB) require.NoError(t, err) // m0's ValidatorEntry is deleted. @@ -1461,5 +1430,264 @@ func TestUnjailValidator(t *testing.T) { } func _testUnjailValidator(t *testing.T, flushToDB bool) { + var validatorEntry *ValidatorEntry + var err error + + // Initialize balance model fork heights. + setBalanceModelBlockHeights() + defer resetBalanceModelBlockHeights() + + // Initialize test chain and miner. + chain, params, db := NewLowDifficultyBlockchain(t) + mempool, miner := NewTestMiner(t, chain, params, true) + + // Initialize PoS fork height. + params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight = uint32(1) + GlobalDeSoParams.EncoderMigrationHeights = GetEncoderMigrationHeights(¶ms.ForkHeights) + GlobalDeSoParams.EncoderMigrationHeightsList = GetEncoderMigrationHeightsList(¶ms.ForkHeights) + chain.snapshot = nil + + // For these tests, we set ValidatorJailEpochDuration to 3. + params.ValidatorJailEpochDuration = 3 + + utxoView := func() *UtxoView { + newUtxoView, err := mempool.GetAugmentedUniversalView() + require.NoError(t, err) + return newUtxoView + } + + // Mine a few blocks to give the senderPkString some money. + for ii := 0; ii < 10; ii++ { + _, err = miner.MineAndProcessSingleBlock(0, mempool) + require.NoError(t, err) + } + + // We build the testMeta obj after mining blocks so that we save the correct block height. + blockHeight := uint64(chain.blockTip().Height + 1) + testMeta := &TestMeta{ + t: t, + chain: chain, + params: params, + db: db, + mempool: mempool, + miner: miner, + savedHeight: uint32(blockHeight), + feeRateNanosPerKb: uint64(101), + } + + _registerOrTransferWithTestMeta(testMeta, "m0", senderPkString, m0Pub, senderPrivString, 1e3) + _registerOrTransferWithTestMeta(testMeta, "m1", senderPkString, m1Pub, senderPrivString, 1e3) + _registerOrTransferWithTestMeta(testMeta, "", senderPkString, paramUpdaterPub, senderPrivString, 1e3) + + m0PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m0PkBytes).PKID + m1PKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, m1PkBytes).PKID + + // Seed a CurrentEpochEntry. + epochUtxoView, err := NewUtxoView(db, params, chain.postgres, chain.snapshot) + require.NoError(t, err) + epochUtxoView._setCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}) + require.NoError(t, epochUtxoView.FlushToDb(blockHeight)) + currentEpochNumber, err := utxoView().GetCurrentEpochNumber() + require.NoError(t, err) + + { + // ParamUpdater set min fee rate + params.ExtraRegtestParamUpdaterKeys[MakePkMapKey(paramUpdaterPkBytes)] = true + _updateGlobalParamsEntryWithTestMeta( + testMeta, + testMeta.feeRateNanosPerKb, + paramUpdaterPub, + paramUpdaterPriv, + -1, + int64(testMeta.feeRateNanosPerKb), + -1, + -1, + -1, + ) + } + { + // m0 registers as a validator. + registerMetadata := &RegisterAsValidatorMetadata{ + Domains: [][]byte{[]byte("https://example.com")}, + } + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) + require.NoError(t, err) + + validatorEntry, err = utxoView().GetValidatorByPKID(m0PKID) + require.NoError(t, err) + require.NotNil(t, validatorEntry) + } + { + // RuleErrorUnjailingNonjailedValidator + _, err = _submitUnjailValidatorTxn(testMeta, m0Pub, m0Priv, nil, flushToDB) + require.Error(t, err) + require.Contains(t, err.Error(), RuleErrorUnjailingNonjailedValidator) + } + { + // m0 is jailed. + + // Delete m0's ValidatorEntry from the UtxoView. + delete(mempool.universalUtxoView.ValidatorMapKeyToValidatorEntry, validatorEntry.ToMapKey()) + delete(mempool.readOnlyUtxoView.ValidatorMapKeyToValidatorEntry, validatorEntry.ToMapKey()) + + // Set JailedAtEpochNumber. + validatorEntry.JailedAtEpochNumber = currentEpochNumber + + // Store m0's ValidatorEntry in the db. + tmpUtxoView, err := NewUtxoView(db, params, chain.postgres, chain.snapshot) + require.NoError(t, err) + tmpUtxoView._setValidatorEntryMappings(validatorEntry) + require.NoError(t, tmpUtxoView.FlushToDb(blockHeight)) + + // Verify m0 is jailed. + validatorEntry, err = utxoView().GetValidatorByPKID(m0PKID) + require.NoError(t, err) + require.NotNil(t, validatorEntry) + require.Equal(t, validatorEntry.Status(), ValidatorStatusJailed) + } + { + // m1 stakes with m0. Succeeds. You can stake to a jailed validator. + stakeMetadata := &StakeMetadata{ + ValidatorPublicKey: NewPublicKey(m0PkBytes), + StakeAmountNanos: uint256.NewInt().SetUint64(100), + } + _, err = _submitStakeTxn(testMeta, m1Pub, m1Priv, stakeMetadata, nil, flushToDB) + require.NoError(t, err) + + stakeEntry, err := utxoView().GetStakeEntry(m0PKID, m1PKID) + require.NoError(t, err) + require.NotNil(t, stakeEntry) + } + { + // m1 unstakes from m0. Succeeds. You can unstake from a jailed validator. + unstakeMetadata := &UnstakeMetadata{ + ValidatorPublicKey: NewPublicKey(m0PkBytes), + UnstakeAmountNanos: uint256.NewInt().SetUint64(100), + } + _, err = _submitUnstakeTxn(testMeta, m1Pub, m1Priv, unstakeMetadata, nil, flushToDB) + require.NoError(t, err) + + stakeEntry, err := utxoView().GetStakeEntry(m0PKID, m1PKID) + require.NoError(t, err) + require.Nil(t, stakeEntry) + + lockedStakeEntry, err := utxoView().GetLockedStakeEntry(m0PKID, m1PKID, currentEpochNumber) + require.NoError(t, err) + require.NotNil(t, lockedStakeEntry) + } + { + // RuleErrorValidatorNotFound + _, err = _submitUnjailValidatorTxn(testMeta, m1Pub, m1Priv, nil, flushToDB) + require.Error(t, err) + require.Contains(t, err.Error(), RuleErrorValidatorNotFound) + } + { + // RuleErrorUnjailingValidatorTooEarly + _, err = _submitUnjailValidatorTxn(testMeta, m0Pub, m0Priv, nil, flushToDB) + require.Error(t, err) + require.Contains(t, err.Error(), RuleErrorUnjailingValidatorTooEarly) + } + { + // Simulate three epochs passing by seeding a new CurrentEpochEntry. + + // Delete the CurrentEpochEntry from the UtxoView. + mempool.universalUtxoView.CurrentEpochEntry = nil + mempool.readOnlyUtxoView.CurrentEpochEntry = nil + + // Store a new CurrentEpochEntry in the db. + epochUtxoView, err = NewUtxoView(db, params, chain.postgres, chain.snapshot) + require.NoError(t, err) + epochUtxoView._setCurrentEpochEntry( + &EpochEntry{EpochNumber: currentEpochNumber + 3, FinalBlockHeight: blockHeight + 10}, + ) + require.NoError(t, epochUtxoView.FlushToDb(blockHeight)) + + // Verify CurrentEpochNumber. + currentEpochNumber, err = utxoView().GetCurrentEpochNumber() + require.NoError(t, err) + require.Equal(t, currentEpochNumber, uint64(4)) + } + { + // RuleErrorProofofStakeTxnBeforeBlockHeight + //params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight = uint32(0) + //GlobalDeSoParams.EncoderMigrationHeights = GetEncoderMigrationHeights(¶ms.ForkHeights) + //GlobalDeSoParams.EncoderMigrationHeightsList = GetEncoderMigrationHeightsList(¶ms.ForkHeights) + // + //_, err = _submitUnjailValidatorTxn(testMeta, m0Pub, m0Priv, nil, flushToDB) + //require.NoError(t, err) + //require.Contains(t, err.Error(), RuleErrorProofofStakeTxnBeforeBlockHeight) + // + //params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight = uint32(1) + //GlobalDeSoParams.EncoderMigrationHeights = GetEncoderMigrationHeights(¶ms.ForkHeights) + //GlobalDeSoParams.EncoderMigrationHeightsList = GetEncoderMigrationHeightsList(¶ms.ForkHeights) + } + { + // m0 unjails himself. + } + + // Flush mempool to the db and test rollbacks. + require.NoError(t, mempool.universalUtxoView.FlushToDb(blockHeight)) + _executeAllTestRollbackAndFlush(testMeta) +} + +func TestUnjailValidatorWithDerivedKey(t *testing.T) { // TODO } + +func _submitUnjailValidatorTxn( + testMeta *TestMeta, + transactorPublicKeyBase58Check string, + transactorPrivateKeyBase58Check string, + extraData map[string][]byte, + flushToDB bool, +) (_fees uint64, _err error) { + // Record transactor's prevBalance. + prevBalance := _getBalance(testMeta.t, testMeta.chain, testMeta.mempool, transactorPublicKeyBase58Check) + + // Convert PublicKeyBase58Check to PkBytes. + updaterPkBytes, _, err := Base58CheckDecode(transactorPublicKeyBase58Check) + require.NoError(testMeta.t, err) + + // Create the transaction. + txn, totalInputMake, changeAmountMake, feesMake, err := testMeta.chain.CreateUnjailValidatorTxn( + updaterPkBytes, + &UnjailValidatorMetadata{}, + extraData, + testMeta.feeRateNanosPerKb, + testMeta.mempool, + []*DeSoOutput{}, + ) + if err != nil { + return 0, err + } + require.Equal(testMeta.t, totalInputMake, changeAmountMake+feesMake) + + // Sign the transaction now that its inputs are set up. + _signTxn(testMeta.t, txn, transactorPrivateKeyBase58Check) + + // Connect the transaction. + utxoOps, totalInput, totalOutput, fees, err := testMeta.mempool.universalUtxoView.ConnectTransaction( + txn, + txn.Hash(), + getTxnSize(*txn), + testMeta.savedHeight, + true, + false, + ) + if err != nil { + return 0, err + } + require.Equal(testMeta.t, totalInput, totalOutput+fees) + require.Equal(testMeta.t, totalInput, totalInputMake) + require.Equal(testMeta.t, OperationTypeUnjailValidator, utxoOps[len(utxoOps)-1].Type) + if flushToDB { + require.NoError(testMeta.t, testMeta.mempool.universalUtxoView.FlushToDb(uint64(testMeta.savedHeight))) + } + require.NoError(testMeta.t, testMeta.mempool.RegenerateReadOnlyView()) + + // Record the txn. + testMeta.expectedSenderBalances = append(testMeta.expectedSenderBalances, prevBalance) + testMeta.txnOps = append(testMeta.txnOps, utxoOps) + testMeta.txns = append(testMeta.txns, txn) + return fees, nil +} From c5fe55ebc795cf56a02c59ce58e14bc4ebc6510e Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Thu, 4 May 2023 16:14:35 -0400 Subject: [PATCH 20/22] Add unjail validator tests. --- lib/block_view_validator_test.go | 295 +++++++++++++++++++++++++++++-- lib/network.go | 2 + 2 files changed, 279 insertions(+), 18 deletions(-) diff --git a/lib/block_view_validator_test.go b/lib/block_view_validator_test.go index bd4e8ce5a..0848f404f 100644 --- a/lib/block_view_validator_test.go +++ b/lib/block_view_validator_test.go @@ -1510,12 +1510,14 @@ func _testUnjailValidator(t *testing.T, flushToDB bool) { registerMetadata := &RegisterAsValidatorMetadata{ Domains: [][]byte{[]byte("https://example.com")}, } - _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, nil, flushToDB) + extraData := map[string][]byte{"TestKey": []byte("TestValue1")} + _, err = _submitRegisterAsValidatorTxn(testMeta, m0Pub, m0Priv, registerMetadata, extraData, flushToDB) require.NoError(t, err) validatorEntry, err = utxoView().GetValidatorByPKID(m0PKID) require.NoError(t, err) require.NotNil(t, validatorEntry) + require.Equal(t, validatorEntry.ExtraData["TestKey"], []byte("TestValue1")) } { // RuleErrorUnjailingNonjailedValidator @@ -1524,7 +1526,9 @@ func _testUnjailValidator(t *testing.T, flushToDB bool) { require.Contains(t, err.Error(), RuleErrorUnjailingNonjailedValidator) } { - // m0 is jailed. + // m0 is jailed. Since this update takes place outside a transaction, + // we cannot test rollbacks. We will run into an error where m0 is + // trying to unjail himself, but he was never jailed. // Delete m0's ValidatorEntry from the UtxoView. delete(mempool.universalUtxoView.ValidatorMapKeyToValidatorEntry, validatorEntry.ToMapKey()) @@ -1609,29 +1613,284 @@ func _testUnjailValidator(t *testing.T, flushToDB bool) { } { // RuleErrorProofofStakeTxnBeforeBlockHeight - //params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight = uint32(0) - //GlobalDeSoParams.EncoderMigrationHeights = GetEncoderMigrationHeights(¶ms.ForkHeights) - //GlobalDeSoParams.EncoderMigrationHeightsList = GetEncoderMigrationHeightsList(¶ms.ForkHeights) - // - //_, err = _submitUnjailValidatorTxn(testMeta, m0Pub, m0Priv, nil, flushToDB) - //require.NoError(t, err) - //require.Contains(t, err.Error(), RuleErrorProofofStakeTxnBeforeBlockHeight) - // - //params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight = uint32(1) - //GlobalDeSoParams.EncoderMigrationHeights = GetEncoderMigrationHeights(¶ms.ForkHeights) - //GlobalDeSoParams.EncoderMigrationHeightsList = GetEncoderMigrationHeightsList(¶ms.ForkHeights) + params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight = math.MaxUint32 + GlobalDeSoParams.EncoderMigrationHeights = GetEncoderMigrationHeights(¶ms.ForkHeights) + GlobalDeSoParams.EncoderMigrationHeightsList = GetEncoderMigrationHeightsList(¶ms.ForkHeights) + + _, err = _submitUnjailValidatorTxn(testMeta, m0Pub, m0Priv, nil, flushToDB) + require.Error(t, err) + require.Contains(t, err.Error(), RuleErrorProofofStakeTxnBeforeBlockHeight) + + params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight = uint32(1) + GlobalDeSoParams.EncoderMigrationHeights = GetEncoderMigrationHeights(¶ms.ForkHeights) + GlobalDeSoParams.EncoderMigrationHeightsList = GetEncoderMigrationHeightsList(¶ms.ForkHeights) } { // m0 unjails himself. - } + validatorEntry, err = utxoView().GetValidatorByPKID(m0PKID) + require.NoError(t, err) + require.NotNil(t, validatorEntry) + require.Equal(t, validatorEntry.Status(), ValidatorStatusJailed) + require.Equal(t, validatorEntry.LastActiveAtEpochNumber, uint64(1)) - // Flush mempool to the db and test rollbacks. - require.NoError(t, mempool.universalUtxoView.FlushToDb(blockHeight)) - _executeAllTestRollbackAndFlush(testMeta) + extraData := map[string][]byte{"TestKey": []byte("TestValue2")} + _, err = _submitUnjailValidatorTxn(testMeta, m0Pub, m0Priv, extraData, flushToDB) + require.NoError(t, err) + + validatorEntry, err = utxoView().GetValidatorByPKID(m0PKID) + require.NoError(t, err) + require.NotNil(t, validatorEntry) + require.Equal(t, validatorEntry.Status(), ValidatorStatusActive) + require.Equal(t, validatorEntry.LastActiveAtEpochNumber, uint64(4)) + require.Equal(t, validatorEntry.ExtraData["TestKey"], []byte("TestValue2")) + } } func TestUnjailValidatorWithDerivedKey(t *testing.T) { - // TODO + var validatorEntry *ValidatorEntry + var derivedKeyPriv string + var err error + + // Initialize balance model fork heights. + setBalanceModelBlockHeights() + defer resetBalanceModelBlockHeights() + + // Initialize test chain and miner. + chain, params, db := NewLowDifficultyBlockchain(t) + mempool, miner := NewTestMiner(t, chain, params, true) + + // Initialize fork heights. + params.ForkHeights.ProofOfStakeNewTxnTypesBlockHeight = uint32(1) + GlobalDeSoParams.EncoderMigrationHeights = GetEncoderMigrationHeights(¶ms.ForkHeights) + GlobalDeSoParams.EncoderMigrationHeightsList = GetEncoderMigrationHeightsList(¶ms.ForkHeights) + + // Mine a few blocks to give the senderPkString some money. + for ii := 0; ii < 10; ii++ { + _, err = miner.MineAndProcessSingleBlock(0, mempool) + require.NoError(t, err) + } + + // We build the testMeta obj after mining blocks so that we save the correct block height. + blockHeight := uint64(chain.blockTip().Height) + 1 + testMeta := &TestMeta{ + t: t, + chain: chain, + params: params, + db: db, + mempool: mempool, + miner: miner, + savedHeight: uint32(blockHeight), + feeRateNanosPerKb: uint64(101), + } + + _registerOrTransferWithTestMeta(testMeta, "", senderPkString, paramUpdaterPub, senderPrivString, 1e3) + + senderPkBytes, _, err := Base58CheckDecode(senderPkString) + require.NoError(t, err) + senderPrivBytes, _, err := Base58CheckDecode(senderPrivString) + require.NoError(t, err) + senderPrivKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), senderPrivBytes) + senderPKID := DBGetPKIDEntryForPublicKey(db, chain.snapshot, senderPkBytes).PKID + + newUtxoView := func() *UtxoView { + utxoView, err := NewUtxoView(db, params, chain.postgres, chain.snapshot) + require.NoError(t, err) + return utxoView + } + + _submitAuthorizeDerivedKeyUnjailValidatorTxn := func(count uint64) (string, error) { + utxoView := newUtxoView() + + txnSpendingLimit := &TransactionSpendingLimit{ + GlobalDESOLimit: NanosPerUnit, // 1 $DESO spending limit + TransactionCountLimitMap: map[TxnType]uint64{ + TxnTypeAuthorizeDerivedKey: 1, + TxnTypeUnjailValidator: count, + }, + } + + derivedKeyMetadata, derivedKeyAuthPriv := _getAuthorizeDerivedKeyMetadataWithTransactionSpendingLimit( + t, senderPrivKey, blockHeight+5, txnSpendingLimit, false, blockHeight, + ) + derivedKeyAuthPrivBase58Check := Base58CheckEncode(derivedKeyAuthPriv.Serialize(), true, params) + + prevBalance := _getBalance(testMeta.t, testMeta.chain, testMeta.mempool, senderPkString) + + utxoOps, txn, _, err := _doAuthorizeTxnWithExtraDataAndSpendingLimits( + testMeta, + utxoView, + testMeta.feeRateNanosPerKb, + senderPkBytes, + derivedKeyMetadata.DerivedPublicKey, + derivedKeyAuthPrivBase58Check, + derivedKeyMetadata.ExpirationBlock, + derivedKeyMetadata.AccessSignature, + false, + nil, + nil, + txnSpendingLimit, + ) + if err != nil { + return "", err + } + require.NoError(t, utxoView.FlushToDb(blockHeight)) + testMeta.expectedSenderBalances = append(testMeta.expectedSenderBalances, prevBalance) + testMeta.txnOps = append(testMeta.txnOps, utxoOps) + testMeta.txns = append(testMeta.txns, txn) + + err = utxoView.ValidateDerivedKey( + senderPkBytes, derivedKeyMetadata.DerivedPublicKey, blockHeight, + ) + require.NoError(t, err) + return derivedKeyAuthPrivBase58Check, nil + } + + _submitUnjailValidatorTxnWithDerivedKey := func(transactorPkBytes []byte, derivedKeyPrivBase58Check string) error { + utxoView := newUtxoView() + // Construct txn. + txn, _, _, _, err := testMeta.chain.CreateUnjailValidatorTxn( + transactorPkBytes, + &UnjailValidatorMetadata{}, + make(map[string][]byte), + testMeta.feeRateNanosPerKb, + mempool, + []*DeSoOutput{}, + ) + if err != nil { + return err + } + // Sign txn. + _signTxnWithDerivedKeyAndType(t, txn, derivedKeyPrivBase58Check, 1) + // Store the original transactor balance. + transactorPublicKeyBase58Check := Base58CheckEncode(transactorPkBytes, false, params) + prevBalance := _getBalance(testMeta.t, testMeta.chain, testMeta.mempool, transactorPublicKeyBase58Check) + // Connect txn. + utxoOps, _, _, _, err := utxoView.ConnectTransaction( + txn, + txn.Hash(), + getTxnSize(*txn), + testMeta.savedHeight, + true, + false, + ) + if err != nil { + return err + } + // Flush UTXO view to the db. + require.NoError(t, utxoView.FlushToDb(blockHeight)) + // Track txn for rolling back. + testMeta.expectedSenderBalances = append(testMeta.expectedSenderBalances, prevBalance) + testMeta.txnOps = append(testMeta.txnOps, utxoOps) + testMeta.txns = append(testMeta.txns, txn) + return nil + } + + // Seed a CurrentEpochEntry. + epochUtxoView := newUtxoView() + epochUtxoView._setCurrentEpochEntry(&EpochEntry{EpochNumber: 1, FinalBlockHeight: blockHeight + 10}) + require.NoError(t, epochUtxoView.FlushToDb(blockHeight)) + currentEpochNumber, err := newUtxoView().GetCurrentEpochNumber() + require.NoError(t, err) + + { + // ParamUpdater set min fee rate + params.ExtraRegtestParamUpdaterKeys[MakePkMapKey(paramUpdaterPkBytes)] = true + _updateGlobalParamsEntryWithTestMeta( + testMeta, + testMeta.feeRateNanosPerKb, + paramUpdaterPub, + paramUpdaterPriv, + -1, + int64(testMeta.feeRateNanosPerKb), + -1, + -1, + -1, + ) + } + { + // sender registers as a validator. + registerMetadata := &RegisterAsValidatorMetadata{ + Domains: [][]byte{[]byte("https://example.com")}, + } + _, err = _submitRegisterAsValidatorTxn(testMeta, senderPkString, senderPrivString, registerMetadata, nil, true) + require.NoError(t, err) + + validatorEntry, err = newUtxoView().GetValidatorByPKID(senderPKID) + require.NoError(t, err) + require.NotNil(t, validatorEntry) + } + { + // sender is jailed. Since this update takes place outside a transaction, + // we cannot test rollbacks. We will run into an error where sender is + // trying to unjail himself, but he was never jailed. + + // Delete sender's ValidatorEntry from the UtxoView. + delete(mempool.universalUtxoView.ValidatorMapKeyToValidatorEntry, validatorEntry.ToMapKey()) + delete(mempool.readOnlyUtxoView.ValidatorMapKeyToValidatorEntry, validatorEntry.ToMapKey()) + + // Set JailedAtEpochNumber. + validatorEntry.JailedAtEpochNumber = currentEpochNumber + + // Store sender's ValidatorEntry in the db. + tmpUtxoView, err := NewUtxoView(db, params, chain.postgres, chain.snapshot) + require.NoError(t, err) + tmpUtxoView._setValidatorEntryMappings(validatorEntry) + require.NoError(t, tmpUtxoView.FlushToDb(blockHeight)) + + // Verify sender is jailed. + validatorEntry, err = newUtxoView().GetValidatorByPKID(senderPKID) + require.NoError(t, err) + require.NotNil(t, validatorEntry) + require.Equal(t, validatorEntry.Status(), ValidatorStatusJailed) + } + { + // sender creates a DerivedKey that can perform one UnjailValidator txn. + derivedKeyPriv, err = _submitAuthorizeDerivedKeyUnjailValidatorTxn(1) + require.NoError(t, err) + } + { + // RuleErrorUnjailingValidatorTooEarly + err = _submitUnjailValidatorTxnWithDerivedKey(senderPkBytes, derivedKeyPriv) + require.Error(t, err) + require.Contains(t, err.Error(), RuleErrorUnjailingValidatorTooEarly) + } + { + // Simulate three epochs passing by seeding a new CurrentEpochEntry. + + // Delete the CurrentEpochEntry from the UtxoView. + mempool.universalUtxoView.CurrentEpochEntry = nil + mempool.readOnlyUtxoView.CurrentEpochEntry = nil + + // Store a new CurrentEpochEntry in the db. + epochUtxoView, err = NewUtxoView(db, params, chain.postgres, chain.snapshot) + require.NoError(t, err) + epochUtxoView._setCurrentEpochEntry( + &EpochEntry{EpochNumber: currentEpochNumber + 3, FinalBlockHeight: blockHeight + 10}, + ) + require.NoError(t, epochUtxoView.FlushToDb(blockHeight)) + + // Verify CurrentEpochNumber. + currentEpochNumber, err = newUtxoView().GetCurrentEpochNumber() + require.NoError(t, err) + require.Equal(t, currentEpochNumber, uint64(4)) + } + { + // sender unjails himself using a DerivedKey. + validatorEntry, err = newUtxoView().GetValidatorByPKID(senderPKID) + require.NoError(t, err) + require.NotNil(t, validatorEntry) + require.Equal(t, validatorEntry.Status(), ValidatorStatusJailed) + require.Equal(t, validatorEntry.LastActiveAtEpochNumber, uint64(1)) + + err = _submitUnjailValidatorTxnWithDerivedKey(senderPkBytes, derivedKeyPriv) + require.NoError(t, err) + + validatorEntry, err = newUtxoView().GetValidatorByPKID(senderPKID) + require.NoError(t, err) + require.NotNil(t, validatorEntry) + require.Equal(t, validatorEntry.Status(), ValidatorStatusActive) + require.Equal(t, validatorEntry.LastActiveAtEpochNumber, uint64(4)) + } } func _submitUnjailValidatorTxn( diff --git a/lib/network.go b/lib/network.go index 339b5ee59..c096f701f 100644 --- a/lib/network.go +++ b/lib/network.go @@ -583,6 +583,8 @@ func NewTxnMetadata(txType TxnType) (DeSoTxnMetadata, error) { return (&UnstakeMetadata{}).New(), nil case TxnTypeUnlockStake: return (&UnlockStakeMetadata{}).New(), nil + case TxnTypeUnjailValidator: + return (&UnjailValidatorMetadata{}).New(), nil default: return nil, fmt.Errorf("NewTxnMetadata: Unrecognized TxnType: %v; make sure you add the new type of transaction to NewTxnMetadata", txType) } From 6017710f5533a9fe2d7a85e677b278d95f95b9ef Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Thu, 4 May 2023 16:24:09 -0400 Subject: [PATCH 21/22] Retrigger buildkite ci. From 359940c8ac82f615144cbba52560b8c2f2944ac2 Mon Sep 17 00:00:00 2001 From: mattfoley8 Date: Mon, 8 May 2023 10:27:51 -0400 Subject: [PATCH 22/22] Address PR feedback. --- lib/block_view_validator.go | 43 +++++++++---------------------------- 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/lib/block_view_validator.go b/lib/block_view_validator.go index 1d70ef6db..8993dbdc7 100644 --- a/lib/block_view_validator.go +++ b/lib/block_view_validator.go @@ -533,25 +533,13 @@ func (txindexMetadata *UnregisterAsValidatorTxindexMetadata) GetEncoderType() En // type UnjailValidatorTxindexMetadata struct { - ValidatorPublicKeyBase58Check string } func (txindexMetadata *UnjailValidatorTxindexMetadata) RawEncodeWithoutMetadata(blockHeight uint64, skipMetadata ...bool) []byte { - var data []byte - data = append(data, EncodeByteArray([]byte(txindexMetadata.ValidatorPublicKeyBase58Check))...) - return data + return []byte{} } func (txindexMetadata *UnjailValidatorTxindexMetadata) RawDecodeWithoutMetadata(blockHeight uint64, rr *bytes.Reader) error { - var err error - - // ValidatorPublicKeyBase58Check - validatorPublicKeyBase58CheckBytes, err := DecodeByteArray(rr) - if err != nil { - return errors.Wrapf(err, "UnjailValidatorTxindexMetadata.Decode: Problem reading ValidatorPublicKeyBase58Check: ") - } - txindexMetadata.ValidatorPublicKeyBase58Check = string(validatorPublicKeyBase58CheckBytes) - return nil } @@ -889,7 +877,7 @@ func (bc *Blockchain) CreateUnregisterAsValidatorTxn( } // Validate txn metadata. - if err = utxoView.IsValidUnregisterAsValidatorMetadata(transactorPublicKey, metadata); err != nil { + if err = utxoView.IsValidUnregisterAsValidatorMetadata(transactorPublicKey); err != nil { return nil, 0, 0, 0, errors.Wrapf( err, "Blockchain.CreateUnregisterAsValidatorTxn: invalid txn metadata: ", ) @@ -965,7 +953,7 @@ func (bc *Blockchain) CreateUnjailValidatorTxn( } // Validate txn metadata. - if err = utxoView.IsValidUnjailValidatorMetadata(transactorPublicKey, metadata); err != nil { + if err = utxoView.IsValidUnjailValidatorMetadata(transactorPublicKey); err != nil { return nil, 0, 0, 0, errors.Wrapf( err, "Blockchain.CreateUnjailValidatorTxn: invalid txn metadata: ", ) @@ -1240,11 +1228,8 @@ func (bav *UtxoView) _connectUnregisterAsValidator( // public key so there is no need to verify anything further. } - // Grab the txn metadata. - txMeta := txn.TxnMeta.(*UnregisterAsValidatorMetadata) - - // Validate the txn metadata. - if err = bav.IsValidUnregisterAsValidatorMetadata(txn.PublicKey, txMeta); err != nil { + // Validate the transactor. + if err = bav.IsValidUnregisterAsValidatorMetadata(txn.PublicKey); err != nil { return 0, 0, nil, errors.Wrapf(err, "_connectUnregisterAsValidator: ") } @@ -1497,11 +1482,8 @@ func (bav *UtxoView) _connectUnjailValidator( // public key so there is no need to verify anything further. } - // Grab the txn metadata. - txMeta := txn.TxnMeta.(*UnjailValidatorMetadata) - - // Validate the txn metadata. - if err = bav.IsValidUnjailValidatorMetadata(txn.PublicKey, txMeta); err != nil { + // Validate the transactor. + if err = bav.IsValidUnjailValidatorMetadata(txn.PublicKey); err != nil { return 0, 0, nil, errors.Wrapf(err, "_connectUnjailValidator: ") } @@ -1670,7 +1652,7 @@ func (bav *UtxoView) IsValidRegisterAsValidatorMetadata(transactorPublicKey []by return nil } -func (bav *UtxoView) IsValidUnregisterAsValidatorMetadata(transactorPublicKey []byte, metadata *UnregisterAsValidatorMetadata) error { +func (bav *UtxoView) IsValidUnregisterAsValidatorMetadata(transactorPublicKey []byte) error { // Validate ValidatorPKID. transactorPKIDEntry := bav.GetPKIDForPublicKey(transactorPublicKey) if transactorPKIDEntry == nil || transactorPKIDEntry.isDeleted { @@ -1689,7 +1671,7 @@ func (bav *UtxoView) IsValidUnregisterAsValidatorMetadata(transactorPublicKey [] return nil } -func (bav *UtxoView) IsValidUnjailValidatorMetadata(transactorPublicKey []byte, metadata *UnjailValidatorMetadata) error { +func (bav *UtxoView) IsValidUnjailValidatorMetadata(transactorPublicKey []byte) error { // Validate ValidatorPKID. transactorPKIDEntry := bav.GetPKIDForPublicKey(transactorPublicKey) if transactorPKIDEntry == nil || transactorPKIDEntry.isDeleted { @@ -2038,11 +2020,6 @@ func (bav *UtxoView) CreateUnjailValidatorTxindexMetadata( // Cast ValidatorPublicKey to ValidatorPublicKeyBase58Check. validatorPublicKeyBase58Check := PkToString(txn.PublicKey, bav.Params) - // Construct TxindexMetadata. - txindexMetadata := &UnjailValidatorTxindexMetadata{ - ValidatorPublicKeyBase58Check: validatorPublicKeyBase58Check, - } - // Construct AffectedPublicKeys. affectedPublicKeys := []*AffectedPublicKey{ { @@ -2051,7 +2028,7 @@ func (bav *UtxoView) CreateUnjailValidatorTxindexMetadata( }, } - return txindexMetadata, affectedPublicKeys + return &UnjailValidatorTxindexMetadata{}, affectedPublicKeys } //