mirror of
https://github.com/Xahau/xahaud.git
synced 2026-08-22 16:00:57 +00:00
test(export): cover sidecar rejection preflights
This commit is contained in:
@@ -20,7 +20,8 @@ defaults:
|
||||
NetworkOPs: info
|
||||
env:
|
||||
XAHAU_RESOURCE_PER_PORT: "1"
|
||||
XAHAUD_RUNTIME_TEST_CONFIG: '{"set":{"global":{"rng_poll_ms":333}}}'
|
||||
rc:
|
||||
- rng_poll_ms=333
|
||||
|
||||
tests:
|
||||
# --- CE + Export (80% quorum, SHAMap convergence) ---
|
||||
@@ -33,11 +34,10 @@ tests:
|
||||
- name: export_degradation_ce
|
||||
script: .testnet/scenarios/export/export_degradation.py
|
||||
network:
|
||||
node_env:
|
||||
3:
|
||||
XAHAUD_RUNTIME_TEST_CONFIG: '{"set":{"global":{"rng_poll_ms":333,"no_export_sig":true}}}'
|
||||
4:
|
||||
XAHAUD_RUNTIME_TEST_CONFIG: '{"set":{"global":{"rng_poll_ms":333,"no_export_sig":true}}}'
|
||||
rc:
|
||||
- rng_poll_ms=333
|
||||
- n3:no_export_sig=true
|
||||
- n4:no_export_sig=true
|
||||
|
||||
- name: export_without_unl_report
|
||||
script: .testnet/scenarios/export/export_without_unl_report.py
|
||||
@@ -51,9 +51,9 @@ tests:
|
||||
- name: export_no_veto_missing_observation
|
||||
script: .testnet/scenarios/export/export_no_veto_missing_observation.py
|
||||
network:
|
||||
node_env:
|
||||
4:
|
||||
XAHAUD_RUNTIME_TEST_CONFIG: '{"set":{"global":{"rng_poll_ms":333,"no_export_sig_hash":true}}}'
|
||||
rc:
|
||||
- rng_poll_ms=333
|
||||
- n4:no_export_sig_hash=true
|
||||
|
||||
# CE + Export: 1 node suppressed, 4/5 = 80% quorum, should succeed
|
||||
- name: export_ce_one_node_down
|
||||
@@ -61,9 +61,9 @@ tests:
|
||||
params:
|
||||
expect_success: true
|
||||
network:
|
||||
node_env:
|
||||
4:
|
||||
XAHAUD_RUNTIME_TEST_CONFIG: '{"set":{"global":{"rng_poll_ms":333,"no_export_sig":true}}}'
|
||||
rc:
|
||||
- rng_poll_ms=333
|
||||
- n4:no_export_sig=true
|
||||
|
||||
# --- Export only, no CE (80% active-view quorum) ---
|
||||
- name: export_only_all_up
|
||||
@@ -85,9 +85,9 @@ tests:
|
||||
- Export
|
||||
track_features:
|
||||
- Export
|
||||
node_env:
|
||||
4:
|
||||
XAHAUD_RUNTIME_TEST_CONFIG: '{"set":{"global":{"rng_poll_ms":333,"no_export_sig":true}}}'
|
||||
rc:
|
||||
- rng_poll_ms=333
|
||||
- n4:no_export_sig=true
|
||||
|
||||
- name: export_only_two_nodes_down
|
||||
script: .testnet/scenarios/export/export_quorum.py
|
||||
@@ -98,8 +98,7 @@ tests:
|
||||
- Export
|
||||
track_features:
|
||||
- Export
|
||||
node_env:
|
||||
3:
|
||||
XAHAUD_RUNTIME_TEST_CONFIG: '{"set":{"global":{"rng_poll_ms":333,"no_export_sig":true}}}'
|
||||
4:
|
||||
XAHAUD_RUNTIME_TEST_CONFIG: '{"set":{"global":{"rng_poll_ms":333,"no_export_sig":true}}}'
|
||||
rc:
|
||||
- rng_poll_ms=333
|
||||
- n3:no_export_sig=true
|
||||
- n4:no_export_sig=true
|
||||
|
||||
@@ -2,17 +2,52 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from xahaud_scripts.testnet.config import feature_name_to_hash
|
||||
from xahaud_scripts.testnet.config import _unl_report_index, feature_name_to_hash
|
||||
|
||||
|
||||
async def require_export(ctx, log):
|
||||
"""Wait for first ledger and assert Export is enabled."""
|
||||
async def require_export(
|
||||
ctx, log, *, require_unl_report=True, require_runtime_config=True
|
||||
):
|
||||
"""Wait for first ledger and assert Export is enabled.
|
||||
|
||||
Network-mode Export success requires a parent-ledger UNLReport-backed
|
||||
active validator view. Most export scenarios seed that report in genesis;
|
||||
assert it here so a success-path test cannot accidentally pass setup
|
||||
without the condition Export::doApply requires. The no-UNLReport retry
|
||||
scenario opts out deliberately.
|
||||
|
||||
The tracked export suite also uses XAHAUD_RUNTIME_TEST_CONFIG for polling
|
||||
and fault-injection knobs. Default binaries reject the runtime_config RPC,
|
||||
so check it up front rather than silently running without those knobs.
|
||||
"""
|
||||
await ctx.wait_for_ledger_close(timeout=120)
|
||||
|
||||
if require_runtime_config:
|
||||
result = ctx.rpc.runtime_config(0)
|
||||
if not result or result.get("error"):
|
||||
raise AssertionError(
|
||||
"Export suite requires a binary built with "
|
||||
"xahaud_runtime_test_config=ON; runtime_config RPC returned "
|
||||
f"{result}"
|
||||
)
|
||||
log("RuntimeConfig RPC active")
|
||||
|
||||
feature = ctx.feature_check(feature_name_to_hash("Export"), node_id=0)
|
||||
if not feature or not feature.get("enabled", False):
|
||||
raise AssertionError(f"Export not enabled: {feature}")
|
||||
log("Export enabled")
|
||||
|
||||
if require_unl_report:
|
||||
result = ctx.rpc.ledger_entry(0, _unl_report_index())
|
||||
node = (result or {}).get("node", {})
|
||||
active = node.get("ActiveValidators", [])
|
||||
if node.get("LedgerEntryType") != "UNLReport" or not active:
|
||||
raise AssertionError(
|
||||
"Export success scenario requires a ledger UNLReport with "
|
||||
f"ActiveValidators, got: {result}"
|
||||
)
|
||||
log(f"UNLReport active validators: {len(active)}")
|
||||
|
||||
|
||||
def find_export_txns(ctx, seq):
|
||||
"""Find Export transactions in a ledger.
|
||||
|
||||
@@ -12,7 +12,7 @@ from export_helpers import require_export, assert_shadow_ticket
|
||||
|
||||
|
||||
async def scenario(ctx, log):
|
||||
await require_export(ctx, log)
|
||||
await require_export(ctx, log, require_unl_report=False)
|
||||
|
||||
await ctx.fund_accounts({"alice": 10000, "bob": 1000})
|
||||
log("Accounts funded")
|
||||
|
||||
@@ -18,7 +18,8 @@ defaults:
|
||||
NetworkOPs: info
|
||||
env:
|
||||
XAHAU_RESOURCE_PER_PORT: "1"
|
||||
XAHAUD_RUNTIME_TEST_CONFIG: '{"set":{"global":{"rng_poll_ms":333}}}'
|
||||
rc:
|
||||
- rng_poll_ms=333
|
||||
|
||||
tests:
|
||||
- name: steady_state_entropy
|
||||
|
||||
@@ -205,6 +205,21 @@ makeRawSidecarSet(Application& app, std::string const& raw)
|
||||
return map->snapShot(false);
|
||||
}
|
||||
|
||||
STObject
|
||||
makeExportSigSidecar(
|
||||
uint256 const& txHash,
|
||||
PublicKey const& publicKey,
|
||||
Slice signature)
|
||||
{
|
||||
STObject sidecar(sfGeneric);
|
||||
sidecar.setFieldU8(sfSidecarType, sidecarExportSig);
|
||||
sidecar.setFieldH256(sfTransactionHash, txHash);
|
||||
sidecar.setFieldVL(sfSigningPubKey, publicKey.slice());
|
||||
if (!signature.empty())
|
||||
sidecar.setFieldVL(sfTxnSignature, signature);
|
||||
return sidecar;
|
||||
}
|
||||
|
||||
void
|
||||
publishAndFetchSidecarSet(
|
||||
Application& app,
|
||||
@@ -1732,6 +1747,107 @@ class ConsensusExtensions_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(fetched.buildExportSigSet(seq) == exportSigSetHash);
|
||||
}
|
||||
|
||||
void
|
||||
testExportSidecarRejectsInvalidFetchedEntries()
|
||||
{
|
||||
testcase("Export sidecar rejects invalid fetched entries");
|
||||
|
||||
using namespace jtx;
|
||||
Env env{
|
||||
*this, envconfig(validator, ""), supported_amendments(), nullptr};
|
||||
auto const ledger = env.app().getLedgerMaster().getClosedLedger();
|
||||
auto const& valKeys = env.app().getValidatorKeys();
|
||||
BEAST_EXPECT(valKeys.keys);
|
||||
if (!valKeys.keys)
|
||||
return;
|
||||
|
||||
auto const& valPK = valKeys.keys->publicKey;
|
||||
auto const& valSK = valKeys.keys->secretKey;
|
||||
auto const signerAccount = calcAccountID(valPK);
|
||||
auto const dst = calcAccountID(randomKeyPair(KeyType::secp256k1).first);
|
||||
auto const innerObj = makeExportedPayment(signerAccount, dst);
|
||||
auto const innerTx = makeSTTx(innerObj);
|
||||
auto const exportTx = makeExportTx(innerObj, signerAccount);
|
||||
auto const txHash = exportTx->getTransactionID();
|
||||
auto const txSet = makeRCLTxSet(env.app(), {exportTx});
|
||||
|
||||
auto const sigData = buildMultiSigningData(innerTx, signerAccount);
|
||||
auto const sig = sign(valPK, valSK, sigData.slice());
|
||||
Buffer const validSig(sig.data(), sig.size());
|
||||
std::uint8_t const invalidBytes[] = {0x30, 0x03, 0x01, 0x02, 0x03};
|
||||
Buffer const invalidSig{invalidBytes, sizeof(invalidBytes)};
|
||||
|
||||
auto expectRejected = [&](ConsensusExtensions& ce,
|
||||
std::shared_ptr<SHAMap> const& map,
|
||||
uint256 const& expectedTxHash,
|
||||
PublicKey const& expectedSigner) {
|
||||
publishAndFetchSidecarSet(
|
||||
env.app(),
|
||||
ce,
|
||||
map,
|
||||
ConsensusExtensions::SidecarKind::exportSig);
|
||||
BEAST_EXPECT(!ce.exportSigCollector().hasVerifiedSignature(
|
||||
expectedTxHash, expectedSigner));
|
||||
BEAST_EXPECT(
|
||||
ce.exportSigCollector().signatureCount(expectedTxHash) == 0);
|
||||
};
|
||||
|
||||
{
|
||||
auto const [inactivePK, _] = randomKeyPair(KeyType::secp256k1);
|
||||
ConsensusExtensions ce{env.app(), activeNoopJournal()};
|
||||
ce.setExportEnabledThisRound(true);
|
||||
ce.cacheUNLReport(ledger);
|
||||
ce.cacheConsensusTxSet(txSet);
|
||||
|
||||
expectRejected(
|
||||
ce,
|
||||
makeSidecarSet(
|
||||
env.app(),
|
||||
{makeExportSigSidecar(
|
||||
txHash,
|
||||
inactivePK,
|
||||
Slice(validSig.data(), validSig.size()))}),
|
||||
txHash,
|
||||
inactivePK);
|
||||
}
|
||||
|
||||
{
|
||||
ConsensusExtensions ce{env.app(), activeNoopJournal()};
|
||||
ce.setExportEnabledThisRound(true);
|
||||
ce.cacheUNLReport(ledger);
|
||||
ce.cacheConsensusTxSet(makeRCLTxSet(env.app(), {}));
|
||||
|
||||
expectRejected(
|
||||
ce,
|
||||
makeSidecarSet(
|
||||
env.app(),
|
||||
{makeExportSigSidecar(
|
||||
txHash,
|
||||
valPK,
|
||||
Slice(validSig.data(), validSig.size()))}),
|
||||
txHash,
|
||||
valPK);
|
||||
}
|
||||
|
||||
{
|
||||
ConsensusExtensions ce{env.app(), activeNoopJournal()};
|
||||
ce.setExportEnabledThisRound(true);
|
||||
ce.cacheUNLReport(ledger);
|
||||
ce.cacheConsensusTxSet(txSet);
|
||||
|
||||
expectRejected(
|
||||
ce,
|
||||
makeSidecarSet(
|
||||
env.app(),
|
||||
{makeExportSigSidecar(
|
||||
txHash,
|
||||
valPK,
|
||||
Slice(invalidSig.data(), invalidSig.size()))}),
|
||||
txHash,
|
||||
valPK);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testExportAgreedSignaturesIgnoreLiveCollectorMutation()
|
||||
{
|
||||
@@ -3185,6 +3301,7 @@ public:
|
||||
testProposalProofRoundTrip();
|
||||
testHarvestRngDataReplacementAndRejection();
|
||||
testExportSidecarBuildFetchAndMerge();
|
||||
testExportSidecarRejectsInvalidFetchedEntries();
|
||||
testExportAgreedSignaturesIgnoreLiveCollectorMutation();
|
||||
testOnPreBuildPreservesExportDecision();
|
||||
testRngSidecarBuildFetchAndMerge();
|
||||
|
||||
Reference in New Issue
Block a user