github.com/aergoio/aergo@v1.3.1/consensus/chain/tx.go (about) 1 /** 2 * @file 3 * @copyright defined in aergo/LICENSE.txt 4 */ 5 6 package chain 7 8 import ( 9 "errors" 10 "time" 11 12 "github.com/aergoio/aergo-lib/log" 13 "github.com/aergoio/aergo/chain" 14 "github.com/aergoio/aergo/contract" 15 "github.com/aergoio/aergo/internal/enc" 16 "github.com/aergoio/aergo/message" 17 "github.com/aergoio/aergo/pkg/component" 18 "github.com/aergoio/aergo/state" 19 "github.com/aergoio/aergo/types" 20 "github.com/golang/protobuf/proto" 21 ) 22 23 var ( 24 // ErrBestBlock indicates that the best block is being changed in 25 // chainservice soon. 26 ErrBestBlock = errors.New("best block changed in chainservice") 27 28 logger = log.NewLogger("consensus") 29 ) 30 31 // FetchTXs requests to mempool and returns types.Tx array. 32 func FetchTXs(hs component.ICompSyncRequester, maxBlockBodySize uint32) []types.Transaction { 33 //bf.RequestFuture(message.MemPoolSvc, &message.MemPoolGenerateSampleTxs{MaxCount: 3}, time.Second) 34 result, err := hs.RequestFuture(message.MemPoolSvc, 35 &message.MemPoolGet{MaxBlockBodySize: maxBlockBodySize}, time.Second, 36 "consensus/util/info.FetchTXs").Result() 37 if err != nil { 38 logger.Info().Err(err).Msg("can't fetch transactions from mempool") 39 return make([]types.Transaction, 0) 40 } 41 42 return result.(*message.MemPoolGetRsp).Txs 43 } 44 45 // TxOp is an interface used by GatherTXs for apply some transaction related operation. 46 type TxOp interface { 47 Apply(bState *state.BlockState, tx types.Transaction) error 48 } 49 50 // TxOpFn is the type of arguments for CompositeTxDo. 51 type TxOpFn func(bState *state.BlockState, tx types.Transaction) error 52 53 // Apply applies f to tx. 54 func (f TxOpFn) Apply(bState *state.BlockState, tx types.Transaction) error { 55 return f(bState, tx) 56 } 57 58 // NewCompTxOp returns a function which applies each function in fn. 59 func NewCompTxOp(fn ...TxOp) TxOp { 60 return TxOpFn(func(bState *state.BlockState, tx types.Transaction) error { 61 for _, f := range fn { 62 var err error 63 if err = f.Apply(bState, tx); err != nil { 64 return err 65 } 66 } 67 68 // If TxOp executes tx, it has a resulting BlockState. The final 69 // BlockState must be sent to the chain service receiver. 70 return nil 71 }) 72 } 73 74 func newBlockLimitOp(maxBlockBodySize uint32) TxOpFn { 75 // Caution: the closure below captures the local variable 'size.' Generate 76 // it whenever needed. Don't reuse it! 77 size := 0 78 return TxOpFn(func(bState *state.BlockState, tx types.Transaction) error { 79 if size += proto.Size(tx.GetTx()); uint32(size) > maxBlockBodySize { 80 return errBlockSizeLimit 81 } 82 return nil 83 }) 84 } 85 86 // LockChain aquires the chain lock in a non-blocking mode. 87 func LockChain() error { 88 select { 89 case chain.InAddBlock <- struct{}{}: 90 return nil 91 default: 92 return ErrBestBlock 93 } 94 } 95 96 // UnlockChain release the chain lock. 97 func UnlockChain() { 98 <-chain.InAddBlock 99 } 100 101 // GatherTXs returns transactions from txIn. The selection is done by applying 102 // txDo. 103 func GatherTXs(hs component.ICompSyncRequester, bState *state.BlockState, txOp TxOp, maxBlockBodySize uint32) ([]types.Transaction, error) { 104 var ( 105 nCollected int 106 nCand int 107 ) 108 109 if logger.IsDebugEnabled() { 110 logger.Debug().Msg("start gathering tx") 111 } 112 113 if err := LockChain(); err != nil { 114 return nil, ErrBestBlock 115 } 116 defer UnlockChain() 117 118 txIn := FetchTXs(hs, maxBlockBodySize) 119 nCand = len(txIn) 120 if nCand == 0 { 121 return txIn, nil 122 } 123 txRes := make([]types.Transaction, 0, nCand) 124 125 defer func() { 126 logger.Info(). 127 Int("candidates", nCand). 128 Int("collected", nCollected). 129 Msg("transactions collected") 130 contract.CloseDatabase() 131 }() 132 133 134 op := NewCompTxOp(txOp) 135 136 var preLoadTx *types.Tx 137 for i, tx := range txIn { 138 if i != nCand-1 { 139 preLoadTx = txIn[i+1].GetTx() 140 contract.PreLoadRequest(bState, preLoadTx, tx.GetTx(), contract.BlockFactory) 141 } 142 143 err := op.Apply(bState, tx) 144 contract.SetPreloadTx(preLoadTx, contract.BlockFactory) 145 146 //don't include tx that error is occured 147 if e, ok := err.(ErrTimeout); ok { 148 if logger.IsDebugEnabled() { 149 logger.Debug().Msg("stop gathering tx due to time limit") 150 } 151 err = e 152 break 153 } else if err == errBlockSizeLimit { 154 if logger.IsDebugEnabled() { 155 logger.Debug().Msg("stop gathering tx due to size limit") 156 } 157 break 158 } else if err != nil { 159 //FIXME handling system error (panic?) 160 // ex) gas error/nonce error skip, but other system error panic 161 logger.Debug().Err(err).Int("idx", i).Str("hash", enc.ToString(tx.GetHash())).Msg("skip error tx") 162 continue 163 } 164 165 txRes = append(txRes, tx) 166 } 167 168 nCollected = len(txRes) 169 170 if err := chain.SendRewardCoinbase(bState, chain.CoinbaseAccount); err != nil { 171 return nil, err 172 } 173 174 if err := contract.SaveRecoveryPoint(bState); err != nil { 175 return nil, err 176 } 177 178 if err := bState.Update(); err != nil { 179 return nil, err 180 } 181 182 return txRes, nil 183 }