github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/consensus/istanbul/core/justification_test.go (about)

     1  package core
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"math/rand"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/electroneum/electroneum-sc/common"
    11  	"github.com/electroneum/electroneum-sc/consensus/istanbul"
    12  	qbfttypes "github.com/electroneum/electroneum-sc/consensus/istanbul/types"
    13  	"github.com/electroneum/electroneum-sc/consensus/istanbul/validator"
    14  	"github.com/electroneum/electroneum-sc/core/types"
    15  	"github.com/electroneum/electroneum-sc/crypto"
    16  )
    17  
    18  // Tests combinations of justifications that evaluate to true.
    19  func TestJustifyTrue(t *testing.T) {
    20  	for quorumSize := 3; quorumSize <= 10; quorumSize++ {
    21  		// All ROUND-CHANGE messages have pr/pb nil
    22  		testParameterizedCase(t, quorumSize, quorumSize, 0, 0, 0, 0, 0, true)
    23  
    24  		// Some ROUND-CHANGE message has pr/pb not nil
    25  		for equal := 1; equal <= quorumSize; equal++ {
    26  			for less := 0; less <= quorumSize-equal; less++ {
    27  				nil := quorumSize - equal - less
    28  				testParameterizedCase(t, quorumSize, nil, equal, less, 0, quorumSize, 0, true)
    29  			}
    30  		}
    31  	}
    32  }
    33  
    34  // Tests combinations of justifications that evaluate to false.
    35  func TestJustifyFalse(t *testing.T) {
    36  	for quorumSize := 3; quorumSize <= 10; quorumSize++ {
    37  		// Total ROUND-CHANGE messages less than quorumSize
    38  		// all have pr/pb nil
    39  		for totalRoundChange := 0; totalRoundChange < quorumSize; totalRoundChange++ {
    40  			testParameterizedCase(t, quorumSize, totalRoundChange, 0, 0, 0, 0, 0, false)
    41  		}
    42  		// some has pr/pb not nil
    43  		for totalRoundChange := 0; totalRoundChange < quorumSize; totalRoundChange++ {
    44  			for equal := 1; equal <= totalRoundChange; equal++ {
    45  				for less := 0; less <= totalRoundChange-equal; less++ {
    46  					nil := totalRoundChange - equal - less
    47  					testParameterizedCase(t, quorumSize, nil, equal, less, 0, quorumSize, 0, false)
    48  				}
    49  			}
    50  		}
    51  
    52  		// Total ROUND-CHANGE messages equal to quorumSize
    53  		for equal := 1; equal <= quorumSize; equal++ {
    54  			for less := 0; less <= quorumSize-equal; less++ {
    55  				nil := quorumSize - equal - less
    56  
    57  				// Total PREPARE messages less than quorumSize
    58  				for total := 0; total < quorumSize; total++ {
    59  					testParameterizedCase(t, quorumSize, nil, equal, less, 0, total, quorumSize-total, false)
    60  				}
    61  
    62  				// Total PREPARE messages equal to quorumSize and some PREPARE message has round different than others
    63  				for different := 1; different <= quorumSize; different++ {
    64  					testParameterizedCase(t, quorumSize, nil, equal, less, 0, quorumSize-different, different, false)
    65  				}
    66  			}
    67  		}
    68  	}
    69  }
    70  
    71  func testParameterizedCase(
    72  	t *testing.T,
    73  	quorumSize int,
    74  	rcForNil int,
    75  	rcEqualToTargetRound int,
    76  	rcLowerThanTargetRound int,
    77  	rcHigherThanTargetRound int,
    78  	preparesForTargetRound int,
    79  	preparesNotForTargetRound int,
    80  	messageJustified bool) {
    81  	pp := istanbul.NewRoundRobinProposerPolicy()
    82  	pp.Use(istanbul.ValidatorSortByByte())
    83  	validatorSet := validator.NewSet(generateValidators(quorumSize), pp)
    84  	block := makeBlock(1)
    85  	var round int64 = 10
    86  	var targetPreparedRound int64 = 5
    87  
    88  	rng := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
    89  
    90  	if rcForNil+rcEqualToTargetRound+rcLowerThanTargetRound+rcHigherThanTargetRound > quorumSize {
    91  		t.Errorf("rcForNil (%v) + rcEqualToTargetRound (%v) + rcLowerThanTargetRound (%v) + rcHigherThanTargetRound (%v) > quorumSize (%v)",
    92  			rcForNil, rcEqualToTargetRound, rcLowerThanTargetRound, rcHigherThanTargetRound, quorumSize)
    93  	}
    94  
    95  	if preparesForTargetRound+preparesNotForTargetRound > quorumSize {
    96  		t.Errorf("preparesForTargetRound (%v) + preparesNotForTargetRound (%v) > quorumSize (%v)", preparesForTargetRound, preparesNotForTargetRound, quorumSize)
    97  	}
    98  
    99  	// ROUND-CHANGE messages
   100  	roundChangeMessages := make([]*qbfttypes.SignedRoundChangePayload, 0)
   101  	for index, validator := range validatorSet.List() {
   102  		var m *qbfttypes.SignedRoundChangePayload
   103  		if index < rcForNil {
   104  			m = createRoundChangeMessage(validator.Address(), round, 0, nil)
   105  		} else if index >= rcForNil && index < rcForNil+rcEqualToTargetRound {
   106  			m = createRoundChangeMessage(validator.Address(), round, targetPreparedRound, block)
   107  		} else if index >= rcForNil+rcEqualToTargetRound && index < rcForNil+rcEqualToTargetRound+rcLowerThanTargetRound {
   108  			m = createRoundChangeMessage(validator.Address(), round, int64(rng.Intn(int(targetPreparedRound)-1)+1), block)
   109  		} else if index >= rcForNil+rcEqualToTargetRound+rcLowerThanTargetRound && index < rcForNil+rcEqualToTargetRound+rcLowerThanTargetRound+rcHigherThanTargetRound {
   110  			m = createRoundChangeMessage(validator.Address(), round, int64(rng.Intn(int(targetPreparedRound))+int(targetPreparedRound)+1), block)
   111  		} else {
   112  			break
   113  		}
   114  		roundChangeMessages = append(roundChangeMessages, m)
   115  	}
   116  
   117  	// PREPARE messages
   118  	prepareMessages := make([]*qbfttypes.Prepare, 0)
   119  	for index, validator := range validatorSet.List() {
   120  		var m *qbfttypes.Prepare
   121  		if index < preparesForTargetRound {
   122  			m = createPrepareMessage(validator.Address(), targetPreparedRound, block)
   123  		} else if index >= preparesForTargetRound && index < preparesForTargetRound+preparesNotForTargetRound {
   124  			notTargetPreparedRound := targetPreparedRound
   125  			for notTargetPreparedRound == targetPreparedRound {
   126  				notTargetPreparedRound = rng.Int63()
   127  			}
   128  			m = createPrepareMessage(validator.Address(), notTargetPreparedRound, block)
   129  		} else {
   130  			break
   131  		}
   132  		prepareMessages = append(prepareMessages, m)
   133  	}
   134  
   135  	for _, m := range roundChangeMessages {
   136  		fmt.Printf("RC %v\n", m)
   137  	}
   138  	for _, m := range prepareMessages {
   139  		fmt.Printf("PR %v\n", m)
   140  	}
   141  	fmt.Println("roundChangeMessages", roundChangeMessages, len(roundChangeMessages))
   142  	if err := isJustified(block, roundChangeMessages, prepareMessages, quorumSize); err == nil && !messageJustified {
   143  		t.Errorf("quorumSize = %v, rcForNil = %v, rcEqualToTargetRound = %v, rcLowerThanTargetRound = %v, rcHigherThanTargetRound = %v, preparesForTargetRound = %v, preparesNotForTargetRound = %v (Expected: %v, Actual: %v)",
   144  			quorumSize, rcForNil, rcEqualToTargetRound, rcLowerThanTargetRound, rcHigherThanTargetRound, preparesForTargetRound, preparesNotForTargetRound, err == nil, !messageJustified)
   145  	}
   146  }
   147  
   148  func createRoundChangeMessage(from common.Address, round int64, preparedRound int64, preparedBlock istanbul.Proposal) *qbfttypes.SignedRoundChangePayload {
   149  	m := qbfttypes.NewRoundChange(big.NewInt(1), big.NewInt(1), big.NewInt(preparedRound), preparedBlock, false)
   150  	m.SetSource(from)
   151  	return &m.SignedRoundChangePayload
   152  }
   153  
   154  func createPrepareMessage(from common.Address, round int64, preparedBlock istanbul.Proposal) *qbfttypes.Prepare {
   155  	return qbfttypes.NewPrepareWithSigAndSource(big.NewInt(1), big.NewInt(round), preparedBlock.Hash(), nil, from)
   156  }
   157  
   158  func generateValidators(n int) []common.Address {
   159  	vals := make([]common.Address, 0)
   160  	for i := 0; i < n; i++ {
   161  		privateKey, _ := crypto.GenerateKey()
   162  		vals = append(vals, crypto.PubkeyToAddress(privateKey.PublicKey))
   163  	}
   164  	return vals
   165  }
   166  
   167  func makeBlock(number int64) *types.Block {
   168  	header := &types.Header{
   169  		Difficulty: big.NewInt(0),
   170  		Number:     big.NewInt(number),
   171  		GasLimit:   0,
   172  		GasUsed:    0,
   173  		Time:       0,
   174  	}
   175  	block := &types.Block{}
   176  	return block.WithSeal(header)
   177  }