Skip to content

Commit

Permalink
timeout retry
Browse files Browse the repository at this point in the history
added contract cache
added efficiency improvements, preventing multiple similar calls.
  • Loading branch information
n13 committed Nov 11, 2024
1 parent 70e260d commit 9e5ec10
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 21 deletions.
118 changes: 97 additions & 21 deletions lib/core/crypto/eosdart/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,37 @@ import 'package:hypha_wallet/core/crypto/eosdart/eosdart.dart';
import 'package:hypha_wallet/core/crypto/eosdart/src/jsons.dart';
import 'package:hypha_wallet/core/crypto/eosdart/src/serialize.dart' as ser;
import 'package:hypha_wallet/core/crypto/eosdart_ecc/eosdart_ecc.dart' as ecc;
import 'package:hypha_wallet/core/error_handler/extensions/blockchain_error_extension.dart';
import 'package:hypha_wallet/core/logging/log_helper.dart';
import 'package:hypha_wallet/core/network/networking_manager.dart';

class ContractCache {
Map<String, Contract> _contracts = {};
Map<String, DateTime> _timeStamps = {};
final int expirySeconds = 30;

void put(String name, Contract contract) {
_contracts[name] = contract;
_timeStamps[name] = DateTime.now();
}

Contract? get(String name) {
final contract = _contracts[name];
if (contract != null) {
final timeAdded = _timeStamps[name];
final expiryTime = timeAdded?.add(Duration(seconds: expirySeconds));
if (expiryTime != null && DateTime.now().isBefore(expiryTime)) {
return contract;
} else {
// expired
_contracts.remove(name);
_timeStamps.remove(name);
}
}
return null;
}
}

/// EOSClient calls APIs against given EOS nodes
class EOSClient extends NetworkingManager {
int expirationInSec;
Expand Down Expand Up @@ -174,9 +202,14 @@ class EOSClient extends NetworkingManager {
}

/// Get required key by transaction from EOS blockchain
Future<RequiredKeys> getRequiredKeys(Transaction transaction, List<String> availableKeys) async {
final NodeInfo info = await getInfo();
final Block refBlock = await getBlock(info.headBlockNum.toString());
Future<RequiredKeys> getRequiredKeys(
Transaction transaction,
List<String> availableKeys, {
NodeInfo? nodeInfo,
Block? headBlock,
}) async {
final NodeInfo info = nodeInfo ?? await getInfo();
final Block refBlock = headBlock ?? await getBlock(info.headBlockNum.toString());
Transaction trx = await _fullFill(transaction, refBlock);
trx = await _serializeActions(trx);

Expand Down Expand Up @@ -220,24 +253,54 @@ class EOSClient extends NetworkingManager {
}

/// Push transaction to EOS chain
Future<dio.Response> pushTransaction(Transaction transaction,
{bool sign = true, int blocksBehind = 3, int expireSecond = 180}) async {
Future<dio.Response> pushTransaction(
Transaction transaction, {
bool sign = true,
int blocksBehind = 3,
int expireSecond = 180,
int maxRetries = 2,
Duration retryDelay = const Duration(seconds: 1),
}) async {
var attempt = 0;
Object? timeoutException;
final NodeInfo info = await getInfo();
final Block refBlock = await getBlock((info.headBlockNum! - blocksBehind).toString());

final Transaction trx = await _fullFill(transaction, refBlock);
final PushTransactionArgs pushTransactionArgs =
await _pushTransactionArgs(info.chainId, transactionTypes['transaction']!, trx, sign);

return _post('/chain/push_transaction', {
'signatures': pushTransactionArgs.signatures,
'compression': 0,
'packed_context_free_data': '',
'packed_trx': ser.arrayToHex(pushTransactionArgs.serializedTransaction),
}).then((processedTrx) {
print('processedTrx $processedTrx');
return processedTrx;
});
final PushTransactionArgs pushTransactionArgs = await _pushTransactionArgs(
info.chainId, transactionTypes['transaction']!, trx, sign,
nodeInfo: info, headBlock: refBlock);

while (attempt < maxRetries) {
try {
final processedTrx = await _post('/chain/push_transaction', {
'signatures': pushTransactionArgs.signatures,
'compression': 0,
'packed_context_free_data': '',
'packed_trx': ser.arrayToHex(pushTransactionArgs.serializedTransaction),
});

print('processedTrx $processedTrx');
return processedTrx;
} catch (e) {
final errorString = e.extendedBlockchainErrorMessage().toLowerCase();
final isTimeoutError = errorString.contains('exceeded the current cpu usage limit') ||
errorString.contains('executing for too long');
if (isTimeoutError) {
timeoutException = e;
attempt++;
print('Timeout error occurred. Retrying attempt $attempt/$maxRetries after $retryDelay...');
await Future.delayed(retryDelay);
} else {
rethrow;
}
}
}
if (timeoutException != null) {
throw timeoutException;
} else {
throw Exception('Unexpected state after timeout');
}
}

/// Push transaction to EOS chain
Expand All @@ -253,9 +316,14 @@ class EOSClient extends NetworkingManager {
return pushTransactionArgs;
}

final _contractCache = ContractCache();

/// Get data needed to serialize actions in a contract */
// ignore: unused_element
Future<Contract> _getContract(String? accountName, {bool reload = false}) async {
Future<Contract> _getContract(String accountName, {bool reload = false}) async {
final cachedContract = _contractCache.get(accountName);
if (cachedContract != null) return cachedContract;

final abi = await getRawAbi(accountName);
final types = ser.getTypesFromAbi(ser.createInitialTypes(), abi.abi!);
// ignore: prefer_collection_literals
Expand All @@ -264,6 +332,7 @@ class EOSClient extends NetworkingManager {
actions[act?.name] = ser.getType(types, act?.type);
}
final result = Contract(types, actions);
_contractCache.put(accountName, result);
return result;
}

Expand All @@ -281,7 +350,7 @@ class EOSClient extends NetworkingManager {
for (final Action? action in transaction.actions!) {
final String? account = action?.account;

final Contract contract = await _getContract(account);
final Contract contract = await _getContract(account!);

action?.data = _serializeActionData(contract, account, action.name, action.data);
}
Expand Down Expand Up @@ -313,10 +382,17 @@ class EOSClient extends NetworkingManager {
// }

Future<PushTransactionArgs> _pushTransactionArgs(
String? chainId, Type transactionType, Transaction transaction, bool sign) async {
String? chainId,
Type transactionType,
Transaction transaction,
bool sign, {
NodeInfo? nodeInfo,
Block? headBlock,
}) async {
final List<String> signatures = [];

final RequiredKeys requiredKeys = await getRequiredKeys(transaction, keys.keys.toList());
final RequiredKeys requiredKeys =
await getRequiredKeys(transaction, keys.keys.toList(), nodeInfo: nodeInfo, headBlock: headBlock);

final Uint8List serializedTrx = transaction.toBinary(transactionType);

Expand Down
28 changes: 28 additions & 0 deletions lib/core/error_handler/extensions/blockchain_error_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:dio/dio.dart';
import 'package:hypha_wallet/core/logging/log_helper.dart';

extension BlockChainErrorExtension on Object {
String extendedBlockchainErrorMessage() {
final DioException? thisException = (this is DioException ? this : null) as DioException?;
if (thisException != null && thisException.response != null) {
try {
final data = thisException.response!.data;
final code = data['code'];
final errorData = data['error'] as Map;
final what = errorData['what'];
final details = errorData['details'] as List;
final detailedMessage = details[0];

return 'Error Code: $code'
'\nWhat: $what'
'\nMessage: $detailedMessage';
} catch (e) {
LogHelper.e('error during parsing of block chain error $e');
return 'Raw error: ${thisException.response!.data}';
}
}

return toString();
}
}
// flutter: Response Data: {code: 401, message: UnAuthorized, error: {code: 3090003, name: unsatisfied_authorization, what: Provided keys, permissions, and delays do not satisfy declared authorizations, details: [{message: transaction declares authority '{"actor":"illumination","permission":"owner"}', but does not have signatures for it., file: authorization_manager.cpp, line_number: 626, method: get_required_keys}]}}

0 comments on commit 9e5ec10

Please sign in to comment.