mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-24 23:50:22 +00:00
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:
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user