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