github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/app/rpc/namespaces/eth/tx_pool.go (about) 1 package eth 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "strconv" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/ethereum/go-ethereum/common" 12 rpctypes "github.com/fibonacci-chain/fbc/app/rpc/types" 13 ethermint "github.com/fibonacci-chain/fbc/app/types" 14 clientcontext "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/context" 15 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 16 sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors" 17 authclient "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/client/utils" 18 authtypes "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/types" 19 "github.com/fibonacci-chain/fbc/libs/tendermint/libs/log" 20 "github.com/fibonacci-chain/fbc/libs/tendermint/types" 21 tmdb "github.com/fibonacci-chain/fbc/libs/tm-db" 22 evmtypes "github.com/fibonacci-chain/fbc/x/evm/types" 23 "github.com/spf13/viper" 24 ) 25 26 const ( 27 FlagEnableTxPool = "enable-tx-pool" 28 TxPoolCap = "tx-pool-cap" 29 BroadcastPeriodSecond = "broadcast-period-second" 30 txPoolDb = "tx_pool" 31 ) 32 33 var broadcastErrors = map[uint32]*sdkerrors.Error{ 34 sdkerrors.ErrTxInMempoolCache.ABCICode(): sdkerrors.ErrTxInMempoolCache, 35 sdkerrors.ErrMempoolIsFull.ABCICode(): sdkerrors.ErrMempoolIsFull, 36 sdkerrors.ErrTxTooLarge.ABCICode(): sdkerrors.ErrTxTooLarge, 37 sdkerrors.ErrInvalidSequence.ABCICode(): sdkerrors.ErrInvalidSequence, 38 } 39 40 type TxPool struct { 41 addressTxsPool map[common.Address][]*evmtypes.MsgEthereumTx // All currently processable transactions 42 clientCtx clientcontext.CLIContext 43 db tmdb.DB 44 mu sync.Mutex 45 cap uint64 46 broadcastInterval time.Duration 47 logger log.Logger 48 } 49 50 func NewTxPool(clientCtx clientcontext.CLIContext, api *PublicEthereumAPI) *TxPool { 51 db, err := openDB() 52 if err != nil { 53 panic(err) 54 } 55 interval := time.Second * time.Duration(viper.GetInt(BroadcastPeriodSecond)) 56 pool := &TxPool{ 57 addressTxsPool: make(map[common.Address][]*evmtypes.MsgEthereumTx), 58 clientCtx: clientCtx, 59 db: db, 60 cap: viper.GetUint64(TxPoolCap), 61 broadcastInterval: interval, 62 logger: api.logger.With("module", "tx_pool", "namespace", "eth"), 63 } 64 65 if err = pool.initDB(api); err != nil { 66 panic(err) 67 } 68 69 return pool 70 } 71 72 func openDB() (tmdb.DB, error) { 73 rootDir := viper.GetString("home") 74 dataDir := filepath.Join(rootDir, "data") 75 return sdk.NewDB(txPoolDb, dataDir) 76 } 77 78 func (pool *TxPool) initDB(api *PublicEthereumAPI) error { 79 itr, err := pool.db.Iterator(nil, nil) 80 if err != nil { 81 return err 82 } 83 defer itr.Close() 84 for ; itr.Valid(); itr.Next() { 85 key := string(itr.Key()) 86 txBytes := itr.Value() 87 88 tmp := strings.Split(key, "|") 89 if len(tmp) != 2 { 90 continue 91 } 92 address := common.HexToAddress(tmp[0]) 93 txNonce, err := strconv.Atoi(tmp[1]) 94 if err != nil { 95 return err 96 } 97 98 tx := new(evmtypes.MsgEthereumTx) 99 if err = authtypes.EthereumTxDecode(txBytes, tx); err != nil { 100 return err 101 } 102 if int(tx.Data.AccountNonce) != txNonce { 103 return fmt.Errorf("nonce[%d] in key is not equal to nonce[%d] in value", tx.Data.AccountNonce, txNonce) 104 } 105 blockNrOrHash := rpctypes.BlockNumberOrHashWithNumber(rpctypes.PendingBlockNumber) 106 pCurrentNonce, err := api.GetTransactionCount(address, blockNrOrHash) 107 if err != nil { 108 return err 109 } 110 currentNonce := int(*pCurrentNonce) 111 if txNonce < currentNonce { 112 continue 113 } 114 115 if err = pool.insertTx(address, tx); err != nil { 116 return err 117 } 118 } 119 120 return nil 121 } 122 123 func broadcastTxByTxPool(api *PublicEthereumAPI, tx *evmtypes.MsgEthereumTx, txBytes []byte) (common.Hash, error) { 124 //TODO: to delete after venus height 125 lastHeight, err := api.clientCtx.Client.LatestBlockNumber() 126 if err != nil { 127 return common.Hash{}, err 128 } 129 // Get sender address 130 chainIDEpoch, err := ethermint.ParseChainID(api.clientCtx.ChainID) 131 if err != nil { 132 return common.Hash{}, err 133 } 134 err = tx.VerifySig(chainIDEpoch, api.clientCtx.Height) 135 if err != nil { 136 return common.Hash{}, err 137 } 138 139 txHash := common.BytesToHash(types.Tx(txBytes).Hash(lastHeight)) 140 tx.Data.Hash = &txHash 141 from := common.HexToAddress(tx.GetFrom()) 142 api.txPool.mu.Lock() 143 defer api.txPool.mu.Unlock() 144 if err = api.txPool.CacheAndBroadcastTx(api, from, tx); err != nil { 145 api.txPool.logger.Error("eth_sendRawTransaction txPool err:", err.Error()) 146 return common.Hash{}, err 147 } 148 149 return txHash, nil 150 } 151 152 func (pool *TxPool) CacheAndBroadcastTx(api *PublicEthereumAPI, address common.Address, tx *evmtypes.MsgEthereumTx) error { 153 // get currentNonce 154 blockNrOrHash := rpctypes.BlockNumberOrHashWithNumber(rpctypes.PendingBlockNumber) 155 pCurrentNonce, err := api.GetTransactionCount(address, blockNrOrHash) 156 if err != nil { 157 return err 158 } 159 currentNonce := uint64(*pCurrentNonce) 160 161 if tx.Data.AccountNonce < currentNonce { 162 return fmt.Errorf("AccountNonce of tx is less than currentNonce in memPool: AccountNonce[%d], currentNonce[%d]", tx.Data.AccountNonce, currentNonce) 163 } 164 165 if tx.Data.AccountNonce > currentNonce+pool.cap { 166 return fmt.Errorf("AccountNonce of tx is bigger than txPool capacity, please try later: AccountNonce[%d]", tx.Data.AccountNonce) 167 } 168 169 if err = pool.insertTx(address, tx); err != nil { 170 return err 171 } 172 173 // update DB 174 if err = pool.writeTxInDB(address, tx); err != nil { 175 return err 176 } 177 178 _ = pool.continueBroadcast(api, currentNonce, address) 179 180 return nil 181 } 182 183 func (pool *TxPool) update(index int, address common.Address, tx *evmtypes.MsgEthereumTx) error { 184 txsLen := len(pool.addressTxsPool[address]) 185 if index >= txsLen { 186 pool.addressTxsPool[address] = append(pool.addressTxsPool[address], tx) 187 } else { 188 tmpTx := make([]*evmtypes.MsgEthereumTx, len(pool.addressTxsPool[address][index:])) 189 copy(tmpTx, pool.addressTxsPool[address][index:]) 190 191 pool.addressTxsPool[address] = 192 append(append(pool.addressTxsPool[address][:index], tx), tmpTx...) 193 } 194 return nil 195 } 196 197 // insert the tx into the txPool 198 func (pool *TxPool) insertTx(address common.Address, tx *evmtypes.MsgEthereumTx) error { 199 // if this is the first time to insertTx, make the cap of txPool be TxPoolSliceMaxLen 200 if _, ok := pool.addressTxsPool[address]; !ok { 201 pool.addressTxsPool[address] = make([]*evmtypes.MsgEthereumTx, 0, pool.cap) 202 } 203 index := 0 204 for index < len(pool.addressTxsPool[address]) { 205 // the tx nonce has in txPool, drop duplicate tx 206 if tx.Data.AccountNonce == pool.addressTxsPool[address][index].Data.AccountNonce { 207 return fmt.Errorf("duplicate tx, this AccountNonce of tx has been in txPool. AccountNonce[%d]", tx.Data.AccountNonce) 208 } 209 // find the index to insert 210 if tx.Data.AccountNonce < pool.addressTxsPool[address][index].Data.AccountNonce { 211 break 212 } 213 index++ 214 } 215 // update txPool 216 return pool.update(index, address, tx) 217 } 218 219 // iterate through the txPool map, check if need to continue broadcast tx and do it 220 func (pool *TxPool) continueBroadcast(api *PublicEthereumAPI, currentNonce uint64, address common.Address) error { 221 i := 0 222 txsLen := len(pool.addressTxsPool[address]) 223 var err error 224 for ; i < txsLen; i++ { 225 if pool.addressTxsPool[address][i].Data.AccountNonce == currentNonce { 226 // do broadcast 227 if err = pool.broadcast(pool.addressTxsPool[address][i]); err != nil { 228 pool.logger.Error(err.Error()) 229 // delete the tx when broadcast failed 230 if err := pool.delTxInDB(address, pool.addressTxsPool[address][i].Data.AccountNonce); err != nil { 231 pool.logger.Error(err.Error()) 232 } 233 break 234 } 235 // update currentNonce 236 currentNonce++ 237 } else if pool.addressTxsPool[address][i].Data.AccountNonce < currentNonce { 238 continue 239 } else { 240 break 241 } 242 } 243 // i is the start index of txs that don't need to be dropped 244 if err != nil { 245 if !strings.Contains(err.Error(), sdkerrors.ErrMempoolIsFull.Error()) && 246 !strings.Contains(err.Error(), sdkerrors.ErrInvalidSequence.Error()) { 247 // tx has err, and err is not mempoolfull, the tx should be dropped 248 err = fmt.Errorf("broadcast failed and tx dropped. err:%s; nonce:%d; tx_hash:%s; address:%s\n", 249 err.Error(), pool.addressTxsPool[address][i].Data.AccountNonce, pool.addressTxsPool[address][i].Data.Hash.String(), address.String()) 250 pool.dropTxs(i+1, address) 251 } else { 252 err = fmt.Errorf("broadcast failed. err:%s; nonce:%d; tx_hash:%s; address:%s\n", 253 err.Error(), pool.addressTxsPool[address][i].Data.AccountNonce, pool.addressTxsPool[address][i].Data.Hash.String(), address.String()) 254 pool.dropTxs(i, address) 255 } 256 pool.logger.Error(err.Error()) 257 } else { 258 pool.dropTxs(i, address) 259 } 260 261 return err 262 } 263 264 // drop [0:index) txs in txpool 265 func (pool *TxPool) dropTxs(index int, address common.Address) { 266 tmp := make([]*evmtypes.MsgEthereumTx, len(pool.addressTxsPool[address][index:]), pool.cap) 267 copy(tmp, pool.addressTxsPool[address][index:]) 268 pool.addressTxsPool[address] = tmp 269 } 270 271 func (pool *TxPool) broadcast(tx *evmtypes.MsgEthereumTx) error { 272 // TODO: to delete after venus height 273 lastHeight, err := pool.clientCtx.Client.LatestBlockNumber() 274 if err != nil { 275 return err 276 } 277 var txEncoder sdk.TxEncoder 278 if types.HigherThanVenus(lastHeight) { 279 txEncoder = authclient.GetTxEncoder(nil, authclient.WithEthereumTx()) 280 } else { 281 txEncoder = authclient.GetTxEncoder(pool.clientCtx.Codec) 282 } 283 284 txBytes, err := txEncoder(tx) 285 if err != nil { 286 return err 287 } 288 res, err := pool.clientCtx.BroadcastTx(txBytes) 289 if err != nil { 290 pool.logger.Error(err.Error()) 291 } 292 if res.Code != sdk.CodeOK { 293 if broadcastErrors[res.Code] == nil { 294 return fmt.Errorf("broadcast tx failed, code: %d, rawLog: %s", res.Code, res.RawLog) 295 } else { 296 return fmt.Errorf("broadcast tx failed, err: %s", broadcastErrors[res.Code].Error()) 297 } 298 } 299 return nil 300 } 301 302 func (pool *TxPool) writeTxInDB(address common.Address, tx *evmtypes.MsgEthereumTx) error { 303 key := []byte(address.Hex() + "|" + strconv.Itoa(int(tx.Data.AccountNonce))) 304 305 txEncoder := authclient.GetTxEncoder(nil, authclient.WithEthereumTx()) 306 // Encode transaction by RLP encoder 307 txBytes, err := txEncoder(tx) 308 if err != nil { 309 return err 310 } 311 312 ok, err := pool.db.Has(key) 313 if err != nil { 314 return err 315 } 316 if ok { 317 return fmt.Errorf("this AccountNonce of tx has been in DB. AccountNonce[%d]", tx.Data.AccountNonce) 318 } 319 320 return pool.db.Set(key, txBytes) 321 } 322 323 func (pool *TxPool) delTxInDB(address common.Address, txNonce uint64) error { 324 key := []byte(address.Hex() + "|" + strconv.Itoa(int(txNonce))) 325 ok, err := pool.db.Has(key) 326 if err != nil { 327 return err 328 } 329 if !ok { 330 return fmt.Errorf("this AccontNonce is not found in DB. AccountNonce[%d]", txNonce) 331 } 332 333 return pool.db.Delete(key) 334 } 335 336 func (pool *TxPool) broadcastPeriod(api *PublicEthereumAPI) { 337 for { 338 time.Sleep(pool.broadcastInterval) 339 pool.broadcastPeriodCore(api) 340 } 341 } 342 func (pool *TxPool) broadcastPeriodCore(api *PublicEthereumAPI) { 343 pool.mu.Lock() 344 defer pool.mu.Unlock() 345 blockNrOrHash := rpctypes.BlockNumberOrHashWithNumber(rpctypes.PendingBlockNumber) 346 for address, _ := range pool.addressTxsPool { 347 pCurrentNonce, err := api.GetTransactionCount(address, blockNrOrHash) 348 if err != nil { 349 pool.logger.Error(err.Error()) 350 continue 351 } 352 currentNonce := uint64(*pCurrentNonce) 353 354 pool.continueBroadcast(api, currentNonce, address) 355 } 356 } 357 358 func (pool *TxPool) broadcastOnce(api *PublicEthereumAPI) { 359 pool.mu.Lock() 360 defer pool.mu.Unlock() 361 blockNrOrHash := rpctypes.BlockNumberOrHashWithNumber(rpctypes.PendingBlockNumber) 362 for address, _ := range pool.addressTxsPool { 363 pCurrentNonce, err := api.GetTransactionCount(address, blockNrOrHash) 364 if err != nil { 365 continue 366 } 367 currentNonce := uint64(*pCurrentNonce) 368 369 pool.continueBroadcast(api, currentNonce, address) 370 } 371 }