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

Strongly typed wrapper for DepositPreAuthorization Ledger Object #18

Open
wants to merge 1 commit into
base: wrapper
Choose a base branch
from

Conversation

ckeshava
Copy link

High Level Overview of Change

This PR introduces wrapper for Deposit PreAuth Ledger Object. The existing usages of readSLE and peekSLE functions have been modified to use the read/peek functions respectively.

Context of Change

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Tests (You added tests for code that already exists, or your new feature included in this PR)
  • Documentation Updates
  • Release

Test Plan

I have not included any new unit tests. Since this is an internal refactor, existing test cases should suffice in validating the new changes. As part of future work, additional test cases can be added pertaining to the APIs of newly added classes.

Copy link
Owner

@scottschurr scottschurr 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! I left some comments for you to consider. Feel free to push back or ask questions on any of these. Thanks!


using KeyletBase::check;

DPAuthKeylet(uint256 const& key) : KeyletBase(ltDEPOSIT_PREAUTH, key)
Copy link
Owner

Choose a reason for hiding this comment

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

I think this constructor should be explicit. We don't want the compiler to convert any uint256 into a DPAuthKeylet without being asked to do so.

Copy link
Author

Choose a reason for hiding this comment

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

understood, thanks for pointing it out 👍

@@ -171,16 +171,16 @@ DepositPreauth::removeFromLedger(
{
// Verify that the Preauth entry they asked to remove is
// in the ledger.
std::shared_ptr<SLE> const slePreauth{
view.peekSLE(keylet::depositPreauth(preauthIndex))};
std::optional<DPAuthImpl<true>> slePreauth{
Copy link
Owner

Choose a reason for hiding this comment

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

Using DPAuthImpl<true> is less convenient than it might be. The AcctRoot.h header file declares the AcctRootImpl template, but it also declares...

using AcctRootRd = AcctRootImpl<false>;
using AcctRoot = AcctRootImpl<true>;

If you did something similar in DPAuth.h, then this declaration would be std::optional<DPAuth> which is easier to read.

Copy link
Owner

Choose a reason for hiding this comment

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

While we're on this line, the variable name slePreauth is now misleading. The variable is no longer a Serialized Ledger Entry (which is what sle stands for) any more. I suggest you change the variable name to simply preauth. When I integrated the AcctRoot wrapper I tried to be conscientious about fixing the variable names.

Fixing the variable names will be particularly important when the type is auto, not quite as important here, since you have an explicit type. But we shouldn't leave misleading names hanging around.

Copy link
Author

Choose a reason for hiding this comment

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

thanks for calling out the variable name, I'll work on it.

Regarding creating a type-alias for the read-only version, I did consider having using DPAuthRd = DPAuth<true>;. But I felt another alias decreases the readability of the code 😓

But if this is the coding convention, I'll definitely incorporate your suggestion, it's just my personal bias against typedefs 😄


namespace ripple {

// Wrapper class for DepositPreAuth Ledger Object. This class encapsulates the std::shared_ptr<SLE> to the actual ledger object.
Copy link
Owner

Choose a reason for hiding this comment

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

This comment is too long. It should wrap before it gets to 80 characters. Yeah, clang-format may do that for you, but I personally prefer to be in control of how my comments look -- including where the line breaks are.

friend class ApplyView;

public:
// Conversion operator from DPAuthImpl<true> to DPAuthImpl<false>.
Copy link
Owner

Choose a reason for hiding this comment

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

Nice work with removing the explicit special member functions!

Copy link
Author

Choose a reason for hiding this comment

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

thank you :)

return wrapped_->at(sfOwnerNode);
}
};
}
Copy link
Owner

Choose a reason for hiding this comment

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

I suggest you add the following declarations to the bottom of the file (but still inside the ripple namespace):

using DPAuthRd = DPAuthImpl<false>;
using DPAuth = DPAuthImpl<true>;

Copy link
Owner

Choose a reason for hiding this comment

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

Given the class rename, please add these declarations to the file, but use the right names:

using DepPreAuthRd = DepPreAuthImpl<false>;
using DepPreAuth = DepPreAuthImpl<true>;

After you've changed these names grep the source code for DPAuth. Fix up any instances that you find, either in source or in comments.

The reason I want to introduce the DepPreAuthRd and DepPreAuth names is because DepPreAuthImpl being a template is an implementation artifact. Scott D and were looking for a way to make sure the interfaces of DepPreAuthRd and DepPreAuth are identical where ever possible. And we didn't want to leave behind a maintenance problem. Using a base template for the two types was the best way we identified to accomplish that.

We want people to think of DepPreAuthRd and DepPreAuth as being two distinct types, just
like iterator and const_iterator are two distinct but related types.

depositPreauth(uint256 const& key) noexcept
{
return {ltDEPOSIT_PREAUTH, key};
return {key};
Copy link
Owner

Choose a reason for hiding this comment

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

Once the DPAuthKeylet constructor is made explicit, I think you'll change the implementation like this:

return DPAuthKeylet(key);

Copy link
Author

Choose a reason for hiding this comment

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

ok. I have a tangential question, what are the tradeoffs with return DPAuthKeyley{key} ?

I am aware the there could multiple ways to initialize objects, but I am not clear about the differences.
Since this is an aggregate type, I believe using the {} syntax should be okay, but IDK the performance costs.

Copy link
Owner

Choose a reason for hiding this comment

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

The runtime costs of () vs {} are identical. The difference is in how permissive the syntaxes are.

This is really guidance that I got from Howard. If I'm remembering correctly, there are situations where braced initialization will consider using an explicit constructor. So if you just casually use {} to initialize stuff, then you may be invoking an explicit constructor without realizing it. Howard said that using = or () for initialization does not have that problem.

For me, at least, C++ is a complicated enough language that I don't want to think, "Hmmm, what kind of initialization should I use here?" every time I initialize a value. So as a heuristic I tend not to use braced initialization unless I have a specific reason.

Note that Howard's guidance disagrees with guidance that Nico Jossutis (at least used to) give. Nico used to like defaulting to braced initialization (without the =).

As I'm looking around now I think you'll see that the safest default is = {<value(s)>}: https://www.josuttis.com/cpp/c++initialization.pdf But I'm not convinced that we could convince rippled developers to type that many characters every time they initialize initialize something.

That's my current take. But I suggest you talk to Howard about it. He has opinions and did the tests to back them up. Let me know what he says.

Copy link
Author

Choose a reason for hiding this comment

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

will do, thanks for sharing your thoughts.
Yes, I saw Nicolai Josutis's talk on the NIghtmare of Initialization. It was a fascinating talk :)

return {
ltDEPOSIT_PREAUTH,
indexHash(LedgerNameSpace::DEPOSIT_PREAUTH, owner, preauthorized)};
return {indexHash(LedgerNameSpace::DEPOSIT_PREAUTH, owner, preauthorized)};
Copy link
Owner

Choose a reason for hiding this comment

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

Once the DPAuthKeylet constructor is made explicit, I think you'll need to change the implementation like this:

return DPAuthKeylet(
    indexHash(LedgerNameSpace::DEPOSIT_PREAUTH, owner, preauthorized));

@@ -195,7 +195,7 @@ DepositPreauth::removeFromLedger(
adjustOwnerCount(view, *ownerAcctRoot, -1, app.journal("View"));

// Remove DepositPreauth from ledger.
view.erase(slePreauth);
view.erase(slePreauth->slePtr());
Copy link
Owner

Choose a reason for hiding this comment

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

This works, but I think a better approach is to call view.erase(*preauth). That passes the (non-optional) wrapper to erase.

Copy link
Author

Choose a reason for hiding this comment

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

right 👍 your structure for the erase function template is beautiful! (which enables the wrapper to be passed into erase and insert)

Comment on lines 104 to 106
auto const sleDepositAuth =
ledger->readSLE(keylet::depositPreauth(dstAcct, srcAcct));
ledger->read(keylet::depositPreauth(dstAcct, srcAcct));
depositAuthorized = static_cast<bool>(sleDepositAuth);
Copy link
Owner

Choose a reason for hiding this comment

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

I'd be inclined to change these lines into...

depositAuthorized =
    ledger->read(keylet::depositPreauth(dstAcct, srcAcct))
        .has_value();

That way you don't even need to name a local variable.

Copy link
Author

Choose a reason for hiding this comment

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

great point 👍


// Wrapper class for DepositPreAuth Ledger Object. This class encapsulates the std::shared_ptr<SLE> to the actual ledger object.
template <bool Writable>
class DPAuthImpl final : public LedgerEntryWrapper<Writable>
Copy link
Owner

Choose a reason for hiding this comment

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

I'm not really excited about DPAuthImpl as a name. If I'm not already thinking about deposit preauthorization the DP prefix really doesn't get me oriented. But DepositPreauth is already taken. DepositPreAuthorization is getting kind'a long winded, even for me.

What would you think of DepPreAuthImpl? Still not great. We could also leave this as DPAuthImpl for now and see what folks think during the code review. Your call. I guess the good news is that this will usually be a return type declared with auto, so it won't show up in the source code a whole lot.

Copy link
Author

Choose a reason for hiding this comment

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

Agreed, I couldn't think of a better name, so I rushed through with the first one that popped into my head.
I like DepPreAuthImpl too, I'm open to any other suggestions as well 👍

Copy link
Owner

Choose a reason for hiding this comment

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

Rename this class to DepPreAuthImpl.

@ckeshava
Copy link
Author

Ok, thanks for the suggestions @scottschurr this was really helpful.
I squashed my commits and force-pushed into this branch, sorry about that.
I have addressed your comments 👍

Copy link
Owner

@scottschurr scottschurr left a comment

Choose a reason for hiding this comment

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

Here's the way I think of code reviews. The reviewer is doing you a favor. Most reviewers would rather be writing their own code than reviewing yours. Anything they flag is for a reason, even if you don't necessarily agree 100% with the reason. So when I (personally) get a review comment I implement that change unless I have a defensible reason not to make the change. If it's close to a wash whether my way is better or theirs, then I go with theirs. This helps make my reviewers happier about reviewing my code next time. Do ask questions. But be biased toward assuming the reviewer is probably right.

I suggest you re-review all of your changes using your difftool before submitting any kind of pull request. Walk through the list of requested changes and make sure you either made the change or responded to the comment. In reality you only get one good review out of a reviewer. The second or third time they review your code they can no longer see things with fresh eyes. So you want to make sure that the first thing you show them is your very best effort.

You should make sure to run clang-format over your code before submitting a pull request. I can tell that you did not do that on this branch because you have some lines that exceed 80 characters. I have my text editor set up with a fence at 80 characters. If you use CLion as your editor, then I suggest you see if CLion can do something like that. I bet it can.

You are welcome to disagree with any of the suggestions I make. But if you don't disagree, with a cogent reason, then I'll expect to see the change made in the next revision.

auto const sleDepositAuth =
ledger->readSLE(keylet::depositPreauth(dstAcct, srcAcct));
depositAuthorized = static_cast<bool>(sleDepositAuth);
auto const depositAuthorized =
Copy link
Owner

Choose a reason for hiding this comment

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

Compiling this line gives the warning "unused variable 'depositAuthorized'". I saw this because I build with -Dwerr=ON -Dwextra=ON. I think you should build using those flags too.

The bug here is that you've included auto const in your assignment to depositAuthorized. So you're assigning to a brand new variable. The name of that variable happens to shadow a variable of the same name in the outer scope. So the variable in the outer scope was unaffected by the assignment. Only the inner (unused) variable received the assignment.

To fix the bug remove the auto const.

Copy link
Author

Choose a reason for hiding this comment

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

Okay, thanks for this catch. Do you happen to remember which CMake or Conan file needs to be updated with this setting?
I use this command to set up CMake (cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release ..), but I'm not sure if these configuration options can be passed into CMake and not clang.

Copy link
Owner

Choose a reason for hiding this comment

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

You can just add those flags to the cmake command line. Here's the cmake command line I use for a debug build:

cmake -DCMAKE_BUILD_TYPE=Debug -Dunity=OFF -Dassert=ON -Dwerr=ON -Dwextra=ON -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake ${SOURCE_DIR}

src/ripple/protocol/DPAuth.h Outdated Show resolved Hide resolved

// Wrapper class for DepositPreAuth Ledger Object. This class encapsulates the std::shared_ptr<SLE> to the actual ledger object.
template <bool Writable>
class DPAuthImpl final : public LedgerEntryWrapper<Writable>
Copy link
Owner

Choose a reason for hiding this comment

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

Rename this class to DepPreAuthImpl.

return wrapped_->at(sfOwnerNode);
}
};
}
Copy link
Owner

Choose a reason for hiding this comment

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

Given the class rename, please add these declarations to the file, but use the right names:

using DepPreAuthRd = DepPreAuthImpl<false>;
using DepPreAuth = DepPreAuthImpl<true>;

After you've changed these names grep the source code for DPAuth. Fix up any instances that you find, either in source or in comments.

The reason I want to introduce the DepPreAuthRd and DepPreAuth names is because DepPreAuthImpl being a template is an implementation artifact. Scott D and were looking for a way to make sure the interfaces of DepPreAuthRd and DepPreAuth are identical where ever possible. And we didn't want to leave behind a maintenance problem. Using a base template for the two types was the best way we identified to accomplish that.

We want people to think of DepPreAuthRd and DepPreAuth as being two distinct types, just
like iterator and const_iterator are two distinct but related types.

src/ripple/app/tx/impl/DepositPreauth.cpp Outdated Show resolved Hide resolved
Comment on lines 99 to 112
template<bool>
class DPAuthImpl;

struct DPAuthKeylet final : public KeyletBase {
template <bool Writable>
using TWrapped = DPAuthImpl<Writable>;

using KeyletBase::check;

explicit DPAuthKeylet(uint256 const& key) : KeyletBase(ltDEPOSIT_PREAUTH, key)
{
}
};

Copy link
Owner

Choose a reason for hiding this comment

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

By the time everything is done there will be quite a number of these specialized Keylets in the file. I think it would be a good idea to (almost) alphabetize the Keylets. Put KeyletBase first, followed by Keylet. Then alphabetize the remaining Keylets as they are added.

Copy link
Author

Choose a reason for hiding this comment

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

okay. By alphabetize, you mean capitalize the first letter of the keylet? like DepPreAuthKeylet ?

Copy link
Owner

Choose a reason for hiding this comment

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

By alphabetize, you mean capitalize the first letter of the keylet? like DepPreAuthKeylet ?

Sorry for not being clear. By the time you are done Keylet.h will contain a bunch of classes derived from KeyletBase. I'm suggesting that the file should keep (most) of those definitions in alphabetical order so they are easier for folks to find. For example, this file now contains class definitions in this order:

KeyletBase
Keylet
DepPreAuthKeylet
AccountRootKeylet

I think it makes sense for KeyletBase and Keylet to stay at the top. But the remainder should be alphabetical. So, for this revision of the file they'd be in this order:

KeyletBase
Keylet
AccountRootKeylet
DepPreAuthKeylet

It's not really important right now. But by the time you get to the end of the project there will be quite a number of Keylets in here, and they will be easier to find if they are alphabetical.

Make sense?

Copy link
Author

Choose a reason for hiding this comment

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

yes okay, I'll keep this in mind 👍

Copy link
Author

Choose a reason for hiding this comment

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

yeah, it will be easier to organize them in alphabetical order once all the PRs are merged into once place

src/ripple/protocol/Keylet.h Outdated Show resolved Hide resolved
@ckeshava
Copy link
Author

ckeshava commented Jun 1, 2023

I'm really sorry that I didn't address your comments from last week. I got caught up in the other PRs and forgot to update this one, won't repeat it again.

Copy link
Owner

@scottschurr scottschurr left a comment

Choose a reason for hiding this comment

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

Looks good, but a couple of notes:

  1. The most recent changes were not clang-formatted. I did that and pushed the commit here: https://github.com/scottschurr/rippled/commits/chenna-depositPAuthWrapper You can pick that up if yo want to cherry-pick it. Or you can do the clang-format yourself.
  2. While I was in there I moved DepPreAuthKeylet below AcctRootKeylet in Keylet.h. That will make it easier to alphabetize the entries as they are merged.
  3. Somehow or other the bottom-most commit looks like it is now attributed to me, not to you. This is all mostly your work, and should be attributed to you. When you squash the commits for the pull request that you submit to develop would you please make sure that commit is attributed to you?

Thanks.

@ckeshava
Copy link
Author

ckeshava commented Jun 9, 2023 via email

@scottschurr
Copy link
Owner

Try something like this:

git commit --amend --author="John Doe <[email protected]>"

…Keylet is used to distinguish between various other types of SLEs

update readSLE and peekSLE function calls associated with keylet::depositPreAuth into the new read, peek functions
@ckeshava
Copy link
Author

ckeshava commented Jun 9, 2023 via email

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.

2 participants