Skip to content
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 invariant to validate that ClaimableBalanceEntries are immutable #2708

Merged
merged 4 commits into from
Sep 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 30 additions & 27 deletions src/invariant/LedgerEntryIsValid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ LedgerEntryIsValid::checkOnOperationApply(Operation const& operation,
if (!entryDelta.second.current)
continue;

auto s = checkIsValid(*entryDelta.second.current, currLedgerSeq, ver);
auto s = checkIsValid(*entryDelta.second.current,
entryDelta.second.previous, currLedgerSeq, ver);
if (!s.empty())
{
s += ": " + entryDelta.second.current->toString();
Expand All @@ -63,45 +64,33 @@ LedgerEntryIsValid::checkOnOperationApply(Operation const& operation,
return {};
}

template <typename IterType>
std::string
LedgerEntryIsValid::check(IterType iter, IterType const& end,
uint32_t ledgerSeq, uint32 version) const
{
for (; iter != end; ++iter)
{
auto s = checkIsValid(iter->current->mEntry, ledgerSeq, version);
if (!s.empty())
{
s += ": ";
s += xdr::xdr_to_string(iter->current->mEntry);
return s;
}
}
return {};
}

std::string
LedgerEntryIsValid::checkIsValid(GeneralizedLedgerEntry const& le,
uint32_t ledgerSeq, uint32 version) const
LedgerEntryIsValid::checkIsValid(
GeneralizedLedgerEntry const& le,
std::shared_ptr<GeneralizedLedgerEntry const> const& genPrevious,
uint32_t ledgerSeq, uint32 version) const
{
if (le.type() == GeneralizedLedgerEntryType::LEDGER_ENTRY)
{
return checkIsValid(le.ledgerEntry(), ledgerSeq, version);
auto const* previous =
genPrevious ? &genPrevious->ledgerEntry() : nullptr;
return checkIsValid(le.ledgerEntry(), previous, ledgerSeq, version);
}
return "";
}

std::string
LedgerEntryIsValid::checkIsValid(LedgerEntry const& le, uint32_t ledgerSeq,
uint32 version) const
LedgerEntryIsValid::checkIsValid(LedgerEntry const& le,
LedgerEntry const* previous,
uint32_t ledgerSeq, uint32 version) const
{
if (le.lastModifiedLedgerSeq != ledgerSeq)
{
return fmt::format("LedgerEntry lastModifiedLedgerSeq ({}) does not"
" equal LedgerHeader ledgerSeq ({})",
le.lastModifiedLedgerSeq, ledgerSeq);
}

switch (le.data.type())
{
case ACCOUNT:
Expand All @@ -113,7 +102,7 @@ LedgerEntryIsValid::checkIsValid(LedgerEntry const& le, uint32_t ledgerSeq,
case DATA:
return checkIsValid(le.data.data(), version);
case CLAIMABLE_BALANCE:
return checkIsValid(le, version);
return checkIsValid(le, previous, version);
default:
return "LedgerEntry has invalid type";
}
Expand Down Expand Up @@ -180,7 +169,7 @@ LedgerEntryIsValid::checkIsValid(TrustLineEntry const& tl, uint32 version) const
}
if (tl.limit <= 0)
{
return fmt::format("TrustLine balance ({}) is not positive", tl.limit);
return fmt::format("TrustLine limit ({}) is not positive", tl.limit);
}
if (tl.balance > tl.limit)
{
Expand Down Expand Up @@ -292,14 +281,28 @@ LedgerEntryIsValid::validatePredicate(ClaimPredicate const& pred,
}

std::string
LedgerEntryIsValid::checkIsValid(LedgerEntry const& le, uint32 version) const
LedgerEntryIsValid::checkIsValid(LedgerEntry const& le,
LedgerEntry const* previous,
uint32 version) const
{
if (le.ext.v() != 1 || !le.ext.v1().sponsoringID)
{
return "ClaimableBalance is not sponsored";
}

auto const& cbe = le.data.claimableBalance();

if (previous)
{
assert(previous->data.type() == CLAIMABLE_BALANCE);
auto const& previousCbe = previous->data.claimableBalance();

if (!(cbe == previousCbe))
{
return "ClaimableBalance cannot be modified";
}
}

if (cbe.claimants.empty())
{
return "ClaimableBalance claimants is empty";
Expand Down
15 changes: 7 additions & 8 deletions src/invariant/LedgerEntryIsValid.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,18 @@ class LedgerEntryIsValid : public Invariant
LedgerTxnDelta const& ltxDelta) override;

private:
template <typename IterType>
std::string check(IterType iter, IterType const& end, uint32_t ledgerSeq,
uint32 version) const;

std::string checkIsValid(GeneralizedLedgerEntry const& le,
std::string checkIsValid(
GeneralizedLedgerEntry const& le,
std::shared_ptr<GeneralizedLedgerEntry const> const& genPrevious,
uint32_t ledgerSeq, uint32 version) const;
std::string checkIsValid(LedgerEntry const& le, LedgerEntry const* previous,
uint32_t ledgerSeq, uint32 version) const;
std::string checkIsValid(LedgerEntry const& le, uint32_t ledgerSeq,
uint32 version) const;
std::string checkIsValid(AccountEntry const& ae, uint32 version) const;
std::string checkIsValid(TrustLineEntry const& tl, uint32 version) const;
std::string checkIsValid(OfferEntry const& oe, uint32 version) const;
std::string checkIsValid(DataEntry const& de, uint32 version) const;
std::string checkIsValid(LedgerEntry const& le, uint32 version) const;
std::string checkIsValid(LedgerEntry const& le, LedgerEntry const* previous,
uint32 version) const;

bool validatePredicate(ClaimPredicate const& pred, uint32_t depth) const;
};
Expand Down
106 changes: 106 additions & 0 deletions src/invariant/test/LedgerEntryIsValidTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2020 Stellar Development Foundation and contributors. Licensed
// under the Apache License, Version 2.0. See the COPYING file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0

#include "invariant/test/InvariantTestUtils.h"
#include "ledger/LedgerTxnHeader.h"
#include "ledger/test/LedgerTestUtils.h"
#include "lib/catch.hpp"
#include "main/Application.h"
#include "test/TestUtils.h"
#include "test/TxTests.h"
#include "test/test.h"
#include "transactions/TransactionUtils.h"

using namespace stellar;
using namespace stellar::txtest;
using namespace stellar::InvariantTestUtils;

TEST_CASE("Trigger validity check for each entry type",
"[invariant][ledgerentryisvalid]")
{
Config cfg = getTestConfig(0);
cfg.INVARIANT_CHECKS = {"LedgerEntryIsValid"};

VirtualClock clock;
Application::pointer app = createTestApplication(clock, cfg);

LedgerEntry le;
SECTION("account")
{
le.data.type(ACCOUNT);
le.data.account() = LedgerTestUtils::generateValidAccountEntry(5);
le.data.account().flags = MASK_ACCOUNT_FLAGS + 1;
REQUIRE(!store(*app, makeUpdateList({le}, nullptr)));
}
SECTION("trustline")
{
le.data.type(TRUSTLINE);
le.data.trustLine() = LedgerTestUtils::generateValidTrustLineEntry(5);
le.data.trustLine().flags = MASK_TRUSTLINE_FLAGS_V13 + 1;
REQUIRE(!store(*app, makeUpdateList({le}, nullptr)));
}
SECTION("offer")
{
le.data.type(OFFER);
le.data.offer() = LedgerTestUtils::generateValidOfferEntry(5);
le.data.offer().flags = MASK_OFFERENTRY_FLAGS + 1;
REQUIRE(!store(*app, makeUpdateList({le}, nullptr)));
}
SECTION("data")
{
le.data.type(DATA);
le.data.data() = LedgerTestUtils::generateValidDataEntry(5);
le.data.data().dataName.clear();
REQUIRE(!store(*app, makeUpdateList({le}, nullptr)));
}
SECTION("claimable balance")
{
le.data.type(CLAIMABLE_BALANCE);
le.data.claimableBalance() =
LedgerTestUtils::generateValidClaimableBalanceEntry(5);
le.data.claimableBalance().claimants.clear();
REQUIRE(!store(*app, makeUpdateList({le}, nullptr)));
}
}

TEST_CASE("Modify ClaimableBalanceEntry",
"[invariant][ledgerentryisvalid][claimablebalance]")
{
Config cfg = getTestConfig(0);
cfg.INVARIANT_CHECKS = {"LedgerEntryIsValid"};

VirtualClock clock;
Application::pointer app = createTestApplication(clock, cfg);

auto accountID = getAccount("acc").getPublicKey();

LedgerEntry le;
le.data.type(CLAIMABLE_BALANCE);
le.data.claimableBalance() =
LedgerTestUtils::generateValidClaimableBalanceEntry(5);
prepareLedgerEntryExtensionV1(le).sponsoringID.activate() = accountID;

ClaimPredicate pred;
pred.type(CLAIM_PREDICATE_UNCONDITIONAL);

Claimant c;
c.v0().destination = accountID;
c.v0().predicate = pred;

le.data.claimableBalance().claimants = {c};
auto leCopy = le;

REQUIRE(store(*app, makeUpdateList({le}, nullptr)));

SECTION("claimable balance is modified")
{
leCopy.data.claimableBalance().amount++;
REQUIRE(!store(*app, makeUpdateList({leCopy}, {le})));
}

SECTION("claimable balance is not modified")
{
REQUIRE(store(*app, makeUpdateList({leCopy}, {le})));
}
}