github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/consensus/abci/process.go (about)

     1  package abci
     2  
     3  import (
     4  	"context"
     5  	"crypto/sha256"
     6  	"fmt"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/hyperledger/burrow/bcm"
    11  
    12  	"github.com/hyperledger/burrow/execution"
    13  	"github.com/hyperledger/burrow/txs"
    14  	"github.com/tendermint/tendermint/abci/types"
    15  	"github.com/tendermint/tendermint/mempool"
    16  	tmTypes "github.com/tendermint/tendermint/types"
    17  )
    18  
    19  type Process struct {
    20  	ticker       *time.Ticker
    21  	committer    execution.BatchCommitter
    22  	blockchain   *bcm.Blockchain
    23  	done         chan struct{}
    24  	panic        func(error)
    25  	commitNeeded bool
    26  	txDecoder    txs.Decoder
    27  	shutdownOnce sync.Once
    28  }
    29  
    30  // NewProcess returns a no-consensus ABCI process suitable for running a single node without Tendermint.
    31  // The CheckTx function can be used to submit transactions which are processed according
    32  func NewProcess(committer execution.BatchCommitter, blockchain *bcm.Blockchain, txDecoder txs.Decoder,
    33  	commitInterval time.Duration, panicFunc func(error)) *Process {
    34  
    35  	p := &Process{
    36  		committer:  committer,
    37  		blockchain: blockchain,
    38  		done:       make(chan struct{}),
    39  		txDecoder:  txDecoder,
    40  		panic:      panicFunc,
    41  	}
    42  
    43  	if commitInterval != 0 {
    44  		p.ticker = time.NewTicker(commitInterval)
    45  		go p.triggerCommits()
    46  	}
    47  
    48  	return p
    49  }
    50  
    51  func (p *Process) CheckTx(tx tmTypes.Tx, cb func(*types.Response), txInfo mempool.TxInfo) error {
    52  	const header = "DeliverTx"
    53  	p.committer.Lock()
    54  	defer p.committer.Unlock()
    55  	// Skip check - deliver immediately
    56  	// FIXME: [Silas] this means that any transaction that a transaction that fails CheckTx
    57  	// that would not normally end up stored in state (as an exceptional tx) will get stored in state.
    58  	// This means that the same sequence of transactions fed to no consensus mode can give rise to a state with additional
    59  	// invalid transactions in state. Since the state hash is non-deterministic based on when the commits happen it's not
    60  	// clear this is a problem. The underlying state will be compatible.
    61  	checkTx := ExecuteTx(header, p.committer, p.txDecoder, tx)
    62  	cb(types.ToResponseCheckTx(checkTx))
    63  	p.commitNeeded = true
    64  	if p.ticker == nil {
    65  		err := p.commit()
    66  		if err != nil {
    67  			return err
    68  		}
    69  	}
    70  	return nil
    71  }
    72  
    73  func (p *Process) Shutdown(ctx context.Context) (err error) {
    74  	p.committer.Lock()
    75  	defer p.committer.Unlock()
    76  	p.shutdownOnce.Do(func() {
    77  		if p.ticker != nil {
    78  			p.ticker.Stop()
    79  		}
    80  		close(p.done)
    81  	})
    82  	return
    83  }
    84  
    85  func (p *Process) triggerCommits() {
    86  	for {
    87  		select {
    88  		case <-p.ticker.C:
    89  			p.commitOrPanic()
    90  		case <-p.done:
    91  			// Escape loop since ticket channel is never closed
    92  		}
    93  	}
    94  }
    95  
    96  func (p *Process) commitOrPanic() {
    97  	p.committer.Lock()
    98  	defer p.committer.Unlock()
    99  	err := p.commit()
   100  	if err != nil {
   101  		p.panic(err)
   102  	}
   103  }
   104  
   105  func (p *Process) commit() error {
   106  	const errHeader = "commit():"
   107  	if !p.commitNeeded {
   108  		return nil
   109  	}
   110  
   111  	appHash, err := p.committer.Commit(nil)
   112  	if err != nil {
   113  		return fmt.Errorf("%s could not Commit tx %v", errHeader, err)
   114  	}
   115  
   116  	// Maintain a basic hashed linked list, mixing in the appHash as we go
   117  	hasher := sha256.New()
   118  	hasher.Write(appHash)
   119  	hasher.Write(p.blockchain.LastBlockHash())
   120  
   121  	err = p.blockchain.CommitBlock(time.Now(), hasher.Sum(nil), appHash)
   122  	if err != nil {
   123  		return fmt.Errorf("%s could not CommitBlock %v", errHeader, err)
   124  	}
   125  	p.commitNeeded = false
   126  	return nil
   127  }