Skip to content

Conversation

jonasschnelli
Copy link
Contributor

@jonasschnelli jonasschnelli commented Feb 3, 2017

This is a required step for using the current bumpfee logic in the GUI.
The first two commits are "moveonly".

BumpFeeResult_INVALID_PARAMETER,
BumpFeeResult_WALLET_ERROR,
BumpFeeResult_MISC_ERROR,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some reason not to use exceptions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I though about using exceptions but came to the conclusion that a simple result state enum will (probably) allow more flexibility for further optimisations. But no strong opinion.

@sipa
Copy link
Member

sipa commented Feb 3, 2017

I prefer an enum here (I'll elaborate if wanted).

@laanwj
Copy link
Member

laanwj commented Feb 6, 2017

Concept ACK, it is better to have this in wallet than in the RPC code, especially as the code is going to be used by the GUI.

No opinion on how to handle the errors. Though returning an enum is consistent with some other wallet functions that return a status.

Aside: As said earlier on IRC I'm not entirely happy with the current trend of moving everything wallet related to CWallet - we're creating a "god class". It would be nice to see if we can parcel up the functionality and separate concerns, to avoid a huge file and exponentially growing complexity.

But in any case, this is a step forward...

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just left a few minor comments. Hopefully this can be merged soon to avoid conflicts.

Agree with laanwj about the "god class" problem, but it seems straightforward to solve. BumpFee could easily be a standalone function taking a CWallet* pointer, instead of a CWallet method.

*/
enum BumpFeeResult
{
BumpFeeResult_OK,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could use "enum class" here to avoid the "BumpFeeResult_" prefixes here while still requiring them where the values are referenced.

// calculation, but we should be able to refactor after priority is removed).
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
// be IsAllFromMe).
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears that you are only copying, not moving the CalculateMaximumSignedTxSize function in this commit (8993411d7b3e6fe2c4baa917874941ed95c39188 [Move] move bumpfee core logic to CWallet). Would make more sense to remove it from rpcwallet.cpp in this commit instead of the next one.

* Bumps the fee of an opt-in-RBF transaction <txid>,
* replacing it with a new transaction <wtxNew>
*/
enum BumpFeeResult
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum is not being used in this commit (8993411d7b3e6fe2c4baa917874941ed95c39188 [Move] move bumpfee core logic to CWallet), would probably make more sense to define in the next commit.

@@ -2313,71 +2313,85 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx)
if (!pwalletMain->DummySignTx(txNew, vCoins)) {
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
// implies that we can sign for every input.
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction contains inputs that cannot be signed");
return -1;
}
return GetVirtualTransactionSize(txNew);
}

enum CWallet::BumpFeeResult CWallet::BumpFee(uint256 txid, int newConfirmTarget, bool specifiedConfirmTarget, CAmount totalFee, bool newTxReplaceable, CAmount& nOldFee, CAmount& nNewFee, std::shared_ptr<CWalletTx>& wtxNew, std::vector<std::string>& vErrors)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can drop "enum" in c++.

// TODO: see if JSON-RPC has a standard way of returning a response
// along with an exception. It would be good to return information about
// wtxBumped to the caller even if marking the original transaction
// replaced does not succeed for some reason.
vErrors.push_back("Error: Created new bumpfee transaction but could not mark the original transaction as replaced.");
}

wtxNew = std::make_shared<CWalletTx>(wtxBumped);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a good idea to return a mutable pointer to a copy of the CWalletTx that isn't actually in mapwallet, because changes a caller makes to this pointer might have unexpected or no effects. Since the shared pointer doesn't appear to be needed here or in #9697, I think it'd be better to return just wtxBumped.tx or wtxBumped.GetHash() instead.

@jonasschnelli jonasschnelli force-pushed the 2017/02/qt_bump branch 2 times, most recently from ba57464 to 65e2e00 Compare February 7, 2017 14:52
@jonasschnelli
Copy link
Contributor Author

Completely rewrote the refactor.

  • Moved/refactored relevant code into feeBumper.cpp (CFeeBump) which breaks the pattern of CWallet as god class.
  • Split BumpFee into three steps, Bump, Sign (new CWallet function, can later be used by CreateTransaction) and Commit.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK cc585d5

I looked at all the moves in meld and verified that no behavior should be changing. I left a few comments on how I think the feebumper API might be improved, but do not think any work in that area should block this PR, because this is already doing a great job of moving things where they need to be, and this doesn't seem like an easy PR to keep maintaining and updating.

One suggestion I might make, though, is to maybe squash some of the commits. For example the commit 641c99c doesn't compile and might be annoying for git-bisect. And the multiple moves might be confusing for reviewers looking at the PR commit-by-commit.

Will take a look at the followup PR 9697 as soon as I get a chance.

@@ -2318,50 +2318,70 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx)
return GetVirtualTransactionSize(txNew);
}

enum CWallet::BumpFeeResult CWallet::BumpFee(uint256 txid, int newConfirmTarget, bool specifiedConfirmTarget, CAmount totalFee, bool newTxReplaceable, CAmount& nOldFee, CAmount& nNewFee, std::shared_ptr<CWalletTx>& wtxNew, std::vector<std::string>& vErrors)
bool CWallet::SignTransaction(std::shared_ptr<CMutableTransaction> tx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't see a reason to require shared_ptr here. CMutableTransaction& would allow callers to use it or not use it. Similarly for other shared_ptr arguments.

@@ -451,7 +451,9 @@ struct CMutableTransaction
};

typedef std::shared_ptr<const CTransaction> CTransactionRef;
typedef std::shared_ptr<CMutableTransaction> CMutableTransactionRef;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's a reason to have these typedefs or even to use shared pointers in feebumper at all. Shared pointers are a great tool in places where they are actually needed, but plain c++ references that don't need to be deferenced, don't require reference counting, and can't be null are a better option in the places where they work. Another disadvantage of shared_ptr interfaces is that the requirement to wrap everything in shared_ptr can start spreading virally from callers, to callers callers, etc.

class CFeeBumper
{
public:
enum class BumpFeeResult
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the less verbose BumpFeeResult::OK would be preferable to CFeeBumper::BumpFeeResult::OK

Could move enum above class to accomplish this. Or could change "enum class" to "enum" for CFeeBumper::OK.

CAmount getOldFee() const { return nOldFee; }
CAmount getNewFee() const { return nNewFee; }
CMutableTransactionRef getBumpedTxRef() const { return mtx; }
uint256 getBumpedTxId() const { return bumpedTxid; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing all these getters makes me think this API grouping is not a good idea. We have this class with two functions that do real work (constructor and commit), and then a bunch of members, and a bunch of getters, and somehow the caller is supposed to keep track of all the getters and know which ones are valid to call at different stages in the object lifetime. Also, member lifetimes are unnecessarily prolonged (constructor's return values like nOldFee/nNewFee/currentResult/vErrors do not need to be kept alive in order for the commit method to do it's job).

I think a better API here would look something like:

void CreateBumpTx(<input const ref args>, <output ref args>);
void CommitBumpTx(<input const ref args>, <output ref args>);

without the stateful class.

@jonasschnelli
Copy link
Contributor Author

Added a commit c81a8a7ddef6f3c1e4cb0816aa12ae16b91cbf02 that addresses the shared pointer for CMutableTransaction.

The second commit a9c25b611504225d61291365ab6025dbd426e2d0 moves the enum class BumpFeeResult into the global scope.

I haven't implemented the proposed API change (#9681 (comment)) which maybe could be a follow up. I'n not sure if this would go into a direction to purely static in/out functions.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK a9c25b611504225d61291365ab6025dbd426e2d0

Should squash the commits down though to reduce churn, one or two commits would be ideal.

// wallet, with a valid index into the vout array.
for (auto& input : tx.vin) {
const auto mi = pwalletMain->mapWallet.find(input.prevout.hash);
assert(mi != pwalletMain->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe split into two asserts, to be a little easier to read, and so you can tell which condition failed from the error.

@jonasschnelli jonasschnelli force-pushed the 2017/02/qt_bump branch 2 times, most recently from 42a66ce to 4ce1d6f Compare March 3, 2017 15:26
@jonasschnelli
Copy link
Contributor Author

Needed rebase (#8775).
Squashed down to three relevant commits.

@ryanofsky
Copy link
Contributor

utACK 4ce1d6fb995bc30551571fe3621f185ea66eae7f

Comparing against previous a9c25b611504225d61291365ab6025dbd426e2d0, there were no changes except for some whitespace and a renamed local iterator variable.

@jonasschnelli
Copy link
Contributor Author

[...] renamed local iterator variable.

Had to change this to respect the -Wshadow (#9828) change

@ryanofsky
Copy link
Contributor

It would be really nice to have this merged soon. This PR is blocking the bumpfee GUI PR #9697, which is blocking the RBF checkbox PR #9592, which is blocking the RBF RPC PR #9672, and possibly the -walletrbf default PR #9527. It would be great to start clearing this backlog.

I guess maybe this needs another reviewer? It also needs a rebase to avoid trivial conflicts with #9853 and #9643.


// Calculate the expected size of the new transaction.
int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a minor bug here. This needs to pass pWallet into CalculateMaximumSignedTxSize so it can stop using pwalletMain. When making this change, I also had to make the CWallet::DummySignTx method const. Feel free to use my complete fix: ryanofsky@731ca5c

@jonasschnelli
Copy link
Contributor Author

Rebased (carefully).
Added @ryanofsky's ryanofsky@731ca5c

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK a6183598ea15d0b9acd8e22b3f8af6ac444f9db6, but any chance you could squash the following three commits into one?

  • 26df8ae0fb9403acc8c64fe60d891f74a197a231 [Move] move bumpfee core logic to CWallet
  • e9c88c855e558b7372445414a57ef635d18d22eb Refactor Bumpfee core functionality
  • a6183598ea15d0b9acd8e22b3f8af6ac444f9db6 fixup! Refactor BumpFee, move relevant code into CFeeBumper

The first commit there doesn't compile, and the third commit was meant to be a fixup to another commit which no longer exists. Also, having the code move twice in one PR (rpcwallet -> wallet -> feebumper) only makes the PR harder to rebase and review, AFAICT.


if (wtx.GetDepthInMainChain() != 0) {
vErrors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
currentResult = BumpFeeResult::MISC_ERROR;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be WALLET_ERROR not MISC_ERROR in master:

throw JSONRPCError(RPC_WALLET_ERROR, "Transaction has been mined, or is conflicted with a mined transaction");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Great catch... fixed now.

SignatureData sigdata;
if (!ProduceSignature(TransactionSignatureCreator(pwallet, &txNewConst, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
CFeeBumper feeBump(pwalletMain, hash, newConfirmTarget, specifiedConfirmTarget, totalFee, replaceable);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pwalletMain should be pwallet, also a few places below

@jonasschnelli
Copy link
Contributor Author

Fixed @ryanofsky findings and squashed into a single commit.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly tested ACK 1d8529b.

Ran tests, confirmed the RPC error code and pwalletMain usages were fixed and that there were no other changes since last my previous ACK.

I did notice one case where the PR seemed to be unintentionally changing behavior (no longer raising error on 0 or negative totalFee option values, see comment in rpcwallet.cpp), but other than that I confirmed this should only be moving code, not changing behavior.

@@ -2933,157 +2850,55 @@ UniValue bumpfee(const JSONRPCRequest& request)
}
} else if (options.exists("totalFee")) {
totalFee = options["totalFee"].get_int64();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "Bumpfee move request parameter interaction to the top":

This commit seems to be changing behavior in a way that I think would be good to avoid. Previously, if you passed a negative or 0 totalFee option, you would get an "Insufficient totalFee" error. But now, an automatic fee will be calculated, ignoring the totalFee value that was passed.

I think you could fix this by writing:

totalFee = options["totalFee"].get_int64();
if (totalFee <= 0) {
    throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid totalFee %s (must be greater than 0)", FormatMoney(totalFee)));
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. That's correct. I'm adding a commit that restores that behaviour.

Copy link
Contributor

@jnewbery jnewbery left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've quickly reviewed the parts which I think have changed during the move. I haven't verified that the code move hasn't changed any of the functionality in the code that has been moved.

A couple of comments from me about error handling. Otherwise, lightly tested ACK 1d8529b

int nIn = 0;
for (auto& input : tx.vin) {
std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash);
assert(mi != mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer this not to be an assert. Is it possible to return an error rather than assert?

I know that this was here before the code move, but my worry is that moving it to wallet.cpp might encourage people to call this function without knowing that they must lock the wallet and verify that all the transaction inputs are in the wallet map first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. This is a valid point. Adding a commit that returns false instead of the assert().

{
AssertLockHeld(pWallet->cs_wallet);
vErrors.clear();
if (txid.IsNull() || !pWallet->mapWallet.count(txid)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if you need to check this here, when you already checked it in CFeeBumper::CFeeBumper()?

If this check fails, I think you need to return false from this function immediately rather than fall through, since the function goes on to deference the transaction in the map.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's sane to keep this check... but your right, it should immediately return. Will fix.

@jonasschnelli
Copy link
Contributor Author

Addressed @ryanofsky and @jnewbery points.
Hopefully soon ready for merge.

@ryanofsky
Copy link
Contributor

utACK ccbdb2ceadade6059d7c375592f01db7feda05f6. The three new commits, as well as the main commit 1d8529b (previously ACKed) all look good.

Copy link
Contributor

@JeremyRubin JeremyRubin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utack ccbdb2ceadade6059d7c375592f01db7feda05f6, couple of nits/things to fix.

// calculation, but we should be able to refactor after priority is removed).
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
// be IsAllFromMe).
int64_t CalculateMaximumSignedTxSize(const CWallet *pWallet, const CTransaction &tx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to change the signature of CalculateMaximumSignedTxSize, and the prior version seems better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. This seems to be a rebase issue. Will change.

for (auto& input : tx.vin) {
const auto mi = pWallet->mapWallet.find(input.prevout.hash);
assert(mi != pWallet->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
vCoins.emplace_back(std::make_pair(&(mi->second), input.prevout.n));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make_pair with emplace_back is not necessary, may as well fix it.


// Calculate the expected size of the new transaction.
int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(pWallet, *wtx.tx);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might make more sense for CalculateMaximumSignexTxSize to take a vErrors argument, as this is non-obvious to me (from local code) why maxNewTxSize < 0 means there were unsignable inputs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, though the scope of this PR is – ideally – pure refactoring to make it usable over the UI. Let's optimise such things later.

currentResult = BumpFeeResult::INVALID_PARAMETER;
return;
}
CAmount requiredFee = pWallet->GetRequiredFee(maxNewTxSize);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reason to change this line from CWallet::GetRequiredFee?

} else {
// if user specified a confirm target then don't consider any global payTxFee
if (specifiedConfirmTarget) {
nNewFee = pWallet->GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, CAmount(0));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also static, please keep CWallet::

}
// otherwise use the regular wallet logic to select payTxFee or default confirm target
else {
nNewFee = pWallet->GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also static, please keep CWallet::

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This seems to be again a rebase issue. Though, changing it to pWallet would allow to be more flexible when multi wallet takes off... but changing it back for now.

// Now modify the output to increase the fee.
// If the output is not large enough to pay the fee, fail.
CAmount nDelta = nNewFee - nOldFee;
assert(nDelta > 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you should refactor this to:

 if (nOldFee >= nNewFee) {
    vErrors.push_back("Attempt to increase fee decreased fee.");
    currentResult = BumpFeeResult::WALLET_ERROR;     
    return;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do this later and focus now on refactoring out the core functionality.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to just change it now... #8456 (comment) is where it got introduced, I think it still may be possible to trigger & throwing seems better for this than crashing your node.

I'll leave it up to you though -- just let me know if you decide not to & I'll prepare a patch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to keep the scope ("refactoring"), behaviours changes have already sneaked into this PR.


CFeeBumper feeBump(pwallet, hash, newConfirmTarget, specifiedConfirmTarget, totalFee, replaceable);
BumpFeeResult res = feeBump.getResult();
if (res != BumpFeeResult::OK)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should just make this a case of the switch.

bool CFeeBumper::commit(CWallet *pWallet)
{
AssertLockHeld(pWallet->cs_wallet);
vErrors.clear();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you should fail to commit if (!vErrors.empty())?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Will change.

currentResult = BumpFeeResult::OK;
}

bool CFeeBumper::commit(CWallet *pWallet)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would prefer to see commit be done via RAII (but maybe that's bad if you make a container of CFeeBumpers...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. not sure if RAII for a bump commit is appropriate. IMO the commit (it actually does sign and broadcast) should be a clearly visible call.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if you do something like

commit() {
    assert(!comitted)
    comitted = true;
     ...
}
~CFeeBumper() {
    if (vErrors.empty())
    assert(comitted);
}

To ensure that if a CFeeBump could be comitted, it is & comitting isn't tried multiple times?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure but this looks after too much hand holding. But if someone wants to implement this, yeah, maybe after this PR?

@jonasschnelli
Copy link
Contributor Author

Thanks @JeremyRubin for the review. Fixed the relevant points.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 3 new commits in 99070569662dde84a4f4b1d38cd53762063f53d2 since previously ACKed ccbdb2ceadade6059d7c375592f01db7feda05f6. This seems like it is ready to be merged.

@@ -20,7 +20,7 @@
// calculation, but we should be able to refactor after priority is removed).
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
// be IsAllFromMe).
int64_t CalculateMaximumSignedTxSize(const CWallet *pWallet, const CTransaction &tx)
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *pWallet)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "Restore CalculateMaximumSignedTxSize function signature":

Previous signature took a CWallet reference instead of a pointer. (Just an observation, feel free to leave it.)

@@ -30,7 +30,7 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *pWal
for (auto& input : tx.vin) {
const auto mi = pWallet->mapWallet.find(input.prevout.hash);
assert(mi != pWallet->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
vCoins.emplace_back(std::make_pair(&(mi->second), input.prevout.n));
vCoins.emplace_back(&(mi->second), input.prevout.n);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "Use static calls for GetRequiredFee and GetMinimumFee, remove make_pair from emplace_back"

Could drop parens around mi->second. (Just an observation, feel free to leave.)

@@ -232,6 +232,9 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf
bool CFeeBumper::commit(CWallet *pWallet)
{
AssertLockHeld(pWallet->cs_wallet);
if (!vErrors.empty()) {
return false;
}
vErrors.clear();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vErrors.clear() is now redundant.

// Now modify the output to increase the fee.
// If the output is not large enough to pay the fee, fail.
CAmount nDelta = nNewFee - nOldFee;
assert(nDelta > 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to just change it now... #8456 (comment) is where it got introduced, I think it still may be possible to trigger & throwing seems better for this than crashing your node.

I'll leave it up to you though -- just let me know if you decide not to & I'll prepare a patch.

currentResult = BumpFeeResult::OK;
}

bool CFeeBumper::commit(CWallet *pWallet)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if you do something like

commit() {
    assert(!comitted)
    comitted = true;
     ...
}
~CFeeBumper() {
    if (vErrors.empty())
    assert(comitted);
}

To ensure that if a CFeeBump could be comitted, it is & comitting isn't tried multiple times?

const uint256 txid;
uint256 bumpedTxid;
CMutableTransaction mtx;
std::vector<std::string> vErrors;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do not use vErrors to store more than one error, so may as well just use a std::string (this clarifies it is set-once).

Alternatively, you could use a smart pointer to a string and just check that it is not nullptr.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L265 and L274 can both add an error that then get's spit out over the JSON-RPC "errors" array. I haven't changed this in this PR.

bool CFeeBumper::commit(CWallet *pWallet)
{
AssertLockHeld(pWallet->cs_wallet);
if (!vErrors.empty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe change to/add && BumpFeeResult::OK == currentResult

@jonasschnelli
Copy link
Contributor Author

Amend-changed the last commit to remove the redundant vErrors.clear().
Added a check for currentResult == BumpFeeResult::OK during commit.

Copy link
Contributor

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good, only issue I have is the API where the RPC calls SignTransaction directly feels super strange.

}
CAmount requiredFee = CWallet::GetRequiredFee(maxNewTxSize);
if (totalFee < requiredFee) {
vErrors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having this and the totalFee error be separate is somewhat strange, maybe combine them now that the checks are in the same place (in an additional commit) into one generic error so we dont have users chasing error messages for a few rounds?


CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConfirmTarget, bool specifiedConfirmTarget, CAmount totalFee, bool newTxReplaceable)
:
txid(txidIn),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: pass txidIn by reference or std::move it here.

const std::vector<std::string>& getErrors() const { return vErrors; }
CAmount getOldFee() const { return nOldFee; }
CAmount getNewFee() const { return nNewFee; }
CMutableTransaction* getBumpedTxRef() { return &mtx; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a super strange API...you're getting a reference to memory owned by CFeeBumper, passing it to the wallet, then calling a method owned by CFeeBumper which assumes you mutated its (seemingly internal) state. Given you're already passing a pwallet reference into CFeeBumper, maybe replace this with bool SignTransaction(CWallet *pwalletIn)?

CMutableTransaction* getBumpedTxRef() { return &mtx; }
uint256 getBumpedTxId() const { return bumpedTxid; }

bool commit(CWallet *pWalletNonConst);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you document the return value here? It initially confused me because I thought it would return false if it set vErrors, when, in fact, it might add vErrors and then return true.

@jonasschnelli
Copy link
Contributor Author

jonasschnelli commented Mar 30, 2017

Added a commit that addresses @TheBlueMatt's points.

  • Changed CFeeBumper's interface. It has now a signTransaction method instead of the – indeed strange – getBumpedTxRef().
  • Made use of std::move for the txIn.
  • Added some comments

@jonasschnelli
Copy link
Contributor Author

Rebased (trivial) after #9424

@ryanofsky
Copy link
Contributor

utACK 5f59d3e, confirmed first 8 commits (previously acked as 99070569662dde84a4f4b1d38cd53762063f53d2) did not change in the rebase, other than the log category fix. New 9th commit also looks fine.

I think it would be good to merge this because it is a burden to maintain separately and keep reviewing. It's been rebased many times and is blocking other prs.

@laanwj laanwj merged commit 5f59d3e into bitcoin:master Apr 7, 2017
laanwj added a commit that referenced this pull request Apr 7, 2017
5f59d3e Improve CFeeBumper interface, add comments, make use of std::move (Jonas Schnelli)
0df22ed Cancel feebump is vErrors is not empty (Jonas Schnelli)
44cabe6 Use static calls for GetRequiredFee and GetMinimumFee, remove make_pair from emplace_back (Jonas Schnelli)
bb78c15 Restore CalculateMaximumSignedTxSize function signature (Jonas Schnelli)
51ea44f Use "return false" instead assert() in CWallet::SignTransaction (Jonas Schnelli)
bcc72cc Directly abort execution in FeeBumper::commit if wallet or tx is not available (Jonas Schnelli)
2718db0 Restore invalid fee check (must be > 0) (Jonas Schnelli)
0337a39 Refactor Bumpfee core functionality (Jonas Schnelli)
d1a95e8 Bumpfee move request parameter interaction to the top (Jonas Schnelli)

Tree-SHA512: 0e6d1f3322ed671fa2291e59ac9556ce4646bc78267edc6eedc46b0014b7b08aa83c30315358b911d82898847d4845634a18b67e253a7b699dcc852eb2652c07
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants