Skip to content

Commit

Permalink
chore: accomplished implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
shumkov committed Oct 23, 2024
1 parent 5267e4a commit d5b6698
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 81 deletions.
50 changes: 50 additions & 0 deletions packages/dapi/lib/externalApis/tenderdash/requestTenderRpc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const UnavailableGrpcError = require('@dashevo/grpc-common/lib/server/error/UnavailableGrpcError');
const ResourceExhaustedGrpcError = require('@dashevo/grpc-common/lib/server/error/ResourceExhaustedGrpcError');
const RPCError = require('../../rpcServer/RPCError');

/**
* @param {jaysonClient} rpcClient
* @return {requestTenderRpc}
*/
function requestTenderRpcFactory(rpcClient) {
/**
* @typedef requestTenderRpc
* @param {string} uri
* @param {Object} [params]
* @return {Promise<Object>}
*/
return async function requestTenderRpc(uri, params = {}) {
let response;
try {
response = await rpcClient.request(uri, params);
} catch (e) {
if (e.message === 'socket hang up') {
throw new UnavailableGrpcError('Tenderdash is not available');
}

e.message = `Failed to request ${uri}: ${e.message}`;

throw e;
}

const { result, error: jsonRpcError } = response;

if (jsonRpcError) {
if (typeof jsonRpcError.data === 'string') {
if (jsonRpcError.data.includes('too_many_resets')) {
throw new ResourceExhaustedGrpcError('tenderdash is not responding: too many requests');
}
}

throw new RPCError(
jsonRpcError.code || -32602,
jsonRpcError.message || 'Internal error',
jsonRpcError.data,
);
}

return result;
};
}

module.exports = requestTenderRpcFactory;

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ const {
server: {
error: {
InvalidArgumentGrpcError,
AlreadyExistsGrpcError,
ResourceExhaustedGrpcError,
UnavailableGrpcError,
AlreadyExistsGrpcError,
InternalGrpcError,
},
},
} = require('@dashevo/grpc-common');
Expand All @@ -14,19 +15,23 @@ const {
BroadcastStateTransitionResponse,
},
} = require('@dashevo/dapi-grpc');

const crypto = require('crypto');

const logger = require('../../../logger');

/**
* @param {jaysonClient} rpcClient
* @param {createGrpcErrorFromDriveResponse} createGrpcErrorFromDriveResponse
* @param {fetchCachedStateTransitionResult} fetchCachedStateTransitionResult
* @param {requestTenderRpc} requestTenderRpc
*
* @returns {broadcastStateTransitionHandler}
*/
function broadcastStateTransitionHandlerFactory(
rpcClient,
createGrpcErrorFromDriveResponse,
fetchCachedStateTransitionResult) {
requestTenderRpc,
) {
/**
* @typedef broadcastStateTransitionHandler
*
Expand Down Expand Up @@ -61,16 +66,65 @@ function broadcastStateTransitionHandlerFactory(
throw e;
}

let { result } = response;
const { error: jsonRpcError } = response;
const { result, error: jsonRpcError } = response;

if (jsonRpcError) {
if (typeof jsonRpcError.data === 'string') {
if (jsonRpcError.data === 'tx already exists in cache') {
result = fetchCachedStateTransitionResult(stBytes);


throw new AlreadyExistsGrpcError('state transition already in chain');
// We need to figure out and report to user why the ST cached
const stHash = crypto.createHash('sha256')
.update(stBytes)
.digest();

// TODO: Apply search filter to fetch specific state transition
// Throw an already exist in mempool error if the ST in mempool
const unconfirmedTxsResponse = await requestTenderRpc('unconfirmed_txs', { limit: 100 });

if (unconfirmedTxsResponse?.txs?.includes(stBytes.toString('base64'))) {
throw new AlreadyExistsGrpcError('state transition already in mempool');
}

// Throw an already exist in chain error if the ST is committed
let txResponse;
try {
txResponse = await requestTenderRpc('tx', { hash: stHash.toString('base64') });
} catch (e) {
if (typeof e.data !== 'string' || !e.data.includes('not found')) {
throw e;
}
}

if (txResponse?.tx_result) {
throw new AlreadyExistsGrpcError('state transition already in chain');
}

// If the ST not in mempool and not in the state but still in the cache
// it means it was invalidated by CheckTx so we run CheckTx again to provide
// the validation error
const checkTxResponse = await requestTenderRpc('check_tx', { tx });

if (checkTxResponse?.code !== 0) {
// Return validation error
throw await createGrpcErrorFromDriveResponse(
checkTxResponse.code,
checkTxResponse.info,
);
} else {
// CheckTx passes for the ST, it means we have a bug in Drive so ST is passing check
// tx and then removed from the block. The removal from the block doesn't remove ST
// from the cache because it's happening only one proposer and other nodes do not know
// that this ST was processed and keep it in the cache
// The best what we can do is to return an internal error and and log the transaction
logger.warn({
tx,
}, `State transition ${stHash.toString('hex')} is passing CheckTx but removed from the block by proposal`);

const error = new Error('State Transition processing error. Please report'
+ ' faulty state transition and try to create a new state transition with different'
+ ' hash as a workaround.');

throw new InternalGrpcError(error);
}
}

if (jsonRpcError.data.startsWith('Tx too large.')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const waitForTransactionToBeProvableFactory = require('../../../externalApis/ten
const waitForTransactionResult = require('../../../externalApis/tenderdash/waitForTransactionToBeProvable/waitForTransactionResult');
const getExistingTransactionResultFactory = require('../../../externalApis/tenderdash/waitForTransactionToBeProvable/getExistingTransactionResult');
const getConsensusParamsFactory = require('../../../externalApis/tenderdash/getConsensusParamsFactory');
const requestTenderRpcFactory = require('../../../externalApis/tenderdash/requestTenderRpc');

/**
* @param {jaysonClient} rpcClient
Expand All @@ -73,10 +74,13 @@ function platformHandlersFactory(
) {
const wrapInErrorHandler = wrapInErrorHandlerFactory(logger, isProductionEnvironment);

const requestTenderRpc = requestTenderRpcFactory(rpcClient);

// broadcastStateTransition
const broadcastStateTransitionHandler = broadcastStateTransitionHandlerFactory(
rpcClient,
createGrpcErrorFromDriveResponse,
requestTenderRpc,
);

const wrappedBroadcastStateTransition = jsonToProtobufHandlerWrapper(
Expand Down

0 comments on commit d5b6698

Please sign in to comment.