From 48c967d85393ac988c714ef9ec59b71488a22af0 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Tue, 11 Feb 2025 18:17:36 +0700 Subject: [PATCH 1/6] feat(sdk): return state transition execution error --- ...tForStateTransitionResultHandlerFactory.js | 6 ++- packages/rs-sdk/src/error.rs | 42 +++++++++++++++++++ .../src/platform/transition/broadcast.rs | 30 ++++++++++++- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/packages/dapi/lib/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.js b/packages/dapi/lib/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.js index 4bfafbe30f9..69652d14eff 100644 --- a/packages/dapi/lib/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.js +++ b/packages/dapi/lib/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.js @@ -49,9 +49,13 @@ function waitForStateTransitionResultHandlerFactory( const error = new StateTransitionBroadcastError(); + const metadata = grpcError.getRawMetadata(); + if (metadata['dash-serialized-consensus-error-bin']) { + error.setData(metadata['dash-serialized-consensus-error-bin']); + } + error.setCode(txDeliverResult.code); error.setMessage(grpcError.getMessage()); - error.setData(cbor.encode(grpcError.getRawMetadata())); return error; } diff --git a/packages/rs-sdk/src/error.rs b/packages/rs-sdk/src/error.rs index 16edd7496ce..276595958e4 100644 --- a/packages/rs-sdk/src/error.rs +++ b/packages/rs-sdk/src/error.rs @@ -1,4 +1,5 @@ //! Definitions of errors +use dapi_grpc::platform::v0::StateTransitionBroadcastError as StateTransitionBroadcastErrorProto; use dapi_grpc::tonic::Code; use dpp::consensus::ConsensusError; use dpp::serialization::PlatformDeserializable; @@ -77,6 +78,47 @@ pub enum Error { /// Remote node is stale; try another server #[error(transparent)] StaleNode(#[from] StaleNodeError), + + /// Error returned when trying to broadcast a state transition + #[error(transparent)] + StateTransitionBroadcastError(#[from] StateTransitionBroadcastError), +} + +/// State transition broadcast error +#[derive(Debug, thiserror::Error)] +#[error("state transition broadcast error: {message}")] +pub struct StateTransitionBroadcastError { + /// Error code + pub code: u32, + /// Error message + pub message: String, + /// Consensus error caused the state transition broadcast error + pub cause: Option, +} + +impl TryFrom for StateTransitionBroadcastError { + type Error = Error; + + fn try_from(value: StateTransitionBroadcastErrorProto) -> Result { + let cause = if value.data.len() > 0 { + let consensus_error = + ConsensusError::deserialize_from_bytes(&value.data).map_err(|e| { + tracing::debug!("Failed to deserialize consensus error: {}", e); + + Error::Protocol(e) + })?; + + Some(consensus_error) + } else { + None + }; + + Ok(Self { + code: value.code, + message: value.message, + cause, + }) + } } // TODO: Decompose DapiClientError to more specific errors like connection, node error instead of DAPI client error diff --git a/packages/rs-sdk/src/platform/transition/broadcast.rs b/packages/rs-sdk/src/platform/transition/broadcast.rs index f7c3f75d32e..72957948b28 100644 --- a/packages/rs-sdk/src/platform/transition/broadcast.rs +++ b/packages/rs-sdk/src/platform/transition/broadcast.rs @@ -1,9 +1,13 @@ use super::broadcast_request::BroadcastRequestForStateTransition; use super::put_settings::PutSettings; +use crate::error::StateTransitionBroadcastError; use crate::platform::block_info_from_metadata::block_info_from_metadata; use crate::sync::retry; use crate::{Error, Sdk}; -use dapi_grpc::platform::v0::{Proof, WaitForStateTransitionResultResponse}; +use dapi_grpc::platform::v0::wait_for_state_transition_result_response::wait_for_state_transition_result_response_v0; +use dapi_grpc::platform::v0::{ + wait_for_state_transition_result_response, Proof, WaitForStateTransitionResultResponse, +}; use dapi_grpc::platform::VersionedGrpcResponse; use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; @@ -80,6 +84,30 @@ impl BroadcastStateTransition for StateTransition { let response = request.execute(sdk, request_settings).await.inner_into()?; let grpc_response: &WaitForStateTransitionResultResponse = &response.inner; + + // We use match here to have a compilation error if a new version of the response is introduced + let state_transition_broadcast_error = match &grpc_response.version { + Some(wait_for_state_transition_result_response::Version::V0(result)) => { + match &result.result { + Some(wait_for_state_transition_result_response_v0::Result::Error(e)) => { + Some(e) + } + _ => None, + } + } + None => None, + }; + + if let Some(e) = state_transition_broadcast_error { + let state_transition_broadcast_error: StateTransitionBroadcastError = + StateTransitionBroadcastError::try_from(e.clone()) + .wrap_to_execution_result(&response)? + .inner; + + return Err(Error::from(state_transition_broadcast_error)) + .wrap_to_execution_result(&response); + } + let metadata = grpc_response .metadata() .wrap_to_execution_result(&response)? From 52e0592914b6427a5745daab06fe20f95d0b1069 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Tue, 11 Feb 2025 18:23:13 +0700 Subject: [PATCH 2/6] fix: pass error data to a metadata field in JS SDK --- .../src/SDK/Client/Platform/broadcastStateTransition.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts index a12b67872f4..3c0bd8664d9 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts @@ -89,7 +89,9 @@ export default async function broadcastStateTransition( // instead of passing it as GrpcError constructor argument // Otherwise it will be converted to grpc-js metadata // Which is not compatible with web - grpcError.metadata = error.data; + grpcError.metadata = { + 'dash-serialized-consensus-error-bin': error.data, + }; let cause = await createGrpcTransportError(grpcError); From 7c3a7a41a15a6f1da51b9907f94267f9740b450c Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 13 Feb 2025 15:47:06 +0700 Subject: [PATCH 3/6] test: fix waitForStateTransitionResultHandlerFactory test --- ...tateTransitionResultHandlerFactory.spec.js | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/dapi/test/integration/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.spec.js b/packages/dapi/test/integration/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.spec.js index 9e996362167..21559b20848 100644 --- a/packages/dapi/test/integration/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.spec.js +++ b/packages/dapi/test/integration/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.spec.js @@ -64,7 +64,7 @@ describe('waitForStateTransitionResultHandlerFactory', () => { errorInfo = { message: 'Identity not found', metadata: { - error: 'some data', + 'dash-serialized-consensus-error-bin': Buffer.from('0122a249dac309c9a8b775316c905688da04bf0ee05b3861db05814540c32fba4179', 'hex'), }, }; @@ -305,26 +305,28 @@ describe('waitForStateTransitionResultHandlerFactory', () => { it('should wait for state transition and return result with error', (done) => { waitForStateTransitionResultHandler(call).then((result) => { - expect(result).to.be.an.instanceOf(WaitForStateTransitionResultResponse); - expect(result.getV0().getProof()).to.be.undefined(); - - const error = result.getV0().getError(); - expect(error).to.be.an.instanceOf(StateTransitionBroadcastError); - - const errorData = error.getData(); - const errorCode = error.getCode(); - const errorMessage = error.getMessage(); - - expect(createGrpcErrorFromDriveResponseMock).to.be.calledOnceWithExactly( - wsMessagesFixture.error.data.value.result.code, - wsMessagesFixture.error.data.value.result.info, - ); - - expect(errorCode).to.equal(wsMessagesFixture.error.data.value.result.code); - expect(errorData).to.deep.equal(cbor.encode(errorInfo.metadata)); - expect(errorMessage).to.equal(errorInfo.message); - - done(); + try { + expect(result).to.be.an.instanceOf(WaitForStateTransitionResultResponse); + expect(result.getV0().getProof()).to.be.undefined(); + + const error = result.getV0().getError(); + expect(error).to.be.an.instanceOf(StateTransitionBroadcastError); + + const errorData = error.getData(); + const errorCode = error.getCode(); + const errorMessage = error.getMessage(); + + expect(createGrpcErrorFromDriveResponseMock).to.be.calledOnceWithExactly( + wsMessagesFixture.error.data.value.result.code, + wsMessagesFixture.error.data.value.result.info, + ); + + expect(errorCode).to.equal(wsMessagesFixture.error.data.value.result.code); + expect(errorData).to.deep.equal(cbor.encode(errorInfo.metadata)); + expect(errorMessage).to.equal(errorInfo.message); + } finally { + done(); + } }); process.nextTick(() => { From cee3dec13ffd833ecba34a1469c6059c7dc89e03 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 13 Feb 2025 15:48:22 +0700 Subject: [PATCH 4/6] refactor: remove unused import --- .../platform/waitForStateTransitionResultHandlerFactory.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/dapi/lib/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.js b/packages/dapi/lib/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.js index 69652d14eff..104a8f2671a 100644 --- a/packages/dapi/lib/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.js +++ b/packages/dapi/lib/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.js @@ -14,7 +14,6 @@ const { }, } = require('@dashevo/dapi-grpc'); -const cbor = require('cbor'); const UnavailableGrpcError = require('@dashevo/grpc-common/lib/server/error/UnavailableGrpcError'); const TransactionWaitPeriodExceededError = require('../../../errors/TransactionWaitPeriodExceededError'); const TransactionErrorResult = require('../../../externalApis/tenderdash/waitForTransactionToBeProvable/transactionResult/TransactionErrorResult'); From 0669d3c3973e9e8b14301c0b75becdabc1f7510d Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Mon, 17 Feb 2025 14:44:54 +0700 Subject: [PATCH 5/6] fix: consensus error is not handling in JS SDK --- .../platform/waitForStateTransitionResult/ErrorResult.js | 4 ++-- .../WaitForStateTransitionResultResponse.js | 8 +++----- .../src/SDK/Client/Platform/IStateTransitionResult.ts | 2 +- .../src/SDK/Client/Platform/broadcastStateTransition.ts | 8 +++++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js b/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js index 8bc98691f89..55072f849b6 100644 --- a/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js +++ b/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js @@ -2,7 +2,7 @@ class ErrorResult { /** * @param {number} code * @param {string} message - * @param {*} data + * @param {Buffer|undefined} data */ constructor(code, message, data) { this.code = code; @@ -25,7 +25,7 @@ class ErrorResult { } /** - * @returns {*} + * @returns {Buffer|undefined} */ getData() { return this.data; diff --git a/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js b/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js index da357d7d9d7..836987a2376 100644 --- a/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js +++ b/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js @@ -1,5 +1,3 @@ -const cbor = require('cbor'); - const AbstractResponse = require('../response/AbstractResponse'); const Metadata = require('../response/Metadata'); const Proof = require('../response/Proof'); @@ -38,9 +36,9 @@ class WaitForStateTransitionResultResponse extends AbstractResponse { if (proto.getV0().getError()) { let data; - const rawData = proto.getV0().getError().getData(); - if (rawData) { - data = cbor.decode(Buffer.from(rawData)); + + if (proto.getV0().getError().getData()) { + data = Buffer.from(proto.getV0().getError().getData()); } error = new ErrorResult( diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts index 2b679814453..08e562082b4 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts @@ -7,6 +7,6 @@ export interface IStateTransitionResult { error?: { code: number, message: string, - data: any, + data?: Buffer, } } diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts index 3c0bd8664d9..2dce38a116e 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts @@ -89,9 +89,11 @@ export default async function broadcastStateTransition( // instead of passing it as GrpcError constructor argument // Otherwise it will be converted to grpc-js metadata // Which is not compatible with web - grpcError.metadata = { - 'dash-serialized-consensus-error-bin': error.data, - }; + if (error.data) { + grpcError.metadata = { + 'dash-serialized-consensus-error-bin': error.data.toString('base64'), + }; + } let cause = await createGrpcTransportError(grpcError); From a573c606c935379cb8378913ea5520929cdee821 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Mon, 17 Feb 2025 16:37:50 +0700 Subject: [PATCH 6/6] test: fix dapi client tests --- .../waitForStateTransitionResultFactory.spec.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/js-dapi-client/test/unit/methods/platform/waitForStateTransitionResult/waitForStateTransitionResultFactory.spec.js b/packages/js-dapi-client/test/unit/methods/platform/waitForStateTransitionResult/waitForStateTransitionResultFactory.spec.js index 3f58dfbd7bf..b0a8e5b219f 100644 --- a/packages/js-dapi-client/test/unit/methods/platform/waitForStateTransitionResult/waitForStateTransitionResultFactory.spec.js +++ b/packages/js-dapi-client/test/unit/methods/platform/waitForStateTransitionResult/waitForStateTransitionResultFactory.spec.js @@ -119,10 +119,12 @@ describe('waitForStateTransitionResultFactory', () => { }); it('should return response with error', async () => { + const data = cbor.encode({ data: 'error data' }); + const error = new StateTransitionBroadcastError(); error.setCode(2); error.setMessage('Some error'); - error.setData(cbor.encode({ data: 'error data' })); + error.setData(data); response.getV0().setError(error); @@ -135,7 +137,7 @@ describe('waitForStateTransitionResultFactory', () => { expect(result.getError()).to.be.deep.equal({ code: 2, message: 'Some error', - data: { data: 'error data' }, + data: Buffer.from(data), }); const { WaitForStateTransitionResultRequestV0 } = WaitForStateTransitionResultRequest;