Compare commits

...

36 Commits

Author SHA1 Message Date
Mayukha Vadari
61bcb7621f Merge branch 'develop' into ripple/wasmi 2026-03-03 11:18:26 -04:00
Sergey Kuznetsov
5300e65686 tests: Improve stability of Subscribe tests (#6420)
The `Subscribe` tests were flaky, because each test performs some operations (e.g. sends transactions) and waits for messages to appear in subscription with a 100ms timeout. If tests are slow (e.g. compiled in debug mode or a slow machine) then some of them could fail. This change adds an attempt to synchronize the background Env's thread and the test's thread by ensuring that all the scheduled operations are started before the test's thread starts to wait for a websocket message. This is done by limiting I/O threads of the app inside Env to 1 and adding a synchronization barrier after closing the ledger.
2026-03-03 08:46:55 -05:00
Mayukha Vadari
a3f71b1774 Merge branch 'develop' into ripple/wasmi 2026-03-02 17:06:17 -05:00
Mayukha Vadari
b08bcf5d21 Merge branch 'develop' into ripple/wasmi 2026-02-27 16:41:44 -05:00
Mayukha Vadari
77dfd56ace Merge branch 'develop' into ripple/wasmi 2026-02-27 13:49:25 -05:00
Mayukha Vadari
9c3c0280b1 Merge branch 'develop' into ripple/wasmi 2026-02-18 12:35:51 -05:00
Mayukha Vadari
bd16f7989d Merge branch 'develop' into ripple/wasmi 2026-02-10 17:26:33 -05:00
Mayukha Vadari
2ec4a1114e Merge branch 'develop' into ripple/wasmi 2026-02-04 18:13:00 -05:00
Mayukha Vadari
b2627039f6 Merge branch 'develop' into ripple/wasmi 2026-02-03 14:51:59 -05:00
Mayukha Vadari
e85e7b1b1a Merge branch 'develop' into ripple/wasmi 2026-01-29 13:53:55 -05:00
Mayukha Vadari
72fffb6e51 Merge branch 'develop' into ripple/wasmi 2026-01-28 15:56:18 -05:00
Mayukha Vadari
f7ee580f01 Merge commit '5f638f55536def0d88b970d1018a465a238e55f4' into ripple/wasmi 2026-01-28 15:56:11 -05:00
Mayukha Vadari
122d405750 Merge commit '92046785d1fea5f9efe5a770d636792ea6cab78b' into ripple/wasmi 2026-01-28 15:56:04 -05:00
Mayukha Vadari
d7ed6d6512 Merge branch 'develop' into ripple/wasmi 2026-01-27 13:26:39 -05:00
Mayukha Vadari
8bc6f9cd70 Merge branch 'develop' into ripple/wasmi 2026-01-23 13:13:11 -05:00
Mayukha Vadari
ed5139d4e3 Merge branch 'develop' into ripple/wasmi 2026-01-21 12:57:29 -05:00
Mayukha Vadari
7a9d245950 Merge branch 'develop' into ripple/wasmi 2026-01-14 13:01:35 -05:00
Olek
d83ec96848 Switch to wasmi v1.0.6 (#6204) 2026-01-12 13:36:02 -05:00
Mayukha Vadari
419d53ec4c Merge branch 'develop' into ripple/wasmi 2026-01-12 13:10:58 -05:00
Mayukha Vadari
d4d70d5675 Merge branch 'develop' into ripple/wasmi 2026-01-12 12:27:48 -05:00
Mayukha Vadari
bbc28b3b1c Merge branch 'develop' into ripple/wasmi 2026-01-08 11:42:28 -05:00
Mayukha Vadari
5aab274b7a Merge branch 'develop' into ripple/wasmi 2026-01-07 16:52:10 -05:00
Mayukha Vadari
2c30e41191 use the develop hashes 2026-01-07 16:50:45 -05:00
Mayukha Vadari
8ea5106b0b Merge branch 'develop' into ripple/wasmi 2026-01-07 14:34:49 -05:00
Mayukha Vadari
1977df9c2e Merge remote-tracking branch 'upstream/develop' into ripple/wasmi 2026-01-05 18:43:49 -05:00
Mayukha Vadari
6c95548df5 Merge remote-tracking branch 'upstream/develop' into ripple/wasmi 2025-12-22 15:51:19 -08:00
Mayukha Vadari
90e0bbd0fc Merge branch 'develop' into ripple/wasmi 2025-12-08 14:28:41 -05:00
Olek
b57df290de Use conan repo for wasmi lib (#6109)
* Use conan repo for wasmi lib
* Generate lockfile
2025-12-08 13:02:01 -05:00
Mayukha Vadari
8a403f1241 Merge branch 'develop' into ripple/wasmi 2025-12-05 14:32:48 -05:00
Mayukha Vadari
6d2640871d Merge branch 'develop' into ripple/wasmi 2025-12-02 18:40:54 -05:00
Olek
500bb68831 Fix win build (#6076) 2025-11-24 16:56:23 -05:00
Mayukha Vadari
16087c9680 fix merge issue 2025-11-25 02:57:47 +05:30
Mayukha Vadari
25c3060fef remove conan.lock (temporary) 2025-11-25 02:40:57 +05:30
Mayukha Vadari
ce9f0b38a4 Merge branch 'develop' into ripple/wasmi 2025-11-25 02:33:47 +05:30
Mayukha Vadari
35f7cbf772 update 2025-11-25 02:31:51 +05:30
Mayukha Vadari
0db564d261 WASMI data 2025-11-04 15:57:07 -05:00
12 changed files with 133 additions and 53 deletions

View File

@@ -106,6 +106,7 @@ find_package(OpenSSL REQUIRED)
find_package(secp256k1 REQUIRED)
find_package(SOCI REQUIRED)
find_package(SQLite3 REQUIRED)
find_package(wasmi REQUIRED)
find_package(xxHash REQUIRED)
target_link_libraries(

View File

@@ -47,6 +47,7 @@ target_link_libraries(
Xrpl::opts
Xrpl::syslibs
secp256k1::secp256k1
wasmi::wasmi
xrpl.libpb
xxHash::xxhash
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>)

View File

@@ -3,6 +3,7 @@
"requires": [
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075",
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
"wasmi/1.0.6#407c9db14601a8af1c7dd3b388f3e4cd%1768164779.349",
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1765850149.926",
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1765850149.46",
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878",

View File

@@ -35,6 +35,7 @@ class Xrpl(ConanFile):
"openssl/3.5.5",
"secp256k1/0.7.1",
"soci/4.0.3",
"wasmi/1.0.6",
"zlib/1.3.1",
]
@@ -215,6 +216,7 @@ class Xrpl(ConanFile):
"soci::soci",
"secp256k1::secp256k1",
"sqlite3::sqlite",
"wasmi::wasmi",
"xxhash::xxhash",
"zlib::zlib",
]

View File

@@ -293,6 +293,7 @@ words:
- venv
- vfalco
- vinnie
- wasmi
- wextra
- wptr
- writeme

View File

@@ -31,6 +31,7 @@
#include <xrpl/protocol/STTx.h>
#include <functional>
#include <future>
#include <source_location>
#include <string>
#include <tuple>
@@ -393,6 +394,48 @@ public:
return close(std::chrono::seconds(5));
}
/** Close and advance the ledger, then synchronize with the server's
io_context to ensure all async operations initiated by the close have
been started.
This function performs the same ledger close as close(), but additionally
ensures that all tasks posted to the server's io_context (such as
WebSocket subscription message sends) have been initiated before returning.
What it guarantees:
- All async operations posted before syncClose() have been STARTED
- For WebSocket sends: async_write_some() has been called
- The actual I/O completion may still be pending (async)
What it does NOT guarantee:
- Async operations have COMPLETED
- WebSocket messages have been received by clients
- However, for localhost connections, the remaining latency is typically
microseconds, making tests reliable
Use this instead of close() when:
- Test code immediately checks for subscription messages
- Race conditions between test and worker threads must be avoided
- Deterministic test behavior is required
@param timeout Maximum time to wait for the barrier task to execute
@return true if close succeeded and barrier executed within timeout,
false otherwise
*/
[[nodiscard]] bool
syncClose(std::chrono::steady_clock::duration timeout = std::chrono::seconds{1})
{
XRPL_ASSERT(
app().getNumberOfThreads() == 1,
"syncClose() is only useful on an application with a single thread");
auto const result = close();
auto serverBarrier = std::make_shared<std::promise<void>>();
auto future = serverBarrier->get_future();
boost::asio::post(app().getIOContext(), [serverBarrier]() { serverBarrier->set_value(); });
auto const status = future.wait_for(timeout);
return result && status == std::future_status::ready;
}
/** Turn on JSON tracing.
With no arguments, trace all
*/

View File

@@ -73,6 +73,8 @@ std::unique_ptr<Config> admin_localnet(std::unique_ptr<Config>);
std::unique_ptr<Config> secure_gateway_localnet(std::unique_ptr<Config>);
std::unique_ptr<Config> single_thread_io(std::unique_ptr<Config>);
/// @brief adjust configuration with params needed to be a validator
///
/// this is intended for use with envconfig, as in

View File

@@ -87,6 +87,12 @@ secure_gateway_localnet(std::unique_ptr<Config> cfg)
(*cfg)[PORT_WS].set("secure_gateway", "127.0.0.0/8");
return cfg;
}
std::unique_ptr<Config>
single_thread_io(std::unique_ptr<Config> cfg)
{
cfg->IO_WORKERS = 1;
return cfg;
}
auto constexpr defaultseed = "shUwVw52ofnCUX5m7kPTKzJdr4HEH";

View File

@@ -26,7 +26,7 @@ public:
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
Env env{*this, single_thread_io(envconfig())};
auto wsc = makeWSClient(env.app().config());
Json::Value stream;
@@ -92,7 +92,7 @@ public:
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
Env env{*this, single_thread_io(envconfig())};
auto wsc = makeWSClient(env.app().config());
Json::Value stream;
@@ -114,7 +114,7 @@ public:
{
// Accept a ledger
env.close();
BEAST_EXPECT(env.syncClose());
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
@@ -125,7 +125,7 @@ public:
{
// Accept another ledger
env.close();
BEAST_EXPECT(env.syncClose());
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
@@ -150,7 +150,7 @@ public:
{
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
Env env(*this, single_thread_io(envconfig()));
auto baseFee = env.current()->fees().base.drops();
auto wsc = makeWSClient(env.app().config());
Json::Value stream;
@@ -171,7 +171,7 @@ public:
{
env.fund(XRP(10000), "alice");
env.close();
BEAST_EXPECT(env.syncClose());
// Check stream update for payment transaction
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
@@ -195,7 +195,7 @@ public:
}));
env.fund(XRP(10000), "bob");
env.close();
BEAST_EXPECT(env.syncClose());
// Check stream update for payment transaction
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
@@ -249,12 +249,12 @@ public:
{
// Transaction that does not affect stream
env.fund(XRP(10000), "carol");
env.close();
BEAST_EXPECT(env.syncClose());
BEAST_EXPECT(!wsc->getMsg(10ms));
// Transactions concerning alice
env.trust(Account("bob")["USD"](100), "alice");
env.close();
BEAST_EXPECT(env.syncClose());
// Check stream updates
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
@@ -288,6 +288,7 @@ public:
using namespace jtx;
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
cfg->FEES.reference_fee = 10;
cfg = single_thread_io(std::move(cfg));
return cfg;
}));
auto wsc = makeWSClient(env.app().config());
@@ -310,7 +311,7 @@ public:
{
env.fund(XRP(10000), "alice");
env.close();
BEAST_EXPECT(env.syncClose());
// Check stream update for payment transaction
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
@@ -360,7 +361,7 @@ public:
testManifests()
{
using namespace jtx;
Env env(*this);
Env env(*this, single_thread_io(envconfig()));
auto wsc = makeWSClient(env.app().config());
Json::Value stream;
@@ -394,7 +395,7 @@ public:
{
using namespace jtx;
Env env{*this, envconfig(validator, ""), features};
Env env{*this, single_thread_io(envconfig(validator, "")), features};
auto& cfg = env.app().config();
if (!BEAST_EXPECT(cfg.section(SECTION_VALIDATION_SEED).empty()))
return;
@@ -483,7 +484,7 @@ public:
// at least one flag ledger.
while (env.closed()->header().seq < 300)
{
env.close();
BEAST_EXPECT(env.syncClose());
using namespace std::chrono_literals;
BEAST_EXPECT(wsc->findMsg(5s, validValidationFields));
}
@@ -505,7 +506,7 @@ public:
{
using namespace jtx;
testcase("Subscribe by url");
Env env{*this};
Env env{*this, single_thread_io(envconfig())};
Json::Value jv;
jv[jss::url] = "http://localhost/events";
@@ -536,7 +537,7 @@ public:
auto const method = subscribe ? "subscribe" : "unsubscribe";
testcase << "Error cases for " << method;
Env env{*this};
Env env{*this, single_thread_io(envconfig())};
auto wsc = makeWSClient(env.app().config());
{
@@ -572,7 +573,7 @@ public:
}
{
Env env_nonadmin{*this, no_admin(envconfig())};
Env env_nonadmin{*this, single_thread_io(no_admin(envconfig()))};
Json::Value jv;
jv[jss::url] = "no-url";
auto jr = env_nonadmin.rpc("json", method, to_string(jv))[jss::result];
@@ -834,12 +835,13 @@ public:
* send payments between the two accounts a and b,
* and close ledgersToClose ledgers
*/
auto sendPayments = [](Env& env,
Account const& a,
Account const& b,
int newTxns,
std::uint32_t ledgersToClose,
int numXRP = 10) {
auto sendPayments = [this](
Env& env,
Account const& a,
Account const& b,
int newTxns,
std::uint32_t ledgersToClose,
int numXRP = 10) {
env.memoize(a);
env.memoize(b);
for (int i = 0; i < newTxns; ++i)
@@ -852,7 +854,7 @@ public:
jtx::sig(jtx::autofill));
}
for (int i = 0; i < ledgersToClose; ++i)
env.close();
BEAST_EXPECT(env.syncClose());
return newTxns;
};
@@ -945,7 +947,7 @@ public:
*
* also test subscribe to the account before it is created
*/
Env env(*this);
Env env(*this, single_thread_io(envconfig()));
auto wscTxHistory = makeWSClient(env.app().config());
Json::Value request;
request[jss::account_history_tx_stream] = Json::objectValue;
@@ -988,7 +990,7 @@ public:
* subscribe genesis account tx history without txns
* subscribe to bob's account after it is created
*/
Env env(*this);
Env env(*this, single_thread_io(envconfig()));
auto wscTxHistory = makeWSClient(env.app().config());
Json::Value request;
request[jss::account_history_tx_stream] = Json::objectValue;
@@ -998,6 +1000,7 @@ public:
if (!BEAST_EXPECT(goodSubRPC(jv)))
return;
IdxHashVec genesisFullHistoryVec;
BEAST_EXPECT(env.syncClose());
if (!BEAST_EXPECT(!getTxHash(*wscTxHistory, genesisFullHistoryVec, 1).first))
return;
@@ -1016,6 +1019,7 @@ public:
if (!BEAST_EXPECT(goodSubRPC(jv)))
return;
IdxHashVec bobFullHistoryVec;
BEAST_EXPECT(env.syncClose());
r = getTxHash(*wscTxHistory, bobFullHistoryVec, 1);
if (!BEAST_EXPECT(r.first && r.second))
return;
@@ -1050,6 +1054,7 @@ public:
"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
jv = wscTxHistory->invoke("subscribe", request);
genesisFullHistoryVec.clear();
BEAST_EXPECT(env.syncClose());
BEAST_EXPECT(getTxHash(*wscTxHistory, genesisFullHistoryVec, 31).second);
jv = wscTxHistory->invoke("unsubscribe", request);
@@ -1062,13 +1067,13 @@ public:
* subscribe account and subscribe account tx history
* and compare txns streamed
*/
Env env(*this);
Env env(*this, single_thread_io(envconfig()));
auto wscAccount = makeWSClient(env.app().config());
auto wscTxHistory = makeWSClient(env.app().config());
std::array<Account, 2> accounts = {alice, bob};
env.fund(XRP(222222), accounts);
env.close();
BEAST_EXPECT(env.syncClose());
// subscribe account
Json::Value stream = Json::objectValue;
@@ -1131,18 +1136,18 @@ public:
* alice issues USD to carol
* mix USD and XRP payments
*/
Env env(*this);
Env env(*this, single_thread_io(envconfig()));
auto const USD_a = alice["USD"];
std::array<Account, 2> accounts = {alice, carol};
env.fund(XRP(333333), accounts);
env.trust(USD_a(20000), carol);
env.close();
BEAST_EXPECT(env.syncClose());
auto mixedPayments = [&]() -> int {
sendPayments(env, alice, carol, 1, 0);
env(pay(alice, carol, USD_a(100)));
env.close();
BEAST_EXPECT(env.syncClose());
return 2;
};
@@ -1152,6 +1157,7 @@ public:
request[jss::account_history_tx_stream][jss::account] = carol.human();
auto ws = makeWSClient(env.app().config());
auto jv = ws->invoke("subscribe", request);
BEAST_EXPECT(env.syncClose());
{
// take out existing txns from the stream
IdxHashVec tempVec;
@@ -1169,10 +1175,10 @@ public:
/*
* long transaction history
*/
Env env(*this);
Env env(*this, single_thread_io(envconfig()));
std::array<Account, 2> accounts = {alice, carol};
env.fund(XRP(444444), accounts);
env.close();
BEAST_EXPECT(env.syncClose());
// many payments, and close lots of ledgers
auto oneRound = [&](int numPayments) {
@@ -1185,6 +1191,7 @@ public:
request[jss::account_history_tx_stream][jss::account] = carol.human();
auto wscLong = makeWSClient(env.app().config());
auto jv = wscLong->invoke("subscribe", request);
BEAST_EXPECT(env.syncClose());
{
// take out existing txns from the stream
IdxHashVec tempVec;
@@ -1222,7 +1229,7 @@ public:
jtx::testable_amendments() | featurePermissionedDomains | featureCredentials |
featurePermissionedDEX};
Env env(*this, all);
Env env(*this, single_thread_io(envconfig()), all);
PermissionedDEX permDex(env);
auto const alice = permDex.alice;
auto const bob = permDex.bob;
@@ -1241,10 +1248,10 @@ public:
if (!BEAST_EXPECT(jv[jss::status] == "success"))
return;
env(offer(alice, XRP(10), USD(10)), domain(domainID), txflags(tfHybrid));
env.close();
BEAST_EXPECT(env.syncClose());
env(pay(bob, carol, USD(5)), path(~USD), sendmax(XRP(5)), domain(domainID));
env.close();
BEAST_EXPECT(env.syncClose());
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
if (jv[jss::changes].size() != 1)
@@ -1284,9 +1291,9 @@ public:
Account const bob{"bob"};
Account const broker{"broker"};
Env env{*this, features};
Env env{*this, single_thread_io(envconfig()), features};
env.fund(XRP(10000), alice, bob, broker);
env.close();
BEAST_EXPECT(env.syncClose());
auto wsc = test::makeWSClient(env.app().config());
Json::Value stream;
@@ -1350,12 +1357,12 @@ public:
// Verify the NFTokenIDs are correct in the NFTokenMint tx meta
uint256 const nftId1{token::getNextID(env, alice, 0u, tfTransferable)};
env(token::mint(alice, 0u), txflags(tfTransferable));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenID(nftId1);
uint256 const nftId2{token::getNextID(env, alice, 0u, tfTransferable)};
env(token::mint(alice, 0u), txflags(tfTransferable));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenID(nftId2);
// Alice creates one sell offer for each NFT
@@ -1363,32 +1370,32 @@ public:
// meta
uint256 const aliceOfferIndex1 = keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId1, drops(1)), txflags(tfSellNFToken));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenOfferID(aliceOfferIndex1);
uint256 const aliceOfferIndex2 = keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId2, drops(1)), txflags(tfSellNFToken));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenOfferID(aliceOfferIndex2);
// Alice cancels two offers she created
// Verify the NFTokenIDs are correct in the NFTokenCancelOffer tx
// meta
env(token::cancelOffer(alice, {aliceOfferIndex1, aliceOfferIndex2}));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenIDsInCancelOffer({nftId1, nftId2});
// Bobs creates a buy offer for nftId1
// Verify the offer id is correct in the NFTokenCreateOffer tx meta
auto const bobBuyOfferIndex = keylet::nftoffer(bob, env.seq(bob)).key;
env(token::createOffer(bob, nftId1, drops(1)), token::owner(alice));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenOfferID(bobBuyOfferIndex);
// Alice accepts bob's buy offer
// Verify the NFTokenID is correct in the NFTokenAcceptOffer tx meta
env(token::acceptBuyOffer(alice, bobBuyOfferIndex));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenID(nftId1);
}
@@ -1397,7 +1404,7 @@ public:
// Alice mints a NFT
uint256 const nftId{token::getNextID(env, alice, 0u, tfTransferable)};
env(token::mint(alice, 0u), txflags(tfTransferable));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenID(nftId);
// Alice creates sell offer and set broker as destination
@@ -1405,18 +1412,18 @@ public:
env(token::createOffer(alice, nftId, drops(1)),
token::destination(broker),
txflags(tfSellNFToken));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenOfferID(offerAliceToBroker);
// Bob creates buy offer
uint256 const offerBobToBroker = keylet::nftoffer(bob, env.seq(bob)).key;
env(token::createOffer(bob, nftId, drops(1)), token::owner(alice));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenOfferID(offerBobToBroker);
// Check NFTokenID meta for NFTokenAcceptOffer in brokered mode
env(token::brokerOffers(broker, offerBobToBroker, offerAliceToBroker));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenID(nftId);
}
@@ -1426,24 +1433,24 @@ public:
// Alice mints a NFT
uint256 const nftId{token::getNextID(env, alice, 0u, tfTransferable)};
env(token::mint(alice, 0u), txflags(tfTransferable));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenID(nftId);
// Alice creates 2 sell offers for the same NFT
uint256 const aliceOfferIndex1 = keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId, drops(1)), txflags(tfSellNFToken));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenOfferID(aliceOfferIndex1);
uint256 const aliceOfferIndex2 = keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId, drops(1)), txflags(tfSellNFToken));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenOfferID(aliceOfferIndex2);
// Make sure the metadata only has 1 nft id, since both offers are
// for the same nft
env(token::cancelOffer(alice, {aliceOfferIndex1, aliceOfferIndex2}));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenIDsInCancelOffer({nftId});
}
@@ -1451,7 +1458,7 @@ public:
{
uint256 const aliceMintWithOfferIndex1 = keylet::nftoffer(alice, env.seq(alice)).key;
env(token::mint(alice), token::amount(XRP(0)));
env.close();
BEAST_EXPECT(env.syncClose());
verifyNFTokenOfferID(aliceMintWithOfferIndex1);
}
}

View File

@@ -1072,6 +1072,12 @@ public:
return trapTxID_;
}
size_t
getNumberOfThreads() const override
{
return get_number_of_threads();
}
private:
// For a newly-started validator, this is the greatest persisted ledger
// and new validations must be greater than this.

View File

@@ -157,6 +157,10 @@ public:
* than the last ledger it persisted. */
virtual LedgerIndex
getMaxDisallowedLedger() = 0;
/** Returns the number of io_context (I/O worker) threads used by the application. */
virtual size_t
getNumberOfThreads() const = 0;
};
std::unique_ptr<Application>

View File

@@ -23,4 +23,10 @@ public:
{
return io_context_;
}
size_t
get_number_of_threads() const
{
return threads_.size();
}
};