fix: Charge kFeeHeavyBurdenRpc in doChannelVerify

This commit is contained in:
Bart
2026-07-16 16:31:34 -04:00
committed by Ayaz Salikhov
parent e1dae5f775
commit a5af6b4e4a
3 changed files with 159 additions and 2 deletions

View File

@@ -13,13 +13,18 @@
#include <test/jtx/ticket.h>
#include <test/jtx/txflags.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/Role.h>
#include <xrpld/rpc/handlers/Handlers.h>
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Zero.h> // IWYU pragma: keep
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/Job.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/to_string.h>
@@ -39,6 +44,9 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/resource/Charge.h>
#include <xrpl/resource/Consumer.h>
#include <xrpl/resource/Fees.h>
#include <algorithm>
#include <cassert>
@@ -48,6 +56,7 @@
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
@@ -495,7 +504,7 @@ struct PayChan_test : public beast::unit_test::Suite
// Owner closes, will close after settleDelay
env(claim(alice, chan), Txflags(tfClose));
BEAST_EXPECT(channelExists(*env.current(), chan));
env.close(settleTimepoint - settleDelay / 2);
env.close(settleTimepoint - (settleDelay / 2));
{
// receiver can still claim
auto const chanBal = channelBalance(*env.current(), chan);
@@ -1587,6 +1596,146 @@ struct PayChan_test : public beast::unit_test::Suite
}
}
void
testChannelVerifyLoadType(FeatureBitset features)
{
testcase("channel_verify sets kFEE_HEAVY_BURDEN_RPC load type");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
env.fund(XRP(10000), alice, bob);
auto const pk = alice.pk();
auto const settleDelay = 3600s;
auto const channelFunds = XRP(1000);
auto const chanStr = to_string(channel(alice, bob, env.seq(alice)));
env(create(alice, bob, channelFunds, settleDelay, pk));
env.close();
// Step 1: get a valid signature from channel_authorize
auto const authResult = env.rpc("channel_authorize", "alice", chanStr, "1000");
auto const sig = authResult[jss::result][jss::signature].asString();
BEAST_EXPECT(!sig.empty());
auto const pkHex = strHex(pk.slice());
// Step 2: build RPC::JsonContext directly so we can inspect loadType
auto& app = env.app();
Resource::Charge loadType = Resource::kFeeReferenceRpc;
Resource::Consumer c;
RPC::JsonContext context{
{.j = env.journal,
.app = app,
.loadType = loadType,
.netOps = app.getOPs(),
.ledgerMaster = app.getLedgerMaster(),
.consumer = c,
.role = Role::USER,
.coro = {},
.infoSub = {},
.apiVersion = RPC::kApiVersionIfUnspecified},
{},
{}};
json::Value params;
params[jss::public_key] = pkHex;
params[jss::channel_id] = chanStr;
params[jss::amount] = "1000";
params[jss::signature] = sig;
context.params = std::move(params);
// Confirm default before calling handler
BEAST_EXPECT(context.loadType == Resource::kFeeReferenceRpc);
json::Value result;
Gate g;
app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) {
context.coro = coro;
result = doChannelVerify(context);
g.signal();
});
using namespace std::chrono_literals;
BEAST_EXPECT(g.waitFor(5s));
// Signature must verify correctly
BEAST_EXPECT(result[jss::signature_verified].asBool());
// KEY ASSERTION: loadType must be kFEE_HEAVY_BURDEN_RPC after the fix
// Before fix: this will FAIL because loadType stays kFEE_REFERENCE_RPC (20)
// After fix: this will PASS because loadType is kFEE_HEAVY_BURDEN_RPC (3000)
BEAST_EXPECT(context.loadType == Resource::kFeeHeavyBurdenRpc);
// Confirm the charge is 150x heavier than the current (broken) default
BEAST_EXPECT(context.loadType.cost() == Resource::kFeeHeavyBurdenRpc.cost()); // 3000
BEAST_EXPECT(context.loadType.cost() != Resource::kFeeReferenceRpc.cost()); // not 20
}
void
testChannelAuthorizeLoadType(FeatureBitset features)
{
testcase("channel_authorize sets kFEE_HEAVY_BURDEN_RPC load type");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
env.fund(XRP(10000), alice, bob);
auto const pk = alice.pk();
auto const settleDelay = 3600s;
auto const chanStr = to_string(channel(alice, bob, env.seq(alice)));
env(create(alice, bob, XRP(1000), settleDelay, pk));
env.close();
auto& app = env.app();
Resource::Charge loadType = Resource::kFeeReferenceRpc;
Resource::Consumer c;
RPC::JsonContext context{
{.j = env.journal,
.app = app,
.loadType = loadType,
.netOps = app.getOPs(),
.ledgerMaster = app.getLedgerMaster(),
.consumer = c,
.role = Role::ADMIN, // channel_authorize requires ADMIN or canSign()
.coro = {},
.infoSub = {},
.apiVersion = RPC::kApiVersionIfUnspecified},
{},
{}};
json::Value params;
params[jss::channel_id] = chanStr;
params[jss::amount] = "1000";
params[jss::secret] = alice.name(); // use account name as seed
context.params = std::move(params);
// Confirm default before calling handler
BEAST_EXPECT(context.loadType == Resource::kFeeReferenceRpc);
json::Value result;
Gate g;
app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) {
context.coro = coro;
result = doChannelAuthorize(context);
g.signal();
});
using namespace std::chrono_literals;
BEAST_EXPECT(g.waitFor(5s));
// Must return a valid signature
BEAST_EXPECT(result.isMember(jss::signature));
BEAST_EXPECT(!result[jss::signature].asString().empty());
// KEY ASSERTION: loadType must be kFEE_HEAVY_BURDEN_RPC after the fix
// Before fix: FAILS — stays at kFEE_REFERENCE_RPC (charge=20)
// After fix: PASSES — set to kFEE_HEAVY_BURDEN_RPC (charge=3000)
BEAST_EXPECT(context.loadType == Resource::kFeeHeavyBurdenRpc);
}
void
testMalformedPK(FeatureBitset features)
{
@@ -1983,6 +2132,8 @@ struct PayChan_test : public beast::unit_test::Suite
testMetaAndOwnership(features);
testAccountDelete(features);
testUsingTickets(features);
testChannelVerifyLoadType(features);
testChannelAuthorizeLoadType(features);
}
public:

View File

@@ -13,6 +13,7 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol/tokens.h>
#include <xrpl/resource/Fees.h>
#include <cstdint>
#include <optional>
@@ -36,6 +37,8 @@ doChannelVerify(RPC::JsonContext& context)
return RPC::missingFieldError(p);
}
context.loadType = Resource::kFeeHeavyBurdenRpc;
std::optional<PublicKey> pk;
{
std::string const strPk = params[jss::public_key].asString();

View File

@@ -15,6 +15,7 @@
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/resource/Fees.h>
#include <cstdint>
#include <exception>
@@ -37,6 +38,8 @@ doChannelAuthorize(RPC::JsonContext& context)
return RPC::makeError(RpcNotSupported, "Signing is not supported by this server.");
}
context.loadType = Resource::kFeeHeavyBurdenRpc;
auto const& params(context.params);
for (auto const& p : {jss::channel_id, jss::amount})
{