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  }