-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Support for the Deposit Authorization account root flag #2239
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #2239 +/- ##
========================================
Coverage 70.26% 70.26%
========================================
Files 706 706
Lines 61410 61410
========================================
Hits 43147 43147
Misses 18263 18263 Continue to review full report at Codecov.
|
Is having exactly the minimum reserve of XRP enough to get an account operable again (accounts can and do reach exactly 0 XRP)? Maybe a |
@MarkusTeufelberger, Yes, having the minimum reserve is (usually) a bit more than necessary to make an account useful. The protocol allows people to chew into their reserve to pay fees. So once they have the minimum reserve (or less) they can pay fees for any transaction they care to. However they can't make an XRP payment that would take them below the minimum reserve. The only situation where having the minimum reserve would not be sufficient is if the network fee rose above the minimum reserve. That's worth thinking about for an account that may be exclusively multisigned with 8 signers. They would have to always pay a fee of at least 9 times the current network fee. But, even for the multisigned case, I don't think a network fee that high is likely to become a steady-state condition. Thanks for asking. Good question! |
@nbougalis and @seelabs, I know you folks are frantic busy. Would you like me to pick a couple of other reviewers for this pull request? Thanks. |
@scottschurr Apologies. I'll plan on reviewing this early next week after my higher-priority tasks are finished. |
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.
Left some minor feedback, but looks good.
src/ripple/app/tx/impl/PayChan.cpp
Outdated
// Check whether the destination account requires deposit authorization | ||
if (txAccount != dst) | ||
{ | ||
// If dst set lsfDepositAuth and signer is not dst, then fail. |
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.
Nit: I think we're better off leaving comments such as this out out of the code base. It's a comment that explains the one line of code below it, and that one line is already straight forward. I don't care about this particular comment, but it's something I noticed in a couple places in this PR.
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.
Noted. You and I have a different threshold for when a comment is appropriate, mine being much lower than most software engineers. I've seldom met a comment I didn't like 😉. I doubt you'll train me out of writing non-essential comments at this point in my career. Speaking only for myself, they help me think about code I'm writing and reason about code I'm reading. But I'm happy to discuss removing any comments that you feel distract from the code.
|
||
bool const sigWithMaster {[tx, acct = account_] () |
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.
You are copying tx
and acct
here, I think you want refs.
Note: for an immediately invoked function expression, such as this one, I prefer capture all by ref [&]
. What the lambda actually captures isn't important in such a context.
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.
Good catch on the refs. Thanks.
I hear your preference on lambda captures. My personal preference is still to follow Scott Meyers' advice, "Item 31: Avoid default capture modes." I find his reasoning compelling, even for local lambdas.
src/ripple/app/tx/impl/Payment.cpp
Outdated
TER terResult {tesSUCCESS}; | ||
|
||
// Determine whether the destination requires deposit authorization. | ||
bool const reqDepositAuth = [] (ReadView& view, std::uint32_t dstFlags) |
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 initialization is simple enough that it doesn't need the lambda:
bool const reqDepositAuth = sleDst->getFlags() & lsfDepositAuth &&
view().rules().enabled(featureDepositAuth);
src/ripple/app/tx/impl/Payment.cpp
Outdated
// 1. payment is not direct XRP to XRP and | ||
// 2. the destination has lsfDepositAuth set, | ||
// then the payment is not authorized. | ||
terResult = tecNO_PERMISSION; |
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.
Instead of setting terResult
would could return here (and in other places in this function). I think doing to simplifies the code. I took a cut at a refactor here: seelabs@d6efff9
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.
Done. I incorporated your refactor as a [FOLD] commit.
d72755d
to
c4e1244
Compare
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.
Thanks for the review @seelabs, especially for spotting the lambda capture-by-value mistake. I think I've addressed most of your comments.
src/ripple/app/tx/impl/PayChan.cpp
Outdated
// Check whether the destination account requires deposit authorization | ||
if (txAccount != dst) | ||
{ | ||
// If dst set lsfDepositAuth and signer is not dst, then fail. |
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.
Noted. You and I have a different threshold for when a comment is appropriate, mine being much lower than most software engineers. I've seldom met a comment I didn't like 😉. I doubt you'll train me out of writing non-essential comments at this point in my career. Speaking only for myself, they help me think about code I'm writing and reason about code I'm reading. But I'm happy to discuss removing any comments that you feel distract from the code.
src/ripple/app/tx/impl/Payment.cpp
Outdated
// 1. payment is not direct XRP to XRP and | ||
// 2. the destination has lsfDepositAuth set, | ||
// then the payment is not authorized. | ||
terResult = tecNO_PERMISSION; |
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.
Done. I incorporated your refactor as a [FOLD] commit.
|
||
bool const sigWithMaster {[tx, acct = account_] () |
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.
Good catch on the refs. Thanks.
I hear your preference on lambda captures. My personal preference is still to follow Scott Meyers' advice, "Item 31: Avoid default capture modes." I find his reasoning compelling, even for local lambdas.
c4e1244
to
4fa77c1
Compare
Rebased to 0.80.1. |
4fa77c1
to
988a494
Compare
Rebased to 0.90.0-b1. |
Jenkins Build SummaryBuilt from this commit Built at 20171222 - 00:06:54 Test Results
|
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.
re-reviewed Refactor Payment::doApply
only (I believe this is the only change since I last reviewed).
👍
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.
There's a transaction-breaking change here we need to fix.
src/ripple/app/tx/impl/Payment.cpp
Outdated
|
||
bool const bRipple = paths || sendMax || !saDstAmount.native (); | ||
// XXX Should sendMax be sufficient to imply ripple? | ||
|
||
if (bRipple && reqDepositAuth) | ||
// If the destination has lsfDepositAuth set, then only direct |
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.
I prefer such comments above the block instead of interjected between the if
and the single-line statement within it, especially in the absence of surrounding braces. No change needed, really. Just passing commentary.
if (uSetFlag == asfDepositAuth || uClearFlag == asfDepositAuth) | ||
{ | ||
if (!ctx.rules.enabled(featureDepositAuth)) | ||
return temDISABLED; |
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.
Versions of rippled
without this code (i.e. any version before this patch is merged) with return tesSUCCESS
when trying to process an operation that specifies asfDepositAuth
(or indeed any unknown value) for either SetFlag
or ClearFlag
. So this is, ironically, a transaction-breaking change.
I propose that we either remove this block entirely from preflight
and fall through into doApply
which is what happens today (essentially treating this operation a nop
if no other changes are made), or that we simply add a distinct fix
amendment to add a block similar to this here:
if (ctx.rules.enabled(fixWhatever))
{
if (uSetFlag >= asfMaxValue || uClearFlags >= asfMaxValue)
return temINVALID_FLAG;
}
We could then define asfMaxValue
accordingly, and note that it needs to be maintained whenever new values are added.
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.
Excellent catch. I've removed this code and extended the AccountSet_test.cpp to make it apparent whether the DepositAuth
flag handling is similar to the other flags before and after the amendment goes live. Good eyes. 👀.
src/ripple/app/tx/impl/Escrow.cpp
Outdated
if (ctx_.view().rules().enabled(featureDepositAuth)) | ||
{ | ||
// NOTE: Escrow payments cannot be used to fund accounts | ||
if (! sled) |
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.
I don't think that it is possible for this situation to ever trigger. See: https://github.com/ripple/rippled/blob/3e5490ef6db9cbc3abdff04655d80952b288d097/src/ripple/app/tx/impl/Escrow.cpp#L229-L234.
I understand that this check was already present further down, and you just cloned it here for completeness, and maybe it's just better to actually move it in a single place, immediately after the lookup.
While this may seem like a transaction breaking change, I propose that it can't be. My rationale is this: we check in EscrowCreate
whether the destination exists and refuse to create the escrow if that is the case. Since accounts cannot be deleted, then it is not possible for an escrow to exist without the destination account existing.
We could also add the following invariant check, which would catch this if it ever were to happen. (warning: I just typed this out here; it's hasn't been compile-tested):
/**
* @brief Invariant: an escrow must not create a new account root.
*/
class NoFundingEscrow
{
bool fundedAccount_ = false;
public:
void
visitEntry(
uint256 const&,
bool,
std::shared_ptr<SLE const> const&,
std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER, beast::Journal const&);
};
void
NoFundingEscrow::visitEntry(
uint256 const&,
bool isDelete,
std::shared_ptr <SLE const> const& before,
std::shared_ptr <SLE const> const& after)
{
// If an account root didn't exist before and exists after the transaction
// then the transaction funded an account:
if (!before && after && after->getType() == ltACCOUNT_ROOT)
fundedAccount_ = true;
}
bool
NoFundingEscrow::finalize(STTx const&tx, TER, beast::Journal const& j)
{
if (!fundedAccount_)
return true;
auto const type = tx.getTxnType();
if (type == ttESCROW_CREATE || type == ttESCROW_FINISH || type == ttESCROW_CANCEL)
{
JLOG(j.fatal()) << "Invariant failed: an escrow operation funded an account";
return false;
}
return true;
}
Coincidentally, this invariant checker could be augmented to ensure that if an account root is created by a transaction (regardless of type) then its initial balance is greater than or equal to the reserve, because we don't allow partially funding a new account.
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.
Okay. I'm good with the rationale that the situation ought never to occur unless the ledger is corrupt. I still want to check for the impossible case -- I would feel bad about accessing through an unchecked shared_ptr
. But I'll check in only one place, that that check ought to never fire.
988a494
to
1f742df
Compare
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.
@nbougalis, thanks for the keen eye. I believe I've addressed the issues you found with the commit named [FOLD] Address review comments
.
src/ripple/app/tx/impl/Escrow.cpp
Outdated
if (ctx_.view().rules().enabled(featureDepositAuth)) | ||
{ | ||
// NOTE: Escrow payments cannot be used to fund accounts | ||
if (! sled) |
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.
Okay. I'm good with the rationale that the situation ought never to occur unless the ledger is corrupt. I still want to check for the impossible case -- I would feel bad about accessing through an unchecked shared_ptr
. But I'll check in only one place, that that check ought to never fire.
if (uSetFlag == asfDepositAuth || uClearFlag == asfDepositAuth) | ||
{ | ||
if (!ctx.rules.enabled(featureDepositAuth)) | ||
return temDISABLED; |
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.
Excellent catch. I've removed this code and extended the AccountSet_test.cpp to make it apparent whether the DepositAuth
flag handling is similar to the other flags before and after the amendment goes live. Good eyes. 👀.
1f742df
to
733422c
Compare
The DepositAuth feature allows an account to require that it signs for any funds that are deposited to the account. For the time being this limits the account to accepting only XRP, although there are plans to allow IOU payments in the future. The lsfDepositAuth protections are not extended to offers. If an account creates an offer it is in effect saying, “I will accept funds from anyone who takes this offer.” Therefore, the typical user of the lsfDepositAuth flag will choose never to create any offers. But they can if they so choose. The DepositAuth feature leaves a small gap in its protections. An XRP payment is allowed to a destination account with the lsfDepositAuth flag set if: - The Destination XRP balance is less than or equal to the base reserve and - The value of the XRP Payment is less than or equal to the base reserve. This exception is intended to make it impossible for an account to wedge itself by spending all of its XRP on fees and leave itself unable to pay the fee to get more XRP. This commit - adds featureDepositAuth, - adds the lsfDepositAuth flag, - adds support for lsfDepositAuth in SetAccount.cpp - adds support in Payment.cpp for rejecting payments that don't meet the lsfDepositAuth requirements, - adds unit tests for Payment transactions to an an account with lsfDepositAuth set. - adds Escrow and PayChan support for lsfDepositAuth along with as unit tests.
733422c
to
be45af3
Compare
Squashed. |
I'd like to see DepositAuth also allow incoming multi-signed Payments where the destination's active master or regular key pair is a signer on the Payment tx. Maybe that can come in a later PR. |
In 0.90.0-b3 |
Deposit Authorization allows an account to specify that it will not accept any deposit unless it signs the transaction. That means an account with with the DepositAuthorization flag set will not (usually) accept Payment transactions. It will accept Escrow and PaymentChannel deposits, but only if it signs the transaction that causes the transfer.
A legitimate concern was raised that an account with this flag set could put itself in an inoperable condition if it used all of its XRP. Without XRP it could not pay the necessary fee to sign a transaction to replenish its XRP.
Three approaches have been discussed to address this concern:
Have twice the fee to set the
lsfDepositAuth
flag on an account root. Then have no fee to clear the flag on the account root. Clearing thelsfDepositAuth
flag for free would allow the account to accept more XRP.If the account XRP balance falls below a particular level, then the
lsfDepositAuth
protections are removed, but only for small payments of XRP. The account's XRP balance must fall below the minimum reserve before an XRP Payment transaction will be accepted. Within that window, the largest XRP Payment that may be sent is equal to the XRP minimum reserve.One other approach that has been suggested is that, as standard procedures for checks, creating a check takes twice the standard fee and cashing a check requires no fee. This would allow a check to replenish the XRP on an account, once checks are implemented and live on the network.
After discussions and deliberations, I've chosen approach 2: allow small XRP payments if the account balance gets low. Here are the reasons for the choice:
Both Payment Channels and Escrow are affected, since they are (currently) the only methods that always allow funds to be deposited into an account with the
lsfDepositAuth
flag set.We hope to get this pull request integrated into the 0.90.0 release chain early on for testing on the AltNet.
Reviewers: @nbougalis for Escrow experience, @seelabs for Payment Channels experience.