xmrspammer is an alpha project. Expect breaking changes in future versions.
Installation
Install R. Linux users should install the r-base and r-base-dev system packages.
You can install the development version of xmrspammer from GitHub if you have git and curl installed on your system with:
install.packages("remotes")
remotes::install_github("Rucknium/xmrspammer", upgrade = FALSE)❗❗❗ WARNING: Wallets not secure
xmrspammer is intended to be used with valueless testnet coins. The security of the wallet creation and management processes have not been evaluated. If you use xmrspammer on mainnet, your coins could be stolen.
Design
The Monero protocol has a few restrictions that make transaction spamming more difficult than, say, bitcoin.
First is the 16-output limit. The maximum number of outputs that a Monero transaction can have is 16. A single output cannot be as easily split into a very large number of outputs due to this restriction.
Second is the 10-block lock. Wallets must wait 10 confirmations to spend new outputs.
A third factor in stressnet testing is the need to fill the stressnet txpool to test node performance when the txpool is large. Transactions cannot be chained together in the txpool (i.e. child transactions cannot spend unconfirmed parent outputs). A wallet needs to have many outputs available to spend so that many of its unconfirmed transactions can wait in the txpool.
The design of xmrspammer works around these limitations. Wallets are set up to expand a single output into a “tree”. At the end of the process, a wallet will have many “leaves” that contain a single output within an account. From there, the default spamming procedure iterates through each leaf, creating a self-spend transaction back to each leaf.
xmrspammer creates a user-specified number of wallets and boots a monero-wallet-rpc process for each wallet. A monerod Monero node process must be available on the local machine or you must be able to connect to the RPC port of a Monero node process on another machine. For each wallet, xmrspammer spawns an R process that runs a spamming loop. The loop instructs each monero-wallet-rpc process to create and broadcast transactions as quickly as possible.
Usage
xmrspammer has only been tested on Linux.
Users should view coins put into the spamming wallets as unrecoverable. Large amounts of coins should not be put into single spamming wallets because wallets with hundreds of thousands of transactions become slow to produce new transactions. At that point, the wallets should be abandoned and fresh wallets created. About 60 XMR per spamming wallet is recommended.
Prerequisites
You must have a Monero node running locally or you must be able to connect to the RPC port of a Monero node process on another machine. It is best to enable the restricted RPC port because connections to the unrestricted RPC port experience performance problems when the txpool is large. You can start your Monero node with the standard testnet restricted RPC port by adding
--rpc-restricted-bind-port=28089the flag in yourmonerodstartup command. If you prefer not to enable the restricted RPC port, the unrestricted RPC port for testnet at 28081 is available by default to connections from within your local machine. For a smooth connection experience, also add--rpc-max-connections=1000,--rpc-max-connections-per-public-ip=1000and--rpc-max-connections-per-private-ip=1000as startup flags.You should have a wallet with some testnet XMR. You will send coins from this wallet to the spamming wallets.
You must create a new empty working directory. Place the correct version of
monero-wallet-rpcinto the working directory.
Starting with a small-scale test wallet can help familiarize users with the process and discover bugs early.
Small-scale test
Start a new R instance in a terminal in the working directory. Use screen or another similar program to prevent losing the instance. Once you have installed xmrspammer, load the package into the workspace:
You must specify the monerod RPC port and the number of spamming wallets to be created. In this example we will assume that you have the node’s restricted RPC port available at 28089. We will create one wallet for this test.
wallets <- gen.wallets(monerod.rpc.port = 28089, n.wallets = 1)If you wish to connect to a node on another machine, the format of the monerod.rpc.port argument is "http://IP:port", e.g. "http://xxx.xxx.xxx.xxx:28089". The other machine can be on your local network or on the broader Internet. You must remember to open the appropriate port on the other machine.
A single monero-wallet-rpc process should have launched to manage the single wallet we created. Check that the process is alive:
wallets[[1]]$monero_wallet_rpc_process$is_alive()The console should print TRUE.
Next, we will fund the spamming wallet. fund.wallets() is a convenience function that will print the exact command to input into the monero-wallet-cli of the funding wallet. Input the amount argument to specify the total amount of XMR you will send to the spamming wallet(s).
fund.wallets(wallets, amount = 60)R will print a statement like
Input the statement into monero-wallet-cli and confirm broadcast of the transaction. You will have to wait 10 blocks or about 20 minutes for the outputs of the transaction to be spendable for the next step.
Now we will create the account leaves of the wallet. This test wallet will use three levels of 15-output transactions. The process will create 15^3 = 3375 leaf accounts. Fee priority will be set to 4 for each level. There are five standard fee tiers: 1, 2, 3, 4, and 5. When the txpool is full, a low-fee transaction could take a long time to confirm, slowing down this step in the process. We will choose fee priority 4 for all tree levels so that the accounts are built quickly.
wallets <- prep.leaves.wallets(wallets, n.outputs = 15,
n.tree.levels = 3, fee.priority = c(4, 4, 4))These transactions should take about 60 minutes (30 blocks) to become spendable.
Finally, start the spamming. The next line will initiate an infinite loop. Input ctrl + c to interrupt the spamming loop if desired.
spam.1in.2out.wallets(wallets)You can check the status of the spamming wallets by starting a new R session in a different terminal window (or a different screen session). You must start the R session in the working directory where you started the original R session. It should have some files and subdirectories by now. Remember to load the package into the workspace:
Now load the wallet data into the workspace:
wallets <- readRDS("spam_wallets_data.rds")Get some status information about the wallet:
wallets.status(wallets, get_balance = TRUE)Ignore the message about the RPC process being dead. You may see log messages about insufficient funds for creating new transactions. When the txpool is large, there will be many transactions in the queue in front of yours, so you may have all of your accounts with transactions waiting in the txpool.
Large-scale use
Your computer’s RAM and CPU resources will limit the maximum number of spamming wallets you can have. You would not want to have more spamming wallets than your CPU’s number of threads. The RAM consumed by monero-wallet-rpc is variable. It can occupy 5 GB of RAM per wallet or more on stressnet. Four wallets is a good place to start on a high-resource machine:
wallets <- gen.wallets(monerod.rpc.port = 28089, n.wallets = 4)The wallet funding function divides the amount equally between the wallets. Multiply the standard 60 XMR by the number of wallets:
fund.wallets(wallets, amount = 60*4)After funding the wallets, prep the leaves:
wallets <- prep.leaves.wallets(wallets, n.outputs = 15,
n.tree.levels = 3, fee.priority = c(4, 4, 4))More wallets increases the probability that at least one of them will fail to fully build the tree. Usually, a failure will occur at the last step where a small number of final leaves will fail to be produced. You can check the status of the leaves preparation by inputting wallets.status(wallets) in another R session launched from the wallet’s working directory. If a lot of time passes without prep.leaves.wallets() finishing, e.g. 12 or 24 hours, you can assume that there was a small failure. You can still move forward without a full successful tree build. You must halt the dysfunctional prep.leaves.wallets() process that will never finish by inputting Ctrl + c in the R terminal. Then re-run prep.leaves.wallets(), but skip the steps that actually send the transactions:
wallets <- prep.leaves.wallets(wallets, n.outputs = 15,
n.tree.levels = 3, fee.priority = c(4, 4, 4), send.txs = FALSE)After a few moments, control should return to you and you can start the spamming process with the four wallets:
spam.1in.2out.wallets(wallets)Revive wallets
You may need to close the R session you used to generate the wallets and/or shut down your machine. Or one or more monero-wallet-rpc processes may have spontaneously quit. The revive.wallets() function can bring the wallets back under your control in a new R session.
First, make sure that no other R sessions with the wallets are open.
Open a new R session in the directory where the wallet files are located. Load the package:
Then use the revive.wallets() function to gain control of the wallets:
wallets <- revive.wallets(monerod.rpc.port = 28089)If 28089 is not your monerod’s RPC port, change it.
You can check the wallet status:
wallets.status(wallets, get_balance = TRUE)And start spamming again:
spam.1in.2out.wallets(wallets)revive.wallets() will launch new monero-wallet-rpc processes if the ones you launched before had quit. By default, revive.wallets() will just connect to an existing monero-wallet-rpc process if one is already running. To quit all monero-wallet-rpc processes and restart a fresh monero-wallet-rpc process for every wallet, input this:
wallets <- revive.wallets(monerod.rpc.port = 28089,
restart.alive.processes = TRUE)If you wish to kill all the monero-wallet-rpc processes associated with a set of wallets, input:
kill.wallet.processes(wallets)