test large WASM modules (#6206)

* [WIP] first attempt at large wasm test

* finish large WASM modules

* fix windows build (hopefully)

* Apply suggestions from code review

* respond to comments

* add file and line to fail

* clean up test

* add source_location

* simplify

* fix windows
This commit is contained in:
Mayukha Vadari
2026-01-23 14:45:09 -05:00
committed by GitHub
parent 94b35a234e
commit 57d2a91ad5
3 changed files with 292 additions and 0 deletions

View File

@@ -12,6 +12,7 @@
#include <algorithm>
#include <iterator>
#include <source_location>
namespace xrpl {
namespace test {
@@ -982,6 +983,129 @@ struct EscrowSmart_test : public beast::unit_test::suite
}
}
void
testLargeWasmModules(FeatureBitset features)
{
testcase("Test large wasm modules");
using namespace jtx;
using namespace std::chrono;
using namespace wasm_constants;
enum class ExpectedStatus { Success, Malformed, Crash };
auto runTest = [&](std::vector<uint8_t> const& wasm,
std::optional<uint32_t> sizeLimit,
ExpectedStatus expectedStatus,
std::source_location const& loc =
std::source_location::current()) {
auto makeEnv = [&]() -> Env {
if (sizeLimit)
return Env(
*this,
envconfig([&sizeLimit](std::unique_ptr<Config> cfg) {
cfg->FEES.extension_size_limit = *sizeLimit;
return cfg;
}),
features);
else
return Env(*this, features);
};
Env env = makeEnv();
auto const alice = Account("alice");
env.fund(XRP(1'000'000), alice);
env.close();
auto const wasmHex = strHex(wasm);
try
{
env(escrow::create(alice, alice, XRP(1000)),
escrow::finish_function(wasmHex),
escrow::cancel_time(env.now() + 100s),
fee(env.current()->fees().base * 10 +
wasmHex.size() / 2 * 5),
ter(expectedStatus == ExpectedStatus::Success
? TER{tesSUCCESS}
: TER{temMALFORMED}));
if (expectedStatus == ExpectedStatus::Crash)
fail("Expected crash", loc.file_name(), loc.line());
else
pass();
}
catch (std::exception const& e)
{
if (expectedStatus == ExpectedStatus::Crash)
pass();
else
fail(e.what(), loc.file_name(), loc.line());
}
};
// Table-driven test cases
struct TestCase
{
enum class BlobType { Code, Data };
BlobType type;
uint32_t size;
std::optional<uint32_t> sizeLimit;
ExpectedStatus expected;
};
std::vector<TestCase> const testCases = {
// Code blob tests
{TestCase::BlobType::Code,
99'959,
std::nullopt,
ExpectedStatus::Success}, // just under 100kb
{TestCase::BlobType::Code,
99'961,
std::nullopt,
ExpectedStatus::Malformed}, // just over 100kb
{TestCase::BlobType::Code,
200'000,
10'000'000,
ExpectedStatus::Success}, // ~200kb
{TestCase::BlobType::Code,
490'000,
10'000'000,
ExpectedStatus::Success}, // just under 1MB JSON
{TestCase::BlobType::Code,
999'999,
10'000'000,
ExpectedStatus::Crash}, // just over 1MB JSON
// Data blob tests
{TestCase::BlobType::Data,
99'946,
std::nullopt,
ExpectedStatus::Success}, // just under 100kb
{TestCase::BlobType::Data,
99'948,
std::nullopt,
ExpectedStatus::Malformed}, // just over 100kb
{TestCase::BlobType::Data,
200'000,
10'000'000,
ExpectedStatus::Success}, // ~200kb
{TestCase::BlobType::Data,
490'000,
10'000'000,
ExpectedStatus::Success}, // just under 1MB JSON
{TestCase::BlobType::Data,
999'950,
10'000'000,
ExpectedStatus::Crash}, // just over 1MB JSON
};
for (auto const& tc : testCases)
{
auto const wasm = tc.type == TestCase::BlobType::Code
? generateCodeBlob(tc.size)
: generateDataBlob(tc.size);
runTest(wasm, tc.sizeLimit, tc.expected);
}
}
void
testWithFeats(FeatureBitset features)
{
@@ -994,6 +1118,8 @@ struct EscrowSmart_test : public beast::unit_test::suite
// TODO: Update module with new host functions
testAllHostFunctions(features);
testKeyletHostFunctions(features);
testLargeWasmModules(features);
}
public:

View File

@@ -2,6 +2,116 @@
#include <test/app/wasm_fixtures/fixtures.h>
#include <iterator>
namespace wasm_constants {
namespace {
// Helper: Variable-length integer encoding (LEB128)
void
pushLeb128(std::vector<uint8_t>& buf, uint32_t val)
{
do
{
uint8_t byte = val & 0x7F;
val >>= 7;
if (val != 0)
byte |= 0x80;
buf.push_back(byte);
} while (val != 0);
}
// Helper: append bytes from an array to a vector
template <typename T>
void
appendBytes(std::vector<uint8_t>& buf, T const& arr)
{
buf.insert(buf.end(), std::begin(arr), std::end(arr));
}
// Helper: append a WASM section (ID + LEB128 size + content)
// extraSize is added to the encoded size (for trailing fill bytes)
void
appendSection(
std::vector<uint8_t>& wasm,
uint8_t sectionId,
std::vector<uint8_t> const& content,
uint32_t extraSize = 0)
{
wasm.push_back(sectionId);
pushLeb128(wasm, static_cast<uint32_t>(content.size() + extraSize));
appendBytes(wasm, content);
}
} // namespace
std::vector<uint8_t>
generateCodeBlob(uint32_t num_instructions)
{
std::vector<uint8_t> wasm;
appendBytes(wasm, WASM_HEADER);
appendBytes(wasm, TYPE_EMPTY_FUNC);
appendBytes(wasm, FUNC_TYPE0);
appendBytes(wasm, EXPORT_FINISH);
std::vector<uint8_t> body;
pushLeb128(body, 0); // No locals
body.insert(body.end(), num_instructions, INSTR_NOP);
body.push_back(INSTR_END);
std::vector<uint8_t> section;
pushLeb128(section, 1); // 1 function
pushLeb128(section, static_cast<uint32_t>(body.size()));
appendBytes(section, body);
appendSection(wasm, SECTION_CODE, section);
return wasm;
}
std::vector<uint8_t>
generateDataBlob(uint32_t data_size)
{
std::vector<uint8_t> wasm;
appendBytes(wasm, WASM_HEADER);
appendBytes(wasm, TYPE_EMPTY_FUNC);
appendBytes(wasm, FUNC_TYPE0);
// Memory Section: must be large enough for data_size
uint32_t pages = (data_size + 65535) / 65536;
std::vector<uint8_t> mem_p;
pushLeb128(mem_p, 1); // 1 memory defined
mem_p.push_back(0x00); // Flags (minimum only)
pushLeb128(mem_p, pages); // Page count
appendSection(wasm, SECTION_MEMORY, mem_p);
appendBytes(wasm, EXPORT_FINISH);
// Code Section: MUST come before Data Section per WASM spec
std::vector<uint8_t> code_p;
pushLeb128(code_p, 1); // 1 function body
pushLeb128(code_p, static_cast<uint32_t>(std::size(EMPTY_BODY)));
appendBytes(code_p, EMPTY_BODY);
appendSection(wasm, SECTION_CODE, code_p);
// Data Section: the actual bloat
std::vector<uint8_t> data_seg;
data_seg.push_back(0x00); // Memory index 0
appendBytes(data_seg, DATA_OFFSET_ZERO);
pushLeb128(data_seg, data_size);
std::vector<uint8_t> data_p;
pushLeb128(data_p, 1); // 1 data segment
appendBytes(data_p, data_seg);
appendSection(wasm, SECTION_DATA, data_p, data_size);
wasm.insert(wasm.end(), data_size, DATA_FILL_BYTE);
return wasm;
}
} // namespace wasm_constants
extern std::string const fibWasmHex =
"0061736d0100000001090260000060017f017f0303020001071b02115f5f"
"7761736d5f63616c6c5f63746f727300000366696200010a440202000b3f"

View File

@@ -2,7 +2,63 @@
// TODO: consider moving these to separate files (and figure out the build)
#include <cstdint>
#include <string>
#include <vector>
// WASM binary format constants and helpers for building test modules
namespace wasm_constants {
// Magic + version header
static constexpr uint8_t WASM_HEADER[] = {
0x00,
0x61,
0x73,
0x6d, // magic: \0asm
0x01,
0x00,
0x00,
0x00 // version: 1
};
// Type section: () -> ()
static constexpr uint8_t TYPE_EMPTY_FUNC[] =
{0x01, 0x04, 0x01, 0x60, 0x00, 0x00};
// Function section: one function using type 0
static constexpr uint8_t FUNC_TYPE0[] = {0x03, 0x02, 0x01, 0x00};
// Export section: export func 0 as "finish"
static constexpr uint8_t EXPORT_FINISH[] =
{0x07, 0x0a, 0x01, 0x06, 'f', 'i', 'n', 'i', 's', 'h', 0x00, 0x00};
// Empty function body: 0 locals, end
static constexpr uint8_t EMPTY_BODY[] = {0x00, 0x0b};
// Data segment offset: i32.const 0, end
static constexpr uint8_t DATA_OFFSET_ZERO[] = {0x41, 0x00, 0x0b};
// Section IDs
static constexpr uint8_t SECTION_MEMORY = 0x05;
static constexpr uint8_t SECTION_CODE = 0x0a;
static constexpr uint8_t SECTION_DATA = 0x0b;
// Instructions
static constexpr uint8_t INSTR_NOP = 0x01;
static constexpr uint8_t INSTR_END = 0x0b;
// Fill byte for data section bloat
static constexpr uint8_t DATA_FILL_BYTE = 0xEE;
// Generator for WASM module with large code section (many NOPs)
std::vector<uint8_t>
generateCodeBlob(uint32_t num_instructions);
// Generator for WASM module with large data section
std::vector<uint8_t>
generateDataBlob(uint32_t data_size);
} // namespace wasm_constants
extern std::string const ledgerSqnWasmHex;