github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/test/e2e/app/app.go (about)

     1  package app
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"strconv"
    11  	"time"
    12  
    13  	abci "github.com/tendermint/tendermint/abci/types"
    14  	"github.com/tendermint/tendermint/proto/tendermint/crypto"
    15  
    16  	"github.com/line/ostracon/abci/example/code"
    17  	ocabci "github.com/line/ostracon/abci/types"
    18  	cryptoenc "github.com/line/ostracon/crypto/encoding"
    19  	"github.com/line/ostracon/libs/log"
    20  	"github.com/line/ostracon/version"
    21  )
    22  
    23  const E2EAppVersion = 999
    24  
    25  // Application is an ABCI application for use by end-to-end tests. It is a
    26  // simple key/value store for strings, storing data in memory and persisting
    27  // to disk as JSON, taking state sync snapshots if requested.
    28  
    29  type Application struct {
    30  	ocabci.BaseApplication
    31  	logger          log.Logger
    32  	state           *State
    33  	snapshots       *SnapshotStore
    34  	cfg             *Config
    35  	restoreSnapshot *abci.Snapshot
    36  	restoreChunks   [][]byte
    37  }
    38  
    39  // Config allows for the setting of high level parameters for running the e2e Application
    40  // KeyType and ValidatorUpdates must be the same for all nodes running the same application.
    41  type Config struct {
    42  	// The directory with which state.json will be persisted in. Usually $HOME/.ostracon/data
    43  	Dir string `toml:"dir"`
    44  
    45  	// SnapshotInterval specifies the height interval at which the application
    46  	// will take state sync snapshots. Defaults to 0 (disabled).
    47  	SnapshotInterval uint64 `toml:"snapshot_interval"`
    48  
    49  	// RetainBlocks specifies the number of recent blocks to retain. Defaults to
    50  	// 0, which retains all blocks. Must be greater that PersistInterval,
    51  	// SnapshotInterval and EvidenceAgeHeight.
    52  	RetainBlocks uint64 `toml:"retain_blocks"`
    53  
    54  	// KeyType sets the curve that will be used by validators.
    55  	// Options are ed25519 & secp256k1
    56  	KeyType string `toml:"key_type"`
    57  
    58  	// PersistInterval specifies the height interval at which the application
    59  	// will persist state to disk. Defaults to 1 (every height), setting this to
    60  	// 0 disables state persistence.
    61  	PersistInterval uint64 `toml:"persist_interval"`
    62  
    63  	// ValidatorUpdates is a map of heights to validator names and their power,
    64  	// and will be returned by the ABCI application. For example, the following
    65  	// changes the power of validator01 and validator02 at height 1000:
    66  	//
    67  	// [validator_update.1000]
    68  	// validator01 = 20
    69  	// validator02 = 10
    70  	//
    71  	// Specifying height 0 returns the validator update during InitChain. The
    72  	// application returns the validator updates as-is, i.e. removing a
    73  	// validator must be done by returning it with power 0, and any validators
    74  	// not specified are not changed.
    75  	//
    76  	// height <-> pubkey <-> voting power
    77  	ValidatorUpdates map[string]map[string]uint8 `toml:"validator_update"`
    78  
    79  	// Add artificial delays to each of the main ABCI calls to mimic computation time
    80  	// of the application
    81  	PrepareProposalDelay time.Duration `toml:"prepare_proposal_delay"`
    82  	ProcessProposalDelay time.Duration `toml:"process_proposal_delay"`
    83  	CheckTxDelay         time.Duration `toml:"check_tx_delay"`
    84  	// TODO: add vote extension and finalize block delays once completed (@cmwaters)
    85  }
    86  
    87  func DefaultConfig(dir string) *Config {
    88  	return &Config{
    89  		PersistInterval:  1,
    90  		SnapshotInterval: 100,
    91  		Dir:              dir,
    92  	}
    93  }
    94  
    95  // NewApplication creates the application.
    96  func NewApplication(cfg *Config) (*Application, error) {
    97  	state, err := NewState(cfg.Dir, cfg.PersistInterval)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	snapshots, err := NewSnapshotStore(filepath.Join(cfg.Dir, "snapshots"))
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	return &Application{
   106  		logger:    log.NewOCLogger(log.NewSyncWriter(os.Stdout)),
   107  		state:     state,
   108  		snapshots: snapshots,
   109  		cfg:       cfg,
   110  	}, nil
   111  }
   112  
   113  // Info implements ABCI.
   114  func (app *Application) Info(req abci.RequestInfo) abci.ResponseInfo {
   115  	return abci.ResponseInfo{
   116  		Version:          version.ABCIVersion,
   117  		AppVersion:       E2EAppVersion,
   118  		LastBlockHeight:  int64(app.state.Height),
   119  		LastBlockAppHash: app.state.Hash,
   120  	}
   121  }
   122  
   123  // Info implements ABCI.
   124  func (app *Application) InitChain(req abci.RequestInitChain) abci.ResponseInitChain {
   125  	var err error
   126  	app.state.initialHeight = uint64(req.InitialHeight)
   127  	if len(req.AppStateBytes) > 0 {
   128  		err = app.state.Import(0, req.AppStateBytes)
   129  		if err != nil {
   130  			panic(err)
   131  		}
   132  	}
   133  	resp := abci.ResponseInitChain{
   134  		AppHash: app.state.Hash,
   135  	}
   136  	if resp.Validators, err = app.validatorUpdates(0); err != nil {
   137  		panic(err)
   138  	}
   139  	return resp
   140  }
   141  
   142  // CheckTx implements ABCI.
   143  func (app *Application) CheckTx(req abci.RequestCheckTx) ocabci.ResponseCheckTx {
   144  	_, _, err := parseTx(req.Tx)
   145  	if err != nil {
   146  		return ocabci.ResponseCheckTx{
   147  			Code: code.CodeTypeEncodingError,
   148  			Log:  err.Error(),
   149  		}
   150  	}
   151  
   152  	if app.cfg.CheckTxDelay != 0 {
   153  		time.Sleep(app.cfg.CheckTxDelay)
   154  	}
   155  
   156  	return ocabci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}
   157  }
   158  
   159  // DeliverTx implements ABCI.
   160  func (app *Application) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
   161  	key, value, err := parseTx(req.Tx)
   162  	if err != nil {
   163  		panic(err) // shouldn't happen since we verified it in CheckTx
   164  	}
   165  	app.state.Set(key, value)
   166  	return abci.ResponseDeliverTx{Code: code.CodeTypeOK}
   167  }
   168  
   169  // EndBlock implements ABCI.
   170  func (app *Application) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock {
   171  	valUpdates, err := app.validatorUpdates(uint64(req.Height))
   172  	if err != nil {
   173  		panic(err)
   174  	}
   175  
   176  	return abci.ResponseEndBlock{
   177  		ValidatorUpdates: valUpdates,
   178  		Events: []abci.Event{
   179  			{
   180  				Type: "val_updates",
   181  				Attributes: []abci.EventAttribute{
   182  					{
   183  						Key:   []byte("size"),
   184  						Value: []byte(strconv.Itoa(valUpdates.Len())),
   185  					},
   186  					{
   187  						Key:   []byte("height"),
   188  						Value: []byte(strconv.Itoa(int(req.Height))),
   189  					},
   190  				},
   191  			},
   192  		},
   193  	}
   194  }
   195  
   196  // Commit implements ABCI.
   197  func (app *Application) Commit() abci.ResponseCommit {
   198  	height, hash, err := app.state.Commit()
   199  	if err != nil {
   200  		panic(err)
   201  	}
   202  	if app.cfg.SnapshotInterval > 0 && height%app.cfg.SnapshotInterval == 0 {
   203  		snapshot, err := app.snapshots.Create(app.state)
   204  		if err != nil {
   205  			panic(err)
   206  		}
   207  		app.logger.Info("Created state sync snapshot", "height", snapshot.Height)
   208  	}
   209  	retainHeight := int64(0)
   210  	if app.cfg.RetainBlocks > 0 {
   211  		retainHeight = int64(height - app.cfg.RetainBlocks + 1)
   212  	}
   213  	return abci.ResponseCommit{
   214  		Data:         hash,
   215  		RetainHeight: retainHeight,
   216  	}
   217  }
   218  
   219  // Query implements ABCI.
   220  func (app *Application) Query(req abci.RequestQuery) abci.ResponseQuery {
   221  	return abci.ResponseQuery{
   222  		Height: int64(app.state.Height),
   223  		Key:    req.Data,
   224  		Value:  []byte(app.state.Get(string(req.Data))),
   225  	}
   226  }
   227  
   228  // ListSnapshots implements ABCI.
   229  func (app *Application) ListSnapshots(req abci.RequestListSnapshots) abci.ResponseListSnapshots {
   230  	snapshots, err := app.snapshots.List()
   231  	if err != nil {
   232  		panic(err)
   233  	}
   234  	return abci.ResponseListSnapshots{Snapshots: snapshots}
   235  }
   236  
   237  // LoadSnapshotChunk implements ABCI.
   238  func (app *Application) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abci.ResponseLoadSnapshotChunk {
   239  	chunk, err := app.snapshots.LoadChunk(req.Height, req.Format, req.Chunk)
   240  	if err != nil {
   241  		panic(err)
   242  	}
   243  	return abci.ResponseLoadSnapshotChunk{Chunk: chunk}
   244  }
   245  
   246  // OfferSnapshot implements ABCI.
   247  func (app *Application) OfferSnapshot(req abci.RequestOfferSnapshot) abci.ResponseOfferSnapshot {
   248  	if app.restoreSnapshot != nil {
   249  		panic("A snapshot is already being restored")
   250  	}
   251  	app.restoreSnapshot = req.Snapshot
   252  	app.restoreChunks = [][]byte{}
   253  	return abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}
   254  }
   255  
   256  // ApplySnapshotChunk implements ABCI.
   257  func (app *Application) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) abci.ResponseApplySnapshotChunk {
   258  	if app.restoreSnapshot == nil {
   259  		panic("No restore in progress")
   260  	}
   261  	app.restoreChunks = append(app.restoreChunks, req.Chunk)
   262  	if len(app.restoreChunks) == int(app.restoreSnapshot.Chunks) {
   263  		bz := []byte{}
   264  		for _, chunk := range app.restoreChunks {
   265  			bz = append(bz, chunk...)
   266  		}
   267  		err := app.state.Import(app.restoreSnapshot.Height, bz)
   268  		if err != nil {
   269  			panic(err)
   270  		}
   271  		app.restoreSnapshot = nil
   272  		app.restoreChunks = nil
   273  	}
   274  	return abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}
   275  }
   276  
   277  func (app *Application) Rollback() error {
   278  	return app.state.Rollback()
   279  }
   280  
   281  // validatorUpdates generates a validator set update.
   282  func (app *Application) validatorUpdates(height uint64) (ocabci.ValidatorUpdates, error) {
   283  	updates := app.cfg.ValidatorUpdates[fmt.Sprintf("%v", height)]
   284  	if len(updates) == 0 {
   285  		return nil, nil
   286  	}
   287  
   288  	valUpdates := ocabci.ValidatorUpdates{}
   289  	for keyString, power := range updates {
   290  
   291  		keyBytes, err := base64.StdEncoding.DecodeString(keyString)
   292  		if err != nil {
   293  			return nil, fmt.Errorf("invalid base64.enc pubkey %q: %w", keyString, err)
   294  		}
   295  		pubKeyProto := crypto.PublicKey{}
   296  		err = pubKeyProto.Unmarshal(keyBytes)
   297  		if err != nil {
   298  			return nil, fmt.Errorf("invalid marshalled pubkey %q: %w", keyString, err)
   299  		}
   300  		pubKey, err := cryptoenc.PubKeyFromProto(&pubKeyProto)
   301  		if err != nil {
   302  			return nil, fmt.Errorf("invalid crypto pubkey %q: %w", keyString, err)
   303  		}
   304  		valUpdates = append(valUpdates, ocabci.NewValidatorUpdate(pubKey, int64(power)))
   305  	}
   306  	return valUpdates, nil
   307  }
   308  
   309  // parseTx parses a tx in 'key=value' format into a key and value.
   310  func parseTx(tx []byte) (string, string, error) {
   311  	parts := bytes.Split(tx, []byte("="))
   312  	if len(parts) != 2 {
   313  		return "", "", fmt.Errorf("invalid tx format: %q", string(tx))
   314  	}
   315  	if len(parts[0]) == 0 {
   316  		return "", "", errors.New("key cannot be empty")
   317  	}
   318  	return string(parts[0]), string(parts[1]), nil
   319  }