github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/test/e2e/runner/evidence.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"math/rand"
    10  	"os"
    11  	"path/filepath"
    12  	"time"
    13  
    14  	"github.com/ari-anchor/sei-tendermint/crypto"
    15  	"github.com/ari-anchor/sei-tendermint/internal/test/factory"
    16  	"github.com/ari-anchor/sei-tendermint/libs/log"
    17  	"github.com/ari-anchor/sei-tendermint/privval"
    18  	tmproto "github.com/ari-anchor/sei-tendermint/proto/tendermint/types"
    19  	e2e "github.com/ari-anchor/sei-tendermint/test/e2e/pkg"
    20  	"github.com/ari-anchor/sei-tendermint/types"
    21  	"github.com/ari-anchor/sei-tendermint/version"
    22  )
    23  
    24  // 1 in 4 evidence is light client evidence, the rest is duplicate vote evidence
    25  const lightClientEvidenceRatio = 4
    26  
    27  // InjectEvidence takes a running testnet and generates an amount of valid
    28  // evidence and broadcasts it to a random node through the rpc endpoint `/broadcast_evidence`.
    29  // Evidence is random and can be a mixture of LightClientAttackEvidence and
    30  // DuplicateVoteEvidence.
    31  func InjectEvidence(ctx context.Context, logger log.Logger, r *rand.Rand, testnet *e2e.Testnet, amount int) error {
    32  	// select a random node
    33  	var targetNode *e2e.Node
    34  
    35  	for _, idx := range r.Perm(len(testnet.Nodes)) {
    36  		if !testnet.Nodes[idx].Stateless() {
    37  			targetNode = testnet.Nodes[idx]
    38  			break
    39  		}
    40  	}
    41  
    42  	if targetNode == nil {
    43  		return errors.New("could not find node to inject evidence into")
    44  	}
    45  
    46  	logger.Info(fmt.Sprintf("Injecting evidence through %v (amount: %d)...", targetNode.Name, amount))
    47  
    48  	client, err := targetNode.Client()
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	// request the latest block and validator set from the node
    54  	blockRes, err := client.Block(ctx, nil)
    55  	if err != nil {
    56  		return err
    57  	}
    58  	evidenceHeight := blockRes.Block.Height - 3
    59  
    60  	nValidators := 100
    61  	valRes, err := client.Validators(ctx, &evidenceHeight, nil, &nValidators)
    62  	if err != nil {
    63  		return err
    64  	}
    65  	valSet, err := types.ValidatorSetFromExistingValidators(valRes.Validators)
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	// get the private keys of all the validators in the network
    71  	privVals, err := getPrivateValidatorKeys(testnet)
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	// request the latest block and validator set from the node
    77  	blockRes, err = client.Block(ctx, &evidenceHeight)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	var ev types.Evidence
    83  	for i := 1; i <= amount; i++ {
    84  		if i%lightClientEvidenceRatio == 0 {
    85  			ev, err = generateLightClientAttackEvidence(ctx,
    86  				privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time,
    87  			)
    88  		} else {
    89  			var dve *types.DuplicateVoteEvidence
    90  			dve, err = generateDuplicateVoteEvidence(ctx,
    91  				privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time,
    92  			)
    93  			if dve.VoteA.Height < testnet.VoteExtensionsEnableHeight {
    94  				dve.VoteA.StripExtension()
    95  				dve.VoteB.StripExtension()
    96  			}
    97  			ev = dve
    98  		}
    99  		if err != nil {
   100  			return err
   101  		}
   102  
   103  		_, err := client.BroadcastEvidence(ctx, ev)
   104  		if err != nil {
   105  			return err
   106  		}
   107  	}
   108  
   109  	logger.Info("Finished sending evidence",
   110  		"node", testnet.Name,
   111  		"amount", amount,
   112  		"height", evidenceHeight,
   113  	)
   114  
   115  	wctx, cancel := context.WithTimeout(ctx, time.Minute)
   116  	defer cancel()
   117  
   118  	// wait for the node to make progress after submitting
   119  	// evidence (3 (forged height) + 1 (progress))
   120  	_, err = waitForNode(wctx, logger, targetNode, evidenceHeight+4)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func getPrivateValidatorKeys(testnet *e2e.Testnet) ([]types.MockPV, error) {
   129  	privVals := []types.MockPV{}
   130  
   131  	for _, node := range testnet.Nodes {
   132  		if node.Mode == e2e.ModeValidator {
   133  			privKeyPath := filepath.Join(testnet.Dir, node.Name, PrivvalKeyFile)
   134  			privKey, err := readPrivKey(privKeyPath)
   135  			if err != nil {
   136  				return nil, err
   137  			}
   138  			// Create mock private validators from the validators private key. MockPV is
   139  			// stateless which means we can double vote and do other funky stuff
   140  			privVals = append(privVals, types.NewMockPVWithParams(privKey, false, false))
   141  		}
   142  	}
   143  
   144  	return privVals, nil
   145  }
   146  
   147  // creates evidence of a lunatic attack. The height provided is the common height.
   148  // The forged height happens 2 blocks later.
   149  func generateLightClientAttackEvidence(
   150  	ctx context.Context,
   151  	privVals []types.MockPV,
   152  	height int64,
   153  	vals *types.ValidatorSet,
   154  	chainID string,
   155  	evTime time.Time,
   156  ) (*types.LightClientAttackEvidence, error) {
   157  	// forge a random header
   158  	forgedHeight := height + 2
   159  	forgedTime := evTime.Add(1 * time.Second)
   160  	header := makeHeaderRandom(chainID, forgedHeight)
   161  	header.Time = forgedTime
   162  
   163  	// add a new bogus validator and remove an existing one to
   164  	// vary the validator set slightly
   165  	pv, conflictingVals, err := mutateValidatorSet(ctx, privVals, vals)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	header.ValidatorsHash = conflictingVals.Hash()
   171  
   172  	// create a commit for the forged header
   173  	blockID := makeBlockID(header.Hash(), 1000, []byte("partshash"))
   174  	voteSet := types.NewExtendedVoteSet(chainID, forgedHeight, 0, tmproto.SignedMsgType(2), conflictingVals)
   175  
   176  	ec, err := factory.MakeExtendedCommit(ctx, blockID, forgedHeight, 0, voteSet, pv, forgedTime)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	ev := &types.LightClientAttackEvidence{
   182  		ConflictingBlock: &types.LightBlock{
   183  			SignedHeader: &types.SignedHeader{
   184  				Header: header,
   185  				Commit: ec.ToCommit(),
   186  			},
   187  			ValidatorSet: conflictingVals,
   188  		},
   189  		CommonHeight:     height,
   190  		TotalVotingPower: vals.TotalVotingPower(),
   191  		Timestamp:        evTime,
   192  	}
   193  	ev.ByzantineValidators = ev.GetByzantineValidators(vals, &types.SignedHeader{
   194  		Header: makeHeaderRandom(chainID, forgedHeight),
   195  	})
   196  	return ev, nil
   197  }
   198  
   199  // generateDuplicateVoteEvidence picks a random validator from the val set and
   200  // returns duplicate vote evidence against the validator
   201  func generateDuplicateVoteEvidence(
   202  	ctx context.Context,
   203  	privVals []types.MockPV,
   204  	height int64,
   205  	vals *types.ValidatorSet,
   206  	chainID string,
   207  	time time.Time,
   208  ) (*types.DuplicateVoteEvidence, error) {
   209  	privVal, valIdx, err := getRandomValidatorIndex(privVals, vals)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	voteA, err := factory.MakeVote(ctx, privVal, chainID, valIdx, height, 0, 2, makeRandomBlockID(), time)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	voteB, err := factory.MakeVote(ctx, privVal, chainID, valIdx, height, 0, 2, makeRandomBlockID(), time)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  	ev, err := types.NewDuplicateVoteEvidence(voteA, voteB, time, vals)
   223  	if err != nil {
   224  		return nil, fmt.Errorf("could not generate evidence: %w", err)
   225  	}
   226  
   227  	return ev, nil
   228  }
   229  
   230  // getRandomValidatorIndex picks a random validator from a slice of mock PrivVals that's
   231  // also part of the validator set, returning the PrivVal and its index in the validator set
   232  func getRandomValidatorIndex(privVals []types.MockPV, vals *types.ValidatorSet) (types.MockPV, int32, error) {
   233  	for _, idx := range rand.Perm(len(privVals)) {
   234  		pv := privVals[idx]
   235  		valIdx, _ := vals.GetByAddress(pv.PrivKey.PubKey().Address())
   236  		if valIdx >= 0 {
   237  			return pv, valIdx, nil
   238  		}
   239  	}
   240  	return types.MockPV{}, -1, errors.New("no private validator found in validator set")
   241  }
   242  
   243  func readPrivKey(keyFilePath string) (crypto.PrivKey, error) {
   244  	keyJSONBytes, err := os.ReadFile(keyFilePath)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	pvKey := privval.FilePVKey{}
   249  	err = json.Unmarshal(keyJSONBytes, &pvKey)
   250  	if err != nil {
   251  		return nil, fmt.Errorf("error reading PrivValidator key from %v: %w", keyFilePath, err)
   252  	}
   253  
   254  	return pvKey.PrivKey, nil
   255  }
   256  
   257  func makeHeaderRandom(chainID string, height int64) *types.Header {
   258  	return &types.Header{
   259  		Version:            version.Consensus{Block: version.BlockProtocol, App: 1},
   260  		ChainID:            chainID,
   261  		Height:             height,
   262  		Time:               time.Now(),
   263  		LastBlockID:        makeBlockID([]byte("headerhash"), 1000, []byte("partshash")),
   264  		LastCommitHash:     crypto.CRandBytes(crypto.HashSize),
   265  		DataHash:           crypto.CRandBytes(crypto.HashSize),
   266  		ValidatorsHash:     crypto.CRandBytes(crypto.HashSize),
   267  		NextValidatorsHash: crypto.CRandBytes(crypto.HashSize),
   268  		ConsensusHash:      crypto.CRandBytes(crypto.HashSize),
   269  		AppHash:            crypto.CRandBytes(crypto.HashSize),
   270  		LastResultsHash:    crypto.CRandBytes(crypto.HashSize),
   271  		EvidenceHash:       crypto.CRandBytes(crypto.HashSize),
   272  		ProposerAddress:    crypto.CRandBytes(crypto.AddressSize),
   273  	}
   274  }
   275  
   276  func makeRandomBlockID() types.BlockID {
   277  	return makeBlockID(crypto.CRandBytes(crypto.HashSize), 100, crypto.CRandBytes(crypto.HashSize))
   278  }
   279  
   280  func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID {
   281  	var (
   282  		h   = make([]byte, crypto.HashSize)
   283  		psH = make([]byte, crypto.HashSize)
   284  	)
   285  	copy(h, hash)
   286  	copy(psH, partSetHash)
   287  	return types.BlockID{
   288  		Hash: h,
   289  		PartSetHeader: types.PartSetHeader{
   290  			Total: partSetSize,
   291  			Hash:  psH,
   292  		},
   293  	}
   294  }
   295  
   296  func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *types.ValidatorSet) ([]types.PrivValidator, *types.ValidatorSet, error) {
   297  	newVal, newPrivVal, err := factory.Validator(ctx, 10)
   298  	if err != nil {
   299  		return nil, nil, err
   300  	}
   301  
   302  	var newVals *types.ValidatorSet
   303  	if vals.Size() > 2 {
   304  		newVals = types.NewValidatorSet(append(vals.Copy().Validators[:vals.Size()-1], newVal))
   305  	} else {
   306  		newVals = types.NewValidatorSet(append(vals.Copy().Validators, newVal))
   307  	}
   308  
   309  	// we need to sort the priv validators with the same index as the validator set
   310  	pv := make([]types.PrivValidator, newVals.Size())
   311  	for idx, val := range newVals.Validators {
   312  		found := false
   313  		for _, p := range append(privVals, newPrivVal.(types.MockPV)) {
   314  			if bytes.Equal(p.PrivKey.PubKey().Address(), val.Address) {
   315  				pv[idx] = p
   316  				found = true
   317  				break
   318  			}
   319  		}
   320  		if !found {
   321  			return nil, nil, fmt.Errorf("missing priv validator for %v", val.Address)
   322  		}
   323  	}
   324  
   325  	return pv, newVals, nil
   326  }