achow101
commented at 6:09 pm on June 20, 2017:
member
This is an implementation of the Branch and Bound coin selection algorithm written by Murch (@xekyo). I have it set so this algorithm will run first and if it fails, it will fall back to the current coin selection algorithm. The coin selection algorithms and tests have been refactored to separate files instead of having them all in wallet.cpp.
I have added some tests for the new algorithm and a test for all of coin selection in general. However, more tests may be needed, but I will need help with coming up with more test cases.
This PR uses some code borrowed from #10360 to use effective values when selecting coins.
achow101 force-pushed
on Jun 20, 2017
in
src/wallet/coinselection.cpp:48
in
0246f1f79foutdated
Have you filtered utxo_pool to exclude utxo’s that have a net-neg value? Otherwise you’re underestimating the lookahead here. To get an accurate figure for what you may still collect downtree, you should only add utxo.txout.nValue >=0
@gmaxwell has concerns that Core wallet is only doing semi-sane utxo handling by spending these. With exact match + sane backoff algorithm this concern may be alleviated?
I don’t have much of a concern here about the 0/negative effective value inputs: Failing to select negative effective value inputs for an exact match won’t lead to a UTXO count inflation, because changeless transactions are by definition strictly UTXO reducing.
@instagibbs: I’m not completely opposed to spending net-negative UTXO, my concern here is primarily that it actually may cause the lookahead to be underestimated causing valid solutions not to be found.
I realize now that the knapsack algorithm would also not select uneconomic UTXO anymore, as if it had selected enough value before it reached them it would have already returned the set, and if it actually starts exploring them, cannot add more value in the first place.
Advocatus Diaboli: Would it be that terrible though, if UTXO were only considered when they actually have a net positive value? During times of low fees, they’d be used both during BnB and knapsack, during times of high fees, they wouldn’t bloat the blocks and lose their owner money.
@xekyo we should assume that it would be terrible unless someone can show that it will not cause another massive UTXO bloat event… but thats offtopic here, as I don’t think anyone has this concern with exact matches.
The utxos with negative effective values are filtered anyway in wallet/wallet.cpp, which is the only place (except for tests) from where SelectCoinsBnB is called.
in
src/wallet/test/coinselector_tests.cpp:150
in
0246f1f79foutdated
It seems to me that you’re also collecting coins that have a net-negative here. This will cause your lookahead to be underestimated, unless you cater to that case when calculating the remainder.
in
src/wallet/test/coinselector_tests.cpp:116
in
0246f1f79foutdated
I would perhaps add a test that checks what happens if the utxo_pool includes a UTXO that is more costly to spend than its own value. As far as I can tell, this would currently reduce your lookahead and may cause a premature search failure.
Xekyo changes_requested
Xekyo
commented at 7:23 pm on June 20, 2017:
member
Concept ACK, looks good to me. AFAICT, there’s an issue with the lookahead that should be addressed still.
Xekyo
commented at 7:46 pm on June 20, 2017:
member
Have you tested the effect of random exploration vs largest first exploration?
Either way, BranchAndBound already guarantees that the global utxo set doesn’t grow (for one output transactions) due to saving the change output.
LFE guarantees the creation of a minimal input set, and purposefully finds a possible solution. This should minimize the input set size variance. In my simulations BranchAndBound with LFE already caused a smaller average UTXO footprint than legacy Core selection.
Random Exploration could find a larger input set by skipping a key UTXO higher up in the tree. This could lead to the selection of a larger number of inputs, or in an edge case could even cause tries to be exhausted before a solution is found. This may increase input set variance, or could perhaps even exhaust small UTXOs too quickly for BnB to often find a viable solution.
I am not sure there is a significant privacy benefit for Random Exploration as for either selection method an attacker would already need to know about another eligible input that would achieve an exact match when switched out for one of the input set.
What benefit do you expect from using Random Exploration?
achow101
commented at 8:25 pm on June 20, 2017:
member
@Xekyo I was thinking that Random Exploration would be better for privacy but I see that it probably wouldn’t help. If you think it would be better to change to LFE, I can certainly do that.
Xekyo
commented at 8:33 pm on June 20, 2017:
member
@achow101: I don’t know how strong the effect is, but I’d expect Random Exploration to increase the required computational effort.
instagibbs
commented at 8:52 pm on June 20, 2017:
member
Noting that this PR has fairly heavy overlap with #10360 .
From chatting with @achow101 the intention of this PR is to touch as little as possible while still getting BranchNBound coin selection.
To make this successful it should really only be run on first iteration of the loop in CreateTransaction, when nFeeRet == 0 and only use effective value for the BnB coin selection step, rather than the knapsack as well. Once nFeeRet becomes more than zero, interactions start to get strange without a more complete overhaul like #10360.
instagibbs
commented at 8:59 pm on June 20, 2017:
member
This PR I believe will still create just-over-dust change outputs when BnB finds an exact match. Whenever we are allowing BnB matches(first iteration) we should not make change outputs less than the exact match slack value.
in
src/wallet/wallet.cpp:2244
in
0246f1f79foutdated
2313+ }
2314+ if (!use_only_knapsack) {
2315+ // Calculate cost of change
2316+ // TODO: In the future, we should use the change output actually made for the transaction and calculate the cost
2317+ // requred to spend it.
2318+ CAmount cost_of_change = effective_fee.GetFee(148+34); // 148 bytes for the input, 34 bytes for making the output
This assumes that the input will be spent at a feerate at least as high as the current. This was a valid assumption in my thesis, as I was using a fixed fee rate. I’m not sure whether this a valid assumption for realnet transaction selection, as we’ve literally seen fees between 8-540 sat/byte in the past two weeks. We might want to consider discounting the cost of the input slightly.
For now I think it is fine to use the current feerate.
Xekyo
commented at 9:04 pm on June 20, 2017:
member
@instagibbs: In fact, BnB is designed to only work when creating a transaction without a change output. If we were creating a change in the first place, the extensive search pattern would be unnecessarily wasteful.
instagibbs
commented at 9:15 pm on June 20, 2017:
member
To append onto my previous comments, any effective value match attempt should account for the fees just obtained by SelectCoins. Currently it completely ignores the newly-obtained fees, keeping the previous loop’s value, and then asks if nFeeRet >= nFeeRequired to break from the loop(which currently is 0 on the first go-around).
fanquake added the label
Wallet
on Jun 20, 2017
achow101
commented at 0:32 am on June 21, 2017:
member
I have made the BnB selector to be only run on the first pass of the coin selection loop. It is now set so that effective value is only used for the BnB selector and not the knapsack one. I have also added the negative effective value check and test just as a belt-and-suspenders thing. I also made BnB use Largest First Exploration instead of Random Exploration.
in
src/wallet/coinselection.cpp:58
in
1f4b03a33foutdated
53+ {
54+ if (value_ret > target_value + cost_of_change) { // Selected value is out of range, go back and try other branch
55+ backtrack = true;
56+ } else if (value_ret >= target_value) { // Selected value is within range
57+ done = true;
58+ } else if (tries <= 0) { // Too many tries, exit
Here’s a unexpected behavior in my algorithm: if there is a number of input combinations whose value_ret all exceed the target_value when tries == 0 is passed, tries can go into the negative.
The tries check should be moved to the top of the checks.
sipa
commented at 11:26 pm on June 21, 2017:
member
Perhaps generically, we should never create change if the amount is less than the cost of creating + spending it (regardless of whether BnB was used to find the inputs or not)?
instagibbs
commented at 2:05 pm on June 22, 2017:
member
@sipa one question is if we should allow the wallet to consider consolidation-level prices for that change. Perhaps the user is in a hurry now, but would consider spending that change at a much slower pace.
Maybe for a first pass only consider the selected feerate, then Future Work allow a parameter which has more aggressive change protection given longer timescales.
sipa
commented at 4:26 pm on June 22, 2017:
member
@instagibbs Yes, I agree; we should use long-eatimates for the spend part of change rather than the actual feerate the user is willing to pay now. Perhaps we can make it more conservative without doing that by using a factor 2 or 3 reduction?
in
src/wallet/wallet.cpp:2224
in
12aa63abf1outdated
2281- continue;
2282+ if (!only_knapsack) {
2283+ // Calculate cost of change
2284+ // TODO: In the future, we should use the change output actually made for the transaction and calculate the cost
2285+ // requred to spend it.
2286+ CAmount cost_of_change = effective_fee.GetFee(148+34); // 148 bytes for the input, 34 bytes for making the output
not correct for segwit. If this code ends up being changed to follow pieter’s suggestion of dividing the rate by two or three it should be bounded by the min relay fee. (I’m not super fond of that suggestion).
gmaxwell
commented at 6:57 pm on June 22, 2017:
contributor
@sipa@achow101 it would be very very easy in the current PR to ask for another estimate for the change, I think ~two loc addition, and minor addition to the selectcoins arguments to pass down a second fee. I think this would be much more desirable than a fixed division. Future work could do things like make that second confirmation target configurable.
in
src/wallet/wallet.cpp:2565
in
12aa63abf1outdated
2561@@ -2562,7 +2562,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
2562 }
25632564 const CAmount nChange = nValueIn - nValueToSelect;
2565- if (nChange > 0)
2566+ if (nChange > 0 && (!first_pass || nFeeRet == 0)) // nFeeRet is only 0 on the first pass if BnB was not used.
Using nFeeRet to signal BNB usage is ugly. I think you shouldn’t pass in nFeeRet at all, but have some explicit signal (e.g. boolean return) for BNB usage and if its set; after select coins set nFeeRet to nChange and use the same signal to bypass this branch.
I also think this condition is slightly incorrect but benign in the current code, lets say our configured feerate were zero: now BNB could find a solution and leave nFeeRet==0. (though nChange would currently be zero too, so it would be harmless but seems to me like the kind of thing to be brittle in future changes)
achow101
commented at 5:32 pm on June 23, 2017:
member
Travis failure seems to be unrelated
karelbilek
commented at 2:46 am on July 2, 2017:
contributor
just fyi, I have used your code as a reference for this code
karelbilek
commented at 8:14 pm on July 2, 2017:
contributor
I have to say, I don’t understand the target size; maybe there is a bug there.
In wallet.cpp, in CWallet::CreateTransaction, you create nValue, which seems to be the sum of all the outputs. Because the BnB is used only at the first pass, nFeeRet is 0 and nValueToSelect is just the sum of all the outputs.
This is then used as the exact target in the BnB algorithm.
achow101
commented at 8:22 pm on July 2, 2017:
member
@runn1ng BnB uses effective values for the inputs so the fee is accounted for when coins are selected. The effective values are calculated in SelectCoinsMinConf
karelbilek
commented at 8:27 pm on July 2, 2017:
contributor
That eff. value accounts for the inputs of the new transaction, but not for the outputs (plus the overhead of the tx itself, but that is only about 10 bytes).
In SelectCoinsMinConf, you already have the target, which does not account for that.
achow101
commented at 9:26 pm on July 2, 2017:
member
Ah, yes. That is a bug. Thanks for finding that!
achow101 force-pushed
on Jul 2, 2017
achow101 force-pushed
on Jul 2, 2017
achow101 force-pushed
on Jul 2, 2017
in
src/wallet/wallet.cpp:2537
in
407133d206outdated
2531@@ -2517,6 +2532,9 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
2532 fFirst = false;
2533 txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
2534 }
2535+ } else if (first_pass){
2536+ // On the first pass BnB selector, include the fee cost for outputs
2537+ output_fees += nFeeRateNeeded.GetFee(recipient.scriptPubKey.size());
I think it may be better to directly check on serialized size of an output based on that pubkey
in
src/wallet/wallet.cpp:2224
in
407133d206outdated
2281- continue;
2282+ if (!only_knapsack) {
2283+ // Get the fee rate to use for the change fee rate
2284+ CFeeRate change_feerate;
2285+ FeeCalculation feeCalc;
2286+ change_feerate = GetMinimumFeeRate(1008, ::mempool, ::feeEstimator, &feeCalc);
right now it only uses one or the other, so !only_knapsack means used_bnb. I assume this interface is future-looking to where we may try multiple strategies?
The idea behind this was to have BnB be just strictly on top of the current behavior, and separating it like this makes that possible. The first time through the loop uses BnB, but then every time after that uses only the current selector. The loop behavior also stays the same since nFeeRet will remain 0 if the BnB fails.
in
src/wallet/wallet.cpp:2507
in
407133d206outdated
2502@@ -2556,7 +2503,22 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
2503 CAmount nValueToSelect = nValue;
2504 if (nSubtractFeeFromAmount == 0)
2505 nValueToSelect += nFeeRet;
2506+
2507+ // Get the fee rate to use effective values in coin selection
Add a comment saying this triggers BnB to be the only type tried when true
in
src/wallet/wallet.h:863
in
407133d206outdated
849@@ -837,7 +850,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
850 * completion the coin set and corresponding actual target value is
851 * assembled
852 */
853- bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, uint64_t nMaxAncestors, std::vector<COutput> vCoins, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet) const;
854+ // TODO: Change the hard coded change_size when we aren't only using P2PKH change outputs
if we’re going to change it later to something without a default/dynamic value, maybe just get rid of the default arg and pass it each time.
instagibbs
commented at 8:34 pm on September 18, 2017:
this is getting more important now with segwit, and the fact that we have two related optional arguments
in
src/wallet/wallet.h:984
in
407133d206outdated
975@@ -962,11 +976,23 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
976 */
977 static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc = nullptr, bool ignoreGlobalPayTxFee = false);
978 /**
979+ * Estimate the minimum fee rate considering user set parameters
980+ * and the required fee
perhaps note it doesn’t have the maxtxfee check inside it, making it slightly asymmetrical to the total fee one.
instagibbs
commented at 5:28 pm on July 3, 2017:
member
In general the semantics of first_run and used_bnb seem tightly linked, and are seemingly used interchangeably. Perhaps something to revisit.
achow101 force-pushed
on Jul 3, 2017
karelbilek
commented at 10:22 pm on July 3, 2017:
contributor
@achow101 for some reason, when I do simulations either on @Xekyo set (in scala) or on bitcoinjs randomly generated data (with the algo rewritten into javascript), the total fees are actually lower when I make the target lower (that is, when I do not include the output cost in the target). So maybe tightening the target rejects more transactions and then the fallbacks somehow make better results.
instagibbs
commented at 5:40 pm on July 6, 2017:
member
@runn1ng if you wouldn’t mind, I’d like to know what the difference in rate of change creation for each of those experiments as well.
Xekyo
commented at 8:21 pm on July 6, 2017:
member
[…]the total fees are actually lower when I make the target lower (that is, when I do not include the output cost in the target).
@runn1ng: Um wait. “Target” is the amount to be selected. We are talking about the “cost of change” parameter that gives the leniency window for the exact match, right? Also, do you mean “input cost” instead of “output cost”?
It would be lovely if you could post your experiment’s results somewhere, so we all have the same dataset to discuss.
karelbilek
commented at 4:18 pm on July 11, 2017:
contributor
@Xekyo The problem with your experiment is that it’s non-deterministic… but maybe I could put there some pre-set random seed
karelbilek
commented at 8:13 am on July 13, 2017:
contributor
edit: ignore the graphs, see comment below
0 1~~~Anyway. I tried changing the cost of change on your scala code, as I wrote here - https://github.com/Xekyo/CoinSelectionSimulator/pull/8 . Now I tried values from 0 to 100 as percent, and this is the result (note that left axis doesn't start at 0)~~~
2 3
4 5[google spreadsheet link](https://docs.google.com/spreadsheets/d/1ePfoi08uV_QU48Q68aAu6apB-BG9DwM52JRgxNgSWPk/edit?usp=sharing)
6 7~~~x axis is how much percent of the current cost of change is used; y axis is total cost on the big honeypot data set.~~~
8 9~~~On the small random test cases there is no difference (what matters more there is the fallback, but that's for another experiment).~~~
1011~~~Note that there was a [typo](https://github.com/Xekyo/CoinSelectionSimulator/pull/4/files) in the original experiments for the paper, which makes the cost of change "factor" 83%~~~
karelbilek
commented at 10:18 am on July 13, 2017:
contributor
edit: ignore the graphs, see comment below
01~~~If you I try the same at the bitcoinjs example - [defined here](https://github.com/bitcoinjs/coinselect/blob/master/stats/index.js#L9) - small random examples with relatively few utxos - the graph looks completely different, and very dependent on what is a "backup plan" in the case of not found match~~~
23
45[google sheet](https://docs.google.com/spreadsheets/d/1zUen5aiKfDwRIRkOg-QJgAE0uEn-_OsrSUQwJm2xWDs/edit?usp=sharing)
67~~~"rand" is random, "min" is sorting the utxos from the biggest to the lowest and starting from the biggest. (Both are total cost.) I am not sure what "min" strategy does on the big data.~~~
89~~~(Just for interest, when I tried 100-200%, the graph goes up again, but not that quickly)~~~
karelbilek
commented at 9:44 am on July 14, 2017:
contributor
edit: ignore the graphs, see comment below
01
23~~~Again, it's always better to take the "minimal" strategy (that is, to sort utxos by value size descending, and then take from the start until it's enough).~~~
45~~~If you want to replicate this experiment - note that it takes, for reasons I don't understand, terribly long time, and you will have to parallelize the simulation - luckily, that's trivial with scala paralel collections - see the commits at https://github.com/runn1ng/CoinSelectionSimulator/tree/exp_multi~~~
67~~~I would like to hear [@Xekyo](/bitcoin-bitcoin/contributor/xekyo/) opinions :)~~~
89~~~Also I would like to try this PR strategy, that is, to take the current core strategy as a backup.... but that is too complicated (especially in the javascript code), so I won't do it.~~~
achow101
commented at 7:53 pm on July 14, 2017:
member
@runn1ng would you be able to try the strategy with Core’s current selector as fallback? The easiest way to do that would be to add/modify the test cases for coin selection.
Xekyo
commented at 9:52 pm on July 14, 2017:
member
@runn1ng: re random data: I’d surmise that BnB doesn’t perform well on small datasets as there are too few possible combinations. That could easily cause the fallback algorithm to dominate.
re moneypot:
What I do find confusing is that your total cost is so much higher than my result with Branch and Bound + Single Random Draw of 58,940,772.30. Were you still running with fixed fees of 10000 satoshi/kB?
I haven’t comprehensively tested all possible fallback algorithms, it is possible that Largest First selection as a fallback to BnB is more efficient as it doesn’t take away as many small utxo that can be used to create combinations.
Do I understand correctly that you calculated “cost of change” and then took a percentage of that, or is this percentage only on the cost of the input? If you did the former, it appears that using just the cost of an additional output as “cost of change” leads to a minimum, considering that 34 bytes is 18.7% of what I proposed as “cost of change” with output+input.
karelbilek
commented at 3:29 am on July 15, 2017:
contributor
What I do find confusing is that your total cost is so much higher than my result with Branch and Bound + Single Random Draw of 58,940,772.30. Were you still running with fixed fees of 10000 satoshi/kB?
That is weird indeed.
I am running code from your repo. To be sure I reverted all my local changes and I still get 72506973.
I use only StackEfficientTailRecursiveBnB, should I try the other BnBs? edit: well, they get stack overflow, so I won’t. :D
karelbilek
commented at 3:31 am on July 15, 2017:
contributor
I am running the code through sbt run in the main directory. I look just at the total cost in the resulting csv.
karelbilek
commented at 3:45 am on July 15, 2017:
contributor
I get totally different numbers than in your paper with the other scenarios too. The numbers don’t correspond to neither of the three tables, unfortunately.
edit: oooh, that’s because I am running “MoneyPot After LF”, which was the default scenario, but it’s actually with additional UTXOs from a previous run. The actual scenario from the paper (the first one) is TestCaseMoneyPotEmpty, right.
Xekyo
commented at 4:34 am on July 15, 2017:
member
Yes correct. The Moneypot after LF, is running the MoneyPot scenario starting with the resulting UTXO pool of running it with Largest First selection before.
karelbilek
commented at 11:27 pm on July 17, 2017:
contributor
I found out that the tworepos for coinselect simulation returned different results for the same strategy, so I painstakingly went through both of them and found where they differ… and put tons of of PRs to both, so they now both return the same results with the same fees + setup
The differences in simulations were:
how they deal with insufficient funds (and how they detect it)
what is minimum change
what are the sizes of “defaut” input/output
some small bugs
…and those added to significant differences. Anyway, when I fixed all the issues, those are the results/graphs I get:
this is for the moneypot scenario, with the fees 10 sat/bitcoin
In both, rand is slightly better. I am not sure what happened, if it’s because the scenario is different (without the large UTXO set) or because of the subtle differences in benchmarking. The shape is similar though.
This is the scenario of small randomly generated wallets
BnB+LF performs better, optimum about 50% cost of change.
So, different strategies/parameters are better at different scenarios. Again, there is danger of overfitting on one scenario - plus there might be some more subtle bugs in the benchmark code… in my wallet code, I will probably just use BnB+LF with 50% of cost and call it a day :)
I haven’t shown this in graphs, but having BnB is always better than not having it. :)
If you want to repeat the tests, my forks of the repos are here 12
karelbilek
commented at 11:29 pm on July 17, 2017:
contributor
Do I understand correctly that you calculated “cost of change” and then took a percentage of that, or is this percentage only on the cost of the input? If you did the former, it appears that using just the cost of an additional output as “cost of change” leads to a minimum, considering that 34 bytes is 18.7% of what I proposed as “cost of change” with output+input.
Hm, that doesn’t seem to be the case, the minimum is not 18%, but 30% to 50% on these two scenarios.
instagibbs
commented at 11:50 pm on July 17, 2017:
member
@runn1ng is there a plausible explanation why not accounting for the full cost of the change is cheaper overall?
karelbilek
commented at 0:00 am on July 18, 2017:
contributor
@runn1ng would you be able to try the strategy with Core’s current selector as fallback? The easiest way to do that would be to add/modify the test cases for coin selection.
Hm, I already spent too much time on this… :/ I will see if I have time to look into the bitcoin coin selection tests and how to add benchmarks there, but not promising anything.
karelbilek
commented at 0:14 am on July 18, 2017:
contributor
I think - and that’s a speculation - that it’s because the target is “tighter”, so the BnB will reject more “lose” matches and will continue searching until it finds better match. So less fee is spent then, even when some matches are rejected that didn’t have to be (and those spend more on fees).
Btw. An interesting thing I just noticed - in the “small random” example, there is not that many BnB matches in the first place! Around 30 (out of 10.000 transactions). It still has an effect on the result…
in
src/wallet/coinselection.cpp:105
in
71e9d683beoutdated
84+
85+ // Walk backwards to find the first utxo which has not has its second branch traversed
86+ while (selection.at(depth).second) {
87+ // Reset this utxo's selection
88+ if (selection.at(depth).first) {
89+ value_ret -= utxo_pool.at(depth).txout.nValue;
It never happens, that an utxo is at the same time in an exclusion branch (which is what .second does) and is also selected (what .first does). Which makes sense; you never at the same time select and not select an utxo :)
With all my simulations, this line never seems to fire (when I rewrote this to JS).
I also think that .second is not needed at all; all the information necessary is in the first and depth; the only situation where .first != !(.second) is after we backtrack here, but the information in .second is useless anyway (since we will change it anyway before we backtrack to it again).
Right. That appears to be a relic of when this randomly selected which branch to try first before I changed it to always try including first.
in
src/wallet/coinselection.cpp:123
in
71e9d683beoutdated
98+ if (depth < 0) { // We have walked back to the first utxo and no branch is untraversed. No solution, exit.
99+ return false;
100+ }
101+ }
102+
103+ if (!done) {
In the if at the start of the while cycle, either backtrack or done is set, never both. We got here only when backtrack == true, so done cannot be true.
instagibbs
commented at 8:23 pm on September 18, 2017:
ACK on @runn1ng ’s comment. Impossible to be “done” and require backtracking.
karelbilek
commented at 12:11 pm on August 10, 2017:
contributor
@achow101 frankly, the “old” (current) core code seems to complex to me, because it introduces state, I don’t understand it enough to make the simulation with the current core code as a backup
achow101 force-pushed
on Aug 11, 2017
achow101 force-pushed
on Aug 15, 2017
achow101 force-pushed
on Aug 17, 2017
achow101
commented at 11:00 pm on August 17, 2017:
member
I have squashed a few commits and rebased this. I’ll work on this some more after the 0.15.0 release.
achow101 force-pushed
on Sep 6, 2017
achow101 force-pushed
on Sep 6, 2017
achow101
commented at 9:28 pm on September 6, 2017:
member
I have squashed this even more (by getting a diff and re-committing) and hopefully made each commit a more logical chunk of code.
I have also changed this to use the discard fee rate for the change fee rate. The cost of change is also now calculated with the fee rate for making an output being the effective fee rate (fee rate being used) and the fee rate for spending the change being the discard fee rate.
achow101
commented at 3:45 pm on September 18, 2017:
member
I’m not sure what the travis failure here was so I restarted that build.
MarcoFalke
commented at 7:22 pm on September 18, 2017:
member
@achow101 Travis failure is due to some weird behavior of travis to merge in latest master, but keep the yaml or config from the past (maybe when the pull was created).
You might be able to fix it by force pushing the last commit (EDITOR=true git commit --amend && git push origin bnb-coin -f) or just rebase on master.
in
src/wallet/coinselection.cpp:31
in
88ca715178outdated
instagibbs
commented at 7:53 pm on September 18, 2017:
define “remaining”, or drop the comment
achow101
commented at 1:47 am on September 19, 2017:
Done; renamed to lookahead
in
src/wallet/coinselection.cpp:51
in
88ca715178outdated
46+ CAmount remaining = 0;
47+ for (CInputCoin utxo : utxo_pool) {
48+ remaining += utxo.txout.nValue;
49+ }
50+
51+ // Depth first search to find
instagibbs
commented at 7:54 pm on September 18, 2017:
to find what?
achow101
commented at 1:47 am on September 19, 2017:
Done
in
src/wallet/coinselection.cpp:36
in
88ca715178outdated
31+ if (utxo_pool.size() <=0) {
32+ return false;
33+ }
34+
35+ int depth = 0;
36+ int tries = 100000;
instagibbs
commented at 7:54 pm on September 18, 2017:
remaining_tries?
achow101
commented at 1:47 am on September 19, 2017:
done
in
src/wallet/coinselection.cpp:37
in
88ca715178outdated
32+ return false;
33+ }
34+
35+ int depth = 0;
36+ int tries = 100000;
37+ std::vector<std::pair<bool, bool>> selection; // First bool: select the utxo at this index; Second bool: traversing second branch of this utxo
instagibbs
commented at 7:56 pm on September 18, 2017:
define what second branch means?
achow101
commented at 1:47 am on September 19, 2017:
Done
in
src/wallet/wallet.h:457
in
feeb3cab3eoutdated
452@@ -453,6 +453,9 @@ class CWalletTx : public CMerkleTx
453 CAmount GetAvailableWatchOnlyCredit(const bool& fUseCache=true) const;
454 CAmount GetChange() const;
455456+ // Get the marginal bytes if spending the specified output from this transaction
457+ int GetSpendSize(unsigned int i) const;
instagibbs
commented at 8:32 pm on September 18, 2017:
nOut or something better please
achow101
commented at 1:47 am on September 19, 2017:
done
in
src/wallet/wallet.cpp:2357
in
feeb3cab3eoutdated
instagibbs
commented at 8:33 pm on September 18, 2017:
Some asymmetry now with SC and SCMC, the former having one “change size” parameter, the other two. change_output_size to match.
achow101
commented at 1:47 am on September 19, 2017:
done
in
src/wallet/wallet.h:1239
in
feeb3cab3eoutdated
1209@@ -1195,4 +1210,12 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins
1210 return true;
1211 }
12121213+// Calculate the size of the transaction assuming all signatures are max size
1214+// Use DummySignatureCreator, which inserts 72 byte signatures everywhere.
1215+// TODO: re-use this in CWallet::CreateTransaction (right now
1216+// CreateTransaction uses the constructed dummy-signed tx to do a priority
1217+// calculation, but we should be able to refactor after priority is removed).
instagibbs
commented at 8:34 pm on September 18, 2017:
priority is removed; revisit?
achow101
commented at 1:43 am on September 19, 2017:
You wrote it (I took it from one of your commits); revisit it for me :D
in
src/wallet/coinselection.cpp:72
in
31914f0697outdated
instagibbs
commented at 8:46 pm on September 18, 2017:
parens around the value being subtracted would be nice
in
src/wallet/coinselection.cpp:67
in
31914f0697outdated
64 backtrack = true;
65+ } else if (curr_waste > best_waste) { // Don't select things which we know will be more wasteful
66+ backtrack = true;
67 } else if (value_ret >= target_value) { // Selected value is within range
68- done = true;
69+ curr_waste += value_ret - target_value;
instagibbs
commented at 8:47 pm on September 18, 2017:
parens around the value being subtracted would be nice
instagibbs
commented at 9:00 pm on September 18, 2017:
Also, note that this value is the “excess” being added and removed later, in contrast to the input-selection waste (wonder if we can find a good name for this cost)
achow101
commented at 1:47 am on September 19, 2017:
Done here and elsewhere
achow101
commented at 1:48 am on September 19, 2017:
Done
instagibbs
commented at 8:53 pm on September 18, 2017:
member
Added some comments. Still working through the latest commit.
in
src/wallet/coinselection.cpp:26
in
88ca715178outdated
long_term_feerate will be some very small fee that guarantees send in 1008 blocks, right? Why is that used in the waste calculation?
Xekyo
commented at 10:40 pm on September 30, 2017:
member
Pieter pointed out that the algorithm doesn’t necessarily find the best solution when it discovers the first solution. However, it was then at first not clear how to make the trade-off between making the input set smaller or reducing the excess in the selection.
The idea is that during high fee rates, it might be beneficial to the user if a smaller input count is preferred in exchange for a higher excess, while at low fee rates, a higher input count might be acceptable to achieve a smaller excess.
We came up with the waste metric to combine both of these dimensions:
where excess is the amount that we overshoot the selection target, and the second term derives from how much more expensive it is to spend the inputs right now than we expect in the longterm.
The algorithm now doesn’t stop on the first solution, but continues to search for a solution with a lower waste metric (using that as another bound) until either it has searched all possibilities or runs into the maximum allowed tries.
in
src/wallet/coinselection.cpp:83
in
0fb73ae532outdated
60 if (tries <= 0) { // Too many tries, exit
61- return false;
62+ break;
63 } else if (value_ret > target_value + cost_of_change) { // Selected value is out of range, go back and try other branch
64 backtrack = true;
65+ } else if (curr_waste > best_waste) { // Don't select things which we know will be more wasteful
Xekyo
commented at 11:27 pm on September 30, 2017:
Otherwise, you’d be prematurely stopping when a transaction is created with fees below the long-term fee estimate.
karelbilek
commented at 9:15 pm on October 1, 2017:
Maybe I misunderstand what long_term_fee is, but how can it be lower than long term fee estimate? I thought that long term fee looks for confirmation target 1008, thus all smart fee estimates will be only bigger than the long term estimate…
ryanofsky
commented at 4:57 pm on October 2, 2017:
Note for reviewers: This function is not new, just moved from wallet.cpp.
in
src/wallet/coinselection.cpp:11
in
2ca869b0faoutdated
6+#include "util.h"
7+#include "utilmoneystr.h"
8+
9+// Descending order comparator
10+struct {
11+ bool operator()(CInputCoin a, CInputCoin b) const
ryanofsky
commented at 6:16 pm on October 2, 2017:
Here and throughout the PR, probably would be better to pass CInputCoin by const reference to avoid copying the struct. Probably want for (const CInputCoin& utxo : utxo_pool) instead of for (CInputCoin utxo : utxo_pool) as well.
ryanofsky
commented at 6:24 pm on October 2, 2017:
Don’t you need to update input_fee_vec and long_term_fee_vec during the sort? Maybe it would be simpler to make input_fee and long_term_fee new fields in CInputCoin.
Yeah, I do… I took your suggestion and added fields to CInputCoin for that coin’s fee and long_term_fee values.
in
src/wallet/coinselection.cpp:97
in
abf526092aoutdated
69+ best_selection.assign(selection.begin(), selection.end());
70+ best_waste = curr_waste;
71+ }
72+ curr_waste -= (value_ret - target_value); // Remove the excess value as we will be selecting different coins now
73+ backtrack = true;
74+ } else if (depth >= (int)utxo_pool.size()) { // Reached a leaf node, no solution here
ryanofsky
commented at 6:27 pm on October 2, 2017:
I have added a very long comment describing what this does and the arguments of the function.
ryanofsky
commented at 6:56 pm on October 2, 2017:
member
Started looking at this. Just some superficial comments so far.
karelbilek
commented at 10:27 pm on October 2, 2017:
contributor
I tried my javascript simulations with the additional code (the waste comparation) and it actually had slightly worse results (higher total cost)
BUT it might be some bug in the simulation, I don’t have that much time for experimenting right now
karelbilek
commented at 10:31 pm on October 2, 2017:
contributor
Some simulation that actually test the resulting total fees on a given scenario - similar to @Xekyo repo - but on actual bitcoin binary - would be probably nice. So the testing would be on this code and not on re-implementations.
I am thinking how hard would that be on regtest, with the current python qa testing framework.
achow101 force-pushed
on Oct 3, 2017
achow101 force-pushed
on Oct 3, 2017
laanwj added this to the "Blockers" column in a project
achow101 force-pushed
on Nov 30, 2017
achow101
commented at 5:08 pm on November 30, 2017:
member
Rebased this.
I’ll try to fix up some of the fee estimation things wrt segwit.
@runn1ng I’ll see if I can do any sort of simulation stuff.
karelbilek
commented at 7:08 pm on November 30, 2017:
contributor
achow101
commented at 6:39 am on December 20, 2017:
member
I have rebased this and added a new commit that uses an actual prototype change output and gets its size and spend size instead of hardcoding constants.
I finally have the time to work on simulations for this and will be doing so over the next few weeks.
@karel-3d What are the axes of the simulation graphs that you posted earlier?
achow101
commented at 8:37 pm on December 21, 2017:
member
I figured out how to run simulations with this. https://github.com/achow101/bitcoin/tree/bnb-simulate contains the modifications and script that I am using to simulate the behavior. It uses the test framework to setup 2 nodes. One node will then mine a bunch of blocks and send money to the other node as the scenarios specify. Then the other node will spend from those UTXOs as the scenario specifies. At the end, a bunch of data is printed out.
All of this is done in test/functional/simulate-coinselection.py. Modify that file as necessary if you wan to run simulations.
There is also a modification there to print to the debug.log about whether BnB was used or not. To calculate the BnB usage, grep through the debug.log file. This will require running the simulation with --nocleanup so that the datadir is not removed after it completes.
I simulated BnB and compared that to the current coin selection behavior (cherry-picked the above commit and removed the printing to debug.log part). I used set the following parameters: -dustrelayfee=0 -paytxfee=0.00001000. Setting the dust relay fee to 0 is to allow the dust UTXOs to be added. The transaction fee rate was set to be a fixed 1 sat/byte.
The results from this aren’t very impressive, and that’s likely because the BnB algorithm was used for a very small percentage of the coin selections, less than 3%. I will be doing more simulations with different parameters and the other datasets.
achow101
commented at 3:07 am on December 22, 2017:
member
The difference here is a bit more pronounced, but I’m not quite sure how to interpret this result. @Xekyo, can you comment?
One other thing I noticed is that, with these large datasets and trying to go through them as fast as possible, BnB does take a noticeably longer, but I think that is expected behavior. I don’t expect that to actually have that much of an impact in practice.
achow101
commented at 4:56 pm on December 26, 2017:
member
achow101
commented at 7:45 pm on January 11, 2018:
member
Rebased
karelbilek
commented at 1:06 pm on January 16, 2018:
contributor
@achow101 the axes are - X is how “tight” the target is - by which I mean, I took the cost of change and I multiplied it by a factor. So I took cost of change just X% of the cost in the original algorithm.
Y is total cost from Murch thesis (what is spent + what it costs to spend the resulting utxos).
Btw. I wrote @Xekyo it would be nice to make a BIP with this algorithm, so we don’t have to write “Murch algorithm” and have the algorithm specified as a BIP. (There are already BIPs that don’t describe the protocol, just wallet behaviour.)
achow101 force-pushed
on Jan 19, 2018
achow101
commented at 6:13 pm on January 22, 2018:
member
I have done some more simulations, this time with a variable fee rate. The fee data I am using is pulled from https://bitcoinfees.info/. The fee files are available here.
There are two fee things, one of them has each fee rate repeated 8 times in a row, the other cycles through the list 8 times.
achow101
commented at 3:16 am on January 26, 2018:
member
So apparently there was a bug that was allowing change to still be made even with BnB. The latest commit should fix this, and I think that will significantly change the simulation results.
Xekyo
commented at 7:21 pm on January 26, 2018:
member
There is a mismatch here with the “#changes created” and “BnB usage”. Every time you use BnB, there should no change be created.
I would suspect that the “excess” from BnB is not being dropped to the benefits of the fees, but instead put into a change output?
Edit: oh, saw your comment upon send. Good, curious about new results!
achow101 force-pushed
on Jan 29, 2018
achow101 force-pushed
on Jan 29, 2018
achow101 force-pushed
on Jan 29, 2018
achow101
commented at 5:27 pm on February 4, 2018:
member
achow101
commented at 3:36 pm on February 5, 2018:
member
@instagibbs They’re all BnB simulations. I moved the fees file and simulations file fields to the left (they were on the right side originally).
Xekyo
commented at 0:08 am on February 15, 2018:
member
@achow101: Row 4 has “min input set” of 0, but a transaction must have at least one input.
Why do you think did the BnB usage almost halve? Are you using the solution of the RandomSelector if it has a lower waste metric? Otherwise, I’d expect the BnB usage to increase with higher fees since the target window for a solution without a change output increases.
The minChange of 0.00005428 also seems oddly low. Did the wallet balance dip to zero in your simulation?
I’ve had another idea for an optimization of the exploration: We see a lot of unspent values duplicated at round figures (e.g. 0.1 BTC, 10,000 sats). When exploring the combination tree, we should never allow a second branch to place the same value in the same position.
E.g. before, when you would have the unspent set of one UTXO with 1,000,000 and 100 UTXO with 100,000, and are looking for a target of 1,150,000,
you’d explore:
1,000,000
/
100,000(A)
/
0 100,000(B) —–> Σ=1,200,000 –> too much, cut branch
100,000(C) —–> Σ=1,200,000 –> too much, cut branch
…
and once you’ve tried all 99 other 100 with 100,000(A), you’d try the remaining 98 100,000s with 100,000(B) and so forth.
If we don’t allow expanding a branch that has the same value at the same position this becomes:
1,000,000
/
| 100,000(A)
| /
| 0 100,000(B) —–> Σ=1,200,000 –> too much, cut branch
|
| 100,000(C-CV) —–> don’t expand, already considered 100,000 in combo with this 0th and 1st
|
100,000(B-CV) —–> don’t expand, already considered 100,000 in combo with this 0th
Done, no solution.
In practice this could be implemented by remembering the value of the last item that we backtracked from and requiring that any new UTXO we consider for that position is at least one satoshi smaller.
achow101
commented at 3:04 am on February 15, 2018:
member
Row 4 has “min input set” of 0, but a transaction must have at least one input.
That’s strange. I’ll rerun that simulation.
Why do you think did the BnB usage almost halve?
I think that’s a consequence of the bug I fixed. The BnB usage in the previous simulations was the number of times that BnB was hit, but BnB wasn’t actually used for the actual coin selection; the normal normal coin selector was used. So some inputs weren’t actually consumed, and additional change outputs were created which would then allow for more hits with BnB as there are more UTXOs.
Are you using the solution of the RandomSelector if it has a lower waste metric
No, the random selector is not implemented yet.
The minChange of 0.00005428 also seems oddly low. Did the wallet balance dip to zero in your simulation?
I believe it did. Such low values of min change do line up with the min change values for the normal coin selector though.
When exploring the combination tree, we should never allow a second branch to place the same value in the same position.
We still need to take into account the waste though; two outputs with the same effective value could still have different waste values which may make one output more desireable than another even with the same values. When we backtrack, we aren’t always cutting off a branch; we could have backtracked in order to find something else with a lower waste value.
achow101
commented at 6:56 pm on February 15, 2018:
member
achow101
commented at 7:43 pm on March 5, 2018:
member
All of the valid results for varying fee rate simulations, but readable
Type
Fees file
Simulation File
final value
mean #UTXO
final #UTXO
#received
#spent
#changes created
min change
max change
mean change
stDev of change
total fees
average fees
total cost
min input set
max input set
mean size of input set
stdev of input set size
BnB Usage
BnB+Core
fees-bitcoinfees-info-repeats.csv
derived-1I-2O.csv
55.88713405
19.86
17
6097
17913
11833
0.00997809
4.15153434
0.10399897
0.28233767
-0.02520509
-0.00000213
-0.02523025
1
24
1.51
1.04
27/11860
Core
fees-bitcoinfees-info-repeats.csv
derived-1I-2O.csv
55.88712329
27.51
22
6097
17935
11860
0.00001005
10.30069834
0.29223202
0.72740965
-0.02521585
-0.00000213
-0.02524841
1
21
1.51
1.20
N/A
BnB+Core
fees-bitcoinfees-info-cycle.csv
derived-1I-2O.csv
55.88713391
22.93
19
6097
17909
11831
0.00998908
9.14499834
0.10465966
0.28952080
-0.02520523
-0.00000213
-0.02523335
0
27
1.51
1.05
28/11860
Core
fees-bitcoinfees-info-cycle.csv
derived-1I-2O.csv
55.88712420
26.84
23
6097
17934
11860
0.00002034
10.30069834
0.28121957
0.65214921
-0.02521494
-0.00000213
-0.02524898
1
21
1.51
1.17
N/A
BnB+Core
fees-bitcoinfees-info-repeats.csv
derived-balanced.csv
55.88171750
27.27
21
12194
23840
11667
0.00221449
5.69999834
0.056819638
0.19930147
-0.03062164
-0.00000258
-0.03065272
1
84
2.01
1.83
193/11860
Core
fees-bitcoinfees-info-repeats.csv
derived-balanced.csv
55.88158139
37.74
29
12194
24025
11860
0.00030847
9.96073034
0.10688567
0.34970815
-0.03075775
-0.00000259
-0.03080067
1
32
2.03
1.74
N/A
BnB+Core
fees-bitcoinfees-info-cycle.csv
derived-balanced.csv
55.88173059
28.78
20
12194
23831
11657
0.00223019
5.69999834
0.056200963
0.19728515
-0.03060855
-0.00000258
-0.03063815
1
45
2.01
1.72
203/11860
Core
fees-bitcoinfees-info-cycle.csv
derived-balanced.csv
55.88158048
36.26
28
12194
24026
11860
0.00006546
9.91069834
0.12069723
0.41430597
-0.03075866
-0.00000259
-0.03080010
1
44
2.026
1.84
N/A
BnB+Core
fees-bitcoinfees-info-repeats.csv
moneypot.csv
55.87130774
131.06
33
24388
35121
10766
0.00005428
4.69999834
0.036069275
0.13266262
-0.04103140
-0.00000346
-0.04108024
1
279
2.96
4.20
1094/11860
Core
fees-bitcoinfees-info-repeats.csv
moneypot.csv
55.87047548
208.40
29
24388
36214
11855
0.00001333
7.99999834
0.055334876
0.25061846
-0.04186366
-0.00000353
-0.04190658
1
668
3.05
7.05
N/A
BnB+Core
fees-bitcoinfees-info-cycle.csv
moneypot.csv
55.87119478
64.37
29
24388
35312
10953
0.00015309
4.69999834
0.035168758
0.12918029
-0.04114436
-0.00000347
-0.04118728
1
92
2.98
2.90
907/11860
Core
fees-bitcoinfees-info-cycle.csv
moneypot.csv
55.87049772
218.06
49
24388
36197
11858
0.00000184
9.20999834
0.046778658
0.22037920
-0.04184142
-0.00000353
-0.04191394
1
338
3.05
5.70
N/A
Xekyo
commented at 8:42 pm on March 5, 2018:
member
We still need to take into account the waste though; two outputs with the same effective value could still have different waste values which may make one output more desireable than another even with the same values. When we backtrack, we aren’t always cutting off a branch; we could have backtracked in order to find something else with a lower waste value.
Okay: We should not allow two outputs with the same effective value and same waste. I guess that would mean that we should potentially sort the UTXO for BnB by effective value first and then by waste.
in
src/wallet/coinselection.cpp:1
in
6d4586d130outdated
waste is a tie-breaker or a short-circuit. Shouldn’t need it.
in
src/wallet/coinselection.cpp:22
in
6d4586d130outdated
17+/*
18+ * This is the Branch and Bound Coin Selection algorithm designed by Mark Erhardt. It is an exact match algorithm where the exact match
19+ * is a range with the lower bound being the value we want to spend and the upper bound being that value plus the additional cost
20+ * required to create and spend a change output. To do this, the algorithm builds a binary tree where each node is a UTXO and whether
21+ * that UTXO is included or not in the current coin selection. The tree is searched in include-first order. Each UTXO is first included
22+ * and the current selection is evaluated for whether it is within the current threshold. If it is over the threshold, we try excluding
in
src/wallet/coinselection.cpp:41
in
6d4586d130outdated
31+ * An additional optimization of this algorithm implemented here is a lookahead value which maintains the total value of the UTXO set
32+ * of all unexplored UTXOs (i.e. UTXOs that have not yet been included or excluded). This allows us to cut a branch if the remaining
33+ * amount is not sufficient to reach our target.
34+ *
35+ * SelectCoinsBnB Arguments:
36+ * const std::vector<CInputCoin>& utxo_pool -> The set of UTXOs that we are choosing from. These UTXOs will be sorted in descending order
in
src/wallet/coinselection.cpp:96
in
c2e2db1806outdated
79+ if (remaining_tries <= 0) { // Too many tries, exit
80+ break;
81+ } else if (value_ret > target_value + cost_of_change) { // Selected value is out of range, go back and try other branch
82+ backtrack = true;
83+ } else if (curr_waste > best_waste) { // Don't select things which we know will be more wasteful
84+ backtrack = true;
Do we have a test that checks whether this behaves correctly for fee rates below the longterm expected fee rate? (I seem to remember that we discussed that before, and we came to the conclusion that it is correct, but if we had a test I’d be more comfortable.)
in
src/wallet/coinselection.cpp:156
in
c2e2db1806outdated
150+ if (best_selection.empty()) {
151+ return false;
152+ }
153+
154+ // Set output set
155+ value_ret = 0;
The value_ret above is not necessarily our best set. It is used for tracking our work during the selection so it may not actually be the value for the selection we want to use. It should probably be a different variable.
in
src/wallet/coinselection.cpp:37
in
859160cfdboutdated
32+ * of all unexplored UTXOs (i.e. UTXOs that have not yet been included or excluded). This allows us to cut a branch if the remaining
33+ * amount is not sufficient to reach our target.
34+ *
35+ * SelectCoinsBnB Arguments:
36+ * const std::vector<CInputCoin>& utxo_pool -> The set of UTXOs that we are choosing from. These UTXOs will be sorted in descending order
37+ * by value and the CInputCoins' values are their effective values.
in
src/wallet/coinselection.cpp:83
in
859160cfdboutdated
78+ {
79+ if (remaining_tries <= 0) { // Too many tries, exit
80+ break;
81+ } else if (value_track + lookahead < target_value) { // Cannot possibly reach target with the amount remaining in the lookahead.
82+ // This also catches if we are at a leaf node and still have not met the target value
83+ if (depth == 0) { // At the first utxo, no possible selections, so exit
Removed. Also compacted the next 2 else ifs with this one to make one large else if with or.
achow101 force-pushed
on Mar 6, 2018
achow101 force-pushed
on Mar 6, 2018
achow101 force-pushed
on Mar 6, 2018
in
src/wallet/coinselection.cpp:113
in
235da3d4ecoutdated
108+ --depth;
109+
110+ // Walk backwards to find the first utxo which has not has its second branch traversed
111+ while (!selection.at(depth)) {
112+ selection.at(depth) = false;
113+ selection.at(depth) = false;
actually can remove both of these, selection.at(depth) is already false.
in
src/wallet/coinselection.cpp:113
in
bf280e4659outdated
108+ --depth;
109+
110+ // Walk backwards to find the first utxo which has not has its second branch traversed
111+ while (!selection.at(depth)) {
112+ selection.at(depth) = false;
113+ selection.at(depth) = false;
in
src/wallet/coinselection.cpp:111
in
235da3d4ecoutdated
106+ if (backtrack) {
107+ backtrack = false; // Reset
108+ --depth;
109+
110+ // Walk backwards to find the first utxo which has not has its second branch traversed
111+ while (!selection.at(depth)) {
I’m not loving the way we track depth, it gets a bit confusing to make sure it stays in bounds. I don’t know if it’s possible to hit in practice, but if you are trying to pay 0 total value, then you’ll end up here trying to do selection.at(-1).
I agree, but we shouldn’t have a 0 value target in practice. I could just add a check to make sure that depth is greater than 0 before decrementing it.
yeah we should do something.
maybe change while loop to:
while (depth >= 0 && !selection.at(depth)) {
and then take the if (depth < 0) check out of the while loop and put it afterwards, and then it’s
and then you can get rid of the if (!done)
and in fact you could actually break instead of setting done=true and get rid of done and have the while loop be a for loop over remaining tries instead
morcos
commented at 8:38 pm on March 6, 2018:
member
in progress
in
src/wallet/coinselection.cpp:134
in
bf280e4659outdated
129+ curr_waste -= (utxo_pool.at(depth).fee - utxo_pool.at(depth).long_term_fee);
130+ ++depth;
131+ }
132+ } else { // Moving forwards, continuing down this branch
133+ // Assert that this utxo is not negative. It should never be negative, effective value calculation should have removed it
134+ assert(utxo_pool.at(depth).txout.nValue >= 0);
in
src/wallet/coinselection.cpp:84
in
d55accd6e5outdated
83+ if (remaining_tries <= 0) { // Too many tries, exit
84+ break;
85+ }
86+ // Conditions for starting a backtrack
87+ else if ( value_track + lookahead < target_value || // Cannot possibly reach target with the amount remaining in the lookahead.
88+ value_track > target_value + cost_of_change || // Selected value is out of range, go back and try other branch
in
src/wallet/coinselection.cpp:107
in
d55accd6e5outdated
105+ // Backtracking, moving backwards
106+ if (backtrack) {
107+ backtrack = false; // Reset
108+ --depth;
109+
110+ // Walk backwards to find the first utxo which has not has its second branch traversed
in
src/wallet/wallet.cpp:1601
in
a1b36b1e9eoutdated
1596+ int totalBytes = CalculateMaximumSignedTxSize(txn, pwallet, txouts);
1597+ if (totalBytes == -1) return -1;
1598+ int witnessversion = 0;
1599+ std::vector<unsigned char> witnessprogram;
1600+ // We don't want to multi-count segwit empty vin and flag bytes
1601+ if (txout.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
instagibbs
commented at 10:16 pm on March 6, 2018:
This is wrong, we need to look for segwit spends directly, then decrements by 1 virtual byte, since these are witness bytes.
morcos
commented at 10:19 pm on March 6, 2018:
member
still in progress
achow101 force-pushed
on Mar 6, 2018
in
src/wallet/coinselection.cpp:46
in
f41f751f2eoutdated
41+ * std::set<CInputCoin>& out_set -> This is an output parameter for the set of CInputCoins that have been selected.
42+ * CAmount& value_ret -> This is an output parameter for the total value of the CInputCoins that were selected.
43+ * CAmount& fee_ret -> This is an output parameter for the value of the transaction fees for the CInputCoins that were selected.
44+ */
45+
46+static const int remaining_tries = 100000;
in
src/wallet/coinselection.cpp:113
in
f41f751f2eoutdated
108+ while (depth >= 0 && !selection.at(depth)) {
109+ // Step back one
110+ lookahead += utxo_pool.at(depth).txout.nValue;
111+ --depth;
112+ }
113+ if (depth < 0) { // We have walked back to the first utxo and no branch is untraversed. No solution, exit.
perhaps move that check here, to avoid deferencencing an invalid iterator.
achow101 force-pushed
on Mar 7, 2018
achow101 force-pushed
on Mar 7, 2018
achow101 force-pushed
on Mar 7, 2018
achow101 force-pushed
on Mar 7, 2018
in
src/wallet/wallet.cpp:2787
in
4eba2d5729outdated
2782 bool pick_new_inputs = true;
2783 CAmount nValueIn = 0;
2784+
2785+ // BnB selector is the only selector used when this is true.
2786+ // That should only happen on the first pass through the loop.
2787+ bool use_bnb = true;
In commit “Copy KnapsackSolver coin selection code to coinselection.cpp” (5e64d67dc2bf4961a7efc7e3e160fd2629072a20)
It would be a little easier to confirm there are no unintentional changes in behavior if this commit moved code instead of copying it. Doing this would also make the later “Have SelectCoinsMinConf and SelectCoins use BnB or Knapsack” (f93ae0c1d3476bfe7d3460d46179998eeda0cd46) commit simpler.
As far as I can see, though, the main change here is dropping the output.fSpendable, output.nDepth, output.tx IsFromMe and TransactionWithinChainLimit checks that were in SelectCoinsMinConf. Other than that, changes made to this code are:
renaming setCoinsRet to out_set, nValueRet to value_ret, etc
sorting coins in descending order instead of sorting then reversing
ryanofsky
commented at 4:14 pm on March 7, 2018:
member
utACKe98badc2c519308949f7511201c38633fb32f8d9. Left various minor notes below, which you should feel free to ignore.
in
src/wallet/coinselection.cpp:33
in
51952ad62coutdated
28+ * now minus the cost to spend the current inputs later, plus the amount exceeding the target value. We search the tree to find the
29+ * set of UTXOs which falls within our range and minimizes waste.
30+ *
31+ * An additional optimization of this algorithm implemented here is a lookahead value which maintains the total value of the UTXO set
32+ * of all unexplored UTXOs (i.e. UTXOs that have not yet been included or excluded). This allows us to cut a branch if the remaining
33+ * amount is not sufficient to reach our target.
I’d like to suggest the following for the above description:
This is the Branch and Bound Coin Selection algorithm designed by Murch. It searches for an input set that can pay for the spending target and does not exceed the spending target by more than the cost of creating and spending a change output. The algorithm uses a depth-first search on a binary tree. In the binary tree, each node corresponds to the inclusion or the omission of a UTXO. UTXOs are sorted by their effective values and the trees is explored deterministically per the inclusion branch first. At each node, the algorithm checks whether the selection is within the target range. While the selection has not reached the target range, more UTXOs are included. When a selection’s value exceeds the target range, the complete subtree deriving from this selection can be omitted. At that point, the last included UTXO is deselected and the corresponding omission branch explored instead. The search ends after the complete tree has been searched or after a limited number of tries.
The search continues to search for better solutions after one solution has been found. The best solution is chosen by minimizing the waste metric. The waste metric is defined as the cost to spend the current inputs at the given fee rate minus the long term expected cost to spend the inputs, plus the amount the selection exceeds the spending target:
The algorithm uses two additional optimizations. A lookahead keeps track of the total value of the unexplored UTXOs. A subtree is not explored if the lookahead indicates that the target range cannot be reached. Further, it is unnecessary to test equivalent combinations. This allows us to skip testing the inclusion of UTXOs that match the effective value and waste of an omitted predecessor.
in
src/wallet/coinselection.cpp:104
in
73cee79007outdated
99+ // Backtracking, moving backwards
100+ if (backtrack) {
101+ backtrack = false; // Reset
102+ --depth;
103+
104+ // Walk backwards to find the first utxo which had not has its second branch traversed
in
src/wallet/wallet.cpp:2505
in
9c0c8777c1outdated
2501 std::vector<COutput> vCoins(vAvailableCoins);
25022503 // coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
2504 if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs)
2505 {
2506+ // For now, don't use BnB if preset inputs are selected. TODO: Enable this later
instagibbs
commented at 6:32 pm on March 7, 2018:
member
needs rebase
achow101 force-pushed
on Mar 7, 2018
achow101
commented at 6:48 pm on March 7, 2018:
member
Rebased
in
src/wallet/coinselection.cpp:51
in
f87a620362outdated
46+ * std::set<CInputCoin>& out_set -> This is an output parameter for the set of CInputCoins that have been selected.
47+ * CAmount& value_ret -> This is an output parameter for the total value of the CInputCoins that were selected.
48+ * CAmount not_input_fees -> The fees that need to be paid for the stuff that aren't inputs
49+ */
50+
51+static const int TOTAL_TRIES = 100000;
in
src/wallet/coinselection.cpp:40
in
f87a620362outdated
35+ * equivalent combinations. This allows us to skip testing the inclusion of UTXOs that match the effective value and waste of an
36+ * omitted predecessor.
37+ *
38+ * The Branch and Bound algorithm is described in detail in Murch's Master Thesis: https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf
39+ *
40+ * SelectCoinsBnB Arguments:
in
src/wallet/coinselection.cpp:48
in
f87a620362outdated
43+ * const CAmount& target_value -> This is the value that we want to select. It is the lower bound of the range.
44+ * const CAmount& cost_of_change -> This is the cost of creating and spending a change output. This plus target_value is the upper bound
45+ * of the range.
46+ * std::set<CInputCoin>& out_set -> This is an output parameter for the set of CInputCoins that have been selected.
47+ * CAmount& value_ret -> This is an output parameter for the total value of the CInputCoins that were selected.
48+ * CAmount not_input_fees -> The fees that need to be paid for the stuff that aren't inputs
eklitzke
commented at 9:25 pm on March 7, 2018:
contributor
Just nits in this first pass.
instagibbs
commented at 3:14 pm on March 8, 2018:
member
linter failing the tests:
equivalent combinations. This allows us to skip testing the inclusion of UTXOs that match the effective value and waste of an
^—- failure generated from contrib/devtools/lint-whitespace.sh
in
src/wallet/coinselection.cpp:35
in
f87a620362outdated
30+ *
31+ * waste = selectionTotal - target + inputs × (currentFeeRate - longTermFeeRate)
32+ *
33+ * The algorithm uses two additional optimizations. A lookahead keeps track of the total value of the unexplored UTXOs. A subtree
34+ * is not explored if the lookahead indicates that the target range cannot be reached. Further, it is unnecessary to test
35+ * equivalent combinations. This allows us to skip testing the inclusion of UTXOs that match the effective value and waste of an
In commit “Calculate and store the number of bytes required to spend an input” (dcf6d17c9269dcba1233ca696296ed8cee3ea3bf)
Maybe add a comment here to explain scriptWitness part, like “scriptWitness size is added here because witnesses and txins are split up in segwit serialization.”
in
src/wallet/wallet.cpp:1587
in
dcf6d17c92outdated
1582+ txouts.emplace_back(mi->second.tx->vout[input.prevout.n]);
1583+ }
1584+ return CalculateMaximumSignedTxSize(tx, pWallet, txouts);
1585+}
1586+
1587+// txouts needs to be in the order of tx.vin
Never mind, misinterpreted “in the order of.” I was suggesting you could assert the two vectors were the same size.
in
src/wallet/wallet.cpp:1588
in
dcf6d17c92outdated
1583+ }
1584+ return CalculateMaximumSignedTxSize(tx, pWallet, txouts);
1585+}
1586+
1587+// txouts needs to be in the order of tx.vin
1588+int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *pWallet, const std::vector<CTxOut> txouts)
535@@ -527,6 +536,9 @@ class COutput
536 int i;
537 int nDepth;
538539+ /** Pre-computed estimated size of this output as a fully-signed input in a transaction */
540+ int nInputBytes;
In commit “Calculate and store the number of bytes required to spend an input” (dcf6d17c9269dcba1233ca696296ed8cee3ea3bf)
Would be good to name GetTxOutSpendSize consistently with CalculateMaximumSignedTxSize() since they are basically doing the same thing. Maybe CalculateMaximumSignedInputSize.
In commit “Have SelectCoinsMinConf and SelectCoins use BnB or Knapsack” (44657d0374d7fb25cca5cd098b27a5163f139de6)
Would be nice to share spendable / depth / fromme / chainlimit checks between bnb and knapsack solvers. Maybe they could be factored out into a coin IsEligible function that both loops could call.
In commit “Have SelectCoinsMinConf and SelectCoins use BnB or Knapsack” (44657d0374d7fb25cca5cd098b27a5163f139de6)
I’m not sure having pcoin and i variables actually makes the code easier to read. It would seem simpler to drop these extra variables and just write output.tx and output.i directly.
In commit “Have SelectCoinsMinConf and SelectCoins use BnB or Knapsack” (44657d0374d7fb25cca5cd098b27a5163f139de6)
Maybe some of these arguments could be moved to structs, since there are so many values that have to be passed along from CreateTransaction -> SelectCoins -> SelectCoinsMinConf, and SelectCoinsMinConf is pretty rewritten in this commit anyway.
I’ve created two structs, one for the confirmation target and one for additional coin selection parameters.
ryanofsky
commented at 4:07 pm on March 9, 2018:
member
That was done in order to make the changes clearer and easier to follow. Otherwise it would be very large commit that is not reviewer friendly IMO.
My suggestion to move code to coinselection.cpp instead of copying it in #10637 (review) would make a combined SelectCoins / CreateTransaction commit smaller, and I think would make it easier to see how behavior is changing.
But I don’t think it’s worth spending much effort to restructure commits unless you are expecting to have a number of new reviewers look at this.
in
src/wallet/wallet.cpp:2914
in
851f71b84boutdated
2908@@ -2889,20 +2909,12 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
2909 txNew.vin.push_back(CTxIn(coin.outpoint,CScript(),
2910 nSequence));
29112912- // Fill in dummy signatures for fee calculation.
2913- if (!DummySignTx(txNew, setCoins)) {
2914+ nBytes = CalculateMaximumSignedTxSize(txNew, this);
In commit “Use BnB in CreateTransaction on the first pass through the loop” (851f71b84b12581cce933fd066ded98d213295ad)
This change and other nBytes updates in CreateTransaction would seem to make more sense as part of the “Calculate and store the number of bytes” commit instead of the “Use BnB” commit since they are refactorings that don’t change behavior.
in
src/wallet/wallet.cpp:2853
in
851f71b84boutdated
2845@@ -2833,23 +2846,30 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
2846 if (pick_new_inputs) {
2847 nValueIn = 0;
2848 setCoins.clear();
2849- if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, &coin_control))
2850+ if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, not_input_fees, nFeeRateNeeded, coin_control, use_bnb, change_prototype_size, CalculateMaximumSignedInputSize(change_prototype_txout, this)))
2851 {
2852- strFailReason = _("Insufficient funds");
2853- return false;
2854+ // If BnB was used, it was the first pass. No longer the first pass and continue loop with knapsack.
In commit “Use BnB in CreateTransaction on the first pass through the loop” (851f71b84b12581cce933fd066ded98d213295ad)
Would update comment to mention that SelectCoins call will change use_bnb to false if it was not used, since it’s not obvious that use_bnb is an input/output parameter. Could also split use_bnb into separate input and output parameters like bnb_used and disable_bnb.
sipa
commented at 4:50 pm on March 9, 2018:
member
@achow101 We have a policy that every commit in a pull request must compile and pass tests. This helps with review, if you know you can assume that every incremental change is a fully-functional codebase, and there aren’t things you need to understand from future commits to see how things fit together. It also helps with git bisects, making sure that things don’t accidentally get broken and unbroken somewhere in a codebase’s history, leading you on a goose chase.
Currently, none of your commits compile, except the last one. Fixing this would help review greatly.
in
src/wallet/wallet.cpp:2826
in
851f71b84boutdated
2820@@ -2811,6 +2821,9 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
2821 fFirst = false;
2822 txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
2823 }
2824+ } else if (use_bnb){
2825+ // On the first pass BnB selector, include the fee cost for outputs
2826+ not_input_fees += nFeeRateNeeded.GetFee(::GetSerializeSize(txout, SER_NETWORK, PROTOCOL_VERSION));
In commit “Use BnB in CreateTransaction on the first pass through the loop” (851f71b84b12581cce933fd066ded98d213295ad)
Might be safer/simpler to always compute not_input_fees correctly, instead of it leaving partially set depending on fSubtractFeeFromAmount and use_bnb values (in cases where it isn’t used).
In commit “Have SelectCoinsMinConf and SelectCoins use BnB or Knapsack” (44657d0374d7fb25cca5cd098b27a5163f139de6)
Seems like it would be a little simpler for the caller and more consistent as a way of passing params if a tx_noinputs_size value were passed instead of not_input_fees.
In commit “Implement Branch and Bound coin selection in a new file” (fe2ce81)
Might be clarifying to add “curr” prefix to other variables besides curr_waste. E.g. you could rename value_trackcurr_value, selectioncurr_selection, lookaheadcurr_available_value.
In commit “Implement Branch and Bound coin selection in a new file” (fe2ce81f30c288b7caf16ffed3b270272d4b5818)
Seems like backtrack variable should just be declared inside the loop. It looks like it will always be false at the top of every iteration given if (backtrack) backtrack = false; below.
in
src/wallet/coinselection.cpp:58
in
fe2ce81f30outdated
53+{
54+ out_set.clear();
55+ CAmount value_track = 0;
56+
57+ int depth = 0;
58+ std::vector<bool> selection(utxo_pool.size()); // select the utxo at this index
In commit “Implement Branch and Bound coin selection in a new file” (fe2ce81f30c288b7caf16ffed3b270272d4b5818)
It might be helpful if there was a comment explaining how selection gets updated during the loop. The way the code uses depth variable to select coins from beginning and backtrack variable to deselect coins from the end is pretty unusual (or at least is something I never encountered before). For example, if there are three coins, and no optimizations apply, the coin selections that get tested are:
I’m going to squash, rebase, and re-commit this in order to make each commit compilable and better organized. The original commits will be saved here: https://github.com/achow101/bitcoin/tree/bnb-orig. The ending diff between what I am going to do and the original should be the same, just reorganized.
Calculate and store the number of bytes required to spend an input12ec29d3bb
Store effective value, fee, and long term fee in CInputCoin
Have CInputCOin store effective value information. This includes the effective
value itself, the fee, and the long term fee for the input
f84fed8eb6
achow101 force-pushed
on Mar 10, 2018
achow101
commented at 6:58 am on March 10, 2018:
member
I have gone through and reorganized the commits. Each commit should now be individually compilable. Additionally I have taken @ryanofsky’s suggestion of moving the code for the KnapsackSolver instead of copying it. The commits that change SelectCoins, SelectCoinsMinConf, and CreateTransaction to support BnB have been combined into one large-ish commit.
The code should be functionally the same as before; the only differences are some variable names and whitespace. The original code can be found in this branch: https://github.com/achow101/bitcoin/tree/bnb-orig. Diff’ing the diffs of the two branches will show the changes that were made.
achow101 force-pushed
on Mar 10, 2018
in
src/bench/coin_selection.cpp:65
in
328733eaf0outdated
I would prefer to continue to use what is currently done in the codebase unless we agreed to change the style.
in
src/wallet/coinselection.cpp:18
in
328733eaf0outdated
13+ return a.effective_value > b.effective_value;
14+ }
15+} descending;
16+
17+/*
18+ * This is the Branch and Bound Coin Selection algorithm designed by Murch. It searches for an input set that can pay for the
These long line doc strings don’t fit on my screen, even when my browser is maximized :-(
in
src/wallet/coinselection.cpp:60
in
328733eaf0outdated
45+ * @param std::set<CInputCoin>& out_set -> This is an output parameter for the set of CInputCoins that have been selected.
46+ * @param CAmount& value_ret -> This is an output parameter for the total value of the CInputCoins that were selected.
47+ * @param CAmount not_input_fees -> The fees that need to be paid for the outputs and fixed size overhead (version, locktime, marker and flag)
48+ */
49+
50+static const size_t TOTAL_TRIES = 100000;
in
src/wallet/test/coinselector_tests.cpp:27
in
328733eaf0outdated
22+// we repeat those tests this many times and only complain if all iterations of the test fail
23+#define RANDOM_REPEATS 5
24+
25+std::vector<std::unique_ptr<CWalletTx>> wtxn;
26+
27+typedef std::set<CInputCoin> CoinSet;
in
src/wallet/test/coinselector_tests.cpp:23
in
328733eaf0outdated
18+// how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
19+#define RUN_TESTS 100
20+
21+// some tests fail 1% of the time due to bad luck.
22+// we repeat those tests this many times and only complain if all iterations of the test fail
23+#define RANDOM_REPEATS 5
eklitzke
commented at 3:42 pm on March 10, 2018:
contributor
More nits, and I think you should consider running these changes through clang-format. But this looks pretty good (at least the parts I understand).
The thing I’m most curious about is how the random shuffle can cause the test cases to fail. That’s probably due to a lack of understanding on my part about how BnB works, could you explain?
eklitzke
commented at 3:44 pm on March 10, 2018:
contributor
I will also try to run this locally to test it out.
achow101
commented at 5:19 pm on March 10, 2018:
member
The random failure thing is part of the tests for the old coinselection stuff and is unrelated to BnB.
in
src/wallet/wallet.cpp:2447
in
34145b6a02outdated
in
src/wallet/wallet.cpp:2498
in
34145b6a02outdated
2502 }
25032504-bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const
2505+// use_bnb is both an input param, and an output param. It indicates that BnB should be
2506+// used but also informs the caller whether BnB was used
2507+bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params) const
In commit “Have SelectCoinsMinConf and SelectCoins use BnB or Knapsack and use it” (34145b6a0237afd0160c9624339f49fd34e524ff)
Might be worth pulling use_bnb parameter out of coin_selection_params, because it’s the only mutable member, and otherwise this be a const reference.
I do still think my previous suggestion of splitting use_bnb inout param up into separate disable_bnb (input) and used_bnb (output) params (https://github.com/bitcoin/bitcoin/pull/10637#discussion_r173502504)/ would make the logic most clear, and would obviate the need for the new comment above explaining dual meanings of use_bnb.
In commit “Implement Branch and Bound coin selection in a new file” (a406c66f1b113e6418eec3b03e0301017478634e)
I think it would make the traversal code easier to understand to get rid of depth variable and the trailing false entries in the curr_selection vector. I implemented this in cf0a82d49112e204112b54d76e661a8e66a28c8b, (fetchable with git fetch -n https://github.com/ryanofsky/bitcoin pr/nodepth:nodepth).
I’ve used your commit. It will be squashed into a406c66
ryanofsky
commented at 6:19 pm on March 12, 2018:
member
utACK328733eaf0b4dfcca0f58cc1062fe6bd51566281. Changes since previous review: reorganizing all the commits, implementing various suggestions like variable renames, grouping params into structs, factoring out output eligible function.
in
src/wallet/wallet.cpp:2472
in
34145b6a02outdated
2472+ if (!OutputEligibleForSpending(output, eligibilty_filter))
2473+ continue;
2474+
2475+ CInputCoin coin(output.tx->tx, output.i);
2476+ coin.effective_value = coin.txout.nValue - (output.nInputBytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(output.nInputBytes));
2477+ // Only include outputs that are not negative effective value (i.e. not dust)
instagibbs
commented at 7:12 pm on March 12, 2018:
in
src/wallet/wallet.cpp:2473
in
34145b6a02outdated
2473+ continue;
2474+
2475+ CInputCoin coin(output.tx->tx, output.i);
2476+ coin.effective_value = coin.txout.nValue - (output.nInputBytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(output.nInputBytes));
2477+ // Only include outputs that are not negative effective value (i.e. not dust)
2478+ if (coin.txout.nValue > 0) {
instagibbs
commented at 7:16 pm on March 12, 2018:
this should be coin.txout.effective_value…. which likely means there is no test for this.
instagibbs changes_requested
in
src/wallet/wallet.cpp:2473
in
328733eaf0outdated
2589- setCoinsRet.insert(vValue[i]);
2590- nValueRet += vValue[i].txout.nValue;
2591+ CInputCoin coin(output.tx->tx, output.i);
2592+ coin.effective_value = coin.txout.nValue - (output.nInputBytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(output.nInputBytes));
2593+ // Only include outputs that are not negative effective value (i.e. not dust)
2594+ if (coin.txout.nValue > 0) {
instagibbs
commented at 7:44 pm on March 12, 2018:
github is seemingly hiding comments, so saying this here again:
this should be coin.txout.effective_value…. which likely means there is no test for this.
instagibbs
commented at 10:56 pm on March 12, 2018:
SCMC, with use_bnb being true, two outputs which summed together reach the target? If you remove the negative effective value one, it overshoots the window.
I added a test that runs SelectCoinsMinConf with an input with negative effective value. If that input somehow makes it through into SelectCoinsBnB, it will trigger an assert causing the test to fail. That should be sufficient to check for this case.
achow101 force-pushed
on Mar 12, 2018
in
src/wallet/test/coinselector_tests.cpp:223
in
81e11bb029outdated
223+ CoinSet setCoinsRet;
224+ CAmount nValueRet;
225+ bool bnb_used;
226+ empty_wallet();
227+ add_coin(1);
228+ vCoins.at(0).nInputBytes = 40; // Make sure that it has a egative effective value. The next check should assert if this somehow got through. Otherwise it will fail
instagibbs
commented at 2:43 pm on March 13, 2018:
ryanofsky
commented at 3:31 pm on March 13, 2018:
member
utACKc3136ea7e936714886b1329402931a3ec514afaf with squash. Changes since last review: adding bnb_used variable, getting rid of depth variable, using coin effective value instead of nValue to check for dust and testing this, moving effective value assert out of search loop.
instagibbs approved
instagibbs
commented at 3:37 pm on March 13, 2018:
member
utACK
Implement Branch and Bound coin selection in a new file
Create a new file for coin selection logic and implement the BnB algorithm in it.
0185939be6
Move output eligibility to a separate functionce7435cf1e
Use a struct for output eligibility
Instead of specifying 3 parameters, use a struct for those parameters
in order to reduce the number of arguments to SelectCoinsMinConf.
Changes CInputCoin to coinselection and to use CTransactionRef in
order to avoid a circular dependency. Also moves other coin selection
specific variables out of wallet.h to coinselectoin.h
4b2716da46
Add tests for the Branch and Bound algorithm4566ab75f2
Move current coin selection algorithm to coinselection.{cpp,h}
Moves the current coin selection algorithm out of SelectCoinsMinConf
and puts it in coinselection.{cpp,h}. The new function, KnapsackSolver,
instead of taking a vector of COutputs, will take a vector of CInputCoins
that is prepared by SelectCoinsMinConf.
fb716f7b25
Move original knapsack solver tests to coinselector_tests.cppcd927ff328
Add a GetMinimumFeeRate function which is wrapped by GetMinimumFeefab04887c2
Have SelectCoinsMinConf and SelectCoins use BnB or Knapsack and use it
Allows SelectCoinsMinConf and SelectCoins be able to switch between
using BnB or Knapsack for choosing coins.
Has SelectCoinsMinConf do the preprocessing necessary to support either
BnB or Knapsack. This includes calculating the filtering the effective
values for each input.
Uses BnB in CreateTransaction to find an exact match for the output.
If BnB fails, it will fallback to the Knapsack solver.
6a34ff5335
Benchmark BnB in the worst case where it exhausts76d2f068a4
Add a test to make sure that negative effective values are filtered73b5bf2cb4
achow101 force-pushed
on Mar 13, 2018
achow101
commented at 4:47 pm on March 13, 2018:
member
Squashed
ryanofsky
commented at 5:25 pm on March 13, 2018:
member
utACK73b5bf2cb40720bb4e4436ea63b5badf3d89ceb9. Only change since last review is comment fix and squash.
instagibbs
commented at 5:30 pm on March 13, 2018:
member
This is a metadata mirror of the GitHub repository
bitcoin/bitcoin.
This site is not affiliated with GitHub.
Content is generated from a GitHub metadata backup.
generated: 2025-04-08 09:12 UTC
This site is hosted by @0xB10C More mirrored repositories can be found on mirror.b10c.me