diff --git a/cmd/txpool/main.go b/cmd/txpool/main.go index 27894b1d3ad..c65efca683d 100644 --- a/cmd/txpool/main.go +++ b/cmd/txpool/main.go @@ -20,6 +20,7 @@ import ( "github.com/gateway-fm/cdk-erigon-lib/kv/remotedbserver" "github.com/gateway-fm/cdk-erigon-lib/txpool/txpoolcfg" "github.com/gateway-fm/cdk-erigon-lib/types" + jsoniter "github.com/json-iterator/go" "github.com/ledgerwatch/erigon/cmd/rpcdaemon/rpcdaemontest" common2 "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/eth/ethconfig" @@ -66,6 +67,8 @@ var ( freeGasExAddrs []string freeGasCountPerAddr uint64 freeGasLimit uint64 + enableFreeGasList bool + freeGasList string ) func init() { @@ -99,6 +102,8 @@ func init() { rootCmd.Flags().StringSliceVar(&freeGasExAddrs, utils.TxPoolFreeGasExAddrs.Name, ethconfig.DeprecatedDefaultTxPoolConfig.FreeGasExAddrs, utils.TxPoolFreeGasExAddrs.Usage) rootCmd.PersistentFlags().Uint64Var(&freeGasCountPerAddr, utils.TxPoolFreeGasCountPerAddr.Name, ethconfig.DeprecatedDefaultTxPoolConfig.FreeGasCountPerAddr, utils.TxPoolFreeGasCountPerAddr.Usage) rootCmd.PersistentFlags().Uint64Var(&freeGasLimit, utils.TxPoolFreeGasLimit.Name, ethconfig.DeprecatedDefaultTxPoolConfig.FreeGasLimit, utils.TxPoolFreeGasLimit.Usage) + rootCmd.Flags().BoolVar(&enableFreeGasList, utils.TxPoolEnableFreeGasList.Name, ethconfig.DeprecatedDefaultTxPoolConfig.EnableFreeGasList, utils.TxPoolEnableFreeGasList.Usage) + rootCmd.PersistentFlags().StringVar(&freeGasList, utils.TxPoolFreeGasList.Name, "", utils.TxPoolFreeGasList.Usage) } var rootCmd = &cobra.Command{ @@ -203,6 +208,12 @@ func doTxpool(ctx context.Context) error { } ethCfg.DeprecatedTxPool.FreeGasCountPerAddr = freeGasCountPerAddr ethCfg.DeprecatedTxPool.FreeGasLimit = freeGasLimit + ethCfg.DeprecatedTxPool.EnableFreeGasList = enableFreeGasList + if len(freeGasList) > 0 { + if err := jsoniter.UnmarshalFromString(freeGasList, ðCfg.DeprecatedTxPool.FreeGasList); err != nil { + panic("unable to unmarshal freeGasList:" + err.Error()) + } + } newTxs := make(chan types.Announcements, 1024) defer close(newTxs) diff --git a/cmd/utils/flags_xlayer.go b/cmd/utils/flags_xlayer.go index 5424b21bdcf..9aecd84c9a0 100644 --- a/cmd/utils/flags_xlayer.go +++ b/cmd/utils/flags_xlayer.go @@ -2,6 +2,7 @@ package utils import ( "fmt" + jsoniter "github.com/json-iterator/go" "math/big" "time" @@ -94,6 +95,14 @@ var ( Name: "txpool.freegaslimit", Usage: "FreeGasLimit is the max gas allowed use to do a free gas tx", } + TxPoolEnableFreeGasList = cli.BoolFlag{ + Name: "txpool.enablefreegaslist", + Usage: "Enable or disable free gas for a special project", + } + TxPoolFreeGasList = cli.StringFlag{ + Name: "txpool.freegaslist", + Usage: "FreeGasList is the special project of XLayer. Use json string", + } // Gas Pricer GpoTypeFlag = cli.StringFlag{ Name: "gpo.type", @@ -308,6 +317,17 @@ func setTxPoolXLayer(ctx *cli.Context, cfg *ethconfig.DeprecatedTxPoolConfig) { if ctx.IsSet(TxPoolFreeGasLimit.Name) { cfg.FreeGasLimit = ctx.Uint64(TxPoolFreeGasLimit.Name) } + if ctx.IsSet(TxPoolEnableFreeGasList.Name) { + cfg.EnableFreeGasList = ctx.Bool(TxPoolEnableFreeGasList.Name) + } + if ctx.IsSet(TxPoolFreeGasList.Name) { + freeGasListStr := ctx.String(TxPoolFreeGasList.Name) + if len(freeGasListStr) > 0 { + if err := jsoniter.UnmarshalFromString(freeGasListStr, &cfg.FreeGasList); err != nil { + panic("unable to unmarshal freeGasList:" + err.Error()) + } + } + } } // SetApolloGPOXLayer is a public wrapper function to internally call setGPO diff --git a/eth/ethconfig/tx_pool.go b/eth/ethconfig/tx_pool.go index 4f2f505043a..990936942e8 100644 --- a/eth/ethconfig/tx_pool.go +++ b/eth/ethconfig/tx_pool.go @@ -63,6 +63,19 @@ type DeprecatedTxPoolConfig struct { FreeGasCountPerAddr uint64 // FreeGasLimit is the max gas allowed use to do a free gas tx FreeGasLimit uint64 + // EnableFreeGasList enable the special project of XLayer for free gas + EnableFreeGasList bool + // FreeGasList is the special project of XLayer + FreeGasList []FreeGasInfo +} + +// FreeGasInfo contains the details for what tx should be free +type FreeGasInfo struct { + Name string `json:"name"` + FromList []string `json:"from_list"` + ToList []string `json:"to_list"` + MethodSigs []string `json:"method_sigs"` + GasPriceMultiple uint64 `json:"gas_price_multiple"` } // DeprecatedDefaultTxPoolConfig contains the default configurations for the transaction @@ -89,6 +102,7 @@ var DeprecatedDefaultTxPoolConfig = DeprecatedTxPoolConfig{ FreeGasExAddrs: []string{}, FreeGasCountPerAddr: 3, FreeGasLimit: 21000, + EnableFreeGasList: false, } var DefaultTxPool2Config = func(pool1Cfg DeprecatedTxPoolConfig) txpoolcfg.Config { diff --git a/test/config/test.erigon.seq.config.yaml b/test/config/test.erigon.seq.config.yaml index 20acb38cb01..75b613154fa 100644 --- a/test/config/test.erigon.seq.config.yaml +++ b/test/config/test.erigon.seq.config.yaml @@ -71,6 +71,8 @@ txpool.gaspricemultiple : 2 txpool.blockedlist: ["0xdD2FD4581271e230360230F9337D5c0430Bf44C0"] txpool.enablefreegasbynonce : true txpool.freegascountperaddr : 100 +txpool.enablefreegaslist : true +txpool.freegaslist : '[{"name":"test", "from_list":["0xaaa"], "to_list":[], "method_sigs":[], "gas_price_multiple": 1}]' gpo.type: "follower" gpo.update-period: "2s" @@ -88,4 +90,4 @@ networkid: 195 pprof: true pprof.port: 6060 -pprof.addr: 127.0.0.1 \ No newline at end of file +pprof.addr: 127.0.0.1 diff --git a/turbo/cli/default_flags.go b/turbo/cli/default_flags.go index da8813aba12..d2f5fb10f99 100644 --- a/turbo/cli/default_flags.go +++ b/turbo/cli/default_flags.go @@ -269,4 +269,6 @@ var DefaultFlags = []cli.Flag{ &utils.TxPoolFreeGasCountPerAddr, &utils.TxPoolFreeGasExAddrs, &utils.TxPoolFreeGasLimit, + &utils.TxPoolEnableFreeGasList, + &utils.TxPoolFreeGasList, } diff --git a/zk/apollo/pool.go b/zk/apollo/pool.go index 6d22950f306..61e1fc33421 100644 --- a/zk/apollo/pool.go +++ b/zk/apollo/pool.go @@ -126,3 +126,13 @@ func (cfg *ApolloConfig) CheckFreeGasExAddr(localFreeGasExAddrs []string, addr l } return containsAddress(localFreeGasExAddrs, addr) } + +func (cfg *ApolloConfig) GetEnableFreeGasList(localEnableFreeGasList bool) bool { + cfg.RLock() + defer cfg.RUnlock() + + if cfg.isPoolEnabled() { + return cfg.EthCfg.DeprecatedTxPool.EnableFreeGasList + } + return localEnableFreeGasList +} diff --git a/zk/txpool/pool.go b/zk/txpool/pool.go index 1920e53c4fb..677386bdd9e 100644 --- a/zk/txpool/pool.go +++ b/zk/txpool/pool.go @@ -369,7 +369,8 @@ func New(newTxs chan types.Announcements, coreDB kv.RoDB, cfg txpoolcfg.Config, for _, sender := range cfg.TracedSenders { tracedSenders[common.BytesToAddress([]byte(sender))] = struct{}{} } - return &TxPool{ + + tp := &TxPool{ lock: &sync.Mutex{}, byHash: map[string]*metaTx{}, isLocalLRU: localsHistory, @@ -404,9 +405,24 @@ func New(newTxs chan types.Announcements, coreDB kv.RoDB, cfg txpoolcfg.Config, FreeGasExAddrs: ethCfg.DeprecatedTxPool.FreeGasExAddrs, FreeGasCountPerAddr: ethCfg.DeprecatedTxPool.FreeGasCountPerAddr, FreeGasLimit: ethCfg.DeprecatedTxPool.FreeGasLimit, + EnableFreeGasList: ethCfg.DeprecatedTxPool.EnableFreeGasList, }, freeGasAddrs: map[string]bool{}, - }, nil + } + tp.setFreeGasList(ethCfg.DeprecatedTxPool.FreeGasList) + return tp, nil +} + +func (p *TxPool) setFreeGasList(freeGasList []ethconfig.FreeGasInfo) { + p.xlayerCfg.FreeGasFromNameMap = make(map[string]string) + p.xlayerCfg.FreeGasList = make(map[string]*ethconfig.FreeGasInfo, len(freeGasList)) + for _, info := range freeGasList { + for _, from := range info.FromList { + p.xlayerCfg.FreeGasFromNameMap[from] = info.Name + } + infoCopy := info + p.xlayerCfg.FreeGasList[info.Name] = &infoCopy + } } func (p *TxPool) OnNewBlock(ctx context.Context, stateChanges *remote.StateChangeBatch, unwindTxs, minedTxs types.TxSlots, tx kv.Tx) error { @@ -745,7 +761,7 @@ func (p *TxPool) validateTx(txn *types.TxSlot, isLocal bool, stateCache kvcache. if p.gpCache != nil { rgp = p.gpCache.GetLatestRawGP() } - if !p.isFreeGasXLayer(txn.SenderID) && uint256.NewInt(rgp.Uint64()).Cmp(&txn.FeeCap) == 1 { + if !p.isFreeGasXLayer(txn.SenderID, txn) && uint256.NewInt(rgp.Uint64()).Cmp(&txn.FeeCap) == 1 { if txn.Traced { log.Info(fmt.Sprintf("TX TRACING: validateTx underpriced idHash=%x local=%t, feeCap=%d, cfg.MinFeeCap=%d", txn.IDHash, isLocal, txn.FeeCap, p.cfg.MinFeeCap)) } diff --git a/zk/txpool/pool_xlayer.go b/zk/txpool/pool_xlayer.go index 339c3bca315..f4794dcc27d 100644 --- a/zk/txpool/pool_xlayer.go +++ b/zk/txpool/pool_xlayer.go @@ -2,8 +2,21 @@ package txpool import ( "math/big" + "strings" "github.com/gateway-fm/cdk-erigon-lib/common" + "github.com/gateway-fm/cdk-erigon-lib/types" + ecommon "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/eth/ethconfig" + "github.com/ledgerwatch/erigon/zkevm/hex" +) + +// free gas tx type +const ( + notFree = iota + claim + freeByNonce + specialProject ) // XLayerConfig contains the X Layer configs for the txpool @@ -26,6 +39,11 @@ type XLayerConfig struct { FreeGasCountPerAddr uint64 // FreeGasLimit is the max gas allowed use to do a free gas tx FreeGasLimit uint64 + // For special project + // EnableFreeGasList enable the special project of XLayer for free gas + EnableFreeGasList bool + FreeGasFromNameMap map[string]string // map[from]projectName + FreeGasList map[string]*ethconfig.FreeGasInfo // map[projectName]FreeGasInfo } type GPCache interface { @@ -44,6 +62,25 @@ type ApolloConfig interface { CheckWhitelistAddr(localWhitelist []string, addr common.Address) bool CheckFreeClaimAddr(localFreeClaimGasAddrs []string, addr common.Address) bool CheckFreeGasExAddr(localFreeGasExAddrs []string, addr common.Address) bool + GetEnableFreeGasList(localEnableFreeGasList bool) bool +} + +func contains(addresses []string, addr common.Address) bool { + for _, item := range addresses { + if common.HexToAddress(item) == addr { + return true + } + } + return false +} + +func containsMethod(data string, methods []string) bool { + for _, m := range methods { + if strings.HasPrefix(data, m) { + return true + } + } + return false } // SetApolloConfig sets the apollo config with the node's apollo config @@ -64,20 +101,60 @@ func (p *TxPool) checkFreeGasExAddrXLayer(senderID uint64) bool { return p.apolloCfg.CheckFreeGasExAddr(p.xlayerCfg.FreeGasExAddrs, addr) } -func (p *TxPool) checkFreeGasAddrXLayer(senderID uint64) (bool, bool) { +func (p *TxPool) checkFreeGasAddrXLayer(senderID uint64, tx *types.TxSlot) (freeType int, gpMul uint64) { addr, ok := p.senders.senderID2Addr[senderID] if !ok { - return false, false + return } + // is claim tx if p.apolloCfg.CheckFreeClaimAddr(p.xlayerCfg.FreeClaimGasAddrs, addr) { - return true, true + return claim, p.xlayerCfg.GasPriceMultiple } + + // special project + if p.apolloCfg.GetEnableFreeGasList(p.xlayerCfg.EnableFreeGasList) { + fromToName, freeGpList := p.xlayerCfg.FreeGasFromNameMap, p.xlayerCfg.FreeGasList + info := freeGpList[fromToName[addr.String()]] + if info != nil && + contains(info.ToList, tx.To) && + containsMethod("0x"+ecommon.Bytes2Hex(tx.Rlp), info.MethodSigs) { + return specialProject, info.GasPriceMultiple + } + } + + // new bridge address free := p.freeGasAddrs[addr.String()] - return free, false + if free { + return freeByNonce, 1 + } + + return notFree, 0 +} + +func (p *TxPool) setFreeGasByNonceCache(senderID uint64, mt *metaTx, isClaim bool) { + if p.xlayerCfg.EnableFreeGasByNonce { + if p.checkFreeGasExAddrXLayer(senderID) { + inputHex := hex.EncodeToHex(mt.Tx.Rlp) + if strings.HasPrefix(inputHex, "0xa9059cbb") && len(inputHex) > 74 { + addrHex := "0x" + inputHex[10:74] + p.freeGasAddrs[addrHex] = true + } else { + p.freeGasAddrs[mt.Tx.To.Hex()] = true + } + } else if isClaim && mt.Tx.Nonce < p.xlayerCfg.FreeGasCountPerAddr { + inputHex := hex.EncodeToHex(mt.Tx.Rlp) + if len(inputHex) > 4554 { + addrHex := "0x" + inputHex[4490:4554] + p.freeGasAddrs[addrHex] = true + } else { + p.freeGasAddrs[mt.Tx.To.Hex()] = true + } + } + } } -func (p *TxPool) isFreeGasXLayer(senderID uint64) bool { - free, _ := p.checkFreeGasAddrXLayer(senderID) - return free +func (p *TxPool) isFreeGasXLayer(senderID uint64, tx *types.TxSlot) bool { + freeType, _ := p.checkFreeGasAddrXLayer(senderID, tx) + return freeType > notFree } diff --git a/zk/txpool/pool_zk.go b/zk/txpool/pool_zk.go index b29f08925c8..3c20ddcd696 100644 --- a/zk/txpool/pool_zk.go +++ b/zk/txpool/pool_zk.go @@ -4,9 +4,7 @@ import ( "bytes" "context" "fmt" - "github.com/ledgerwatch/erigon/zkevm/log" "math/big" - "strings" mapset "github.com/deckarep/golang-set/v2" "github.com/gateway-fm/cdk-erigon-lib/common" @@ -18,7 +16,7 @@ import ( "github.com/holiman/uint256" "github.com/ledgerwatch/erigon/common/math" "github.com/ledgerwatch/erigon/zk/utils" - "github.com/ledgerwatch/erigon/zkevm/hex" + "github.com/ledgerwatch/erigon/zkevm/log" ) /* @@ -45,8 +43,6 @@ func (p *TxPool) onSenderStateChange(senderID uint64, senderNonce uint64, sender minFeeCap := uint256.NewInt(0).SetAllOne() minTip := uint64(math.MaxUint64) var toDel []*metaTx // can't delete items while iterate them - // For X Layer - isfreeGasAddr, claim := p.checkFreeGasAddrXLayer(senderID) byNonce.ascend(senderID, func(mt *metaTx) bool { if mt.Tx.Traced { log.Info(fmt.Sprintf("TX TRACING: onSenderStateChange loop iteration idHash=%x senderID=%d, senderNonce=%d, txn.nonce=%d, currentSubPool=%s", mt.Tx.IDHash, senderID, senderNonce, mt.Tx.Nonce, mt.currentSubPool)) @@ -69,27 +65,6 @@ func (p *TxPool) onSenderStateChange(senderID uint64, senderNonce uint64, sender toDel = append(toDel, mt) return true } - // For X Layer - // parse claim tx or dex tx, and add the withdraw addr into free gas cache - if p.xlayerCfg.EnableFreeGasByNonce { - if p.checkFreeGasExAddrXLayer(senderID) { - inputHex := hex.EncodeToHex(mt.Tx.Rlp) - if strings.HasPrefix(inputHex, "0xa9059cbb") && len(inputHex) > 74 { - addrHex := "0x" + inputHex[10:74] - p.freeGasAddrs[addrHex] = true - } else { - p.freeGasAddrs[mt.Tx.To.Hex()] = true - } - } else if claim && mt.Tx.Nonce < p.xlayerCfg.FreeGasCountPerAddr { - inputHex := hex.EncodeToHex(mt.Tx.Rlp) - if len(inputHex) > 4554 { - addrHex := "0x" + inputHex[4490:4554] - p.freeGasAddrs[addrHex] = true - } else { - p.freeGasAddrs[mt.Tx.To.Hex()] = true - } - } - } if minFeeCap.Gt(&mt.Tx.FeeCap) { *minFeeCap = mt.Tx.FeeCap @@ -101,9 +76,15 @@ func (p *TxPool) onSenderStateChange(senderID uint64, senderNonce uint64, sender mt.minTip = minTip // For X Layer - // free case: 1. is claim tx; 2. new bridge account with the first few tx - if claim || - (p.xlayerCfg.EnableFreeGasByNonce && isfreeGasAddr && mt.Tx.Nonce < p.xlayerCfg.FreeGasCountPerAddr) { + // free type: + // 0. not free + // 1. is claim tx; + // 2. new bridge account with the first few tx + // 3. special project + freeType, gpMul := p.checkFreeGasAddrXLayer(senderID, mt.Tx) + // parse claim tx or dex tx, and add the withdraw addr into free gas cache + p.setFreeGasByNonceCache(senderID, mt, freeType == claim) + if freeType > notFree { // get dynamic gp // here for the case when restart gpCache has not init // use the max uint64 as default because the remain claimTx should handle first @@ -111,14 +92,9 @@ func (p *TxPool) onSenderStateChange(senderID uint64, senderNonce uint64, sender if p.gpCache != nil { _, dGp := p.gpCache.GetLatest() if dGp != nil { - newGpBig.Set(dGp) - if claim { - newGpBig = newGpBig.Mul(newGpBig, big.NewInt(int64(p.xlayerCfg.GasPriceMultiple))) - log.Info(fmt.Sprintf("Free tx: type claim. dGp:%v, factor:%d, newGp:%d", dGp, p.xlayerCfg.GasPriceMultiple, newGpBig)) - } else { - log.Info(fmt.Sprintf("Free tx: type newAddr. nonce:%d, dGp:%v, newGp:%d", mt.Tx.Nonce, dGp, newGpBig)) - } + newGpBig = newGpBig.Mul(dGp, big.NewInt(int64(gpMul))) } + log.Info(fmt.Sprintf("Free tx: nonce:%d, type %d. dGp:%v, factor:%d, newGp:%d", mt.Tx.Nonce, freeType, dGp, gpMul, newGpBig)) } mt.minTip = newGpBig.Uint64()