-
Notifications
You must be signed in to change notification settings - Fork 278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wallet.fund() messes up existing inputs #639
Conversation
Pull Request Test Coverage Report for Build 1527358618
💛 - Coveralls |
MTX clone() would not carry over coinview, in order to get around this the CoinSelector would remove all inputs and add them back which made working with partially signed transactions a pain, this should fix that issue.
Previous version of interactive-name-swap used a ugly hack to get around limitations on wallet funding, since wallet.fund() no longer wipes previous inputs, the previous implementation is not longer the recommended way.
@@ -1839,7 +1838,7 @@ class Wallet extends EventEmitter { | |||
output.covenant.pushHash(nameHash); | |||
output.covenant.pushU32(height); | |||
output.covenant.pushHash(nonce); | |||
reveal.addOutpoint(Outpoint.fromTX(bid, bidOuputIndex)); | |||
reveal.addCoin(bidCoin); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are technically not necessary because the Coin would attempted to be added to CoinView if it's already not there. But this is cleaner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think now with this logic: edc1f25 using addCoin()
is actually an optimization!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addCoin will still stay an optimization if we use my suggestion.
Have you looked in to this? I wonder why we don't clone the Lines 102 to 113 in eea15c2
For example you probbaly won't need to add |
I'm going to continue to review this, but I think it would be helpful to have an explicit test case to show what the change is. i.e. a straightforward test that fails without the patch and succeeds with the patch. I know we have the atomic swap example working already but I think something simple in mtx-test.js or wallet-test.js would be helpful too. |
// CoinView has other properties like UndoCoins and BitView | ||
// that an MTX doesn't care about. We just want a copy of the | ||
// coins spent by the MTX. | ||
for (const [key, value] of mtx.view.map.entries()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason behind not cloning the view is that in most cases it's not necessary and it's complex.
Also we have couple of variations on the view. Notably, the new WalletCoinView
needs different approach to be properly cloned.
So in most cases mtx
clone is done like:
const nmtx = mtx.clone();
nmtx.view = mtx.view;
Referencing the old view. It needs double checking if there's a case where old way messes things up, but I doubt. Most views in the wallet side are short lived.
@@ -158,11 +164,19 @@ class MTX extends TX { | |||
addCoin(coin) { | |||
assert(coin instanceof Coin, 'Cannot add non-coin.'); | |||
|
|||
const input = Input.fromCoin(coin); | |||
// Avoid duplicate inputs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addCoin
, addInput
, addOutpoint
and addTX
are utilities that are meant to be as simple as possible.
They don't do anything extra, pretty dumb methods. So complicating addCoin here I don't think is necessary, as you can always use pretty cheap check: isSane
or checkSanity
after you are done with everything.
@@ -158,11 +164,19 @@ class MTX extends TX { | |||
addCoin(coin) { | |||
assert(coin instanceof Coin, 'Cannot add non-coin.'); | |||
|
|||
const input = Input.fromCoin(coin); | |||
// Avoid duplicate inputs | |||
for (const input of this.inputs) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another note is this is inefficient way of checking this O(n^2)
. If we really wanted to do this(and as I mentioned above, we should not), we have to create new Map for the hash/index pair of each input similar to CoinView (Not coinview is not good case for this, as it may not have some coins)
@@ -1122,12 +1136,31 @@ class MTX extends TX { | |||
assert(options, 'Options are required.'); | |||
assert(options.changeAddress, 'Change address is required.'); | |||
|
|||
// Ensure backward compatibility with the old API: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really want to remove old API? Other than incorrectly managing preferred inputs, was there another thing wrong with it?
As far as I can see, what we want to fix is - when tx.clone
ing in the coinSelector, CoinSelector should use existing mtx.view as reference lookup table for these coins and don't expect them to be supplied with coins
array that is coming from the database. That way "preferred" inputs that have coins wont get removed, but be checked against supplied coinview.
// (pre-signed) inputs OR add new "preferred" inputs for covenants. | ||
// The logic below was moved here from CoinSelector.fund() in case | ||
// the old method was used and "preferred" coins are not yet in the view. | ||
for (const input of this.inputs) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will become totally unnecessary, if we do the thing mentioned above.
Also Complexity of this will be off charts. We already have issues with the coins
being too big for the big wallets, and iterating it for every input will just explode )
if (this.inputs.size > 0) { | ||
const coins = []; | ||
|
||
for (let i = 0; i < this.inputs.size; i++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here instead of pushing blindly, we check if view
has it, if it already has it we don't push this. Everything else can remain same.
@@ -1839,7 +1838,7 @@ class Wallet extends EventEmitter { | |||
output.covenant.pushHash(nameHash); | |||
output.covenant.pushU32(height); | |||
output.covenant.pushHash(nonce); | |||
reveal.addOutpoint(Outpoint.fromTX(bid, bidOuputIndex)); | |||
reveal.addCoin(bidCoin); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addCoin will still stay an optimization if we use my suggestion.
Closing in favour of #666 |
wallet.fund()
used to mess up existing inputs for reasons mentioned below, this made it incredibly awkward to create transactions with custom inputs, this should fix those issues.As I understand this, the issue was
The CoinSelector works by making a clone of the MTX and trying to add inputs to it till the output's value is met, and then returning those coins.
hsd/lib/primitives/mtx.js
Lines 1420 to 1427 in 484ede0
Because of how MTX cloning is done, the CoinView is not cloned, CoinView contained the input values, without which determining if a inputAmount is adequate is not possible.
hsd/lib/primitives/mtx.js
Lines 103 to 109 in 484ede0
So there is this "hack" in place which basically works by checking the prevout in the utxo set, and then "adding them back" and returning their list, but this would not always work cause the prevout could possibly be not in the utxo.
hsd/lib/primitives/mtx.js
Lines 1692 to 1717 in 484ede0
After which all the inputs of the MTX are wiped and the inputs from above are added "back"
hsd/lib/primitives/mtx.js
Lines 1126 to 1133 in 484ede0
This required changes to the current implementation ofinteractive-name-swap
test since it used a "hack" which is no longer needed.Backward compatibility with the previous hack is maintained for now.
Compatibility with Bob and Shakedex needs to be checked before merging, but is likely gonna be a non-issue.