mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
Miscellaneous refactors and updates (#5590)
- Added a new Invariant: `ValidPseudoAccounts` which checks that all pseudo-accounts behave consistently through creation and updates, and that no "real" accounts look like pseudo-accounts (which means they don't have a 0 sequence).
- `to_short_string(base_uint)`. Like `to_string`, but only returns the first 8 characters. (Similar to how a git commit ID can be abbreviated.) Used as a wrapped sink to prefix most transaction-related messages. More can be added later.
- `XRPL_ASSERT_PARTS`. Convenience wrapper for `XRPL_ASSERT`, which takes the `function` and `description` as separate parameters.
- `SField::sMD_PseudoAccount`. Metadata option for `SField` definitions to indicate that the field, if set in an `AccountRoot` indicates that account is a pseudo-account. Removes the need for hard-coded field lists all over the place. Added the flag to `AMMID` and `VaultID`.
- Added functionality to `SField` ctor to detect both code and name collisions using asserts. And require all SFields to have a name
- Convenience type aliases `STLedgerEntry::const_pointer` and `STLedgerEntry::const_ref`. (`SLE` is an alias to `STLedgerEntry`.)
- Generalized `feeunit.h` (`TaggedFee`) into `unit.h` (`ValueUnit`) and added new "BIPS"-related tags for future use. Also refactored the type restrictions to use Concepts.
- Restructured `transactions.macro` to do two big things
1. Include the `#include` directives for transactor header files directly in the macro file. Removes the need to update `applySteps.cpp` and the resulting conflicts.
2. Added a `privileges` parameter to the `TRANSACTION` macro, which specifies some of the operations a transaction is allowed to do. These `privileges` are enforced by invariant checks. Again, removed the need to update scattered lists of transaction types in various checks.
- Unit tests:
1. Moved more helper functions into `TestHelpers.h` and `.cpp`.
2. Cleaned up the namespaces to prevent / mitigate random collisions and ambiguous symbols, particularly in unity builds.
3. Generalized `Env::balance` to add support for `MPTIssue` and `Asset`.
4. Added a set of helper classes to simplify `Env` transaction parameter classes: `JTxField`, `JTxFieldWrapper`, and a bunch of classes derived or aliased from it. For an example of how awesome it is, check the changes `src/test/jtx/escrow.h` for how much simpler the definitions are for `finish_time`, `cancel_time`, `condition`, and `fulfillment`.
5. Generalized several of the amount-related helper classes to understand `Asset`s.
6. `env.balance` for an MPT issuer will return a negative number (or 0) for consistency with IOUs.
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class Invariants_test : public beast::unit_test::suite
|
||||
{
|
||||
@@ -112,13 +113,13 @@ class Invariants_test : public beast::unit_test::suite
|
||||
{
|
||||
terActual = ac.checkInvariants(terActual, fee);
|
||||
BEAST_EXPECT(terExpect == terActual);
|
||||
auto const messages = sink.messages().str();
|
||||
BEAST_EXPECT(
|
||||
sink.messages().str().starts_with("Invariant failed:") ||
|
||||
sink.messages().str().starts_with(
|
||||
"Transaction caused an exception"));
|
||||
messages.starts_with("Invariant failed:") ||
|
||||
messages.starts_with("Transaction caused an exception"));
|
||||
for (auto const& m : expect_logs)
|
||||
{
|
||||
if (sink.messages().str().find(m) == std::string::npos)
|
||||
if (messages.find(m) == std::string::npos)
|
||||
{
|
||||
// uncomment if you want to log the invariant failure
|
||||
// message log << " --> " << m << std::endl;
|
||||
@@ -1435,6 +1436,127 @@ class Invariants_test : public beast::unit_test::suite
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
}
|
||||
|
||||
void
|
||||
testValidPseudoAccounts()
|
||||
{
|
||||
testcase << "valid pseudo accounts";
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
AccountID pseudoAccountID;
|
||||
Preclose createPseudo =
|
||||
[&, this](Account const& a, Account const& b, Env& env) {
|
||||
PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
|
||||
|
||||
// Create vault
|
||||
Vault vault{env};
|
||||
auto [tx, vKeylet] =
|
||||
vault.create({.owner = a, .asset = xrpAsset});
|
||||
env(tx);
|
||||
env.close();
|
||||
if (auto const vSle = env.le(vKeylet); BEAST_EXPECT(vSle))
|
||||
{
|
||||
pseudoAccountID = vSle->at(sfAccount);
|
||||
}
|
||||
|
||||
return BEAST_EXPECT(env.le(keylet::account(pseudoAccountID)));
|
||||
};
|
||||
|
||||
/* Cases to check
|
||||
"pseudo-account has 0 pseudo-account fields set"
|
||||
"pseudo-account has 2 pseudo-account fields set"
|
||||
"pseudo-account sequence changed"
|
||||
"pseudo-account flags are not set"
|
||||
"pseudo-account has a regular key"
|
||||
*/
|
||||
struct Mod
|
||||
{
|
||||
std::string expectedFailure;
|
||||
std::function<void(SLE::pointer&)> func;
|
||||
};
|
||||
auto const mods = std::to_array<Mod>({
|
||||
{
|
||||
"pseudo-account has 0 pseudo-account fields set",
|
||||
[this](SLE::pointer& sle) {
|
||||
BEAST_EXPECT(sle->at(~sfVaultID));
|
||||
sle->at(~sfVaultID) = std::nullopt;
|
||||
},
|
||||
},
|
||||
{
|
||||
"pseudo-account sequence changed",
|
||||
[](SLE::pointer& sle) { sle->at(sfSequence) = 12345; },
|
||||
},
|
||||
{
|
||||
"pseudo-account flags are not set",
|
||||
[](SLE::pointer& sle) { sle->at(sfFlags) = lsfNoFreeze; },
|
||||
},
|
||||
{
|
||||
"pseudo-account has a regular key",
|
||||
[](SLE::pointer& sle) {
|
||||
sle->at(sfRegularKey) = Account("regular").id();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (auto const& mod : mods)
|
||||
{
|
||||
doInvariantCheck(
|
||||
{{mod.expectedFailure}},
|
||||
[&](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::account(pseudoAccountID));
|
||||
if (!sle)
|
||||
return false;
|
||||
mod.func(sle);
|
||||
ac.view().update(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject& tx) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
createPseudo);
|
||||
}
|
||||
for (auto const pField : getPseudoAccountFields())
|
||||
{
|
||||
// createPseudo creates a vault, so sfVaultID will be set, and
|
||||
// setting it again will not cause an error
|
||||
if (pField == &sfVaultID)
|
||||
continue;
|
||||
doInvariantCheck(
|
||||
{{"pseudo-account has 2 pseudo-account fields set"}},
|
||||
[&](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::account(pseudoAccountID));
|
||||
if (!sle)
|
||||
return false;
|
||||
|
||||
auto const vaultID = ~sle->at(~sfVaultID);
|
||||
BEAST_EXPECT(vaultID && !sle->isFieldPresent(*pField));
|
||||
sle->setFieldH256(*pField, *vaultID);
|
||||
|
||||
ac.view().update(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject& tx) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
createPseudo);
|
||||
}
|
||||
|
||||
// Take one of the regular accounts and set the sequence to 0, which
|
||||
// will make it look like a pseudo-account
|
||||
doInvariantCheck(
|
||||
{{"pseudo-account has 0 pseudo-account fields set"},
|
||||
{"pseudo-account sequence changed"},
|
||||
{"pseudo-account flags are not set"}},
|
||||
[&](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::account(A1.id()));
|
||||
if (!sle)
|
||||
return false;
|
||||
sle->at(sfSequence) = 0;
|
||||
ac.view().update(sle);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
testPermissionedDEX()
|
||||
{
|
||||
@@ -1622,10 +1744,12 @@ public:
|
||||
testValidNewAccountRoot();
|
||||
testNFTokenPageInvariants();
|
||||
testPermissionedDomainInvariants();
|
||||
testValidPseudoAccounts();
|
||||
testPermissionedDEX();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Invariants, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
Reference in New Issue
Block a user