diff --git a/packages/dapi/lib/grpcServer/handlers/platform/broadcastStateTransitionHandlerFactory.js b/packages/dapi/lib/grpcServer/handlers/platform/broadcastStateTransitionHandlerFactory.js index 335794d42f2..245d95c4dce 100644 --- a/packages/dapi/lib/grpcServer/handlers/platform/broadcastStateTransitionHandlerFactory.js +++ b/packages/dapi/lib/grpcServer/handlers/platform/broadcastStateTransitionHandlerFactory.js @@ -4,6 +4,7 @@ const { InvalidArgumentGrpcError, AlreadyExistsGrpcError, ResourceExhaustedGrpcError, + UnavailableGrpcError, }, }, } = require('@dashevo/grpc-common'); @@ -44,7 +45,11 @@ function broadcastStateTransitionHandlerFactory(rpcClient, createGrpcErrorFromDr try { response = await rpcClient.request('broadcast_tx_sync', { tx }); } catch (e) { - logger.error(e, 'Failed broadcasting state transition'); + logger.error(`Failed broadcasting state transition: ${e}`); + + if (e.message === 'socket hang up') { + throw new UnavailableGrpcError('Tenderdash is not available'); + } throw e; } @@ -65,12 +70,18 @@ function broadcastStateTransitionHandlerFactory(rpcClient, createGrpcErrorFromDr if (jsonRpcError.data.startsWith('mempool is full')) { throw new ResourceExhaustedGrpcError(jsonRpcError.data); } + + if (jsonRpcError.data.startsWith('broadcast confirmation not received:')) { + logger.error(`Failed broadcasting state transition: ${jsonRpcError.data}`); + + throw new UnavailableGrpcError(jsonRpcError.data); + } } const error = new Error(); Object.assign(error, jsonRpcError); - logger.error(error, 'Unexpected JSON RPC error during broadcasting state transition'); + logger.error(error, `Unexpected JSON RPC error during broadcasting state transition: ${JSON.stringify(jsonRpcError)}`); throw error; } diff --git a/packages/dapi/test/unit/grpcServer/handlers/platform/broadcastStateTransitionHandlerFactory.spec.js b/packages/dapi/test/unit/grpcServer/handlers/platform/broadcastStateTransitionHandlerFactory.spec.js index 1b9fa6867c9..4a1b09a9a97 100644 --- a/packages/dapi/test/unit/grpcServer/handlers/platform/broadcastStateTransitionHandlerFactory.spec.js +++ b/packages/dapi/test/unit/grpcServer/handlers/platform/broadcastStateTransitionHandlerFactory.spec.js @@ -3,6 +3,8 @@ const { error: { InvalidArgumentGrpcError, AlreadyExistsGrpcError, + UnavailableGrpcError, + ResourceExhaustedGrpcError, }, }, } = require('@dashevo/grpc-common'); @@ -115,23 +117,72 @@ describe('broadcastStateTransitionHandlerFactory', () => { expect(rpcClientMock.request).to.be.calledOnceWith('broadcast_tx_sync', { tx }); }); - it('should throw an error if transaction broadcast returns error', async () => { - const error = { code: -1, message: "Something didn't work", data: 'Some data' }; + it('should throw a UnavailableGrpcError if tenderdash hands up', async () => { + const error = new Error('socket hang up'); + rpcClientMock.request.throws(error); - response.error = error; + try { + await broadcastStateTransitionHandler(call); + + expect.fail('should throw UnavailableGrpcError'); + } catch (e) { + expect(e).to.be.an.instanceOf(UnavailableGrpcError); + expect(e.getMessage()).to.equal('Tenderdash is not available'); + } + }); + + it('should throw a UnavailableGrpcError if broadcast confirmation not received', async () => { + response.error = { + code: -32603, + message: 'Internal error', + data: 'broadcast confirmation not received: heya', + }; try { await broadcastStateTransitionHandler(call); - expect.fail('should throw an error'); + expect.fail('should throw UnavailableGrpcError'); } catch (e) { - expect(e.message).to.equal(error.message); - expect(e.data).to.equal(error.data); - expect(e.code).to.equal(error.code); + expect(e).to.be.an.instanceOf(UnavailableGrpcError); + expect(e.getMessage()).to.equal(response.error.data); } }); - it('should throw FailedPreconditionGrpcError if transaction was broadcasted twice', async () => { + it('should throw an InvalidArgumentGrpcError if state transition size is too big', async () => { + response.error = { + code: -32603, + message: 'Internal error', + data: 'Tx too large. La la la', + }; + + try { + await broadcastStateTransitionHandler(call); + + expect.fail('should throw UnavailableGrpcError'); + } catch (e) { + expect(e).to.be.an.instanceOf(InvalidArgumentGrpcError); + expect(e.getMessage()).to.equal('state transition is too large. La la la'); + } + }); + + it('should throw a ResourceExhaustedGrpcError if mempool is full', async () => { + response.error = { + code: -32603, + message: 'Internal error', + data: 'mempool is full: heya', + }; + + try { + await broadcastStateTransitionHandler(call); + + expect.fail('should throw UnavailableGrpcError'); + } catch (e) { + expect(e).to.be.an.instanceOf(ResourceExhaustedGrpcError); + expect(e.getMessage()).to.equal(response.error.data); + } + }); + + it('should throw AlreadyExistsGrpcError if transaction was broadcasted twice', async () => { response.error = { code: -32603, message: 'Internal error', @@ -148,7 +199,7 @@ describe('broadcastStateTransitionHandlerFactory', () => { } }); - it('should throw call createGrpcErrorFromDriveResponse if error code is not 0', async () => { + it('should throw a gRPC error based on drive\'s response', async () => { const message = 'not found'; const metadata = { data: 'some data', @@ -176,4 +227,20 @@ describe('broadcastStateTransitionHandlerFactory', () => { ); } }); + + it('should throw an error if transaction broadcast returns unknown error', async () => { + const error = { code: -1, message: "Something didn't work", data: 'Some data' }; + + response.error = error; + + try { + await broadcastStateTransitionHandler(call); + + expect.fail('should throw an error'); + } catch (e) { + expect(e.message).to.equal(error.message); + expect(e.data).to.equal(error.data); + expect(e.code).to.equal(error.code); + } + }); });