bitbucket.org/number571/tendermint@v0.8.14/state/execution.go (about)

     1  package state
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	abci "bitbucket.org/number571/tendermint/abci/types"
    10  	cryptoenc "bitbucket.org/number571/tendermint/crypto/encoding"
    11  	"bitbucket.org/number571/tendermint/internal/libs/fail"
    12  	mempl "bitbucket.org/number571/tendermint/internal/mempool"
    13  	"bitbucket.org/number571/tendermint/libs/log"
    14  	tmstate "bitbucket.org/number571/tendermint/proto/tendermint/state"
    15  	"bitbucket.org/number571/tendermint/proxy"
    16  	"bitbucket.org/number571/tendermint/types"
    17  )
    18  
    19  //-----------------------------------------------------------------------------
    20  // BlockExecutor handles block execution and state updates.
    21  // It exposes ApplyBlock(), which validates & executes the block, updates state w/ ABCI responses,
    22  // then commits and updates the mempool atomically, then saves state.
    23  
    24  // BlockExecutor provides the context and accessories for properly executing a block.
    25  type BlockExecutor struct {
    26  	// save state, validators, consensus params, abci responses here
    27  	store Store
    28  
    29  	// use blockstore for the pruning functions.
    30  	blockStore BlockStore
    31  
    32  	// execute the app against this
    33  	proxyApp proxy.AppConnConsensus
    34  
    35  	// events
    36  	eventBus types.BlockEventPublisher
    37  
    38  	// manage the mempool lock during commit
    39  	// and update both with block results after commit.
    40  	mempool mempl.Mempool
    41  	evpool  EvidencePool
    42  
    43  	logger  log.Logger
    44  	metrics *Metrics
    45  
    46  	// cache the verification results over a single height
    47  	cache map[string]struct{}
    48  }
    49  
    50  type BlockExecutorOption func(executor *BlockExecutor)
    51  
    52  func BlockExecutorWithMetrics(metrics *Metrics) BlockExecutorOption {
    53  	return func(blockExec *BlockExecutor) {
    54  		blockExec.metrics = metrics
    55  	}
    56  }
    57  
    58  // NewBlockExecutor returns a new BlockExecutor with a NopEventBus.
    59  // Call SetEventBus to provide one.
    60  func NewBlockExecutor(
    61  	stateStore Store,
    62  	logger log.Logger,
    63  	proxyApp proxy.AppConnConsensus,
    64  	mempool mempl.Mempool,
    65  	evpool EvidencePool,
    66  	blockStore BlockStore,
    67  	options ...BlockExecutorOption,
    68  ) *BlockExecutor {
    69  	res := &BlockExecutor{
    70  		store:      stateStore,
    71  		proxyApp:   proxyApp,
    72  		eventBus:   types.NopEventBus{},
    73  		mempool:    mempool,
    74  		evpool:     evpool,
    75  		logger:     logger,
    76  		metrics:    NopMetrics(),
    77  		cache:      make(map[string]struct{}),
    78  		blockStore: blockStore,
    79  	}
    80  
    81  	for _, option := range options {
    82  		option(res)
    83  	}
    84  
    85  	return res
    86  }
    87  
    88  func (blockExec *BlockExecutor) Store() Store {
    89  	return blockExec.store
    90  }
    91  
    92  // SetEventBus - sets the event bus for publishing block related events.
    93  // If not called, it defaults to types.NopEventBus.
    94  func (blockExec *BlockExecutor) SetEventBus(eventBus types.BlockEventPublisher) {
    95  	blockExec.eventBus = eventBus
    96  }
    97  
    98  // CreateProposalBlock calls state.MakeBlock with evidence from the evpool
    99  // and txs from the mempool. The max bytes must be big enough to fit the commit.
   100  // Up to 1/10th of the block space is allcoated for maximum sized evidence.
   101  // The rest is given to txs, up to the max gas.
   102  func (blockExec *BlockExecutor) CreateProposalBlock(
   103  	height int64,
   104  	state State, commit *types.Commit,
   105  	proposerAddr []byte,
   106  ) (*types.Block, *types.PartSet) {
   107  
   108  	maxBytes := state.ConsensusParams.Block.MaxBytes
   109  	maxGas := state.ConsensusParams.Block.MaxGas
   110  
   111  	evidence, evSize := blockExec.evpool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes)
   112  
   113  	// Fetch a limited amount of valid txs
   114  	maxDataBytes := types.MaxDataBytes(maxBytes, evSize, state.Validators.Size())
   115  
   116  	txs := blockExec.mempool.ReapMaxBytesMaxGas(maxDataBytes, maxGas)
   117  
   118  	return state.MakeBlock(height, txs, commit, evidence, proposerAddr)
   119  }
   120  
   121  // ValidateBlock validates the given block against the given state.
   122  // If the block is invalid, it returns an error.
   123  // Validation does not mutate state, but does require historical information from the stateDB,
   124  // ie. to verify evidence from a validator at an old height.
   125  func (blockExec *BlockExecutor) ValidateBlock(state State, block *types.Block) error {
   126  	hash := block.Hash()
   127  	if _, ok := blockExec.cache[hash.String()]; ok {
   128  		return nil
   129  	}
   130  
   131  	err := validateBlock(state, block)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	err = blockExec.evpool.CheckEvidence(block.Evidence.Evidence)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	blockExec.cache[hash.String()] = struct{}{}
   142  	return nil
   143  }
   144  
   145  // ApplyBlock validates the block against the state, executes it against the app,
   146  // fires the relevant events, commits the app, and saves the new state and responses.
   147  // It returns the new state.
   148  // It's the only function that needs to be called
   149  // from outside this package to process and commit an entire block.
   150  // It takes a blockID to avoid recomputing the parts hash.
   151  func (blockExec *BlockExecutor) ApplyBlock(
   152  	state State, blockID types.BlockID, block *types.Block,
   153  ) (State, error) {
   154  
   155  	// validate the block if we haven't already
   156  	if err := blockExec.ValidateBlock(state, block); err != nil {
   157  		return state, ErrInvalidBlock(err)
   158  	}
   159  
   160  	startTime := time.Now().UnixNano()
   161  	abciResponses, err := execBlockOnProxyApp(
   162  		blockExec.logger, blockExec.proxyApp, block, blockExec.store, state.InitialHeight,
   163  	)
   164  	endTime := time.Now().UnixNano()
   165  	blockExec.metrics.BlockProcessingTime.Observe(float64(endTime-startTime) / 1000000)
   166  	if err != nil {
   167  		return state, ErrProxyAppConn(err)
   168  	}
   169  
   170  	fail.Fail() // XXX
   171  
   172  	// Save the results before we commit.
   173  	if err := blockExec.store.SaveABCIResponses(block.Height, abciResponses); err != nil {
   174  		return state, err
   175  	}
   176  
   177  	fail.Fail() // XXX
   178  
   179  	// validate the validator updates and convert to tendermint types
   180  	abciValUpdates := abciResponses.EndBlock.ValidatorUpdates
   181  	err = validateValidatorUpdates(abciValUpdates, state.ConsensusParams.Validator)
   182  	if err != nil {
   183  		return state, fmt.Errorf("error in validator updates: %v", err)
   184  	}
   185  
   186  	validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciValUpdates)
   187  	if err != nil {
   188  		return state, err
   189  	}
   190  	if len(validatorUpdates) > 0 {
   191  		blockExec.logger.Debug("updates to validators", "updates", types.ValidatorListString(validatorUpdates))
   192  	}
   193  
   194  	// Update the state with the block and responses.
   195  	state, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates)
   196  	if err != nil {
   197  		return state, fmt.Errorf("commit failed for application: %v", err)
   198  	}
   199  
   200  	// Lock mempool, commit app state, update mempoool.
   201  	appHash, retainHeight, err := blockExec.Commit(state, block, abciResponses.DeliverTxs)
   202  	if err != nil {
   203  		return state, fmt.Errorf("commit failed for application: %v", err)
   204  	}
   205  
   206  	// Update evpool with the latest state.
   207  	blockExec.evpool.Update(state, block.Evidence.Evidence)
   208  
   209  	fail.Fail() // XXX
   210  
   211  	// Update the app hash and save the state.
   212  	state.AppHash = appHash
   213  	if err := blockExec.store.Save(state); err != nil {
   214  		return state, err
   215  	}
   216  
   217  	fail.Fail() // XXX
   218  
   219  	// Prune old heights, if requested by ABCI app.
   220  	if retainHeight > 0 {
   221  		pruned, err := blockExec.pruneBlocks(retainHeight)
   222  		if err != nil {
   223  			blockExec.logger.Error("failed to prune blocks", "retain_height", retainHeight, "err", err)
   224  		} else {
   225  			blockExec.logger.Debug("pruned blocks", "pruned", pruned, "retain_height", retainHeight)
   226  		}
   227  	}
   228  
   229  	// reset the verification cache
   230  	blockExec.cache = make(map[string]struct{})
   231  
   232  	// Events are fired after everything else.
   233  	// NOTE: if we crash between Commit and Save, events wont be fired during replay
   234  	fireEvents(blockExec.logger, blockExec.eventBus, block, blockID, abciResponses, validatorUpdates)
   235  
   236  	return state, nil
   237  }
   238  
   239  // Commit locks the mempool, runs the ABCI Commit message, and updates the
   240  // mempool.
   241  // It returns the result of calling abci.Commit (the AppHash) and the height to retain (if any).
   242  // The Mempool must be locked during commit and update because state is
   243  // typically reset on Commit and old txs must be replayed against committed
   244  // state before new txs are run in the mempool, lest they be invalid.
   245  func (blockExec *BlockExecutor) Commit(
   246  	state State,
   247  	block *types.Block,
   248  	deliverTxResponses []*abci.ResponseDeliverTx,
   249  ) ([]byte, int64, error) {
   250  	blockExec.mempool.Lock()
   251  	defer blockExec.mempool.Unlock()
   252  
   253  	// while mempool is Locked, flush to ensure all async requests have completed
   254  	// in the ABCI app before Commit.
   255  	err := blockExec.mempool.FlushAppConn()
   256  	if err != nil {
   257  		blockExec.logger.Error("client error during mempool.FlushAppConn", "err", err)
   258  		return nil, 0, err
   259  	}
   260  
   261  	// Commit block, get hash back
   262  	res, err := blockExec.proxyApp.CommitSync(context.Background())
   263  	if err != nil {
   264  		blockExec.logger.Error("client error during proxyAppConn.CommitSync", "err", err)
   265  		return nil, 0, err
   266  	}
   267  
   268  	// ResponseCommit has no error code - just data
   269  	blockExec.logger.Info(
   270  		"committed state",
   271  		"height", block.Height,
   272  		"num_txs", len(block.Txs),
   273  		"app_hash", fmt.Sprintf("%X", res.Data),
   274  	)
   275  
   276  	// Update mempool.
   277  	err = blockExec.mempool.Update(
   278  		block.Height,
   279  		block.Txs,
   280  		deliverTxResponses,
   281  		TxPreCheck(state),
   282  		TxPostCheck(state),
   283  	)
   284  
   285  	return res.Data, res.RetainHeight, err
   286  }
   287  
   288  //---------------------------------------------------------
   289  // Helper functions for executing blocks and updating state
   290  
   291  // Executes block's transactions on proxyAppConn.
   292  // Returns a list of transaction results and updates to the validator set
   293  func execBlockOnProxyApp(
   294  	logger log.Logger,
   295  	proxyAppConn proxy.AppConnConsensus,
   296  	block *types.Block,
   297  	store Store,
   298  	initialHeight int64,
   299  ) (*tmstate.ABCIResponses, error) {
   300  	var validTxs, invalidTxs = 0, 0
   301  
   302  	txIndex := 0
   303  	abciResponses := new(tmstate.ABCIResponses)
   304  	dtxs := make([]*abci.ResponseDeliverTx, len(block.Txs))
   305  	abciResponses.DeliverTxs = dtxs
   306  
   307  	// Execute transactions and get hash.
   308  	proxyCb := func(req *abci.Request, res *abci.Response) {
   309  		if r, ok := res.Value.(*abci.Response_DeliverTx); ok {
   310  			// TODO: make use of res.Log
   311  			// TODO: make use of this info
   312  			// Blocks may include invalid txs.
   313  			txRes := r.DeliverTx
   314  			if txRes.Code == abci.CodeTypeOK {
   315  				validTxs++
   316  			} else {
   317  				logger.Debug("invalid tx", "code", txRes.Code, "log", txRes.Log)
   318  				invalidTxs++
   319  			}
   320  
   321  			abciResponses.DeliverTxs[txIndex] = txRes
   322  			txIndex++
   323  		}
   324  	}
   325  	proxyAppConn.SetResponseCallback(proxyCb)
   326  
   327  	commitInfo := getBeginBlockValidatorInfo(block, store, initialHeight)
   328  
   329  	byzVals := make([]abci.Evidence, 0)
   330  	for _, evidence := range block.Evidence.Evidence {
   331  		byzVals = append(byzVals, evidence.ABCI()...)
   332  	}
   333  
   334  	ctx := context.Background()
   335  
   336  	// Begin block
   337  	var err error
   338  	pbh := block.Header.ToProto()
   339  	if pbh == nil {
   340  		return nil, errors.New("nil header")
   341  	}
   342  
   343  	abciResponses.BeginBlock, err = proxyAppConn.BeginBlockSync(
   344  		ctx,
   345  		abci.RequestBeginBlock{
   346  			Hash:                block.Hash(),
   347  			Header:              *pbh,
   348  			LastCommitInfo:      commitInfo,
   349  			ByzantineValidators: byzVals,
   350  		},
   351  	)
   352  	if err != nil {
   353  		logger.Error("error in proxyAppConn.BeginBlock", "err", err)
   354  		return nil, err
   355  	}
   356  
   357  	// run txs of block
   358  	for _, tx := range block.Txs {
   359  		_, err = proxyAppConn.DeliverTxAsync(ctx, abci.RequestDeliverTx{Tx: tx})
   360  		if err != nil {
   361  			return nil, err
   362  		}
   363  	}
   364  
   365  	abciResponses.EndBlock, err = proxyAppConn.EndBlockSync(ctx, abci.RequestEndBlock{Height: block.Height})
   366  	if err != nil {
   367  		logger.Error("error in proxyAppConn.EndBlock", "err", err)
   368  		return nil, err
   369  	}
   370  
   371  	logger.Info("executed block", "height", block.Height, "num_valid_txs", validTxs, "num_invalid_txs", invalidTxs)
   372  	return abciResponses, nil
   373  }
   374  
   375  func getBeginBlockValidatorInfo(block *types.Block, store Store,
   376  	initialHeight int64) abci.LastCommitInfo {
   377  	voteInfos := make([]abci.VoteInfo, block.LastCommit.Size())
   378  	// Initial block -> LastCommitInfo.Votes are empty.
   379  	// Remember that the first LastCommit is intentionally empty, so it makes
   380  	// sense for LastCommitInfo.Votes to also be empty.
   381  	if block.Height > initialHeight {
   382  		lastValSet, err := store.LoadValidators(block.Height - 1)
   383  		if err != nil {
   384  			panic(err)
   385  		}
   386  
   387  		// Sanity check that commit size matches validator set size - only applies
   388  		// after first block.
   389  		var (
   390  			commitSize = block.LastCommit.Size()
   391  			valSetLen  = len(lastValSet.Validators)
   392  		)
   393  		if commitSize != valSetLen {
   394  			panic(fmt.Sprintf(
   395  				"commit size (%d) doesn't match valset length (%d) at height %d\n\n%v\n\n%v",
   396  				commitSize, valSetLen, block.Height, block.LastCommit.Signatures, lastValSet.Validators,
   397  			))
   398  		}
   399  
   400  		for i, val := range lastValSet.Validators {
   401  			commitSig := block.LastCommit.Signatures[i]
   402  			voteInfos[i] = abci.VoteInfo{
   403  				Validator:       types.TM2PB.Validator(val),
   404  				SignedLastBlock: !commitSig.Absent(),
   405  			}
   406  		}
   407  	}
   408  
   409  	return abci.LastCommitInfo{
   410  		Round: block.LastCommit.Round,
   411  		Votes: voteInfos,
   412  	}
   413  }
   414  
   415  func validateValidatorUpdates(abciUpdates []abci.ValidatorUpdate,
   416  	params types.ValidatorParams) error {
   417  	for _, valUpdate := range abciUpdates {
   418  		if valUpdate.GetPower() < 0 {
   419  			return fmt.Errorf("voting power can't be negative %v", valUpdate)
   420  		} else if valUpdate.GetPower() == 0 {
   421  			// continue, since this is deleting the validator, and thus there is no
   422  			// pubkey to check
   423  			continue
   424  		}
   425  
   426  		// Check if validator's pubkey matches an ABCI type in the consensus params
   427  		pk, err := cryptoenc.PubKeyFromProto(valUpdate.PubKey)
   428  		if err != nil {
   429  			return err
   430  		}
   431  
   432  		if !params.IsValidPubkeyType(pk.Type()) {
   433  			return fmt.Errorf("validator %v is using pubkey %s, which is unsupported for consensus",
   434  				valUpdate, pk.Type())
   435  		}
   436  	}
   437  	return nil
   438  }
   439  
   440  // updateState returns a new State updated according to the header and responses.
   441  func updateState(
   442  	state State,
   443  	blockID types.BlockID,
   444  	header *types.Header,
   445  	abciResponses *tmstate.ABCIResponses,
   446  	validatorUpdates []*types.Validator,
   447  ) (State, error) {
   448  
   449  	// Copy the valset so we can apply changes from EndBlock
   450  	// and update s.LastValidators and s.Validators.
   451  	nValSet := state.NextValidators.Copy()
   452  
   453  	// Update the validator set with the latest abciResponses.
   454  	lastHeightValsChanged := state.LastHeightValidatorsChanged
   455  	if len(validatorUpdates) > 0 {
   456  		err := nValSet.UpdateWithChangeSet(validatorUpdates)
   457  		if err != nil {
   458  			return state, fmt.Errorf("error changing validator set: %v", err)
   459  		}
   460  		// Change results from this height but only applies to the next next height.
   461  		lastHeightValsChanged = header.Height + 1 + 1
   462  	}
   463  
   464  	// Update validator proposer priority and set state variables.
   465  	nValSet.IncrementProposerPriority(1)
   466  
   467  	// Update the params with the latest abciResponses.
   468  	nextParams := state.ConsensusParams
   469  	lastHeightParamsChanged := state.LastHeightConsensusParamsChanged
   470  	if abciResponses.EndBlock.ConsensusParamUpdates != nil {
   471  		// NOTE: must not mutate s.ConsensusParams
   472  		nextParams = state.ConsensusParams.UpdateConsensusParams(abciResponses.EndBlock.ConsensusParamUpdates)
   473  		err := nextParams.ValidateConsensusParams()
   474  		if err != nil {
   475  			return state, fmt.Errorf("error updating consensus params: %v", err)
   476  		}
   477  
   478  		state.Version.Consensus.App = nextParams.Version.AppVersion
   479  
   480  		// Change results from this height but only applies to the next height.
   481  		lastHeightParamsChanged = header.Height + 1
   482  	}
   483  
   484  	nextVersion := state.Version
   485  
   486  	// NOTE: the AppHash has not been populated.
   487  	// It will be filled on state.Save.
   488  	return State{
   489  		Version:                          nextVersion,
   490  		ChainID:                          state.ChainID,
   491  		InitialHeight:                    state.InitialHeight,
   492  		LastBlockHeight:                  header.Height,
   493  		LastBlockID:                      blockID,
   494  		LastBlockTime:                    header.Time,
   495  		NextValidators:                   nValSet,
   496  		Validators:                       state.NextValidators.Copy(),
   497  		LastValidators:                   state.Validators.Copy(),
   498  		LastHeightValidatorsChanged:      lastHeightValsChanged,
   499  		ConsensusParams:                  nextParams,
   500  		LastHeightConsensusParamsChanged: lastHeightParamsChanged,
   501  		LastResultsHash:                  ABCIResponsesResultsHash(abciResponses),
   502  		AppHash:                          nil,
   503  	}, nil
   504  }
   505  
   506  // Fire NewBlock, NewBlockHeader.
   507  // Fire TxEvent for every tx.
   508  // NOTE: if Tendermint crashes before commit, some or all of these events may be published again.
   509  func fireEvents(
   510  	logger log.Logger,
   511  	eventBus types.BlockEventPublisher,
   512  	block *types.Block,
   513  	blockID types.BlockID,
   514  	abciResponses *tmstate.ABCIResponses,
   515  	validatorUpdates []*types.Validator,
   516  ) {
   517  	if err := eventBus.PublishEventNewBlock(types.EventDataNewBlock{
   518  		Block:            block,
   519  		BlockID:          blockID,
   520  		ResultBeginBlock: *abciResponses.BeginBlock,
   521  		ResultEndBlock:   *abciResponses.EndBlock,
   522  	}); err != nil {
   523  		logger.Error("failed publishing new block", "err", err)
   524  	}
   525  
   526  	if err := eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{
   527  		Header:           block.Header,
   528  		NumTxs:           int64(len(block.Txs)),
   529  		ResultBeginBlock: *abciResponses.BeginBlock,
   530  		ResultEndBlock:   *abciResponses.EndBlock,
   531  	}); err != nil {
   532  		logger.Error("failed publishing new block header", "err", err)
   533  	}
   534  
   535  	if len(block.Evidence.Evidence) != 0 {
   536  		for _, ev := range block.Evidence.Evidence {
   537  			if err := eventBus.PublishEventNewEvidence(types.EventDataNewEvidence{
   538  				Evidence: ev,
   539  				Height:   block.Height,
   540  			}); err != nil {
   541  				logger.Error("failed publishing new evidence", "err", err)
   542  			}
   543  		}
   544  	}
   545  
   546  	for i, tx := range block.Data.Txs {
   547  		if err := eventBus.PublishEventTx(types.EventDataTx{TxResult: abci.TxResult{
   548  			Height: block.Height,
   549  			Index:  uint32(i),
   550  			Tx:     tx,
   551  			Result: *(abciResponses.DeliverTxs[i]),
   552  		}}); err != nil {
   553  			logger.Error("failed publishing event TX", "err", err)
   554  		}
   555  	}
   556  
   557  	if len(validatorUpdates) > 0 {
   558  		if err := eventBus.PublishEventValidatorSetUpdates(
   559  			types.EventDataValidatorSetUpdates{ValidatorUpdates: validatorUpdates}); err != nil {
   560  			logger.Error("failed publishing event", "err", err)
   561  		}
   562  	}
   563  }
   564  
   565  //----------------------------------------------------------------------------------------------------
   566  // Execute block without state. TODO: eliminate
   567  
   568  // ExecCommitBlock executes and commits a block on the proxyApp without validating or mutating the state.
   569  // It returns the application root hash (result of abci.Commit).
   570  func ExecCommitBlock(
   571  	be *BlockExecutor,
   572  	appConnConsensus proxy.AppConnConsensus,
   573  	block *types.Block,
   574  	logger log.Logger,
   575  	store Store,
   576  	initialHeight int64,
   577  	s State,
   578  ) ([]byte, error) {
   579  	abciResponses, err := execBlockOnProxyApp(logger, appConnConsensus, block, store, initialHeight)
   580  	if err != nil {
   581  		logger.Error("failed executing block on proxy app", "height", block.Height, "err", err)
   582  		return nil, err
   583  	}
   584  
   585  	// the BlockExecutor condition is using for the final block replay process.
   586  	if be != nil {
   587  		abciValUpdates := abciResponses.EndBlock.ValidatorUpdates
   588  		err = validateValidatorUpdates(abciValUpdates, s.ConsensusParams.Validator)
   589  		if err != nil {
   590  			logger.Error("err", err)
   591  			return nil, err
   592  		}
   593  		validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciValUpdates)
   594  		if err != nil {
   595  			logger.Error("err", err)
   596  			return nil, err
   597  		}
   598  
   599  		blockID := types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(types.BlockPartSizeBytes).Header()}
   600  		fireEvents(be.logger, be.eventBus, block, blockID, abciResponses, validatorUpdates)
   601  	}
   602  
   603  	// Commit block, get hash back
   604  	res, err := appConnConsensus.CommitSync(context.Background())
   605  	if err != nil {
   606  		logger.Error("client error during proxyAppConn.CommitSync", "err", res)
   607  		return nil, err
   608  	}
   609  
   610  	// ResponseCommit has no error or log, just data
   611  	return res.Data, nil
   612  }
   613  
   614  func (blockExec *BlockExecutor) pruneBlocks(retainHeight int64) (uint64, error) {
   615  	base := blockExec.blockStore.Base()
   616  	if retainHeight <= base {
   617  		return 0, nil
   618  	}
   619  	pruned, err := blockExec.blockStore.PruneBlocks(retainHeight)
   620  	if err != nil {
   621  		return 0, fmt.Errorf("failed to prune block store: %w", err)
   622  	}
   623  
   624  	err = blockExec.Store().PruneStates(retainHeight)
   625  	if err != nil {
   626  		return 0, fmt.Errorf("failed to prune state store: %w", err)
   627  	}
   628  	return pruned, nil
   629  }