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

Proposed version 2.3.1 #5243

Draft
wants to merge 40 commits into
base: release
Choose a base branch
from

Conversation

vlntb
Copy link
Collaborator

@vlntb vlntb commented Jan 13, 2025

High Level Overview of Change

This is a hotfix release that includes the following updates:

  • A fix for an erroneous high fee penalty that peers could incur for sending older transactions;
  • Updates to the fees charged for imposing a load on the server;
  • A fix to prevent the relaying of internal pseudo-transactions;
  • Improved logging.

Context of Change

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Release

API Impact

  • Public API: New feature (new methods and/or new fields)
  • Public API: Breaking change (in general, breaking changes should only impact the next api_version)
  • libxrpl change (any change that may affect libxrpl or dependents of libxrpl)
  • Peer protocol change (must be backward compatible or bump the peer protocol version)

Copy link

codecov bot commented Jan 13, 2025

Codecov Report

Attention: Patch coverage is 27.37430% with 130 lines in your changes missing coverage. Please review.

Project coverage is 77.9%. Comparing base (f64cf91) to head (59820e3).

Files with missing lines Patch % Lines
src/xrpld/overlay/detail/PeerImp.cpp 1.0% 98 Missing ⚠️
src/xrpld/overlay/detail/OverlayImpl.cpp 68.0% 8 Missing ⚠️
src/xrpld/overlay/detail/PeerImp.h 0.0% 7 Missing ⚠️
src/xrpld/app/ledger/detail/InboundLedger.cpp 0.0% 5 Missing ⚠️
...rc/xrpld/app/ledger/detail/InboundTransactions.cpp 0.0% 4 Missing ⚠️
src/xrpld/app/ledger/detail/LedgerMaster.cpp 0.0% 4 Missing ⚠️
src/libxrpl/resource/Charge.cpp 0.0% 2 Missing ⚠️
include/xrpl/resource/detail/Logic.h 92.9% 1 Missing ⚠️
src/xrpld/rpc/detail/ServerHandler.cpp 88.9% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##           release   #5243     +/-   ##
=========================================
- Coverage     77.9%   77.9%   -0.1%     
=========================================
  Files          784     784             
  Lines        66681   66742     +61     
  Branches      8162    8184     +22     
=========================================
+ Hits         51950   51963     +13     
- Misses       14731   14779     +48     
Files with missing lines Coverage Δ
src/libxrpl/protocol/BuildInfo.cpp 98.2% <ø> (ø)
src/libxrpl/resource/Consumer.cpp 89.8% <100.0%> (ø)
src/xrpld/overlay/Overlay.h 70.0% <ø> (ø)
src/xrpld/overlay/Peer.h 100.0% <ø> (ø)
src/xrpld/overlay/detail/OverlayImpl.h 40.0% <ø> (ø)
src/xrpld/rpc/detail/RPCHelpers.cpp 82.8% <100.0%> (ø)
src/xrpld/rpc/handlers/GatewayBalances.cpp 89.8% <100.0%> (ø)
src/xrpld/rpc/handlers/LedgerHandler.cpp 22.6% <100.0%> (ø)
src/xrpld/rpc/handlers/PathFind.cpp 48.3% <100.0%> (ø)
src/xrpld/rpc/handlers/RipplePathFind.cpp 42.5% <100.0%> (ø)
... and 12 more

... and 4 files with indirect coverage changes

Impacted file tree graph

@vlntb vlntb changed the title fix fee from feeInvalidSignature to feeUnwantedData Proposed version 2.3.1 Jan 14, 2025
@vlntb vlntb requested a review from ximinez January 14, 2025 12:59
@vlntb vlntb marked this pull request as ready for review January 14, 2025 13:00
* Before: Pseudo-transactions received from a peer will fail the signature
  check, even if they were requested (using TMGetObjectByHash), because
  they have no signature. This causes the peer to be charge for an
  invalid signature.
* After: Pseudo-transactions, are put into the global cache
  (TransactionMaster) only. If the transaction is part of
  a TMTransactions batch, the peer is charged the equivalent of one
  trivial request per transaction. If not, the peer is charged an
  unwanted data fee. These fees will not be a problem in the normal
  course of operations, but should dissuade peers from behaving badly by
  sending a bunch of junk.
* If reduce relay is enabled, it will queue up the tx id for peers that
  also have it enabled so they can ask for it later if they need it.
Copy link
Collaborator

@ximinez ximinez left a comment

Choose a reason for hiding this comment

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

Go ahead and squash the last two commits (version and release notes) into one, and change the commit message to follow the standard format: "Set version to 2.3.1"

@vlntb vlntb marked this pull request as draft January 16, 2025 18:19
Comment on lines 447 to 451
// Only use these for logging in Logic::charge, and keep the values in
// descending order
static Charge const feeLogAsWarn(3000, "log as warn");
static Charge const feeLogAsInfo(1000, "log as info");
static Charge const feeLogAsDebug(100, "log as debug");
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would recommend defining these in Fees.h/cpp so the consequences of changing any of the fees are more apparent in terms of how they also will be logged.

Copy link
Collaborator

@ximinez ximinez Jan 24, 2025

Choose a reason for hiding this comment

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

I actually had the opposite thought - now that I see them here, there's no need to define these as Charges. They can just be straight up ints. (Or more specifically, Charge::value_type to ensure the types remain consistent.)
e.g.

static constexpr Charge::value_type feeLogAsWarn = 3000;

Even if we change the values in Fees.cpp in the future, it wouldn't make sense to change them so radically that these cutoffs should ever need to change. e.g. There should always be something that only charges 1. Even the things we changed, we made the values larger, so if anything, they'll log at a higher level, which is probably what we want.

I think a comment in Fees.cpp would be sufficient.

// See also Resource::Logic::charge for log level cutoff values

Now, all that said, if you still disagree, I don't object to moving them to Fees.cpp 😄

Edit: And if you notice there, I used constexpr, so you could add a quick static_assert that won't hurt anything.

static_assert(feeLogAsWarn > feeLogAsInfo && feeLogAsInfo > feeLogAsDebug && feeLogAsDebug > 10);

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not sure about this. Putting these definitions into Fees.h/cpp will encourage someone to use them as actual fees mistakenly. Keeping them close to the log-selection logic makes more sense to me.

Comment on lines 447 to 451
// Only use these for logging in Logic::charge, and keep the values in
// descending order
static Charge const feeLogAsWarn(3000, "log as warn");
static Charge const feeLogAsInfo(1000, "log as info");
static Charge const feeLogAsDebug(100, "log as debug");
Copy link
Collaborator

@ximinez ximinez Jan 24, 2025

Choose a reason for hiding this comment

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

I actually had the opposite thought - now that I see them here, there's no need to define these as Charges. They can just be straight up ints. (Or more specifically, Charge::value_type to ensure the types remain consistent.)
e.g.

static constexpr Charge::value_type feeLogAsWarn = 3000;

Even if we change the values in Fees.cpp in the future, it wouldn't make sense to change them so radically that these cutoffs should ever need to change. e.g. There should always be something that only charges 1. Even the things we changed, we made the values larger, so if anything, they'll log at a higher level, which is probably what we want.

I think a comment in Fees.cpp would be sufficient.

// See also Resource::Logic::charge for log level cutoff values

Now, all that said, if you still disagree, I don't object to moving them to Fees.cpp 😄

Edit: And if you notice there, I used constexpr, so you could add a quick static_assert that won't hurt anything.

static_assert(feeLogAsWarn > feeLogAsInfo && feeLogAsInfo > feeLogAsDebug && feeLogAsDebug > 10);

@vlntb vlntb requested review from bthomee and ximinez January 24, 2025 19:21
Comment on lines 1214 to 1221
if (relay)
{
auto& txn = tx->get();
sm = std::make_shared<Message>(txn, protocol::mtTRANSACTION);
}

if (!relay && !app_.config().TX_REDUCE_RELAY_ENABLE)
return;
Copy link
Collaborator

@bthomee bthomee Jan 24, 2025

Choose a reason for hiding this comment

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

It looks like this can be simplified to:

if (!relay) {
    auto peers = getActivePeers(toSkip, 0, 0, 0)
    JLOG(journal_.trace()) << "not relaying tx, total peers " << peers.size();
    for (auto const& p : peers)
        p->addTxQueue(hash);
    return;
}

auto& txn = tx->get();
sm = std::make_shared<Message>(txn, protocol::mtTRANSACTION);

And then the statements below no longer need to check whether relay is set.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Message allocation is more expensive than couple of additional boolean checks. The current approach should have performance advantage.

Copy link
Collaborator

Choose a reason for hiding this comment

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

There should be no performance disadvantage to my proposal, since in the current code the message is allocated when relay is set, which is also the case in my proposal - the code returns early when relay is not set.

Copy link
Collaborator Author

@vlntb vlntb Jan 24, 2025

Choose a reason for hiding this comment

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

the relay initial is set in here:

bool relay = tx.has_value();

which guarantees that transaction is initialised.
If we remove the check, then later when we try to unbox transaction from optional

auto& txn = tx->get();

we may get a runtime error.

Copy link
Collaborator

@bthomee bthomee left a comment

Choose a reason for hiding this comment

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

LGTM for everything else.

src/xrpld/overlay/detail/PeerImp.cpp Outdated Show resolved Hide resolved
break;
case ListDisposition::invalid:
// This shouldn't ever happen with a well-behaved peer
fee_ = Resource::feeInvalidSignature;
fee_.update(
Resource::feeInvalidSignature, "invalid list disposition");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is invalid signature here appropriate? If not but we just want to charge the peer a lot, you can create a new const fee.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This error is returned from

// The returned PublicKey value is read from the manifest. Manifests do not
// contain the default-constructed public keys
std::pair<ListDisposition, std::optional<PublicKey>>
ValidatorList::verify

from several places including cryptographic signature check.

I agree that we need to look into this and potentially add more granular error types here, because we may get this error for non-cryptographic reasons too, but I wouldn't suggest doing this as part of the hotfix.

Comment on lines +156 to +157
assert(f >= fee);
fee = f;
Copy link
Collaborator

Choose a reason for hiding this comment

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

reading how we call fee_.update in PeerImp.cpp I do not see anything to provide the guarantee that this assert wants. Should this be fee = std::max(f, fee); instead ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

... or even if (f < fee) return;

Copy link
Collaborator Author

@vlntb vlntb Jan 24, 2025

Choose a reason for hiding this comment

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

    handler.onMessageBegin(
        header.message_type,
        m,
        header.payload_wire_size,
        header.uncompressed_size,
        header.algorithm != Algorithm::None);
    handler.onMessage(m);
    handler.onMessageEnd(header.message_type, m);

We always start from onMessageBegin, which sets the fee to the lowest level (feeTrivialPeer = 1).
We only process one message in this run.
This guarantees that fee update always goes from 1 to any other fee that is higher.


### Bug Fixes and Performance Improvements

- Change the charged fee for sending older transactions from feeInvalidSignature to feeUnwantedData. [#5243](https://github.com/XRPLF/rippled/pull/5243)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Change this line; preferably into multiple items (change fees overall, do not charge repeated fees on batch transactions, improve logging of fees, not sure how to describe changes to relay)

if (tx->getStatus() == NEW)
{
JLOG(p_journal_.debug())
<< "Cacheing " << (batch ? "batch" : "unsolicited")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Typo ?

@ximinez ximinez changed the base branch from master to release January 24, 2025 21:36
src/xrpld/overlay/detail/PeerImp.cpp Outdated Show resolved Hide resolved
src/libxrpl/protocol/BuildInfo.cpp Outdated Show resolved Hide resolved
src/xrpld/overlay/detail/OverlayImpl.cpp Outdated Show resolved Hide resolved
@vlntb vlntb requested review from bthomee, ximinez and Bronek January 25, 2025 07:03
Comment on lines +2575 to +2576
// Something new: Dynamic fee
fee_.fee = Resource::Charge(m->transactions_size(), "transactions");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Given that the existing code does not have a corresponding fee for one or more transactions, what is the practical effect of this change?

The handleTransaction function is called for each transaction and already applies charges. Does charging this dynamic fee here cause double-charging or does it serve to fill a gap that currently exists?

Comment on lines +2781 to +2783
JLOG(p_journal_.debug())
<< "Caching " << (batch ? "batch" : "unsolicited")
<< " pseudo-transaction tx " << tx->getID();
Copy link
Collaborator

Choose a reason for hiding this comment

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

This log message doesn't seem to match what is happening here. There's canonicalization and possibly relaying going on, while the log message suggests that both batch and non-batch (unsolicited) tx are being cached.

I'd also recommend just having three separate and appropriately phrased log messages, depending on the situation below (skipped, relayed, charged).

Also, to confirm:

  • One of the fixes introduced in this hotfix was to not relay pseudo-tx, but isn't that what actually can happen here anyway?
  • If a pseudo-tx is non-batch it is unwanted - is there any point to canonicalize and possibly relay it then, before charging the peer for useless data? If possible, moving the if (!batch) check up and then returning early would seem sensible.

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.

4 participants