Recently, I needed to generate many transactions from several machines to simulate a Bitcoin network with very large blocks. Because I created several simple scripts that used bitcoind's "sendtoaddress" API, I started these scripts in an infinite loop to see what would happen. The result is not satisfactory. To understand why this happens, you have to understand the problem of "unconfirmed inputs". Basically, if you use a small portion of the advance payment, then you will generate 2 new outputs - one output to your destination, and one output to yourself (the change). These outputs are then considered "unconfirmed" until a new block is found, and then you can use these unconfirmed outputs again, but Bitcoin client only lets users build a relatively short chain of unconfirmed outputs before it returns a "too many unconfirmed outputs" error. (Translator's note: the original text of the last sentence butBitcoin client only lets you build a relatively shallow chain of unconfirmedoutputs before it returns a "too many unconfirmed outputs" error.) I would be generating over 50,000 transactions from a single wallet per block, so I quickly realized I needed a method similar to confirmed outputs to reliably produce many payments. 100% of the CPU was used, and transaction generation slowed to about 1 transaction per 10 seconds. This is a problem. I made this up artificially, but is this really an unlikely number for a large enterprise? I'm guessing that for large enterprises processing millions of payments per year, reusing addresses is not the answer - the problem is the number of transaction outputs in the wallet, not the number of addresses. So I thought about optimizing the transaction code. Ultimately, I was able to improve the efficiency of generating transactions by about 1000 times, and it helped reduce the growth of UXTO. See below for details! Incremental Optimization The first thing I noticed was that a lot of time was spent in locks. Especially in As mentioned before, I also noticed that GetTimeOffset() consumes a lot of CPU... well... it takes locks again. This time the purpose is to ensure atomic read of 64-bit variables. There are better ways to achieve this on all CPU architectures, so I replaced the lock with atomic 64-bit operations on supported compilers: int64_tGetTimeOffset() { int64_t t1; #ifdef __GNUC__ // BU optimize—doa barrier rather than a lock t1 = __sync_fetch_and_add(&nTimeOffset,0); #else LOCK(cs_nTimeOffset); t1 = nTimeOffset; #endif return t1; } This problem could also be solved by factoring in While these patches are a good introduction to the subsystem, it won't get me to where I want to be. To really fix this, I need to review the big implementation decisions. Optimize implementation The first obvious problem is that the entire wallet is extracted into a vector every time a payment is made (see SelectCoins() calling The second efficiency bottleneck is the code verifying that the wallet contains enough funds to satisfy the payment requirement separately from constructing the transaction. The problem is that in order to do this, the code must count all the outputs in the wallet - so iterates again through the entire wallet's TXO set. In general, if testing an abnormal condition is expensive and will be discovered sooner or later, it is better to appear in the performance bottleneck code later. Another approach is to cache the current balance of the wallet and update it based on the inflow and outflow of funds instead of recalculating it. But again, this approach requires a lot of fragile code to ensure consistency. Finally, the code in Algorithm optimizationThe final performance option in the optimization toolbox is to ask “is this algorithm actually achieving my goal?” The more I thought about it, the more I realized it wasn’t. The algorithm implemented was trying to solve the “knapsack problem” — the main problem is choosing the maximum value of “something” that doesn’t exceed a certain weight. For a Bitcoin wallet, the problem is similar but different: it’s choosing a set of inputs that are larger than the transaction value, but trying to match the values of the inputs and outputs as closely as possible. The “knapsack problem” is very difficult — it’s proven to be NP-hard. However, I realized that Bitcoin had an additional problem that made it worthless — the size of the “knapsack problem” (the transaction value) is based on the transaction fee, and the fee is unknown until the entire transaction is serialized. The end result is that if the optimizer does a "good job" by finding a set of inputs that are close to the output values, then it will not be able to include the transaction fee, so the transaction will be rejected. In this case, the code will calculate the fee and add it to the transaction amount, and try again. However, this does not make any sense. There is no actual relationship between the size of the previous solution and the next solution. Instead, it's just an operation due to the general similarity of transactions - if you know the inputs and outputs in a transaction, you can roughly estimate its size. In fact, in my testing, the backpack optimizer was asked to pay several times almost every time. What is the purpose of optimizing this "knapsack problem"? Who cares if a change of .1 or .0001 is accepted? The penalty for failing to find an exact solution is an additional output change, and the hurdle is exactly the same regardless of what is missed. Yes, an exact match saves the output, but the probability of that being true given a 6-10 digit number (1 Bitcoin is 100,000,000 Satoshi, the count in the code, Satoshi) is tiny. Especially since the transaction fee adds a small non-round-number to each payment, we're really dealing with 6-10 digits, not 3 digits with a lot of zeros at the end. The algorithm doesn't have a lot of flexibility - there's a real hurdle with using large inputs. Finally, a near-miss is actually a minor error for the user, not a major one, because in the case of a near-miss the code turns this "dust" into a transaction fee instead of generating an output. But a bigger mistake pays the remaining amount to the change address... So clearly the currency selection is optimizing the wrong thing. So what should currency selection actually optimize for? In the context of Bitcoin today, the answer to this question becomes obvious. Currency selection should minimize the UTXO set in a way that does not noticeably affect transaction fees. In other words, it should try to consume more TXO inputs than it produces outputs, and ideally use some wallets’ near-dust outputs in transactions with larger inputs that don’t obstruct the transaction. [Privacy advocates may be shocked — shouldn’t wallets try to separate coins to confuse blockchain analysis? To this question I respond: relying on an algorithm that must combine these outputs to create a transaction is extremely dangerous. Many wallets take a better approach, which is to let you separate your wallet into “accounts” or subsections that don’t mix] New AlgorithmThe new algorithm is very simple because the goal is no longer to solve NP-hard problems, but to minimize the UTXO set and maximize efficiency. It starts "offline" by storing all wallet outputs in a container sorted by output value. This container is not reloaded from the wallet every time a payment request is received. Instead, it is only reloaded when it contains less than a few hundred TXOs. When a payment request comes in, it first estimates the transaction fee and adds it to the requested value, given a reasonable number of inputs and outputs. Let's call this the "target value". It then simply picks the smallest TXO that is larger than the target value and stores it as the solution (if there isn't a solution, it combines it with the largest TXO until a solution is found, or nothing else is possible). It then iterates backwards, picking TXOs that are smaller than this target value. For each of these TXOs, it creates a new target value = target value - TXO value, and recursively builds a set of solutions that exceed the configurable number of TXOs. At the same time, it picks random TXOs and tries the same algorithm. This ensures that the algorithm takes into account a lot of different values of TXOs, especially in cases where the backward iterator may operate over a set of small value changes. After a bunch of iterations, or if the algorithm gets "close" to the target value (the more iterations required, the more relaxed we are about what "close" means), the optimal solution is returned. The entire algorithm is implemented in about 200 lines of code. Other ideasOne thing users seem to complain about is that the fees are huge. This is obvious - nobody wants to pay 5-20%. Might as well use a credit card. So why not include some checks in the code? It has to be complicated, right? Not true. resultThis algorithm supports 20 payments per second on "standard" 2GHZ laptop hardware and inside a VM, and uses only a few percent of CPU (I'll update this file when I find out what's blocking larger traffic - but probably writing wallet changes to disk). So the traffic is about 200 times that of the original algorithm for the wallet size used for testing, and most importantly the traffic increases with the log of the wallet size, not exponentially. The original algorithm uses 100% of the CPU, while this algorithm uses 10% at most - so it's about 1000 times more efficient. Assuming a wallet has 10000+ random TXOs ranging from 1 mBTC to 10 BTC, it mostly chooses 2 to 4 inputs and spends 2 outputs (output + change). Therefore, the UTXO size either remains the same or decreases. There is much more analysis to be done, including obviously comparing its TXO selection results to the original algorithm. However, this code easily achieved my goal of testing very large blocks, so I felt it was time to write this post. There is clearly a lot of work to be done on transaction creation to improve the user experience and create a fully validated, enterprise-friendly Bitcoin wallet. As I think and as I show, these changes can also incentivize better wallet behavior (such as UTXO bloat control) in a simpler way than other solutions. And algorithms that already try to minimize UTXO bloat will likely handle miner algorithms that also prioritize transactions that reduce UTXO. With time and effort, this change could happen. With enough change — changes focused on user experience and making Bitcoin clients usable by enterprises — we might actually see an increase in node counts! |
>>: The Chinese government website published an article: Blockchain gives you a trustworthy world!
The length of a person's life can actually be ...
The size of your eyes tells your fortune Eyes are...
Moles have good meanings and bad meanings, and dif...
If a person wants to be beautiful, then the most ...
Everyone hopes to have a lot of money, to have en...
How many romances a person will have in his life ...
If a woman can find a man who is loyal to her, sh...
Yintang refers to: Yintang is one of the acupoint...
People with short and wide eyebrows have bad luck...
People with double chins look very wealthy, and a ...
The career line, also known as the destiny line, ...
In today's society, leftover men and women ha...
The short side stopped at the point, and the long...
Everyone's face is different, because everyon...
Some women are born with a destiny that gets bett...