Skip to content

Commit 91a9766

Browse files
authored
feat(sdk): return state transition execution error (#2454)
1 parent cb915a7 commit 91a9766

File tree

9 files changed

+114
-35
lines changed

9 files changed

+114
-35
lines changed

packages/dapi/lib/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const {
1414
},
1515
} = require('@dashevo/dapi-grpc');
1616

17-
const cbor = require('cbor');
1817
const UnavailableGrpcError = require('@dashevo/grpc-common/lib/server/error/UnavailableGrpcError');
1918
const TransactionWaitPeriodExceededError = require('../../../errors/TransactionWaitPeriodExceededError');
2019
const TransactionErrorResult = require('../../../externalApis/tenderdash/waitForTransactionToBeProvable/transactionResult/TransactionErrorResult');
@@ -49,9 +48,13 @@ function waitForStateTransitionResultHandlerFactory(
4948

5049
const error = new StateTransitionBroadcastError();
5150

51+
const metadata = grpcError.getRawMetadata();
52+
if (metadata['dash-serialized-consensus-error-bin']) {
53+
error.setData(metadata['dash-serialized-consensus-error-bin']);
54+
}
55+
5256
error.setCode(txDeliverResult.code);
5357
error.setMessage(grpcError.getMessage());
54-
error.setData(cbor.encode(grpcError.getRawMetadata()));
5558

5659
return error;
5760
}

packages/dapi/test/integration/grpcServer/handlers/platform/waitForStateTransitionResultHandlerFactory.spec.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ describe('waitForStateTransitionResultHandlerFactory', () => {
6464
errorInfo = {
6565
message: 'Identity not found',
6666
metadata: {
67-
error: 'some data',
67+
'dash-serialized-consensus-error-bin': Buffer.from('0122a249dac309c9a8b775316c905688da04bf0ee05b3861db05814540c32fba4179', 'hex'),
6868
},
6969
};
7070

@@ -305,26 +305,28 @@ describe('waitForStateTransitionResultHandlerFactory', () => {
305305

306306
it('should wait for state transition and return result with error', (done) => {
307307
waitForStateTransitionResultHandler(call).then((result) => {
308-
expect(result).to.be.an.instanceOf(WaitForStateTransitionResultResponse);
309-
expect(result.getV0().getProof()).to.be.undefined();
310-
311-
const error = result.getV0().getError();
312-
expect(error).to.be.an.instanceOf(StateTransitionBroadcastError);
313-
314-
const errorData = error.getData();
315-
const errorCode = error.getCode();
316-
const errorMessage = error.getMessage();
317-
318-
expect(createGrpcErrorFromDriveResponseMock).to.be.calledOnceWithExactly(
319-
wsMessagesFixture.error.data.value.result.code,
320-
wsMessagesFixture.error.data.value.result.info,
321-
);
322-
323-
expect(errorCode).to.equal(wsMessagesFixture.error.data.value.result.code);
324-
expect(errorData).to.deep.equal(cbor.encode(errorInfo.metadata));
325-
expect(errorMessage).to.equal(errorInfo.message);
326-
327-
done();
308+
try {
309+
expect(result).to.be.an.instanceOf(WaitForStateTransitionResultResponse);
310+
expect(result.getV0().getProof()).to.be.undefined();
311+
312+
const error = result.getV0().getError();
313+
expect(error).to.be.an.instanceOf(StateTransitionBroadcastError);
314+
315+
const errorData = error.getData();
316+
const errorCode = error.getCode();
317+
const errorMessage = error.getMessage();
318+
319+
expect(createGrpcErrorFromDriveResponseMock).to.be.calledOnceWithExactly(
320+
wsMessagesFixture.error.data.value.result.code,
321+
wsMessagesFixture.error.data.value.result.info,
322+
);
323+
324+
expect(errorCode).to.equal(wsMessagesFixture.error.data.value.result.code);
325+
expect(errorData).to.deep.equal(cbor.encode(errorInfo.metadata));
326+
expect(errorMessage).to.equal(errorInfo.message);
327+
} finally {
328+
done();
329+
}
328330
});
329331

330332
process.nextTick(() => {

packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ class ErrorResult {
22
/**
33
* @param {number} code
44
* @param {string} message
5-
* @param {*} data
5+
* @param {Buffer|undefined} data
66
*/
77
constructor(code, message, data) {
88
this.code = code;
@@ -25,7 +25,7 @@ class ErrorResult {
2525
}
2626

2727
/**
28-
* @returns {*}
28+
* @returns {Buffer|undefined}
2929
*/
3030
getData() {
3131
return this.data;

packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
const cbor = require('cbor');
2-
31
const AbstractResponse = require('../response/AbstractResponse');
42
const Metadata = require('../response/Metadata');
53
const Proof = require('../response/Proof');
@@ -38,9 +36,9 @@ class WaitForStateTransitionResultResponse extends AbstractResponse {
3836

3937
if (proto.getV0().getError()) {
4038
let data;
41-
const rawData = proto.getV0().getError().getData();
42-
if (rawData) {
43-
data = cbor.decode(Buffer.from(rawData));
39+
40+
if (proto.getV0().getError().getData()) {
41+
data = Buffer.from(proto.getV0().getError().getData());
4442
}
4543

4644
error = new ErrorResult(

packages/js-dapi-client/test/unit/methods/platform/waitForStateTransitionResult/waitForStateTransitionResultFactory.spec.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,12 @@ describe('waitForStateTransitionResultFactory', () => {
119119
});
120120

121121
it('should return response with error', async () => {
122+
const data = cbor.encode({ data: 'error data' });
123+
122124
const error = new StateTransitionBroadcastError();
123125
error.setCode(2);
124126
error.setMessage('Some error');
125-
error.setData(cbor.encode({ data: 'error data' }));
127+
error.setData(data);
126128

127129
response.getV0().setError(error);
128130

@@ -135,7 +137,7 @@ describe('waitForStateTransitionResultFactory', () => {
135137
expect(result.getError()).to.be.deep.equal({
136138
code: 2,
137139
message: 'Some error',
138-
data: { data: 'error data' },
140+
data: Buffer.from(data),
139141
});
140142

141143
const { WaitForStateTransitionResultRequestV0 } = WaitForStateTransitionResultRequest;

packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ export interface IStateTransitionResult {
77
error?: {
88
code: number,
99
message: string,
10-
data: any,
10+
data?: Buffer,
1111
}
1212
}

packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,11 @@ export default async function broadcastStateTransition(
8989
// instead of passing it as GrpcError constructor argument
9090
// Otherwise it will be converted to grpc-js metadata
9191
// Which is not compatible with web
92-
grpcError.metadata = error.data;
92+
if (error.data) {
93+
grpcError.metadata = {
94+
'dash-serialized-consensus-error-bin': error.data.toString('base64'),
95+
};
96+
}
9397

9498
let cause = await createGrpcTransportError(grpcError);
9599

packages/rs-sdk/src/error.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! Definitions of errors
2+
use dapi_grpc::platform::v0::StateTransitionBroadcastError as StateTransitionBroadcastErrorProto;
23
use dapi_grpc::tonic::Code;
34
use dpp::consensus::ConsensusError;
45
use dpp::serialization::PlatformDeserializable;
@@ -77,6 +78,47 @@ pub enum Error {
7778
/// Remote node is stale; try another server
7879
#[error(transparent)]
7980
StaleNode(#[from] StaleNodeError),
81+
82+
/// Error returned when trying to broadcast a state transition
83+
#[error(transparent)]
84+
StateTransitionBroadcastError(#[from] StateTransitionBroadcastError),
85+
}
86+
87+
/// State transition broadcast error
88+
#[derive(Debug, thiserror::Error)]
89+
#[error("state transition broadcast error: {message}")]
90+
pub struct StateTransitionBroadcastError {
91+
/// Error code
92+
pub code: u32,
93+
/// Error message
94+
pub message: String,
95+
/// Consensus error caused the state transition broadcast error
96+
pub cause: Option<ConsensusError>,
97+
}
98+
99+
impl TryFrom<StateTransitionBroadcastErrorProto> for StateTransitionBroadcastError {
100+
type Error = Error;
101+
102+
fn try_from(value: StateTransitionBroadcastErrorProto) -> Result<Self, Self::Error> {
103+
let cause = if value.data.len() > 0 {
104+
let consensus_error =
105+
ConsensusError::deserialize_from_bytes(&value.data).map_err(|e| {
106+
tracing::debug!("Failed to deserialize consensus error: {}", e);
107+
108+
Error::Protocol(e)
109+
})?;
110+
111+
Some(consensus_error)
112+
} else {
113+
None
114+
};
115+
116+
Ok(Self {
117+
code: value.code,
118+
message: value.message,
119+
cause,
120+
})
121+
}
80122
}
81123

82124
// TODO: Decompose DapiClientError to more specific errors like connection, node error instead of DAPI client error

packages/rs-sdk/src/platform/transition/broadcast.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
use super::broadcast_request::BroadcastRequestForStateTransition;
22
use super::put_settings::PutSettings;
3+
use crate::error::StateTransitionBroadcastError;
34
use crate::platform::block_info_from_metadata::block_info_from_metadata;
45
use crate::sync::retry;
56
use crate::{Error, Sdk};
6-
use dapi_grpc::platform::v0::{Proof, WaitForStateTransitionResultResponse};
7+
use dapi_grpc::platform::v0::wait_for_state_transition_result_response::wait_for_state_transition_result_response_v0;
8+
use dapi_grpc::platform::v0::{
9+
wait_for_state_transition_result_response, Proof, WaitForStateTransitionResultResponse,
10+
};
711
use dapi_grpc::platform::VersionedGrpcResponse;
812
use dpp::state_transition::proof_result::StateTransitionProofResult;
913
use dpp::state_transition::StateTransition;
@@ -80,6 +84,30 @@ impl BroadcastStateTransition for StateTransition {
8084
let response = request.execute(sdk, request_settings).await.inner_into()?;
8185

8286
let grpc_response: &WaitForStateTransitionResultResponse = &response.inner;
87+
88+
// We use match here to have a compilation error if a new version of the response is introduced
89+
let state_transition_broadcast_error = match &grpc_response.version {
90+
Some(wait_for_state_transition_result_response::Version::V0(result)) => {
91+
match &result.result {
92+
Some(wait_for_state_transition_result_response_v0::Result::Error(e)) => {
93+
Some(e)
94+
}
95+
_ => None,
96+
}
97+
}
98+
None => None,
99+
};
100+
101+
if let Some(e) = state_transition_broadcast_error {
102+
let state_transition_broadcast_error: StateTransitionBroadcastError =
103+
StateTransitionBroadcastError::try_from(e.clone())
104+
.wrap_to_execution_result(&response)?
105+
.inner;
106+
107+
return Err(Error::from(state_transition_broadcast_error))
108+
.wrap_to_execution_result(&response);
109+
}
110+
83111
let metadata = grpc_response
84112
.metadata()
85113
.wrap_to_execution_result(&response)?

0 commit comments

Comments
 (0)