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

CAP-0023 implementation #2591

Merged
merged 21 commits into from
Jul 21, 2020
Merged

Conversation

sisuresh
Copy link
Contributor

@sisuresh sisuresh commented Jun 22, 2020

Description

Implements https://github.com/stellar/stellar-protocol/blob/master/core/cap-0023.md

Checklist

  • Reviewed the contributing document
  • Rebased on top of master (no merge commits)
  • Ran clang-format v5.0.0 (via make format or the Visual Studio extension)
  • Compiles
  • Ran all tests
  • If change impacts performance, include supporting evidence per the performance document

@sisuresh sisuresh marked this pull request as ready for review June 22, 2020 23:49
Copy link
Contributor

@jonjove jonjove left a comment

Choose a reason for hiding this comment

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

Completed a preliminary review of everything after the LedgerTxn changes, excluding the tests. Looks pretty good, but a few important comments in here.

src/transactions/ClaimClaimableBalanceOpFrame.cpp Outdated Show resolved Hide resolved
src/transactions/ClaimClaimableBalanceOpFrame.cpp Outdated Show resolved Hide resolved
src/transactions/ClaimClaimableBalanceOpFrame.cpp Outdated Show resolved Hide resolved
src/transactions/ClaimClaimableBalanceOpFrame.cpp Outdated Show resolved Hide resolved
src/transactions/ClaimClaimableBalanceOpFrame.cpp Outdated Show resolved Hide resolved
src/transactions/CreateClaimableBalanceOpFrame.cpp Outdated Show resolved Hide resolved
src/transactions/CreateClaimableBalanceOpFrame.cpp Outdated Show resolved Hide resolved
src/transactions/CreateClaimableBalanceOpFrame.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@MonsieurNicolas MonsieurNicolas left a comment

Choose a reason for hiding this comment

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

I did a full review of this PR - see comments.

Overall this is pretty close to mergeable (good job!):

  • I added a couple questions that happen to be CAP issues/suggestions that we missed
  • I think you have a few missing test cases that would have uncovered a couple bugs

case CLAIM_PREDICATE_AFTER_ABSOLUTE_TIME:
int64 absAfter;
case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME:
int64 relBefore; // Seconds since closeTime of the ledger in which the
Copy link
Contributor

Choose a reason for hiding this comment

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

you need to run clang-format manually on the .x files (make sure to comment the %#include before doing so)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

CLAIM_PREDICATE_AND = 1,
CLAIM_PREDICATE_OR = 2,
CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME = 3,
CLAIM_PREDICATE_AFTER_ABSOLUTE_TIME = 4,
Copy link
Contributor

Choose a reason for hiding this comment

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

CAP question - should we remove the AFTER (or BEFORE) predicates and just add a NOT operator?

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

I thought about this (it was actually my original approach) but I had some concerns that it could be a future foot-gun. For example, suppose we added a predicate like CLAIM_PREDICATE_HASH_PREIMAGE where you have to reveal a SHA256-preimage in order to claim. NOT doesn't behave particularly well with this predicate because it would be satisfied by default. Maybe this concern is overblown, because you could always configure the predicates incorrectly anyway. What do you think? I'm pretty flexible on this.

REQUIRE(claimableBalance.amount == amount);
REQUIRE(claimableBalance.balanceID == balanceID);
REQUIRE(claimableBalance.createdBy == getPublicKey());
REQUIRE(claimableBalance.reserve ==
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: signed vs unsigned comparison

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be fixed.

int64_t
relativeToAbsolute(TimePoint closeTime, int64_t relative)
{
return closeTime > (INT64_MAX - relative) ? INT64_MAX
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: signed/unsigned comparison

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be fixed.

return false;
}

auto ok = addBalance(header, sourceAccount, -amount);
Copy link
Contributor

Choose a reason for hiding this comment

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

warning ok shadows line 156

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@@ -47,7 +49,7 @@ getNeededThreshold(LedgerTxnEntry const& account, ThresholdLevel const level)

shared_ptr<OperationFrame>
OperationFrame::makeHelper(Operation const& op, OperationResult& res,
TransactionFrame& tx)
TransactionFrame& tx, size_t index)
Copy link
Contributor

Choose a reason for hiding this comment

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

per my other comment elsewhere, size_t is overkill for this as we later have to downcast it. Better to just downcast it once (here). Not sure why it was size_t before in TransactionFrame::makeOperation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

src/main/Config.cpp Show resolved Hide resolved
src/invariant/LedgerEntryIsValid.cpp Show resolved Hide resolved
@sisuresh sisuresh force-pushed the cap23-implementation branch 3 times, most recently from e1ce5de to 42f2e20 Compare June 30, 2020 20:35
Copy link

@osman-20068 osman-20068 left a comment

Choose a reason for hiding this comment

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

os

Copy link
Contributor

@MonsieurNicolas MonsieurNicolas 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 resolved all issues that I consider resolved; added a couple new ones.
Next step is to rebase on top of master and squash commits so that you can address the SQL related issues.

mDatabase.getSession() << "CREATE TABLE claimablebalance ("
<< "balanceid VARCHAR(56) " << coll
<< " PRIMARY KEY, "
<< "createdby VARCHAR(56) " << coll
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 dumb for not having noticed this earlier: why do we need to expose all those internal fields?
I think the minimal set is
balanceid(HEX) | claimablebalanceentry (XDR) | lastmodified (BIGINT)

Copy link
Contributor

Choose a reason for hiding this comment

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

so we end up with a lot less code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes sense to me, but I noticed that this wasn't done for the other ledger entries. I want to make sure there wasn't a specific reason for listing the fields individually before I change this. @jonjove?

Copy link
Contributor

Choose a reason for hiding this comment

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

The reason other ledger entries aren't like this is historical. It's possible we will move this direction in the long run. We already moved slightly in this direction with #2593.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. Thanks

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 made the change to use a claimablebalanceentry column. I also updated db-schema.md.

@@ -440,6 +440,7 @@ Database::initialize()
mApp.getLedgerTxnRoot().dropOffers();
mApp.getLedgerTxnRoot().dropTrustLines();
mApp.getLedgerTxnRoot().dropData();
mApp.getLedgerTxnRoot().dropClaimableBalances();
Copy link
Contributor

Choose a reason for hiding this comment

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

you still need to do this

@@ -102,6 +102,8 @@ LedgerEntryIsValid::checkIsValid(LedgerEntry const& le, uint32_t ledgerSeq,
return checkIsValid(le.data.offer(), version);
case DATA:
return checkIsValid(le.data.data(), version);
case CLAIMABLE_BALANCE:
return checkIsValid(le.data.claimableBalance(), version);
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe open an issue: this invariant should also check that we don't modify immutable fields. For claimable balance, all fields are immutable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added an issue - #2620

@@ -23,6 +23,24 @@ calculateDeltaBalance(std::shared_ptr<LedgerEntry const> const& current,
return (current ? current->data.account().balance : 0) -
(previous ? previous->data.account().balance : 0);
}
if (let == CLAIMABLE_BALANCE)
{
int64_t reserveDelta =
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 written as if reserve and amount are mutable, maybe OK in this context (albeit a bit confusing as the code doesn't work at all if asset gets modified) but see my comment in the other invariant on validity

Copy link
Contributor

Choose a reason for hiding this comment

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

This will be simplified when we rebase CAP-33 on top, because the reserve is not stored in that case. But I the code should work correctly in the case that asset changed. This would obviously be a bug if it were to happen, but for clarity and safety the code should account for it (after all, invariants are all about catching bugs).


for (uint32_t i = 0; i < 4; i++)
{
SECTION("predicate at level " + std::to_string(i))
Copy link
Contributor

Choose a reason for hiding this comment

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

from recent findings, let's be defensive and avoid using to_string -> use fmt::format instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


Field | Type | Description
------|------|---------------
balanceid | VARCHAR(56) PRIMARY KEY | (BASE64)
Copy link
Contributor

Choose a reason for hiding this comment

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

  • balanceid is a base64 encoded xdr object -> type should be "XDR", also description should say that it's a ClaimableBalanceID ; we use BASE64 as a type when there is no good XDR type.
  • claimablebalanceentry is a based64 encoded xdr object -> type should be "XDR" (and description should say that it's a ClaimableBalanceEntry)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -440,6 +440,7 @@ Database::initialize()
mApp.getLedgerTxnRoot().dropOffers();
mApp.getLedgerTxnRoot().dropTrustLines();
mApp.getLedgerTxnRoot().dropData();
mApp.getLedgerTxnRoot().dropClaimableBalances();
Copy link
Contributor

Choose a reason for hiding this comment

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

yes you need to call mApp.getLedgerTxnRoot().dropClaimableBalances(); from Database::applySchemaUpgrade when upgrading to schema version 13 otherwise the table won't be created

// 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 "crypto/KeyUtils.h"
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 those 2 "crypto" includes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed


mDatabase.getSession() << "DROP TABLE IF EXISTS claimablebalance;";
mDatabase.getSession() << "CREATE TABLE claimablebalance ("
<< "balanceid VARCHAR(56) " << coll
Copy link
Contributor

Choose a reason for hiding this comment

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

56 is wrong (needs to be updated in the md file as well):
I think it's base64(4+32) = 4 * round_up(36/3) = 4*12 = 48

mDatabase.getSession() << "CREATE TABLE claimablebalance ("
<< "balanceid VARCHAR(56) " << coll
<< " PRIMARY KEY, "
<< "claimablebalanceentry TEXT " << coll
Copy link
Contributor

Choose a reason for hiding this comment

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

we should store not just ClaimableBalanceEntry but the full LedgerEntry in this column, this duplicates a small amount of data but avoids adding an extra column for ledgerext (this was added in the latest round of changes to the database)

Copy link
Contributor

Choose a reason for hiding this comment

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

I support this approach as well. We can always change it later if the performance is worse-than-desired, but this approach will considerably simplify future development.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This would also allow us to remove the lastmodified column right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nevermind. The lastmodified column is required.

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 made the change to store the full LedgerEntry.

Copy link
Contributor

@jonjove jonjove left a comment

Choose a reason for hiding this comment

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

Nice work! This looks largely correct to me at this point, and @MonsieurNicolas had already identified most of the remaining issues. Most of my comments are picking out minor details. The main issue here for me is that the tests are pretty confusing: it was hard for me to tell exactly what is being tested in various places, and also hard for me to tell if we achieved the desired coverage. For example, I wasn't able to pick out a test that created a claimable balance entry in a transaction where the operation and transaction have different source accounts (is there such a test?). Is there anything we can do to improve the overall clarity of the tests?

src/ledger/test/LedgerTestUtils.cpp Outdated Show resolved Hide resolved
mDatabase.getSession() << "CREATE TABLE claimablebalance ("
<< "balanceid VARCHAR(56) " << coll
<< " PRIMARY KEY, "
<< "claimablebalanceentry TEXT " << coll
Copy link
Contributor

Choose a reason for hiding this comment

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

I support this approach as well. We can always change it later if the performance is worse-than-desired, but this approach will considerably simplify future development.

src/ledger/LedgerTxnClaimableBalanceSQL.cpp Outdated Show resolved Hide resolved
src/transactions/CreateClaimableBalanceOpFrame.cpp Outdated Show resolved Hide resolved
@@ -23,6 +23,24 @@ calculateDeltaBalance(std::shared_ptr<LedgerEntry const> const& current,
return (current ? current->data.account().balance : 0) -
(previous ? previous->data.account().balance : 0);
}
if (let == CLAIMABLE_BALANCE)
{
int64_t reserveDelta =
Copy link
Contributor

Choose a reason for hiding this comment

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

This will be simplified when we rebase CAP-33 on top, because the reserve is not stored in that case. But I the code should work correctly in the case that asset changed. This would obviously be a bug if it were to happen, but for clarity and safety the code should account for it (after all, invariants are all about catching bugs).

src/transactions/test/ClaimableBalanceTests.cpp Outdated Show resolved Hide resolved
src/transactions/test/ClaimableBalanceTests.cpp Outdated Show resolved Hide resolved
// authorize trustline
if (!isNative)
{
SECTION("no trust")
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this all actually work as you intend? The sequential sections with intermittent code is a pretty confusing pattern. It leaves me wondering, what is supposed to happen here and what actually does happen here?

Are these early sections like "no trust" and "not authorized" even required? The code in those sections should have no effect other than paying fees and consuming sequence numbers, so the sections might not really be doing anything.

In summary, I'm confused by this.

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 cleaned up the tests and squashed with 572598aa0be8290908319e5d1d98e83c09c5520f.

@sisuresh
Copy link
Contributor Author

@jonjove You make a good point about the test cases. They're confusing to look at, and the pattern where I use sequential sections with intermittent code can be hard to follow. I did this to avoid duplicating code, but the end result isn't great. I'll fix this.

I wasn't able to pick out a test that created a claimable balance entry in a transaction where the operation and transaction have different source accounts

SECTION("validate Tx account is used in hash") tests this, but that's obviously not clear from the name. Being able to identify any tested functionality based on the SECTION name is a good requirement. I'll make a pass through the test cases and try to change -

  1. Ambiguous section names.
  2. Any Large SECTION that tests multiple code paths without making it clear.

@sisuresh sisuresh force-pushed the cap23-implementation branch 2 times, most recently from 18ec217 to cb0d1cf Compare July 16, 2020 18:00
Copy link
Contributor

@jonjove jonjove left a comment

Choose a reason for hiding this comment

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

This looks almost ready to me now. The tests are vastly easier to parse, so thank you for redesigning that! The only test that I don't remember seeing is one in which we create a claimable balance and then claim it in the same transaction. I expect this to work, but I think we should test it because it is an edge case in the sense that if you are doing this then you aren't really taking advantage of the behavior of claimable balance.

mDatabase.getSession() << "CREATE TABLE claimablebalance ("
<< "balanceid VARCHAR(48) " << coll
<< " PRIMARY KEY, "
<< "claimablebalanceentry TEXT NOT NULL, "
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 this name is a good description anymore, given that we are storing the LedgerEntry.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to ledgerentry.

ex_CREATE_CLAIMABLE_BALANCE_NO_TRUST);
}

// create and modify trustlines regardless of asset type so the
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 a little confused about this comment. acc1 can't have the same balance in both scenarios because you are executing some transactions only if !isNative. Do I misunderstand?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah the comment should be updated to say the number of subentries is the same in both scenarios.

}
}

SECTION("multiple create and claims")
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I understand the purpose of this test? Unless I'm mistaken, it effectively does "create -> claim -> fund -> create -> claim" with each step in its own transaction. Was the intention to do that as a single 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.

The test here was to make sure that the claim doesn't have any unintended side effects that could get in the way of another create/claim later on.

@sisuresh
Copy link
Contributor Author

@jonjove I made the last round of PR changes and squashed/rebased. I also added a test for creating and claiming in the same tx under the section create and claim in same tx.

@jonjove
Copy link
Contributor

jonjove commented Jul 20, 2020

r+ 29c1cab

@MonsieurNicolas
Copy link
Contributor

@latobarita: retry

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants