From ef494d19129007c76ac7e0847c420f43fd852d6b Mon Sep 17 00:00:00 2001 From: parodyBit <58690522+parodyBit@users.noreply.github.com> Date: Thu, 5 Sep 2024 20:14:00 -0600 Subject: [PATCH 1/2] feat: mock explorer adds a tool to run a mock version of the block explorer to get simulated transactions to test specific cases. to enable the feature: - in `lib/constanst.dart` set `USE_EXPLORER_MOCK` and `USE_MOCK_WALLETS_FILE ` to true - set the `EXPLORER_MOCK_ADDRESS` to the correct local address in the .env file - run `dart bin/mock_explorer.dart` - run the main app the test transactions in `bin/mock_data.json` are tied to the test node xprv in the .env.example file --- .env.example | 2 +- bin/mock_data.json | 90 ++++++++++++ bin/mock_explorer.dart | 134 ++++++++++++++++++ lib/bloc/explorer/api_explorer.dart | 14 +- lib/constants.dart | 3 + lib/util/storage/path_provider_interface.dart | 4 + pubspec.yaml | 1 + 7 files changed, 243 insertions(+), 5 deletions(-) create mode 100644 bin/mock_data.json create mode 100644 bin/mock_explorer.dart diff --git a/.env.example b/.env.example index 05f4a13a3..557d5531c 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ EXPLORER_ADDRESS = 'witnet.network' EXPLORER_DEV_ADDRESS = '0.0.0.0' - +EXPLORER_MOCK_ADDRESS = '0.0.0.0:8080' # [ Integration Test Settings ] # how long to delay on the first screen diff --git a/bin/mock_data.json b/bin/mock_data.json new file mode 100644 index 000000000..48ac4d4c4 --- /dev/null +++ b/bin/mock_data.json @@ -0,0 +1,90 @@ +{ + "headers": { + "content-length": "body.length", + "content-type": "text/plain; charset=utf-8", + "x-content-type-options": "nosniff", + "x-frame-options": "SAMEORIGIN", + "x-powered-by": "Dart with package:shelf", + "x-xss-protection": "1; mode=block", + "x-version": "1.0.0" + }, + "status": { + "database_confirmed": { + "epoch": 1, + "hash": "69fae23c1a316313189559fa6ecb10213e90770cdd0288b8873d3462051f362f" + }, + "database_message": "database processes seem healthy", + "database_unconfirmed": { + "epoch": 1, + "hash": "41250277bd1601f11659077df12cb5cc39efff19d1ef1ec37992d8060223ac86" + }, + "expected_epoch": 1, + "message": "all backend services are up and running", + "node_pool_message": { + "epoch": 1, + "message": "fetched node pool status correctly", + "status": "Synced" + } + }, + "utxos": { + "wit1vzm7xrguwf5uzjx72l65stgj3npfn292tya50u": { + "address": "wit1vzm7xrguwf5uzjx72l65stgj3npfn292tya50u", + "utxos": [ + {"output_pointer":"0000000000000000000000000000000000000000000000000000000000000001:0","timelock":0,"utxo_mature":false,"value":805030938}, + {"output_pointer":"0000000000000000000000000000000000000000000000000000000000000001:1","timelock":0,"utxo_mature":false,"value":799999912469} + ] + } + }, + "value-transfers": { + "wit1vzm7xrguwf5uzjx72l65stgj3npfn292tya50u": [ + { + "confirmed":true, + "direction":"self", + "epoch":2563701, + "fee":0, + "hash":"0000000000000000000000000000000000000000000000000000000000000001", + "input_addresses":["wit100000000000000000000000000000000r0v4g2"], + "locked":false, + "output_addresses":["wit1vzm7xrguwf5uzjx72l65stgj3npfn292tya50u"], + "priority":1, + "timestamp":1718032590, + "value":0, + "weight":493} + ] + }, + "priority": { + "vtt_stinky": {"priority": 0.1, "time_to_block": 21600}, + "vtt_low": {"priority": 0.2, "time_to_block": 3600}, + "vtt_medium": {"priority": 0.3, "time_to_block": 900}, + "vtt_high": {"priority": 0.4, "time_to_block": 300}, + "vtt_opulent": {"priority": 0.5, "time_to_block": 60} + }, + "hashes": { + "0000000000000000000000000000000000000000000000000000000000000001": { + "response_type":"value_transfer", + "value_transfer": { + "block":"0000000000000000000000000000000000000000000000000000000000000000", + "change_output_addresses":["wit100000000000000000000000000000000r0v4g2"], + "change_value":0, + "confirmed":true, + "epoch":2052870, + "fee":0, + "hash":"0000000000000000000000000000000000000000000000000000000000000001", + "input_addresses":["wit100000000000000000000000000000000r0v4g2"], + "input_utxos":[ + {"address":"wit100000000000000000000000000000000r0v4g2", "input_utxo":"000000000000000000000000000000000000000000000000000000000000000A:1", "value":800804943407}], + "inputs_merged":[{"address":"wit100000000000000000000000000000000r0v4g2", "value":800804943407}], + "output_addresses":["wit1vzm7xrguwf5uzjx72l65stgj3npfn292tya50u","wit1vzm7xrguwf5uzjx72l65stgj3npfn292tya50u"], + "output_values":[805030938,799999912469], + "priority":1, + "reverted":false, + "timelocks":[0,0], + "timestamp":1695045195, + "true_output_addresses":[], + "true_value":0, + "utxos":[ + {"address":"wit1vzm7xrguwf5uzjx72l65stgj3npfn292tya50u","locked":false,"timelock":0,"value":805030938}, + {"address":"wit1vzm7xrguwf5uzjx72l65stgj3npfn292tya50u","locked":false,"timelock":0,"value":799999912469}], + "utxos_merged":[{"address":"wit1vzm7xrguwf5uzjx72l65stgj3npfn292tya50u","locked":false,"timelock":0,"value":800804943407}],"value":800804943407,"weight":853}} + } +} \ No newline at end of file diff --git a/bin/mock_explorer.dart b/bin/mock_explorer.dart new file mode 100644 index 000000000..364bbc584 --- /dev/null +++ b/bin/mock_explorer.dart @@ -0,0 +1,134 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:shelf/shelf.dart'; +import 'package:shelf/shelf_io.dart'; +import 'package:shelf_router/shelf_router.dart'; + +void main(List args) async { + await _loadMockData(); + final InternetAddress ip = InternetAddress.anyIPv4; + final FutureOr Function(Request) handler = + Pipeline().addMiddleware(logRequests()).addHandler(_router); + final int port = int.parse(Platform.environment['PORT'] ?? '8080'); + final HttpServer server = await serve(handler, ip, port); + print('Server listening on ${ip.address}:${server.port}'); +} + +// Configure routes. +final _router = Router() + ..get('/', _rootHandler) + ..get('/echo/', _echoHandler) + ..get('/api/status', statusHandler) + ..get('/api/address/', addressHandler) + ..get('/api/transaction/', transactionHandler) + ..get('/api/search/', searchHandler); + +Future> readJsonFile(String filePath) async { + var input = await File(filePath).readAsString(); + var map = jsonDecode(input); + return map; +} + +Response searchHandler(Request request) { + final Map queryParams = request.url.queryParameters; + return Response.ok( + json.encode(_hashData[queryParams['value']]), + headers: _headerData, + ); +} + +Response statusHandler(Request request) { + return Response.ok(json.encode(_statusData), headers: _headerData); +} + +Future _utxos(Request request) async { + final Map queryParams = request.url.queryParameters; + List addresses = queryParams['addresses']; + List addressList = []; + addresses.forEach((address) { + if (_utxoData.containsKey(address)) { + addressList.add(json.encode(_utxoData[address]!)); + } else { + addressList.add(json.encode({"address": address, "utxos": []})); + } + }); + return Response.ok(addressList.toString(), headers: _headerData); +} + +Response _valueTransfers(Request request) { + Map queryParams = request.requestedUri.queryParameters; + String response = json.encode(_vttData[queryParams['address']]!); + return Response.ok(response, headers: _headerData); +} + +Response _dataRequestsSolved(Request request) { + return Response.ok('[]', headers: _headerData); +} + +Response _blocks(Request request) { + return Response.ok('[]', headers: _headerData); +} + +Future addressHandler(Request request) async { + String method = + request.requestedUri.toString().split('/').last.split('?').first; + + switch (method) { + case "utxos": + return _utxos(request); + case 'value-transfers': + return _valueTransfers(request); + case 'data-requests-solved': + return _dataRequestsSolved(request); + case 'blocks': + return _blocks(request); + default: + return Response.notFound(""); + } +} + +Response transactionHandler(Request request) { + String method = + request.requestedUri.toString().split('/').last.split('?').first; + switch (method) { + case 'priority': + return Response.ok(json.encode(_priorityData)); + } + return Response.ok(''); +} + +Response _rootHandler(Request req) { + return Response.ok('Hello, World!\n'); +} + +Response _echoHandler(Request request) { + final message = request.params['message']; + return Response.ok('$message\n'); +} + +Map _headerData = {}; +Map _statusData = {}; +Map _utxoData = {}; +Map _vttData = {}; +Map _hashData = {}; +Map _priorityData = {}; + +Future _loadMockData() async { + var input = await File('bin/mock_data.json').readAsString(); + var map = jsonDecode(input); + _headerData = Map.from(map['headers']); + _headerData['x-pagination'] = json.encode({ + "total": 1, + "total_pages": 1, + "first_page": 1, + "last_page": 1, + "page": 1 + }); + _statusData = map['status']; + _utxoData = map['utxos']; + _vttData = map['value-transfers']; + _priorityData = map['priority']; + _hashData = map['hashes']; +} diff --git a/lib/bloc/explorer/api_explorer.dart b/lib/bloc/explorer/api_explorer.dart index 5157e6320..fbf9aaa75 100644 --- a/lib/bloc/explorer/api_explorer.dart +++ b/lib/bloc/explorer/api_explorer.dart @@ -28,10 +28,16 @@ class ApiExplorer { late ExplorerClient client; late Status status; ApiExplorer() { - client = (USE_EXPLORER_DEV) - ? ExplorerClient( - url: EXPLORER_DEV_ADDRESS, mode: ExplorerMode.development) - : ExplorerClient(url: EXPLORER_ADDRESS, mode: ExplorerMode.production); + + if(USE_EXPLORER_MOCK) { + client = ExplorerClient( + url: EXPLORER_MOCK_ADDRESS, mode: ExplorerMode.development); + } else { + client = (USE_EXPLORER_DEV) + ? ExplorerClient( + url: EXPLORER_DEV_ADDRESS, mode: ExplorerMode.development) + : ExplorerClient(url: EXPLORER_ADDRESS, mode: ExplorerMode.production); + } } Future hash(String value, [bool simple = true]) async { diff --git a/lib/constants.dart b/lib/constants.dart index c9a9a3b82..caa05657a 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -49,10 +49,13 @@ Map localizedConfigSteps = { /// Explorer Settings const bool USE_EXPLORER_DEV = false; +const bool USE_EXPLORER_MOCK = false; +const bool USE_MOCK_WALLETS_FILE = false; // ignore: non_constant_identifier_names String EXPLORER_ADDRESS = dotenv.get('EXPLORER_ADDRESS'); // ignore: non_constant_identifier_names String EXPLORER_DEV_ADDRESS = dotenv.get('EXPLORER_DEV_ADDRESS'); +String EXPLORER_MOCK_ADDRESS = dotenv.get('EXPLORER_MOCK_ADDRESS'); const int EXPLORER_DELAY_MS = 100; const int SYNC_TIMER_IN_SECONDS = 30; diff --git a/lib/util/storage/path_provider_interface.dart b/lib/util/storage/path_provider_interface.dart index 2f3a9e552..263f35bec 100644 --- a/lib/util/storage/path_provider_interface.dart +++ b/lib/util/storage/path_provider_interface.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io' as io; +import 'package:my_wit_wallet/constants.dart'; import 'package:my_wit_wallet/globals.dart' as globals; import 'package:path_provider/path_provider.dart'; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; @@ -71,6 +72,9 @@ class PathProviderInterface { } String getDbWalletsPath() { + if (USE_MOCK_WALLETS_FILE){ + return getFilePath('mock-wallets', 'wit'); + } return getFilePath( globals.testingActive ? 'test-wallets' : 'wallets', 'wit'); } diff --git a/pubspec.yaml b/pubspec.yaml index 6e34c64b8..0331b5d8e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,6 +58,7 @@ dependencies: updat: ^1.2.0+1 logger: ^2.0.2+1 pub_semver: ^2.1.4 + shelf_router: ^1.1.0 flutter_icons: android: true From 3ae4b97422411187631d8f2a3cb0a721f6736375 Mon Sep 17 00:00:00 2001 From: parodyBit <58690522+parodyBit@users.noreply.github.com> Date: Fri, 6 Sep 2024 08:19:35 -0600 Subject: [PATCH 2/2] fix: pagination on utxos for mock explorer --- bin/mock_explorer.dart | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/bin/mock_explorer.dart b/bin/mock_explorer.dart index 364bbc584..959ca0331 100644 --- a/bin/mock_explorer.dart +++ b/bin/mock_explorer.dart @@ -45,16 +45,24 @@ Response statusHandler(Request request) { Future _utxos(Request request) async { final Map queryParams = request.url.queryParameters; - List addresses = queryParams['addresses']; + var addresses = queryParams['addresses']; List addressList = []; - addresses.forEach((address) { - if (_utxoData.containsKey(address)) { - addressList.add(json.encode(_utxoData[address]!)); + if (addresses.runtimeType == String) { + if (_utxoData.containsKey(addresses)) { + addressList.add(json.encode(_utxoData[addresses]!)); } else { - addressList.add(json.encode({"address": address, "utxos": []})); + addressList.add(json.encode({"address": addresses, "utxos": []})); } - }); - return Response.ok(addressList.toString(), headers: _headerData); + } else { + addresses.forEach((address) { + if (_utxoData.containsKey(address)) { + addressList.add(json.encode(_utxoData[address]!)); + } else { + addressList.add(json.encode({"address": address, "utxos": []})); + } + }); + } + return Response.ok(addressList.toString()); } Response _valueTransfers(Request request) {