#include "daemon/daemon_controller.h" #include "data/transaction_history_cache.h" #include "daemon/lifecycle_adapters.h" #include "data/wallet_state.h" #include "rpc/connection.h" #include "resources/embedded_resources.h" #include "services/network_refresh_service.h" #include "services/refresh_scheduler.h" #include "services/wallet_security_controller.h" #include "services/wallet_security_workflow.h" #include "services/wallet_security_workflow_executor.h" #include "ui/explorer/explorer_block_cache.h" #include "ui/windows/balance_address_list.h" #include "ui/windows/balance_recent_tx.h" #include "ui/windows/console_input_model.h" #include "ui/windows/console_output_model.h" #include "ui/windows/console_tab_helpers.h" #include "ui/windows/mining_benchmark.h" #include "ui/windows/mining_pool_panel.h" #include "ui/windows/mining_tab_helpers.h" #include "util/amount_format.h" #include "util/payment_uri.h" #include "wallet/lite_backend_artifact_contract.h" #include "wallet/lite_bridge_runtime.h" #include "wallet/lite_wallet_controller.h" #include "wallet/lite_wallet_gateway.h" #include "wallet/lite_wallet_state_mapper.h" #include "config/settings.h" #include "data/wallet_state.h" #include "fake_lite_backend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; namespace { int g_failures = 0; void expectTrue(bool value, const char* expr, const char* file, int line) { if (value) return; std::cerr << file << ":" << line << " expected true: " << expr << "\n"; ++g_failures; } template void expectEqual(const T& actual, const U& expected, const char* actualExpr, const char* expectedExpr, const char* file, int line) { if (actual == expected) return; std::cerr << file << ":" << line << " expected " << actualExpr << " == " << expectedExpr << "\n"; ++g_failures; } void expectNear(double actual, double expected, double epsilon, const char* file, int line) { if (std::abs(actual - expected) <= epsilon) return; std::cerr << file << ":" << line << " expected near " << expected << " (actual=" << actual << ")\n"; ++g_failures; } #define EXPECT_TRUE(expr) expectTrue((expr), #expr, __FILE__, __LINE__) #define EXPECT_FALSE(expr) expectTrue(!(expr), "!(" #expr ")", __FILE__, __LINE__) #define EXPECT_EQ(actual, expected) expectEqual((actual), (expected), #actual, #expected, __FILE__, __LINE__) #define EXPECT_NEAR(actual, expected, epsilon) expectNear((actual), (expected), (epsilon), __FILE__, __LINE__) fs::path makeTempDir() { auto now = std::chrono::steady_clock::now().time_since_epoch().count(); fs::path dir = fs::temp_directory_path() / ("obsidian_phase4_tests_" + std::to_string(now)); fs::create_directories(dir); return dir; } void writeTestFile(const fs::path& path, const std::string& content) { std::ofstream output(path, std::ios::binary); output << content; } using LiteBackendArtifactContractInput = dragonx::wallet::LiteBackendArtifactContractInput; using LiteBackendArtifactContractIssue = dragonx::wallet::LiteBackendArtifactContractIssue; using LiteBackendArtifactContractLinkMode = dragonx::wallet::LiteBackendArtifactContractLinkMode; using LiteBackendArtifactContractResult = dragonx::wallet::LiteBackendArtifactContractResult; using LiteBackendArtifactContractStatus = dragonx::wallet::LiteBackendArtifactContractStatus; using LiteBridgeRuntime = dragonx::wallet::LiteBridgeRuntime; using LiteBridgeRuntimeBindingInput = dragonx::wallet::LiteBridgeRuntimeBindingInput; using LiteBridgeRuntimeBindingResult = dragonx::wallet::LiteBridgeRuntimeBindingResult; using LiteBridgeRuntimeFakeDynamicLoaderInput = dragonx::wallet::LiteBridgeRuntimeFakeDynamicLoaderInput; using LiteBridgeRuntimeFakeDynamicLoaderResult = dragonx::wallet::LiteBridgeRuntimeFakeDynamicLoaderResult; using LiteBridgeRuntimeIssue = dragonx::wallet::LiteBridgeRuntimeIssue; using LiteBridgeRuntimeLinkMode = dragonx::wallet::LiteBridgeRuntimeLinkMode; using LiteBridgeRuntimeDynamicLoaderSmokeGateInput = dragonx::wallet::LiteBridgeRuntimeDynamicLoaderSmokeGateInput; using LiteBridgeRuntimeDynamicLoaderSmokeGateResult = dragonx::wallet::LiteBridgeRuntimeDynamicLoaderSmokeGateResult; using LiteBridgeRuntimePlatformLoaderReviewInput = dragonx::wallet::LiteBridgeRuntimePlatformLoaderReviewInput; using LiteBridgeRuntimePlatformLoaderReviewResult = dragonx::wallet::LiteBridgeRuntimePlatformLoaderReviewResult; using LiteBridgeRuntimePlatformDynamicLoaderAdapterContractInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderAdapterContractInput; using LiteBridgeRuntimePlatformDynamicLoaderAdapterContractResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderAdapterContractResult; using LiteBridgeRuntimePlatformDynamicLoaderAdapter = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderAdapter; using LiteBridgeRuntimePlatformDynamicLoaderAdapterCallResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderAdapterCallResult; using LiteBridgeRuntimePlatformDynamicLoaderNoOpAdapterSeamInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderNoOpAdapterSeamInput; using LiteBridgeRuntimePlatformDynamicLoaderNoOpAdapterSeamResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderNoOpAdapterSeamResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterImplementationReviewInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterImplementationReviewInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterImplementationReviewResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterImplementationReviewResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterDisabledScaffoldInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterDisabledScaffoldInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterDisabledScaffoldResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterDisabledScaffoldResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionPreflightInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionPreflightInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionPreflightResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionPreflightResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionApprovalGateInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionApprovalGateInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionApprovalGateResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionApprovalGateResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledOwnerHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledOwnerHandoffInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledOwnerHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledOwnerHandoffResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackBindingInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackBindingInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackBindingResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackBindingResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackPreInvocationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackPreInvocationGuardInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackPreInvocationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackPreInvocationGuardResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultPropagationInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultPropagationInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultPropagationResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultPropagationResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; using LiteBridgeRuntimeBatch46ResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; using LiteBridgeRuntimeBatch46ResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; using LiteBridgeRuntimeBatch47ReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; using LiteBridgeRuntimeBatch47ReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; using LiteBridgeRuntimeBatch48StatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; using LiteBridgeRuntimeBatch48StatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; using LiteBridgeRuntimeBatch49PublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; using LiteBridgeRuntimeBatch49PublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; using LiteBridgeRuntimeBatch50ResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; using LiteBridgeRuntimeBatch50ResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; using LiteBridgeRuntimeBatch51ReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; using LiteBridgeRuntimeBatch51ReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; using LiteBridgeRuntimeBatch52StatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; using LiteBridgeRuntimeBatch52StatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; using LiteBridgeRuntimeBatch53PublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; using LiteBridgeRuntimeBatch53PublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; using LiteBridgeRuntimeBatch54ResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; using LiteBridgeRuntimeBatch54ResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; using LiteBridgeRuntimeBatch55ReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; using LiteBridgeRuntimeBatch55ReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; using LiteBridgeRuntimeBatch56StatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; using LiteBridgeRuntimeBatch56StatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; using LiteBridgeRuntimeBatch57PublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; using LiteBridgeRuntimeBatch57PublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; using LiteBridgeRuntimeBatch58ResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; using LiteBridgeRuntimeBatch58ResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; using LiteBridgeRuntimeBatch59ReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; using LiteBridgeRuntimeBatch59ReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; using LiteBridgeRuntimeBatch60StatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; using LiteBridgeRuntimeBatch60StatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; static_assert(!std::is_same_v); static_assert(!std::is_same_v); static_assert(!std::is_same_v); static_assert(!std::is_same_v); static_assert(!std::is_same_v); static_assert(!std::is_same_v); using LiteBridgeRuntimeSharedArtifactSmokeEvidenceAcquisitionInput = dragonx::wallet::LiteBridgeRuntimeSharedArtifactSmokeEvidenceAcquisitionInput; using LiteBridgeRuntimeSharedArtifactSmokeEvidenceAcquisitionResult = dragonx::wallet::LiteBridgeRuntimeSharedArtifactSmokeEvidenceAcquisitionResult; using LiteBridgeRuntimeStatus = dragonx::wallet::LiteBridgeRuntimeStatus; template std::unique_ptr heapConstructPlanResult(Factory&& factory) { void* storage = ::operator new(sizeof(T)); try { return std::unique_ptr(::new (storage) T(factory())); } catch (...) { ::operator delete(storage); throw; } } dragonx::wallet::LiteBackendArtifactProvenanceMetadata makeReadyLiteBackendArtifactProvenance() { dragonx::wallet::LiteBackendArtifactProvenanceMetadata provenance; provenance.ownerReady = true; provenance.metadataProvided = true; provenance.source = "test-fixture"; provenance.builder = "phase4-tests"; provenance.sourceRevision = "test-revision"; provenance.artifactSetId = "phase1-contract"; provenance.redacted = true; return provenance; } bool liteBackendArtifactContractHasIssue( const LiteBackendArtifactContractResult& result, LiteBackendArtifactContractIssue issue) { for (const auto& issueInfo : result.issues) { if (issueInfo.issue == issue) return true; } return false; } int g_liteBridgeRuntimeFakeCallCount = 0; int g_liteBridgeRuntimeTrackedFreeCount = 0; int g_liteBridgeRuntimeTrackedShutdownCount = 0; int g_liteBridgeRuntimeTrackedUnloadCount = 0; std::vector g_liteBridgeRuntimeTrackedFreedValues; std::vector g_liteBridgeRuntimeTrackedUnloadedHandles; std::vector g_liteBridgeRuntimeTeardownEvents; char* makeLiteBridgeRuntimeOwnedCString(const std::string& value) { char* rawValue = new char[value.size() + 1]; for (std::size_t index = 0; index < value.size(); ++index) { rawValue[index] = value[index]; } rawValue[value.size()] = '\0'; return rawValue; } void resetLiteBridgeRuntimeTrackedFree() { g_liteBridgeRuntimeTrackedFreeCount = 0; g_liteBridgeRuntimeTrackedShutdownCount = 0; g_liteBridgeRuntimeTrackedUnloadCount = 0; g_liteBridgeRuntimeTrackedFreedValues.clear(); g_liteBridgeRuntimeTrackedUnloadedHandles.clear(); g_liteBridgeRuntimeTeardownEvents.clear(); } void fakeLiteBridgeRuntimeTrackedFreeString(char* value) { ++g_liteBridgeRuntimeTrackedFreeCount; if (value) { g_liteBridgeRuntimeTrackedFreedValues.push_back(value); g_liteBridgeRuntimeTeardownEvents.push_back("free:" + std::string(value)); } delete[] value; } void fakeLiteBridgeRuntimeTrackedShutdown() { ++g_liteBridgeRuntimeTrackedShutdownCount; g_liteBridgeRuntimeTeardownEvents.push_back("shutdown"); } void fakeLiteBridgeRuntimeTrackedUnload(const char* handleLabel) { ++g_liteBridgeRuntimeTrackedUnloadCount; const std::string label = handleLabel ? handleLabel : ""; g_liteBridgeRuntimeTrackedUnloadedHandles.push_back(label); g_liteBridgeRuntimeTeardownEvents.push_back("unload:" + label); } bool fakeLiteBridgeRuntimeWalletExists(const char*) { ++g_liteBridgeRuntimeFakeCallCount; return true; } char* fakeLiteBridgeRuntimeInitializeNew(bool, const char*) { ++g_liteBridgeRuntimeFakeCallCount; return nullptr; } char* fakeLiteBridgeRuntimeOwnedInitializeNew(bool, const char*) { ++g_liteBridgeRuntimeFakeCallCount; return makeLiteBridgeRuntimeOwnedCString("initialize-new-ok"); } char* fakeLiteBridgeRuntimeInitializeNewFromPhrase(bool, const char*, const char*, unsigned long long, unsigned long long, bool) { ++g_liteBridgeRuntimeFakeCallCount; return nullptr; } char* fakeLiteBridgeRuntimeOwnedInitializeNewFromPhrase(bool, const char*, const char*, unsigned long long, unsigned long long, bool) { ++g_liteBridgeRuntimeFakeCallCount; return makeLiteBridgeRuntimeOwnedCString("initialize-phrase-ok"); } char* fakeLiteBridgeRuntimeInitializeExisting(bool, const char*) { ++g_liteBridgeRuntimeFakeCallCount; return nullptr; } char* fakeLiteBridgeRuntimeOwnedInitializeExisting(bool, const char*) { ++g_liteBridgeRuntimeFakeCallCount; return makeLiteBridgeRuntimeOwnedCString("initialize-existing-ok"); } char* fakeLiteBridgeRuntimeExecute(const char*, const char*) { ++g_liteBridgeRuntimeFakeCallCount; return nullptr; } char* fakeLiteBridgeRuntimeOwnedExecute(const char*, const char*) { ++g_liteBridgeRuntimeFakeCallCount; return makeLiteBridgeRuntimeOwnedCString("bridge-runtime-ok"); } void fakeLiteBridgeRuntimeFreeString(char*) { ++g_liteBridgeRuntimeFakeCallCount; } bool fakeLiteBridgeRuntimeCheckServerOnline(const char*) { ++g_liteBridgeRuntimeFakeCallCount; return true; } void fakeLiteBridgeRuntimeShutdown() { ++g_liteBridgeRuntimeFakeCallCount; } dragonx::wallet::LiteClientBridgeApi makeCompleteFakeLiteBridgeRuntimeApi() { return dragonx::wallet::LiteClientBridgeApi{ &fakeLiteBridgeRuntimeWalletExists, &fakeLiteBridgeRuntimeInitializeNew, &fakeLiteBridgeRuntimeInitializeNewFromPhrase, &fakeLiteBridgeRuntimeInitializeExisting, &fakeLiteBridgeRuntimeExecute, &fakeLiteBridgeRuntimeFreeString, &fakeLiteBridgeRuntimeCheckServerOnline, &fakeLiteBridgeRuntimeShutdown, }; } void clearLiteBridgeRuntimeSymbol(dragonx::wallet::LiteClientBridgeApi& bridgeApi, const std::string& logicalName) { if (logicalName == "walletExists") bridgeApi.walletExists = nullptr; if (logicalName == "initializeNew") bridgeApi.initializeNew = nullptr; if (logicalName == "initializeNewFromPhrase") bridgeApi.initializeNewFromPhrase = nullptr; if (logicalName == "initializeExisting") bridgeApi.initializeExisting = nullptr; if (logicalName == "execute") bridgeApi.execute = nullptr; if (logicalName == "freeString") bridgeApi.freeString = nullptr; if (logicalName == "checkServerOnline") bridgeApi.checkServerOnline = nullptr; if (logicalName == "shutdown") bridgeApi.shutdown = nullptr; } LiteBridgeRuntimeFakeDynamicLoaderInput makeReadyFakeLiteBridgeRuntimeDynamicLoaderInput( const std::string& source = "fake-dynamic-library", const std::string& handleLabel = "phase6-handle") { LiteBridgeRuntimeFakeDynamicLoaderInput input; input.artifactPathReviewed = true; input.platformLoaderStrategyReady = true; input.loadSequenceReady = true; input.unloadSequenceReady = true; input.handleStoreReady = true; input.symbolLookupReady = true; input.fakeHandleProvided = true; input.artifactPath = "/tmp/obsidian-dragon-phase6/" + handleLabel + ".so"; input.displayPath = handleLabel + ".so"; input.platform = "fake-linux"; input.handleLabel = handleLabel; input.source = source; input.api = makeCompleteFakeLiteBridgeRuntimeApi(); input.api.initializeNew = &fakeLiteBridgeRuntimeOwnedInitializeNew; input.api.initializeNewFromPhrase = &fakeLiteBridgeRuntimeOwnedInitializeNewFromPhrase; input.api.initializeExisting = &fakeLiteBridgeRuntimeOwnedInitializeExisting; input.api.execute = &fakeLiteBridgeRuntimeOwnedExecute; input.api.freeString = &fakeLiteBridgeRuntimeTrackedFreeString; input.api.shutdown = &fakeLiteBridgeRuntimeTrackedShutdown; input.unload = &fakeLiteBridgeRuntimeTrackedUnload; return input; } LiteBridgeRuntimePlatformDynamicLoaderAdapterCallResult fakeLiteBridgeRuntimeNoOpAdapterBadSymbolLookup(const char*, const char*) { LiteBridgeRuntimePlatformDynamicLoaderAdapterCallResult result; result.ok = true; result.disabled = false; result.noPlatformSymbolResolution = false; result.symbolAddressProduced = true; result.summary = "bad symbol lookup produced an address"; return result; } LiteBridgeRuntimePlatformDynamicLoaderAdapterCallResult fakeLiteBridgeRuntimeRealAdapterBadLoad(const char*, const char*) { LiteBridgeRuntimePlatformDynamicLoaderAdapterCallResult result; result.ok = true; result.disabled = false; result.noPlatformDynamicLibraryLoaded = false; result.handleProduced = true; result.summary = "bad real adapter scaffold load produced a handle"; return result; } bool liteBridgeRuntimeHasIssue( const LiteBridgeRuntimeBindingResult& result, LiteBridgeRuntimeIssue issue) { for (const auto& issueInfo : result.issues) { if (issueInfo.issue == issue) return true; } return false; } class MockWalletSecurityRpc : public dragonx::services::WalletSecurityController::RpcGateway { public: bool encryptResult = true; std::string encryptError; int encryptCalls = 0; std::string lastPassphrase; bool encryptWallet(const std::string& passphrase, std::string& error) override { ++encryptCalls; lastPassphrase = passphrase; error = encryptError; return encryptResult; } bool unlockWallet(const std::string&, int, std::string&) override { return true; } bool exportWallet(const std::string&, long, std::string&) override { return true; } bool importWallet(const std::string&, long, std::string&) override { return true; } }; class MockDaemonLifecycleRuntime : public dragonx::daemon::DaemonController::LifecycleRuntime { public: std::vector calls; int deletedItems = 7; bool startResult = true; std::string rpcStopContext; std::string disconnectReason; void stopDaemonWithPolicy() override { calls.push_back("stop"); } bool startDaemon() override { calls.push_back("start"); return startResult; } int deleteBlockchainData() override { calls.push_back("delete"); return deletedItems; } void resetOutputOffset() override { calls.push_back("reset-output"); } void requestRpcStopAndDisconnect(const char* context, const char* reason) override { calls.push_back("rpc-stop-disconnect"); rpcStopContext = context ? context : ""; disconnectReason = reason ? reason : ""; } }; class MockDaemonLifecycleTask : public dragonx::daemon::DaemonController::LifecycleTaskContext { public: bool isCancelled = false; bool isShuttingDown = false; int cancelAfterSleeps = 0; int sleeps = 0; int sleptMs = 0; bool cancelled() const override { return isCancelled; } bool shuttingDown() const override { return isShuttingDown; } void sleepForMs(int milliseconds) override { ++sleeps; sleptMs += milliseconds; if (cancelAfterSleeps > 0 && sleeps >= cancelAfterSleeps) { isCancelled = true; } } }; class MockWalletSecurityVault : public dragonx::services::WalletSecurityController::VaultGateway { public: bool storeResult = true; int storeCalls = 0; std::string lastPin; std::string lastPassphrase; bool storePin(const std::string& pin, const std::string& passphrase) override { ++storeCalls; lastPin = pin; lastPassphrase = passphrase; return storeResult; } }; class MockWorkflowRpc : public dragonx::services::WalletSecurityWorkflowExecutor::RpcGateway { public: bool unlockResult = true; bool exportResult = true; bool stopResult = true; int probeSuccessOnCall = 1; std::string unlockError; std::string exportError; std::string stopError; std::string probeError; int unlockCalls = 0; int exportCalls = 0; int stopCalls = 0; int probeCalls = 0; std::string lastPassphrase; std::string lastExportFile; bool unlockWallet(const std::string& passphrase, int, std::string& error) override { ++unlockCalls; lastPassphrase = passphrase; error = unlockError; return unlockResult; } bool exportWallet(const std::string& fileName, long, std::string& error) override { ++exportCalls; lastExportFile = fileName; error = exportError; return exportResult; } bool requestDaemonStop(std::string& error) override { ++stopCalls; error = stopError; return stopResult; } bool probeDaemon(std::string& error) override { ++probeCalls; error = probeError; return probeCalls >= probeSuccessOnCall; } }; class MockWorkflowFiles : public dragonx::services::WalletSecurityWorkflowExecutor::FileGateway { public: std::string dir = "/tmp/dragonx/"; bool backupResult = true; std::string backupError; int backupCalls = 0; std::string dataDir() override { return dir; } bool backupEncryptedWallet(const dragonx::services::WalletSecurityWorkflowExecutor::WalletFilePlan&, std::string& error) override { ++backupCalls; error = backupError; return backupResult; } }; class MockWorkflowDaemon : public dragonx::services::WalletSecurityWorkflowExecutor::DaemonGateway { public: bool usingEmbedded = true; bool isCancelled = false; bool isShuttingDown = false; int cancelAfterSleeps = 0; int sleepCalls = 0; int sleptMs = 0; int stopCalls = 0; int startCalls = 0; bool isUsingEmbeddedDaemon() const override { return usingEmbedded; } void stopEmbeddedDaemon() override { ++stopCalls; } bool startEmbeddedDaemon() override { ++startCalls; return true; } bool cancelled() const override { return isCancelled; } bool shuttingDown() const override { return isShuttingDown; } void sleepForMs(int milliseconds) override { ++sleepCalls; sleptMs += milliseconds; if (cancelAfterSleeps > 0 && sleepCalls >= cancelAfterSleeps) { isCancelled = true; } } }; class MockWorkflowImporter : public dragonx::services::WalletSecurityWorkflowExecutor::ImportGateway { public: bool result = true; std::string importError; int importCalls = 0; std::string lastExportPath; bool importWallet(const std::string& exportPath, long, std::string& error) override { ++importCalls; lastExportPath = exportPath; error = importError; return result; } }; class FakeRefreshWorker { public: std::size_t reportedPending = 0; std::vector tasks; std::size_t pendingTaskCount() const { return reportedPending; } void post(dragonx::rpc::RPCWorker::WorkFn work) { tasks.push_back(std::move(work)); } dragonx::rpc::RPCWorker::MainCb runNext() { auto work = std::move(tasks.front()); tasks.erase(tasks.begin()); return work(); } dragonx::rpc::RPCWorker::MainCb runAt(std::size_t index) { auto work = std::move(tasks[index]); tasks.erase(tasks.begin() + static_cast(index)); return work(); } }; class MockRefreshRpc : public dragonx::services::NetworkRefreshService::RefreshRpcGateway { public: struct RecordedCall { std::string method; nlohmann::json params; }; struct Response { nlohmann::json value; bool throws = false; std::string error = "mock rpc failure"; }; std::vector calls; std::unordered_map> responses; void addResponse(const std::string& method, nlohmann::json value) { responses[method].push_back({std::move(value), false, {}}); } void addFailure(const std::string& method, std::string error = "mock rpc failure") { responses[method].push_back({nlohmann::json(), true, std::move(error)}); } nlohmann::json call(const std::string& method, const nlohmann::json& params) override { calls.push_back({method, params}); auto& queue = responses[method]; if (queue.empty()) { throw std::runtime_error("unexpected rpc call: " + method); } auto response = std::move(queue.front()); queue.pop_front(); if (response.throws) throw std::runtime_error(response.error); return response.value; } std::vector methodNames() const { std::vector names; names.reserve(calls.size()); for (const auto& call : calls) names.push_back(call.method); return names; } }; void testConnectionConfig() { using dragonx::rpc::AuthSource; using dragonx::rpc::Connection; fs::path dir = makeTempDir(); fs::path conf = dir / "DRAGONX.conf"; { std::ofstream out(conf); out << "rpcuser=alice\n" << "rpcpassword = secret \r\n" << "rpcconnect=rpc.example.test\n" << "rpcport=12345\n" << "rpctls=yes\n"; } auto config = Connection::parseConfFile(conf.string()); EXPECT_EQ(config.rpcuser, std::string("alice")); EXPECT_EQ(config.rpcpassword, std::string("secret")); EXPECT_EQ(config.host, std::string("rpc.example.test")); EXPECT_EQ(config.port, std::string("12345")); EXPECT_TRUE(config.use_tls); EXPECT_EQ(config.auth_source, AuthSource::ConfigFile); EXPECT_FALSE(Connection::usesPlaintextRemote(config)); fs::path tlsConf = dir / "tls.conf"; { std::ofstream out(tlsConf); out << "rpcuser=bob\n" << "rpcpassword=remote-secret\n" << "rpcconnect=198.51.100.10\n" << "rpcssl=1\n"; } auto tlsConfig = Connection::parseConfFile(tlsConf.string()); EXPECT_TRUE(tlsConfig.use_tls); EXPECT_FALSE(Connection::usesPlaintextRemote(tlsConfig)); config.use_tls = false; EXPECT_TRUE(Connection::usesPlaintextRemote(config)); config.host = "127.4.5.6"; EXPECT_FALSE(Connection::usesPlaintextRemote(config)); config.host = "[::1]"; EXPECT_FALSE(Connection::usesPlaintextRemote(config)); fs::path cookieDir = dir / "cookie"; fs::create_directories(cookieDir); { std::ofstream out(cookieDir / ".cookie"); out << "__cookie__:cookie-secret\r\n"; } dragonx::rpc::ConnectionConfig cookieBase; cookieBase.host = "rpc.example.test"; cookieBase.hush_dir = cookieDir.string(); cookieBase.auth_source = AuthSource::ConfigFile; cookieBase.use_tls = true; dragonx::rpc::ConnectionConfig cookieConfig; EXPECT_TRUE(Connection::buildCookieAuthConfig(cookieBase, cookieConfig)); EXPECT_EQ(cookieConfig.rpcuser, std::string("__cookie__")); EXPECT_EQ(cookieConfig.rpcpassword, std::string("cookie-secret")); EXPECT_EQ(cookieConfig.auth_source, AuthSource::Cookie); EXPECT_TRUE(cookieConfig.use_tls); fs::remove_all(dir); } void testPaymentUri() { std::string taddr = "R" + std::string(33, 'a'); auto parsed = dragonx::util::parsePaymentURI( "drgx:" + taddr + "?amount=1.25000000&label=Main+Wallet&memo=hello%20there&message=thanks"); EXPECT_TRUE(parsed.valid); EXPECT_EQ(parsed.address, taddr); EXPECT_NEAR(parsed.amount, 1.25, 0.00000001); EXPECT_EQ(parsed.label, std::string("Main Wallet")); EXPECT_EQ(parsed.memo, std::string("hello there")); EXPECT_EQ(parsed.message, std::string("thanks")); std::string zaddr = "zs" + std::string(76, 'b'); auto zparsed = dragonx::util::parsePaymentURI("hush://" + zaddr + "?amt=0.5"); EXPECT_TRUE(zparsed.valid); EXPECT_NEAR(zparsed.amount, 0.5, 0.00000001); auto invalid = dragonx::util::parsePaymentURI("drgx:" + taddr + "?amount=-1"); EXPECT_FALSE(invalid.valid); EXPECT_EQ(invalid.error, std::string("Invalid negative amount")); } void testAmountFormatting() { EXPECT_EQ(dragonx::util::formatAmountFixed(1.0), std::string("1.00000000")); EXPECT_EQ(dragonx::util::formatAmountFixed(0.1), std::string("0.10000000")); EXPECT_EQ(dragonx::util::formatAmountFixed(0.000000019), std::string("0.00000002")); EXPECT_EQ(dragonx::util::formatAmountFixed(12.3456, 2), std::string("12.35")); } void testSpendableFiltering() { std::vector addresses; addresses.push_back({"zs-view", 50.0, "shielded", false}); addresses.push_back({"zs-low", 2.0, "shielded", true}); addresses.push_back({"R-zero", 0.0, "transparent", true}); addresses.push_back({"R-high", 5.0, "transparent", true}); EXPECT_EQ(dragonx::bestSpendableAddressIndex(addresses), 3); auto indices = dragonx::sortedSpendableAddressIndices(addresses); EXPECT_EQ(indices.size(), static_cast(2)); EXPECT_EQ(indices[0], static_cast(3)); EXPECT_EQ(indices[1], static_cast(1)); auto includeZero = dragonx::sortedSpendableAddressIndices(addresses, false); EXPECT_EQ(includeZero.size(), static_cast(3)); } void testRefreshScheduler() { using dragonx::services::RefreshScheduler; using Timer = RefreshScheduler::Timer; RefreshScheduler scheduler; scheduler.applyPage(dragonx::ui::NavPage::Overview); EXPECT_NEAR(scheduler.intervals().core, 2.0, 0.0001); EXPECT_NEAR(scheduler.intervals().peers, 0.0, 0.0001); scheduler.tick(1.99f); EXPECT_FALSE(scheduler.isDue(Timer::Core)); scheduler.tick(0.02f); EXPECT_TRUE(scheduler.consumeDue(Timer::Core)); EXPECT_FALSE(scheduler.isDue(Timer::Core)); scheduler.markWalletMutationRefresh(); EXPECT_TRUE(scheduler.isDue(Timer::Core)); EXPECT_TRUE(scheduler.isDue(Timer::Transactions)); EXPECT_TRUE(scheduler.isDue(Timer::Addresses)); EXPECT_FALSE(scheduler.isDue(Timer::Peers)); scheduler.applyPage(dragonx::ui::NavPage::Peers); scheduler.markDue(Timer::Peers); EXPECT_TRUE(scheduler.consumeDue(Timer::Peers)); scheduler.applyPage(dragonx::ui::NavPage::Console); EXPECT_NEAR(scheduler.intervals().core, 10.0, 0.0001); EXPECT_NEAR(scheduler.intervals().transactions, 30.0, 0.0001); EXPECT_NEAR(scheduler.intervals().addresses, 30.0, 0.0001); EXPECT_NEAR(scheduler.intervals().peers, 0.0, 0.0001); EXPECT_FALSE(scheduler.isDue(Timer::Price)); scheduler.markDue(Timer::Price); EXPECT_TRUE(scheduler.consumeDue(Timer::Price)); EXPECT_FALSE(scheduler.shouldRefreshTransactions(100, 100, false)); EXPECT_TRUE(scheduler.shouldRefreshTransactions(-1, 100, false)); EXPECT_TRUE(scheduler.shouldRefreshTransactions(99, 100, false)); EXPECT_TRUE(scheduler.shouldRefreshTransactions(100, 100, true)); scheduler.tick(RefreshScheduler::kTxMaxAge); EXPECT_FALSE(scheduler.shouldRefreshTransactions(100, 100, false)); EXPECT_TRUE(scheduler.isDue(Timer::TxAge)); } void testNetworkRefreshService() { using dragonx::services::NetworkRefreshService; using Job = NetworkRefreshService::Job; using Timer = NetworkRefreshService::Timer; NetworkRefreshService service; service.applyPage(dragonx::ui::NavPage::Peers); service.markImmediateRefresh(); EXPECT_TRUE(service.consumeDue(Timer::Core)); EXPECT_TRUE(service.consumeDue(Timer::Transactions)); EXPECT_TRUE(service.consumeDue(Timer::Addresses)); EXPECT_TRUE(service.consumeDue(Timer::Peers)); EXPECT_TRUE(service.beginJob(Job::Core)); EXPECT_TRUE(service.jobInProgress(Job::Core)); EXPECT_FALSE(service.beginJob(Job::Core)); service.finishJob(Job::Core); EXPECT_FALSE(service.jobInProgress(Job::Core)); EXPECT_TRUE(service.beginJob(Job::Core)); service.resetJobs(); EXPECT_FALSE(service.jobInProgress(Job::Core)); EXPECT_TRUE(service.beginJob(Job::ConnectionInit)); EXPECT_TRUE(service.jobInProgress(Job::ConnectionInit)); service.finishJob(Job::ConnectionInit); EXPECT_FALSE(service.jobInProgress(Job::ConnectionInit)); auto cancelOld = service.beginDispatch(Job::Core); EXPECT_TRUE(cancelOld.accepted); service.resetJobs(); auto cancelNew = service.beginDispatch(Job::Core); EXPECT_TRUE(cancelNew.accepted); service.cancelDispatch(cancelOld); service.cancelDispatch(cancelNew); EXPECT_EQ(service.stats(Job::Core).staleCallbacks, static_cast(1)); auto ticket = service.beginDispatch(Job::Addresses, 1, 4); EXPECT_TRUE(ticket.accepted); auto busyTicket = service.beginDispatch(Job::Addresses, 1, 4); EXPECT_FALSE(busyTicket.accepted); auto stats = service.stats(Job::Addresses); EXPECT_EQ(stats.started, static_cast(1)); EXPECT_EQ(stats.skippedInFlight, static_cast(1)); EXPECT_EQ(stats.lastQueueDepth, static_cast(1)); EXPECT_TRUE(service.completeDispatch(ticket)); stats = service.stats(Job::Addresses); EXPECT_EQ(stats.finished, static_cast(1)); auto pressureTicket = service.beginDispatch(Job::Mining, 3, 3); EXPECT_FALSE(pressureTicket.accepted); stats = service.stats(Job::Mining); EXPECT_EQ(stats.skippedQueuePressure, static_cast(1)); EXPECT_EQ(stats.lastQueueDepth, static_cast(3)); auto staleTicket = service.beginDispatch(Job::Peers); EXPECT_TRUE(staleTicket.accepted); service.resetJobs(); auto newerTicket = service.beginDispatch(Job::Peers); EXPECT_TRUE(newerTicket.accepted); EXPECT_FALSE(service.completeDispatch(staleTicket)); service.cancelDispatch(newerTicket); stats = service.stats(Job::Peers); EXPECT_EQ(stats.started, static_cast(2)); EXPECT_EQ(stats.staleCallbacks, static_cast(1)); FakeRefreshWorker worker; int workerRuns = 0; int mainRuns = 0; auto enqueued = service.enqueue(Job::Price, worker, [&]() -> dragonx::rpc::RPCWorker::MainCb { ++workerRuns; return [&]() { ++mainRuns; }; }, 3); EXPECT_TRUE(enqueued.enqueued); EXPECT_TRUE(service.jobInProgress(Job::Price)); EXPECT_EQ(worker.tasks.size(), static_cast(1)); auto mainCallback = worker.runNext(); EXPECT_EQ(workerRuns, 1); mainCallback(); EXPECT_EQ(mainRuns, 1); EXPECT_FALSE(service.jobInProgress(Job::Price)); stats = service.stats(Job::Price); EXPECT_EQ(stats.finished, static_cast(1)); auto nullMainEnqueued = service.enqueue(Job::Price, worker, []() -> dragonx::rpc::RPCWorker::MainCb { return nullptr; }, 3); EXPECT_TRUE(nullMainEnqueued.enqueued); EXPECT_TRUE(service.jobInProgress(Job::Price)); auto nullMainCallback = worker.runNext(); EXPECT_TRUE(static_cast(nullMainCallback)); nullMainCallback(); EXPECT_FALSE(service.jobInProgress(Job::Price)); stats = service.stats(Job::Price); EXPECT_EQ(stats.finished, static_cast(2)); worker.reportedPending = 5; auto unboundedPrice = service.enqueue(Job::Price, worker, []() -> dragonx::rpc::RPCWorker::MainCb { return []() {}; }, 0); EXPECT_TRUE(unboundedPrice.enqueued); EXPECT_EQ(unboundedPrice.queueDepth, static_cast(5)); auto unboundedPriceMain = worker.runNext(); unboundedPriceMain(); EXPECT_FALSE(service.jobInProgress(Job::Price)); worker.reportedPending = 3; auto pressured = service.enqueue(Job::Mining, worker, []() -> dragonx::rpc::RPCWorker::MainCb { return []() {}; }, 3); EXPECT_FALSE(pressured.enqueued); EXPECT_EQ(service.stats(Job::Mining).skippedQueuePressure, static_cast(2)); FakeRefreshWorker staleWorker; int staleMainRuns = 0; auto first = service.enqueue(Job::Encryption, staleWorker, [&]() -> dragonx::rpc::RPCWorker::MainCb { return [&]() { ++staleMainRuns; }; }, 3); EXPECT_TRUE(first.enqueued); auto oldCallback = staleWorker.runNext(); service.resetJobs(); auto second = service.enqueue(Job::Encryption, staleWorker, [&]() -> dragonx::rpc::RPCWorker::MainCb { return [&]() { ++staleMainRuns; }; }, 3); EXPECT_TRUE(second.enqueued); oldCallback(); EXPECT_EQ(staleMainRuns, 0); auto freshCallback = staleWorker.runNext(); freshCallback(); EXPECT_EQ(staleMainRuns, 1); EXPECT_EQ(service.stats(Job::Encryption).staleCallbacks, static_cast(1)); FakeRefreshWorker orderingWorker; std::vector callbackOrder; auto orderedCore = service.enqueue(Job::Core, orderingWorker, [&]() -> dragonx::rpc::RPCWorker::MainCb { callbackOrder.push_back("core-work"); return [&]() { callbackOrder.push_back("core-main"); }; }, 3); auto orderedPrice = service.enqueue(Job::Price, orderingWorker, [&]() -> dragonx::rpc::RPCWorker::MainCb { callbackOrder.push_back("price-work"); return [&]() { callbackOrder.push_back("price-main"); }; }, 3); EXPECT_TRUE(orderedCore.enqueued); EXPECT_TRUE(orderedPrice.enqueued); auto priceMain = orderingWorker.runAt(1); auto coreMain = orderingWorker.runAt(0); priceMain(); coreMain(); EXPECT_TRUE(callbackOrder == std::vector({"price-work", "core-work", "price-main", "core-main"})); EXPECT_FALSE(service.jobInProgress(Job::Core)); EXPECT_FALSE(service.jobInProgress(Job::Price)); FakeRefreshWorker reconnectWorker; int reconnectMainRuns = 0; auto staleBefore = service.stats(Job::Transactions).staleCallbacks; auto oldTransactions = service.enqueue(Job::Transactions, reconnectWorker, [&]() -> dragonx::rpc::RPCWorker::MainCb { return [&]() { ++reconnectMainRuns; }; }, 3); EXPECT_TRUE(oldTransactions.enqueued); auto oldTransactionsMain = reconnectWorker.runNext(); service.resetJobs(); auto freshTransactions = service.enqueue(Job::Transactions, reconnectWorker, [&]() -> dragonx::rpc::RPCWorker::MainCb { return [&]() { ++reconnectMainRuns; }; }, 3); EXPECT_TRUE(freshTransactions.enqueued); auto freshTransactionsMain = reconnectWorker.runNext(); freshTransactionsMain(); oldTransactionsMain(); EXPECT_EQ(reconnectMainRuns, 1); EXPECT_EQ(service.stats(Job::Transactions).staleCallbacks, staleBefore + 1); EXPECT_FALSE(service.jobInProgress(Job::Transactions)); } void testNetworkRefreshSnapshotHelpers() { using Refresh = dragonx::services::NetworkRefreshService; using nlohmann::json; auto shielded = Refresh::buildShieldedAddressInfo("zs-viewonly", json{{"ismine", false}}, true); EXPECT_EQ(shielded.address, std::string("zs-viewonly")); EXPECT_EQ(shielded.type, std::string("shielded")); EXPECT_FALSE(shielded.has_spending_key); auto legacyShielded = Refresh::buildShieldedAddressInfo("zs-legacy", json::object(), false); EXPECT_TRUE(legacyShielded.has_spending_key); auto transparentAddresses = Refresh::parseTransparentAddressList(json::array({"R-one", "R-two"})); EXPECT_EQ(transparentAddresses.size(), static_cast(2)); EXPECT_EQ(transparentAddresses[0].type, std::string("transparent")); std::vector balanceAddresses = {shielded, legacyShielded}; Refresh::applyShieldedBalancesFromUnspent(balanceAddresses, json::array({ json{{"address", "zs-viewonly"}, {"amount", 1.25}}, json{{"address", "zs-viewonly"}, {"amount", 0.75}}, json{{"address", "zs-legacy"}, {"amount", 3.0}} })); EXPECT_NEAR(balanceAddresses[0].balance, 2.0, 0.00000001); EXPECT_NEAR(balanceAddresses[1].balance, 3.0, 0.00000001); std::vector transactions; std::set knownTxids; Refresh::appendTransparentTransactions(transactions, knownTxids, json::array({ json{{"txid", "transparent-a"}, {"category", "receive"}, {"amount", 1.5}, {"timereceived", 100}, {"confirmations", 2}, {"address", "R-one"}}, json{{"txid", "transparent-b"}, {"category", "send"}, {"amount", -0.25}, {"time", 300}, {"confirmations", 12}} })); EXPECT_EQ(transactions.size(), static_cast(2)); EXPECT_EQ(knownTxids.count("transparent-a"), static_cast(1)); EXPECT_EQ(transactions[0].timestamp, static_cast(100)); EXPECT_EQ(transactions[1].timestamp, static_cast(300)); Refresh::appendShieldedReceivedTransactions(transactions, knownTxids, "zs-viewonly", json::array({ json{{"txid", "transparent-a"}, {"amount", 9.0}, {"confirmations", 1}}, json{{"txid", "shielded-change"}, {"change", true}, {"amount", 2.0}}, json{{"txid", "shielded-receive"}, {"amount", 4.0}, {"confirmations", 5}, {"time", 200}, {"memoStr", "hello"}} })); EXPECT_EQ(transactions.size(), static_cast(3)); EXPECT_EQ(knownTxids.count("shielded-receive"), static_cast(1)); EXPECT_EQ(transactions.back().address, std::string("zs-viewonly")); EXPECT_EQ(transactions.back().memo, std::string("hello")); Refresh::sortTransactionsNewestFirst(transactions); EXPECT_EQ(transactions.front().txid, std::string("transparent-b")); EXPECT_EQ(transactions.back().txid, std::string("transparent-a")); dragonx::WalletState state; state.z_addresses.push_back(shielded); dragonx::AddressInfo emptyShielded; emptyShielded.type = "shielded"; state.z_addresses.push_back(emptyShielded); dragonx::TransactionInfo oldTransaction; oldTransaction.txid = "old-complete"; oldTransaction.confirmations = 7; oldTransaction.timestamp = 1000; state.transactions.push_back(oldTransaction); Refresh::TransactionViewCache viewCache; viewCache["cached-view"].from_address = "zs-from"; std::unordered_set sendTxids{"pending-send"}; auto snapshot = Refresh::buildTransactionRefreshSnapshot(state, viewCache, sendTxids); EXPECT_EQ(snapshot.shieldedAddresses.size(), static_cast(1)); EXPECT_EQ(snapshot.shieldedAddresses[0], std::string("zs-viewonly")); EXPECT_EQ(snapshot.fullyEnrichedTxids.count("cached-view"), static_cast(1)); EXPECT_EQ(snapshot.fullyEnrichedTxids.count("old-complete"), static_cast(1)); EXPECT_EQ(snapshot.viewTxCache.size(), static_cast(1)); EXPECT_EQ(snapshot.sendTxids.count("pending-send"), static_cast(1)); } void testNetworkRefreshRpcCollectors() { using Refresh = dragonx::services::NetworkRefreshService; using nlohmann::json; MockRefreshRpc warmupRpc; warmupRpc.addResponse("getinfo", json{ {"version", 120000}, {"protocolversion", 170002}, {"blocks", 12}, {"longestchain", 15}, {"notarized", 9} }); auto warmup = Refresh::collectWarmupPollResult(warmupRpc); EXPECT_TRUE(warmupRpc.methodNames() == std::vector({"getinfo"})); EXPECT_EQ(warmupRpc.calls[0].params, json::array()); EXPECT_TRUE(warmup.ready); EXPECT_TRUE(warmup.info.ok); EXPECT_EQ(*warmup.info.blocks, 12); EXPECT_EQ(*warmup.info.longestChain, 15); EXPECT_EQ(warmup.errorMessage, std::string()); MockRefreshRpc warmupFailureRpc; warmupFailureRpc.addFailure("getinfo", "Loading block index"); auto warmupFailure = Refresh::collectWarmupPollResult(warmupFailureRpc); EXPECT_TRUE(warmupFailureRpc.methodNames() == std::vector({"getinfo"})); EXPECT_EQ(warmupFailureRpc.calls[0].params, json::array()); EXPECT_FALSE(warmupFailure.ready); EXPECT_FALSE(warmupFailure.info.ok); EXPECT_EQ(warmupFailure.errorMessage, std::string("Loading block index")); MockRefreshRpc connectionRpc; connectionRpc.addResponse("getinfo", json{ {"version", 120001}, {"protocolversion", 170003}, {"p2pport", 21769}, {"longestchain", 250}, {"blocks", 240}, {"notarized", 230} }); connectionRpc.addResponse("getwalletinfo", json{{"unlocked_until", 0}}); auto connection = Refresh::collectConnectionInitResult(connectionRpc); EXPECT_TRUE(connectionRpc.methodNames() == std::vector({ "getinfo", "getwalletinfo" })); EXPECT_EQ(connectionRpc.calls[0].params, json::array()); EXPECT_EQ(connectionRpc.calls[1].params, json::array()); EXPECT_TRUE(connection.info.ok); EXPECT_EQ(*connection.info.daemonVersion, 120001); EXPECT_TRUE(connection.encryption.ok); EXPECT_TRUE(connection.encryption.encrypted); EXPECT_EQ(connection.encryption.unlockedUntil, static_cast(0)); MockRefreshRpc connectionPrefetchRpc; connectionPrefetchRpc.addFailure("getinfo", "daemon info unavailable"); connectionPrefetchRpc.addResponse("getwalletinfo", json{{"unlocked_until", 99}}); auto prefetch = Refresh::collectConnectionInitResult(connectionPrefetchRpc); EXPECT_TRUE(connectionPrefetchRpc.methodNames() == std::vector({ "getinfo", "getwalletinfo" })); EXPECT_FALSE(prefetch.info.ok); EXPECT_TRUE(prefetch.encryption.ok); EXPECT_TRUE(prefetch.encryption.encrypted); EXPECT_EQ(prefetch.encryption.unlockedUntil, static_cast(99)); MockRefreshRpc connectionReuseRpc; connectionReuseRpc.addResponse("getwalletinfo", json{{"unlocked_until", 0}}); auto reusedInfo = Refresh::parseConnectionInfoResult(json{{"version", 120002}, {"blocks", 241}}); auto reusedConnection = Refresh::collectConnectionInitResult(connectionReuseRpc, reusedInfo); EXPECT_TRUE(connectionReuseRpc.methodNames() == std::vector({"getwalletinfo"})); EXPECT_TRUE(reusedConnection.info.ok); EXPECT_EQ(*reusedConnection.info.daemonVersion, 120002); EXPECT_TRUE(reusedConnection.encryption.ok); MockRefreshRpc coreRpc; coreRpc.addResponse("z_gettotalbalance", json{ {"private", "3.00000000"}, {"transparent", "1.25000000"}, {"total", "4.25000000"} }); coreRpc.addResponse("getblockchaininfo", json{ {"blocks", 150}, {"headers", 155}, {"bestblockhash", "core-best-150"}, {"verificationprogress", 0.80}, {"longestchain", 160}, {"notarized", 145} }); auto core = Refresh::collectCoreRefreshResult(coreRpc); EXPECT_TRUE(coreRpc.methodNames() == std::vector({ "z_gettotalbalance", "getblockchaininfo" })); EXPECT_EQ(coreRpc.calls[0].params, json::array()); EXPECT_EQ(coreRpc.calls[1].params, json::array()); EXPECT_TRUE(core.balanceOk); EXPECT_TRUE(core.blockchainOk); EXPECT_NEAR(*core.totalBalance, 4.25, 0.00000001); EXPECT_EQ(*core.blocks, 150); EXPECT_EQ(*core.bestBlockHash, std::string("core-best-150")); EXPECT_EQ(*core.longestChain, 160); MockRefreshRpc coreFallbackRpc; coreFallbackRpc.addFailure("z_gettotalbalance", "wallet warming up"); coreFallbackRpc.addResponse("getblockchaininfo", json{{"blocks", 8}, {"headers", 9}}); auto partialCore = Refresh::collectCoreRefreshResult(coreFallbackRpc); EXPECT_TRUE(coreFallbackRpc.methodNames() == std::vector({ "z_gettotalbalance", "getblockchaininfo" })); EXPECT_FALSE(partialCore.balanceOk); EXPECT_TRUE(partialCore.blockchainOk); EXPECT_EQ(*partialCore.blocks, 8); MockRefreshRpc peerRpc; peerRpc.addResponse("getpeerinfo", json::array({ json{{"id", 42}, {"addr", "203.0.113.7:21769"}, {"tls_verified", true}} })); peerRpc.addResponse("listbanned", json::array({ json{{"address", "198.51.100.9"}, {"banned_until", 4444}} })); auto peers = Refresh::collectPeerRefreshResult(peerRpc); EXPECT_TRUE(peerRpc.methodNames() == std::vector({ "getpeerinfo", "listbanned" })); EXPECT_EQ(peerRpc.calls[0].params, json::array()); EXPECT_EQ(peerRpc.calls[1].params, json::array()); EXPECT_EQ(peers.peers.size(), static_cast(1)); EXPECT_EQ(peers.peers[0].id, 42); EXPECT_TRUE(peers.peers[0].tls_verified); EXPECT_EQ(peers.bannedPeers.size(), static_cast(1)); EXPECT_EQ(peers.bannedPeers[0].address, std::string("198.51.100.9")); MockRefreshRpc peerPartialRpc; peerPartialRpc.addFailure("getpeerinfo", "peer table unavailable"); peerPartialRpc.addResponse("listbanned", json::array({ json{{"address", "203.0.113.8"}, {"banned_until", 5555}} })); auto partialPeers = Refresh::collectPeerRefreshResult(peerPartialRpc); EXPECT_TRUE(peerPartialRpc.methodNames() == std::vector({ "getpeerinfo", "listbanned" })); EXPECT_EQ(partialPeers.peers.size(), static_cast(0)); EXPECT_EQ(partialPeers.bannedPeers.size(), static_cast(1)); MockRefreshRpc miningSlowRpc; miningSlowRpc.addResponse("getlocalsolps", json(72.5)); miningSlowRpc.addResponse("getmininginfo", json{ {"generate", true}, {"genproclimit", 6}, {"blocks", 321}, {"difficulty", 19.75}, {"networkhashps", 1250.0}, {"chain", "main"} }); auto miningSlow = Refresh::collectMiningRefreshResult(miningSlowRpc, 128.0, true); EXPECT_TRUE(miningSlowRpc.methodNames() == std::vector({ "getlocalsolps", "getmininginfo" })); EXPECT_EQ(miningSlowRpc.calls[0].params, json::array()); EXPECT_EQ(miningSlowRpc.calls[1].params, json::array()); EXPECT_NEAR(*miningSlow.localHashrate, 72.5, 0.00000001); EXPECT_TRUE(miningSlow.miningOk); EXPECT_TRUE(*miningSlow.generate); EXPECT_EQ(*miningSlow.genproclimit, 6); EXPECT_NEAR(miningSlow.daemonMemoryMb, 128.0, 0.00000001); MockRefreshRpc miningFastRpc; miningFastRpc.addResponse("getlocalsolps", json(80.25)); auto miningFast = Refresh::collectMiningRefreshResult(miningFastRpc, 64.0, false); EXPECT_TRUE(miningFastRpc.methodNames() == std::vector({ "getlocalsolps" })); EXPECT_NEAR(*miningFast.localHashrate, 80.25, 0.00000001); EXPECT_FALSE(miningFast.miningOk); EXPECT_NEAR(miningFast.daemonMemoryMb, 64.0, 0.00000001); MockRefreshRpc miningPartialRpc; miningPartialRpc.addFailure("getlocalsolps", "local solps unavailable"); miningPartialRpc.addResponse("getmininginfo", json{{"generate", false}, {"blocks", 7}}); auto miningPartial = Refresh::collectMiningRefreshResult(miningPartialRpc, 12.0, true); EXPECT_TRUE(miningPartialRpc.methodNames() == std::vector({ "getlocalsolps", "getmininginfo" })); EXPECT_FALSE(miningPartial.localHashrate.has_value()); EXPECT_TRUE(miningPartial.miningOk); EXPECT_FALSE(*miningPartial.generate); MockRefreshRpc miningSlowOnlyRpc; miningSlowOnlyRpc.addResponse("getmininginfo", json{{"generate", false}, {"networkhashps", 222.0}}); auto miningSlowOnly = Refresh::collectMiningRefreshResult(miningSlowOnlyRpc, 7.0, true, false); EXPECT_TRUE(miningSlowOnlyRpc.methodNames() == std::vector({"getmininginfo"})); EXPECT_FALSE(miningSlowOnly.localHashrate.has_value()); EXPECT_TRUE(miningSlowOnly.miningOk); EXPECT_NEAR(*miningSlowOnly.networkHashrate, 222.0, 0.00000001); MockRefreshRpc addressRpc; addressRpc.addResponse("z_listaddresses", json::array({"zs-one", "zs-two"})); addressRpc.addResponse("z_validateaddress", json{{"ismine", true}}); addressRpc.addResponse("z_validateaddress", json{{"ismine", false}}); addressRpc.addResponse("z_listunspent", json::array({ json{{"address", "zs-one"}, {"amount", 1.0}}, json{{"address", "zs-one"}, {"amount", 0.5}} })); addressRpc.addResponse("getaddressesbyaccount", json::array({"R-one"})); addressRpc.addResponse("listunspent", json::array({ json{{"address", "R-one"}, {"amount", 2.25}} })); auto addresses = Refresh::collectAddressRefreshResult(addressRpc); EXPECT_TRUE(addressRpc.methodNames() == std::vector({ "z_listaddresses", "z_validateaddress", "z_validateaddress", "z_listunspent", "getaddressesbyaccount", "listunspent" })); EXPECT_EQ(addressRpc.calls[1].params, json::array({"zs-one"})); EXPECT_EQ(addressRpc.calls[2].params, json::array({"zs-two"})); EXPECT_EQ(addressRpc.calls[4].params, json::array({""})); EXPECT_EQ(addresses.shieldedAddresses.size(), static_cast(2)); EXPECT_TRUE(addresses.shieldedAddresses[0].has_spending_key); EXPECT_FALSE(addresses.shieldedAddresses[1].has_spending_key); EXPECT_NEAR(addresses.shieldedAddresses[0].balance, 1.5, 0.00000001); EXPECT_EQ(addresses.transparentAddresses.size(), static_cast(1)); EXPECT_NEAR(addresses.transparentAddresses[0].balance, 2.25, 0.00000001); MockRefreshRpc fallbackRpc; fallbackRpc.addResponse("z_listaddresses", json::array({"zs-fallback"})); fallbackRpc.addFailure("z_validateaddress", "legacy daemon"); fallbackRpc.addFailure("z_listunspent", "method not found"); fallbackRpc.addResponse("z_getbalance", json(4.75)); fallbackRpc.addResponse("getaddressesbyaccount", json::array()); fallbackRpc.addResponse("listunspent", json::array()); auto fallbackAddresses = Refresh::collectAddressRefreshResult(fallbackRpc); EXPECT_TRUE(fallbackRpc.methodNames() == std::vector({ "z_listaddresses", "z_validateaddress", "z_listunspent", "z_getbalance", "getaddressesbyaccount", "listunspent" })); EXPECT_TRUE(fallbackAddresses.shieldedAddresses[0].has_spending_key); EXPECT_NEAR(fallbackAddresses.shieldedAddresses[0].balance, 4.75, 0.00000001); dragonx::WalletState cachedAddressState; cachedAddressState.z_addresses.push_back({"zs-cached", 0.0, "shielded", false}); auto addressSnapshot = Refresh::buildAddressRefreshSnapshot(cachedAddressState); MockRefreshRpc cachedAddressRpc; cachedAddressRpc.addResponse("z_listaddresses", json::array({"zs-cached", "zs-new"})); cachedAddressRpc.addResponse("z_validateaddress", json{{"ismine", true}}); cachedAddressRpc.addResponse("z_listunspent", json::array()); cachedAddressRpc.addResponse("getaddressesbyaccount", json::array()); cachedAddressRpc.addResponse("listunspent", json::array()); auto cachedAddresses = Refresh::collectAddressRefreshResult(cachedAddressRpc, addressSnapshot); EXPECT_TRUE(cachedAddressRpc.methodNames() == std::vector({ "z_listaddresses", "z_validateaddress", "z_listunspent", "getaddressesbyaccount", "listunspent" })); EXPECT_FALSE(cachedAddresses.shieldedAddresses[0].has_spending_key); EXPECT_TRUE(cachedAddresses.shieldedAddresses[1].has_spending_key); Refresh::TransactionRefreshSnapshot snapshot; snapshot.shieldedAddresses = {"zs-one", "zs-two"}; snapshot.sendTxids = {"cached-send", "pending-send"}; snapshot.fullyEnrichedTxids = {"shielded-receive", "transparent-a"}; Refresh::TransactionViewCacheEntry cachedEntry; cachedEntry.from_address = "zs-cache-from"; cachedEntry.outgoing_outputs.push_back({"zs-cache-dest", 0.20, "cached memo"}); snapshot.viewTxCache["cached-send"] = cachedEntry; MockRefreshRpc transactionRpc; transactionRpc.addResponse("listtransactions", json::array({ json{{"txid", "transparent-a"}, {"category", "receive"}, {"amount", 1.0}, {"time", 100}, {"confirmations", 3}, {"address", "R-one"}} })); transactionRpc.addResponse("z_listreceivedbyaddress", json::array({ json{{"txid", "shielded-receive"}, {"amount", 2.0}, {"confirmations", 4}, {"time", 50}, {"memoStr", "shielded memo"}} })); transactionRpc.addResponse("z_listreceivedbyaddress", json::array()); transactionRpc.addResponse("z_viewtransaction", json{ {"spends", json::array({json{{"address", "zs-from"}}})}, {"outputs", json::array({ json{{"outgoing", true}, {"address", "zs-dest"}, {"value", 0.40}, {"memoStr", "fresh memo"}} })} }); transactionRpc.addResponse("gettransaction", json{{"time", 500}, {"confirmations", 1}}); auto transactionResult = Refresh::collectTransactionRefreshResult(transactionRpc, snapshot, 321, 4); EXPECT_TRUE(transactionRpc.methodNames() == std::vector({ "listtransactions", "z_listreceivedbyaddress", "z_listreceivedbyaddress", "z_viewtransaction", "gettransaction" })); EXPECT_EQ(transactionRpc.calls[1].params, json::array({"zs-one", 0})); EXPECT_EQ(transactionRpc.calls[2].params, json::array({"zs-two", 0})); EXPECT_EQ(transactionRpc.calls[3].params, json::array({"pending-send"})); EXPECT_EQ(transactionRpc.calls[4].params, json::array({"pending-send"})); EXPECT_EQ(transactionResult.blockHeight, 321); EXPECT_EQ(transactionResult.newViewTxEntries.size(), static_cast(1)); EXPECT_EQ(transactionResult.newViewTxEntries.count("pending-send"), static_cast(1)); EXPECT_EQ(transactionResult.newViewTxEntries.at("pending-send").timestamp, static_cast(500)); EXPECT_EQ(transactionResult.newViewTxEntries.at("pending-send").confirmations, 1); EXPECT_EQ(transactionResult.transactions.size(), static_cast(4)); EXPECT_EQ(transactionResult.transactions.front().txid, std::string("pending-send")); EXPECT_EQ(transactionResult.transactions.front().type, std::string("send")); EXPECT_NEAR(transactionResult.transactions.front().amount, -0.40, 0.00000001); EXPECT_EQ(transactionResult.transactions.front().timestamp, static_cast(500)); EXPECT_EQ(transactionResult.transactions[1].txid, std::string("transparent-a")); Refresh::TransactionRefreshSnapshot cachedOnlySnapshot; auto cachedOnlyEntry = cachedEntry; cachedOnlyEntry.timestamp = 450; cachedOnlyEntry.confirmations = 6; cachedOnlySnapshot.viewTxCache["cached-only-send"] = cachedOnlyEntry; cachedOnlySnapshot.fullyEnrichedTxids = {"cached-only-send"}; MockRefreshRpc cachedOnlyRpc; cachedOnlyRpc.addResponse("listtransactions", json::array()); auto cachedOnlyResult = Refresh::collectTransactionRefreshResult(cachedOnlyRpc, cachedOnlySnapshot, 322, 4); EXPECT_TRUE(cachedOnlyRpc.methodNames() == std::vector({"listtransactions"})); EXPECT_EQ(cachedOnlyResult.transactions.size(), static_cast(1)); EXPECT_EQ(cachedOnlyResult.transactions[0].txid, std::string("cached-only-send")); EXPECT_EQ(cachedOnlyResult.transactions[0].type, std::string("send")); EXPECT_NEAR(cachedOnlyResult.transactions[0].amount, -0.20, 0.00000001); EXPECT_EQ(cachedOnlyResult.transactions[0].timestamp, static_cast(450)); EXPECT_EQ(cachedOnlyResult.transactions[0].confirmations, 6); Refresh::TransactionRefreshSnapshot retrySnapshot; retrySnapshot.sendTxids = {"retry-send"}; retrySnapshot.viewTxCache["retry-send"] = Refresh::TransactionViewCacheEntry{}; retrySnapshot.fullyEnrichedTxids = {"retry-send"}; MockRefreshRpc retryRpc; retryRpc.addResponse("listtransactions", json::array()); retryRpc.addResponse("z_viewtransaction", json{ {"spends", json::array({json{{"address", "zs-retry-from"}}})}, {"outputs", json::array({ json{{"outgoing", true}, {"address", "zs-retry-dest"}, {"value", 0.55}} })} }); retryRpc.addResponse("gettransaction", json{{"time", 650}, {"confirmations", 2}}); auto retryResult = Refresh::collectTransactionRefreshResult(retryRpc, retrySnapshot, 323, 4); EXPECT_TRUE(retryRpc.methodNames() == std::vector({ "listtransactions", "z_viewtransaction", "gettransaction" })); EXPECT_EQ(retryResult.transactions.size(), static_cast(1)); EXPECT_EQ(retryResult.transactions[0].txid, std::string("retry-send")); EXPECT_NEAR(retryResult.transactions[0].amount, -0.55, 0.00000001); EXPECT_EQ(retryResult.newViewTxEntries.count("retry-send"), static_cast(1)); EXPECT_EQ(retryResult.newViewTxEntries.at("retry-send").timestamp, static_cast(650)); EXPECT_EQ(retryResult.newViewTxEntries.at("retry-send").confirmations, 2); Refresh::TransactionRefreshSnapshot placeholderSnapshot; placeholderSnapshot.sendTxids = {"placeholder-send"}; MockRefreshRpc placeholderRpc; placeholderRpc.addResponse("listtransactions", json::array()); placeholderRpc.addResponse("z_viewtransaction", json{{"spends", json::array()}, {"outputs", json::array()}}); placeholderRpc.addResponse("gettransaction", json{ {"time", 700}, {"confirmations", 0}, {"amount", -0.33}, {"details", json::array({ json{{"category", "send"}, {"address", "zs-placeholder-dest"}, {"amount", -0.33}} })} }); auto placeholderResult = Refresh::collectTransactionRefreshResult(placeholderRpc, placeholderSnapshot, 324, 4); EXPECT_TRUE(placeholderRpc.methodNames() == std::vector({ "listtransactions", "z_viewtransaction", "gettransaction" })); EXPECT_EQ(placeholderResult.transactions.size(), static_cast(1)); EXPECT_EQ(placeholderResult.transactions[0].txid, std::string("placeholder-send")); EXPECT_EQ(placeholderResult.transactions[0].type, std::string("send")); EXPECT_NEAR(placeholderResult.transactions[0].amount, -0.33, 0.00000001); EXPECT_EQ(placeholderResult.transactions[0].timestamp, static_cast(700)); EXPECT_EQ(placeholderResult.transactions[0].confirmations, 0); EXPECT_EQ(placeholderResult.transactions[0].address, std::string("zs-placeholder-dest")); EXPECT_EQ(placeholderResult.newViewTxEntries.count("placeholder-send"), static_cast(0)); Refresh::TransactionRefreshSnapshot missingAddressesSnapshot; missingAddressesSnapshot.previousTransactions.push_back(dragonx::TransactionInfo{ "shielded-fallback", "receive", 1.25, 150, 2, "zs-one", "", "memo" }); MockRefreshRpc missingAddressesRpc; missingAddressesRpc.addResponse("listtransactions", json::array()); auto missingAddressesResult = Refresh::collectTransactionRefreshResult( missingAddressesRpc, missingAddressesSnapshot, 325, 4); EXPECT_TRUE(missingAddressesRpc.methodNames() == std::vector({"listtransactions"})); EXPECT_EQ(missingAddressesResult.transactions.size(), static_cast(1)); EXPECT_EQ(missingAddressesResult.transactions[0].txid, std::string("shielded-fallback")); EXPECT_EQ(missingAddressesResult.transactions[0].type, std::string("receive")); Refresh::TransactionRefreshSnapshot pendingOpidSnapshot; pendingOpidSnapshot.pendingOpids = {"opid-visible-send"}; pendingOpidSnapshot.previousTransactions.push_back(dragonx::TransactionInfo{ "opid-visible-send", "send", -4.25, 170, 0, "R-destination", "R-source", "" }); MockRefreshRpc pendingOpidRpc; pendingOpidRpc.addResponse("listtransactions", json::array()); auto pendingOpidResult = Refresh::collectTransactionRefreshResult( pendingOpidRpc, pendingOpidSnapshot, 326, 4); EXPECT_TRUE(pendingOpidRpc.methodNames() == std::vector({"listtransactions"})); EXPECT_EQ(pendingOpidResult.transactions.size(), static_cast(1)); EXPECT_EQ(pendingOpidResult.transactions[0].txid, std::string("opid-visible-send")); EXPECT_EQ(pendingOpidResult.transactions[0].type, std::string("send")); EXPECT_NEAR(pendingOpidResult.transactions[0].amount, -4.25, 0.00000001); Refresh::TransactionRefreshSnapshot partialFailureSnapshot; partialFailureSnapshot.shieldedAddresses = {"zs-one"}; partialFailureSnapshot.previousTransactions.push_back(dragonx::TransactionInfo{ "old-receive", "receive", 2.50, 140, 8, "zs-one", "", "old memo" }); MockRefreshRpc partialFailureRpc; partialFailureRpc.addResponse("listtransactions", json::array({ json{{"txid", "transparent-b"}, {"category", "receive"}, {"amount", 0.75}, {"time", 160}, {"confirmations", 2}, {"address", "R-two"}} })); partialFailureRpc.addFailure("z_listreceivedbyaddress", "temporary receive failure"); auto partialFailureResult = Refresh::collectTransactionRefreshResult( partialFailureRpc, partialFailureSnapshot, 326, 4); EXPECT_EQ(partialFailureResult.transactions.size(), static_cast(2)); EXPECT_EQ(partialFailureResult.transactions[0].txid, std::string("transparent-b")); EXPECT_EQ(partialFailureResult.transactions[1].txid, std::string("old-receive")); Refresh::TransactionRefreshSnapshot pagedSnapshot; MockRefreshRpc pagedRpc; json firstPage = json::array(); for (int i = 0; i < 1000; ++i) { firstPage.push_back(json{{"txid", "paged-" + std::to_string(i)}, {"category", "receive"}, {"amount", 0.01}, {"time", i}, {"confirmations", 10}, {"address", "R-page"}}); } pagedRpc.addResponse("listtransactions", firstPage); pagedRpc.addResponse("listtransactions", json::array({ json{{"txid", "paged-1000"}, {"category", "receive"}, {"amount", 0.02}, {"time", 2000}, {"confirmations", 11}, {"address", "R-page"}} })); auto pagedResult = Refresh::collectTransactionRefreshResult(pagedRpc, pagedSnapshot, 327, 0); EXPECT_TRUE(pagedRpc.methodNames() == std::vector({"listtransactions", "listtransactions"})); EXPECT_EQ(pagedRpc.calls[0].params, json::array({"", 1000, 0})); EXPECT_EQ(pagedRpc.calls[1].params, json::array({"", 1000, 1000})); EXPECT_EQ(pagedResult.transactions.size(), static_cast(1001)); EXPECT_EQ(pagedResult.transactions[0].txid, std::string("paged-1000")); Refresh::TransactionRefreshSnapshot recentSnapshot; dragonx::TransactionInfo previousShielded; previousShielded.txid = "shielded-old"; previousShielded.type = "receive"; previousShielded.address = "zs-one"; previousShielded.amount = 3.0; previousShielded.timestamp = 10; dragonx::TransactionInfo previousTransparent; previousTransparent.txid = "recent-one"; previousTransparent.type = "receive"; previousTransparent.address = "R-one"; previousTransparent.amount = 1.0; previousTransparent.timestamp = 20; recentSnapshot.previousTransactions = {previousShielded, previousTransparent}; MockRefreshRpc recentRpc; recentRpc.addResponse("listtransactions", json::array({ json{{"txid", "recent-one"}, {"category", "receive"}, {"address", "R-one"}, {"amount", 2.0}, {"time", 30}, {"confirmations", 0}}, json{{"txid", "recent-two"}, {"category", "send"}, {"address", "R-two"}, {"amount", -0.5}, {"time", 40}, {"confirmations", 0}} })); auto recent = Refresh::collectRecentTransactionRefreshResult(recentRpc, recentSnapshot, 123); EXPECT_TRUE(recentRpc.methodNames() == std::vector({"listtransactions"})); EXPECT_EQ(recentRpc.calls[0].params, json::array({"", 100, 0})); EXPECT_EQ(recent.blockHeight, 123); EXPECT_EQ(recent.transactions.size(), static_cast(3)); EXPECT_EQ(recent.transactions[0].txid, std::string("recent-two")); EXPECT_EQ(recent.transactions[1].txid, std::string("recent-one")); EXPECT_NEAR(recent.transactions[1].amount, 2.0, 0.00000001); EXPECT_EQ(recent.transactions[2].txid, std::string("shielded-old")); Refresh::TransactionRefreshSnapshot recentShieldedProbeSnapshot; recentShieldedProbeSnapshot.shieldedAddresses = {"zs-probe-a", "zs-probe-b"}; recentShieldedProbeSnapshot.shieldedScanStartIndex = 1; recentShieldedProbeSnapshot.maxShieldedReceiveScans = 1; recentShieldedProbeSnapshot.shieldedScanHeights = {{"zs-probe-a", 600}, {"zs-probe-b", 600}}; MockRefreshRpc recentShieldedProbeRpc; recentShieldedProbeRpc.addResponse("listtransactions", json::array()); recentShieldedProbeRpc.addResponse("z_listreceivedbyaddress", json::array({ json{{"txid", "same-tip-shielded"}, {"amount", 1.75}, {"confirmations", 0}, {"time", 170}} })); auto recentShieldedProbe = Refresh::collectRecentTransactionRefreshResult( recentShieldedProbeRpc, recentShieldedProbeSnapshot, 600); EXPECT_TRUE(recentShieldedProbeRpc.methodNames() == std::vector({ "listtransactions", "z_listreceivedbyaddress" })); EXPECT_EQ(recentShieldedProbeRpc.calls[1].params, json::array({"zs-probe-b", 0})); EXPECT_EQ(recentShieldedProbe.nextShieldedScanStartIndex, static_cast(0)); EXPECT_EQ(recentShieldedProbe.shieldedAddressesScanned, static_cast(1)); EXPECT_EQ(recentShieldedProbe.shieldedScanHeights.at("zs-probe-b"), 600); EXPECT_EQ(recentShieldedProbe.transactions.size(), static_cast(1)); EXPECT_EQ(recentShieldedProbe.transactions[0].txid, std::string("same-tip-shielded")); EXPECT_EQ(recentShieldedProbe.transactions[0].confirmations, 0); Refresh::TransactionRefreshSnapshot partialShieldedSnapshot; partialShieldedSnapshot.shieldedAddresses = {"zs-zero", "zs-one", "zs-two"}; partialShieldedSnapshot.maxShieldedReceiveScans = 2; partialShieldedSnapshot.previousTransactions.push_back(dragonx::TransactionInfo{ "old-zs-two", "receive", 1.0, 90, 10, "zs-two", "", "memo" }); MockRefreshRpc partialShieldedRpc; partialShieldedRpc.addResponse("listtransactions", json::array()); partialShieldedRpc.addResponse("z_listreceivedbyaddress", json::array({ json{{"txid", "new-zs-zero"}, {"amount", 0.5}, {"confirmations", 1}, {"time", 100}} })); partialShieldedRpc.addResponse("z_listreceivedbyaddress", json::array()); auto partialShielded = Refresh::collectTransactionRefreshResult( partialShieldedRpc, partialShieldedSnapshot, 400, 0); EXPECT_TRUE(partialShieldedRpc.methodNames() == std::vector({ "listtransactions", "z_listreceivedbyaddress", "z_listreceivedbyaddress" })); EXPECT_EQ(partialShieldedRpc.calls[1].params, json::array({"zs-zero", 0})); EXPECT_EQ(partialShieldedRpc.calls[2].params, json::array({"zs-one", 0})); EXPECT_FALSE(partialShielded.shieldedScanComplete); EXPECT_EQ(partialShielded.nextShieldedScanStartIndex, static_cast(2)); EXPECT_EQ(partialShielded.shieldedAddressesScanned, static_cast(2)); EXPECT_EQ(partialShielded.transactions.size(), static_cast(2)); EXPECT_EQ(partialShielded.transactions[0].txid, std::string("new-zs-zero")); EXPECT_EQ(partialShielded.transactions[1].txid, std::string("old-zs-two")); partialShieldedSnapshot.shieldedScanStartIndex = partialShielded.nextShieldedScanStartIndex; partialShieldedSnapshot.shieldedScanHeights = partialShielded.shieldedScanHeights; MockRefreshRpc finalShieldedRpc; finalShieldedRpc.addResponse("listtransactions", json::array()); finalShieldedRpc.addResponse("z_listreceivedbyaddress", json::array()); auto finalShielded = Refresh::collectTransactionRefreshResult( finalShieldedRpc, partialShieldedSnapshot, 400, 0); EXPECT_TRUE(finalShieldedRpc.methodNames() == std::vector({ "listtransactions", "z_listreceivedbyaddress" })); EXPECT_EQ(finalShieldedRpc.calls[1].params, json::array({"zs-two", 0})); EXPECT_TRUE(finalShielded.shieldedScanComplete); EXPECT_EQ(finalShielded.nextShieldedScanStartIndex, static_cast(0)); EXPECT_EQ(finalShielded.shieldedScanHeights.at("zs-zero"), 400); EXPECT_EQ(finalShielded.shieldedScanHeights.at("zs-one"), 400); EXPECT_EQ(finalShielded.shieldedScanHeights.at("zs-two"), 400); Refresh::TransactionRefreshSnapshot cachedShieldedSnapshot; cachedShieldedSnapshot.shieldedAddresses = {"zs-cached"}; cachedShieldedSnapshot.shieldedScanHeights = {{"zs-cached", 500}}; cachedShieldedSnapshot.previousTransactions.push_back(dragonx::TransactionInfo{ "cached-shielded", "receive", 1.5, 120, 20, "zs-cached", "", "cached memo" }); MockRefreshRpc cachedShieldedRpc; cachedShieldedRpc.addResponse("listtransactions", json::array()); auto cachedShielded = Refresh::collectTransactionRefreshResult( cachedShieldedRpc, cachedShieldedSnapshot, 500, 0); EXPECT_TRUE(cachedShieldedRpc.methodNames() == std::vector({"listtransactions"})); EXPECT_TRUE(cachedShielded.shieldedScanComplete); EXPECT_EQ(cachedShielded.shieldedAddressesScanned, static_cast(0)); EXPECT_EQ(cachedShielded.transactions.size(), static_cast(1)); EXPECT_EQ(cachedShielded.transactions[0].txid, std::string("cached-shielded")); Refresh::TransactionRefreshSnapshot staleProgressSnapshot; staleProgressSnapshot.shieldedAddresses = {"zs-current", "zs-stale", "zs-missing"}; staleProgressSnapshot.shieldedScanHeights = {{"zs-current", 500}, {"zs-stale", 499}}; staleProgressSnapshot.maxShieldedReceiveScans = 1; staleProgressSnapshot.previousTransactions.push_back(dragonx::TransactionInfo{ "current-shielded", "receive", 0.75, 110, 12, "zs-current", "", "" }); MockRefreshRpc staleProgressRpc; staleProgressRpc.addResponse("listtransactions", json::array()); staleProgressRpc.addResponse("z_listreceivedbyaddress", json::array({ json{{"txid", "stale-shielded"}, {"amount", 2.0}, {"confirmations", 1}, {"time", 130}} })); auto staleProgress = Refresh::collectTransactionRefreshResult( staleProgressRpc, staleProgressSnapshot, 500, 0); EXPECT_TRUE(staleProgressRpc.methodNames() == std::vector({ "listtransactions", "z_listreceivedbyaddress" })); EXPECT_EQ(staleProgressRpc.calls[1].params, json::array({"zs-stale", 0})); EXPECT_FALSE(staleProgress.shieldedScanComplete); EXPECT_EQ(staleProgress.nextShieldedScanStartIndex, static_cast(2)); EXPECT_EQ(staleProgress.shieldedAddressesScanned, static_cast(1)); EXPECT_EQ(staleProgress.shieldedScanHeights.at("zs-current"), 500); EXPECT_EQ(staleProgress.shieldedScanHeights.at("zs-stale"), 500); EXPECT_TRUE(staleProgress.shieldedScanHeights.find("zs-missing") == staleProgress.shieldedScanHeights.end()); EXPECT_EQ(staleProgress.transactions.size(), static_cast(2)); EXPECT_EQ(staleProgress.transactions[0].txid, std::string("stale-shielded")); EXPECT_EQ(staleProgress.transactions[1].txid, std::string("current-shielded")); Refresh::TransactionRefreshSnapshot miningSnapshot; miningSnapshot.shieldedAddresses = {"zs-mine"}; miningSnapshot.miningAddresses = {"R-mine", "zs-mine"}; MockRefreshRpc miningTxRpc; miningTxRpc.addResponse("listtransactions", json::array({ json{{"txid", "transparent-mined"}, {"category", "receive"}, {"amount", 3.0}, {"time", 220}, {"confirmations", 101}, {"address", "R-mine"}} })); miningTxRpc.addResponse("z_listreceivedbyaddress", json::array({ json{{"txid", "shielded-mined"}, {"amount", 4.0}, {"confirmations", 102}, {"time", 210}, {"memoStr", "pool"}} })); auto miningTxResult = Refresh::collectTransactionRefreshResult(miningTxRpc, miningSnapshot, 328, 0); EXPECT_EQ(miningTxResult.transactions.size(), static_cast(2)); EXPECT_EQ(miningTxResult.transactions[0].type, std::string("mined")); EXPECT_EQ(miningTxResult.transactions[1].type, std::string("mined")); } void testNetworkRefreshResultModels() { using Refresh = dragonx::services::NetworkRefreshService; using nlohmann::json; dragonx::WalletState state; auto core = Refresh::parseCoreRefreshResult( json{{"private", "1.25000000"}, {"transparent", "0.50000000"}, {"total", "1.75000000"}}, true, json{{"blocks", 100}, {"headers", 105}, {"bestblockhash", "apply-best-100"}, {"verificationprogress", 0.75}, {"longestchain", 110}, {"notarized", 90}}, true); Refresh::applyCoreRefreshResult(state, core, 1234); EXPECT_NEAR(state.shielded_balance, 1.25, 0.00000001); EXPECT_NEAR(state.transparent_balance, 0.5, 0.00000001); EXPECT_NEAR(state.total_balance, 1.75, 0.00000001); EXPECT_EQ(state.sync.blocks, 100); EXPECT_EQ(state.sync.headers, 105); EXPECT_EQ(state.sync.best_blockhash, std::string("apply-best-100")); EXPECT_TRUE(state.sync.syncing); EXPECT_EQ(state.longestchain, 110); EXPECT_EQ(state.notarized, 90); EXPECT_EQ(state.last_balance_update, static_cast(1234)); auto connectionInfo = Refresh::parseConnectionInfoResult( json{{"version", 120000}, {"protocolversion", 170002}, {"p2pport", 8233}, {"longestchain", 0}, {"blocks", 120}, {"notarized", 118}}); Refresh::applyConnectionInfoResult(state, connectionInfo); EXPECT_EQ(state.daemon_version, 120000); EXPECT_EQ(state.protocol_version, 170002); EXPECT_EQ(state.p2p_port, 8233); EXPECT_EQ(state.sync.blocks, 120); EXPECT_EQ(state.longestchain, 120); EXPECT_EQ(state.notarized, 118); auto encrypted = Refresh::parseWalletEncryptionResult(json{{"unlocked_until", 0}}); Refresh::applyWalletEncryptionResult(state, encrypted); EXPECT_TRUE(state.encrypted); EXPECT_TRUE(state.locked); EXPECT_TRUE(state.encryption_state_known); auto unencrypted = Refresh::parseWalletEncryptionResult(json::object()); Refresh::applyWalletEncryptionResult(state, unencrypted); EXPECT_FALSE(state.encrypted); EXPECT_FALSE(state.locked); EXPECT_EQ(state.unlocked_until, static_cast(0)); auto mining = Refresh::parseMiningRefreshResult( json{{"generate", true}, {"genproclimit", 4}, {"blocks", 101}, {"difficulty", 12.5}, {"networkhashps", 900.0}, {"chain", "main"}}, true, json(45.0), true, 64.0); Refresh::applyMiningRefreshResult(state, mining, 2345); EXPECT_TRUE(state.mining.generate); EXPECT_EQ(state.mining.genproclimit, 4); EXPECT_NEAR(state.mining.localHashrate, 45.0, 0.0001); EXPECT_EQ(state.mining.hashrate_history.size(), static_cast(1)); EXPECT_NEAR(state.mining.networkHashrate, 900.0, 0.0001); EXPECT_NEAR(state.mining.daemon_memory_mb, 64.0, 0.0001); EXPECT_EQ(state.last_mining_update, static_cast(2345)); auto peers = Refresh::parsePeerRefreshResult( json::array({json{{"id", 7}, {"addr", "127.0.0.1:8233"}, {"tls_verified", true}}}), json::array({json{{"address", "192.0.2.1"}, {"banned_until", 3456}}})); Refresh::applyPeerRefreshResult(state, std::move(peers), 3456); EXPECT_EQ(state.peers.size(), static_cast(1)); EXPECT_EQ(state.peers[0].id, 7); EXPECT_TRUE(state.peers[0].tls_verified); EXPECT_EQ(state.bannedPeers.size(), static_cast(1)); EXPECT_EQ(state.bannedPeers[0].address, std::string("192.0.2.1")); EXPECT_EQ(state.last_peer_update, static_cast(3456)); auto price = Refresh::parseCoinGeckoPriceResponse( R"({"dragonx-2":{"usd":0.5,"btc":0.00001,"usd_24h_change":2.5,"usd_24h_vol":1000,"usd_market_cap":50000}})", 0); EXPECT_TRUE(price.has_value()); if (price) { Refresh::markPriceRefreshStarted(state); EXPECT_TRUE(state.market.price_loading); Refresh::applyPriceRefreshResult(state, *price, std::chrono::steady_clock::now()); EXPECT_FALSE(state.market.price_loading); EXPECT_EQ(state.market.price_error, std::string()); EXPECT_NEAR(state.market.price_usd, 0.5, 0.00000001); EXPECT_NEAR(state.market.price_btc, 0.00001, 0.00000001); EXPECT_NEAR(state.market.change_24h, 2.5, 0.0001); EXPECT_EQ(state.market.price_history.size(), static_cast(1)); } Refresh::markPriceRefreshStarted(state); Refresh::applyPriceRefreshFailure(state, "timeout"); EXPECT_FALSE(state.market.price_loading); EXPECT_EQ(state.market.price_error, std::string("timeout")); EXPECT_NEAR(state.market.price_usd, 0.5, 0.00000001); Refresh::PriceHttpResponse priceHttpOk; priceHttpOk.transportOk = true; priceHttpOk.httpStatus = 200; priceHttpOk.body = R"({"dragonx-2":{"usd":0.75,"btc":0.00002,"usd_24h_change":1.5,"usd_24h_vol":2000,"usd_market_cap":75000}})"; auto priceHttp = Refresh::parsePriceHttpResponse(priceHttpOk, 0); EXPECT_TRUE(priceHttp.price.has_value()); if (priceHttp.price) { EXPECT_NEAR(priceHttp.price->market.price_usd, 0.75, 0.00000001); EXPECT_NEAR(priceHttp.price->market.price_btc, 0.00002, 0.00000001); } EXPECT_EQ(priceHttp.errorMessage, std::string()); Refresh::PriceHttpResponse priceHttpStatus; priceHttpStatus.transportOk = true; priceHttpStatus.httpStatus = 429; auto priceHttpStatusResult = Refresh::parsePriceHttpResponse(priceHttpStatus, 0); EXPECT_FALSE(priceHttpStatusResult.price.has_value()); EXPECT_EQ(priceHttpStatusResult.errorMessage, std::string("Price fetch failed: OK (HTTP 429)")); Refresh::PriceHttpResponse priceHttpTransport; priceHttpTransport.transportOk = false; priceHttpTransport.httpStatus = 0; priceHttpTransport.transportError = "timeout"; auto priceHttpTransportResult = Refresh::parsePriceHttpResponse(priceHttpTransport, 0); EXPECT_FALSE(priceHttpTransportResult.price.has_value()); EXPECT_EQ(priceHttpTransportResult.errorMessage, std::string("Price fetch failed: timeout (HTTP 0)")); Refresh::PriceHttpResponse priceHttpParse; priceHttpParse.transportOk = true; priceHttpParse.httpStatus = 200; priceHttpParse.body = R"({"other-coin":{"usd":1.0}})"; auto priceHttpParseResult = Refresh::parsePriceHttpResponse(priceHttpParse, 0); EXPECT_FALSE(priceHttpParseResult.price.has_value()); EXPECT_EQ(priceHttpParseResult.errorMessage, std::string("Price fetch returned an unrecognized response")); Refresh::AddressRefreshResult addresses; dragonx::AddressInfo zAddr; zAddr.address = "zs-refresh"; zAddr.type = "shielded"; zAddr.balance = 2.0; zAddr.has_spending_key = false; dragonx::AddressInfo tAddr; tAddr.address = "R-refresh"; tAddr.type = "transparent"; tAddr.balance = 3.0; addresses.shieldedAddresses.push_back(zAddr); addresses.transparentAddresses.push_back(tAddr); Refresh::applyAddressRefreshResult(state, std::move(addresses)); EXPECT_EQ(state.z_addresses.size(), static_cast(1)); EXPECT_EQ(state.t_addresses.size(), static_cast(1)); EXPECT_EQ(state.z_addresses[0].address, std::string("zs-refresh")); EXPECT_FALSE(state.z_addresses[0].has_spending_key); auto viewEntry = Refresh::parseViewTransactionCacheEntry(json{ {"spends", json::array({json{{"address", "zs-from"}}})}, {"outputs", json::array({ json{{"outgoing", true}, {"address", "zs-destination"}, {"value", 0.75}, {"memoStr", "memo"}}, json{{"outgoing", false}, {"address", "zs-change"}, {"value", 0.25}} })} }); EXPECT_EQ(viewEntry.from_address, std::string("zs-from")); EXPECT_EQ(viewEntry.outgoing_outputs.size(), static_cast(1)); EXPECT_EQ(viewEntry.outgoing_outputs[0].address, std::string("zs-destination")); std::vector enrichedTxs; dragonx::TransactionInfo baseTx; baseTx.txid = "shielded-send"; baseTx.type = "receive"; baseTx.confirmations = 7; baseTx.timestamp = 111; enrichedTxs.push_back(baseTx); Refresh::appendViewTransactionOutputs(enrichedTxs, "shielded-send", viewEntry); Refresh::appendViewTransactionOutputs(enrichedTxs, "shielded-send", viewEntry); EXPECT_EQ(enrichedTxs.size(), static_cast(2)); EXPECT_EQ(enrichedTxs[1].type, std::string("send")); EXPECT_EQ(enrichedTxs[1].from_address, std::string("zs-from")); EXPECT_NEAR(enrichedTxs[1].amount, -0.75, 0.00000001); EXPECT_EQ(enrichedTxs[1].confirmations, 7); EXPECT_EQ(enrichedTxs[1].timestamp, static_cast(111)); Refresh::TransactionRefreshResult transactionResult; transactionResult.blockHeight = 222; dragonx::TransactionInfo confirmedTx; confirmedTx.txid = "confirmed"; confirmedTx.type = "receive"; confirmedTx.confirmations = 12; confirmedTx.timestamp = 1000; dragonx::TransactionInfo pendingTx; pendingTx.txid = "pending"; pendingTx.type = "receive"; pendingTx.confirmations = 2; pendingTx.timestamp = 1001; transactionResult.transactions = {pendingTx, confirmedTx}; transactionResult.newViewTxEntries["shielded-send"] = viewEntry; transactionResult.newViewTxEntries["pending-empty"] = Refresh::TransactionViewCacheEntry{}; Refresh::TransactionViewCache viewCache; std::unordered_set sendTxids{"shielded-send", "pending", "pending-empty"}; std::vector confirmedCache; std::unordered_set confirmedIds; int confirmedBlock = -1; int lastTxBlock = -1; Refresh::TransactionCacheUpdate cacheUpdate{ viewCache, sendTxids, confirmedCache, confirmedIds, confirmedBlock, lastTxBlock }; Refresh::applyTransactionRefreshResult(state, cacheUpdate, std::move(transactionResult), 4567); EXPECT_EQ(state.transactions.size(), static_cast(2)); EXPECT_EQ(state.last_tx_update, static_cast(4567)); EXPECT_EQ(lastTxBlock, 222); EXPECT_EQ(viewCache.size(), static_cast(2)); EXPECT_EQ(sendTxids.count("shielded-send"), static_cast(0)); EXPECT_EQ(sendTxids.count("pending"), static_cast(1)); EXPECT_EQ(sendTxids.count("pending-empty"), static_cast(1)); EXPECT_EQ(confirmedCache.size(), static_cast(1)); EXPECT_EQ(confirmedCache[0].txid, std::string("confirmed")); EXPECT_EQ(confirmedIds.count("confirmed"), static_cast(1)); EXPECT_EQ(confirmedBlock, 222); } void testOperationStatusPollParsing() { using Refresh = dragonx::services::NetworkRefreshService; using nlohmann::json; auto parsed = Refresh::parseOperationStatusPoll(json::array({ json{{"id", "op-success"}, {"status", "success"}, {"result", json{{"txid", "tx-success"}}}}, json{{"id", "op-failed"}, {"status", "failed"}, {"error", json{{"message", "bad memo"}}}}, json{{"id", "op-running"}, {"status", "executing"}} }), {"op-success", "op-failed", "op-running", "op-stale"}); EXPECT_TRUE(parsed.anySuccess); EXPECT_EQ(parsed.doneOpids.size(), static_cast(2)); EXPECT_EQ(parsed.doneOpids[0], std::string("op-success")); EXPECT_EQ(parsed.doneOpids[1], std::string("op-failed")); EXPECT_EQ(parsed.successTxids.size(), static_cast(1)); EXPECT_EQ(parsed.successTxids[0], std::string("tx-success")); EXPECT_EQ(parsed.successTxidsByOpid.size(), static_cast(1)); EXPECT_EQ(parsed.successTxidsByOpid.at("op-success"), std::string("tx-success")); EXPECT_EQ(parsed.failureMessages.size(), static_cast(1)); EXPECT_EQ(parsed.failureMessages[0], std::string("bad memo")); EXPECT_EQ(parsed.staleOpids.size(), static_cast(1)); EXPECT_EQ(parsed.staleOpids[0], std::string("op-stale")); auto malformed = Refresh::parseOperationStatusPoll(json{{"status", "success"}}, {"op-keep"}); EXPECT_FALSE(malformed.anySuccess); EXPECT_TRUE(malformed.doneOpids.empty()); EXPECT_TRUE(malformed.staleOpids.empty()); } void testWalletSecurityController() { using dragonx::services::WalletSecurityController; using PinError = WalletSecurityController::PinValidationError; WalletSecurityController controller; EXPECT_FALSE(controller.hasDeferredEncryption()); controller.beginDeferredEncryption("secret-passphrase", "1234"); EXPECT_TRUE(controller.hasDeferredEncryption()); auto snapshot = controller.deferredEncryption(); EXPECT_EQ(snapshot.passphrase, std::string("secret-passphrase")); EXPECT_EQ(snapshot.pin, std::string("1234")); EXPECT_TRUE(controller.shouldAttemptDeferredConnect(0.0)); EXPECT_FALSE(controller.shouldAttemptDeferredConnect(1.0)); EXPECT_TRUE(controller.shouldAttemptDeferredConnect(3.1)); controller.clearDeferredEncryption(); EXPECT_FALSE(controller.hasDeferredEncryption()); EXPECT_FALSE(controller.shouldAttemptDeferredConnect(10.0)); auto valid = WalletSecurityController::validatePinSetup("1234", "1234"); EXPECT_TRUE(valid.ok); auto emptyAllowed = WalletSecurityController::validatePinSetup("", "", true); EXPECT_TRUE(emptyAllowed.ok); auto mismatch = WalletSecurityController::validatePinSetup("1234", "4321"); EXPECT_EQ(mismatch.error, PinError::Mismatch); auto shortPin = WalletSecurityController::validatePinSetup("123", "123"); EXPECT_EQ(shortPin.error, PinError::TooShort); auto nondigit = WalletSecurityController::validatePinSetup("12a4", "12a4"); EXPECT_EQ(nondigit.error, PinError::NonDigit); EXPECT_EQ(WalletSecurityController::classifyAddress("zs123"), WalletSecurityController::KeyKind::Shielded); EXPECT_EQ(WalletSecurityController::classifyAddress("R123"), WalletSecurityController::KeyKind::Transparent); EXPECT_EQ(WalletSecurityController::classifyPrivateKey("secret-key"), WalletSecurityController::KeyKind::Shielded); EXPECT_EQ(WalletSecurityController::classifyPrivateKey("Kx123"), WalletSecurityController::KeyKind::Transparent); EXPECT_EQ(std::string(WalletSecurityController::importSuccessMessage(WalletSecurityController::KeyKind::Shielded)), std::string("Z-address key imported successfully. Wallet is rescanning.")); EXPECT_EQ(WalletSecurityController::decryptExportFileName(42), std::string("obsidiandecryptexport42")); MockWalletSecurityRpc rpc; MockWalletSecurityVault vault; auto encrypted = controller.runDeferredEncryption({"passphrase", "9876"}, rpc, &vault); EXPECT_TRUE(encrypted.encrypted); EXPECT_TRUE(encrypted.pinProvided); EXPECT_TRUE(encrypted.pinStored); EXPECT_TRUE(encrypted.restartRequired); EXPECT_EQ(rpc.encryptCalls, 1); EXPECT_EQ(rpc.lastPassphrase, std::string("passphrase")); EXPECT_EQ(vault.storeCalls, 1); EXPECT_EQ(vault.lastPin, std::string("9876")); EXPECT_EQ(vault.lastPassphrase, std::string("passphrase")); MockWalletSecurityRpc failingRpc; MockWalletSecurityVault unusedVault; failingRpc.encryptResult = false; failingRpc.encryptError = "rpc unavailable"; auto failed = controller.runDeferredEncryption({"bad-passphrase", "1111"}, failingRpc, &unusedVault); EXPECT_FALSE(failed.encrypted); EXPECT_TRUE(failed.pinProvided); EXPECT_FALSE(failed.pinStored); EXPECT_FALSE(failed.restartRequired); EXPECT_EQ(failed.error, std::string("rpc unavailable")); EXPECT_EQ(unusedVault.storeCalls, 0); } void testWalletSecurityWorkflow() { using dragonx::services::WalletSecurityWorkflow; using DecryptPhase = WalletSecurityWorkflow::DecryptPhase; using DecryptStep = WalletSecurityWorkflow::DecryptStep; WalletSecurityWorkflow workflow; auto start = std::chrono::steady_clock::now(); EXPECT_EQ(workflow.phase(), DecryptPhase::PassphraseEntry); EXPECT_TRUE(workflow.canClose()); workflow.start(start); EXPECT_EQ(workflow.phase(), DecryptPhase::Working); EXPECT_EQ(workflow.step(), DecryptStep::Unlock); EXPECT_FALSE(workflow.canClose()); EXPECT_TRUE(workflow.inProgress()); workflow.advanceTo(DecryptStep::BackupWallet, WalletSecurityWorkflow::stepStatus(DecryptStep::BackupWallet), start + std::chrono::seconds(2)); EXPECT_EQ(workflow.step(), DecryptStep::BackupWallet); EXPECT_EQ(workflow.status(), std::string("Backing up encrypted wallet...")); EXPECT_TRUE(WalletSecurityWorkflow::stepIsComplete(workflow.step(), DecryptStep::StopDaemon)); EXPECT_FALSE(WalletSecurityWorkflow::stepIsComplete(workflow.step(), DecryptStep::RestartDaemon)); workflow.closeDialogForImport(); EXPECT_FALSE(workflow.inProgress()); EXPECT_TRUE(workflow.importActive()); workflow.finishImport(); EXPECT_FALSE(workflow.importActive()); workflow.fail("Daemon failed to restart"); EXPECT_EQ(workflow.phase(), DecryptPhase::Error); EXPECT_TRUE(workflow.canClose()); EXPECT_EQ(workflow.status(), std::string("Daemon failed to restart")); workflow.failEntry("Incorrect passphrase"); EXPECT_EQ(workflow.phase(), DecryptPhase::PassphraseEntry); EXPECT_FALSE(workflow.inProgress()); EXPECT_EQ(workflow.status(), std::string("Incorrect passphrase")); auto plan = WalletSecurityWorkflow::planWalletFiles("/tmp/dragonx/", 1234); EXPECT_EQ(plan.exportFile, std::string("obsidiandecryptexport1234")); EXPECT_EQ(plan.exportPath, std::string("/tmp/dragonx/obsidiandecryptexport1234")); EXPECT_EQ(plan.walletPath, std::string("/tmp/dragonx/wallet.dat")); EXPECT_EQ(plan.backupPath, std::string("/tmp/dragonx/wallet.dat.encrypted.bak")); } void testWalletSecurityWorkflowExecutor() { using Executor = dragonx::services::WalletSecurityWorkflowExecutor; MockWorkflowRpc rpc; auto unlock = Executor::unlockWallet("passphrase", rpc); EXPECT_TRUE(unlock.ok); EXPECT_EQ(rpc.unlockCalls, 1); EXPECT_EQ(rpc.lastPassphrase, std::string("passphrase")); rpc.unlockResult = false; rpc.unlockError = "bad passphrase"; auto failedUnlock = Executor::unlockWallet("bad", rpc); EXPECT_FALSE(failedUnlock.ok); EXPECT_TRUE(failedUnlock.passphraseRejected); EXPECT_EQ(failedUnlock.error, std::string("bad passphrase")); MockWorkflowFiles files; auto exported = Executor::exportWallet(rpc, files, 77); EXPECT_TRUE(exported.ok); EXPECT_EQ(exported.filePlan.exportFile, std::string("obsidiandecryptexport77")); EXPECT_EQ(exported.filePlan.exportPath, std::string("/tmp/dragonx/obsidiandecryptexport77")); EXPECT_EQ(rpc.lastExportFile, std::string("obsidiandecryptexport77")); rpc.exportResult = false; rpc.exportError = "disk full"; auto failedExport = Executor::exportWallet(rpc, files, 78); EXPECT_FALSE(failedExport.ok); EXPECT_EQ(failedExport.error, std::string("Export failed: disk full")); rpc.stopResult = false; auto stop = Executor::stopDaemon(rpc); EXPECT_TRUE(stop.ok); EXPECT_EQ(rpc.stopCalls, 1); auto backup = Executor::backupEncryptedWallet(files, exported.filePlan); EXPECT_TRUE(backup.ok); EXPECT_EQ(files.backupCalls, 1); files.backupResult = false; files.backupError = "permission denied"; auto failedBackup = Executor::backupEncryptedWallet(files, exported.filePlan); EXPECT_FALSE(failedBackup.ok); EXPECT_EQ(failedBackup.error, std::string("Failed to rename wallet.dat: permission denied")); MockWorkflowDaemon daemon; rpc.probeCalls = 0; rpc.probeSuccessOnCall = 3; auto restart = Executor::restartDaemonAndWait(daemon, rpc, 200, 100, 5); EXPECT_TRUE(restart.ok); EXPECT_EQ(daemon.stopCalls, 1); EXPECT_EQ(daemon.startCalls, 1); EXPECT_EQ(rpc.probeCalls, 3); MockWorkflowDaemon cancelledDaemon; cancelledDaemon.cancelAfterSleeps = 1; rpc.probeCalls = 0; auto cancelled = Executor::restartDaemonAndWait(cancelledDaemon, rpc, 200, 100, 5); EXPECT_FALSE(cancelled.ok); EXPECT_TRUE(cancelled.error.empty()); MockWorkflowDaemon failedDaemon; rpc.probeCalls = 0; rpc.probeSuccessOnCall = 99; auto failedRestart = Executor::restartDaemonAndWait(failedDaemon, rpc, 0, 0, 2); EXPECT_FALSE(failedRestart.ok); EXPECT_EQ(failedRestart.error, std::string("Daemon failed to restart")); MockWorkflowImporter importer; auto imported = Executor::importWallet(importer, exported.filePlan.exportPath); EXPECT_TRUE(imported.ok); EXPECT_EQ(importer.importCalls, 1); EXPECT_EQ(importer.lastExportPath, exported.filePlan.exportPath); importer.result = false; importer.importError = "rescan failed"; auto failedImport = Executor::importWallet(importer, exported.filePlan.exportPath); EXPECT_FALSE(failedImport.ok); EXPECT_EQ(failedImport.error, std::string("Key import failed: rescan failed")); int cleanupCalls = 0; Executor::cleanupVaultAndPin([&]() { ++cleanupCalls; }); EXPECT_EQ(cleanupCalls, 1); } void testDaemonShutdownPolicy() { using dragonx::daemon::DaemonController; using ShutdownAction = DaemonController::ShutdownAction; auto noDaemon = DaemonController::evaluateShutdownPolicy(false, false, false, false); EXPECT_EQ(noDaemon.action, ShutdownAction::DisconnectOnly); auto keepRunning = DaemonController::evaluateShutdownPolicy(true, false, true, false); EXPECT_EQ(keepRunning.action, ShutdownAction::DisconnectOnly); EXPECT_EQ(std::string(keepRunning.logReason), std::string("keep_daemon_running enabled")); auto externalPreserved = DaemonController::evaluateShutdownPolicy(true, true, false, false); EXPECT_EQ(externalPreserved.action, ShutdownAction::DisconnectOnly); EXPECT_EQ(std::string(externalPreserved.logReason), std::string("external daemon (not ours to stop)")); auto externalStopAllowed = DaemonController::evaluateShutdownPolicy(true, true, false, true); EXPECT_EQ(externalStopAllowed.action, ShutdownAction::StopDaemon); auto ownedDaemon = DaemonController::evaluateShutdownPolicy(true, false, false, false); EXPECT_EQ(ownedDaemon.action, ShutdownAction::StopDaemon); auto restart = DaemonController::evaluateLifecycleOperation( DaemonController::LifecycleOperation::ManualRestart, true, true, true, false); EXPECT_TRUE(restart.allowed); EXPECT_TRUE(restart.wasRunning); EXPECT_TRUE(restart.resetCrashCount); EXPECT_TRUE(restart.disconnectRpc); EXPECT_EQ(restart.restartDelayMs, 500); EXPECT_EQ(std::string(restart.taskName), std::string("daemon-restart")); auto blockedRestart = DaemonController::evaluateLifecycleOperation( DaemonController::LifecycleOperation::ManualRestart, true, true, true, true); EXPECT_FALSE(blockedRestart.allowed); auto externalRescan = DaemonController::evaluateLifecycleOperation( DaemonController::LifecycleOperation::Rescan, false, false, false, false); EXPECT_FALSE(externalRescan.allowed); EXPECT_EQ(std::string(externalRescan.warning), std::string("Rescan requires embedded daemon. Restart your daemon with -rescan manually.")); auto deleteData = DaemonController::evaluateLifecycleOperation( DaemonController::LifecycleOperation::DeleteBlockchainData, true, true, false, false); EXPECT_TRUE(deleteData.allowed); EXPECT_FALSE(deleteData.wasRunning); EXPECT_EQ(deleteData.restartDelayMs, 3000); auto bootstrap = DaemonController::evaluateLifecycleOperation( DaemonController::LifecycleOperation::BootstrapStop, false, false, true, false); EXPECT_TRUE(bootstrap.allowed); EXPECT_TRUE(bootstrap.wasRunning); EXPECT_TRUE(bootstrap.disconnectRpc); } void testDaemonLifecycleExecution() { using dragonx::daemon::DaemonController; auto restart = DaemonController::evaluateLifecycleOperation( DaemonController::LifecycleOperation::ManualRestart, true, true, true, false); MockDaemonLifecycleRuntime restartRuntime; MockDaemonLifecycleTask restartTask; auto restartResult = DaemonController::executeLifecycleOperation(restart, restartRuntime, restartTask); EXPECT_TRUE(restartResult.completed); EXPECT_TRUE(restartResult.stopped); EXPECT_TRUE(restartResult.started); EXPECT_EQ(restartTask.sleeps, 5); EXPECT_TRUE(restartRuntime.calls == std::vector({"stop", "start"})); auto rescan = DaemonController::evaluateLifecycleOperation( DaemonController::LifecycleOperation::Rescan, true, true, true, false); MockDaemonLifecycleRuntime rescanRuntime; MockDaemonLifecycleTask rescanTask; auto rescanResult = DaemonController::executeLifecycleOperation(rescan, rescanRuntime, rescanTask); EXPECT_TRUE(rescanResult.completed); EXPECT_EQ(rescanTask.sleeps, 30); EXPECT_TRUE(rescanRuntime.calls == std::vector({"stop", "reset-output", "start"})); auto deleteData = DaemonController::evaluateLifecycleOperation( DaemonController::LifecycleOperation::DeleteBlockchainData, true, true, false, false); MockDaemonLifecycleRuntime deleteRuntime; MockDaemonLifecycleTask deleteTask; auto deleteResult = DaemonController::executeLifecycleOperation(deleteData, deleteRuntime, deleteTask); EXPECT_TRUE(deleteResult.completed); EXPECT_EQ(deleteResult.deletedItems, 7); EXPECT_TRUE(deleteRuntime.calls == std::vector({"stop", "delete", "reset-output", "start"})); auto bootstrap = DaemonController::evaluateLifecycleOperation( DaemonController::LifecycleOperation::BootstrapStop, true, true, true, false); MockDaemonLifecycleRuntime bootstrapRuntime; MockDaemonLifecycleTask bootstrapTask; auto bootstrapResult = DaemonController::executeLifecycleOperation(bootstrap, bootstrapRuntime, bootstrapTask); EXPECT_TRUE(bootstrapResult.completed); EXPECT_TRUE(bootstrapResult.stopped); EXPECT_TRUE(bootstrapRuntime.calls == std::vector({"rpc-stop-disconnect"})); EXPECT_EQ(bootstrapRuntime.rpcStopContext, std::string("Bootstrap daemon stop")); EXPECT_EQ(bootstrapRuntime.disconnectReason, std::string("Bootstrap")); MockDaemonLifecycleRuntime cancelRuntime; MockDaemonLifecycleTask cancelTask; cancelTask.cancelAfterSleeps = 1; auto cancelResult = DaemonController::executeLifecycleOperation(rescan, cancelRuntime, cancelTask); EXPECT_TRUE(cancelResult.cancelled); EXPECT_FALSE(cancelResult.started); EXPECT_TRUE(cancelRuntime.calls == std::vector({"stop"})); MockDaemonLifecycleRuntime shutdownRuntime; MockDaemonLifecycleTask shutdownTask; shutdownTask.isShuttingDown = true; auto shutdownResult = DaemonController::executeLifecycleOperation(restart, shutdownRuntime, shutdownTask); EXPECT_TRUE(shutdownResult.cancelled); EXPECT_TRUE(shutdownResult.stopped); EXPECT_FALSE(shutdownResult.started); EXPECT_TRUE(shutdownRuntime.calls == std::vector({"stop"})); } void testDaemonLifecycleAdapters() { using dragonx::daemon::AsyncLifecycleTaskContext; using dragonx::daemon::BlockchainDataCleaner; using dragonx::daemon::ImmediateLifecycleTaskContext; using dragonx::util::AsyncTaskManager; auto cancelled = std::make_shared>(false); std::atomic shuttingDown{false}; AsyncTaskManager::Token token(cancelled); AsyncLifecycleTaskContext asyncContext(token, shuttingDown); EXPECT_FALSE(asyncContext.cancelled()); EXPECT_FALSE(asyncContext.shuttingDown()); cancelled->store(true); EXPECT_TRUE(asyncContext.cancelled()); shuttingDown.store(true); EXPECT_TRUE(asyncContext.shuttingDown()); ImmediateLifecycleTaskContext immediateContext; EXPECT_FALSE(immediateContext.cancelled()); EXPECT_FALSE(immediateContext.shuttingDown()); immediateContext.sleepForMs(50); fs::path dataDir = makeTempDir(); fs::create_directories(dataDir / "blocks" / "nested"); fs::create_directories(dataDir / "chainstate"); fs::create_directories(dataDir / "database"); fs::create_directories(dataDir / "notarizations"); std::ofstream(dataDir / "blocks" / "nested" / "blk00000.dat") << "block"; std::ofstream(dataDir / "chainstate" / "state.dat") << "state"; std::ofstream(dataDir / "database" / "db.dat") << "db"; std::ofstream(dataDir / "notarizations" / "notary.dat") << "notary"; std::ofstream(dataDir / "peers.dat") << "peers"; std::ofstream(dataDir / "fee_estimates.dat") << "fees"; std::ofstream(dataDir / "wallet.dat") << "wallet"; int removed = BlockchainDataCleaner::removeBlockchainData(dataDir); EXPECT_TRUE(removed >= 6); EXPECT_FALSE(fs::exists(dataDir / "blocks")); EXPECT_FALSE(fs::exists(dataDir / "chainstate")); EXPECT_FALSE(fs::exists(dataDir / "database")); EXPECT_FALSE(fs::exists(dataDir / "notarizations")); EXPECT_FALSE(fs::exists(dataDir / "peers.dat")); EXPECT_FALSE(fs::exists(dataDir / "fee_estimates.dat")); EXPECT_TRUE(fs::exists(dataDir / "wallet.dat")); fs::remove_all(dataDir); } void testRendererHelpers() { EXPECT_NEAR(dragonx::ui::ComputeConsoleInputHeight(20.0f, 4.0f, 8.0f, 2.0f, 1.0f), 43.0, 0.0001); EXPECT_NEAR(dragonx::ui::ComputeConsoleOutputHeight(200.0f, 50.0f, 120.0f, 0.75f), 150.0, 0.0001); EXPECT_NEAR(dragonx::ui::ComputeConsoleOutputHeight(120.0f, 80.0f, 70.0f, 0.5f), 70.0, 0.0001); EXPECT_NEAR(dragonx::ui::ClampConsoleWrapWidth(40.0f, 8.0f), 50.0, 0.0001); EXPECT_NEAR(dragonx::ui::ClampConsoleWrapWidth(200.0f, 12.0f), 176.0, 0.0001); EXPECT_EQ(dragonx::ui::ClampMiningThreads(0, 8), 1); EXPECT_EQ(dragonx::ui::ClampMiningThreads(12, 8), 8); EXPECT_EQ(dragonx::ui::ClampMiningThreads(4, 8), 4); EXPECT_TRUE(dragonx::ui::IsPoolMiningActive(true, true, false)); EXPECT_FALSE(dragonx::ui::IsPoolMiningActive(true, false, true)); EXPECT_TRUE(dragonx::ui::IsPoolMiningActive(false, false, true)); dragonx::ui::ConsoleOutputFilter filter{"error", false, false, false, 10, 20, 30}; EXPECT_TRUE(dragonx::ui::consoleLinePassesFilter("RPC Error", 20, filter)); EXPECT_FALSE(dragonx::ui::consoleLinePassesFilter("daemon line", 10, filter)); filter.text.clear(); EXPECT_FALSE(dragonx::ui::consoleLinePassesFilter("[rpc] History -> listtransactions", 30, filter)); filter.rpcTraceEnabled = true; EXPECT_TRUE(dragonx::ui::consoleLinePassesFilter("[rpc] History -> listtransactions", 30, filter)); filter.daemonMessagesEnabled = true; filter.errorsOnly = true; filter.text.clear(); EXPECT_TRUE(dragonx::ui::consoleLinePassesFilter("error line", 20, filter)); EXPECT_FALSE(dragonx::ui::consoleLinePassesFilter("info line", 30, filter)); std::vector poolAddresses; poolAddresses.push_back({"R-transparent", 0.0, "transparent", true}); poolAddresses.push_back({"zs-default-worker", 0.0, "shielded", true}); EXPECT_TRUE(dragonx::ui::shouldDefaultPoolWorker("", false)); EXPECT_TRUE(dragonx::ui::shouldDefaultPoolWorker("x", false)); EXPECT_FALSE(dragonx::ui::shouldDefaultPoolWorker("zs-manual", false)); EXPECT_FALSE(dragonx::ui::shouldDefaultPoolWorker("", true)); EXPECT_EQ(dragonx::ui::defaultPoolWorkerAddress(poolAddresses), std::string("zs-default-worker")); EXPECT_TRUE(dragonx::ui::miningValueAlreadySaved({"pool-a", "pool-b"}, "pool-b")); EXPECT_FALSE(dragonx::ui::miningValueAlreadySaved({"pool-a"}, "")); EXPECT_EQ(std::string(dragonx::ui::defaultPoolUrl()), std::string("pool.dragonx.is:3433")); dragonx::TransactionInfo tx; tx.type = "send"; tx.amount = -1.25; tx.address = "zsabcdefghijklmnopqrstuvwxyz"; auto txDisplay = dragonx::ui::buildRecentTxDisplay(tx, 12); EXPECT_EQ(txDisplay.typeText, std::string("Sent")); EXPECT_EQ(txDisplay.addressText, std::string("zsab...wxyz")); EXPECT_EQ(txDisplay.amountText, std::string("-1.2500 DRGX")); EXPECT_EQ(txDisplay.timeText, std::string("")); } void testConsoleInputModel() { std::vector history; dragonx::ui::AppendConsoleHistory(history, "getinfo", 3); dragonx::ui::AppendConsoleHistory(history, "getinfo", 3); dragonx::ui::AppendConsoleHistory(history, "getpeerinfo", 3); dragonx::ui::AppendConsoleHistory(history, "getblockcount", 3); dragonx::ui::AppendConsoleHistory(history, "help", 3); EXPECT_EQ(history.size(), static_cast(3)); EXPECT_EQ(history.front(), std::string("getpeerinfo")); int index = dragonx::ui::NavigateConsoleHistoryIndex(-1, history.size(), true); EXPECT_EQ(index, 2); EXPECT_EQ(dragonx::ui::ConsoleHistoryEntry(history, index), std::string("help")); index = dragonx::ui::NavigateConsoleHistoryIndex(index, history.size(), true); EXPECT_EQ(index, 1); index = dragonx::ui::NavigateConsoleHistoryIndex(index, history.size(), false); EXPECT_EQ(index, 2); index = dragonx::ui::NavigateConsoleHistoryIndex(index, history.size(), false); EXPECT_EQ(index, -1); auto single = dragonx::ui::CompleteConsoleCommand("stop"); EXPECT_EQ(single.matches.size(), static_cast(1)); EXPECT_EQ(single.commonPrefix, std::string("stop")); auto multiple = dragonx::ui::CompleteConsoleCommand("getblock"); EXPECT_TRUE(multiple.matches.size() > 1); EXPECT_EQ(multiple.commonPrefix, std::string("getblock")); auto lines = dragonx::ui::FormatConsoleCompletionLines(multiple.matches, 40); EXPECT_FALSE(lines.empty()); auto args = dragonx::ui::ParseConsoleCommandArgs("z_sendmany \"from addr\" [{\"address\":\"zs\",\"amount\":1.25}] true"); EXPECT_EQ(args.size(), static_cast(4)); EXPECT_EQ(args[1], std::string("from addr")); auto call = dragonx::ui::BuildConsoleRpcCall("setgenerate true 4"); EXPECT_TRUE(call.valid); EXPECT_EQ(call.method, std::string("setgenerate")); EXPECT_EQ(call.params.size(), static_cast(2)); EXPECT_TRUE(call.params[0].get()); EXPECT_EQ(call.params[1].get(), 4LL); auto resultLines = dragonx::ui::FormatConsoleRpcResultLines("{\n \"balance\": 12,\n \"ok\": true\n}", false); EXPECT_EQ(resultLines.size(), static_cast(4)); EXPECT_EQ(resultLines[0].role, dragonx::ui::ConsoleResultLineRole::JsonBrace); EXPECT_EQ(resultLines[1].role, dragonx::ui::ConsoleResultLineRole::JsonKey); auto nullLines = dragonx::ui::FormatConsoleRpcResultLines("null", false); EXPECT_EQ(nullLines[0].text, std::string("(no result)")); auto errorLines = dragonx::ui::FormatConsoleRpcResultLines("bad rpc", true); EXPECT_EQ(errorLines[0].role, dragonx::ui::ConsoleResultLineRole::Error); EXPECT_EQ(errorLines[0].text, std::string("Error: bad rpc")); } void testMiningBenchmarkModel() { dragonx::ui::ThreadBenchmark benchmark; EXPECT_FALSE(benchmark.active()); benchmark.buildCandidates(8); EXPECT_EQ(benchmark.candidates.size(), static_cast(5)); EXPECT_EQ(benchmark.candidates.front(), 4); EXPECT_EQ(benchmark.candidates.back(), 8); EXPECT_NEAR(benchmark.avgWarmupSecs(), 195.0, 0.0001); benchmark.phase = dragonx::ui::ThreadBenchmark::Phase::WarmingUp; EXPECT_TRUE(benchmark.active()); benchmark.current_index = 2; benchmark.total_warmup_secs = 210.0f; EXPECT_NEAR(benchmark.avgWarmupSecs(), 105.0, 0.0001); EXPECT_NEAR(benchmark.perTestSecs(), 135.0, 0.0001); benchmark.phase_timer = 15.0f; EXPECT_TRUE(benchmark.progress() > 0.0f); benchmark.resetStabilityTracking(); EXPECT_EQ(benchmark.window_samples, 0); EXPECT_EQ(benchmark.consecutive_stable, 0); benchmark.phase = dragonx::ui::ThreadBenchmark::Phase::Starting; benchmark.candidates = {2}; benchmark.current_index = 0; auto update = dragonx::ui::AdvanceThreadBenchmark(benchmark, 0.1f, 0.0); EXPECT_TRUE(update.stopPoolMining); EXPECT_TRUE(update.startPoolMining); EXPECT_EQ(update.startThreads, 2); EXPECT_TRUE(benchmark.phase == dragonx::ui::ThreadBenchmark::Phase::WarmingUp); benchmark.phase_timer = dragonx::ui::ThreadBenchmark::MAX_WARMUP_SECS - 0.5f; update = dragonx::ui::AdvanceThreadBenchmark(benchmark, 1.0f, 100.0); EXPECT_TRUE(benchmark.phase == dragonx::ui::ThreadBenchmark::Phase::Measuring); benchmark.phase_timer = dragonx::ui::ThreadBenchmark::MEASURE_SECS - 0.5f; update = dragonx::ui::AdvanceThreadBenchmark(benchmark, 1.0f, 250.0); EXPECT_TRUE(benchmark.phase == dragonx::ui::ThreadBenchmark::Phase::Advancing); EXPECT_EQ(benchmark.optimal_threads, 2); benchmark.was_pool_running = true; update = dragonx::ui::AdvanceThreadBenchmark(benchmark, 0.1f, 0.0); EXPECT_TRUE(update.stopPoolMining); EXPECT_TRUE(update.saveOptimalThreads); EXPECT_TRUE(update.startPoolMining); EXPECT_EQ(update.optimalThreads, 2); benchmark.reset(); EXPECT_FALSE(benchmark.active()); EXPECT_TRUE(benchmark.candidates.empty()); } void testBalanceAddressListModel() { std::vector addresses; dragonx::AddressInfo zHigh; zHigh.address = "zs-high"; zHigh.label = "Vault"; zHigh.balance = 10.0; zHigh.type = "shielded"; addresses.push_back(zHigh); dragonx::AddressInfo tZero; tZero.address = "R-zero"; tZero.label = "Empty"; tZero.balance = 0.0; tZero.type = "transparent"; addresses.push_back(tZero); dragonx::AddressInfo tFav; tFav.address = "R-favorite"; tFav.label = "Favorite"; tFav.balance = 0.0; tFav.type = "transparent"; addresses.push_back(tFav); std::vector inputs = { {&addresses[0], true, false, false, false, "Main Vault", "", -1}, {&addresses[1], false, false, false, false, "Empty", "", -1}, {&addresses[2], false, false, true, false, "Favorite", "", -1} }; auto rows = dragonx::ui::BuildAddressListRows(inputs, "", true, false); EXPECT_EQ(rows.size(), static_cast(2)); EXPECT_EQ(rows[0].info->address, std::string("R-favorite")); EXPECT_EQ(rows[1].info->address, std::string("zs-high")); inputs[2].sortOrder = 0; rows = dragonx::ui::BuildAddressListRows(inputs, "", true, false); EXPECT_EQ(rows[0].info->address, std::string("R-favorite")); rows = dragonx::ui::BuildAddressListRows(inputs, "vault", false, false); EXPECT_EQ(rows.size(), static_cast(1)); EXPECT_EQ(rows[0].info->address, std::string("zs-high")); auto layout = dragonx::ui::ComputeAddressRowLayout(10.0f, 20.0f, 300.0f, 50.0f, 12.0f, 16.0f, 6.0f, 4.0f, 2.0f); EXPECT_NEAR(layout.contentStartX, 22.0, 0.0001); EXPECT_NEAR(layout.contentStartY, 26.0, 0.0001); EXPECT_NEAR(layout.buttonSize, 38.0, 0.0001); EXPECT_NEAR(layout.favoriteButton.x, 270.0, 0.0001); EXPECT_NEAR(layout.visibilityButton.x, 228.0, 0.0001); EXPECT_NEAR(layout.contentRight, 224.0, 0.0001); EXPECT_EQ(dragonx::ui::FormatAddressUsdValue(2.0, 3.5), std::string("$7.00")); EXPECT_EQ(dragonx::ui::FormatAddressUsdValue(0.001, 2.0), std::string("$0.002000")); EXPECT_EQ(dragonx::ui::FormatAddressUsdValue(0.0, 2.0), std::string("")); } void testExplorerBlockCache() { using dragonx::ui::ExplorerBlockCache; using dragonx::ui::ExplorerBlockSummary; using nlohmann::json; fs::path dir = makeTempDir(); fs::path databasePath = dir / "explorer_blocks.sqlite"; fs::path legacyPath = dir / "explorer_blocks_cache.json"; json legacy = { {"version", 1}, {"tip_height", 10}, {"tip_hash", "hash-10"}, {"blocks", json::array({ json{{"height", 10}, {"hash", "hash-10"}, {"tx_count", 3}, {"size", 1000}, {"time", 5000}, {"difficulty", 1.25}}, json{{"height", 9}, {"hash", "hash-9"}, {"tx_count", 2}, {"size", 900}, {"time", 4900}, {"difficulty", 1.20}} })} }; { std::ofstream file(legacyPath); file << legacy.dump(2); } ExplorerBlockCache cache(databasePath.string(), legacyPath.string()); EXPECT_TRUE(cache.ensureOpen()); EXPECT_EQ(cache.cachedBlockCount(), 2); auto range = cache.loadRange(9, 10); EXPECT_EQ(range.size(), static_cast(2)); EXPECT_EQ(range[10].hash, std::string("hash-10")); EXPECT_EQ(range[9].tx_count, 2); auto sameTipValidation = cache.prepareValidation(10, "hash-10"); EXPECT_FALSE(sameTipValidation.needed); auto advancedValidation = cache.prepareValidation(12, "hash-12"); EXPECT_TRUE(advancedValidation.needed); EXPECT_EQ(advancedValidation.height, 10); EXPECT_EQ(advancedValidation.expectedHash, std::string("hash-10")); cache.applySavedTipValidation(advancedValidation, "hash-10", 12, "hash-12"); ExplorerBlockSummary block12; block12.height = 12; block12.hash = "hash-12"; block12.tx_count = 5; block12.size = 1200; block12.time = 5200; block12.difficulty = 1.40; EXPECT_TRUE(cache.storeBlock(block12)); EXPECT_EQ(cache.loadRange(12, 12)[12].tx_count, 5); { ExplorerBlockCache reopened(databasePath.string(), legacyPath.string()); EXPECT_TRUE(reopened.ensureOpen()); EXPECT_EQ(reopened.cachedBlockCount(), 3); auto noRepeatValidation = reopened.prepareValidation(12, "hash-12"); EXPECT_FALSE(noRepeatValidation.needed); } cache.prepareValidation(12, "different-tip"); EXPECT_EQ(cache.cachedBlockCount(), 0); { ExplorerBlockCache reopened(databasePath.string(), legacyPath.string()); EXPECT_TRUE(reopened.ensureOpen()); EXPECT_EQ(reopened.cachedBlockCount(), 0); } fs::remove_all(dir); } void testTransactionHistoryCache() { using dragonx::TransactionInfo; using dragonx::data::TransactionHistoryCache; std::string identityA = TransactionHistoryCache::walletIdentityFromAddresses( {"zs-beta", "zs-alpha"}, {"R-one"}); std::string identityB = TransactionHistoryCache::walletIdentityFromAddresses( {"zs-alpha", "zs-beta"}, {"R-one"}); std::string identityWithSameText = TransactionHistoryCache::walletIdentityFromAddresses( {"same-address-text"}, {"same-address-text"}); EXPECT_EQ(identityA, identityB); EXPECT_TRUE(identityA.find("protocol=") == std::string::npos); EXPECT_TRUE(identityA.find("p2p_port=") == std::string::npos); EXPECT_TRUE(identityA.find("z:zs-alpha") != std::string::npos); EXPECT_TRUE(identityA.find("t:R-one") != std::string::npos); EXPECT_TRUE(identityWithSameText.find("z:same-address-text") != std::string::npos); EXPECT_TRUE(identityWithSameText.find("t:same-address-text") != std::string::npos); fs::path dir = makeTempDir(); fs::path databasePath = dir / "transaction_history.sqlite"; std::string walletIdentity = "mainnet|R-alpha|zs-beta"; std::string passphrase = "correct horse battery staple"; std::vector transactions; transactions.push_back(TransactionInfo{ "tx-sensitive-send", "send", -1.25, 1700000100, 12, "zs-destination-sensitive", "R-source-sensitive", "private memo" }); transactions.push_back(TransactionInfo{ "tx-mined", "mined", 3.5, 1700000000, 104, "R-mining-address", "", "" }); std::unordered_map shieldedScanHeights{ {"zs-beta", 120}, {"zs-archive", 118} }; { TransactionHistoryCache cache(databasePath.string()); EXPECT_TRUE(cache.ensureOpen()); EXPECT_TRUE(cache.unlockWithPassphrase(walletIdentity, passphrase)); EXPECT_TRUE(cache.replace(walletIdentity, 120, "tip-120", transactions, 1700000200, shieldedScanHeights)); EXPECT_EQ(cache.snapshotCount(), 1); } { std::ifstream database(databasePath, std::ios::binary); std::string bytes((std::istreambuf_iterator(database)), std::istreambuf_iterator()); EXPECT_TRUE(bytes.find("tx-sensitive-send") == std::string::npos); EXPECT_TRUE(bytes.find("zs-destination-sensitive") == std::string::npos); EXPECT_TRUE(bytes.find("zs-archive") == std::string::npos); EXPECT_TRUE(bytes.find("private memo") == std::string::npos); } { TransactionHistoryCache wrongKey(databasePath.string()); EXPECT_TRUE(wrongKey.unlockWithPassphrase(walletIdentity, "wrong passphrase")); auto wrongLoad = wrongKey.load(walletIdentity, 120, "tip-120"); EXPECT_FALSE(wrongLoad.loaded); } { TransactionHistoryCache reopened(databasePath.string()); EXPECT_TRUE(reopened.unlockWithPassphrase(walletIdentity, passphrase)); auto loaded = reopened.load(walletIdentity, 120, "tip-120"); EXPECT_TRUE(loaded.loaded); EXPECT_FALSE(loaded.invalidated); EXPECT_EQ(loaded.tipHeight, 120); EXPECT_EQ(loaded.tipHash, std::string("tip-120")); EXPECT_EQ(loaded.updatedAt, static_cast(1700000200)); EXPECT_EQ(loaded.transactions.size(), static_cast(2)); EXPECT_EQ(loaded.transactions[0].txid, std::string("tx-sensitive-send")); EXPECT_EQ(loaded.transactions[0].memo, std::string("private memo")); EXPECT_EQ(loaded.transactions[1].type, std::string("mined")); EXPECT_EQ(loaded.shieldedScanHeights.size(), static_cast(2)); EXPECT_EQ(loaded.shieldedScanHeights.at("zs-beta"), 120); EXPECT_EQ(loaded.shieldedScanHeights.at("zs-archive"), 118); } { TransactionHistoryCache cache(databasePath.string()); EXPECT_TRUE(cache.unlockWithPassphrase(walletIdentity, passphrase)); EXPECT_TRUE(cache.replace(walletIdentity, 120, "tip-120", transactions, 1700000250)); auto loaded = cache.load(walletIdentity, 120, "tip-120"); EXPECT_TRUE(loaded.loaded); EXPECT_EQ(loaded.shieldedScanHeights.size(), static_cast(0)); } { TransactionHistoryCache staleTip(databasePath.string()); EXPECT_TRUE(staleTip.unlockWithPassphrase(walletIdentity, passphrase)); auto invalidated = staleTip.load(walletIdentity, 119, "tip-119"); EXPECT_FALSE(invalidated.loaded); EXPECT_TRUE(invalidated.invalidated); EXPECT_EQ(staleTip.snapshotCount(), 0); } { TransactionHistoryCache cache(databasePath.string()); EXPECT_TRUE(cache.unlockWithPassphrase(walletIdentity, passphrase)); EXPECT_TRUE(cache.replace(walletIdentity, 120, "tip-120", transactions, 1700000300)); auto invalidated = cache.load(walletIdentity, 120, "different-tip"); EXPECT_FALSE(invalidated.loaded); EXPECT_TRUE(invalidated.invalidated); EXPECT_EQ(cache.snapshotCount(), 0); } fs::remove_all(dir); } void testTransactionHistoryCacheRefreshApply() { using Refresh = dragonx::services::NetworkRefreshService; using dragonx::TransactionInfo; using dragonx::data::TransactionHistoryCache; fs::path dir = makeTempDir(); fs::path databasePath = dir / "transaction_history_refresh.sqlite"; std::string walletIdentity = "mainnet|R-refresh-cache|zs-refresh-cache"; std::string passphrase = "refresh cache passphrase"; dragonx::WalletState state; state.sync.blocks = 333; state.sync.best_blockhash = "tip-333"; Refresh::TransactionRefreshResult refreshResult; refreshResult.blockHeight = 333; TransactionInfo refreshedTx; refreshedTx.txid = "rpc-refreshed-send"; refreshedTx.type = "send"; refreshedTx.amount = -2.75; refreshedTx.timestamp = 1700000400; refreshedTx.confirmations = 14; refreshedTx.address = "zs-rpc-destination"; refreshedTx.from_address = "R-refresh-cache"; refreshedTx.memo = "rpc refreshed memo"; refreshResult.transactions = {refreshedTx}; Refresh::TransactionViewCache viewCache; std::unordered_set sendTxids; std::vector confirmedCache; std::unordered_set confirmedIds; int confirmedBlock = -1; int lastTxBlock = -1; Refresh::TransactionCacheUpdate cacheUpdate{ viewCache, sendTxids, confirmedCache, confirmedIds, confirmedBlock, lastTxBlock }; Refresh::applyTransactionRefreshResult(state, cacheUpdate, std::move(refreshResult), 1700000500); TransactionHistoryCache cache(databasePath.string()); EXPECT_TRUE(cache.unlockWithPassphrase(walletIdentity, passphrase)); EXPECT_TRUE(cache.replace(walletIdentity, state.sync.blocks, state.sync.best_blockhash, state.transactions, state.last_tx_update)); TransactionHistoryCache reopened(databasePath.string()); EXPECT_TRUE(reopened.unlockWithPassphrase(walletIdentity, passphrase)); auto loaded = reopened.load(walletIdentity, 333, "tip-333"); EXPECT_TRUE(loaded.loaded); EXPECT_EQ(loaded.transactions.size(), static_cast(1)); EXPECT_EQ(loaded.transactions[0].txid, std::string("rpc-refreshed-send")); EXPECT_EQ(loaded.transactions[0].memo, std::string("rpc refreshed memo")); EXPECT_EQ(loaded.updatedAt, static_cast(1700000500)); EXPECT_EQ(lastTxBlock, 333); EXPECT_EQ(confirmedIds.count("rpc-refreshed-send"), static_cast(1)); fs::remove_all(dir); } void testLiteBridgeOwnedStringCopiesBeforeFreeOnSuccess() { resetLiteBridgeRuntimeTrackedFree(); dragonx::wallet::LiteBridgeOwnedString ownedString( makeLiteBridgeRuntimeOwnedCString("phase2-ok"), &fakeLiteBridgeRuntimeTrackedFreeString); auto result = ownedString.intoResult(); EXPECT_TRUE(result.ok); EXPECT_EQ(result.value, std::string("phase2-ok")); EXPECT_TRUE(result.error.empty()); EXPECT_TRUE(ownedString.rawPointerReceived()); EXPECT_TRUE(ownedString.copiedBeforeFree()); EXPECT_TRUE(ownedString.freed()); EXPECT_FALSE(ownedString.rawPointerEscaped()); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues.size(), static_cast(1)); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues[0], std::string("phase2-ok")); auto secondResult = ownedString.intoResult(); EXPECT_FALSE(secondResult.ok); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); } void testLiteBridgeOwnedStringClassifiesNullWithoutFree() { resetLiteBridgeRuntimeTrackedFree(); dragonx::wallet::LiteBridgeOwnedString ownedString( nullptr, &fakeLiteBridgeRuntimeTrackedFreeString); auto result = ownedString.intoResult(); EXPECT_FALSE(result.ok); EXPECT_TRUE(result.value.empty()); EXPECT_TRUE(result.error.find("null string") != std::string::npos); EXPECT_FALSE(ownedString.rawPointerReceived()); EXPECT_FALSE(ownedString.copiedBeforeFree()); EXPECT_FALSE(ownedString.freed()); EXPECT_FALSE(ownedString.rawPointerEscaped()); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 0); } void testLiteBridgeOwnedStringClassifiesErrorAndFreesOnce() { resetLiteBridgeRuntimeTrackedFree(); dragonx::wallet::LiteBridgeOwnedString ownedString( makeLiteBridgeRuntimeOwnedCString("Error: phase2 denied"), &fakeLiteBridgeRuntimeTrackedFreeString); auto result = ownedString.intoResult(); EXPECT_FALSE(result.ok); EXPECT_TRUE(result.value.empty()); EXPECT_EQ(result.error, std::string("Error: phase2 denied")); EXPECT_TRUE(ownedString.copiedBeforeFree()); EXPECT_TRUE(ownedString.freed()); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues.size(), static_cast(1)); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues[0], std::string("Error: phase2 denied")); } void testLiteBridgeOwnedStringMovePreventsDoubleFree() { resetLiteBridgeRuntimeTrackedFree(); { dragonx::wallet::LiteBridgeOwnedString original( makeLiteBridgeRuntimeOwnedCString("phase2-move"), &fakeLiteBridgeRuntimeTrackedFreeString); dragonx::wallet::LiteBridgeOwnedString moved(std::move(original)); EXPECT_FALSE(original.rawPointerReceived()); EXPECT_TRUE(moved.rawPointerReceived()); EXPECT_FALSE(moved.rawPointerEscaped()); } EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues.size(), static_cast(1)); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues[0], std::string("phase2-move")); } void testLiteClientBridgeUsesRuntimeOwnedStringCleanup() { resetLiteBridgeRuntimeTrackedFree(); g_liteBridgeRuntimeFakeCallCount = 0; auto bridgeApi = makeCompleteFakeLiteBridgeRuntimeApi(); bridgeApi.execute = &fakeLiteBridgeRuntimeOwnedExecute; bridgeApi.freeString = &fakeLiteBridgeRuntimeTrackedFreeString; auto bridge = dragonx::wallet::LiteClientBridge::fromApi(bridgeApi); auto result = bridge.execute("phase2", "{}"); EXPECT_TRUE(result.ok); EXPECT_EQ(result.value, std::string("bridge-runtime-ok")); EXPECT_TRUE(result.error.empty()); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues.size(), static_cast(1)); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues[0], std::string("bridge-runtime-ok")); EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 1); } void testLiteBridgeRuntimeUnavailableDoesNotCallShutdown() { resetLiteBridgeRuntimeTrackedFree(); auto runtime = LiteBridgeRuntime::fromBindingResult({}); EXPECT_FALSE(runtime.available()); EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::Unavailable); runtime.shutdown(); EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::Shutdown); EXPECT_TRUE(runtime.shutdownCalled()); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 0); EXPECT_TRUE(runtime.dynamicLibraryUnloadDeferred()); } void testGeneratedResourceBehavior() { const auto* themes = dragonx::resources::getEmbeddedThemes(); EXPECT_TRUE(themes != nullptr); EXPECT_TRUE(dragonx::resources::getEmbeddedResource("__missing_resource__") == nullptr); if (!dragonx::resources::hasEmbeddedResources()) { EXPECT_TRUE(themes[0].data == nullptr); EXPECT_EQ(dragonx::resources::extractBundledThemes("/tmp/obsidian-dragon-empty-themes"), 0); } } // ---- restored live lite_bridge_runtime tests ---- std::vector requiredLiteBackendAbiSymbolNames() { std::vector names; for (const auto& symbol : dragonx::wallet::liteBackendArtifactContractRequiredSymbols()) { names.push_back(symbol.abiName); } return names; } LiteBackendArtifactContractInput makeReadyLiteBackendArtifactContractInput(const fs::path& artifactPath) { LiteBackendArtifactContractInput input; input.contractOwnerReady = true; input.readOnlyGateReady = true; input.projectRoot = artifactPath.parent_path().string(); input.artifactPath = artifactPath.string(); input.platform = dragonx::wallet::currentLiteBackendArtifactPlatform(); input.artifactKind = dragonx::wallet::LiteBackendArtifactKind::StaticLibrary; input.linkMode = LiteBackendArtifactContractLinkMode::ImportedLibrary; input.abiVersion = dragonx::wallet::liteBackendArtifactContractSupportedAbiVersion(); input.artifactSha256 = "phase1-artifact-sha256"; input.provenance = makeReadyLiteBackendArtifactProvenance(); input.sdxlCompatible = true; input.symbolInventoryOwnerReady = true; input.exportedSymbols = requiredLiteBackendAbiSymbolNames(); return input; } LiteBackendArtifactContractResult makeReadyLiteBridgeRuntimeArtifactContractResult() { const auto tempDir = makeTempDir(); const auto artifactPath = tempDir / "libsilentdragonxlite.a"; writeTestFile(artifactPath, "phase2 fake archive"); auto contractInput = makeReadyLiteBackendArtifactContractInput(artifactPath); auto contractResult = dragonx::wallet::evaluateLiteBackendArtifactContract(contractInput); fs::remove_all(tempDir); return contractResult; } LiteBridgeRuntimeBindingInput makeReadyFakeLiteBridgeRuntimeBindingInput(bool activationRequested = true) { LiteBridgeRuntimeBindingInput input; input.artifactContract = makeReadyLiteBridgeRuntimeArtifactContractResult(); input.config = dragonx::wallet::liteBridgeRuntimeConfigFromContractResult( input.artifactContract, activationRequested); input.useProvidedSymbolTable = true; input.symbolTable = dragonx::wallet::liteBridgeRuntimeSymbolTableFromApi( makeCompleteFakeLiteBridgeRuntimeApi(), "fake-imported-linked"); return input; } LiteBridgeRuntime makeReadyTrackedLiteBridgeRuntime() { auto input = makeReadyFakeLiteBridgeRuntimeBindingInput(); auto bridgeApi = makeCompleteFakeLiteBridgeRuntimeApi(); bridgeApi.initializeNew = &fakeLiteBridgeRuntimeOwnedInitializeNew; bridgeApi.initializeNewFromPhrase = &fakeLiteBridgeRuntimeOwnedInitializeNewFromPhrase; bridgeApi.initializeExisting = &fakeLiteBridgeRuntimeOwnedInitializeExisting; bridgeApi.execute = &fakeLiteBridgeRuntimeOwnedExecute; bridgeApi.freeString = &fakeLiteBridgeRuntimeTrackedFreeString; bridgeApi.shutdown = &fakeLiteBridgeRuntimeTrackedShutdown; input.symbolTable = dragonx::wallet::liteBridgeRuntimeSymbolTableFromApi( bridgeApi, "fake-imported-linked"); auto bindingResult = dragonx::wallet::evaluateLiteBridgeRuntimeBinding(input); return LiteBridgeRuntime::fromBindingResult(bindingResult); } LiteBridgeRuntime makeReadyFakeDynamicLoaderLiteBridgeRuntime( const std::string& source = "fake-dynamic-library", const std::string& handleLabel = "phase6-handle") { auto result = dragonx::wallet::evaluateLiteBridgeRuntimeFakeDynamicLoader( makeReadyFakeLiteBridgeRuntimeDynamicLoaderInput(source, handleLabel)); return LiteBridgeRuntime::fromFakeDynamicLoaderResult(result); } void testLiteBridgeRuntimeShutdownIsIdempotent() { resetLiteBridgeRuntimeTrackedFree(); auto runtime = makeReadyTrackedLiteBridgeRuntime(); EXPECT_TRUE(runtime.available()); EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::Ready); EXPECT_TRUE(runtime.dynamicLibraryUnloadDeferred()); runtime.shutdown(); EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::Shutdown); EXPECT_TRUE(runtime.shutdownCalled()); EXPECT_FALSE(runtime.shutdownPending()); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); runtime.shutdown(); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(1)); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("shutdown")); } void testLiteBridgeRuntimeDestructorCallsShutdownOnce() { resetLiteBridgeRuntimeTrackedFree(); { auto runtime = makeReadyTrackedLiteBridgeRuntime(); EXPECT_TRUE(runtime.available()); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 0); } EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(1)); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("shutdown")); } void testLiteBridgeRuntimeShutdownWaitsForOwnedStringRelease() { resetLiteBridgeRuntimeTrackedFree(); auto runtime = makeReadyTrackedLiteBridgeRuntime(); auto ownedString = runtime.takeOwnedString(makeLiteBridgeRuntimeOwnedCString("phase3-owned")); EXPECT_EQ(runtime.outstandingOwnedStringCount(), static_cast(1)); runtime.shutdown(); EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::ShuttingDown); EXPECT_TRUE(runtime.shutdownPending()); EXPECT_FALSE(runtime.shutdownCalled()); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 0); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 0); auto result = ownedString.intoResult(); EXPECT_TRUE(result.ok); EXPECT_EQ(result.value, std::string("phase3-owned")); EXPECT_EQ(runtime.outstandingOwnedStringCount(), static_cast(0)); EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::Shutdown); EXPECT_TRUE(runtime.shutdownCalled()); EXPECT_FALSE(runtime.shutdownPending()); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(2)); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("free:phase3-owned")); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[1], std::string("shutdown")); } void testLiteBridgeRuntimeDestructorShutdownWaitsForExternalOwnedString() { resetLiteBridgeRuntimeTrackedFree(); dragonx::wallet::LiteBridgeOwnedString heldString; { auto runtime = makeReadyTrackedLiteBridgeRuntime(); heldString = runtime.takeOwnedString(makeLiteBridgeRuntimeOwnedCString("phase3-destructor")); EXPECT_EQ(runtime.outstandingOwnedStringCount(), static_cast(1)); } EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 0); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 0); EXPECT_TRUE(g_liteBridgeRuntimeTeardownEvents.empty()); auto result = heldString.intoResult(); EXPECT_TRUE(result.ok); EXPECT_EQ(result.value, std::string("phase3-destructor")); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(2)); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("free:phase3-destructor")); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[1], std::string("shutdown")); } void testLiteBridgeRuntimeMoveAssignmentShutsDownReplacedRuntime() { resetLiteBridgeRuntimeTrackedFree(); { auto firstRuntime = makeReadyTrackedLiteBridgeRuntime(); auto secondRuntime = makeReadyTrackedLiteBridgeRuntime(); secondRuntime = std::move(firstRuntime); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); EXPECT_EQ(secondRuntime.status(), LiteBridgeRuntimeStatus::Ready); EXPECT_TRUE(secondRuntime.available()); } EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 2); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(2)); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("shutdown")); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[1], std::string("shutdown")); } void testLiteBridgeRuntimeDryDispatchBoolWrappersAreFakeOnly() { resetLiteBridgeRuntimeTrackedFree(); g_liteBridgeRuntimeFakeCallCount = 0; auto runtime = makeReadyTrackedLiteBridgeRuntime(); auto walletExists = runtime.dryDispatchWalletExists("DRGX"); EXPECT_TRUE(walletExists.ok); EXPECT_TRUE(walletExists.boolValue); EXPECT_TRUE(walletExists.fakeOnlyDispatch); EXPECT_TRUE(walletExists.fakeCallAttempted); EXPECT_TRUE(walletExists.noRealBridgeCalls); EXPECT_TRUE(walletExists.noNetwork); EXPECT_TRUE(walletExists.noWalletLifecycle); EXPECT_TRUE(walletExists.noWalletStateMutation); EXPECT_EQ(walletExists.operation, dragonx::wallet::LiteBridgeRuntimeDryDispatchOperation::WalletExists); auto serverOnline = runtime.dryDispatchCheckServerOnline("https://lite.example.invalid"); EXPECT_TRUE(serverOnline.ok); EXPECT_TRUE(serverOnline.boolValue); EXPECT_TRUE(serverOnline.fakeOnlyDispatch); EXPECT_TRUE(serverOnline.fakeCallAttempted); EXPECT_TRUE(serverOnline.noRealBridgeCalls); EXPECT_TRUE(serverOnline.noNetwork); EXPECT_TRUE(serverOnline.noWalletLifecycle); EXPECT_EQ(serverOnline.operation, dragonx::wallet::LiteBridgeRuntimeDryDispatchOperation::CheckServerOnline); EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 2); } void testLiteBridgeRuntimeDryDispatchExecuteOwnsReturnedString() { resetLiteBridgeRuntimeTrackedFree(); g_liteBridgeRuntimeFakeCallCount = 0; auto runtime = makeReadyTrackedLiteBridgeRuntime(); auto result = runtime.dryDispatchExecute("balance", "{}"); EXPECT_TRUE(result.ok); EXPECT_TRUE(result.fakeCallAttempted); EXPECT_TRUE(result.noRealBridgeCalls); EXPECT_TRUE(result.noWalletLifecycle); EXPECT_TRUE(result.noSendImportExportExecution); EXPECT_EQ(result.stringResult.value, std::string("bridge-runtime-ok")); EXPECT_EQ(runtime.outstandingOwnedStringCount(), static_cast(0)); EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues[0], std::string("bridge-runtime-ok")); result = runtime.dryDispatchExecute("", "{}"); EXPECT_FALSE(result.ok); EXPECT_FALSE(result.fakeCallAttempted); EXPECT_TRUE(result.error.find("empty") != std::string::npos); EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 1); } void testLiteBridgeRuntimeDryDispatchLifecycleWrappersRemainFakeOnly() { resetLiteBridgeRuntimeTrackedFree(); g_liteBridgeRuntimeFakeCallCount = 0; auto runtime = makeReadyTrackedLiteBridgeRuntime(); auto newResult = runtime.dryDispatchInitializeNew(false, "https://lite.example.invalid"); auto existingResult = runtime.dryDispatchInitializeExisting(false, "https://lite.example.invalid"); auto phraseResult = runtime.dryDispatchInitializeNewFromPhrase( false, "https://lite.example.invalid", "seed words redacted", 42, 0, false); EXPECT_TRUE(newResult.ok); EXPECT_TRUE(existingResult.ok); EXPECT_TRUE(phraseResult.ok); EXPECT_EQ(newResult.stringResult.value, std::string("initialize-new-ok")); EXPECT_EQ(existingResult.stringResult.value, std::string("initialize-existing-ok")); EXPECT_EQ(phraseResult.stringResult.value, std::string("initialize-phrase-ok")); EXPECT_TRUE(newResult.noWalletLifecycle); EXPECT_TRUE(existingResult.noWalletLifecycle); EXPECT_TRUE(phraseResult.noWalletLifecycle); EXPECT_TRUE(newResult.noWalletPersistence); EXPECT_TRUE(existingResult.noWalletPersistence); EXPECT_TRUE(phraseResult.noWalletPersistence); EXPECT_TRUE(newResult.noWalletStateMutation); EXPECT_TRUE(existingResult.noWalletStateMutation); EXPECT_TRUE(phraseResult.noWalletStateMutation); EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 3); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 3); } void testLiteBridgeRuntimeDryDispatchRejectsShutdownRuntime() { resetLiteBridgeRuntimeTrackedFree(); g_liteBridgeRuntimeFakeCallCount = 0; auto runtime = makeReadyTrackedLiteBridgeRuntime(); runtime.shutdown(); auto result = runtime.dryDispatchExecute("balance", "{}"); EXPECT_FALSE(result.ok); EXPECT_FALSE(result.fakeCallAttempted); EXPECT_EQ(result.status, LiteBridgeRuntimeStatus::Shutdown); EXPECT_TRUE(result.error.find("not ready") != std::string::npos); EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 0); EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 0); } void testLiteBridgeRuntimeDryDispatchShutdownUsesTeardownOwner() { resetLiteBridgeRuntimeTrackedFree(); auto runtime = makeReadyTrackedLiteBridgeRuntime(); auto result = runtime.dryDispatchShutdown(); EXPECT_TRUE(result.ok); EXPECT_TRUE(result.fakeCallAttempted); EXPECT_EQ(result.status, LiteBridgeRuntimeStatus::Shutdown); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); result = runtime.dryDispatchShutdown(); EXPECT_TRUE(result.ok); EXPECT_FALSE(result.fakeCallAttempted); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); } void testLiteBridgeRuntimeDryDispatchShutdownWaitsForOwnedString() { resetLiteBridgeRuntimeTrackedFree(); auto runtime = makeReadyTrackedLiteBridgeRuntime(); auto ownedString = runtime.takeOwnedString(makeLiteBridgeRuntimeOwnedCString("phase4-dry")); auto result = runtime.dryDispatchShutdown(); EXPECT_TRUE(result.ok); EXPECT_FALSE(result.fakeCallAttempted); EXPECT_EQ(result.status, LiteBridgeRuntimeStatus::ShuttingDown); EXPECT_TRUE(runtime.shutdownPending()); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 0); auto stringResult = ownedString.intoResult(); EXPECT_TRUE(stringResult.ok); EXPECT_EQ(stringResult.value, std::string("phase4-dry")); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(2)); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("free:phase4-dry")); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[1], std::string("shutdown")); } void testLiteBridgeRuntimeFakeDynamicLoaderUnloadWaitsForOwnedStringRelease() { resetLiteBridgeRuntimeTrackedFree(); auto runtime = makeReadyFakeDynamicLoaderLiteBridgeRuntime( "fake-dynamic-library", "phase6-deferred"); auto ownedString = runtime.takeOwnedString(makeLiteBridgeRuntimeOwnedCString("phase6-owned")); EXPECT_TRUE(runtime.dynamicLibraryHandlePresent()); EXPECT_EQ(runtime.outstandingOwnedStringCount(), static_cast(1)); runtime.shutdown(); EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::ShuttingDown); EXPECT_TRUE(runtime.shutdownPending()); EXPECT_FALSE(runtime.shutdownCalled()); EXPECT_TRUE(runtime.dynamicLibraryHandlePresent()); EXPECT_TRUE(runtime.dynamicLibraryUnloadDeferred()); EXPECT_FALSE(runtime.dynamicLibraryUnloadCalled()); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 0); EXPECT_EQ(g_liteBridgeRuntimeTrackedUnloadCount, 0); auto result = ownedString.intoResult(); EXPECT_TRUE(result.ok); EXPECT_EQ(result.value, std::string("phase6-owned")); EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::Shutdown); EXPECT_FALSE(runtime.dynamicLibraryHandlePresent()); EXPECT_TRUE(runtime.dynamicLibraryUnloadCalled()); EXPECT_FALSE(runtime.dynamicLibraryUnloadDeferred()); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTrackedUnloadCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(3)); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("free:phase6-owned")); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[1], std::string("shutdown")); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[2], std::string("unload:phase6-deferred")); } void testLiteBridgeRuntimeFakeDynamicLoaderMoveAssignmentUnloadsReplacedHandle() { resetLiteBridgeRuntimeTrackedFree(); { auto firstRuntime = makeReadyFakeDynamicLoaderLiteBridgeRuntime( "fake-dynamic-library", "phase6-first"); auto secondRuntime = makeReadyFakeDynamicLoaderLiteBridgeRuntime( "fake-dynamic-library", "phase6-second"); secondRuntime = std::move(firstRuntime); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTrackedUnloadCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(2)); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("shutdown")); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[1], std::string("unload:phase6-second")); EXPECT_TRUE(secondRuntime.available()); EXPECT_TRUE(secondRuntime.dynamicLibraryHandlePresent()); } EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 2); EXPECT_EQ(g_liteBridgeRuntimeTrackedUnloadCount, 2); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(4)); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[2], std::string("shutdown")); EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[3], std::string("unload:phase6-first")); } void testLiteBridgeRuntimeFakeDynamicLoaderNonFakeSourceBlocksDryDispatch() { resetLiteBridgeRuntimeTrackedFree(); g_liteBridgeRuntimeFakeCallCount = 0; auto runtime = makeReadyFakeDynamicLoaderLiteBridgeRuntime( "dynamic-library", "phase6-nonfake"); EXPECT_TRUE(runtime.available()); EXPECT_FALSE(runtime.fakeDispatchAllowed()); auto result = runtime.dryDispatchWalletExists("DRGX"); EXPECT_FALSE(result.ok); EXPECT_FALSE(result.fakeCallAttempted); EXPECT_TRUE(result.error.find("fake") != std::string::npos); EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 0); EXPECT_EQ(g_liteBridgeRuntimeTrackedUnloadCount, 0); runtime.shutdown(); EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); EXPECT_EQ(g_liteBridgeRuntimeTrackedUnloadCount, 1); } // M0: deterministic injectable fake backend driving the real LiteClientBridge // (ungated fromApi path). This is the harness M1 service tests reuse. void testLiteBackendInjectableFakeBridge() { using dragonx::wallet::LiteClientBridge; // available() + bool round-trips { dragonx::test::resetLiteFakeCounters(); auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); EXPECT_TRUE(bridge.available()); EXPECT_TRUE(bridge.walletExists("DRGX")); EXPECT_TRUE(bridge.checkServerOnline("https://lite.example")); } // execute() round-trips the canned value and frees the owned string exactly once { dragonx::test::resetLiteFakeCounters(); auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); const auto result = bridge.execute("ping", ""); // unknown command -> default fake response EXPECT_TRUE(result.ok); EXPECT_EQ(result.value, std::string("{\"version\":\"sdxl-fake\"}")); EXPECT_EQ(dragonx::test::g_liteFakeAlloc, 1L); EXPECT_EQ(dragonx::test::g_liteFakeFreed, 1L); } // lifecycle calls round-trip { dragonx::test::resetLiteFakeCounters(); auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); EXPECT_TRUE(bridge.initializeNew(false, "https://lite.example").ok); EXPECT_TRUE(bridge.initializeExisting(false, "https://lite.example").ok); EXPECT_TRUE(bridge.initializeNewFromPhrase(false, "https://lite.example", "seed words", 0, 0, false).ok); EXPECT_EQ(dragonx::test::g_liteFakeAlloc, dragonx::test::g_liteFakeFreed); } // an "Error:"-prefixed backend response maps to ok=false with the message propagated { dragonx::test::resetLiteFakeCounters(); auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); const auto result = bridge.execute("boom", ""); EXPECT_FALSE(result.ok); EXPECT_TRUE(result.error.rfind("Error:", 0) == 0); EXPECT_EQ(dragonx::test::g_liteFakeAlloc, dragonx::test::g_liteFakeFreed); } // empty command is rejected before reaching the backend (no allocation) { dragonx::test::resetLiteFakeCounters(); auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); EXPECT_FALSE(bridge.execute("", "").ok); EXPECT_EQ(dragonx::test::g_liteFakeAlloc, 0L); } // an unavailable bridge fails closed (never fakes success) { dragonx::test::resetLiteFakeCounters(); auto bridge = LiteClientBridge::unavailable("lite backend is not linked"); EXPECT_FALSE(bridge.available()); EXPECT_FALSE(bridge.execute("info", "").ok); EXPECT_FALSE(bridge.walletExists("DRGX")); EXPECT_EQ(dragonx::test::g_liteFakeAlloc, 0L); } // shutdown is invoked (via destructor) and no owned string leaks or double-frees { dragonx::test::resetLiteFakeCounters(); { auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); (void)bridge.execute("info", ""); } EXPECT_TRUE(dragonx::test::g_liteFakeShutdownCalled); EXPECT_EQ(dragonx::test::g_liteFakeAlloc, dragonx::test::g_liteFakeFreed); } } // M1: the App-owned LiteWalletController drives real create/open/restore through an // (injected fake) bridge, reports wallet-ready, persists, wipes secrets, and fails closed. void testLiteWalletControllerLifecycle() { using namespace dragonx::wallet; const auto liteCaps = makeWalletCapabilities(WalletBuildKind::Lite, /*embeddedDaemon*/ false, /*liteBackendLinked*/ true); const LiteConnectionSettings conn = defaultLiteConnectionSettings(); // create -> wallet ready, walletOpen() true, persist callback fires once. // (No alloc==freed leak assert here: a ready wallet launches the detached sync thread + // refresh worker, which are still in flight at end-of-scope and legitimately hold owned // strings. The leak/double-free invariant is checked in the thread-free bridge test.) { dragonx::test::resetLiteFakeCounters(); int persistCount = 0; LiteWalletController controller(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); controller.setPersistCallback([&persistCount]() { ++persistCount; }); EXPECT_FALSE(controller.walletOpen()); LiteWalletCreateRequest req; req.passphrase = "hunter2"; const auto result = controller.createWallet(req); EXPECT_TRUE(result.ok); EXPECT_TRUE(result.walletReady); EXPECT_TRUE(result.operation == LiteWalletLifecycleOperation::CreateNew); EXPECT_TRUE(controller.walletOpen()); EXPECT_EQ(persistCount, 1); } // open existing -> ready. Regression guard: the real backend's open path // (litelib_initialize_existing) returns the bare string "OK", which is NOT valid JSON, so // walletReady must key off a non-empty success response, not JSON validity. { dragonx::test::resetLiteFakeCounters(); dragonx::test::g_liteFakeWalletExists = true; LiteWalletController controller(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); LiteWalletOpenRequest req; req.passphrase = "hunter2"; const auto result = controller.openWallet(req); EXPECT_TRUE(result.ok); EXPECT_TRUE(result.walletReady); EXPECT_TRUE(result.operation == LiteWalletLifecycleOperation::OpenExisting); EXPECT_TRUE(controller.walletOpen()); } // restore-from-seed round-trips to ready { dragonx::test::resetLiteFakeCounters(); LiteWalletController controller(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); LiteWalletRestoreRequest req; req.seedPhrase = "abandon abandon abandon ... art"; const auto result = controller.restoreWallet(req); EXPECT_TRUE(result.walletReady); EXPECT_TRUE(result.operation == LiteWalletLifecycleOperation::RestoreFromSeed); EXPECT_TRUE(controller.walletOpen()); } // empty restore seed is rejected before the backend is touched { dragonx::test::resetLiteFakeCounters(); LiteWalletController controller(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); const auto result = controller.restoreWallet(LiteWalletRestoreRequest{}); EXPECT_FALSE(result.ok); EXPECT_FALSE(result.walletReady); EXPECT_FALSE(controller.walletOpen()); EXPECT_EQ(dragonx::test::g_liteFakeAlloc, 0L); } // bridge calls disabled -> blocked, backend never reached { dragonx::test::resetLiteFakeCounters(); LiteWalletController controller(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()), LiteWalletControllerOptions{/*allowBridgeCalls*/ false}); const auto result = controller.createWallet(LiteWalletCreateRequest{}); EXPECT_FALSE(result.walletReady); EXPECT_FALSE(controller.walletOpen()); EXPECT_EQ(dragonx::test::g_liteFakeAlloc, 0L); } // full-node capabilities -> unsupported, fails closed { dragonx::test::resetLiteFakeCounters(); const auto fullNodeCaps = makeWalletCapabilities(WalletBuildKind::FullNode, true, false); LiteWalletController controller(fullNodeCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); const auto result = controller.createWallet(LiteWalletCreateRequest{}); EXPECT_FALSE(result.walletReady); EXPECT_EQ(dragonx::test::g_liteFakeAlloc, 0L); } // secret-wipe helper zeroes and clears { std::string secret = "super-secret-seed-phrase"; secureWipeLiteSecret(secret); EXPECT_TRUE(secret.empty()); } } // M4: spend & backup. The controller drives send/shield/import/export/seed through the // (injected fake) bridge with the real backend's arg/response contracts: send uses the // JSON-array form (litelib_execute takes one arg), failures arrive as {"error":..} in the // body, and the async send/shield path delivers a txid via takeBroadcastResult(). void testLiteWalletControllerM4() { using namespace dragonx::wallet; const auto liteCaps = makeWalletCapabilities(WalletBuildKind::Lite, false, true); const LiteConnectionSettings conn = defaultLiteConnectionSettings(); auto openController = [&]() { auto c = std::make_unique( liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); LiteWalletCreateRequest req; req.passphrase = "hunter2"; (void)c->createWallet(req); return c; }; // send (blocking core): JSON-array payload -> {"txid":..} { dragonx::test::g_liteFakeSendFails = false; auto c = openController(); LiteSendRequest req; req.recipients.push_back({"zs1dest", 150000000ULL, "hello memo with spaces"}); const auto r = c->sendTransactionBlocking(req); EXPECT_TRUE(r.ok); EXPECT_EQ(r.txid, std::string("faketxid123")); EXPECT_TRUE(r.error.empty()); } // send with no recipients -> error, backend never reached { auto c = openController(); const auto r = c->sendTransactionBlocking(LiteSendRequest{}); EXPECT_FALSE(r.ok); EXPECT_FALSE(r.error.empty()); } // send failure surfaces the backend's {"error":..} body (not an "Error:" prefix) { dragonx::test::g_liteFakeSendFails = true; auto c = openController(); LiteSendRequest req; req.recipients.push_back({"zs1dest", 1ULL, ""}); const auto r = c->sendTransactionBlocking(req); EXPECT_FALSE(r.ok); EXPECT_EQ(r.error, std::string("insufficient funds")); dragonx::test::g_liteFakeSendFails = false; } // shield (blocking core) -> txid { dragonx::test::g_liteFakeSendFails = false; auto c = openController(); const auto r = c->shieldFundsBlocking(); EXPECT_TRUE(r.ok); EXPECT_EQ(r.txid, std::string("fakeshieldtxid")); } // async send: delivers the result to the main-thread slot { dragonx::test::g_liteFakeSendFails = false; auto c = openController(); LiteSendRequest req; req.recipients.push_back({"zs1dest", 42ULL, ""}); EXPECT_TRUE(c->sendTransaction(req)); LiteBroadcastResult r; bool got = false; for (int i = 0; i < 400 && !got; ++i) { // bounded wait (<= ~2s) got = c->takeBroadcastResult(r); if (!got) std::this_thread::sleep_for(std::chrono::milliseconds(5)); } EXPECT_TRUE(got); EXPECT_TRUE(r.ok); EXPECT_EQ(r.txid, std::string("faketxid123")); } // send before a wallet is open -> rejected { LiteWalletController c(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); LiteSendRequest req; req.recipients.push_back({"zs1dest", 1ULL, ""}); EXPECT_FALSE(c.sendTransaction(req)); const auto r = c.sendTransactionBlocking(req); EXPECT_FALSE(r.ok); } // export private keys (SECRET) -> parsed, non-empty { auto c = openController(); const auto r = c->exportPrivateKeys(); EXPECT_TRUE(r.ok); EXPECT_TRUE(r.privateKeysJson.find("private_key") != std::string::npos); } // export seed (SECRET) -> seed phrase + birthday { auto c = openController(); const auto r = c->exportSeed(); EXPECT_TRUE(r.ok); EXPECT_EQ(r.seedPhrase, std::string("fake seed phrase words")); EXPECT_EQ(r.birthday, 0ULL); } // import shielded key -> "import"; the key argument is wiped on return { auto c = openController(); std::string key = "secret-extended-key-main1fakeshieldedkey"; const auto r = c->importKey(std::move(key)); EXPECT_TRUE(r.ok); EXPECT_TRUE(r.detail.find("zs1imported") != std::string::npos); } // import transparent WIF (begins with K) -> "timport" { auto c = openController(); const auto r = c->importKey("Kx1faketransparentwif"); EXPECT_TRUE(r.ok); } // import with no wallet open -> rejected (and key still wiped) { LiteWalletController c(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); const auto r = c.importKey("secret-extended-key-main1abc"); EXPECT_FALSE(r.ok); } } // M5: persistence. The backend auto-saves on new-address/import but NOT after sync/send/shield, // so the controller must trigger `save` at those points (otherwise a ~30-min scan and sent txs // are lost on restart). Drives the fake's save counter to prove saves happen exactly when needed. void testLiteWalletControllerM5Persistence() { using namespace dragonx::wallet; const auto liteCaps = makeWalletCapabilities(WalletBuildKind::Lite, false, true); const LiteConnectionSettings conn = defaultLiteConnectionSettings(); auto openController = [&]() { auto c = std::make_unique( liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); LiteWalletCreateRequest req; req.passphrase = "hunter2"; (void)c->createWallet(req); return c; }; // Open and wait until the background sync (and its persist) has completed, so the save count // is stable before a sub-test captures its baseline. auto openSynced = [&]() { auto c = openController(); for (int i = 0; i < 400 && !c->syncComplete(); ++i) std::this_thread::sleep_for(std::chrono::milliseconds(5)); return c; }; // sync persists: completing a sync saves the freshly-scanned wallet. { dragonx::test::g_liteFakeSyncBlock = false; dragonx::test::g_liteFakeSaveCount = 0; auto c = openSynced(); EXPECT_TRUE(c->syncComplete()); EXPECT_TRUE(dragonx::test::g_liteFakeSaveCount.load() >= 1); } // send persists the new transaction (backend does not auto-save sends) { dragonx::test::g_liteFakeSendFails = false; auto c = openSynced(); const long before = dragonx::test::g_liteFakeSaveCount.load(); LiteSendRequest req; req.recipients.push_back({"zs1dest", 1ULL, ""}); EXPECT_TRUE(c->sendTransactionBlocking(req).ok); EXPECT_EQ(dragonx::test::g_liteFakeSaveCount.load(), before + 1); } // a FAILED send does NOT save { dragonx::test::g_liteFakeSendFails = true; auto c = openSynced(); const long before = dragonx::test::g_liteFakeSaveCount.load(); LiteSendRequest req; req.recipients.push_back({"zs1dest", 1ULL, ""}); EXPECT_FALSE(c->sendTransactionBlocking(req).ok); EXPECT_EQ(dragonx::test::g_liteFakeSaveCount.load(), before); dragonx::test::g_liteFakeSendFails = false; } // shield persists { auto c = openSynced(); const long before = dragonx::test::g_liteFakeSaveCount.load(); EXPECT_TRUE(c->shieldFundsBlocking().ok); EXPECT_EQ(dragonx::test::g_liteFakeSaveCount.load(), before + 1); } // explicit saveWallet() persists; with no wallet open it is a no-op { auto c = openSynced(); const long before = dragonx::test::g_liteFakeSaveCount.load(); EXPECT_TRUE(c->saveWallet()); EXPECT_EQ(dragonx::test::g_liteFakeSaveCount.load(), before + 1); } { LiteWalletController c(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); const long before = dragonx::test::g_liteFakeSaveCount.load(); EXPECT_FALSE(c.saveWallet()); EXPECT_EQ(dragonx::test::g_liteFakeSaveCount.load(), before); } } // Migration: a saved lite chain_name outside {main,test,regtest} (e.g. the legacy // "DRAGONX" ticker) is rewritten to "main" on load, since the backend hard-panics // on unknown chains. Valid values are preserved. void testLiteChainNameMigration() { const fs::path dir = fs::temp_directory_path() / "dragonx_lite_chain_migration_test"; fs::create_directories(dir); { const fs::path p = dir / "legacy.json"; writeTestFile(p, "{\"lite_wallet\":{\"chain_name\":\"DRAGONX\"}}"); dragonx::config::Settings settings; EXPECT_TRUE(settings.load(p.string())); EXPECT_EQ(settings.getLiteChainName(), std::string("main")); } { const fs::path p = dir / "valid.json"; writeTestFile(p, "{\"lite_wallet\":{\"chain_name\":\"test\"}}"); dragonx::config::Settings settings; EXPECT_TRUE(settings.load(p.string())); EXPECT_EQ(settings.getLiteChainName(), std::string("test")); } std::error_code ec; fs::remove_all(dir, ec); } // M2: a parsed lite refresh bundle maps through to the app's WalletState (the last hop // the Balance/Receive/Transactions tabs read), with zatoshi->DRGX conversion, z/t address // split, transaction typing, confirmations, and sync progress. void testLiteRefreshModelAppliesToWalletState() { using namespace dragonx::wallet; LiteWalletRefreshBundle bundle; bundle.complete = true; bundle.successfulCommandCount = 4; bundle.hasBalance = true; bundle.balance.shieldedBalance = 200000000; // 2 DRGX bundle.balance.transparentBalance = 100000000; // 1 DRGX bundle.balance.unconfirmedBalance = 50000000; // 0.5 DRGX bundle.hasAddresses = true; bundle.addresses.zAddresses = {"zs1shielded"}; bundle.addresses.tAddresses = {"t1transparent"}; bundle.hasTransactions = true; LiteTransactionRecord rec; rec.txid = "deadbeef"; rec.datetime = 1700000000; rec.blockHeight = 990; rec.unconfirmed = false; rec.direction = LiteTransactionDirection::Receive; rec.address = "zs1shielded"; rec.amount = 150000000; // 1.5 DRGX bundle.transactions.transactions.push_back(rec); bundle.hasSyncStatus = true; bundle.syncStatus.syncedBlocks = 1000; bundle.syncStatus.totalBlocks = 1000; bundle.syncStatus.progress = 1.0; bundle.syncStatus.complete = true; const auto mapped = mapLiteWalletRefreshBundle(bundle); EXPECT_TRUE(mapped.ok); dragonx::WalletState state; applyLiteRefreshModelToWalletState(mapped.model, state); EXPECT_NEAR(state.privateBalance, 2.0, 1e-9); EXPECT_NEAR(state.transparentBalance, 1.0, 1e-9); EXPECT_NEAR(state.totalBalance, 3.0, 1e-9); EXPECT_NEAR(state.unconfirmedBalance, 0.5, 1e-9); EXPECT_EQ(static_cast(state.addresses.size()), 2); EXPECT_EQ(static_cast(state.z_addresses.size()), 1); EXPECT_EQ(static_cast(state.t_addresses.size()), 1); EXPECT_EQ(state.z_addresses[0].type, std::string("shielded")); EXPECT_EQ(state.t_addresses[0].type, std::string("transparent")); EXPECT_EQ(static_cast(state.transactions.size()), 1); EXPECT_EQ(state.transactions[0].type, std::string("receive")); EXPECT_NEAR(state.transactions[0].amount, 1.5, 1e-9); EXPECT_EQ(state.transactions[0].confirmations, 11); // chain 1000 - block 990 + 1 EXPECT_EQ(state.sync.blocks, 1000); EXPECT_EQ(state.sync.headers, 1000); EXPECT_FALSE(state.sync.syncing); } // M2b: the controller, after a wallet is ready, auto-starts sync and refreshWalletState() // pulls balance/addresses/transactions/syncstatus through the shared bridge into WalletState. void testLiteWalletControllerRefreshPopulatesState() { using namespace dragonx::wallet; const auto caps = makeWalletCapabilities(WalletBuildKind::Lite, /*embeddedDaemon*/ false, /*liteBackendLinked*/ true); const auto conn = defaultLiteConnectionSettings(); LiteWalletController controller(caps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); EXPECT_TRUE(controller.createWallet(LiteWalletCreateRequest{}).walletReady); EXPECT_TRUE(controller.walletOpen()); EXPECT_TRUE(controller.syncStarted()); // auto-started when the wallet became ready // Sync runs on a detached thread; the full refresh (balance/addresses) only runs once it // completes. Wait for it (instant with the fake) so the refresh is deterministic. for (int i = 0; i < 500 && !controller.syncComplete(); ++i) std::this_thread::sleep_for(std::chrono::milliseconds(5)); EXPECT_TRUE(controller.syncComplete()); dragonx::WalletState state; EXPECT_TRUE(controller.refreshWalletState(state)); EXPECT_NEAR(state.privateBalance, 2.0, 1e-9); EXPECT_NEAR(state.transparentBalance, 1.0, 1e-9); EXPECT_EQ(static_cast(state.z_addresses.size()), 1); EXPECT_EQ(static_cast(state.t_addresses.size()), 1); EXPECT_EQ(static_cast(state.transactions.size()), 1); EXPECT_EQ(state.transactions[0].type, std::string("receive")); EXPECT_NEAR(state.transactions[0].amount, 1.5, 1e-9); EXPECT_EQ(state.sync.headers, 1000); // No wallet open -> refresh is a safe no-op LiteWalletController fresh(caps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); dragonx::WalletState empty; EXPECT_FALSE(fresh.refreshWalletState(empty)); } // syncstatus parser must accept both real backend shapes: idle {"syncing":"false"} and // in-progress {"syncing":"true","synced_blocks":N,"total_blocks":M} ("syncing" is a string). void testLiteSyncStatusParserRealShapes() { using namespace dragonx::wallet; // Idle: no block counts, syncing is the string "false". { const auto p = parseLiteSyncStatusResponse(std::string("{\"syncing\":\"false\"}")); EXPECT_TRUE(p.ok); EXPECT_EQ(static_cast(p.syncStatus.syncedBlocks), 0LL); EXPECT_TRUE(p.syncStatus.complete); } // In-progress: block counts present. { const auto p = parseLiteSyncStatusResponse( std::string("{\"syncing\":\"true\",\"synced_blocks\":900,\"total_blocks\":1000}")); EXPECT_TRUE(p.ok); EXPECT_EQ(static_cast(p.syncStatus.syncedBlocks), 900LL); EXPECT_EQ(static_cast(p.syncStatus.totalBlocks), 1000LL); EXPECT_FALSE(p.syncStatus.complete); EXPECT_NEAR(p.syncStatus.progress, 0.9, 1e-9); } // While syncing, missing block counts is still an error. { const auto p = parseLiteSyncStatusResponse(std::string("{\"syncing\":\"true\"}")); EXPECT_FALSE(p.ok); } } // Per-address balances: unspent notes/utxos are summed per address (spent ones excluded) // into the WalletState address list, instead of aggregate-only zeros. void testLitePerAddressBalances() { using namespace dragonx::wallet; LiteWalletRefreshBundle bundle; bundle.complete = true; bundle.hasAddresses = true; bundle.addresses.zAddresses = {"zs1aaa"}; bundle.addresses.tAddresses = {"t1bbb"}; bundle.hasNotes = true; LiteSpendableOutput note; note.address = "zs1aaa"; note.value = 150000000; note.createdInTxid = "n1"; LiteSpendableOutput utxo; utxo.address = "t1bbb"; utxo.value = 50000000; utxo.createdInTxid = "u1"; LiteSpendableOutput spentNote; spentNote.address = "zs1aaa"; spentNote.value = 999; spentNote.spent = true; spentNote.createdInTxid = "n2"; bundle.notes.unspentNotes.push_back(note); bundle.notes.unspentNotes.push_back(spentNote); // excluded: spent bundle.notes.utxos.push_back(utxo); const auto mapped = mapLiteWalletRefreshBundle(bundle); EXPECT_TRUE(mapped.ok); dragonx::WalletState state; applyLiteRefreshModelToWalletState(mapped.model, state); EXPECT_EQ(static_cast(state.z_addresses.size()), 1); EXPECT_EQ(static_cast(state.t_addresses.size()), 1); EXPECT_NEAR(state.z_addresses[0].balance, 1.5, 1e-9); // 150000000; the spent note is excluded EXPECT_NEAR(state.t_addresses[0].balance, 0.5, 1e-9); // 50000000 } // M3: new-address generation via the controller (backend "new" zs/R) returns the address. void testLiteWalletControllerNewAddress() { using namespace dragonx::wallet; const auto caps = makeWalletCapabilities(WalletBuildKind::Lite, /*embeddedDaemon*/ false, /*liteBackendLinked*/ true); const auto conn = defaultLiteConnectionSettings(); LiteWalletController controller(caps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); EXPECT_TRUE(controller.createWallet(LiteWalletCreateRequest{}).walletReady); const auto z = controller.newAddress(/*shielded*/ true); EXPECT_TRUE(z.ok); EXPECT_TRUE(z.address.rfind("zs1", 0) == 0); const auto t = controller.newAddress(/*shielded*/ false); EXPECT_TRUE(t.ok); EXPECT_TRUE(t.address.rfind("R1", 0) == 0); // No wallet open -> error, no address. LiteWalletController idle(caps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); const auto none = idle.newAddress(true); EXPECT_FALSE(none.ok); } // Gateway hardening: one command's parse failure must not abort the whole refresh — the // other commands still populate the bundle (graceful degradation against real-shape drift). void testLiteWalletGatewayRefreshSkipsFailedCommand() { using namespace dragonx::wallet; const auto caps = makeWalletCapabilities(WalletBuildKind::Lite, /*embeddedDaemon*/ false, /*liteBackendLinked*/ true); const auto conn = defaultLiteConnectionSettings(); auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); LiteWalletGateway gateway(caps, conn, &bridge, LiteWalletGatewayOptions{/*allowBridgeCalls*/ true}); dragonx::test::g_liteFakeBadBalance.store(true); // make only the balance command fail to parse const auto result = gateway.refresh(LiteWalletRefreshRequest{}); dragonx::test::g_liteFakeBadBalance.store(false); EXPECT_TRUE(result.ok); // partial success, not a total failure EXPECT_FALSE(result.bundle.complete); // not all commands succeeded EXPECT_FALSE(result.bundle.hasBalance); // the failed command is skipped EXPECT_TRUE(result.bundle.hasAddresses); // the others still populate EXPECT_TRUE(result.bundle.hasTransactions); EXPECT_TRUE(result.bundle.hasInfo); } // M2b-3: opening a wallet auto-starts the background worker, which produces a refresh model // the main thread can pick up via takeRefreshedModel() and apply to WalletState. void testLiteWalletControllerWorkerProducesModel() { using namespace dragonx::wallet; const auto caps = makeWalletCapabilities(WalletBuildKind::Lite, /*embeddedDaemon*/ false, /*liteBackendLinked*/ true); const auto conn = defaultLiteConnectionSettings(); LiteWalletController controller(caps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); EXPECT_TRUE(controller.createWallet(LiteWalletCreateRequest{}).walletReady); // auto-starts the worker // The worker publishes progress-only models while syncing, then full models once synced. // Poll until a full (balance-bearing) model arrives (sync is instant with the fake). LiteWalletAppRefreshModel model; bool gotFull = false; for (int i = 0; i < 500 && !gotFull; ++i) { LiteWalletAppRefreshModel m; if (controller.takeRefreshedModel(m) && m.hasBalance) { model = m; gotFull = true; break; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } EXPECT_TRUE(gotFull); EXPECT_TRUE(model.hasBalance); EXPECT_TRUE(model.hasAddresses); dragonx::WalletState state; applyLiteRefreshModelToWalletState(model, state); EXPECT_NEAR(state.privateBalance, 2.0, 1e-9); // Idle controller (no wallet -> no worker) has nothing pending. LiteWalletController idle(caps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); LiteWalletAppRefreshModel none; EXPECT_FALSE(idle.takeRefreshedModel(none)); } // M2b-3 hardening: the backend `sync` is a blocking, uninterruptible full scan. Destroying the // controller while a sync is in flight must NOT hang (the sync thread is detached, not joined). void testLiteWalletControllerShutdownDoesNotHangDuringSync() { using namespace dragonx::wallet; const auto caps = makeWalletCapabilities(WalletBuildKind::Lite, /*embeddedDaemon*/ false, /*liteBackendLinked*/ true); const auto conn = defaultLiteConnectionSettings(); dragonx::test::g_liteFakeSyncBlock.store(true); // make the backend "sync" block indefinitely const auto start = std::chrono::steady_clock::now(); { LiteWalletController controller(caps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); controller.createWallet(LiteWalletCreateRequest{}); // launches the (now-blocked) sync thread EXPECT_TRUE(controller.syncStarted()); EXPECT_FALSE(controller.syncComplete()); // controller destructs here with the sync thread still blocked -> must return promptly. } const auto elapsedMs = std::chrono::duration_cast( std::chrono::steady_clock::now() - start).count(); EXPECT_TRUE(elapsedMs < 1500); // did not wait for the (blocked) sync to finish dragonx::test::g_liteFakeSyncBlock.store(false); // release the detached sync thread std::this_thread::sleep_for(std::chrono::milliseconds(50)); // let it unwind cleanly } } // namespace int main() { testConnectionConfig(); testPaymentUri(); testAmountFormatting(); testSpendableFiltering(); testRefreshScheduler(); testNetworkRefreshService(); testNetworkRefreshSnapshotHelpers(); testNetworkRefreshRpcCollectors(); testNetworkRefreshResultModels(); testOperationStatusPollParsing(); testWalletSecurityController(); testWalletSecurityWorkflow(); testWalletSecurityWorkflowExecutor(); testDaemonShutdownPolicy(); testDaemonLifecycleExecution(); testDaemonLifecycleAdapters(); testRendererHelpers(); testConsoleInputModel(); testMiningBenchmarkModel(); testBalanceAddressListModel(); testExplorerBlockCache(); testTransactionHistoryCache(); testTransactionHistoryCacheRefreshApply(); testLiteBridgeOwnedStringCopiesBeforeFreeOnSuccess(); testLiteBridgeOwnedStringClassifiesNullWithoutFree(); testLiteBridgeOwnedStringClassifiesErrorAndFreesOnce(); testLiteBridgeOwnedStringMovePreventsDoubleFree(); testLiteClientBridgeUsesRuntimeOwnedStringCleanup(); testLiteBackendInjectableFakeBridge(); testLiteWalletControllerLifecycle(); testLiteWalletControllerM4(); testLiteWalletControllerM5Persistence(); testLiteChainNameMigration(); testLiteRefreshModelAppliesToWalletState(); testLitePerAddressBalances(); testLiteWalletControllerNewAddress(); testLiteSyncStatusParserRealShapes(); testLiteWalletControllerRefreshPopulatesState(); testLiteWalletGatewayRefreshSkipsFailedCommand(); testLiteWalletControllerWorkerProducesModel(); testLiteWalletControllerShutdownDoesNotHangDuringSync(); testLiteBridgeRuntimeShutdownIsIdempotent(); testLiteBridgeRuntimeDestructorCallsShutdownOnce(); testLiteBridgeRuntimeShutdownWaitsForOwnedStringRelease(); testLiteBridgeRuntimeDestructorShutdownWaitsForExternalOwnedString(); testLiteBridgeRuntimeMoveAssignmentShutsDownReplacedRuntime(); testLiteBridgeRuntimeDryDispatchBoolWrappersAreFakeOnly(); testLiteBridgeRuntimeDryDispatchExecuteOwnsReturnedString(); testLiteBridgeRuntimeDryDispatchLifecycleWrappersRemainFakeOnly(); testLiteBridgeRuntimeDryDispatchRejectsShutdownRuntime(); testLiteBridgeRuntimeDryDispatchShutdownUsesTeardownOwner(); testLiteBridgeRuntimeDryDispatchShutdownWaitsForOwnedString(); testLiteBridgeRuntimeFakeDynamicLoaderUnloadWaitsForOwnedStringRelease(); testLiteBridgeRuntimeFakeDynamicLoaderMoveAssignmentUnloadsReplacedHandle(); testLiteBridgeRuntimeFakeDynamicLoaderNonFakeSourceBlocksDryDispatch(); testLiteBridgeRuntimeUnavailableDoesNotCallShutdown(); testGeneratedResourceBehavior(); if (g_failures != 0) { std::cerr << g_failures << " test assertion(s) failed\n"; return 1; } std::cout << "Focused service tests passed\n"; return 0; }