// VulcanizeDB // Copyright © 2020 Vulcanize // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more detailgen. // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. package auto import ( "context" "crypto/ecdsa" "github.com/ethereum/go-ethereum/crypto" log "github.com/sirupsen/logrus" "math/big" "math/rand" "sync" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/vulcanize/tx_spammer/pkg/shared" ) // TxGenerator generates and signs txs type TxGenerator struct { config *Config // keep track of account nonces locally so we aren't spamming to determine the nonce // this assumes these accounts are not sending txs outside this process nonces map[common.Address]uint64 lock sync.Mutex } func (gen *TxGenerator) claimNonce(addr common.Address) uint64 { gen.lock.Lock() ret := gen.nonces[addr] gen.nonces[addr] += 1 gen.lock.Unlock() return ret } // NewTxGenerator creates a new tx generator func NewTxGenerator(config *Config) *TxGenerator { nonces := make(map[common.Address]uint64) for _, addr := range config.SenderAddrs { nonce, _ := config.EthClient.PendingNonceAt(context.Background(), addr) nonces[addr] = nonce } return &TxGenerator{ nonces: nonces, config: config, } } // GenParams params for GenerateTx method calls type GenParams struct { Sender common.Address SenderKey *ecdsa.PrivateKey ChainID *big.Int GasTipCap *big.Int GasFeeCap *big.Int GasLimit uint64 To *common.Address Amount *big.Int Data []byte } func (gen *TxGenerator) GenerateTxs(quitChan <-chan bool) (<-chan bool, <-chan *types.Transaction, <-chan error) { txChan := make(chan *types.Transaction) errChan := make(chan error) wg := new(sync.WaitGroup) for i, sender := range gen.config.SenderKeys { if gen.config.SendConfig.TotalNumber > 0 { wg.Add(1) go gen.genSends(wg, txChan, errChan, quitChan, sender, gen.config.SenderAddrs[i], gen.config.SendConfig) } if gen.config.CallConfig.TotalNumber > 0 { wg.Add(1) go gen.genCalls(wg, txChan, errChan, quitChan, sender, gen.config.SenderAddrs[i], gen.config.CallConfig) } } doneChan := make(chan bool) go func() { wg.Wait() close(doneChan) }() return doneChan, txChan, errChan } func (gen *TxGenerator) genSends(wg *sync.WaitGroup, txChan chan<- *types.Transaction, errChan chan<- error, quitChan <-chan bool, senderKey *ecdsa.PrivateKey, senderAddr common.Address, sendConfig *SendConfig) { defer wg.Done() ticker := time.NewTicker(sendConfig.Frequency) for i := 0; i < sendConfig.TotalNumber; i++ { select { case <-ticker.C: dst := crypto.CreateAddress(receiverAddressSeed, uint64(i)) log.Debugf("Generating send from %s to %s.", senderAddr.Hex(), dst.Hex()) rawTx, _, err := gen.GenerateTx(&GenParams{ ChainID: sendConfig.ChainID, To: &dst, Sender: senderAddr, SenderKey: senderKey, GasLimit: sendConfig.GasLimit, GasFeeCap: sendConfig.GasFeeCap, GasTipCap: sendConfig.GasTipCap, Amount: sendConfig.Amount, }) if err != nil { errChan <- err continue } txChan <- rawTx case <-quitChan: return } } log.Info("Done generating sends for ", senderAddr.Hex()) } func (gen *TxGenerator) genCalls(wg *sync.WaitGroup, txChan chan<- *types.Transaction, errChan chan<- error, quitChan <-chan bool, senderKey *ecdsa.PrivateKey, senderAddr common.Address, callConfig *CallConfig) { defer wg.Done() ticker := time.NewTicker(callConfig.Frequency) for i := 0; i < callConfig.TotalNumber; i++ { select { case <-ticker.C: contractAddr := callConfig.ContractAddrs[rand.Intn(len(callConfig.ContractAddrs))] log.Debugf("Generating call from %s to %s.", senderAddr.Hex(), contractAddr.Hex()) data, err := callConfig.ABI.Pack(callConfig.MethodName, contractAddr, big.NewInt(time.Now().UnixNano())) if err != nil { errChan <- err continue } rawTx, _, err := gen.GenerateTx(&GenParams{ Sender: senderAddr, SenderKey: senderKey, GasLimit: callConfig.GasLimit, GasFeeCap: callConfig.GasFeeCap, GasTipCap: callConfig.GasTipCap, Data: data, To: &contractAddr, }) if err != nil { errChan <- err continue } txChan <- rawTx case <-quitChan: return } } log.Info("Done generating calls for ", senderAddr.Hex()) } // GenerateTx generates tx from the provided params func (gen *TxGenerator) GenerateTx(params *GenParams) (*types.Transaction, common.Address, error) { nonce := gen.claimNonce(params.Sender) tx := new(types.Transaction) var contractAddr common.Address var err error if params.To == nil { tx = types.NewTx( &types.DynamicFeeTx{ ChainID: params.ChainID, Nonce: nonce, Gas: params.GasLimit, GasTipCap: params.GasTipCap, GasFeeCap: params.GasFeeCap, To: nil, Value: params.Amount, Data: params.Data, }) contractAddr, err = shared.WriteContractAddr("", params.Sender, nonce) if err != nil { return nil, common.Address{}, err } } else { tx = types.NewTx( &types.DynamicFeeTx{ ChainID: params.ChainID, Nonce: nonce, GasTipCap: params.GasTipCap, GasFeeCap: params.GasFeeCap, Gas: params.GasLimit, To: params.To, Value: params.Amount, Data: params.Data, }) } signedTx, err := types.SignTx(tx, gen.config.Signer, params.SenderKey) if err != nil { return nil, common.Address{}, err } return signedTx, contractAddr, err }