diff --git a/lib/main.dart b/lib/main.dart index d599c33bb7..445e40d321 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,6 +21,7 @@ import 'package:ardrive/services/authentication/biometric_authentication.dart'; import 'package:ardrive/services/config/config_fetcher.dart'; import 'package:ardrive/shared/blocs/banner/app_banner_bloc.dart'; import 'package:ardrive/sharing/blocs/sharing_file_bloc.dart'; +import 'package:ardrive/sync/data/snapshot_validation_service.dart'; import 'package:ardrive/sync/domain/repositories/sync_repository.dart'; import 'package:ardrive/sync/utils/batch_processor.dart'; import 'package:ardrive/theme/theme_switcher_bloc.dart'; @@ -423,6 +424,9 @@ class AppState extends State { driveDao: _.read(), licenseService: _.read(), batchProcessor: BatchProcessor(), + snapshotValidationService: SnapshotValidationService( + configService: configService, + ), ), ), RepositoryProvider( diff --git a/lib/pages/drive_detail/drive_detail_page.dart b/lib/pages/drive_detail/drive_detail_page.dart index 3e60fd65db..0af1e05e84 100644 --- a/lib/pages/drive_detail/drive_detail_page.dart +++ b/lib/pages/drive_detail/drive_detail_page.dart @@ -154,7 +154,7 @@ class _DriveDetailPageState extends State { }, child: BlocBuilder( buildWhen: (previous, current) { - return context.read().state is! SyncInProgress; + return widget.context.read().state is! SyncInProgress; }, builder: (context, driveDetailState) { if (driveDetailState is DriveDetailLoadInProgress) { diff --git a/lib/sync/data/snapshot_validation_service.dart b/lib/sync/data/snapshot_validation_service.dart new file mode 100644 index 0000000000..26a50c1b8b --- /dev/null +++ b/lib/sync/data/snapshot_validation_service.dart @@ -0,0 +1,63 @@ +import 'package:ardrive/services/config/config_service.dart'; +import 'package:ardrive/utils/logger.dart'; +import 'package:ardrive/utils/snapshots/snapshot_item.dart'; +import 'package:http/http.dart' as http; + +class SnapshotValidationService { + final ConfigService _configService; + + SnapshotValidationService({ + required ConfigService configService, + }) : _configService = configService; + + Future> validateSnapshotItems( + List snapshotItems, + ) async { + List snapshotsVerified = []; + + final futures = snapshotItems.map((snapshotItem) async { + final appConfig = _configService.config; + + try { + final snapshotValidation = await http.head( + Uri.parse( + '${appConfig.defaultArweaveGatewayUrl}/${snapshotItem.txId}'), + ); + + logger.d('Validating snapshot ${snapshotItem.txId}'); + + if (snapshotValidation.statusCode == 200) { + if (snapshotValidation.headers['content-length'] != null) { + int lenght = + int.parse(snapshotValidation.headers['content-length']!); + + final headers = { + 'Range': 'bytes=${lenght - 8}-$lenght', + }; + + final validationRequest = await http.get( + Uri.parse( + '${appConfig.defaultArweaveGatewayUrl}${snapshotItem.txId}'), + headers: headers, + ); + + logger.d( + 'Validation request status code: ${validationRequest.statusCode}'); + } + logger.d('Snapshot ${snapshotItem.txId} is valid'); + + snapshotsVerified.add(snapshotItem); + } + } catch (e) { + logger.w('Error while validating snapshot. ${snapshotItem.txId}'); + } + }); + + await Future.wait(futures); + + snapshotItems.clear(); + snapshotItems = snapshotsVerified; + + return snapshotItems; + } +} diff --git a/lib/sync/domain/repositories/sync_repository.dart b/lib/sync/domain/repositories/sync_repository.dart index f6bae973a8..ba039eae6b 100644 --- a/lib/sync/domain/repositories/sync_repository.dart +++ b/lib/sync/domain/repositories/sync_repository.dart @@ -21,6 +21,7 @@ import 'package:ardrive/services/config/config.dart'; import 'package:ardrive/services/license/license_service.dart'; import 'package:ardrive/services/license/license_state.dart'; import 'package:ardrive/sync/constants.dart'; +import 'package:ardrive/sync/data/snapshot_validation_service.dart'; import 'package:ardrive/sync/domain/ghost_folder.dart'; import 'package:ardrive/sync/domain/models/drive_entity_history.dart'; import 'package:ardrive/sync/domain/sync_progress.dart'; @@ -89,6 +90,7 @@ abstract class SyncRepository { required ConfigService configService, required LicenseService licenseService, required BatchProcessor batchProcessor, + required SnapshotValidationService snapshotValidationService, }) { return _SyncRepository( arweave: arweave, @@ -96,6 +98,7 @@ abstract class SyncRepository { configService: configService, licenseService: licenseService, batchProcessor: batchProcessor, + snapshotValidationService: snapshotValidationService, ); } } @@ -106,6 +109,7 @@ class _SyncRepository implements SyncRepository { final ConfigService _configService; final LicenseService _licenseService; final BatchProcessor _batchProcessor; + final SnapshotValidationService _snapshotValidationService; final Map _ghostFolders = {}; final Set _folderIds = {}; @@ -118,10 +122,12 @@ class _SyncRepository implements SyncRepository { required ConfigService configService, required LicenseService licenseService, required BatchProcessor batchProcessor, + required SnapshotValidationService snapshotValidationService, }) : _arweave = arweave, _driveDao = driveDao, _configService = configService, _licenseService = licenseService, + _snapshotValidationService = snapshotValidationService, _batchProcessor = batchProcessor; @override @@ -552,6 +558,11 @@ class _SyncRepository implements SyncRepository { snapshotsStream, arweave: _arweave, ).toList(); + + List snapshotsVerified = + await _snapshotValidationService.validateSnapshotItems(snapshotItems); + + snapshotItems = snapshotsVerified; } final SnapshotDriveHistory snapshotDriveHistory = SnapshotDriveHistory( diff --git a/lib/utils/snapshots/snapshot_item.dart b/lib/utils/snapshots/snapshot_item.dart index a6d258896c..f5e9f0dd49 100644 --- a/lib/utils/snapshots/snapshot_item.dart +++ b/lib/utils/snapshots/snapshot_item.dart @@ -22,6 +22,7 @@ abstract class SnapshotItem implements SegmentedGQLData { abstract final int blockStart; abstract final int blockEnd; abstract final DriveID driveId; + abstract final String txId; factory SnapshotItem.fromGQLNode({ required SnapshotEntityTransaction node, @@ -72,6 +73,7 @@ abstract class SnapshotItem implements SegmentedGQLData { fakeSource: fakeSource, arweave: arweave, ); + } catch (e) { logger.e('Ignoring snapshot transaction with invalid block range', e); continue; @@ -133,6 +135,8 @@ abstract class SnapshotItem implements SegmentedGQLData { class SnapshotItemOnChain implements SnapshotItem { final int timestamp; + + @override final TxID txId; String? _cachedSource; int _currentIndex = -1;