Skip to content

Commit

Permalink
Test upload-and-run scenario for module cache
Browse files Browse the repository at this point in the history
  • Loading branch information
graydon committed Jan 31, 2025
1 parent 88f6387 commit 7927396
Showing 1 changed file with 122 additions and 0 deletions.
122 changes: 122 additions & 0 deletions src/transactions/test/InvokeHostFunctionTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4807,3 +4807,125 @@ TEST_CASE("Module cache across protocol versions", "[tx][soroban]")
// We can now run the same contract with 700k instructions
REQUIRE(invoke(700'000));
}

TEST_CASE("Module cache miss on immediate execution", "[tx][soroban]")
{
VirtualClock clock;
auto cfg = getTestConfig(0);
cfg.USE_CONFIG_FOR_GENESIS = false;

auto app = createTestApplication(clock, cfg);

// Start in protocol version that supports module cache
auto upgradeTo = LedgerUpgrade{LEDGER_UPGRADE_VERSION};
upgradeTo.newLedgerVersion() =
static_cast<int>(REUSABLE_SOROBAN_MODULE_CACHE_PROTOCOL_VERSION);
executeUpgrade(*app, upgradeTo);

SorobanTest test(app);
auto wasm = rust_bridge::get_test_wasm_add_i32();

// Helper to create an invocation of add(7,16) with specified instructions
// limit
auto makeAddInvocation = [&](TestContract const& contract,
int64_t instructions, TestAccount& source) {
auto fnName = "add";
auto sc7 = makeI32(7);
auto sc16 = makeI32(16);

auto spec = SorobanInvocationSpec()
.setInstructions(instructions)
.setReadBytes(2'000)
.setInclusionFee(12345)
.setNonRefundableResourceFee(33'000)
.setRefundableResourceFee(100'000);

auto invocation = contract.prepareInvocation(fnName, {sc7, sc16}, spec);
return invocation.createTx(&source);
};

SECTION("separate ledger upload and execution")
{
// First upload the contract
auto const& contract = test.deployWasmContract(wasm);

// Confirm upload succeeded and triggered compilation
REQUIRE(app->getLedgerManager()
.getSorobanMetrics()
.mModuleCacheNumEntries.count() == 1);

// Now try to execute with lower instructions since we can use cached
// module
auto tx = makeAddInvocation(contract, 700'000, test.getRoot());
REQUIRE(isSuccessResult(test.invokeTx(tx)));
}

SECTION("same ledger upload and execution")
{

// Here we're going to create 3 txs in the same ledger (so they have to
// come from 3 separate accounts). The first uploads a contract wasm,
// the second creates a contract, and the third runs it.
//
// Because all 3 happen in the same ledger, there is no opportunity for
// the module cache to be populated between the upload and the
// execution. This should result in a cache miss and higher cost: the
// 3rd (invoking) tx fails.
//
// Finally to confirm that the cache is populated, we run the same
// invocation in the next ledger and it should succeed.

auto minbal = test.getApp().getLedgerManager().getLastMinBalance(1);
TestAccount A(test.getRoot().create("A", minbal * 1000));
TestAccount B(test.getRoot().create("B", minbal * 1000));
TestAccount C(test.getRoot().create("C", minbal * 1000));

// Transaction 1: the upload
auto uploadResources = defaultUploadWasmResourcesWithoutFootprint(
wasm, getLclProtocolVersion(test.getApp()));
auto uploadTx = makeSorobanWasmUploadTx(test.getApp(), A, wasm,
uploadResources, 1000);

// Transaction 2: create contract
Hash contractHash = sha256(wasm);
ContractExecutable executable = makeWasmExecutable(contractHash);
Hash salt = sha256("salt");
ContractIDPreimage contractPreimage = makeContractIDPreimage(B, salt);
HashIDPreimage hashPreimage = makeFullContractIdPreimage(
test.getApp().getNetworkID(), contractPreimage);
SCAddress contractId = makeContractAddress(xdrSha256(hashPreimage));
auto createResources = SorobanResources();
createResources.instructions = 5'000'000;
createResources.readBytes =
static_cast<uint32_t>(wasm.data.size() + 1000);
createResources.writeBytes = 1000;
auto createContractTx =
makeSorobanCreateContractTx(test.getApp(), B, contractPreimage,
executable, createResources, 1000);

// Transaction 3: invocation (with inadequate instructions to succeed)
TestContract contract(test, contractId,
{contractCodeKey(contractHash),
makeContractInstanceKey(contractId)});
auto invokeTx = makeAddInvocation(contract, 700'000, C);

// Run single ledger with all 3 txs. First 2 should pass, 3rd should
// fail.
auto txResults = closeLedger(
*app, {uploadTx, createContractTx, invokeTx}, /*strictOrder=*/true);

REQUIRE(txResults.results.size() == 3);
REQUIRE(
isSuccessResult(txResults.results[0].result)); // Upload succeeds
REQUIRE(
isSuccessResult(txResults.results[1].result)); // Create succeeds
REQUIRE(!isSuccessResult(
txResults.results[2].result)); // Invoke fails at 700k

// But if we try again in next ledger with same instructions, it works
// because module is now cached
auto invokeTx2 = makeAddInvocation(contract, 700'000, C);
txResults = closeLedger(*app, {invokeTx2});
REQUIRE(isSuccessResult(txResults.results[0].result));
}
}

0 comments on commit 7927396

Please sign in to comment.