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

     1  package app
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/base64"
     7  	"encoding/binary"
     8  	"errors"
     9  	"fmt"
    10  	"math/rand"
    11  	"path/filepath"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/ari-anchor/sei-tendermint/abci/example/code"
    19  	abci "github.com/ari-anchor/sei-tendermint/abci/types"
    20  	"github.com/ari-anchor/sei-tendermint/crypto"
    21  	"github.com/ari-anchor/sei-tendermint/libs/log"
    22  	"github.com/ari-anchor/sei-tendermint/proto/tendermint/types"
    23  	"github.com/ari-anchor/sei-tendermint/version"
    24  )
    25  
    26  const (
    27  	voteExtensionKey    string = "extensionSum"
    28  	voteExtensionMaxVal int64  = 128
    29  )
    30  
    31  // Application is an ABCI application for use by end-to-end tests. It is a
    32  // simple key/value store for strings, storing data in memory and persisting
    33  // to disk as JSON, taking state sync snapshots if requested.
    34  type Application struct {
    35  	abci.BaseApplication
    36  	mu              sync.Mutex
    37  	logger          log.Logger
    38  	state           *State
    39  	snapshots       *SnapshotStore
    40  	cfg             *Config
    41  	restoreSnapshot *abci.Snapshot
    42  	restoreChunks   [][]byte
    43  }
    44  
    45  // Config allows for the setting of high level parameters for running the e2e Application
    46  // KeyType and ValidatorUpdates must be the same for all nodes running the same application.
    47  type Config struct {
    48  	// The directory with which state.json will be persisted in. Usually $HOME/.tendermint/data
    49  	Dir string `toml:"dir"`
    50  
    51  	// SnapshotInterval specifies the height interval at which the application
    52  	// will take state sync snapshots. Defaults to 0 (disabled).
    53  	SnapshotInterval uint64 `toml:"snapshot_interval"`
    54  
    55  	// RetainBlocks specifies the number of recent blocks to retain. Defaults to
    56  	// 0, which retains all blocks. Must be greater that PersistInterval,
    57  	// SnapshotInterval and EvidenceAgeHeight.
    58  	RetainBlocks uint64 `toml:"retain_blocks"`
    59  
    60  	// KeyType sets the curve that will be used by validators.
    61  	// Options are ed25519 & secp256k1
    62  	KeyType string `toml:"key_type"`
    63  
    64  	// PersistInterval specifies the height interval at which the application
    65  	// will persist state to disk. Defaults to 1 (every height), setting this to
    66  	// 0 disables state persistence.
    67  	PersistInterval uint64 `toml:"persist_interval"`
    68  
    69  	// ValidatorUpdates is a map of heights to validator names and their power,
    70  	// and will be returned by the ABCI application. For example, the following
    71  	// changes the power of validator01 and validator02 at height 1000:
    72  	//
    73  	// [validator_update.1000]
    74  	// validator01 = 20
    75  	// validator02 = 10
    76  	//
    77  	// Specifying height 0 returns the validator update during InitChain. The
    78  	// application returns the validator updates as-is, i.e. removing a
    79  	// validator must be done by returning it with power 0, and any validators
    80  	// not specified are not changed.
    81  	//
    82  	// height <-> pubkey <-> voting power
    83  	ValidatorUpdates map[string]map[string]uint8 `toml:"validator_update"`
    84  
    85  	// Add artificial delays to each of the main ABCI calls to mimic computation time
    86  	// of the application
    87  	PrepareProposalDelayMS uint64 `toml:"prepare_proposal_delay_ms"`
    88  	ProcessProposalDelayMS uint64 `toml:"process_proposal_delay_ms"`
    89  	CheckTxDelayMS         uint64 `toml:"check_tx_delay_ms"`
    90  	VoteExtensionDelayMS   uint64 `toml:"vote_extension_delay_ms"`
    91  	FinalizeBlockDelayMS   uint64 `toml:"finalize_block_delay_ms"`
    92  }
    93  
    94  func DefaultConfig(dir string) *Config {
    95  	return &Config{
    96  		PersistInterval:  1,
    97  		SnapshotInterval: 100,
    98  		Dir:              dir,
    99  	}
   100  }
   101  
   102  // NewApplication creates the application.
   103  func NewApplication(cfg *Config) (*Application, error) {
   104  	state, err := NewState(cfg.Dir, cfg.PersistInterval)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	snapshots, err := NewSnapshotStore(filepath.Join(cfg.Dir, "snapshots"))
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	logger, err := log.NewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	return &Application{
   118  		logger:    logger,
   119  		state:     state,
   120  		snapshots: snapshots,
   121  		cfg:       cfg,
   122  	}, nil
   123  }
   124  
   125  // Info implements ABCI.
   126  func (app *Application) Info(_ context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) {
   127  	app.mu.Lock()
   128  	defer app.mu.Unlock()
   129  
   130  	return &abci.ResponseInfo{
   131  		Version:          version.ABCIVersion,
   132  		AppVersion:       1,
   133  		LastBlockHeight:  int64(app.state.Height),
   134  		LastBlockAppHash: app.state.Hash,
   135  	}, nil
   136  }
   137  
   138  // Info implements ABCI.
   139  func (app *Application) InitChain(_ context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) {
   140  	app.mu.Lock()
   141  	defer app.mu.Unlock()
   142  
   143  	var err error
   144  	app.state.initialHeight = uint64(req.InitialHeight)
   145  	if len(req.AppStateBytes) > 0 {
   146  		err = app.state.Import(0, req.AppStateBytes)
   147  		if err != nil {
   148  			panic(err)
   149  		}
   150  	}
   151  	resp := &abci.ResponseInitChain{
   152  		AppHash: app.state.Hash,
   153  		ConsensusParams: &types.ConsensusParams{
   154  			Version: &types.VersionParams{
   155  				AppVersion: 1,
   156  			},
   157  		},
   158  	}
   159  	if resp.Validators, err = app.validatorUpdates(0); err != nil {
   160  		panic(err)
   161  	}
   162  	return resp, nil
   163  }
   164  
   165  // CheckTx implements ABCI.
   166  func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) {
   167  	app.mu.Lock()
   168  	defer app.mu.Unlock()
   169  
   170  	_, _, err := parseTx(req.Tx)
   171  	if err != nil {
   172  		return &abci.ResponseCheckTx{
   173  			Code: code.CodeTypeEncodingError,
   174  		}, nil
   175  	}
   176  
   177  	if app.cfg.CheckTxDelayMS != 0 {
   178  		time.Sleep(time.Duration(app.cfg.CheckTxDelayMS) * time.Millisecond)
   179  	}
   180  
   181  	return &abci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}, nil
   182  }
   183  
   184  // FinalizeBlock implements ABCI.
   185  func (app *Application) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) {
   186  	var txs = make([]*abci.ExecTxResult, len(req.Txs))
   187  
   188  	app.mu.Lock()
   189  	defer app.mu.Unlock()
   190  
   191  	for i, tx := range req.Txs {
   192  		key, value, err := parseTx(tx)
   193  		if err != nil {
   194  			panic(err) // shouldn't happen since we verified it in CheckTx
   195  		}
   196  		app.state.Set(key, value)
   197  
   198  		txs[i] = &abci.ExecTxResult{Code: code.CodeTypeOK}
   199  	}
   200  
   201  	valUpdates, err := app.validatorUpdates(uint64(req.Height))
   202  	if err != nil {
   203  		panic(err)
   204  	}
   205  
   206  	if app.cfg.FinalizeBlockDelayMS != 0 {
   207  		time.Sleep(time.Duration(app.cfg.FinalizeBlockDelayMS) * time.Millisecond)
   208  	}
   209  
   210  	return &abci.ResponseFinalizeBlock{
   211  		TxResults:        txs,
   212  		ValidatorUpdates: valUpdates,
   213  		AppHash:          app.state.Finalize(),
   214  		Events: []abci.Event{
   215  			{
   216  				Type: "val_updates",
   217  				Attributes: []abci.EventAttribute{
   218  					{
   219  						Key:   []byte("size"),
   220  						Value: []byte(strconv.Itoa(valUpdates.Len())),
   221  					},
   222  					{
   223  						Key:   []byte("height"),
   224  						Value: []byte(strconv.Itoa(int(req.Height))),
   225  					},
   226  				},
   227  			},
   228  		},
   229  	}, nil
   230  }
   231  
   232  // Commit implements ABCI.
   233  func (app *Application) Commit(_ context.Context) (*abci.ResponseCommit, error) {
   234  	app.mu.Lock()
   235  	defer app.mu.Unlock()
   236  
   237  	height, err := app.state.Commit()
   238  	if err != nil {
   239  		panic(err)
   240  	}
   241  	if app.cfg.SnapshotInterval > 0 && height%app.cfg.SnapshotInterval == 0 {
   242  		snapshot, err := app.snapshots.Create(app.state)
   243  		if err != nil {
   244  			panic(err)
   245  		}
   246  		app.logger.Info("created state sync snapshot", "height", snapshot.Height)
   247  		err = app.snapshots.Prune(maxSnapshotCount)
   248  		if err != nil {
   249  			app.logger.Error("failed to prune snapshots", "err", err)
   250  		}
   251  	}
   252  	retainHeight := int64(0)
   253  	if app.cfg.RetainBlocks > 0 {
   254  		retainHeight = int64(height - app.cfg.RetainBlocks + 1)
   255  	}
   256  	return &abci.ResponseCommit{
   257  		RetainHeight: retainHeight,
   258  	}, nil
   259  }
   260  
   261  // Query implements ABCI.
   262  func (app *Application) Query(_ context.Context, req *abci.RequestQuery) (*abci.ResponseQuery, error) {
   263  	app.mu.Lock()
   264  	defer app.mu.Unlock()
   265  
   266  	return &abci.ResponseQuery{
   267  		Height: int64(app.state.Height),
   268  		Key:    req.Data,
   269  		Value:  []byte(app.state.Get(string(req.Data))),
   270  	}, nil
   271  }
   272  
   273  // ListSnapshots implements ABCI.
   274  func (app *Application) ListSnapshots(_ context.Context, req *abci.RequestListSnapshots) (*abci.ResponseListSnapshots, error) {
   275  	app.mu.Lock()
   276  	defer app.mu.Unlock()
   277  
   278  	snapshots, err := app.snapshots.List()
   279  	if err != nil {
   280  		panic(err)
   281  	}
   282  	return &abci.ResponseListSnapshots{Snapshots: snapshots}, nil
   283  }
   284  
   285  // LoadSnapshotChunk implements ABCI.
   286  func (app *Application) LoadSnapshotChunk(_ context.Context, req *abci.RequestLoadSnapshotChunk) (*abci.ResponseLoadSnapshotChunk, error) {
   287  	app.mu.Lock()
   288  	defer app.mu.Unlock()
   289  
   290  	chunk, err := app.snapshots.LoadChunk(req.Height, req.Format, req.Chunk)
   291  	if err != nil {
   292  		panic(err)
   293  	}
   294  	return &abci.ResponseLoadSnapshotChunk{Chunk: chunk}, nil
   295  }
   296  
   297  // OfferSnapshot implements ABCI.
   298  func (app *Application) OfferSnapshot(_ context.Context, req *abci.RequestOfferSnapshot) (*abci.ResponseOfferSnapshot, error) {
   299  	app.mu.Lock()
   300  	defer app.mu.Unlock()
   301  
   302  	if app.restoreSnapshot != nil {
   303  		panic("A snapshot is already being restored")
   304  	}
   305  	app.restoreSnapshot = req.Snapshot
   306  	app.restoreChunks = [][]byte{}
   307  	return &abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, nil
   308  }
   309  
   310  // ApplySnapshotChunk implements ABCI.
   311  func (app *Application) ApplySnapshotChunk(_ context.Context, req *abci.RequestApplySnapshotChunk) (*abci.ResponseApplySnapshotChunk, error) {
   312  	app.mu.Lock()
   313  	defer app.mu.Unlock()
   314  
   315  	if app.restoreSnapshot == nil {
   316  		panic("No restore in progress")
   317  	}
   318  	app.restoreChunks = append(app.restoreChunks, req.Chunk)
   319  	if len(app.restoreChunks) == int(app.restoreSnapshot.Chunks) {
   320  		bz := []byte{}
   321  		for _, chunk := range app.restoreChunks {
   322  			bz = append(bz, chunk...)
   323  		}
   324  		err := app.state.Import(app.restoreSnapshot.Height, bz)
   325  		if err != nil {
   326  			panic(err)
   327  		}
   328  		app.restoreSnapshot = nil
   329  		app.restoreChunks = nil
   330  	}
   331  	return &abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil
   332  }
   333  
   334  // PrepareProposal will take the given transactions and attempt to prepare a
   335  // proposal from them when it's our turn to do so. In the process, vote
   336  // extensions from the previous round of consensus, if present, will be used to
   337  // construct a special transaction whose value is the sum of all of the vote
   338  // extensions from the previous round.
   339  //
   340  // NB: Assumes that the supplied transactions do not exceed `req.MaxTxBytes`.
   341  // If adding a special vote extension-generated transaction would cause the
   342  // total number of transaction bytes to exceed `req.MaxTxBytes`, we will not
   343  // append our special vote extension transaction.
   344  func (app *Application) PrepareProposal(_ context.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) {
   345  	var sum int64
   346  	var extCount int
   347  	for _, vote := range req.LocalLastCommit.Votes {
   348  		if !vote.SignedLastBlock || len(vote.VoteExtension) == 0 {
   349  			continue
   350  		}
   351  		extValue, err := parseVoteExtension(vote.VoteExtension)
   352  		// This should have been verified in VerifyVoteExtension
   353  		if err != nil {
   354  			panic(fmt.Errorf("failed to parse vote extension in PrepareProposal: %w", err))
   355  		}
   356  		valAddr := crypto.Address(vote.Validator.Address)
   357  		app.logger.Info("got vote extension value in PrepareProposal", "valAddr", valAddr, "value", extValue)
   358  		sum += extValue
   359  		extCount++
   360  	}
   361  	// We only generate our special transaction if we have vote extensions
   362  	if extCount > 0 {
   363  		var totalBytes int64
   364  		extTxPrefix := fmt.Sprintf("%s=", voteExtensionKey)
   365  		extTx := []byte(fmt.Sprintf("%s%d", extTxPrefix, sum))
   366  		app.logger.Info("preparing proposal with custom transaction from vote extensions", "tx", extTx)
   367  		// Our generated transaction takes precedence over any supplied
   368  		// transaction that attempts to modify the "extensionSum" value.
   369  		txRecords := make([]*abci.TxRecord, len(req.Txs)+1)
   370  		for i, tx := range req.Txs {
   371  			if strings.HasPrefix(string(tx), extTxPrefix) {
   372  				txRecords[i] = &abci.TxRecord{
   373  					Action: abci.TxRecord_REMOVED,
   374  					Tx:     tx,
   375  				}
   376  			} else {
   377  				txRecords[i] = &abci.TxRecord{
   378  					Action: abci.TxRecord_UNMODIFIED,
   379  					Tx:     tx,
   380  				}
   381  				totalBytes += int64(len(tx))
   382  			}
   383  		}
   384  		if totalBytes+int64(len(extTx)) < req.MaxTxBytes {
   385  			txRecords[len(req.Txs)] = &abci.TxRecord{
   386  				Action: abci.TxRecord_ADDED,
   387  				Tx:     extTx,
   388  			}
   389  		} else {
   390  			app.logger.Info(
   391  				"too many txs to include special vote extension-generated tx",
   392  				"totalBytes", totalBytes,
   393  				"MaxTxBytes", req.MaxTxBytes,
   394  				"extTx", extTx,
   395  				"extTxLen", len(extTx),
   396  			)
   397  		}
   398  		return &abci.ResponsePrepareProposal{
   399  			TxRecords: txRecords,
   400  		}, nil
   401  	}
   402  	// None of the transactions are modified by this application.
   403  	trs := make([]*abci.TxRecord, 0, len(req.Txs))
   404  	var totalBytes int64
   405  	for _, tx := range req.Txs {
   406  		totalBytes += int64(len(tx))
   407  		if totalBytes > req.MaxTxBytes {
   408  			break
   409  		}
   410  		trs = append(trs, &abci.TxRecord{
   411  			Action: abci.TxRecord_UNMODIFIED,
   412  			Tx:     tx,
   413  		})
   414  	}
   415  
   416  	if app.cfg.PrepareProposalDelayMS != 0 {
   417  		time.Sleep(time.Duration(app.cfg.PrepareProposalDelayMS) * time.Millisecond)
   418  	}
   419  
   420  	return &abci.ResponsePrepareProposal{TxRecords: trs}, nil
   421  }
   422  
   423  // ProcessProposal implements part of the Application interface.
   424  // It accepts any proposal that does not contain a malformed transaction.
   425  func (app *Application) ProcessProposal(_ context.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) {
   426  	for _, tx := range req.Txs {
   427  		k, v, err := parseTx(tx)
   428  		if err != nil {
   429  			app.logger.Error("malformed transaction in ProcessProposal", "tx", tx, "err", err)
   430  			return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil
   431  		}
   432  		// Additional check for vote extension-related txs
   433  		if k == voteExtensionKey {
   434  			_, err := strconv.Atoi(v)
   435  			if err != nil {
   436  				app.logger.Error("malformed vote extension transaction", k, v, "err", err)
   437  				return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil
   438  			}
   439  		}
   440  	}
   441  
   442  	if app.cfg.ProcessProposalDelayMS != 0 {
   443  		time.Sleep(time.Duration(app.cfg.ProcessProposalDelayMS) * time.Millisecond)
   444  	}
   445  
   446  	return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil
   447  }
   448  
   449  // ExtendVote will produce vote extensions in the form of random numbers to
   450  // demonstrate vote extension nondeterminism.
   451  //
   452  // In the next block, if there are any vote extensions from the previous block,
   453  // a new transaction will be proposed that updates a special value in the
   454  // key/value store ("extensionSum") with the sum of all of the numbers collected
   455  // from the vote extensions.
   456  func (app *Application) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) {
   457  	// We ignore any requests for vote extensions that don't match our expected
   458  	// next height.
   459  	if req.Height != int64(app.state.Height)+1 {
   460  		app.logger.Error(
   461  			"got unexpected height in ExtendVote request",
   462  			"expectedHeight", app.state.Height+1,
   463  			"requestHeight", req.Height,
   464  		)
   465  		return &abci.ResponseExtendVote{}, nil
   466  	}
   467  	ext := make([]byte, binary.MaxVarintLen64)
   468  	// We don't care that these values are generated by a weak random number
   469  	// generator. It's just for test purposes.
   470  	// nolint:gosec // G404: Use of weak random number generator
   471  	num := rand.Int63n(voteExtensionMaxVal)
   472  	extLen := binary.PutVarint(ext, num)
   473  
   474  	if app.cfg.VoteExtensionDelayMS != 0 {
   475  		time.Sleep(time.Duration(app.cfg.VoteExtensionDelayMS) * time.Millisecond)
   476  	}
   477  
   478  	app.logger.Info("generated vote extension", "num", num, "ext", fmt.Sprintf("%x", ext[:extLen]), "state.Height", app.state.Height)
   479  	return &abci.ResponseExtendVote{
   480  		VoteExtension: ext[:extLen],
   481  	}, nil
   482  }
   483  
   484  // VerifyVoteExtension simply validates vote extensions from other validators
   485  // without doing anything about them. In this case, it just makes sure that the
   486  // vote extension is a well-formed integer value.
   487  func (app *Application) VerifyVoteExtension(_ context.Context, req *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) {
   488  	// We allow vote extensions to be optional
   489  	if len(req.VoteExtension) == 0 {
   490  		return &abci.ResponseVerifyVoteExtension{
   491  			Status: abci.ResponseVerifyVoteExtension_ACCEPT,
   492  		}, nil
   493  	}
   494  	if req.Height != int64(app.state.Height)+1 {
   495  		app.logger.Error(
   496  			"got unexpected height in VerifyVoteExtension request",
   497  			"expectedHeight", app.state.Height,
   498  			"requestHeight", req.Height,
   499  		)
   500  		return &abci.ResponseVerifyVoteExtension{
   501  			Status: abci.ResponseVerifyVoteExtension_REJECT,
   502  		}, nil
   503  	}
   504  
   505  	num, err := parseVoteExtension(req.VoteExtension)
   506  	if err != nil {
   507  		app.logger.Error("failed to verify vote extension", "req", req, "err", err)
   508  		return &abci.ResponseVerifyVoteExtension{
   509  			Status: abci.ResponseVerifyVoteExtension_REJECT,
   510  		}, nil
   511  	}
   512  
   513  	if app.cfg.VoteExtensionDelayMS != 0 {
   514  		time.Sleep(time.Duration(app.cfg.VoteExtensionDelayMS) * time.Millisecond)
   515  	}
   516  
   517  	app.logger.Info("verified vote extension value", "req", req, "num", num)
   518  	return &abci.ResponseVerifyVoteExtension{
   519  		Status: abci.ResponseVerifyVoteExtension_ACCEPT,
   520  	}, nil
   521  }
   522  
   523  func (app *Application) Rollback() error {
   524  	app.mu.Lock()
   525  	defer app.mu.Unlock()
   526  
   527  	return app.state.Rollback()
   528  }
   529  
   530  // validatorUpdates generates a validator set update.
   531  func (app *Application) validatorUpdates(height uint64) (abci.ValidatorUpdates, error) {
   532  	updates := app.cfg.ValidatorUpdates[fmt.Sprintf("%v", height)]
   533  	if len(updates) == 0 {
   534  		return nil, nil
   535  	}
   536  
   537  	valUpdates := abci.ValidatorUpdates{}
   538  	for keyString, power := range updates {
   539  
   540  		keyBytes, err := base64.StdEncoding.DecodeString(keyString)
   541  		if err != nil {
   542  			return nil, fmt.Errorf("invalid base64 pubkey value %q: %w", keyString, err)
   543  		}
   544  		valUpdates = append(valUpdates, abci.UpdateValidator(keyBytes, int64(power), app.cfg.KeyType))
   545  	}
   546  
   547  	// the validator updates could be returned in arbitrary order,
   548  	// and that seems potentially bad. This orders the validator
   549  	// set.
   550  	sort.Slice(valUpdates, func(i, j int) bool {
   551  		return valUpdates[i].PubKey.Compare(valUpdates[j].PubKey) < 0
   552  	})
   553  
   554  	return valUpdates, nil
   555  }
   556  
   557  // parseTx parses a tx in 'key=value' format into a key and value.
   558  func parseTx(tx []byte) (string, string, error) {
   559  	parts := bytes.Split(tx, []byte("="))
   560  	if len(parts) != 2 {
   561  		return "", "", fmt.Errorf("invalid tx format: %q", string(tx))
   562  	}
   563  	if len(parts[0]) == 0 {
   564  		return "", "", errors.New("key cannot be empty")
   565  	}
   566  	return string(parts[0]), string(parts[1]), nil
   567  }
   568  
   569  // parseVoteExtension attempts to parse the given extension data into a positive
   570  // integer value.
   571  func parseVoteExtension(ext []byte) (int64, error) {
   572  	num, errVal := binary.Varint(ext)
   573  	if errVal == 0 {
   574  		return 0, errors.New("vote extension is too small to parse")
   575  	}
   576  	if errVal < 0 {
   577  		return 0, errors.New("vote extension value is too large")
   578  	}
   579  	if num >= voteExtensionMaxVal {
   580  		return 0, fmt.Errorf("vote extension value must be smaller than %d (was %d)", voteExtensionMaxVal, num)
   581  	}
   582  	return num, nil
   583  }