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
Add Transaction Invariant Checks #2054
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
//------------------------------------------------------------------------------ | ||
/* | ||
This file is part of rippled: https://github.com/ripple/rippled | ||
Copyright (c) 2012-2016 Ripple Labs Inc. | ||
|
||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted, provided that the above | ||
copyright notice and this permission notice appear in all copies. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | ||
//============================================================================== | ||
|
||
#include <ripple/app/tx/impl/InvariantCheck.h> | ||
#include <ripple/basics/Log.h> | ||
|
||
namespace ripple { | ||
|
||
void | ||
XRPNotCreated::visitEntry( | ||
uint256 const&, | ||
bool isDelete, | ||
std::shared_ptr <SLE const> const& before, | ||
std::shared_ptr <SLE const> const& after) | ||
{ | ||
if(before) | ||
{ | ||
switch (before->getType()) | ||
{ | ||
case ltACCOUNT_ROOT: | ||
drops_ -= (*before)[sfBalance].xrp().drops(); | ||
break; | ||
case ltPAYCHAN: | ||
drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops(); | ||
break; | ||
case ltESCROW: | ||
drops_ -= (*before)[sfAmount].xrp().drops(); | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
|
||
if(after) | ||
{ | ||
switch (after->getType()) | ||
{ | ||
case ltACCOUNT_ROOT: | ||
drops_ += (*after)[sfBalance].xrp().drops(); | ||
break; | ||
case ltPAYCHAN: | ||
if (! isDelete) | ||
drops_ += ((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops(); | ||
break; | ||
case ltESCROW: | ||
if (! isDelete) | ||
drops_ += (*after)[sfAmount].xrp().drops(); | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
} | ||
|
||
bool | ||
XRPNotCreated::finalize(STTx const& tx, TER /*tec*/, beast::Journal const& j) | ||
{ | ||
auto fee = tx.getFieldAmount(sfFee).xrp().drops(); | ||
if(-1*fee <= drops_ && drops_ <= 0) | ||
return true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume this is a range because a tx could claim less than the specified fee if the relevant account has a balance lower than that, and is thus zeroed out. Correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sounds right to me ;) |
||
|
||
JLOG(j.fatal()) << "Invariant failed: XRP net change was " << drops_ << | ||
" on a fee of " << fee; | ||
return false; | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
void | ||
AccountRootsNotDeleted::visitEntry( | ||
uint256 const&, | ||
bool isDelete, | ||
std::shared_ptr <SLE const> const& before, | ||
std::shared_ptr <SLE const> const&) | ||
{ | ||
if (isDelete && before && before->getType() == ltACCOUNT_ROOT) | ||
accountDeleted_ = true; | ||
} | ||
|
||
bool | ||
AccountRootsNotDeleted::finalize(STTx const&, TER, beast::Journal const& j) | ||
{ | ||
if (! accountDeleted_) | ||
return true; | ||
|
||
JLOG(j.fatal()) << "Invariant failed: an account root was deleted"; | ||
return false; | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
void | ||
LedgerEntryTypesMatch::visitEntry( | ||
uint256 const&, | ||
bool, | ||
std::shared_ptr <SLE const> const& before, | ||
std::shared_ptr <SLE const> const& after) | ||
{ | ||
if (before && after && before->getType() != after->getType()) | ||
typeMismatch_ = true; | ||
|
||
if (after) | ||
{ | ||
switch (after->getType()) | ||
{ | ||
case ltACCOUNT_ROOT: | ||
case ltDIR_NODE: | ||
case ltRIPPLE_STATE: | ||
case ltTICKET: | ||
case ltSIGNER_LIST: | ||
case ltOFFER: | ||
case ltLEDGER_HASHES: | ||
case ltAMENDMENTS: | ||
case ltFEE_SETTINGS: | ||
case ltESCROW: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we wanted to be extra nitpicky, we could check if the respective amendment is enabled here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't believe we're passing rules or the feature set into the invariants right now, so we would have to add that if we wanted to do this. I'm happy to look into it more if there is sufficient value... |
||
case ltPAYCHAN: | ||
break; | ||
default: | ||
invalidTypeAdded_ = true; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
bool | ||
LedgerEntryTypesMatch::finalize(STTx const&, TER, beast::Journal const& j) | ||
{ | ||
if ((! typeMismatch_) && (! invalidTypeAdded_)) | ||
return true; | ||
|
||
if (typeMismatch_) | ||
{ | ||
JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch"; | ||
} | ||
|
||
if (invalidTypeAdded_) | ||
{ | ||
JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added"; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
|
||
void | ||
NoXRPTrustLines::visitEntry( | ||
uint256 const&, | ||
bool, | ||
std::shared_ptr <SLE const> const&, | ||
std::shared_ptr <SLE const> const& after) | ||
{ | ||
if (after && after->getType() == ltRIPPLE_STATE) | ||
{ | ||
// checking the issue directly here instead of | ||
// relying on .native() just in case native somehow | ||
// were systematically incorrect | ||
xrpTrustLine_ = | ||
after->getFieldAmount (sfLowLimit).issue() == xrpIssue() || | ||
after->getFieldAmount (sfHighLimit).issue() == xrpIssue(); | ||
} | ||
} | ||
|
||
bool | ||
NoXRPTrustLines::finalize(STTx const&, TER, beast::Journal const& j) | ||
{ | ||
if (! xrpTrustLine_) | ||
return true; | ||
|
||
JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created"; | ||
return false; | ||
} | ||
|
||
} // ripple | ||
|
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.
Because the whole point here is to check for programming errors elsewhere, I'm thinking we want to explicitly check all the known
LedgerEntryType
s here, and fail if we get an unknown type.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.
If we want to add this check for known types of LE, I'd be inclined to create a separate check for this rather than wrapping it into this one. @JoelKatz do you think it's worth adding a LedgerEntryType-is-valid invariant check?
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'd combine this check into
LedgerEntryTypesMatch
- in addition to checking that types match, we can ensure that only known LETs 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.
This works for me. If catching it in
LedgerEntryTypesMatch
or similar isn't enough to think through the other invariant classes, then we've got bigger problems.