Skip to content

Commit

Permalink
test(e2e): support consensus version update testing
Browse files Browse the repository at this point in the history
  • Loading branch information
lklimek committed Sep 23, 2024
1 parent 72eb8cd commit 8101f7f
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 10 deletions.
23 changes: 23 additions & 0 deletions test/e2e/networks/rotate.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,29 @@ validator04 = 100
validator05 = 100
validator11 = 100


[validator_update.1070]
validator01 = 100
validator02 = 100
validator03 = 100
validator04 = 100
validator05 = 100


[validator_update.1077]
validator01 = 100
validator07 = 100
validator08 = 100
validator10 = 100
validator11 = 100


[consensus_version_updates]

1070 = 1
1076 = 0
1079 = 1

[node.seed01]
mode = "seed"
perturb = ["restart"]
Expand Down
2 changes: 2 additions & 0 deletions test/e2e/node/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Config struct {
QuorumHashUpdate map[string]string `toml:"quorum_hash_update"`
ChainLockUpdates map[string]string `toml:"chainlock_updates"`
PrivValServerType string `toml:"privval_server_type"`
ConsenusVersionUpdates map[string]int32 `toml:"consensus_version_updates"`
}

// App extracts out the application specific configuration parameters
Expand All @@ -46,6 +47,7 @@ func (cfg *Config) App() *kvstore.Config {
QuorumHashUpdate: cfg.QuorumHashUpdate,
ChainLockUpdates: cfg.ChainLockUpdates,
PrivValServerType: cfg.PrivValServerType,
ConsenusVersionUpdates: cfg.ConsenusVersionUpdates,
}
}

Expand Down
9 changes: 9 additions & 0 deletions test/e2e/pkg/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ type Manifest struct {
// not specified are not changed.
ValidatorUpdates map[string]map[string]int64 `toml:"validator_update"`

// ConsensusVersionUpdates is a map of heights to consensus versions, and
// will be sent by the ABCI application as a consensus params update.
// For example, the following sets the consensus version to 1 at height 1000:
//
// [consensus_version_updates]
// 1000 = 1
//

ConsensusVersionUpdates map[string]int32 `toml:"consensus_version_updates"`
// ChainLockUpdates is a map of heights at which a new chain lock should be proposed
// The first number is the tendermint height, and the second is the
//
Expand Down
11 changes: 11 additions & 0 deletions test/e2e/pkg/testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ type Testnet struct {
QuorumType btcjson.LLMQType
QuorumHash crypto.QuorumHash
QuorumHashUpdates map[int64]crypto.QuorumHash
// ConsensusVersionUpdates is maps height where consensus params updates shall be generated
// to ConsensusParams.Version.ProtocolVersion.
ConsensusVersionUpdates map[int64]int32
}

// Node represents a Tenderdash node in a testnet.
Expand Down Expand Up @@ -216,6 +219,7 @@ func LoadTestnet(file string) (*Testnet, error) {
QuorumType: btcjson.LLMQType(quorumType),
QuorumHash: quorumHash,
QuorumHashUpdates: map[int64]crypto.QuorumHash{},
ConsensusVersionUpdates: map[int64]int32{},
}
if len(manifest.KeyType) != 0 {
testnet.KeyType = manifest.KeyType
Expand Down Expand Up @@ -425,6 +429,13 @@ func LoadTestnet(file string) (*Testnet, error) {
testnet.ValidatorUpdates[int64(height)] = valUpdate
testnet.ThresholdPublicKeyUpdates[int64(height)] = ld.ThresholdPubKey
testnet.QuorumHashUpdates[int64(height)] = quorumHash
if cpUpdate, ok := manifest.ConsensusVersionUpdates[strconv.Itoa(height)]; ok {
h, err := strconv.Atoi(heightStr)
if err != nil {
return nil, fmt.Errorf("invalid consensus version update height %q: %w", height, err)
}
testnet.ConsensusVersionUpdates[int64(h)] = cpUpdate
}
}

chainLockSetHeights := make([]int, 0, len(manifest.ChainLockUpdates))
Expand Down
8 changes: 8 additions & 0 deletions test/e2e/runner/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,14 @@ func MakeAppConfig(node *e2e.Node) ([]byte, error) {
cfg["chainlock_updates"] = chainLockUpdates
}

if len(node.Testnet.ConsensusVersionUpdates) > 0 {
consensusVersionUpdates := map[string]int32{}
for height, version := range node.Testnet.ConsensusVersionUpdates {
consensusVersionUpdates[strconv.Itoa(int(height))] = version //#nosec:G115
}
cfg["consensus_version_updates"] = consensusVersionUpdates
}

var buf bytes.Buffer
err := toml.NewEncoder(&buf).Encode(cfg)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions test/e2e/tests/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ func fetchBlockChain(ctx context.Context, t *testing.T) []*types.Block {

from := status.SyncInfo.EarliestBlockHeight
to := status.SyncInfo.LatestBlockHeight
// limit blocks to 100 to avoid long test times
if to-from > 100 {
to = from + 100
}
blocks, ok := blocksCache[testnet.Name]
if !ok {
blocks = make([]*types.Block, 0, to-from+1)
Expand Down
85 changes: 75 additions & 10 deletions test/e2e/tests/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package e2e_test
import (
"bytes"
"context"
"fmt"
"testing"

"github.com/dashpay/dashd-go/btcjson"
Expand All @@ -11,6 +12,7 @@ import (

"github.com/dashpay/tenderdash/crypto"
cryptoenc "github.com/dashpay/tenderdash/crypto/encoding"
"github.com/dashpay/tenderdash/internal/consensus/versioned/selectproposer"
tmbytes "github.com/dashpay/tenderdash/libs/bytes"
e2e "github.com/dashpay/tenderdash/test/e2e/pkg"
"github.com/dashpay/tenderdash/types"
Expand All @@ -35,6 +37,10 @@ func TestValidator_Sets(t *testing.T) {
}

last := status.SyncInfo.LatestBlockHeight
// limit the test to 100 blocks, to avoid long test times
if last > first+100 {
last = first + 100
}

// skip first block if node is pruning blocks, to avoid race conditions
if node.RetainBlocks > 0 {
Expand Down Expand Up @@ -62,7 +68,7 @@ func TestValidator_Sets(t *testing.T) {
}
// fmt.Printf("node %s(%X) validator set for height %d is %v\n",
// node.Name, node.ProTxHash, h, valSchedule.Set)
for i, valScheduleValidator := range valSchedule.Set.Validators {
for i, valScheduleValidator := range valSchedule.ValidatorProposer.ValidatorSet().Validators {
validator := validators[i]
require.Equal(t, valScheduleValidator.ProTxHash, validator.ProTxHash,
"mismatching validator proTxHashes at height %v (%X <=> %X", h,
Expand All @@ -74,9 +80,9 @@ func TestValidator_Sets(t *testing.T) {
// Validators in the schedule don't contain addresses
validator.NodeAddress = types.ValidatorAddress{}
}
require.Equal(t, valSchedule.Set.Validators, validators,
require.Equal(t, valSchedule.ValidatorProposer.ValidatorSet().Validators, validators,
"incorrect validator set at height %v", h)
require.Equal(t, valSchedule.Set.ThresholdPublicKey, thresholdPublicKey,
require.Equal(t, valSchedule.ValidatorProposer.ValidatorSet().ThresholdPublicKey, thresholdPublicKey,
"incorrect thresholdPublicKey at height %v", h)
require.NoError(t, valSchedule.Increment(1))
}
Expand Down Expand Up @@ -122,8 +128,13 @@ func TestValidator_Propose(t *testing.T) {

expectCount := 0
proposeCount := 0
for _, block := range blocks {
if bytes.Equal(valSchedule.Set.Proposer().ProTxHash, proTxHash) {
for i, block := range blocks {
round := int32(0)
if i+1 < len(blocks) { // we might be missing the last commit, we'll assume round 0
round = blocks[i+1].LastCommit.Round
}
expectedProposer := valSchedule.ValidatorProposer.MustGetProposer(block.Height, round).ProTxHash
if bytes.Equal(expectedProposer, proTxHash) {
expectCount++
if bytes.Equal(block.ProposerProTxHash, proTxHash) {
proposeCount++
Expand All @@ -146,11 +157,14 @@ func TestValidator_Propose(t *testing.T) {
// validatorSchedule is a validator set iterator, which takes into account
// validator set updates.
type validatorSchedule struct {
Set *types.ValidatorSet
ValidatorProposer selectproposer.ProposerSelector
height int64
updates map[int64]e2e.ValidatorsMap
thresholdPublicKeyUpdates map[int64]crypto.PubKey
quorumHashUpdates map[int64]crypto.QuorumHash
consensusVersionUpdates map[int64]int32

consensusVersions map[int64]int32
}

func newValidatorSchedule(testnet e2e.Testnet) *validatorSchedule {
Expand All @@ -173,36 +187,87 @@ func newValidatorSchedule(testnet e2e.Testnet) *validatorSchedule {
}
}

vs := types.NewValidatorSet(makeVals(valMap), thresholdPublicKey, quorumType, quorumHash, true)
ps, err := selectproposer.NewProposerSelector(*types.DefaultConsensusParams(), vs, testnet.InitialHeight, 0, nil, nil)
if err != nil {
panic(err)
}

return &validatorSchedule{
height: testnet.InitialHeight,
Set: types.NewValidatorSet(makeVals(valMap), thresholdPublicKey, quorumType, quorumHash, true),
ValidatorProposer: ps,
updates: testnet.ValidatorUpdates,
thresholdPublicKeyUpdates: testnet.ThresholdPublicKeyUpdates,
quorumHashUpdates: testnet.QuorumHashUpdates,
consensusVersions: make(map[int64]int32),
consensusVersionUpdates: testnet.ConsensusVersionUpdates,
}
}
func (s *validatorSchedule) consensusVersionUpdate() {
ver, ok := s.consensusVersionUpdates[s.height]
if !ok && s.height-1 > 0 {
ver = s.consensusVersions[s.height-1]
}

s.consensusVersions[s.height] = ver
}

func (s *validatorSchedule) ConsensusParams() types.ConsensusParams {
ver, ok := s.consensusVersions[s.height]
if !ok {
panic(fmt.Errorf("consensus version not set for height %d", s.height))
}

cp := *types.DefaultConsensusParams()
cp.Version.ConsensusVersion = ver

return cp
}

func (s *validatorSchedule) Increment(heights int64) error {
for i := int64(0); i < heights; i++ {
s.height++

// consensus params update - for now, we only support consensus version updates
s.consensusVersionUpdate()
cp := s.ConsensusParams()

// validator set update
if s.height > 1 {
// validator set updates are offset by 1, since they only take effect
// 1 block after they're returned.
if update, ok := s.updates[s.height-1]; ok {
if thresholdPublicKeyUpdate, ok := s.thresholdPublicKeyUpdates[s.height-1]; ok {
if quorumHashUpdate, ok := s.quorumHashUpdates[s.height-1]; ok {
if bytes.Equal(quorumHashUpdate, s.Set.QuorumHash) {
if err := s.Set.UpdateWithChangeSet(makeVals(update), thresholdPublicKeyUpdate, quorumHashUpdate); err != nil {
currentQuorumHash := s.ValidatorProposer.ValidatorSet().QuorumHash
if bytes.Equal(quorumHashUpdate, currentQuorumHash) {
vs := s.ValidatorProposer.ValidatorSet()

if err := vs.UpdateWithChangeSet(makeVals(update), thresholdPublicKeyUpdate, quorumHashUpdate); err != nil {
return err
}
} else {
s.Set = types.NewValidatorSet(makeVals(update), thresholdPublicKeyUpdate, btcjson.LLMQType_5_60,
vs := types.NewValidatorSet(makeVals(update), thresholdPublicKeyUpdate, btcjson.LLMQType_5_60,
quorumHashUpdate, true)

ps, err := selectproposer.NewProposerSelector(cp, vs, s.height, 0, nil, nil)
if err != nil {
return err
}
if cp.Version.ConsensusVersion == 0 {
//consensus version 0 had an issue where first proposer didn't propose
// TODO: double check if this is really needed
ps.ValidatorSet().IncProposerIndex(1)
}
s.ValidatorProposer = ps
}
}
}
}
}
if err := s.ValidatorProposer.UpdateHeightRound(s.height, 0); err != nil {
return err
}
}
return nil
}
Expand Down

0 comments on commit 8101f7f

Please sign in to comment.