diff --git a/miner-app/lib/features/withdrawal/claim_rewards_dialog.dart b/miner-app/lib/features/withdrawal/claim_rewards_dialog.dart index 026be5c55..8d2dbb959 100644 --- a/miner-app/lib/features/withdrawal/claim_rewards_dialog.dart +++ b/miner-app/lib/features/withdrawal/claim_rewards_dialog.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:quantus_miner/src/services/binary_manager.dart'; import 'package:quantus_miner/src/services/miner_settings_service.dart'; import 'package:quantus_miner/src/services/miner_wallet_service.dart'; -import 'package:quantus_miner/src/services/wormhole_claim_service.dart'; import 'package:quantus_miner/src/utils/app_logger.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; @@ -30,7 +29,7 @@ enum _Screen { input, confirm, progress } class _ClaimRewardsDialogState extends State<_ClaimRewardsDialog> { final _addressController = TextEditingController(); - final _claimService = WormholeClaimService(); + WormholeClaimService? _claimService; final _walletService = MinerWalletService(); final _settingsService = MinerSettingsService(); @@ -105,14 +104,13 @@ class _ClaimRewardsDialogState extends State<_ClaimRewardsDialog> { final binsDir = '${await BinaryManager.getQuantusHomeDirectoryPath()}/generated-bins'; await Directory(binsDir).create(recursive: true); - final rpcUrl = chainConfig.rpcUrl; + _claimService = WormholeClaimService(rpcUrl: chainConfig.rpcUrl); _log.i('Starting claim for ${keyPair.address} to ${_addressController.text.trim()}'); - final result = await _claimService.claimRewards( + final result = await _claimService!.claimRewards( wormholeAddress: keyPair.address, secretHex: keyPair.secretHex, destinationAddress: _addressController.text.trim(), - rpcUrl: rpcUrl, circuitBinsDir: binsDir, onProgress: (progress) { if (!mounted) return; @@ -147,7 +145,7 @@ class _ClaimRewardsDialogState extends State<_ClaimRewardsDialog> { } void _cancelClaim() { - _claimService.cancel(); + _claimService?.cancel(); } @override diff --git a/mobile-app/analysis_options.yaml b/mobile-app/analysis_options.yaml index 1c434ce4e..e2315f253 100644 --- a/mobile-app/analysis_options.yaml +++ b/mobile-app/analysis_options.yaml @@ -21,6 +21,8 @@ analyzer: - "**/*.g.dart" - "**/*.freezed.dart" - "lib/generated/**" + - "build/**" + errors: avoid_print: ignore # print is the most reliable way to debug the app missing_required_param: error diff --git a/mobile-app/fix_ios_build.sh b/mobile-app/fix_ios_build.sh old mode 100644 new mode 100755 diff --git a/mobile-app/ios/Runner/Runner.entitlements b/mobile-app/ios/Runner/Runner.entitlements index fa27cd68b..2af29348d 100644 --- a/mobile-app/ios/Runner/Runner.entitlements +++ b/mobile-app/ios/Runner/Runner.entitlements @@ -8,5 +8,7 @@ applinks:www.quantus.com + com.apple.developer.kernel.extended-virtual-addressing + diff --git a/mobile-app/lib/generated/version.g.dart b/mobile-app/lib/generated/version.g.dart index 98166afb0..42207b05d 100644 --- a/mobile-app/lib/generated/version.g.dart +++ b/mobile-app/lib/generated/version.g.dart @@ -1,2 +1,2 @@ const appVersion = '1.5.0'; -const appBuildNumber = '106'; +const appBuildNumber = '107'; diff --git a/mobile-app/lib/v2/components/address_input_field.dart b/mobile-app/lib/v2/components/address_input_field.dart new file mode 100644 index 000000000..c42da5bd9 --- /dev/null +++ b/mobile-app/lib/v2/components/address_input_field.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +/// Dual-state recipient input. When the controller holds a valid SS58 address +/// (`hasValid`), shows a pill with the truncated address and optional human +/// checksum. Otherwise shows a regular search-style input. +class AddressInputField extends StatelessWidget { + final TextEditingController controller; + final FocusNode focusNode; + final bool hasValid; + final String? recipientChecksum; + final String hintText; + final bool showSearchIcon; + + const AddressInputField({ + super.key, + required this.controller, + required this.focusNode, + required this.hasValid, + required this.recipientChecksum, + this.hintText = 'Search ${AppConstants.tokenSymbol} Address', + this.showSearchIcon = true, + }); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return SizedBox( + height: 48, + child: Stack( + children: [ + Positioned.fill( + child: IgnorePointer( + ignoring: hasValid, + child: Opacity( + opacity: hasValid ? 0 : 1, + child: Container( + padding: const EdgeInsets.only(left: 12, right: 8), + decoration: BoxDecoration(color: colors.sheetBackground, borderRadius: BorderRadius.circular(8)), + child: Row( + children: [ + if (showSearchIcon) ...[ + Icon(Icons.search, size: 14, color: colors.textLabel), + const SizedBox(width: 12), + ], + Expanded( + child: TextField( + controller: controller, + focusNode: focusNode, + keyboardType: TextInputType.text, + textInputAction: TextInputAction.done, + autocorrect: false, + enableSuggestions: false, + textCapitalization: TextCapitalization.none, + scrollPadding: const EdgeInsets.only(bottom: 120), + style: text.smallParagraph?.copyWith(color: colors.textPrimary), + decoration: InputDecoration(hintText: hintText), + ), + ), + ], + ), + ), + ), + ), + ), + if (hasValid) + Positioned.fill( + child: GestureDetector( + onTap: () { + controller.clear(); + focusNode.requestFocus(); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration(color: colors.toasterBackground, borderRadius: BorderRadius.circular(8)), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AddressFormattingService.formatAddress(controller.text.trim()), + style: text.smallParagraph?.copyWith( + color: colors.textPrimary, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (recipientChecksum != null) + Text(recipientChecksum!, style: text.detail?.copyWith(color: colors.checksum)), + ], + ), + ), + Icon(Icons.edit_outlined, size: 16, color: colors.textLabel), + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/mobile-app/lib/v2/components/recent_addresses_list.dart b/mobile-app/lib/v2/components/recent_addresses_list.dart new file mode 100644 index 000000000..f427c98c5 --- /dev/null +++ b/mobile-app/lib/v2/components/recent_addresses_list.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:resonance_network_wallet/features/components/skeleton.dart'; +import 'package:resonance_network_wallet/providers/wallet_providers.dart'; +import 'package:resonance_network_wallet/v2/components/address_checkphrase_with_initial.dart'; +import 'package:resonance_network_wallet/v2/components/loader.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +/// Recents list backed by [recentAddressesServiceProvider]. Filters out any +/// addresses in [excludeAddresses] (typically the user's own active account). +/// Renders as slivers so it can live inside the screen's main CustomScrollView. +class RecentAddressesSlivers extends ConsumerStatefulWidget { + final Set excludeAddresses; + final ValueChanged onTap; + final String title; + + const RecentAddressesSlivers({ + super.key, + required this.excludeAddresses, + required this.onTap, + this.title = 'Recents', + }); + + @override + ConsumerState createState() => _RecentAddressesSliversState(); +} + +class _RecentAddressesSliversState extends ConsumerState { + final Map _checksums = {}; + List _recents = []; + bool _loading = true; + + @override + void initState() { + super.initState(); + _load(); + } + + Future _load() async { + final recentAddressesService = ref.read(recentAddressesServiceProvider); + final checksumService = ref.read(humanReadableChecksumServiceProvider); + try { + final all = await recentAddressesService.getAddresses(); + final addresses = all.where((a) => !widget.excludeAddresses.contains(a)).toList(); + if (!mounted) return; + setState(() { + _recents = addresses; + _loading = false; + }); + for (final addr in addresses) { + checksumService.getHumanReadableName(addr).then((name) { + if (mounted) setState(() => _checksums[addr] = name); + }); + } + } catch (e) { + debugPrint('RecentAddressesSlivers load failed: $e'); + if (mounted) setState(() => _loading = false); + } + } + + @override + Widget build(BuildContext context) { + if (_loading) { + return const SliverFillRemaining(hasScrollBody: false, child: Center(child: Loader())); + } + if (_recents.isEmpty) { + return const SliverFillRemaining(hasScrollBody: false, child: SizedBox.shrink()); + } + + final colors = context.colors; + final text = context.themeText; + + return SliverMainAxisGroup( + slivers: [ + SliverToBoxAdapter( + child: Text(widget.title, style: text.smallTitle?.copyWith(color: colors.textPrimary)), + ), + const SliverToBoxAdapter(child: SizedBox(height: 32)), + SliverList( + delegate: SliverChildBuilderDelegate((context, i) { + final isFirst = i == 0; + final isLast = i == _recents.length - 1; + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + if (!isFirst) const SizedBox(height: 14), + _recentRow(_recents[i], colors), + if (!isLast) ...[const SizedBox(height: 14), Divider(height: 1, color: colors.txItemSeparator)], + ], + ); + }, childCount: _recents.length), + ), + ], + ); + } + + Widget _recentRow(String address, AppColorsV2 colors) { + final checksum = _checksums[address]; + + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () => widget.onTap(address), + borderRadius: BorderRadius.circular(8), + child: checksum != null + ? AddressCheckphraseWithInitial(recipientChecksum: checksum, recipientAddress: address) + : const Skeleton(height: 36), + ), + ); + } +} diff --git a/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart b/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart index 9b2eaa11b..bee4337a3 100644 --- a/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart +++ b/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart @@ -11,6 +11,7 @@ import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; import 'package:resonance_network_wallet/v2/components/split_card.dart'; import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; +import 'package:resonance_network_wallet/v2/screens/settings/redeem_address_screen.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -35,9 +36,21 @@ class MiningRewardsScreen extends ConsumerWidget { ), ], bottomContent: miningAsync.when( - data: (data) => data.totalBlocks > 0 - ? const ScaffoldBaseBottomContent(child: QuantusButton.simple(label: 'Redeem', onTap: null)) - : null, + data: (data) { + if (data.totalBlocks == 0) return null; + final canRedeem = data.redeemableRewards > BigInt.zero; + return ScaffoldBaseBottomContent( + child: QuantusButton.simple( + label: 'Redeem', + isDisabled: !canRedeem, + onTap: canRedeem + ? () => Navigator.of(context).push( + MaterialPageRoute(builder: (_) => RedeemAddressScreen(redeemableRewards: data.redeemableRewards)), + ) + : null, + ), + ); + }, loading: () => null, error: (err, _) => null, ), diff --git a/mobile-app/lib/v2/screens/settings/redeem_address_screen.dart b/mobile-app/lib/v2/screens/settings/redeem_address_screen.dart new file mode 100644 index 000000000..4028c6ec6 --- /dev/null +++ b/mobile-app/lib/v2/screens/settings/redeem_address_screen.dart @@ -0,0 +1,217 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/providers/wallet_providers.dart'; +import 'package:resonance_network_wallet/v2/components/address_input_field.dart'; +import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart'; +import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; +import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; +import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; +import 'package:resonance_network_wallet/v2/screens/settings/redeem_progress_screen.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +/// Fullscreen flow to pick a destination address for redeeming mining +/// rewards. Defaults the field to the user's primary account (Account 0). +class RedeemAddressScreen extends ConsumerStatefulWidget { + final BigInt redeemableRewards; + + const RedeemAddressScreen({super.key, required this.redeemableRewards}); + + @override + ConsumerState createState() => _RedeemAddressScreenState(); +} + +class _RedeemAddressScreenState extends ConsumerState { + final _recipientController = TextEditingController(); + final _recipientFocus = FocusNode(); + + bool _hasAddressError = true; + String? _recipientChecksum; + + @override + void initState() { + super.initState(); + _recipientController.addListener(_onRecipientChanged); + _prefillPrimaryAccount(); + } + + @override + void dispose() { + _recipientController.removeListener(_onRecipientChanged); + _recipientController.dispose(); + _recipientFocus.dispose(); + super.dispose(); + } + + Future _prefillPrimaryAccount() async { + final settings = ref.read(settingsServiceProvider); + final primary = await settings.getAccount(walletIndex: 0, index: 0); + if (!mounted || primary == null) return; + _recipientController.text = primary.accountId; + } + + void _onRecipientChanged() { + final text = _recipientController.text.trim(); + if (text.isEmpty) { + setState(() { + _hasAddressError = true; + _recipientChecksum = null; + }); + return; + } + final substrate = ref.read(substrateServiceProvider); + final isValid = substrate.isValidSS58Address(text); + setState(() { + _hasAddressError = !isValid; + _recipientChecksum = null; + }); + if (isValid) { + ref.read(humanReadableChecksumServiceProvider).getHumanReadableName(text).then((checksum) { + if (mounted) setState(() => _recipientChecksum = checksum); + }); + } + } + + bool get _canRedeem => + _recipientController.text.trim().isNotEmpty && !_hasAddressError && widget.redeemableRewards > BigInt.zero; + + Future _redeem() async { + if (!_canRedeem) return; + final destination = _recipientController.text.trim(); + final fmt = ref.read(numberFormattingServiceProvider); + final formatted = fmt.formatBalance(widget.redeemableRewards, maxDecimals: 2, addSymbol: true); + + final confirmed = await BottomSheetContainer.show( + context, + builder: (_) => _RedeemConfirmSheet(formatted: formatted, destination: destination), + ); + if (confirmed != true || !mounted) return; + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (_) => + RedeemProgressScreen(redeemableRewards: widget.redeemableRewards, destinationAddress: destination), + ), + ); + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + final fmt = ref.watch(numberFormattingServiceProvider); + + final hasValid = _recipientController.text.trim().isNotEmpty && !_hasAddressError; + final amountLabel = fmt.formatBalance(widget.redeemableRewards, maxDecimals: 2, addSymbol: true); + + return ScaffoldBase( + appBar: const V2AppBar(title: 'Redeem'), + mainContent: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _AmountSummary(amountLabel: amountLabel, colors: colors, text: text), + const SizedBox(height: 28), + Text('Redeem To', style: text.sendSectionLabel?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 12), + AddressInputField( + controller: _recipientController, + focusNode: _recipientFocus, + hasValid: hasValid, + recipientChecksum: _recipientChecksum, + hintText: 'Paste a ${AppConstants.tokenSymbol} Address', + showSearchIcon: false, + ), + ], + ), + bottomContent: ScaffoldBaseBottomContent( + child: QuantusButton.simple( + label: _canRedeem ? 'Redeem $amountLabel' : 'Enter Address', + variant: ButtonVariant.primary, + isDisabled: !_canRedeem, + onTap: _redeem, + ), + ), + ); + } +} + +class _AmountSummary extends StatelessWidget { + final String amountLabel; + final AppColorsV2 colors; + final AppTextTheme text; + + const _AmountSummary({required this.amountLabel, required this.colors, required this.text}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration(color: colors.sheetBackground, borderRadius: BorderRadius.circular(12)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('REDEEMABLE', style: text.receiveLabel?.copyWith(color: colors.textLabel)), + FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerRight, + child: Text( + amountLabel, + maxLines: 1, + softWrap: false, + style: text.sendSectionLabel?.copyWith(color: colors.success), + ), + ), + ], + ), + ); + } +} + +class _RedeemConfirmSheet extends StatelessWidget { + final String formatted; + final String destination; + + const _RedeemConfirmSheet({required this.formatted, required this.destination}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return BottomSheetContainer( + title: 'Confirm Redeem', + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _row('Amount', formatted, colors, text), + Divider(color: colors.separator, height: 32), + _row('To', AddressFormattingService.formatAddress(destination), colors, text), + Divider(color: colors.separator, height: 32), + _row('Fee', '0.1% volume fee', colors, text), + const SizedBox(height: 32), + QuantusButton.simple(label: 'Redeem $formatted', onTap: () => Navigator.of(context).pop(true)), + ], + ), + ); + } + + Widget _row(String label, String value, AppColorsV2 colors, AppTextTheme text) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, style: text.smallParagraph?.copyWith(color: colors.textSecondary)), + Flexible( + child: Text( + value, + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + textAlign: TextAlign.end, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ); + } +} diff --git a/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart b/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart new file mode 100644 index 000000000..2378e0007 --- /dev/null +++ b/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart @@ -0,0 +1,390 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/providers/mining_rewards_provider.dart'; +import 'package:resonance_network_wallet/providers/wallet_providers.dart'; +import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; +import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; +import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class RedeemProgressScreen extends ConsumerStatefulWidget { + final BigInt redeemableRewards; + final String destinationAddress; + + const RedeemProgressScreen({super.key, required this.redeemableRewards, required this.destinationAddress}); + + @override + ConsumerState createState() => _RedeemProgressScreenState(); +} + +class _RedeemProgressScreenState extends ConsumerState { + WormholeClaimService? _claimService; + bool _running = true; + bool _done = false; + bool _cancelled = false; + String? _errorMessage; + int _currentStep = 0; + final Map _stepProgress = {}; + ClaimResult? _result; + + @override + void initState() { + super.initState(); + _startClaim(); + } + + Future _startClaim() async { + try { + final mnemonic = await ref.read(settingsServiceProvider).getMnemonic(0); + if (mnemonic == null) throw StateError('Mnemonic not found'); + + final keyPair = ref.read(hdWalletServiceProvider).deriveWormholeKeyPair(mnemonic: mnemonic); + if (keyPair.secretHex.isEmpty) throw StateError('Wormhole key pair not available'); + + final circuitDir = await CircuitManager.getCircuitDirectory(); + _claimService = WormholeClaimService( + maxProofsPerBatch: 16, + proofConcurrency: 1, + freshBuild: true, + provingThreads: 8, + ); + + final result = await _claimService!.claimRewards( + wormholeAddress: keyPair.address, + secretHex: keyPair.secretHex, + destinationAddress: widget.destinationAddress, + circuitBinsDir: circuitDir, + onProgress: (progress) { + if (!mounted) return; + setState(() { + _currentStep = progress.step; + _stepProgress[progress.step] = progress; + }); + }, + ); + + if (!mounted) return; + setState(() { + _done = true; + _running = false; + _result = result; + }); + ref.invalidate(miningRewardsProvider); + } on ClaimCancelled { + if (!mounted) return; + setState(() { + _running = false; + _cancelled = true; + }); + } catch (e) { + print('[Redeem] Claim failed: $e'); + if (!mounted) return; + setState(() { + _running = false; + _errorMessage = e.toString(); + }); + } + } + + void _cancel() { + _claimService?.cancel(); + } + + void _retry() { + setState(() { + _running = true; + _done = false; + _cancelled = false; + _errorMessage = null; + _currentStep = 0; + _stepProgress.clear(); + _result = null; + }); + _startClaim(); + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return PopScope( + canPop: !_running, + child: ScaffoldBase( + appBar: V2AppBar( + title: _done + ? 'Redeem Complete' + : _errorMessage != null + ? 'Redeem Failed' + : 'Redeeming...', + showBackButton: !_running, + ), + mainContent: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 24), + _buildStatusHeader(colors, text), + const SizedBox(height: 32), + _buildSteps(colors, text), + if (_errorMessage != null) ...[const SizedBox(height: 24), _buildErrorBanner(colors, text)], + if (_done && _result != null) ...[const SizedBox(height: 24), _buildSuccessBanner(colors, text)], + ], + ), + bottomContent: _buildBottomContent(colors), + ), + ); + } + + Widget _buildStatusHeader(AppColorsV2 colors, AppTextTheme text) { + final fmt = ref.watch(numberFormattingServiceProvider); + final amountLabel = fmt.formatBalance(widget.redeemableRewards, maxDecimals: 2, addSymbol: true); + + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration(color: colors.sheetBackground, borderRadius: BorderRadius.circular(12)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('REDEEMING', style: text.receiveLabel?.copyWith(color: colors.textLabel)), + Text(amountLabel, style: text.sendSectionLabel?.copyWith(color: colors.success)), + ], + ), + ); + } + + Widget _buildSteps(AppColorsV2 colors, AppTextTheme text) { + const steps = [ + (1, 'Preparing circuits'), + (2, 'Fetching transfers'), + (3, 'Computing nullifiers'), + (4, 'Checking nullifiers'), + (5, 'Generating ZK proofs'), + (6, 'Aggregating & submitting'), + ]; + + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration(color: colors.sheetBackground, borderRadius: BorderRadius.circular(12)), + child: Column( + children: [ + for (int i = 0; i < steps.length; i++) ...[ + _buildStepRow(steps[i].$1, steps[i].$2, colors, text), + if (i < steps.length - 1) _buildConnector(steps[i].$1, colors), + ], + ], + ), + ); + } + + Widget _buildStepRow(int step, String title, AppColorsV2 colors, AppTextTheme text) { + final isCompleted = _done ? true : _currentStep > step; + final isActive = !_done && !_cancelled && _currentStep == step && _errorMessage == null; + final isError = !_done && _currentStep == step && _errorMessage != null; + final progress = _stepProgress[step]; + + final Widget icon; + if (isCompleted) { + icon = Container( + width: 28, + height: 28, + decoration: BoxDecoration(color: colors.success, shape: BoxShape.circle), + child: const Icon(Icons.check, color: Colors.white, size: 16), + ); + } else if (isError) { + icon = Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: colors.textError.withValues(alpha: 0.15), + shape: BoxShape.circle, + border: Border.all(color: colors.textError, width: 2), + ), + child: Icon(Icons.close, color: colors.textError, size: 14), + ); + } else if (isActive) { + icon = Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: colors.success.withValues(alpha: 0.12), + shape: BoxShape.circle, + border: Border.all(color: colors.success, width: 2), + ), + child: Padding( + padding: const EdgeInsets.all(5), + child: CircularProgressIndicator(strokeWidth: 2, color: colors.success), + ), + ); + } else { + icon = Container( + width: 28, + height: 28, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: colors.borderButton.withValues(alpha: 0.3), width: 1.5), + ), + child: Center( + child: Text('$step', style: text.detail?.copyWith(color: colors.textTertiary)), + ), + ); + } + + final titleColor = isCompleted + ? colors.success + : isActive + ? colors.textPrimary + : isError + ? colors.textError + : colors.textTertiary; + + String progressText = ''; + if (progress != null && (isActive || isCompleted)) { + if (step == 2) { + progressText = '${progress.completed} fetched'; + } else if (progress.total != null) { + progressText = '${progress.completed} / ${progress.total}'; + } + } + + double? progressFraction; + if (isActive && progress != null && progress.total != null && progress.total! > 0) { + progressFraction = (progress.completed / progress.total!).clamp(0.0, 1.0); + } + + return Column( + children: [ + Row( + children: [ + icon, + const SizedBox(width: 14), + Expanded( + child: Text(title, style: text.smallParagraph?.copyWith(color: titleColor)), + ), + if (progressText.isNotEmpty) + Text( + progressText, + style: text.detail?.copyWith( + color: isCompleted ? colors.success : colors.textPrimary, + fontFamily: AppTextTheme.fontFamilySecondary, + ), + ), + ], + ), + if (progressFraction != null) ...[ + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.only(left: 42), + child: ClipRRect( + borderRadius: BorderRadius.circular(3), + child: LinearProgressIndicator( + value: progressFraction, + backgroundColor: colors.borderButton.withValues(alpha: 0.2), + valueColor: AlwaysStoppedAnimation(colors.success), + minHeight: 4, + ), + ), + ), + ], + ], + ); + } + + Widget _buildConnector(int afterStep, AppColorsV2 colors) { + final isCompleted = _done ? true : _currentStep > afterStep; + return Padding( + padding: const EdgeInsets.only(left: 13), + child: Align( + alignment: Alignment.centerLeft, + child: Container( + width: 2, + height: 20, + decoration: BoxDecoration( + color: isCompleted ? colors.success.withValues(alpha: 0.4) : colors.borderButton.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(1), + ), + ), + ), + ); + } + + Widget _buildErrorBanner(AppColorsV2 colors, AppTextTheme text) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colors.textError.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: colors.textError.withValues(alpha: 0.2)), + ), + child: Row( + children: [ + Icon(Icons.error_outline, color: colors.textError, size: 16), + const SizedBox(width: 8), + Expanded( + child: Text(_errorMessage!, style: text.detail?.copyWith(color: colors.textError)), + ), + ], + ), + ); + } + + Widget _buildSuccessBanner(AppColorsV2 colors, AppTextTheme text) { + final fmt = ref.watch(numberFormattingServiceProvider); + final withdrawn = fmt.formatBalance(_result!.totalWithdrawn, maxDecimals: 4, addSymbol: true); + + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colors.success.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: colors.success.withValues(alpha: 0.2)), + ), + child: Row( + children: [ + Icon(Icons.check_circle_outline, color: colors.success, size: 16), + const SizedBox(width: 8), + Expanded( + child: Text( + '$withdrawn redeemed in ${_result!.batchesSubmitted} batch(es)', + style: text.detail?.copyWith(color: colors.success), + ), + ), + ], + ), + ); + } + + Widget? _buildBottomContent(AppColorsV2 colors) { + if (_running) { + return ScaffoldBaseBottomContent( + child: QuantusButton.simple(label: 'Cancel', variant: ButtonVariant.secondary, onTap: _cancel), + ); + } + + if (_errorMessage != null) { + return ScaffoldBaseBottomContent( + child: Row( + children: [ + Expanded( + child: QuantusButton.simple(label: 'Retry', onTap: _retry), + ), + const SizedBox(width: 12), + Expanded( + child: QuantusButton.simple( + label: 'Close', + variant: ButtonVariant.secondary, + onTap: () => Navigator.of(context).pop(), + ), + ), + ], + ), + ); + } + + return ScaffoldBaseBottomContent( + child: QuantusButton.simple(label: 'Done', onTap: () => Navigator.of(context).popUntil((route) => route.isFirst)), + ); + } +} diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 0d743e03e..d43dedb63 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -2,7 +2,7 @@ name: resonance_network_wallet description: A Flutter wallet for the Quantus blockchain. publish_to: "none" -version: 1.5.0+106 +version: 1.5.0+107 environment: sdk: ">=3.8.0 <4.0.0" diff --git a/quantus_sdk/lib/quantus_sdk.dart b/quantus_sdk/lib/quantus_sdk.dart index 212f0d196..39c25dbb5 100644 --- a/quantus_sdk/lib/quantus_sdk.dart +++ b/quantus_sdk/lib/quantus_sdk.dart @@ -66,6 +66,7 @@ export 'src/services/taskmaster_service.dart'; export 'src/services/senoti_service.dart'; export 'src/services/circuit_manager.dart'; export 'src/services/wormhole_address_manager.dart'; +export 'src/services/wormhole_claim_service.dart'; export 'src/services/wormhole_utxo_service.dart'; export 'src/extensions/account_extension.dart'; export 'src/quantus_signing_payload.dart'; diff --git a/quantus_sdk/lib/src/rust/api/wormhole.dart b/quantus_sdk/lib/src/rust/api/wormhole.dart index 11714503a..c4648e9d4 100644 --- a/quantus_sdk/lib/src/rust/api/wormhole.dart +++ b/quantus_sdk/lib/src/rust/api/wormhole.dart @@ -6,7 +6,8 @@ import '../frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; -// These functions are ignored because they are not marked as `pub`: `all_required_files_exist`, `vec_to_32`, `vec_to_digest` +// These functions are ignored because they are not marked as `pub`: `all_required_files_exist`, `leaf_files_exist`, `log_mem`, `process_memory`, `read_task_vm_info`, `vec_to_32`, `vec_to_digest` +// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `TaskVmInfoRev1` String computeAddressHashHex({required List rawAddress}) => RustLib.instance.api.crateApiWormholeComputeAddressHashHex(rawAddress: rawAddress); @@ -42,6 +43,13 @@ MerkleProcessed computeMerklePositions({ Future ensureCircuitBinaries({required String binsDir}) => RustLib.instance.api.crateApiWormholeEnsureCircuitBinaries(binsDir: binsDir); +/// Lightweight variant that only generates leaf circuit binaries (prover.bin, +/// common.bin, verifier.bin, dummy_proof.bin). Skips the heavy aggregation +/// circuit generation entirely. Use with `aggregate_proofs_fresh` which builds +/// the aggregation circuit in memory at proving time. +Future ensureLeafCircuitBinaries({required String binsDir}) => + RustLib.instance.api.crateApiWormholeEnsureLeafCircuitBinaries(binsDir: binsDir); + Future generateProof({ required ProofInput input, required String proverBinPath, @@ -55,6 +63,37 @@ Future generateProof({ Future aggregateProofs({required List proofBytesList, required String binsDir}) => RustLib.instance.api.crateApiWormholeAggregateProofs(proofBytesList: proofBytesList, binsDir: binsDir); +/// Dump every field of task_vm_info for deep debugging. Call sparingly. +Future logMemDetailed({required String tag}) => RustLib.instance.api.crateApiWormholeLogMemDetailed(tag: tag); + +/// Returns (phys_footprint_bytes, virtual_bytes). Useful for Dart-side polling. +(BigInt, BigInt) getProcessMemory() => RustLib.instance.api.crateApiWormholeGetProcessMemory(); + +/// Returns (phys_footprint, resident_size, compressed). Dart-side memory probe. +(BigInt, BigInt, BigInt) getProcessMemoryDetailed() => RustLib.instance.api.crateApiWormholeGetProcessMemoryDetailed(); + +/// Dart-side hook to dump every memory field. +void logMemorySnapshot({required String tag}) => RustLib.instance.api.crateApiWormholeLogMemorySnapshot(tag: tag); + +/// Force the system allocator (Apple libmalloc) to return freed pages to the OS. +/// On other platforms this is a no-op. +/// Apple's malloc keeps freed memory in per-zone caches by default; after a heavy +/// allocation phase like ZK proving this can leave 100s of MB of dirty pages. +/// We iterate every registered zone — passing `null` only reaches the default +/// zone, which is NOT where Rust's allocator lives, so calling on null released +/// 0 bytes in practice. +Future releaseMemory() => RustLib.instance.api.crateApiWormholeReleaseMemory(); + +/// Limit rayon parallelism to reduce peak memory during proving. +/// Plonky2 multiplies its per-thread FFT buffers by the rayon pool size, so +/// fewer threads = lower peak memory (at the cost of wall-clock time). +/// Idempotent / safe to call repeatedly; only the first call wins. +Future setProvingThreadCount({required int numThreads}) => + RustLib.instance.api.crateApiWormholeSetProvingThreadCount(numThreads: numThreads); + +Future aggregateProofsFresh({required List proofBytesList, required String binsDir}) => + RustLib.instance.api.crateApiWormholeAggregateProofsFresh(proofBytesList: proofBytesList, binsDir: binsDir); + class MerkleProcessed { final Uint8List sortedSiblingsFlat; final Uint8List positions; diff --git a/quantus_sdk/lib/src/rust/frb_generated.dart b/quantus_sdk/lib/src/rust/frb_generated.dart index 71530da69..ab2da38f5 100644 --- a/quantus_sdk/lib/src/rust/frb_generated.dart +++ b/quantus_sdk/lib/src/rust/frb_generated.dart @@ -64,7 +64,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.12.0'; @override - int get rustContentHash => 623793143; + int get rustContentHash => 1186293619; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( stem: 'rust_lib_quantus_wallet', @@ -77,6 +77,11 @@ class RustLib extends BaseEntrypoint { abstract class RustLibApi extends BaseApi { Future crateApiWormholeAggregateProofs({required List proofBytesList, required String binsDir}); + Future crateApiWormholeAggregateProofsFresh({ + required List proofBytesList, + required String binsDir, + }); + String crateApiWormholeComputeAddressHashHex({required List rawAddress}); MerkleProcessed crateApiWormholeComputeMerklePositions({ @@ -111,6 +116,8 @@ abstract class RustLibApi extends BaseApi { Future crateApiWormholeEnsureCircuitBinaries({required String binsDir}); + Future crateApiWormholeEnsureLeafCircuitBinaries({required String binsDir}); + String crateApiCryptoFirstHashToAddress({required String firstHashHex}); Keypair crateApiCryptoGenerateDerivedKeypair({required String mnemonicStr, required String path}); @@ -125,16 +132,28 @@ abstract class RustLibApi extends BaseApi { required String commonBinPath, }); + (BigInt, BigInt) crateApiWormholeGetProcessMemory(); + + (BigInt, BigInt, BigInt) crateApiWormholeGetProcessMemoryDetailed(); + Future crateApiCryptoInitApp(); bool crateApiUrIsCompleteUr({required List urParts}); + Future crateApiWormholeLogMemDetailed({required String tag}); + + void crateApiWormholeLogMemorySnapshot({required String tag}); + BigInt crateApiCryptoPublicKeyBytes(); + Future crateApiWormholeReleaseMemory(); + BigInt crateApiCryptoSecretKeyBytes(); void crateApiCryptoSetDefaultSs58Prefix({required int prefix}); + Future crateApiWormholeSetProvingThreadCount({required int numThreads}); + Uint8List crateApiCryptoSignMessage({required Keypair keypair, required List message, U8Array32? entropy}); Uint8List crateApiCryptoSignMessageWithPubkey({ @@ -196,6 +215,30 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiWormholeAggregateProofsConstMeta => const TaskConstMeta(debugName: 'aggregate_proofs', argNames: ['proofBytesList', 'binsDir']); + @override + Future crateApiWormholeAggregateProofsFresh({ + required List proofBytesList, + required String binsDir, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_list_prim_u_8_strict(proofBytesList, serializer); + sse_encode_String(binsDir, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 2, port: port_); + }, + codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String), + constMeta: kCrateApiWormholeAggregateProofsFreshConstMeta, + argValues: [proofBytesList, binsDir], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWormholeAggregateProofsFreshConstMeta => + const TaskConstMeta(debugName: 'aggregate_proofs_fresh', argNames: ['proofBytesList', 'binsDir']); + @override String crateApiWormholeComputeAddressHashHex({required List rawAddress}) { return handler.executeSync( @@ -203,7 +246,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(rawAddress, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 2)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 3)!; }, codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeComputeAddressHashHexConstMeta, @@ -229,7 +272,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_loose(unsortedSiblingsFlat, serializer); sse_encode_list_prim_u_8_loose(leafHash, serializer); sse_encode_u_32(depth, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 3)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 4)!; }, codec: SseCodec(decodeSuccessData: sse_decode_merkle_processed, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeComputeMerklePositionsConstMeta, @@ -252,7 +295,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(secret, serializer); sse_encode_u_64(transferCount, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 4)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 5)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeComputeNullifierConstMeta, @@ -272,7 +315,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(secret, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 5)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 6)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeComputeWormholeAddressConstMeta, @@ -291,7 +334,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 6)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 7)!; }, codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null), constMeta: kCrateApiCryptoCrystalAliceConstMeta, @@ -310,7 +353,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 7)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 8)!; }, codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null), constMeta: kCrateApiCryptoCrystalBobConstMeta, @@ -328,7 +371,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 8)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 9)!; }, codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null), constMeta: kCrateApiCryptoCrystalCharlieConstMeta, @@ -348,7 +391,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(leafData, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 9)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 10)!; }, codec: SseCodec(decodeSuccessData: sse_decode_u_32, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeDecodeLeafAmountConstMeta, @@ -368,7 +411,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(leafData, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 10)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 11)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeDecodeLeafToAccountConstMeta, @@ -388,7 +431,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(leafData, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 11)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 12)!; }, codec: SseCodec(decodeSuccessData: sse_decode_u_64, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeDecodeLeafTransferCountConstMeta, @@ -408,7 +451,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_String(urParts, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 12)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 13)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String), constMeta: kCrateApiUrDecodeUrConstMeta, @@ -428,7 +471,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(seed, serializer); sse_encode_String(path, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 13)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 14)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: null), constMeta: kCrateApiCryptoDeriveHdPathConstMeta, @@ -449,7 +492,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(mnemonicStr, serializer); sse_encode_String(path, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 14)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 15)!; }, codec: SseCodec( decodeSuccessData: sse_decode_wormhole_result, @@ -473,7 +516,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(data, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 15)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 16)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_String, decodeErrorData: sse_decode_String), constMeta: kCrateApiUrEncodeUrConstMeta, @@ -492,7 +535,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(binsDir, serializer); - pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 16, port: port_); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 17, port: port_); }, codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeEnsureCircuitBinariesConstMeta, @@ -505,6 +548,26 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiWormholeEnsureCircuitBinariesConstMeta => const TaskConstMeta(debugName: 'ensure_circuit_binaries', argNames: ['binsDir']); + @override + Future crateApiWormholeEnsureLeafCircuitBinaries({required String binsDir}) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(binsDir, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 18, port: port_); + }, + codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: sse_decode_String), + constMeta: kCrateApiWormholeEnsureLeafCircuitBinariesConstMeta, + argValues: [binsDir], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWormholeEnsureLeafCircuitBinariesConstMeta => + const TaskConstMeta(debugName: 'ensure_leaf_circuit_binaries', argNames: ['binsDir']); + @override String crateApiCryptoFirstHashToAddress({required String firstHashHex}) { return handler.executeSync( @@ -512,7 +575,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(firstHashHex, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 17)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 19)!; }, codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: sse_decode_String), constMeta: kCrateApiCryptoFirstHashToAddressConstMeta, @@ -533,7 +596,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(mnemonicStr, serializer); sse_encode_String(path, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 18)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 20)!; }, codec: SseCodec( decodeSuccessData: sse_decode_keypair, @@ -557,7 +620,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(mnemonicStr, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 19)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 21)!; }, codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null), constMeta: kCrateApiCryptoGenerateKeypairConstMeta, @@ -577,7 +640,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(seed, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 20)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 22)!; }, codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null), constMeta: kCrateApiCryptoGenerateKeypairFromSeedConstMeta, @@ -603,7 +666,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_box_autoadd_proof_input(input, serializer); sse_encode_String(proverBinPath, serializer); sse_encode_String(commonBinPath, serializer); - pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 21, port: port_); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 23, port: port_); }, codec: SseCodec(decodeSuccessData: sse_decode_proof_output, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeGenerateProofConstMeta, @@ -616,13 +679,51 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiWormholeGenerateProofConstMeta => const TaskConstMeta(debugName: 'generate_proof', argNames: ['input', 'proverBinPath', 'commonBinPath']); + @override + (BigInt, BigInt) crateApiWormholeGetProcessMemory() { + return handler.executeSync( + SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 24)!; + }, + codec: SseCodec(decodeSuccessData: sse_decode_record_u_64_u_64, decodeErrorData: null), + constMeta: kCrateApiWormholeGetProcessMemoryConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWormholeGetProcessMemoryConstMeta => + const TaskConstMeta(debugName: 'get_process_memory', argNames: []); + + @override + (BigInt, BigInt, BigInt) crateApiWormholeGetProcessMemoryDetailed() { + return handler.executeSync( + SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 25)!; + }, + codec: SseCodec(decodeSuccessData: sse_decode_record_u_64_u_64_u_64, decodeErrorData: null), + constMeta: kCrateApiWormholeGetProcessMemoryDetailedConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWormholeGetProcessMemoryDetailedConstMeta => + const TaskConstMeta(debugName: 'get_process_memory_detailed', argNames: []); + @override Future crateApiCryptoInitApp() { return handler.executeNormal( NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); - pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 22, port: port_); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 26, port: port_); }, codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: null), constMeta: kCrateApiCryptoInitAppConstMeta, @@ -641,7 +742,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_String(urParts, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 23)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 27)!; }, codec: SseCodec(decodeSuccessData: sse_decode_bool, decodeErrorData: null), constMeta: kCrateApiUrIsCompleteUrConstMeta, @@ -654,13 +755,53 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiUrIsCompleteUrConstMeta => const TaskConstMeta(debugName: 'is_complete_ur', argNames: ['urParts']); + @override + Future crateApiWormholeLogMemDetailed({required String tag}) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(tag, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 28, port: port_); + }, + codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: sse_decode_String), + constMeta: kCrateApiWormholeLogMemDetailedConstMeta, + argValues: [tag], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWormholeLogMemDetailedConstMeta => + const TaskConstMeta(debugName: 'log_mem_detailed', argNames: ['tag']); + + @override + void crateApiWormholeLogMemorySnapshot({required String tag}) { + return handler.executeSync( + SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(tag, serializer); + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 29)!; + }, + codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: null), + constMeta: kCrateApiWormholeLogMemorySnapshotConstMeta, + argValues: [tag], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWormholeLogMemorySnapshotConstMeta => + const TaskConstMeta(debugName: 'log_memory_snapshot', argNames: ['tag']); + @override BigInt crateApiCryptoPublicKeyBytes() { return handler.executeSync( SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 24)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 30)!; }, codec: SseCodec(decodeSuccessData: sse_decode_usize, decodeErrorData: null), constMeta: kCrateApiCryptoPublicKeyBytesConstMeta, @@ -673,13 +814,32 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiCryptoPublicKeyBytesConstMeta => const TaskConstMeta(debugName: 'public_key_bytes', argNames: []); + @override + Future crateApiWormholeReleaseMemory() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 31, port: port_); + }, + codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: sse_decode_String), + constMeta: kCrateApiWormholeReleaseMemoryConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWormholeReleaseMemoryConstMeta => + const TaskConstMeta(debugName: 'release_memory', argNames: []); + @override BigInt crateApiCryptoSecretKeyBytes() { return handler.executeSync( SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 25)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 32)!; }, codec: SseCodec(decodeSuccessData: sse_decode_usize, decodeErrorData: null), constMeta: kCrateApiCryptoSecretKeyBytesConstMeta, @@ -699,7 +859,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_u_16(prefix, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 26)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 33)!; }, codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: null), constMeta: kCrateApiCryptoSetDefaultSs58PrefixConstMeta, @@ -712,6 +872,26 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiCryptoSetDefaultSs58PrefixConstMeta => const TaskConstMeta(debugName: 'set_default_ss58_prefix', argNames: ['prefix']); + @override + Future crateApiWormholeSetProvingThreadCount({required int numThreads}) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_u_32(numThreads, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 34, port: port_); + }, + codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: sse_decode_String), + constMeta: kCrateApiWormholeSetProvingThreadCountConstMeta, + argValues: [numThreads], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWormholeSetProvingThreadCountConstMeta => + const TaskConstMeta(debugName: 'set_proving_thread_count', argNames: ['numThreads']); + @override Uint8List crateApiCryptoSignMessage({required Keypair keypair, required List message, U8Array32? entropy}) { return handler.executeSync( @@ -721,7 +901,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_box_autoadd_keypair(keypair, serializer); sse_encode_list_prim_u_8_loose(message, serializer); sse_encode_opt_u_8_array_32(entropy, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 27)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 35)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: null), constMeta: kCrateApiCryptoSignMessageConstMeta, @@ -747,7 +927,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_box_autoadd_keypair(keypair, serializer); sse_encode_list_prim_u_8_loose(message, serializer); sse_encode_opt_u_8_array_32(entropy, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 28)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 36)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: null), constMeta: kCrateApiCryptoSignMessageWithPubkeyConstMeta, @@ -766,7 +946,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 29)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 37)!; }, codec: SseCodec(decodeSuccessData: sse_decode_usize, decodeErrorData: null), constMeta: kCrateApiCryptoSignatureBytesConstMeta, @@ -786,7 +966,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(s, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 30)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 38)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: null), constMeta: kCrateApiCryptoSs58ToAccountIdConstMeta, @@ -806,7 +986,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_box_autoadd_keypair(obj, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 31)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 39)!; }, codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: null), constMeta: kCrateApiCryptoToAccountIdConstMeta, @@ -832,7 +1012,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_box_autoadd_keypair(keypair, serializer); sse_encode_list_prim_u_8_loose(message, serializer); sse_encode_list_prim_u_8_loose(signature, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 32)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 40)!; }, codec: SseCodec(decodeSuccessData: sse_decode_bool, decodeErrorData: null), constMeta: kCrateApiCryptoVerifyMessageConstMeta, @@ -853,7 +1033,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_u_32(inputAmount, serializer); sse_encode_u_32(feeBps, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 33)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 41)!; }, codec: SseCodec(decodeSuccessData: sse_decode_u_32, decodeErrorData: null), constMeta: kCrateApiWormholeWormholeComputeOutputAmountConstMeta, @@ -999,6 +1179,26 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + (BigInt, BigInt) dco_decode_record_u_64_u_64(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) { + throw Exception('Expected 2 elements, got ${arr.length}'); + } + return (dco_decode_u_64(arr[0]), dco_decode_u_64(arr[1])); + } + + @protected + (BigInt, BigInt, BigInt) dco_decode_record_u_64_u_64_u_64(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 3) { + throw Exception('Expected 3 elements, got ${arr.length}'); + } + return (dco_decode_u_64(arr[0]), dco_decode_u_64(arr[1]), dco_decode_u_64(arr[2])); + } + @protected int dco_decode_u_16(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1208,6 +1408,23 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return ProofOutput(proofBytes: var_proofBytes, nullifier: var_nullifier); } + @protected + (BigInt, BigInt) sse_decode_record_u_64_u_64(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_field0 = sse_decode_u_64(deserializer); + var var_field1 = sse_decode_u_64(deserializer); + return (var_field0, var_field1); + } + + @protected + (BigInt, BigInt, BigInt) sse_decode_record_u_64_u_64_u_64(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_field0 = sse_decode_u_64(deserializer); + var var_field1 = sse_decode_u_64(deserializer); + var var_field2 = sse_decode_u_64(deserializer); + return (var_field0, var_field1, var_field2); + } + @protected int sse_decode_u_16(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1392,6 +1609,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_strict(self.nullifier, serializer); } + @protected + void sse_encode_record_u_64_u_64((BigInt, BigInt) self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_u_64(self.$1, serializer); + sse_encode_u_64(self.$2, serializer); + } + + @protected + void sse_encode_record_u_64_u_64_u_64((BigInt, BigInt, BigInt) self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_u_64(self.$1, serializer); + sse_encode_u_64(self.$2, serializer); + sse_encode_u_64(self.$3, serializer); + } + @protected void sse_encode_u_16(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs diff --git a/quantus_sdk/lib/src/rust/frb_generated.io.dart b/quantus_sdk/lib/src/rust/frb_generated.io.dart index 3ec4249a9..2244f87c0 100644 --- a/quantus_sdk/lib/src/rust/frb_generated.io.dart +++ b/quantus_sdk/lib/src/rust/frb_generated.io.dart @@ -70,6 +70,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ProofOutput dco_decode_proof_output(dynamic raw); + @protected + (BigInt, BigInt) dco_decode_record_u_64_u_64(dynamic raw); + + @protected + (BigInt, BigInt, BigInt) dco_decode_record_u_64_u_64_u_64(dynamic raw); + @protected int dco_decode_u_16(dynamic raw); @@ -143,6 +149,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ProofOutput sse_decode_proof_output(SseDeserializer deserializer); + @protected + (BigInt, BigInt) sse_decode_record_u_64_u_64(SseDeserializer deserializer); + + @protected + (BigInt, BigInt, BigInt) sse_decode_record_u_64_u_64_u_64(SseDeserializer deserializer); + @protected int sse_decode_u_16(SseDeserializer deserializer); @@ -221,6 +233,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_proof_output(ProofOutput self, SseSerializer serializer); + @protected + void sse_encode_record_u_64_u_64((BigInt, BigInt) self, SseSerializer serializer); + + @protected + void sse_encode_record_u_64_u_64_u_64((BigInt, BigInt, BigInt) self, SseSerializer serializer); + @protected void sse_encode_u_16(int self, SseSerializer serializer); diff --git a/quantus_sdk/lib/src/rust/frb_generated.web.dart b/quantus_sdk/lib/src/rust/frb_generated.web.dart index 0da556119..e63829c9b 100644 --- a/quantus_sdk/lib/src/rust/frb_generated.web.dart +++ b/quantus_sdk/lib/src/rust/frb_generated.web.dart @@ -72,6 +72,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ProofOutput dco_decode_proof_output(dynamic raw); + @protected + (BigInt, BigInt) dco_decode_record_u_64_u_64(dynamic raw); + + @protected + (BigInt, BigInt, BigInt) dco_decode_record_u_64_u_64_u_64(dynamic raw); + @protected int dco_decode_u_16(dynamic raw); @@ -145,6 +151,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ProofOutput sse_decode_proof_output(SseDeserializer deserializer); + @protected + (BigInt, BigInt) sse_decode_record_u_64_u_64(SseDeserializer deserializer); + + @protected + (BigInt, BigInt, BigInt) sse_decode_record_u_64_u_64_u_64(SseDeserializer deserializer); + @protected int sse_decode_u_16(SseDeserializer deserializer); @@ -223,6 +235,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_proof_output(ProofOutput self, SseSerializer serializer); + @protected + void sse_encode_record_u_64_u_64((BigInt, BigInt) self, SseSerializer serializer); + + @protected + void sse_encode_record_u_64_u_64_u_64((BigInt, BigInt, BigInt) self, SseSerializer serializer); + @protected void sse_encode_u_16(int self, SseSerializer serializer); diff --git a/quantus_sdk/lib/src/services/circuit_manager.dart b/quantus_sdk/lib/src/services/circuit_manager.dart index 0ac7b838b..0241079b9 100644 --- a/quantus_sdk/lib/src/services/circuit_manager.dart +++ b/quantus_sdk/lib/src/services/circuit_manager.dart @@ -48,7 +48,7 @@ class CircuitManager { static Future getCircuitDirectory() async { final appDir = await getApplicationSupportDirectory(); - return path.join(appDir.path, 'circuits'); + return path.join(appDir.path, 'circuits_v3'); } Future checkStatus() async { diff --git a/miner-app/lib/src/services/wormhole_claim_service.dart b/quantus_sdk/lib/src/services/wormhole_claim_service.dart similarity index 54% rename from miner-app/lib/src/services/wormhole_claim_service.dart rename to quantus_sdk/lib/src/services/wormhole_claim_service.dart index 19c02880a..3ee72d636 100644 --- a/miner-app/lib/src/services/wormhole_claim_service.dart +++ b/quantus_sdk/lib/src/services/wormhole_claim_service.dart @@ -1,13 +1,15 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:typed_data'; import 'package:convert/convert.dart'; -import 'package:quantus_miner/src/services/chain_rpc_client.dart'; -import 'package:quantus_miner/src/utils/app_logger.dart'; +import 'package:http/http.dart' as http; import 'package:quantus_sdk/generated/planck/pallets/wormhole.dart' as wormhole_pallet; -import 'package:quantus_sdk/quantus_sdk.dart'; - -final _log = log.withTag('WormholeClaim'); +import 'package:quantus_sdk/src/rust/api/wormhole.dart' as wormhole_ffi; +import 'package:quantus_sdk/src/services/network/redundant_endpoint.dart'; +import 'package:quantus_sdk/src/services/substrate_service.dart' show getAccountId32; +import 'package:quantus_sdk/src/services/wormhole_utxo_service.dart'; +import 'package:polkadart/scale_codec.dart' show ByteOutput, CompactCodec; class ClaimProgressItem { final int step; @@ -41,13 +43,7 @@ class ClaimCancelled implements Exception { } class WormholeClaimService { - static const int _maxProofsPerBatch = 16; - static const int _proofConcurrency = 16; static const int _volumeFeeBps = 10; - - /// Scaled-down → planck multiplier; matches `SCALE_DOWN_FACTOR` in the Rust - /// wormhole API. The proof commits to amounts in scaled-down units, and the - /// chain dispatches `outputAmount * scaleDownFactor` planck. static final BigInt _scaleDownFactor = BigInt.from(10000000000); static const _stepTitles = { @@ -56,17 +52,27 @@ class WormholeClaimService { 3: 'Computing nullifiers', 4: 'Checking nullifiers', 5: 'Generating ZK proofs', - 6: 'Submitting to chain', + 6: 'Aggregating & submitting', }; final WormholeUtxoService _utxoService = WormholeUtxoService(); + final RpcEndpointService _rpcEndpoint = RpcEndpointService(); + final String? _rpcUrl; + final int maxProofsPerBatch; + final int proofConcurrency; + final bool freshBuild; + final int? provingThreads; - /// Completes when the user cancels. Polled by [_checkCancelled] for cheap - /// chain-level checks and raced against the whole flow in [claimRewards] so - /// cancellation is instantaneous even mid-FFI (in-flight proofs are simply - /// orphaned — they'll finish in the background and their results discarded). Completer? _cancelCompleter; + WormholeClaimService({ + String? rpcUrl, + this.maxProofsPerBatch = 16, + this.proofConcurrency = 16, + this.freshBuild = false, + this.provingThreads, + }) : _rpcUrl = rpcUrl; + bool get _cancelled => _cancelCompleter?.isCompleted ?? false; void cancel() { @@ -78,38 +84,29 @@ class WormholeClaimService { required String wormholeAddress, required String secretHex, required String destinationAddress, - required String rpcUrl, required String circuitBinsDir, required ClaimProgressCallback onProgress, }) async { final cancelCompleter = Completer(); _cancelCompleter = cancelCompleter; - final rpc = ChainRpcClient(rpcUrl: rpcUrl, timeout: const Duration(seconds: 30)); try { final flow = _runClaimFlow( - rpc: rpc, wormholeAddress: wormholeAddress, secretHex: secretHex, destinationAddress: destinationAddress, circuitBinsDir: circuitBinsDir, onProgress: onProgress, ); - // Race the flow against cancellation. Future.any returns the first to - // complete; the loser's later completion (success or error) is silently - // ignored by Future.any, so abandoned in-flight FFI work won't surface - // as an unhandled async error. final cancelGuard = cancelCompleter.future.then((_) => throw const ClaimCancelled()); return await Future.any([flow, cancelGuard]); } on WormholeOperationCancelled { throw const ClaimCancelled(); - } finally { - rpc.dispose(); } } void _reportProgress(ClaimProgressCallback onProgress, int step, int completed, {int? total}) { - _log.i('Step $step: ${_stepTitles[step]} $completed${total != null ? '/$total' : ''}'); + _log('Step $step: ${_stepTitles[step]} $completed${total != null ? '/$total' : ''}'); onProgress(ClaimProgressItem(step: step, title: _stepTitles[step]!, completed: completed, total: total)); } @@ -118,7 +115,6 @@ class WormholeClaimService { } Future _runClaimFlow({ - required ChainRpcClient rpc, required String wormholeAddress, required String secretHex, required String destinationAddress, @@ -126,11 +122,22 @@ class WormholeClaimService { required ClaimProgressCallback onProgress, }) async { _checkCancelled(); + _logMem('claim_start'); + + if (provingThreads != null) { + _log('Setting proving thread count to $provingThreads'); + await wormhole_ffi.setProvingThreadCount(numThreads: provingThreads!); + } _reportProgress(onProgress, 1, 0); - _log.i('Ensuring circuit binaries at: $circuitBinsDir'); - await ensureCircuitBinaries(binsDir: circuitBinsDir); - _log.i('Circuit binaries ready'); + _log('Ensuring circuit binaries at: $circuitBinsDir (freshBuild=$freshBuild)'); + if (freshBuild) { + await wormhole_ffi.ensureLeafCircuitBinaries(binsDir: circuitBinsDir); + } else { + await wormhole_ffi.ensureCircuitBinaries(binsDir: circuitBinsDir); + } + _log('Circuit binaries ready'); + _logMem('after_ensure_binaries'); _reportProgress(onProgress, 1, 1); _checkCancelled(); @@ -143,121 +150,129 @@ class WormholeClaimService { _reportProgress(onProgress, phase + 1, completed, total: total); }, ); + _logMem('after_get_unspent_transfers'); if (unspent.isEmpty) { return ClaimResult(totalWithdrawn: BigInt.zero, transfersProcessed: 0, batchesSubmitted: 0, txHashes: const []); } unspent.sort((a, b) => b.amount.compareTo(a.amount)); - _log.i('Found ${unspent.length} unspent transfers'); + _log('Found ${unspent.length} unspent transfers'); + _logMem('after_sort_unspent'); _checkCancelled(); - _reportProgress(onProgress, 5, 0, total: unspent.length); + final numTransfers = unspent.length; + final totalBatches = (numTransfers / maxProofsPerBatch).ceil(); + _reportProgress(onProgress, 5, 0, total: numTransfers); - // Use the current head (not finalized) as the proof block: the user is - // claiming up to the chain tip, and the merkle tree at the finalized head - // would not contain transfers in the last `reorgDepth` blocks. A reorg - // before the claim batch lands will cause on-chain verification to fail - // and the user can simply retry. - final blockHash = await rpc.getBestBlockHash(); - final header = await rpc.getBlockHeader(blockHash: blockHash); + final String blockHash = await _rpcCall('chain_getBlockHash') as String; + final header = await _rpcCall('chain_getHeader', [blockHash]); final blockNumber = _hexToInt(header['number'] as String); final parentHash = _hexBytes(header['parentHash'] as String); final stateRoot = _hexBytes(header['stateRoot'] as String); final extrinsicsRoot = _hexBytes(header['extrinsicsRoot'] as String); final digest = _encodeDigest(header['digest'] as Map); - _log.i('Proof block: #$blockNumber ($blockHash)'); + _log('Proof block: #$blockNumber ($blockHash)'); _checkCancelled(); - final numTransfers = unspent.length; - final proofBytesList = List.filled(numTransfers, null); final secretBytes = Uint8List.fromList(hex.decode(secretHex.replaceFirst('0x', ''))); final destinationBytes = Uint8List.fromList(getAccountId32(destinationAddress)); final blockHashBytes = Uint8List.fromList(_hexBytes(blockHash)); BigInt netTotal = BigInt.zero; - int completed = 0; + int proofsCompleted = 0; + int batchesCompleted = 0; + final txHashes = []; final genSw = Stopwatch()..start(); - for (int chunk = 0; chunk < numTransfers; chunk += _proofConcurrency) { - _checkCancelled(); - final end = (chunk + _proofConcurrency).clamp(0, numTransfers); - final futures = >[]; - - for (int i = chunk; i < end; i++) { - final transfer = unspent[i]; - futures.add( - _generateLeafProof( - rpc: rpc, - transfer: transfer, - blockHash: blockHash, - blockNumber: blockNumber, - parentHash: parentHash, - stateRoot: stateRoot, - extrinsicsRoot: extrinsicsRoot, - digest: digest, - blockHashBytes: blockHashBytes, - secretBytes: secretBytes, - destinationBytes: destinationBytes, - circuitBinsDir: circuitBinsDir, - outputBuffer: proofBytesList, - outputIndex: i, - onComplete: () { - completed++; - // Plain stdout print (not debugPrint) so it survives in release - // builds and is visible from the launching terminal. - // ignore: avoid_print - print( - '[WormholeClaim] Proof $completed/$numTransfers ' - 'leaf=${transfer.leafIndex} (${genSw.elapsedMilliseconds}ms elapsed)', - ); - _reportProgress(onProgress, 5, completed, total: numTransfers); - }, - ), - ); - } + for (int batchStart = 0; batchStart < numTransfers; batchStart += maxProofsPerBatch) { + final batchEnd = (batchStart + maxProofsPerBatch).clamp(0, numTransfers); + final batchIndex = batchStart ~/ maxProofsPerBatch; + _logMem('batch_${batchIndex}_start'); + final batchTransfers = unspent.sublist(batchStart, batchEnd); + final batchProofs = List.filled(batchTransfers.length, null); + + for (int chunk = 0; chunk < batchTransfers.length; chunk += proofConcurrency) { + _checkCancelled(); + final end = (chunk + proofConcurrency).clamp(0, batchTransfers.length); + final futures = >[]; + + for (int i = chunk; i < end; i++) { + final transfer = batchTransfers[i]; + futures.add( + _generateLeafProof( + transfer: transfer, + blockHash: blockHash, + blockNumber: blockNumber, + parentHash: parentHash, + stateRoot: stateRoot, + extrinsicsRoot: extrinsicsRoot, + digest: digest, + blockHashBytes: blockHashBytes, + secretBytes: secretBytes, + destinationBytes: destinationBytes, + circuitBinsDir: circuitBinsDir, + outputBuffer: batchProofs, + outputIndex: i, + onComplete: () { + proofsCompleted++; + if (proofsCompleted % 16 == 0 || proofsCompleted == numTransfers) { + _logMem('after_proof_$proofsCompleted'); + } + print( + '[WormholeClaim] Proof $proofsCompleted/$numTransfers ' + 'leaf=${transfer.leafIndex} (${genSw.elapsedMilliseconds}ms elapsed)', + ); + _reportProgress(onProgress, 5, proofsCompleted, total: numTransfers); + }, + ), + ); + } - final outputs = await Future.wait(futures, eagerError: true); - for (final out in outputs) { - netTotal += out; + final outputs = await Future.wait(futures, eagerError: true); + for (final out in outputs) { + netTotal += out; + } } - _checkCancelled(); - } - - final finalProofs = proofBytesList.cast(); - final batches = >[]; - for (int i = 0; i < finalProofs.length; i += _maxProofsPerBatch) { - final end = (i + _maxProofsPerBatch).clamp(0, finalProofs.length); - batches.add(finalProofs.sublist(i, end)); - } + _logMem('batch_${batchIndex}_leaves_done'); - final txHashes = []; - _reportProgress(onProgress, 6, 0, total: batches.length); - for (int b = 0; b < batches.length; b++) { _checkCancelled(); - _log.i('Aggregating batch ${b + 1}/${batches.length}'); - final aggregated = await aggregateProofs(proofBytesList: batches[b], binsDir: circuitBinsDir); - _log.i('Batch ${b + 1} aggregated (${aggregated.length} bytes)'); + _reportProgress(onProgress, 6, batchesCompleted, total: totalBatches); + _log('Releasing memory before aggregation...'); + await wormhole_ffi.releaseMemory(); + _logMem('batch_${batchIndex}_before_aggregate'); + if (batchIndex == 0) { + wormhole_ffi.logMemorySnapshot(tag: 'before_first_aggregate_detailed'); + } + _log('Aggregating batch ${batchesCompleted + 1}/$totalBatches (freshBuild=$freshBuild)'); + final aggregated = freshBuild + ? await wormhole_ffi.aggregateProofsFresh( + proofBytesList: batchProofs.cast(), + binsDir: circuitBinsDir, + ) + : await wormhole_ffi.aggregateProofs(proofBytesList: batchProofs.cast(), binsDir: circuitBinsDir); + _logMem('batch_${batchIndex}_after_aggregate'); + _log('Releasing memory after aggregation...'); + await wormhole_ffi.releaseMemory(); + _log('Batch ${batchesCompleted + 1} aggregated (${aggregated.length} bytes)'); _checkCancelled(); - final txHash = await _submitExtrinsic(rpc, aggregated); + final txHash = await _submitExtrinsic(aggregated); txHashes.add(txHash); - _log.i('Batch ${b + 1} accepted by pool: $txHash'); - _reportProgress(onProgress, 6, b + 1, total: batches.length); + batchesCompleted++; + _logMem('batch_${batchIndex}_submitted'); + _log('Batch $batchesCompleted accepted by pool: $txHash'); + _reportProgress(onProgress, 6, batchesCompleted, total: totalBatches); } return ClaimResult( totalWithdrawn: netTotal, transfersProcessed: numTransfers, - batchesSubmitted: batches.length, + batchesSubmitted: batchesCompleted, txHashes: txHashes, ); } - /// Generates a single leaf proof and writes it to [outputBuffer]. Returns the - /// net (post-fee) output amount this leaf contributes. [onComplete] fires - /// once the proof is written so callers can update progress per-leaf. Future _generateLeafProof({ - required ChainRpcClient rpc, required WormholeTransfer transfer, required String blockHash, required int blockNumber, @@ -273,7 +288,7 @@ class WormholeClaimService { required int outputIndex, void Function()? onComplete, }) async { - final zkProof = await rpc.getZkMerkleProof(transfer.leafIndex, blockHash); + final zkProof = await _rpcCall('zkTree_getMerkleProof', [transfer.leafIndex.toInt(), blockHash]); final leafData = _toBytes(zkProof['leaf_data']); final leafHash = _toBytes(zkProof['leaf_hash']); @@ -282,14 +297,18 @@ class WormholeClaimService { final rawSiblings = zkProof['siblings'] as List; final siblingsFlat = _flattenSiblings(rawSiblings); - final merkle = computeMerklePositions(unsortedSiblingsFlat: siblingsFlat, leafHash: leafHash, depth: depth); + final merkle = wormhole_ffi.computeMerklePositions( + unsortedSiblingsFlat: siblingsFlat, + leafHash: leafHash, + depth: depth, + ); - final inputAmount = decodeLeafAmount(leafData: leafData); - final outputAmount = wormholeComputeOutputAmount(inputAmount: inputAmount, feeBps: _volumeFeeBps); - final wormholeAddressBytes = decodeLeafToAccount(leafData: leafData); + final inputAmount = wormhole_ffi.decodeLeafAmount(leafData: leafData); + final outputAmount = wormhole_ffi.wormholeComputeOutputAmount(inputAmount: inputAmount, feeBps: _volumeFeeBps); + final wormholeAddressBytes = wormhole_ffi.decodeLeafToAccount(leafData: leafData); - final proof = await generateProof( - input: ProofInput( + final proof = await wormhole_ffi.generateProof( + input: wormhole_ffi.ProofInput( secret: secretBytes, transferCount: transfer.transferCount, wormholeAddress: wormholeAddressBytes, @@ -312,23 +331,17 @@ class WormholeClaimService { commonBinPath: '$circuitBinsDir/common.bin', ); outputBuffer[outputIndex] = proof.proofBytes; + await wormhole_ffi.releaseMemory(); onComplete?.call(); - // On-chain dispatch transfers `outputAmount * scaleDownFactor` planck to - // the destination, so this is the exact net contribution per leaf. return BigInt.from(outputAmount) * _scaleDownFactor; } - /// Submits an unsigned extrinsic via `author_submitExtrinsic` and returns the - /// pool-accepted tx hash. We don't wait for inclusion: pool acceptance of a - /// well-formed unsigned extrinsic is a strong signal it will land, and any - /// rejection (validation, insufficient priority, etc.) surfaces here as a - /// JSON-RPC error from [ChainRpcClient.rpcCall]. - Future _submitExtrinsic(ChainRpcClient rpc, Uint8List aggregatedProofBytes) async { + Future _submitExtrinsic(Uint8List aggregatedProofBytes) async { final fullExtrinsic = _wrapUnsignedExtrinsic(aggregatedProofBytes); final hexExtrinsic = '0x${hex.encode(fullExtrinsic)}'; - _log.i('Submitting unsigned extrinsic (${fullExtrinsic.length} bytes)'); + _log('Submitting unsigned extrinsic (${fullExtrinsic.length} bytes)'); - final result = await rpc.rpcCall('author_submitExtrinsic', [hexExtrinsic]); + final result = await _rpcCall('author_submitExtrinsic', [hexExtrinsic]); if (result is! String) { throw StateError('author_submitExtrinsic returned ${result.runtimeType}: $result'); } @@ -339,7 +352,6 @@ class WormholeClaimService { final runtimeCall = const wormhole_pallet.Txs().verifyAggregatedProof(proofBytes: callBytes); final callEncoded = runtimeCall.encode(); - // Unsigned extrinsic body: [version_byte=0x04][call_data] const versionByte = 0x04; final body = Uint8List(1 + callEncoded.length); body[0] = versionByte; @@ -352,6 +364,39 @@ class WormholeClaimService { return full; } + // --- RPC helpers --- + + Future _rpcCall(String method, [List? params]) async { + final body = jsonEncode({'jsonrpc': '2.0', 'id': 1, 'method': method, 'params': params ?? []}); + + final http.Response response; + if (_rpcUrl != null) { + response = await http.post(Uri.parse(_rpcUrl), headers: {'Content-Type': 'application/json'}, body: body); + } else { + response = await _rpcEndpoint.post(body: body); + } + + if (response.statusCode != 200) { + throw Exception('$method HTTP ${response.statusCode}: ${response.body}'); + } + final parsed = jsonDecode(response.body) as Map; + if (parsed['error'] != null) { + throw Exception('$method RPC error: ${parsed['error']}'); + } + return parsed['result']; + } + + // --- Utilities --- + + static void _log(String msg) => print('[WormholeClaim] $msg'); + + static void _logMem(String tag) { + final (phys, virt) = wormhole_ffi.getProcessMemory(); + final physMb = phys ~/ BigInt.from(1024 * 1024); + final virtMb = virt ~/ BigInt.from(1024 * 1024); + print('[ClaimMem] $tag phys=${physMb}MB virt=${virtMb}MB'); + } + static int _hexToInt(String hexStr) => int.parse(hexStr.replaceFirst('0x', ''), radix: 16); static List _hexBytes(String hexStr) => hex.decode(hexStr.replaceFirst('0x', '')); diff --git a/quantus_sdk/lib/src/services/wormhole_utxo_service.dart b/quantus_sdk/lib/src/services/wormhole_utxo_service.dart index 376f24bd1..44c2edfdf 100644 --- a/quantus_sdk/lib/src/services/wormhole_utxo_service.dart +++ b/quantus_sdk/lib/src/services/wormhole_utxo_service.dart @@ -82,6 +82,13 @@ class WormholeUtxoService { static void _log(String msg) => print('[WormholeUtxo] $msg'); + static void _logMem(String tag) { + final (phys, virt) = wormhole_ffi.getProcessMemory(); + final physMb = phys ~/ BigInt.from(1024 * 1024); + final virtMb = virt ~/ BigInt.from(1024 * 1024); + print('[UtxoMem] $tag phys=${physMb}MB virt=${virtMb}MB'); + } + static String _addressHash(Uint8List raw32) => wormhole_ffi.computeAddressHashHex(rawAddress: raw32); static void _throwIfCancelled(IsCancelledCallback? isCancelled) { @@ -400,6 +407,7 @@ query SpentNullifiers($hashes: [String!]!) { }) async { final sw = Stopwatch()..start(); _log('getTransfersTo START ($wormholeAddress)'); + _logMem('getTransfersTo_start'); final raw = Uint8List.fromList(getAccountId32(wormholeAddress)); final fullHash = _addressHash(raw); @@ -410,6 +418,7 @@ query SpentNullifiers($hashes: [String!]!) { final cache = await _loadTransferCache(fullHash); _log('Cache: ${cache.transfers.length} transfers up to block ${cache.cachedUpToBlock}'); + _logMem('after_load_transfer_cache'); if (cache.transfers.isNotEmpty) onProgress?.call(1, cache.transfers.length); _throwIfCancelled(isCancelled); @@ -438,6 +447,7 @@ query SpentNullifiers($hashes: [String!]!) { final allTransfers = [...cache.transfers, ...newTransfers]; onProgress?.call(1, allTransfers.length); _log('Total transfers: ${allTransfers.length} (${cache.transfers.length} cached + ${newTransfers.length} new)'); + _logMem('after_merge_transfers'); // Cache only the reorg-safe slice; the caller still sees recent (above-cutoff) // transfers so balances / claims include them. @@ -445,6 +455,7 @@ query SpentNullifiers($hashes: [String!]!) { await _saveTransferCache(fullHash, _TransferCache(cachedUpToBlock: safeCutoff, transfers: safeTransfers)); _log('getTransfersTo DONE: ${allTransfers.length} transfers (${sw.elapsedMilliseconds}ms)'); + _logMem('getTransfersTo_done'); return (transfers: allTransfers, safeCutoff: safeCutoff); } @@ -455,6 +466,7 @@ query SpentNullifiers($hashes: [String!]!) { IsCancelledCallback? isCancelled, }) async { _log('getUnspentTransfers($wormholeAddress)'); + _logMem('getUnspentTransfers_start'); final fetched = await getTransfersTo(wormholeAddress, onProgress: onProgress, isCancelled: isCancelled); final transfers = fetched.transfers; final safeCutoff = fetched.safeCutoff; @@ -467,6 +479,7 @@ query SpentNullifiers($hashes: [String!]!) { final fullHash = _addressHash(raw); final cachedSpent = await _loadSpentNullifiers(fullHash); _log('Nullifier cache: ${cachedSpent.length} known spent'); + _logMem('after_load_nullifier_cache'); final hdWalletService = HdWalletService(); final uncheckedPairs = <(String, String)>[]; @@ -490,11 +503,14 @@ query SpentNullifiers($hashes: [String!]!) { uncheckedPairs.add((nullifierHex, nullifierHash)); } onProgress?.call(2, i + 1, total: transfers.length); + if ((i + 1) % 500 == 0) _logMem('nullifiers_computed_${i + 1}'); } _log('Computed nullifiers: $skipped cached-spent, ${uncheckedPairs.length} to check'); + _logMem('after_compute_all_nullifiers'); if (uncheckedPairs.isNotEmpty) { final newSpent = await _checkNullifiersSpent(uncheckedPairs, onProgress: onProgress, isCancelled: isCancelled); + _logMem('after_check_nullifiers_spent'); // In-memory: every spent nullifier we've seen, including ones in // unfinalized blocks — must not be re-claimed in this call. allSpent.addAll(newSpent.keys); @@ -512,6 +528,7 @@ query SpentNullifiers($hashes: [String!]!) { final unspent = nullifierToTransfer.entries.where((e) => !allSpent.contains(e.key)).map((e) => e.value).toList(); _log('getUnspentTransfers: ${unspent.length} unspent out of ${transfers.length} total'); + _logMem('getUnspentTransfers_done'); return unspent; } diff --git a/quantus_sdk/rust/Cargo.lock b/quantus_sdk/rust/Cargo.lock index 3552cb57f..d73a3d077 100644 --- a/quantus_sdk/rust/Cargo.lock +++ b/quantus_sdk/rust/Cargo.lock @@ -2666,8 +2666,6 @@ dependencies = [ [[package]] name = "qp-wormhole-aggregator" version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a23eea8e17c97632f5056d1fecdb83d2e997f79d823fb97c514823fce8580" dependencies = [ "anyhow", "hex", @@ -2684,8 +2682,6 @@ dependencies = [ [[package]] name = "qp-wormhole-circuit" version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72abd3357e1c486621431109d83a259076ed941fcc7f035353a629940c443d9" dependencies = [ "anyhow", "hex", @@ -2697,8 +2693,6 @@ dependencies = [ [[package]] name = "qp-wormhole-circuit-builder" version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d27c981d34a35cb10ee96e16c1fb44fbb4c52f3e58a787ce089c21bf5469514" dependencies = [ "anyhow", "clap", @@ -2711,8 +2705,6 @@ dependencies = [ [[package]] name = "qp-wormhole-inputs" version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f69e54a0f450d349cb1169bbb743a7a846475a1210760308e38f55841e5aa5c0" dependencies = [ "anyhow", ] @@ -2720,8 +2712,6 @@ dependencies = [ [[package]] name = "qp-wormhole-prover" version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68cee4d6a0317f89d1ec7a6c40a9592e5525c75b9cb21bd7d5d3b408301068bd" dependencies = [ "anyhow", "qp-plonky2", @@ -2733,8 +2723,6 @@ dependencies = [ [[package]] name = "qp-zk-circuits-common" version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d5e1d764560fd797f71defbf75a1618596945c53e33033ac42cc7c2e4689f6" dependencies = [ "anyhow", "hex", @@ -2957,6 +2945,7 @@ dependencies = [ "qp-wormhole-prover", "qp-zk-circuits-common", "quantus_ur", + "rayon", "sp-core", ] diff --git a/quantus_sdk/rust/Cargo.toml b/quantus_sdk/rust/Cargo.toml index 4a98973a1..6461f651e 100644 --- a/quantus_sdk/rust/Cargo.toml +++ b/quantus_sdk/rust/Cargo.toml @@ -28,11 +28,22 @@ qp-wormhole-aggregator = { version = "2.0.1", default-features = false, features qp-wormhole-inputs = { version = "2.0.1", default-features = false, features = ["std"] } qp-zk-circuits-common = { version = "2.0.1", default-features = false, features = ["std"] } qp-wormhole-circuit-builder = { version = "2.0.1" } +rayon = "1.10" ## This dependency has been yanked but sp-core depends on version 0.1.1... so we add it with a patch [patch.crates-io] ark-vrf = { git = "https://github.com/davxy/ark-vrf", tag = "v0.1.1" } +# Local qp-zk-circuits checkout — picks up the tighter aggregator config +# from PR #139 (wormhole_aggregator_circuit_config: 135/60/RowBlinding). +# Revert these lines to fall back to crates.io. +qp-zk-circuits-common = { path = "../../../qp-zk-circuits/common" } +qp-wormhole-circuit = { path = "../../../qp-zk-circuits/wormhole/circuit" } +qp-wormhole-prover = { path = "../../../qp-zk-circuits/wormhole/prover" } +qp-wormhole-aggregator = { path = "../../../qp-zk-circuits/wormhole/aggregator" } +qp-wormhole-inputs = { path = "../../../qp-zk-circuits/wormhole/inputs" } +qp-wormhole-circuit-builder = { path = "../../../qp-zk-circuits/wormhole/circuit-builder" } + # Circuit generation is CPU-intensive; without this it takes ~10min instead of ~30s. [profile.dev.build-override] opt-level = 3 diff --git a/quantus_sdk/rust/src/api/wormhole.rs b/quantus_sdk/rust/src/api/wormhole.rs index 18158ed45..476809212 100644 --- a/quantus_sdk/rust/src/api/wormhole.rs +++ b/quantus_sdk/rust/src/api/wormhole.rs @@ -225,6 +225,29 @@ pub fn ensure_circuit_binaries(bins_dir: String) -> Result { Ok(config_str) } +/// Lightweight variant that only generates leaf circuit binaries (prover.bin, +/// common.bin, verifier.bin, dummy_proof.bin). Skips the heavy aggregation +/// circuit generation entirely. Use with `aggregate_proofs_fresh` which builds +/// the aggregation circuit in memory at proving time. +pub fn ensure_leaf_circuit_binaries(bins_dir: String) -> Result<(), String> { + let dir = Path::new(&bins_dir); + std::fs::create_dir_all(dir) + .map_err(|e| format!("Failed to create bins directory {}: {}", bins_dir, e))?; + + if leaf_files_exist(dir) { + eprintln!("[ensure_leaf] all leaf files already exist at {}", bins_dir); + return Ok(()); + } + + eprintln!("[ensure_leaf] generating leaf circuit binaries at {}...", bins_dir); + let t0 = std::time::Instant::now(); + qp_wormhole_circuit_builder::generate_circuit_binaries(dir, true) + .map_err(|e| format!("Leaf circuit binary generation failed: {}", e))?; + eprintln!("[ensure_leaf] DONE (+{}ms)", t0.elapsed().as_millis()); + + Ok(()) +} + fn all_required_files_exist(dir: &Path) -> bool { const REQUIRED: &[&str] = &[ "prover.bin", @@ -239,11 +262,21 @@ fn all_required_files_exist(dir: &Path) -> bool { REQUIRED.iter().all(|f| dir.join(f).exists()) } +fn leaf_files_exist(dir: &Path) -> bool { + const REQUIRED: &[&str] = &["prover.bin", "verifier.bin", "common.bin", "dummy_proof.bin"]; + REQUIRED.iter().all(|f| dir.join(f).exists()) +} + pub fn generate_proof( input: ProofInput, prover_bin_path: String, common_bin_path: String, ) -> Result { + let t0 = std::time::Instant::now(); + let leaf_input_size = input.sorted_siblings_flat.len() + input.positions.len() + input.digest.len(); + log_mem("leaf_entry"); + eprintln!("[leaf] transfer_count={} input_size={}B", input.transfer_count, leaf_input_size); + let secret_digest = vec_to_digest(&input.secret, "secret")?; let wormhole_address = vec_to_32(&input.wormhole_address, "wormhole_address")?; @@ -288,6 +321,8 @@ pub fn generate_proof( zk_merkle_siblings.push(sibs); } + log_mem("before PrivateCircuitInputs"); + let private = PrivateCircuitInputs { secret: secret_digest, transfer_count: input.transfer_count, @@ -301,6 +336,7 @@ pub fn generate_proof( zk_merkle_siblings, zk_merkle_positions: input.positions.clone(), }; + log_mem("after PrivateCircuitInputs"); let public = PublicCircuitInputs { asset_id: input.asset_id, @@ -316,20 +352,38 @@ pub fn generate_proof( let circuit_inputs = CircuitInputs { public, private }; + let t_load = std::time::Instant::now(); let prover = WormholeProver::new_from_files(Path::new(&prover_bin_path), Path::new(&common_bin_path)) .map_err(|e| format!("Failed to load prover: {}", e))?; + eprintln!("[leaf] prover loaded in {}ms", t_load.elapsed().as_millis()); + log_mem("leaf_post_load"); + let t_commit = std::time::Instant::now(); let prover_with_inputs = prover .commit(&circuit_inputs) .map_err(|e| format!("Failed to commit inputs: {}", e))?; + eprintln!("[leaf] commit in {}ms", t_commit.elapsed().as_millis()); + log_mem("leaf_post_commit"); + let t_prove = std::time::Instant::now(); let proof = prover_with_inputs .prove() .map_err(|e| format!("Proof generation failed: {}", e))?; + eprintln!("[leaf] prove in {}ms", t_prove.elapsed().as_millis()); + log_mem("leaf_post_prove"); + + let proof_bytes = proof.to_bytes(); + drop(proof); + log_mem("leaf_post_to_bytes"); + eprintln!( + "[leaf] total {}ms, output {} bytes", + t0.elapsed().as_millis(), + proof_bytes.len() + ); Ok(ProofOutput { - proof_bytes: proof.to_bytes(), + proof_bytes, nullifier: nullifier_bytes.to_vec(), }) } @@ -342,13 +396,20 @@ pub fn aggregate_proofs(proof_bytes_list: Vec>, bins_dir: String) -> Res use qp_zk_circuits_common::circuit::{C, D, F}; let bins_path = Path::new(&bins_dir); + let t0 = std::time::Instant::now(); + eprintln!("[agg] start, num_proofs={}, bins_dir={}", proof_bytes_list.len(), bins_dir); + log_mem("agg_start"); let mut aggregator = Layer0Aggregator::new(bins_path) .map_err(|e| format!("Failed to load aggregator: {}", e))?; + eprintln!("[agg] Layer0Aggregator::new done (+{}ms)", t0.elapsed().as_millis()); + log_mem("agg_after_new"); let common_data = aggregator .load_common_data(CircuitType::Leaf) .map_err(|e| format!("Failed to load leaf circuit data: {}", e))?; + eprintln!("[agg] common_data loaded (+{}ms)", t0.elapsed().as_millis()); + log_mem("agg_after_load_common"); for (i, proof_bytes) in proof_bytes_list.iter().enumerate() { let proof = ProofWithPublicInputs::::from_bytes(proof_bytes.clone(), &common_data) @@ -356,11 +417,318 @@ pub fn aggregate_proofs(proof_bytes_list: Vec>, bins_dir: String) -> Res aggregator .push_proof(proof) .map_err(|e| format!("Failed to push proof {}: {}", i, e))?; + if i == 0 || i + 1 == proof_bytes_list.len() { + log_mem(&format!("agg_after_push_{}", i + 1)); + } } + eprintln!("[agg] all {} proofs pushed (+{}ms)", proof_bytes_list.len(), t0.elapsed().as_millis()); + log_mem("agg_before_aggregate_call"); let aggregated = aggregator .aggregate() .map_err(|e| format!("Aggregation failed: {}", e))?; + eprintln!("[agg] aggregate() returned (+{}ms)", t0.elapsed().as_millis()); + log_mem("agg_after_aggregate_call"); + + let bytes = aggregated.to_bytes(); + drop(aggregated); + log_mem("agg_after_to_bytes"); + eprintln!("[agg] DONE, output {} bytes (+{}ms)", bytes.len(), t0.elapsed().as_millis()); + Ok(bytes) +} + +// Layout must match Apple's task_vm_info exactly. +// Critical types: mach_vm_size_t = u64, integer_t = i32, mach_vm_address_t = u64. +// Previous version had region_count: u64 + page_size: u32 which shifted every +// subsequent field by 4-8 bytes and made phys_footprint actually read the +// `min_address` field, returning a phantom ~4 GB value on ARM64 iOS. +// We stop at phys_footprint (TASK_VM_INFO_REV1 = 38 u32s = 152 bytes). +#[cfg(target_vendor = "apple")] +#[repr(C)] +struct TaskVmInfoRev1 { + virtual_size: u64, + region_count: i32, + page_size: i32, + resident_size: u64, + resident_size_peak: u64, + device: u64, + device_peak: u64, + internal: u64, + internal_peak: u64, + external: u64, + external_peak: u64, + reusable: u64, + reusable_peak: u64, + purgeable_volatile_pmap: u64, + purgeable_volatile_resident: u64, + purgeable_volatile_virtual: u64, + compressed: u64, + compressed_peak: u64, + compressed_lifetime: u64, + phys_footprint: u64, +} + +#[cfg(target_vendor = "apple")] +const _: () = assert!(std::mem::size_of::() == 152); + +#[cfg(target_vendor = "apple")] +const TASK_VM_INFO: u32 = 22; + +#[cfg(target_vendor = "apple")] +unsafe fn read_task_vm_info() -> Option { + use std::mem::MaybeUninit; + extern "C" { + fn mach_task_self() -> u32; + fn task_info(task: u32, flavor: u32, info: *mut i32, count: *mut u32) -> i32; + } + let mut info: MaybeUninit = MaybeUninit::zeroed(); + let mut count: u32 = + (std::mem::size_of::() / std::mem::size_of::()) as u32; + let kr = task_info( + mach_task_self(), + TASK_VM_INFO, + info.as_mut_ptr() as *mut i32, + &mut count, + ); + if kr == 0 { + Some(info.assume_init()) + } else { + None + } +} + +/// Returns (phys_footprint_bytes, virtual_bytes) for the current process. +fn process_memory() -> (u64, u64) { + #[cfg(target_vendor = "apple")] + unsafe { + if let Some(info) = read_task_vm_info() { + return (info.phys_footprint, info.virtual_size); + } + (0, 0) + } + #[cfg(not(target_vendor = "apple"))] + { + (0, 0) + } +} + +fn log_mem(tag: &str) { + #[cfg(target_vendor = "apple")] + unsafe { + if let Some(info) = read_task_vm_info() { + eprintln!( + "[mem] {tag} phys={}MB resident={}MB compressed={}MB internal={}MB external={}MB virt={}MB", + info.phys_footprint / (1024 * 1024), + info.resident_size / (1024 * 1024), + info.compressed / (1024 * 1024), + info.internal / (1024 * 1024), + info.external / (1024 * 1024), + info.virtual_size / (1024 * 1024), + ); + return; + } + } + eprintln!("[mem] {tag} (unavailable on this platform)"); +} + +/// Dump every field of task_vm_info for deep debugging. Call sparingly. +pub fn log_mem_detailed(tag: String) -> Result<(), String> { + #[cfg(target_vendor = "apple")] + unsafe { + let Some(info) = read_task_vm_info() else { + eprintln!("[mem_detailed] {tag} task_info failed"); + return Ok(()); + }; + eprintln!("[mem_detailed] {tag}"); + eprintln!(" virtual_size = {} MB", info.virtual_size / (1024 * 1024)); + eprintln!(" region_count = {}", info.region_count); + eprintln!(" page_size = {}", info.page_size); + eprintln!(" resident_size = {} MB", info.resident_size / (1024 * 1024)); + eprintln!(" resident_size_peak = {} MB", info.resident_size_peak / (1024 * 1024)); + eprintln!(" internal = {} MB", info.internal / (1024 * 1024)); + eprintln!(" internal_peak = {} MB", info.internal_peak / (1024 * 1024)); + eprintln!(" external = {} MB", info.external / (1024 * 1024)); + eprintln!(" external_peak = {} MB", info.external_peak / (1024 * 1024)); + eprintln!(" reusable = {} MB", info.reusable / (1024 * 1024)); + eprintln!(" reusable_peak = {} MB", info.reusable_peak / (1024 * 1024)); + eprintln!(" purgeable_volatile_pmap = {} MB", info.purgeable_volatile_pmap / (1024 * 1024)); + eprintln!(" purgeable_volatile_resident= {} MB", info.purgeable_volatile_resident / (1024 * 1024)); + eprintln!(" compressed = {} MB", info.compressed / (1024 * 1024)); + eprintln!(" compressed_peak = {} MB", info.compressed_peak / (1024 * 1024)); + eprintln!(" compressed_lifetime = {} MB", info.compressed_lifetime / (1024 * 1024)); + eprintln!(" phys_footprint = {} MB", info.phys_footprint / (1024 * 1024)); + } + #[cfg(not(target_vendor = "apple"))] + { + eprintln!("[mem_detailed] {tag} (only available on Apple platforms)"); + } + Ok(()) +} + +/// Returns (phys_footprint_bytes, virtual_bytes). Useful for Dart-side polling. +#[flutter_rust_bridge::frb(sync)] +pub fn get_process_memory() -> (u64, u64) { + process_memory() +} + +/// Returns (phys_footprint, resident_size, compressed). Dart-side memory probe. +#[flutter_rust_bridge::frb(sync)] +pub fn get_process_memory_detailed() -> (u64, u64, u64) { + #[cfg(target_vendor = "apple")] + unsafe { + if let Some(info) = read_task_vm_info() { + return (info.phys_footprint, info.resident_size, info.compressed); + } + } + (0, 0, 0) +} + +/// Dart-side hook to dump every memory field. +#[flutter_rust_bridge::frb(sync)] +pub fn log_memory_snapshot(tag: String) { + let _ = log_mem_detailed(tag); +} + +/// Force the system allocator (Apple libmalloc) to return freed pages to the OS. +/// On other platforms this is a no-op. +/// Apple's malloc keeps freed memory in per-zone caches by default; after a heavy +/// allocation phase like ZK proving this can leave 100s of MB of dirty pages. +/// We iterate every registered zone — passing `null` only reaches the default +/// zone, which is NOT where Rust's allocator lives, so calling on null released +/// 0 bytes in practice. +pub fn release_memory() -> Result<(), String> { + log_mem("release_memory_before"); + #[cfg(target_vendor = "apple")] + unsafe { + extern "C" { + fn malloc_get_all_zones( + task: u32, + reader: *mut std::ffi::c_void, + addresses: *mut *mut *mut std::ffi::c_void, + count: *mut u32, + ) -> i32; + fn malloc_zone_pressure_relief( + zone: *mut std::ffi::c_void, + goal: usize, + ) -> usize; + } + let mut zones: *mut *mut std::ffi::c_void = std::ptr::null_mut(); + let mut count: u32 = 0; + let kr = malloc_get_all_zones(0, std::ptr::null_mut(), &mut zones, &mut count); + let mut total: usize = 0; + if kr == 0 && !zones.is_null() { + for i in 0..count as isize { + let zone = *zones.offset(i); + if zone.is_null() { + continue; + } + total = total.saturating_add(malloc_zone_pressure_relief(zone, 0)); + } + } + let default_released = malloc_zone_pressure_relief(std::ptr::null_mut(), 0); + eprintln!( + "[release_memory] released {} bytes ({} MB) across {} zones (+{} from default)", + total, + total / (1024 * 1024), + count, + default_released + ); + } + log_mem("release_memory_after"); + Ok(()) +} - Ok(aggregated.to_bytes()) +/// Limit rayon parallelism to reduce peak memory during proving. +/// Plonky2 multiplies its per-thread FFT buffers by the rayon pool size, so +/// fewer threads = lower peak memory (at the cost of wall-clock time). +/// Idempotent / safe to call repeatedly; only the first call wins. +pub fn set_proving_thread_count(num_threads: u32) -> Result<(), String> { + let n = num_threads.max(1) as usize; + eprintln!("[rayon] requesting global pool size = {}", n); + match rayon::ThreadPoolBuilder::new().num_threads(n).build_global() { + Ok(()) => { + eprintln!("[rayon] global pool initialized with {} threads", n); + Ok(()) + } + Err(e) => { + eprintln!("[rayon] global pool already initialized: {}", e); + Ok(()) + } + } +} + +pub fn aggregate_proofs_fresh(proof_bytes_list: Vec>, bins_dir: String) -> Result, String> { + use plonky2::plonk::circuit_data::CommonCircuitData; + use plonky2::plonk::proof::ProofWithPublicInputs; + use plonky2::util::serialization::DefaultGateSerializer; + use qp_wormhole_aggregator::layer0::prover::Layer0AggregationProver; + use qp_wormhole_aggregator::dummy_proof::load_dummy_proof; + use qp_zk_circuits_common::circuit::{wormhole_aggregator_circuit_config, C, D, F}; + + let t0 = std::time::Instant::now(); + eprintln!("[agg_fresh] start, num_proofs={}, bins_dir={}", proof_bytes_list.len(), bins_dir); + log_mem("start"); + + let bins_path = Path::new(&bins_dir); + let gate_serializer = DefaultGateSerializer; + + eprintln!("[agg_fresh] reading common.bin (+{}ms)", t0.elapsed().as_millis()); + let leaf_common_bytes = std::fs::read(bins_path.join("common.bin")) + .map_err(|e| format!("Failed to read common.bin: {}", e))?; + eprintln!("[agg_fresh] common.bin read, {} bytes", leaf_common_bytes.len()); + let leaf_common = CommonCircuitData::::from_bytes(leaf_common_bytes, &gate_serializer) + .map_err(|e| format!("Failed to deserialize common.bin: {}", e))?; + eprintln!("[agg_fresh] common.bin deserialized (+{}ms)", t0.elapsed().as_millis()); + + let leaf_verifier_bytes = std::fs::read(bins_path.join("verifier.bin")) + .map_err(|e| format!("Failed to read verifier.bin: {}", e))?; + eprintln!("[agg_fresh] verifier.bin read, {} bytes", leaf_verifier_bytes.len()); + let leaf_verifier_only = plonky2::plonk::circuit_data::VerifierOnlyCircuitData::::from_bytes(leaf_verifier_bytes) + .map_err(|e| format!("Failed to deserialize verifier.bin: {}", e))?; + + let dummy_proof_bytes = std::fs::read(bins_path.join("dummy_proof.bin")) + .map_err(|e| format!("Failed to read dummy_proof.bin: {}", e))?; + eprintln!("[agg_fresh] dummy_proof.bin read, {} bytes", dummy_proof_bytes.len()); + let dummy_proof = load_dummy_proof(dummy_proof_bytes, &leaf_common) + .map_err(|e| format!("Failed to deserialize dummy proof: {}", e))?; + eprintln!("[agg_fresh] leaf artifacts loaded (+{}ms)", t0.elapsed().as_millis()); + + eprintln!("[agg_fresh] deserializing {} input proofs", proof_bytes_list.len()); + let mut proofs: Vec> = Vec::with_capacity(proof_bytes_list.len()); + for (i, bytes) in proof_bytes_list.iter().enumerate() { + let p = ProofWithPublicInputs::::from_bytes(bytes.clone(), &leaf_common) + .map_err(|e| format!("Failed to deserialize proof {}: {:?}", i, e))?; + proofs.push(p); + eprintln!("[agg_fresh] deserialized proof {}/{} (input {} bytes)", i + 1, proof_bytes_list.len(), bytes.len()); + } + eprintln!("[agg_fresh] all input proofs deserialized (+{}ms)", t0.elapsed().as_millis()); + log_mem("after_deserialize"); + + eprintln!("[agg_fresh] BUILDING aggregation circuit in memory (this is the heavy step)..."); + let prover = Layer0AggregationProver::new( + wormhole_aggregator_circuit_config(), + leaf_common, + &leaf_verifier_only, + DEFAULT_NUM_LEAF_PROOFS, + dummy_proof, + ); + eprintln!("[agg_fresh] aggregation circuit BUILT (+{}ms)", t0.elapsed().as_millis()); + log_mem("after_circuit_build"); + + eprintln!("[agg_fresh] committing proofs to witness..."); + let prover = prover.commit(proofs) + .map_err(|e| format!("Failed to commit proofs: {}", e))?; + eprintln!("[agg_fresh] proofs committed (+{}ms)", t0.elapsed().as_millis()); + log_mem("after_commit"); + + eprintln!("[agg_fresh] PROVING aggregation..."); + log_mem("before_prove"); + let aggregated = prover.prove() + .map_err(|e| format!("Aggregation proving failed: {}", e))?; + eprintln!("[agg_fresh] PROVED (+{}ms)", t0.elapsed().as_millis()); + log_mem("after_prove"); + + let bytes = aggregated.to_bytes(); + eprintln!("[agg_fresh] DONE, output {} bytes (+{}ms)", bytes.len(), t0.elapsed().as_millis()); + Ok(bytes) } diff --git a/quantus_sdk/rust/src/frb_generated.rs b/quantus_sdk/rust/src/frb_generated.rs index 27c154ab8..412a0a3c1 100644 --- a/quantus_sdk/rust/src/frb_generated.rs +++ b/quantus_sdk/rust/src/frb_generated.rs @@ -39,7 +39,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 623793143; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1186293619; // Section: executor @@ -82,6 +82,43 @@ fn wire__crate__api__wormhole__aggregate_proofs_impl( }, ) } +fn wire__crate__api__wormhole__aggregate_proofs_fresh_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "aggregate_proofs_fresh", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_proof_bytes_list = >>::sse_decode(&mut deserializer); + let api_bins_dir = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, String>((move || { + let output_ok = crate::api::wormhole::aggregate_proofs_fresh( + api_proof_bytes_list, + api_bins_dir, + )?; + Ok(output_ok) + })()) + } + }, + ) +} fn wire__crate__api__wormhole__compute_address_hash_hex_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -543,6 +580,40 @@ fn wire__crate__api__wormhole__ensure_circuit_binaries_impl( }, ) } +fn wire__crate__api__wormhole__ensure_leaf_circuit_binaries_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "ensure_leaf_circuit_binaries", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_bins_dir = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, String>((move || { + let output_ok = + crate::api::wormhole::ensure_leaf_circuit_binaries(api_bins_dir)?; + Ok(output_ok) + })()) + } + }, + ) +} fn wire__crate__api__crypto__first_hash_to_address_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -706,6 +777,65 @@ fn wire__crate__api__wormhole__generate_proof_impl( }, ) } +fn wire__crate__api__wormhole__get_process_memory_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_process_memory", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok(crate::api::wormhole::get_process_memory())?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__api__wormhole__get_process_memory_detailed_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_process_memory_detailed", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::wormhole::get_process_memory_detailed())?; + Ok(output_ok) + })()) + }, + ) +} fn wire__crate__api__crypto__init_app_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -770,6 +900,71 @@ fn wire__crate__api__ur__is_complete_ur_impl( }, ) } +fn wire__crate__api__wormhole__log_mem_detailed_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "log_mem_detailed", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_tag = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, String>((move || { + let output_ok = crate::api::wormhole::log_mem_detailed(api_tag)?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__wormhole__log_memory_snapshot_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "log_memory_snapshot", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_tag = ::sse_decode(&mut deserializer); + deserializer.end(); + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok({ + crate::api::wormhole::log_memory_snapshot(api_tag); + })?; + Ok(output_ok) + })()) + }, + ) +} fn wire__crate__api__crypto__public_key_bytes_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -799,6 +994,38 @@ fn wire__crate__api__crypto__public_key_bytes_impl( }, ) } +fn wire__crate__api__wormhole__release_memory_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "release_memory", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| { + transform_result_sse::<_, String>((move || { + let output_ok = crate::api::wormhole::release_memory()?; + Ok(output_ok) + })()) + } + }, + ) +} fn wire__crate__api__crypto__secret_key_bytes_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -860,6 +1087,40 @@ fn wire__crate__api__crypto__set_default_ss58_prefix_impl( }, ) } +fn wire__crate__api__wormhole__set_proving_thread_count_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "set_proving_thread_count", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_num_threads = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, String>((move || { + let output_ok = + crate::api::wormhole::set_proving_thread_count(api_num_threads)?; + Ok(output_ok) + })()) + } + }, + ) +} fn wire__crate__api__crypto__sign_message_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -1262,6 +1523,25 @@ impl SseDecode for crate::api::wormhole::ProofOutput { } } +impl SseDecode for (u64, u64) { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_field0 = ::sse_decode(deserializer); + let mut var_field1 = ::sse_decode(deserializer); + return (var_field0, var_field1); + } +} + +impl SseDecode for (u64, u64, u64) { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_field0 = ::sse_decode(deserializer); + let mut var_field1 = ::sse_decode(deserializer); + let mut var_field2 = ::sse_decode(deserializer); + return (var_field0, var_field1, var_field2); + } +} + impl SseDecode for u16 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1341,14 +1621,34 @@ fn pde_ffi_dispatcher_primary_impl( // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { 1 => wire__crate__api__wormhole__aggregate_proofs_impl(port, ptr, rust_vec_len, data_len), - 16 => wire__crate__api__wormhole__ensure_circuit_binaries_impl( + 2 => wire__crate__api__wormhole__aggregate_proofs_fresh_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 17 => wire__crate__api__wormhole__ensure_circuit_binaries_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 18 => wire__crate__api__wormhole__ensure_leaf_circuit_binaries_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 23 => wire__crate__api__wormhole__generate_proof_impl(port, ptr, rust_vec_len, data_len), + 26 => wire__crate__api__crypto__init_app_impl(port, ptr, rust_vec_len, data_len), + 28 => wire__crate__api__wormhole__log_mem_detailed_impl(port, ptr, rust_vec_len, data_len), + 31 => wire__crate__api__wormhole__release_memory_impl(port, ptr, rust_vec_len, data_len), + 34 => wire__crate__api__wormhole__set_proving_thread_count_impl( port, ptr, rust_vec_len, data_len, ), - 21 => wire__crate__api__wormhole__generate_proof_impl(port, ptr, rust_vec_len, data_len), - 22 => wire__crate__api__crypto__init_app_impl(port, ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -1361,39 +1661,46 @@ fn pde_ffi_dispatcher_sync_impl( ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { - 2 => wire__crate__api__wormhole__compute_address_hash_hex_impl(ptr, rust_vec_len, data_len), - 3 => wire__crate__api__wormhole__compute_merkle_positions_impl(ptr, rust_vec_len, data_len), - 4 => wire__crate__api__wormhole__compute_nullifier_impl(ptr, rust_vec_len, data_len), - 5 => wire__crate__api__wormhole__compute_wormhole_address_impl(ptr, rust_vec_len, data_len), - 6 => wire__crate__api__crypto__crystal_alice_impl(ptr, rust_vec_len, data_len), - 7 => wire__crate__api__crypto__crystal_bob_impl(ptr, rust_vec_len, data_len), - 8 => wire__crate__api__crypto__crystal_charlie_impl(ptr, rust_vec_len, data_len), - 9 => wire__crate__api__wormhole__decode_leaf_amount_impl(ptr, rust_vec_len, data_len), - 10 => wire__crate__api__wormhole__decode_leaf_to_account_impl(ptr, rust_vec_len, data_len), - 11 => { + 3 => wire__crate__api__wormhole__compute_address_hash_hex_impl(ptr, rust_vec_len, data_len), + 4 => wire__crate__api__wormhole__compute_merkle_positions_impl(ptr, rust_vec_len, data_len), + 5 => wire__crate__api__wormhole__compute_nullifier_impl(ptr, rust_vec_len, data_len), + 6 => wire__crate__api__wormhole__compute_wormhole_address_impl(ptr, rust_vec_len, data_len), + 7 => wire__crate__api__crypto__crystal_alice_impl(ptr, rust_vec_len, data_len), + 8 => wire__crate__api__crypto__crystal_bob_impl(ptr, rust_vec_len, data_len), + 9 => wire__crate__api__crypto__crystal_charlie_impl(ptr, rust_vec_len, data_len), + 10 => wire__crate__api__wormhole__decode_leaf_amount_impl(ptr, rust_vec_len, data_len), + 11 => wire__crate__api__wormhole__decode_leaf_to_account_impl(ptr, rust_vec_len, data_len), + 12 => { wire__crate__api__wormhole__decode_leaf_transfer_count_impl(ptr, rust_vec_len, data_len) } - 12 => wire__crate__api__ur__decode_ur_impl(ptr, rust_vec_len, data_len), - 13 => wire__crate__api__crypto__derive_hd_path_impl(ptr, rust_vec_len, data_len), - 14 => wire__crate__api__crypto__derive_wormhole_impl(ptr, rust_vec_len, data_len), - 15 => wire__crate__api__ur__encode_ur_impl(ptr, rust_vec_len, data_len), - 17 => wire__crate__api__crypto__first_hash_to_address_impl(ptr, rust_vec_len, data_len), - 18 => wire__crate__api__crypto__generate_derived_keypair_impl(ptr, rust_vec_len, data_len), - 19 => wire__crate__api__crypto__generate_keypair_impl(ptr, rust_vec_len, data_len), - 20 => { + 13 => wire__crate__api__ur__decode_ur_impl(ptr, rust_vec_len, data_len), + 14 => wire__crate__api__crypto__derive_hd_path_impl(ptr, rust_vec_len, data_len), + 15 => wire__crate__api__crypto__derive_wormhole_impl(ptr, rust_vec_len, data_len), + 16 => wire__crate__api__ur__encode_ur_impl(ptr, rust_vec_len, data_len), + 19 => wire__crate__api__crypto__first_hash_to_address_impl(ptr, rust_vec_len, data_len), + 20 => wire__crate__api__crypto__generate_derived_keypair_impl(ptr, rust_vec_len, data_len), + 21 => wire__crate__api__crypto__generate_keypair_impl(ptr, rust_vec_len, data_len), + 22 => { wire__crate__api__crypto__generate_keypair_from_seed_impl(ptr, rust_vec_len, data_len) } - 23 => wire__crate__api__ur__is_complete_ur_impl(ptr, rust_vec_len, data_len), - 24 => wire__crate__api__crypto__public_key_bytes_impl(ptr, rust_vec_len, data_len), - 25 => wire__crate__api__crypto__secret_key_bytes_impl(ptr, rust_vec_len, data_len), - 26 => wire__crate__api__crypto__set_default_ss58_prefix_impl(ptr, rust_vec_len, data_len), - 27 => wire__crate__api__crypto__sign_message_impl(ptr, rust_vec_len, data_len), - 28 => wire__crate__api__crypto__sign_message_with_pubkey_impl(ptr, rust_vec_len, data_len), - 29 => wire__crate__api__crypto__signature_bytes_impl(ptr, rust_vec_len, data_len), - 30 => wire__crate__api__crypto__ss58_to_account_id_impl(ptr, rust_vec_len, data_len), - 31 => wire__crate__api__crypto__to_account_id_impl(ptr, rust_vec_len, data_len), - 32 => wire__crate__api__crypto__verify_message_impl(ptr, rust_vec_len, data_len), - 33 => wire__crate__api__wormhole__wormhole_compute_output_amount_impl( + 24 => wire__crate__api__wormhole__get_process_memory_impl(ptr, rust_vec_len, data_len), + 25 => wire__crate__api__wormhole__get_process_memory_detailed_impl( + ptr, + rust_vec_len, + data_len, + ), + 27 => wire__crate__api__ur__is_complete_ur_impl(ptr, rust_vec_len, data_len), + 29 => wire__crate__api__wormhole__log_memory_snapshot_impl(ptr, rust_vec_len, data_len), + 30 => wire__crate__api__crypto__public_key_bytes_impl(ptr, rust_vec_len, data_len), + 32 => wire__crate__api__crypto__secret_key_bytes_impl(ptr, rust_vec_len, data_len), + 33 => wire__crate__api__crypto__set_default_ss58_prefix_impl(ptr, rust_vec_len, data_len), + 35 => wire__crate__api__crypto__sign_message_impl(ptr, rust_vec_len, data_len), + 36 => wire__crate__api__crypto__sign_message_with_pubkey_impl(ptr, rust_vec_len, data_len), + 37 => wire__crate__api__crypto__signature_bytes_impl(ptr, rust_vec_len, data_len), + 38 => wire__crate__api__crypto__ss58_to_account_id_impl(ptr, rust_vec_len, data_len), + 39 => wire__crate__api__crypto__to_account_id_impl(ptr, rust_vec_len, data_len), + 40 => wire__crate__api__crypto__verify_message_impl(ptr, rust_vec_len, data_len), + 41 => wire__crate__api__wormhole__wormhole_compute_output_amount_impl( ptr, rust_vec_len, data_len, @@ -1657,6 +1964,23 @@ impl SseEncode for crate::api::wormhole::ProofOutput { } } +impl SseEncode for (u64, u64) { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.0, serializer); + ::sse_encode(self.1, serializer); + } +} + +impl SseEncode for (u64, u64, u64) { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.0, serializer); + ::sse_encode(self.1, serializer); + ::sse_encode(self.2, serializer); + } +} + impl SseEncode for u16 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { diff --git a/quantus_sdk/rust_builder/cargokit/build_tool/lib/src/builder.dart b/quantus_sdk/rust_builder/cargokit/build_tool/lib/src/builder.dart index f43b4c336..ae592e2ad 100644 --- a/quantus_sdk/rust_builder/cargokit/build_tool/lib/src/builder.dart +++ b/quantus_sdk/rust_builder/cargokit/build_tool/lib/src/builder.dart @@ -148,7 +148,7 @@ class RustBuilder { manifestPath, '-p', environment.crateInfo.packageName, - if (!environment.configuration.isDebug) '--release', + '--release', // always build in release mode - debug is very slow '--target', target.rust, '--target-dir', @@ -159,7 +159,7 @@ class RustBuilder { return path.join( environment.targetTempDir, target.rust, - environment.configuration.rustName, + 'release', ); }