From 151eae7abd2caa788852afe9dfca2efc98a144b4 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Tue, 4 Apr 2023 22:02:22 +0800 Subject: [PATCH 01/21] fix: identifier deserialization doesn't work for bincode (#885) --- .../src/errors/consensus/consensus_error.rs | 6 ++--- .../decode/protocol_version_parsing_error.rs | 7 ++++++ .../fee/balance_is_not_enough_error.rs | 7 ++++++ .../signature/identity_not_found_error.rs | 14 ++++++++--- .../data_contract_already_present_error.rs | 13 ++++++++-- .../deserializeConsensusError.spec.js | 25 ++++++++----------- 6 files changed, 50 insertions(+), 22 deletions(-) diff --git a/packages/rs-dpp/src/errors/consensus/consensus_error.rs b/packages/rs-dpp/src/errors/consensus/consensus_error.rs index 784e295bab2..685cfc52ed3 100644 --- a/packages/rs-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/rs-dpp/src/errors/consensus/consensus_error.rs @@ -56,8 +56,8 @@ impl ConsensusError { .with_variable_int_encoding() .with_big_endian(); - bincode::serde::encode_to_vec(self, config).map_err(|_| { - ProtocolError::EncodingError(String::from("unable to serialize identity public key")) + bincode::serde::encode_to_vec(self, config).map_err(|e| { + ProtocolError::EncodingError(format!("unable to serialize consensus error: {e}")) }) } @@ -68,7 +68,7 @@ impl ConsensusError { .with_big_endian(); bincode::serde::decode_borrowed_from_slice(bytes, config).map_err(|e| { - ProtocolError::EncodingError(format!("unable to deserialize consensus error {}", e)) + ProtocolError::EncodingError(format!("unable to deserialize consensus error: {e}")) }) } } diff --git a/packages/wasm-dpp/src/errors/consensus/basic/decode/protocol_version_parsing_error.rs b/packages/wasm-dpp/src/errors/consensus/basic/decode/protocol_version_parsing_error.rs index f84b5c32e0a..cbca00edb48 100644 --- a/packages/wasm-dpp/src/errors/consensus/basic/decode/protocol_version_parsing_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/basic/decode/protocol_version_parsing_error.rs @@ -17,6 +17,13 @@ impl From<&ProtocolVersionParsingError> for ProtocolVersionParsingErrorWasm { #[wasm_bindgen(js_class=ProtocolVersionParsingError)] impl ProtocolVersionParsingErrorWasm { + #[wasm_bindgen(constructor)] + pub fn new(parsing_error: String) -> Self { + Self { + inner: ProtocolVersionParsingError::new(parsing_error), + } + } + #[wasm_bindgen(js_name = getParsingError)] pub fn get_parsing_error(&self) -> String { self.inner.parsing_error().to_string() diff --git a/packages/wasm-dpp/src/errors/consensus/fee/balance_is_not_enough_error.rs b/packages/wasm-dpp/src/errors/consensus/fee/balance_is_not_enough_error.rs index bfe21ea751f..ad236f8055b 100644 --- a/packages/wasm-dpp/src/errors/consensus/fee/balance_is_not_enough_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/fee/balance_is_not_enough_error.rs @@ -18,6 +18,13 @@ impl From<&BalanceIsNotEnoughError> for BalanceIsNotEnoughErrorWasm { #[wasm_bindgen(js_class=BalanceIsNotEnoughError)] impl BalanceIsNotEnoughErrorWasm { + #[wasm_bindgen(constructor)] + pub fn new(balance: Credits, fee: Credits) -> Self { + Self { + inner: BalanceIsNotEnoughError::new(balance, fee), + } + } + #[wasm_bindgen(js_name=getBalance)] pub fn get_balance(&self) -> Credits { self.inner.balance() diff --git a/packages/wasm-dpp/src/errors/consensus/signature/identity_not_found_error.rs b/packages/wasm-dpp/src/errors/consensus/signature/identity_not_found_error.rs index e7d7f35642e..02a48220633 100644 --- a/packages/wasm-dpp/src/errors/consensus/signature/identity_not_found_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/signature/identity_not_found_error.rs @@ -4,6 +4,7 @@ use dpp::consensus::ConsensusError; use wasm_bindgen::prelude::*; use crate::buffer::Buffer; +use crate::identifier::IdentifierWrapper; #[wasm_bindgen(js_name=IdentityNotFoundError)] pub struct IdentityNotFoundErrorWasm { @@ -18,9 +19,16 @@ impl From<&IdentityNotFoundError> for IdentityNotFoundErrorWasm { #[wasm_bindgen(js_class=IdentityNotFoundError)] impl IdentityNotFoundErrorWasm { + #[wasm_bindgen(constructor)] + pub fn new(identity_id: IdentifierWrapper) -> Self { + Self { + inner: IdentityNotFoundError::new(identity_id.into()), + } + } + #[wasm_bindgen(js_name=getIdentityId)] - pub fn get_identity_id(&self) -> Buffer { - Buffer::from_bytes(self.inner.identity_id().as_bytes()) + pub fn get_identity_id(&self) -> IdentifierWrapper { + self.inner.identity_id().into() } #[wasm_bindgen(js_name=getCode)] @@ -37,7 +45,7 @@ impl IdentityNotFoundErrorWasm { pub fn serialize(&self) -> Result { let bytes = ConsensusError::from(self.inner.clone()) .serialize() - .map_err(|e| JsError::from(e))?; + .map_err(JsError::from)?; Ok(Buffer::from_bytes(bytes.as_slice())) } diff --git a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_contract_already_present_error.rs b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_contract_already_present_error.rs index e0572742db1..ef4b7fa0dec 100644 --- a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_contract_already_present_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_contract_already_present_error.rs @@ -1,7 +1,9 @@ use crate::buffer::Buffer; +use crate::identifier::IdentifierWrapper; use dpp::consensus::codes::ErrorWithCode; use dpp::consensus::state::data_contract::data_contract_already_present_error::DataContractAlreadyPresentError; use dpp::consensus::ConsensusError; +use dpp::identifier::Identifier; use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name=DataContractAlreadyPresentError)] @@ -17,9 +19,16 @@ impl From<&DataContractAlreadyPresentError> for DataContractAlreadyPresentErrorW #[wasm_bindgen(js_class=DataContractAlreadyPresentError)] impl DataContractAlreadyPresentErrorWasm { + #[wasm_bindgen(constructor)] + pub fn new(data_contract_id: IdentifierWrapper) -> Self { + Self { + inner: DataContractAlreadyPresentError::new(data_contract_id.into()), + } + } + #[wasm_bindgen(js_name=getDataContractId)] - pub fn data_contract_id(&self) -> Buffer { - Buffer::from_bytes(self.inner.data_contract_id().as_bytes()) + pub fn data_contract_id(&self) -> IdentifierWrapper { + self.inner.data_contract_id().to_owned().into() } #[wasm_bindgen(js_name=getCode)] diff --git a/packages/wasm-dpp/test/integration/error/consensus/deserializeConsensusError.spec.js b/packages/wasm-dpp/test/integration/error/consensus/deserializeConsensusError.spec.js index 8e58f571ecc..338b0af1206 100644 --- a/packages/wasm-dpp/test/integration/error/consensus/deserializeConsensusError.spec.js +++ b/packages/wasm-dpp/test/integration/error/consensus/deserializeConsensusError.spec.js @@ -2,7 +2,6 @@ const { default: loadWasmDpp } = require('../../../..'); let { deserializeConsensusError, - decodeProtocolEntity, ProtocolVersionParsingError, } = require('../../../..'); @@ -10,25 +9,23 @@ describe('deserializeConsensusError', () => { before(async () => { ({ deserializeConsensusError, - decodeProtocolEntity, ProtocolVersionParsingError, } = await loadWasmDpp()); }); it('should deserialize consensus error', async () => { - try { - await decodeProtocolEntity(Buffer.alloc(0)); - } catch (consensusError) { - expect(consensusError).to.be.instanceOf(ProtocolVersionParsingError); - expect(consensusError.getCode()).to.equals(1000); - expect(consensusError.message).to.equals('Can\'t read protocol version from serialized object: protocol version could not be decoded as a varint'); + const consensusError = new ProtocolVersionParsingError('test'); + const message = 'Can\'t read protocol version from serialized object: test'; - const bytes = consensusError.serialize(); + expect(consensusError).to.be.instanceOf(ProtocolVersionParsingError); + expect(consensusError.getCode()).to.equals(1000); + expect(consensusError.message).to.equals(message); - const recoveredError = deserializeConsensusError(bytes); - expect(recoveredError).to.be.instanceOf(ProtocolVersionParsingError); - expect(recoveredError.getCode()).to.equals(1000); - expect(recoveredError.message).to.equals('Can\'t read protocol version from serialized object: protocol version could not be decoded as a varint'); - } + const serializedConsensusError = consensusError.serialize(); + + const recoveredError = deserializeConsensusError(serializedConsensusError); + expect(recoveredError).to.be.instanceOf(ProtocolVersionParsingError); + expect(recoveredError.getCode()).to.equals(1000); + expect(recoveredError.message).to.equals(message); }); }); From 6ffa273d26cfff8fc81df6931e0598c59b5dc1b1 Mon Sep 17 00:00:00 2001 From: qrayven Date: Tue, 4 Apr 2023 16:12:52 +0200 Subject: [PATCH 02/21] fix: possible overflow issues (#877) Co-authored-by: Ivan Shumkov --- .../validate_time_in_block_time_window.rs | 25 ++++++--- ...e_data_contract_update_transition_basic.rs | 11 +++- ...lidate_documents_batch_transition_state.rs | 25 +++++---- .../rs-dpp/src/errors/non_consensus_error.rs | 4 ++ .../rs-dpp/src/identity/credits_converter.rs | 20 ++++--- .../apply_identity_create_transition.rs | 2 +- .../apply_identity_topup_transition.rs | 2 +- ...lidate_identity_update_transition_state.rs | 8 ++- .../fee/calculate_operation_fees.rs | 29 +++++++---- .../calculate_state_transition_fee_factory.rs | 11 ++-- ..._transition_fee_from_operations_factory.rs | 24 +++++---- .../state_transition/fee/operations/mod.rs | 10 ++-- .../fee/operations/precalculated_operation.rs | 13 +++-- .../fee/operations/read_operation.rs | 28 +++++++--- .../signature_verification_operation.rs | 9 ++-- .../validate_state_transition_fee.rs | 52 ++++++++++++------- .../src/identity_credit_withdrawal/mod.rs | 2 +- ...lidate_documents_batch_transition_state.rs | 25 +++++---- .../state_transition/identity_update.rs | 5 +- .../fee/calculate_operation_fees.rs | 14 +++-- .../fee/calculate_state_transition_fee.rs | 9 +++- .../operations/pre_calculated_operation.rs | 27 +++++++--- .../fee/operations/read_operation.rs | 23 ++++++-- .../signature_verification_operation.rs | 19 +++++-- 24 files changed, 268 insertions(+), 129 deletions(-) diff --git a/packages/rs-dpp/src/block_time_window/validate_time_in_block_time_window.rs b/packages/rs-dpp/src/block_time_window/validate_time_in_block_time_window.rs index e49b79288c5..f3270f7e93f 100644 --- a/packages/rs-dpp/src/block_time_window/validate_time_in_block_time_window.rs +++ b/packages/rs-dpp/src/block_time_window/validate_time_in_block_time_window.rs @@ -1,4 +1,4 @@ -use crate::prelude::TimestampMillis; +use crate::{prelude::TimestampMillis, NonConsensusError}; use super::validation_result::TimeWindowValidationResult; @@ -9,17 +9,28 @@ pub fn validate_time_in_block_time_window( last_block_header_time_millis: TimestampMillis, time_to_check_millis: TimestampMillis, average_block_spacing_ms: u64, //in the event of very long blocks we need to add this -) -> TimeWindowValidationResult { - let time_window_start = last_block_header_time_millis - BLOCK_TIME_WINDOW_MILLIS; - let time_window_end = - last_block_header_time_millis + BLOCK_TIME_WINDOW_MILLIS + average_block_spacing_ms; +) -> Result { + let time_window_start = last_block_header_time_millis + .checked_sub(BLOCK_TIME_WINDOW_MILLIS) + .ok_or(NonConsensusError::Overflow( + "calculation of start window failed", + ))?; + let time_window_end = last_block_header_time_millis + .checked_add(BLOCK_TIME_WINDOW_MILLIS) + .ok_or(NonConsensusError::Overflow( + "calculation of end window failed: block time window overflow", + ))? + .checked_add(average_block_spacing_ms) + .ok_or(NonConsensusError::Overflow( + "calculation of end window failed: average block spacing overflow", + ))?; let valid = time_to_check_millis >= time_window_start && time_to_check_millis <= time_window_end; - TimeWindowValidationResult { + Ok(TimeWindowValidationResult { time_window_start, time_window_end, valid, - } + }) } diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_data_contract_update_transition_basic.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_data_contract_update_transition_basic.rs index dc3cbbaaf42..f34b9e12d25 100644 --- a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_data_contract_update_transition_basic.rs +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/validation/basic/validate_data_contract_update_transition_basic.rs @@ -144,9 +144,16 @@ where } }; - let new_version = new_data_contract_object.get_integer(contract_property_names::VERSION)?; + let new_version: u32 = + new_data_contract_object.get_integer(contract_property_names::VERSION)?; let old_version = existing_data_contract.version; - if new_version < old_version || new_version - old_version != 1 { + + let version_diff = new_version + .checked_sub(old_version) + .ok_or(ProtocolError::Overflow( + "comparing protocol versions failed", + ))?; + if new_version < old_version || version_diff != 1 { validation_result.add_error(BasicError::InvalidDataContractVersionError( InvalidDataContractVersionError::new(old_version + 1, new_version), )) diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs index a9990a86401..63e3384d528 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs @@ -21,6 +21,7 @@ use crate::document::state_transition::documents_batch_transition::{ }; use crate::document::Document; use crate::validation::{AsyncDataValidator, SimpleConsensusValidationResult}; +use crate::NonConsensusError; use crate::{ block_time_window::validate_time_in_block_time_window::validate_time_in_block_time_window, consensus::ConsensusError, @@ -260,11 +261,11 @@ fn validate_transition( result.merge(validation_result); let validation_result = - check_created_inside_time_window(transition, last_header_block_time_millis); + check_created_inside_time_window(transition, last_header_block_time_millis)?; result.merge(validation_result); let validation_result = - check_updated_inside_time_window(transition, last_header_block_time_millis); + check_updated_inside_time_window(transition, last_header_block_time_millis)?; result.merge(validation_result); let validation_result = @@ -285,7 +286,7 @@ fn validate_transition( DocumentTransitionAction::ReplaceAction(DocumentReplaceTransitionAction::default()), ); let validation_result = - check_updated_inside_time_window(transition, last_header_block_time_millis); + check_updated_inside_time_window(transition, last_header_block_time_millis)?; result.merge(validation_result); let validation_result = check_revision(transition, fetched_documents); @@ -459,15 +460,16 @@ pub fn check_if_timestamps_are_equal( pub fn check_created_inside_time_window( document_transition: &DocumentTransition, last_block_ts_millis: TimestampMillis, -) -> SimpleConsensusValidationResult { +) -> Result { let mut result = SimpleConsensusValidationResult::default(); let created_at = match document_transition.get_created_at() { Some(t) => t, - None => return result, + None => return Ok(result), }; //todo: deal with block spacing - let window_validation = validate_time_in_block_time_window(last_block_ts_millis, created_at, 0); + let window_validation = + validate_time_in_block_time_window(last_block_ts_millis, created_at, 0)?; if !window_validation.is_valid() { result.add_error(ConsensusError::StateError( StateError::DocumentTimestampWindowViolationError( @@ -481,21 +483,22 @@ pub fn check_created_inside_time_window( ), )); } - result + Ok(result) } pub fn check_updated_inside_time_window( document_transition: &DocumentTransition, last_block_ts_millis: TimestampMillis, -) -> SimpleConsensusValidationResult { +) -> Result { let mut result = SimpleConsensusValidationResult::default(); let updated_at = match document_transition.get_updated_at() { Some(t) => t, - None => return result, + None => return Ok(result), }; //todo: deal with block spacing - let window_validation = validate_time_in_block_time_window(last_block_ts_millis, updated_at, 0); + let window_validation = + validate_time_in_block_time_window(last_block_ts_millis, updated_at, 0)?; if !window_validation.is_valid() { result.add_error(ConsensusError::StateError( StateError::DocumentTimestampWindowViolationError( @@ -509,5 +512,5 @@ pub fn check_updated_inside_time_window( ), )); } - result + Ok(result) } diff --git a/packages/rs-dpp/src/errors/non_consensus_error.rs b/packages/rs-dpp/src/errors/non_consensus_error.rs index c1186be059c..db620169b49 100644 --- a/packages/rs-dpp/src/errors/non_consensus_error.rs +++ b/packages/rs-dpp/src/errors/non_consensus_error.rs @@ -48,6 +48,10 @@ pub enum NonConsensusError { #[error(transparent)] Error(#[from] anyhow::Error), + + /// Error + #[error("overflow error: {0}")] + Overflow(&'static str), } pub mod object_names { diff --git a/packages/rs-dpp/src/identity/credits_converter.rs b/packages/rs-dpp/src/identity/credits_converter.rs index beb411ed613..98ff23a815e 100644 --- a/packages/rs-dpp/src/identity/credits_converter.rs +++ b/packages/rs-dpp/src/identity/credits_converter.rs @@ -1,11 +1,17 @@ +use crate::{state_transition::fee::Credits, ProtocolError}; + pub const RATIO: u64 = 1000; -pub fn convert_satoshi_to_credits(amount: u64) -> u64 { - amount * RATIO +pub fn convert_satoshi_to_credits(amount: u64) -> Result { + amount.checked_mul(RATIO).ok_or(ProtocolError::Overflow( + "converting satoshi to credits failed", + )) } -pub fn convert_credits_to_satoshi(amount: u64) -> u64 { - amount / RATIO +pub fn convert_credits_to_satoshi(amount: Credits) -> Result { + amount.checked_div(RATIO).ok_or(ProtocolError::Overflow( + "converting credits to satoshi failed", + )) } #[cfg(test)] @@ -15,7 +21,7 @@ mod test { #[test] fn test_should_convert_satoshi_to_credits() { let amount = 42; - let converted = convert_satoshi_to_credits(amount); + let converted = convert_satoshi_to_credits(amount).unwrap(); assert_eq!(converted, amount * RATIO); } @@ -23,14 +29,14 @@ mod test { #[test] fn test_should_convert_credits_to_satoshi() { let amount = 10000; - let converted = convert_credits_to_satoshi(amount); + let converted = convert_credits_to_satoshi(amount).unwrap(); assert_eq!(converted, amount / RATIO); } #[test] fn test_convert_to_0_satoshi_if_amount_lower_than_ratio() { let amount = RATIO - 1; - let converted = convert_credits_to_satoshi(amount); + let converted = convert_credits_to_satoshi(amount).unwrap(); assert_eq!(converted, 0); } } diff --git a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/apply_identity_create_transition.rs b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/apply_identity_create_transition.rs index c4dbde51b3a..d787bb0cf51 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_create_transition/apply_identity_create_transition.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_create_transition/apply_identity_create_transition.rs @@ -43,7 +43,7 @@ where .fetch(state_transition.get_asset_lock_proof(), execution_context) .await?; - let credits_amount = convert_satoshi_to_credits(output.value); + let credits_amount = convert_satoshi_to_credits(output.value)?; let identity = Identity { protocol_version: state_transition.get_protocol_version(), diff --git a/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/apply_identity_topup_transition.rs b/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/apply_identity_topup_transition.rs index 23877a71c95..7d9d830ff58 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/apply_identity_topup_transition.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_topup_transition/apply_identity_topup_transition.rs @@ -41,7 +41,7 @@ where .fetch(state_transition.get_asset_lock_proof(), execution_context) .await?; - let mut credits_amount = convert_satoshi_to_credits(output.value); + let mut credits_amount = convert_satoshi_to_credits(output.value)?; let out_point = state_transition .get_asset_lock_proof() diff --git a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_identity_update_transition_state.rs b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_identity_update_transition_state.rs index 943ebd76d81..c6bc2408cfe 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_identity_update_transition_state.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/validate_identity_update_transition_state.rs @@ -80,7 +80,11 @@ where let mut identity = stored_identity.clone(); // Check revision - if identity.get_revision() != (state_transition.get_revision() - 1) { + if identity.get_revision() + != (state_transition.get_revision().checked_sub(1).ok_or( + NonConsensusError::Overflow("unable subtract 1 from revision"), + )?) + { validation_result.add_error(StateError::InvalidIdentityRevisionError( InvalidIdentityRevisionError::new( state_transition.get_identity_id().to_owned(), @@ -134,7 +138,7 @@ where )?; //todo: add block spacing ms let window_validation_result = - validate_time_in_block_time_window(last_block_header_time, disabled_at_ms, 0); + validate_time_in_block_time_window(last_block_header_time, disabled_at_ms, 0)?; if !window_validation_result.is_valid() { validation_result.add_error( diff --git a/packages/rs-dpp/src/state_transition/fee/calculate_operation_fees.rs b/packages/rs-dpp/src/state_transition/fee/calculate_operation_fees.rs index d6fe927df83..3656777fa8e 100644 --- a/packages/rs-dpp/src/state_transition/fee/calculate_operation_fees.rs +++ b/packages/rs-dpp/src/state_transition/fee/calculate_operation_fees.rs @@ -1,16 +1,24 @@ +use crate::NonConsensusError; + use super::{ operations::{Operation, OperationLike}, - DummyFeesResult, Refunds, + Credits, DummyFeesResult, Refunds, }; -pub fn calculate_operation_fees(operations: &[Operation]) -> DummyFeesResult { - let mut storage_fee = 0; - let mut processing_fee = 0; +pub fn calculate_operation_fees( + operations: &[Operation], +) -> Result { + let mut storage_fee: Credits = 0; + let mut processing_fee: Credits = 0; let mut fee_refunds: Vec = Vec::new(); for operation in operations { - storage_fee += operation.get_storage_cost(); - processing_fee += operation.get_processing_cost(); + storage_fee = storage_fee + .checked_add(operation.get_storage_cost()?) + .ok_or(NonConsensusError::Overflow("storage cost is too big"))?; + processing_fee = processing_fee + .checked_add(operation.get_processing_cost()?) + .ok_or(NonConsensusError::Overflow("processing cost is too big"))?; // Merge refunds if let Some(operation_refunds) = operation.get_refunds() { @@ -30,16 +38,19 @@ pub fn calculate_operation_fees(operations: &[Operation]) -> DummyFeesResult { .credits_per_epoch .entry(epoch_index.to_string()) .or_default(); - *epoch += credits + + *epoch = epoch + .checked_add(*credits) + .ok_or(NonConsensusError::Overflow("credits per epoch are too big"))? } } } } } - DummyFeesResult { + Ok(DummyFeesResult { storage: storage_fee, processing: processing_fee, fee_refunds, - } + }) } diff --git a/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_factory.rs b/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_factory.rs index 3e0945cede7..3f7edfd93f2 100644 --- a/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_factory.rs +++ b/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_factory.rs @@ -1,7 +1,10 @@ use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; -use crate::state_transition::{ - fee::calculate_state_transition_fee_from_operations_factory::calculate_state_transition_fee_from_operations, - StateTransition, +use crate::{ + state_transition::{ + fee::calculate_state_transition_fee_from_operations_factory::calculate_state_transition_fee_from_operations, + StateTransition, StateTransitionLike, + }, + NonConsensusError, }; use super::FeeResult; @@ -9,7 +12,7 @@ use super::FeeResult; pub fn calculate_state_transition_fee( state_transition: &StateTransition, execution_context: &StateTransitionExecutionContext, -) -> FeeResult { +) -> Result { calculate_state_transition_fee_from_operations( &execution_context.get_operations(), state_transition.get_owner_id(), diff --git a/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_from_operations_factory.rs b/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_from_operations_factory.rs index a5a7adf92f6..89eee9ed9b1 100644 --- a/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_from_operations_factory.rs +++ b/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_from_operations_factory.rs @@ -1,4 +1,4 @@ -use crate::prelude::Identifier; +use crate::{prelude::Identifier, NonConsensusError}; use super::{ calculate_operation_fees::calculate_operation_fees, constants::DEFAULT_USER_TIP, @@ -8,7 +8,7 @@ use super::{ pub fn calculate_state_transition_fee_from_operations( operations: &[Operation], identity_id: &Identifier, -) -> FeeResult { +) -> Result { calculate_state_transition_fee_from_operations_with_custom_calculator( operations, identity_id, @@ -19,9 +19,9 @@ pub fn calculate_state_transition_fee_from_operations( fn calculate_state_transition_fee_from_operations_with_custom_calculator( operations: &[Operation], identity_id: &Identifier, - calculate_operation_fees_fn: impl FnOnce(&[Operation]) -> DummyFeesResult, -) -> FeeResult { - let calculated_fees = calculate_operation_fees_fn(operations); + calculate_operation_fees_fn: impl FnOnce(&[Operation]) -> Result, +) -> Result { + let calculated_fees = calculate_operation_fees_fn(operations)?; let storage_fee = calculated_fees.storage; let processing_fee = calculated_fees.processing; @@ -43,14 +43,14 @@ fn calculate_state_transition_fee_from_operations_with_custom_calculator( let required_amount = (storage_fee - total_refunds) + DEFAULT_USER_TIP; let desired_amount = (storage_fee + processing_fee - total_refunds) + DEFAULT_USER_TIP; - FeeResult { + Ok(FeeResult { storage_fee, processing_fee, fee_refunds, total_refunds, required_amount, desired_amount, - } + }) } #[cfg(test)] @@ -62,6 +62,7 @@ mod test { operations::Operation, Credits, DummyFeesResult, FeeResult, Refunds, }, tests::utils::generate_random_identifier_struct, + NonConsensusError, }; use super::calculate_state_transition_fee_from_operations_with_custom_calculator; @@ -84,19 +85,20 @@ mod test { credits_per_epoch, }; - let mock = |_operations: &[Operation]| -> DummyFeesResult { - DummyFeesResult { + let mock = |_operations: &[Operation]| -> Result { + Ok(DummyFeesResult { storage: storage_fee, processing: processing_fee, fee_refunds: vec![refunds.clone()], - } + }) }; let result = calculate_state_transition_fee_from_operations_with_custom_calculator( &[], &identifier, mock, - ); + ) + .expect("result should be returned"); let expected = FeeResult { storage_fee, processing_fee, diff --git a/packages/rs-dpp/src/state_transition/fee/operations/mod.rs b/packages/rs-dpp/src/state_transition/fee/operations/mod.rs index 59430db494a..63a1b8563b4 100644 --- a/packages/rs-dpp/src/state_transition/fee/operations/mod.rs +++ b/packages/rs-dpp/src/state_transition/fee/operations/mod.rs @@ -10,6 +10,8 @@ use serde_json::Value; mod signature_verification_operation; pub use signature_verification_operation::*; +use crate::NonConsensusError; + use super::{Credits, Refunds}; pub const STORAGE_CREDIT_PER_BYTE: i64 = 5000; @@ -25,9 +27,9 @@ pub enum Operation { pub trait OperationLike { /// Get CPU cost of the operation - fn get_processing_cost(&self) -> Credits; + fn get_processing_cost(&self) -> Result; /// Get storage cost of the operation - fn get_storage_cost(&self) -> Credits; + fn get_storage_cost(&self) -> Result; /// Get refunds fn get_refunds(&self) -> Option<&Vec>; @@ -44,11 +46,11 @@ macro_rules! call_method { } impl OperationLike for Operation { - fn get_processing_cost(&self) -> Credits { + fn get_processing_cost(&self) -> Result { call_method!(self, get_processing_cost) } - fn get_storage_cost(&self) -> Credits { + fn get_storage_cost(&self) -> Result { call_method!(self, get_storage_cost) } diff --git a/packages/rs-dpp/src/state_transition/fee/operations/precalculated_operation.rs b/packages/rs-dpp/src/state_transition/fee/operations/precalculated_operation.rs index b7cdc272502..227cb47475a 100644 --- a/packages/rs-dpp/src/state_transition/fee/operations/precalculated_operation.rs +++ b/packages/rs-dpp/src/state_transition/fee/operations/precalculated_operation.rs @@ -1,6 +1,9 @@ use serde::{Deserialize, Serialize}; -use crate::state_transition::fee::{Credits, DummyFeesResult, Refunds}; +use crate::{ + state_transition::fee::{Credits, DummyFeesResult, Refunds}, + NonConsensusError, +}; use super::OperationLike; @@ -35,12 +38,12 @@ impl PreCalculatedOperation { } impl OperationLike for PreCalculatedOperation { - fn get_processing_cost(&self) -> Credits { - self.processing_cost + fn get_processing_cost(&self) -> Result { + Ok(self.processing_cost) } - fn get_storage_cost(&self) -> Credits { - self.storage_cost + fn get_storage_cost(&self) -> Result { + Ok(self.storage_cost) } fn get_refunds(&self) -> Option<&Vec> { diff --git a/packages/rs-dpp/src/state_transition/fee/operations/read_operation.rs b/packages/rs-dpp/src/state_transition/fee/operations/read_operation.rs index 007be2e941e..e50bd2389ad 100644 --- a/packages/rs-dpp/src/state_transition/fee/operations/read_operation.rs +++ b/packages/rs-dpp/src/state_transition/fee/operations/read_operation.rs @@ -2,9 +2,12 @@ use serde::{Deserialize, Serialize}; use super::OperationLike; -use crate::state_transition::fee::{ - constants::{PROCESSING_CREDIT_PER_BYTE, READ_BASE_PROCESSING_COST}, - Credits, Refunds, +use crate::{ + state_transition::fee::{ + constants::{PROCESSING_CREDIT_PER_BYTE, READ_BASE_PROCESSING_COST}, + Credits, Refunds, + }, + NonConsensusError, }; #[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] @@ -20,12 +23,23 @@ impl ReadOperation { } impl OperationLike for ReadOperation { - fn get_processing_cost(&self) -> Credits { - READ_BASE_PROCESSING_COST + (self.value_size * PROCESSING_CREDIT_PER_BYTE) + fn get_processing_cost(&self) -> Result { + let value_byte_processing_cost = self + .value_size + .checked_mul(PROCESSING_CREDIT_PER_BYTE) + .ok_or(NonConsensusError::Overflow( + "value processing cost is too big", + ))?; + + READ_BASE_PROCESSING_COST + .checked_add(value_byte_processing_cost) + .ok_or(NonConsensusError::Overflow( + "can't add read base processing cost", + )) } - fn get_storage_cost(&self) -> Credits { - 0 + fn get_storage_cost(&self) -> Result { + Ok(0) } fn get_refunds(&self) -> Option<&Vec> { diff --git a/packages/rs-dpp/src/state_transition/fee/operations/signature_verification_operation.rs b/packages/rs-dpp/src/state_transition/fee/operations/signature_verification_operation.rs index 59d43afb249..299d6a2564c 100644 --- a/packages/rs-dpp/src/state_transition/fee/operations/signature_verification_operation.rs +++ b/packages/rs-dpp/src/state_transition/fee/operations/signature_verification_operation.rs @@ -4,6 +4,7 @@ use super::OperationLike; use crate::{ identity::KeyType, state_transition::fee::{constants::signature_verify_cost, Credits, Refunds}, + NonConsensusError, }; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] @@ -19,12 +20,12 @@ impl SignatureVerificationOperation { } impl OperationLike for SignatureVerificationOperation { - fn get_processing_cost(&self) -> Credits { - signature_verify_cost(self.signature_type) + fn get_processing_cost(&self) -> Result { + Ok(signature_verify_cost(self.signature_type)) } - fn get_storage_cost(&self) -> Credits { - 0 + fn get_storage_cost(&self) -> Result { + Ok(0) } fn get_refunds(&self) -> Option<&Vec> { diff --git a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_fee.rs b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_fee.rs index 1081c8b60b7..8081cc88b1f 100644 --- a/packages/rs-dpp/src/state_transition/validation/validate_state_transition_fee.rs +++ b/packages/rs-dpp/src/state_transition/validation/validate_state_transition_fee.rs @@ -9,6 +9,7 @@ use crate::state_transition::fee::calculate_state_transition_fee_factory::calcul use crate::state_transition::fee::{Credits, FeeResult}; use crate::state_transition::state_transition_execution_context::StateTransitionExecutionContext; use crate::state_transition::StateTransitionType; +use crate::NonConsensusError; use crate::{ identity::{ convert_satoshi_to_credits, @@ -58,11 +59,11 @@ where calculate_state_transition_fee_fn: impl Fn( &StateTransition, &StateTransitionExecutionContext, - ) -> FeeResult, + ) -> Result, execution_context: &StateTransitionExecutionContext, ) -> Result { let mut result = SimpleConsensusValidationResult::default(); - let required_fee = calculate_state_transition_fee_fn(state_transition, execution_context); + let required_fee = calculate_state_transition_fee_fn(state_transition, execution_context)?; let balance = match state_transition { StateTransition::IdentityCreate(st) => { @@ -76,7 +77,7 @@ where st.get_asset_lock_proof() ) })?; - convert_satoshi_to_credits(output.value) + convert_satoshi_to_credits(output.value)? } StateTransition::IdentityTopUp(st) => { let output = self @@ -89,7 +90,7 @@ where st.get_asset_lock_proof() ) })?; - let balance = convert_satoshi_to_credits(output.value); + let balance = convert_satoshi_to_credits(output.value)?; let identity_id = st.get_owner_id(); let identity_balance: i64 = self .state_repository @@ -112,9 +113,17 @@ where } if identity_balance.is_negative() { - balance - identity_balance.unsigned_abs() + balance.checked_sub(identity_balance.unsigned_abs()).ok_or( + ProtocolError::Overflow( + "can't subtract identity balance from the state transition balance", + ), + )? } else { - balance + identity_balance as Credits + balance.checked_add(identity_balance as Credits).ok_or( + ProtocolError::Overflow( + "can't add identity balance to state transition balance", + ), + )? } } StateTransition::DataContractCreate(st) => { @@ -414,9 +423,11 @@ mod test { let output_amount = get_output_amount_from_identity_transition!(identity_create_transition); let state_repository_mock = MockStateRepositoryLike::new(); let calculate_state_transition_fee_mock = - |_: &StateTransition, _: &StateTransitionExecutionContext| FeeResult { - desired_amount: output_amount + 1, - ..Default::default() + |_: &StateTransition, _: &StateTransitionExecutionContext| { + Ok(FeeResult { + desired_amount: output_amount + 1, + ..Default::default() + }) }; let execution_context = StateTransitionExecutionContext::default(); @@ -445,9 +456,11 @@ mod test { let output_amount = get_output_amount_from_identity_transition!(identity_create_transition); let state_repository_mock = MockStateRepositoryLike::new(); let calculate_state_transition_fee_mock = - |_: &StateTransition, _: &StateTransitionExecutionContext| FeeResult { - desired_amount: output_amount, - ..Default::default() + |_: &StateTransition, _: &StateTransitionExecutionContext| { + Ok(FeeResult { + desired_amount: output_amount, + ..Default::default() + }) }; let execution_context = StateTransitionExecutionContext::default(); @@ -475,9 +488,11 @@ mod test { let output_amount = get_output_amount_from_identity_transition!(identity_topup_transition); let calculate_state_transition_fee_mock = - |_: &StateTransition, _: &StateTransitionExecutionContext| FeeResult { - desired_amount: output_amount + 2, - ..Default::default() + |_: &StateTransition, _: &StateTransitionExecutionContext| { + Ok(FeeResult { + desired_amount: output_amount + 2, + ..Default::default() + }) }; let execution_context = StateTransitionExecutionContext::default(); @@ -510,11 +525,12 @@ mod test { IdentityTopUpTransition::new(identity_topup_transition_fixture(None)).unwrap(); let output_amount = get_output_amount_from_identity_transition!(identity_topup_transition); - let calculation_mock = - |_: &StateTransition, _: &StateTransitionExecutionContext| FeeResult { + let calculation_mock = |_: &StateTransition, _: &StateTransitionExecutionContext| { + Ok(FeeResult { desired_amount: output_amount - 1, ..Default::default() - }; + }) + }; let execution_context = StateTransitionExecutionContext::default(); diff --git a/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs b/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs index 4fa4a2432bf..fc59b63a5a4 100644 --- a/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs +++ b/packages/rs-drive-abci/src/identity_credit_withdrawal/mod.rs @@ -504,7 +504,7 @@ where let output_script: Script = Script(output_script_bytes.into()); let tx_out = TxOut { - value: convert_credits_to_satoshi(amount), + value: convert_credits_to_satoshi(amount)?, script_pubkey: output_script, }; diff --git a/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/validate_documents_batch_transition_state.rs b/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/validate_documents_batch_transition_state.rs index 9d52166bde0..750dece4bc2 100644 --- a/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/validate_documents_batch_transition_state.rs +++ b/packages/rs-drive-abci/src/validation/state_transition/document_state_validation/validate_documents_batch_transition_state.rs @@ -39,6 +39,7 @@ use dpp::{ StateTransitionIdentitySigned, }, validation::ConsensusValidationResult, + NonConsensusError, ProtocolError, }; use drive::grovedb::TransactionArg; @@ -279,7 +280,7 @@ fn validate_transition( transition, latest_block_time_ms, average_block_spacing_ms, - ); + )?; result.merge(validation_result); if !result.is_valid() { @@ -289,7 +290,7 @@ fn validate_transition( transition, latest_block_time_ms, average_block_spacing_ms, - ); + )?; result.merge(validation_result); if !result.is_valid() { @@ -335,7 +336,7 @@ fn validate_transition( transition, latest_block_time_ms, average_block_spacing_ms, - ); + )?; result.merge(validation_result); if !result.is_valid() { @@ -537,18 +538,19 @@ pub fn check_created_inside_time_window( document_transition: &DocumentTransition, last_block_ts_millis: TimestampMillis, average_block_spacing_ms: u64, -) -> SimpleConsensusValidationResult { +) -> Result { let mut result = SimpleConsensusValidationResult::default(); let created_at = match document_transition.get_created_at() { Some(t) => t, - None => return result, + None => return Ok(result), }; let window_validation = validate_time_in_block_time_window( last_block_ts_millis, created_at, average_block_spacing_ms, - ); + ) + .map_err(|e| Error::Protocol(ProtocolError::NonConsensusError(e)))?; if !window_validation.is_valid() { result.add_error(ConsensusError::StateError( StateError::DocumentTimestampWindowViolationError( @@ -562,25 +564,26 @@ pub fn check_created_inside_time_window( ), )); } - result + Ok(result) } pub fn check_updated_inside_time_window( document_transition: &DocumentTransition, last_block_ts_millis: TimestampMillis, average_block_spacing_ms: u64, -) -> SimpleConsensusValidationResult { +) -> Result { let mut result = SimpleConsensusValidationResult::default(); let updated_at = match document_transition.get_updated_at() { Some(t) => t, - None => return result, + None => return Ok(result), }; let window_validation = validate_time_in_block_time_window( last_block_ts_millis, updated_at, average_block_spacing_ms, - ); + ) + .map_err(|e| Error::Protocol(ProtocolError::NonConsensusError(e)))?; if !window_validation.is_valid() { result.add_error(ConsensusError::StateError( StateError::DocumentTimestampWindowViolationError( @@ -594,5 +597,5 @@ pub fn check_updated_inside_time_window( ), )); } - result + Ok(result) } diff --git a/packages/rs-drive-abci/src/validation/state_transition/identity_update.rs b/packages/rs-drive-abci/src/validation/state_transition/identity_update.rs index be90ac20220..266be8bc6ee 100644 --- a/packages/rs-drive-abci/src/validation/state_transition/identity_update.rs +++ b/packages/rs-drive-abci/src/validation/state_transition/identity_update.rs @@ -1,5 +1,5 @@ use dpp::identity::PartialIdentity; -use dpp::{identity::state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition, state_transition::StateTransitionAction, validation::{ConsensusValidationResult, SimpleConsensusValidationResult}}; +use dpp::{identity::state_transition::identity_update_transition::identity_update_transition::IdentityUpdateTransition, ProtocolError, state_transition::StateTransitionAction, validation::{ConsensusValidationResult, SimpleConsensusValidationResult}}; use dpp::block_time_window::validate_time_in_block_time_window::validate_time_in_block_time_window; use dpp::consensus::state::identity::identity_public_key_disabled_at_window_violation_error::IdentityPublicKeyDisabledAtWindowViolationError; use dpp::consensus::state::identity::invalid_identity_revision_error::InvalidIdentityRevisionError; @@ -163,7 +163,8 @@ impl StateTransitionValidation for IdentityUpdateTransition { last_block_time, disabled_at_ms, platform.config.block_spacing_ms, - ); + ) + .map_err(|e| Error::Protocol(ProtocolError::NonConsensusError(e)))?; if !window_validation_result.is_valid() { validation_result.add_error( diff --git a/packages/wasm-dpp/src/state_transition/fee/calculate_operation_fees.rs b/packages/wasm-dpp/src/state_transition/fee/calculate_operation_fees.rs index 91625c269d8..8a270bbede5 100644 --- a/packages/wasm-dpp/src/state_transition/fee/calculate_operation_fees.rs +++ b/packages/wasm-dpp/src/state_transition/fee/calculate_operation_fees.rs @@ -1,11 +1,14 @@ -use dpp::state_transition::fee::{ - calculate_operation_fees::calculate_operation_fees, operations::Operation, +use dpp::{ + state_transition::fee::{ + calculate_operation_fees::calculate_operation_fees, operations::Operation, + }, + ProtocolError, }; use wasm_bindgen::prelude::*; use crate::{ fee::dummy_fee_result::DummyFeesResultWasm, - utils::{Inner, IntoWasm}, + utils::{Inner, IntoWasm, WithJsError}, }; use super::OperationWasm; @@ -20,5 +23,8 @@ pub fn calculate_operation_fees_wasm( inner_operations.push(operation.into_inner()) } - Ok(calculate_operation_fees(&inner_operations).into()) + Ok(calculate_operation_fees(&inner_operations) + .map_err(ProtocolError::from) + .with_js_error()? + .into()) } diff --git a/packages/wasm-dpp/src/state_transition/fee/calculate_state_transition_fee.rs b/packages/wasm-dpp/src/state_transition/fee/calculate_state_transition_fee.rs index 823b5a22fce..2e6a04e1337 100644 --- a/packages/wasm-dpp/src/state_transition/fee/calculate_state_transition_fee.rs +++ b/packages/wasm-dpp/src/state_transition/fee/calculate_state_transition_fee.rs @@ -1,9 +1,12 @@ -use dpp::state_transition::fee::calculate_state_transition_fee_factory::calculate_state_transition_fee; +use dpp::{ + state_transition::fee::calculate_state_transition_fee_factory::calculate_state_transition_fee, + ProtocolError, +}; use wasm_bindgen::prelude::*; use crate::{ conversion::create_state_transition_from_wasm_instance, fee::fee_result::FeeResultWasm, - StateTransitionExecutionContextWasm, + utils::WithJsError, StateTransitionExecutionContextWasm, }; #[wasm_bindgen(js_name=calculateStateTransitionFee)] @@ -15,6 +18,8 @@ pub fn calculate_state_transition_fee_wasm( Ok( calculate_state_transition_fee(&state_transition, &execution_context.to_owned().into()) + .map_err(ProtocolError::from) + .with_js_error()? .into(), ) } diff --git a/packages/wasm-dpp/src/state_transition/fee/operations/pre_calculated_operation.rs b/packages/wasm-dpp/src/state_transition/fee/operations/pre_calculated_operation.rs index 0ba6380d95d..640c05606e2 100644 --- a/packages/wasm-dpp/src/state_transition/fee/operations/pre_calculated_operation.rs +++ b/packages/wasm-dpp/src/state_transition/fee/operations/pre_calculated_operation.rs @@ -1,7 +1,10 @@ use crate::{fee::dummy_fee_result::DummyFeesResultWasm, utils::Inner}; -use dpp::state_transition::fee::{ - operations::{OperationLike, PreCalculatedOperation}, - Refunds, +use dpp::{ + state_transition::fee::{ + operations::{OperationLike, PreCalculatedOperation}, + Refunds, + }, + ProtocolError, }; use js_sys::{Array, BigInt}; use wasm_bindgen::prelude::*; @@ -57,13 +60,23 @@ impl PreCalculatedOperationWasm { } #[wasm_bindgen(js_name = getProcessingCost)] - pub fn processing_cost(&self) -> BigInt { - BigInt::from(self.0.get_processing_cost()) + pub fn processing_cost(&self) -> Result { + Ok(BigInt::from( + self.0 + .get_processing_cost() + .map_err(ProtocolError::from) + .with_js_error()?, + )) } #[wasm_bindgen(js_name=getStorageCost)] - pub fn storage_cost(&self) -> BigInt { - BigInt::from(self.0.get_storage_cost()) + pub fn storage_cost(&self) -> Result { + Ok(BigInt::from( + self.0 + .get_storage_cost() + .map_err(ProtocolError::from) + .with_js_error()?, + )) } #[wasm_bindgen(getter)] diff --git a/packages/wasm-dpp/src/state_transition/fee/operations/read_operation.rs b/packages/wasm-dpp/src/state_transition/fee/operations/read_operation.rs index abb657d07cd..9d980bfbdb3 100644 --- a/packages/wasm-dpp/src/state_transition/fee/operations/read_operation.rs +++ b/packages/wasm-dpp/src/state_transition/fee/operations/read_operation.rs @@ -1,4 +1,7 @@ -use dpp::state_transition::fee::operations::{OperationLike, ReadOperation}; +use dpp::{ + state_transition::fee::operations::{OperationLike, ReadOperation}, + ProtocolError, +}; use js_sys::{Array, BigInt}; use wasm_bindgen::prelude::*; @@ -32,13 +35,23 @@ impl ReadOperationWasm { } #[wasm_bindgen(getter,js_name = processingCost)] - pub fn processing_cost(&self) -> BigInt { - BigInt::from(self.0.get_processing_cost()) + pub fn processing_cost(&self) -> Result { + Ok(BigInt::from( + self.0 + .get_processing_cost() + .map_err(ProtocolError::from) + .with_js_error()?, + )) } #[wasm_bindgen(getter, js_name=storageCost)] - pub fn storage_cost(&self) -> BigInt { - BigInt::from(self.0.get_storage_cost()) + pub fn storage_cost(&self) -> Result { + Ok(BigInt::from( + self.0 + .get_storage_cost() + .map_err(ProtocolError::from) + .with_js_error()?, + )) } #[wasm_bindgen(getter)] diff --git a/packages/wasm-dpp/src/state_transition/fee/operations/signature_verification_operation.rs b/packages/wasm-dpp/src/state_transition/fee/operations/signature_verification_operation.rs index ffa27dc4723..85fbc7cd360 100644 --- a/packages/wasm-dpp/src/state_transition/fee/operations/signature_verification_operation.rs +++ b/packages/wasm-dpp/src/state_transition/fee/operations/signature_verification_operation.rs @@ -4,6 +4,7 @@ use anyhow::anyhow; use dpp::{ identity::KeyType, state_transition::fee::operations::{OperationLike, SignatureVerificationOperation}, + ProtocolError, }; use js_sys::{Array, BigInt}; use wasm_bindgen::prelude::*; @@ -41,13 +42,23 @@ impl SignatureVerificationOperationWasm { } #[wasm_bindgen(js_name = getProcessingCost)] - pub fn get_processing_cost(&self) -> BigInt { - BigInt::from(self.0.get_processing_cost()) + pub fn get_processing_cost(&self) -> Result { + Ok(BigInt::from( + self.0 + .get_processing_cost() + .map_err(ProtocolError::from) + .with_js_error()?, + )) } #[wasm_bindgen(js_name=getStorageCost)] - pub fn get_storage_cost(&self) -> BigInt { - BigInt::from(self.0.get_storage_cost()) + pub fn get_storage_cost(&self) -> Result { + Ok(BigInt::from( + self.0 + .get_storage_cost() + .map_err(ProtocolError::from) + .with_js_error()?, + )) } #[wasm_bindgen(getter)] From 4e82486df1cc6389959c8dc7b0fccbfb71c7a5ee Mon Sep 17 00:00:00 2001 From: Wisdom Ogwu <40731160+iammadab@users.noreply.github.com> Date: Tue, 4 Apr 2023 15:44:50 +0100 Subject: [PATCH 03/21] feat: drive verification c bindings (#860) --- Cargo.lock | 68 +- Cargo.toml | 1 + packages/rs-dpp/src/lib.rs | 1 + packages/rs-drive-verify-c-binding/.gitignore | 3 + packages/rs-drive-verify-c-binding/Cargo.toml | 20 + packages/rs-drive-verify-c-binding/build.rs | 12 + packages/rs-drive-verify-c-binding/c/main.c | 265 ++++++ packages/rs-drive-verify-c-binding/c/utils.c | 87 ++ .../rs-drive-verify-c-binding/cbindgen.toml | 0 packages/rs-drive-verify-c-binding/src/lib.rs | 780 ++++++++++++++++++ .../rs-drive-verify-c-binding/src/types.rs | 203 +++++ .../rs-drive-verify-c-binding/src/util.rs | 99 +++ packages/rs-drive/Cargo.toml | 1 + packages/rs-drive/src/drive/verify/mod.rs | 2 +- 14 files changed, 1539 insertions(+), 3 deletions(-) create mode 100644 packages/rs-drive-verify-c-binding/.gitignore create mode 100644 packages/rs-drive-verify-c-binding/Cargo.toml create mode 100644 packages/rs-drive-verify-c-binding/build.rs create mode 100644 packages/rs-drive-verify-c-binding/c/main.c create mode 100644 packages/rs-drive-verify-c-binding/c/utils.c create mode 100644 packages/rs-drive-verify-c-binding/cbindgen.toml create mode 100644 packages/rs-drive-verify-c-binding/src/lib.rs create mode 100644 packages/rs-drive-verify-c-binding/src/types.rs create mode 100644 packages/rs-drive-verify-c-binding/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index d7cd7436e3b..afd7ca339ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -525,6 +525,25 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cbindgen" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6358dedf60f4d9b8db43ad187391afe959746101346fe51bb978126bec61dfb" +dependencies = [ + "clap 3.2.24", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "tempfile", + "toml", +] + [[package]] name = "cc" version = "1.0.79" @@ -607,10 +626,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags", - "textwrap", + "textwrap 0.11.0", "unicode-width", ] +[[package]] +name = "clap" +version = "3.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef2b3ded6a26dfaec672a742c93c8cf6b689220324da509ec5caa20de55dc83" +dependencies = [ + "atty", + "bitflags", + "clap_lex 0.2.4", + "indexmap", + "strsim", + "termcolor", + "textwrap 0.16.0", +] + [[package]] name = "clap" version = "4.2.4" @@ -631,7 +665,7 @@ dependencies = [ "anstream", "anstyle", "bitflags", - "clap_lex", + "clap_lex 0.4.1", "strsim", ] @@ -647,6 +681,15 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "clap_lex" version = "0.4.1" @@ -2353,6 +2396,12 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "os_str_bytes" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" + [[package]] name = "output_vt100" version = "0.1.3" @@ -2950,6 +2999,15 @@ dependencies = [ "librocksdb-sys", ] +[[package]] +name = "rs-drive-verify-c-binding" +version = "0.1.0" +dependencies = [ + "cbindgen", + "drive", + "hex", +] + [[package]] name = "rust_decimal" version = "1.29.1" @@ -3542,6 +3600,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + [[package]] name = "thiserror" version = "1.0.40" diff --git a/Cargo.toml b/Cargo.toml index fd540e3fd56..dffb1861363 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,5 @@ members = [ "packages/feature-flags-contract", "packages/dpns-contract", "packages/data-contracts", + "packages/rs-drive-verify-c-binding" ] diff --git a/packages/rs-dpp/src/lib.rs b/packages/rs-dpp/src/lib.rs index f48d5e00a80..5b60db9c068 100644 --- a/packages/rs-dpp/src/lib.rs +++ b/packages/rs-dpp/src/lib.rs @@ -56,6 +56,7 @@ pub mod prelude { pub use crate::document::ExtendedDocument; pub use crate::errors::ProtocolError; pub use crate::identifier::Identifier; + pub use crate::identity::state_transition::asset_lock_proof::AssetLockProof; pub use crate::identity::Identity; pub use crate::identity::IdentityPublicKey; pub use crate::validation::ConsensusValidationResult; diff --git a/packages/rs-drive-verify-c-binding/.gitignore b/packages/rs-drive-verify-c-binding/.gitignore new file mode 100644 index 00000000000..178ab4f911c --- /dev/null +++ b/packages/rs-drive-verify-c-binding/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +a \ No newline at end of file diff --git a/packages/rs-drive-verify-c-binding/Cargo.toml b/packages/rs-drive-verify-c-binding/Cargo.toml new file mode 100644 index 00000000000..7736140b4b0 --- /dev/null +++ b/packages/rs-drive-verify-c-binding/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "rs-drive-verify-c-binding" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "drive" +crate-type = ["staticlib"] + +[build-dependencies] +cbindgen = "0.24.3" + +[dependencies] +hex = "0.4.3" + +[dependencies.drive] +path = "../rs-drive" +features = ["verify"] +default-features = false diff --git a/packages/rs-drive-verify-c-binding/build.rs b/packages/rs-drive-verify-c-binding/build.rs new file mode 100644 index 00000000000..9369ce57341 --- /dev/null +++ b/packages/rs-drive-verify-c-binding/build.rs @@ -0,0 +1,12 @@ + + +use std::env; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let mut config: cbindgen::Config = Default::default(); + config.language = cbindgen::Language::C; + cbindgen::generate_with_config(crate_dir, config) + .unwrap() + .write_to_file("target/drive.h"); +} diff --git a/packages/rs-drive-verify-c-binding/c/main.c b/packages/rs-drive-verify-c-binding/c/main.c new file mode 100644 index 00000000000..a374f303c12 --- /dev/null +++ b/packages/rs-drive-verify-c-binding/c/main.c @@ -0,0 +1,265 @@ +#include +#include +#include "../target/drive.h" +#include "./utils.c" + +void test_verify_full_identity_by_public_key_hash() { + char *proof_hex = "06000100a603014a75cf3f535e81c4680f8137a2208dbcb2652ffd7e715bd4290cc5c560b2cc6102cfbe0535bd2defe586b863b9ccb92d0d66fb2b810d730e7ba2cb7e2fb302613b100401180018020114aee302720896bba837dcf3f2d674f546fd25496f00ca359aa1b2032e3158ae5e5c489f7d46722f29644a15e1cf7c3935b30606def61104012000240201203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a200a0d5a4f6418663468515cd75189be3e1034bbfa9a1807eb81d964ba7442a0b1e100169931838564707dbf11e90a059fd7dd453cc7e68adb7d2c2375bae53566664e711025670752cc3d883200a7598b65cd74b41a760cc0be57cda5536f15f03c8783aa81001c33635136e502e9ac5244b15a20a757e0759ce0a90823cd37f893f6a49556d26040160002d0401203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a2fd1cb12a5b2614000000fbbd3be097e7f07d5619dd69e7767884d116f95ae9a5fcdb651e71727902cc1e10011e0c1443d0925f781132f4c506747202dbffa3ca3ded4d2387d4b7e40e0303e311110201187f03144463a1a994d5040e69c090b6985d7af295bfd11a002300203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a200029e6f2d33b1580030e3b6030e3c25016ab7253965682556059dcc243b75c7fa6d1001e09f88cd09cc595d524892b3e642b939f2827995605703c49c861f653001d5e1110101204d04203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a200090201010201030000007fae89b888b23f4fbdaed2fb990a1f42727aef5bd2a8b91f8cb970570909ab3901203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a27f030100000b000800000000000000100004010100050201010100d651221796b5206a5b9678a4d9995d519d8b9e75e87d85e57effb91f82a23e8d1002bcf84a882c0f72dd0d520a6954b3e1887fa55b7dc67635b44516856b31fd20a8100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201019401030100001e001b0000010200144463a1a994d5040e69c090b6985d7af295bfd11a0000030101003a0037010002010030973988b291fd1bca86d906723e335bdf13d3ebbadfea31dd164b3c672c16da72af8e6edfc0bac44b92b8c536d708dc33000010030102002b00280200030000210360da79c58995e4ec88512af9a4440ca4f2d7bfe84240e17effc4dd8ce94033a20000110201604f04203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a2000b03fd1cb12a5b261400000068f31829eaec02f7e5eddada129d4981a99bda0e5c0fd4eff3c23eafc2c79a02"; + char *pub_key_hex = "4463a1a994d5040e69c090b6985d7af295bfd11a"; + + unsigned char *proof_bin = hex2bin(proof_hex); + unsigned char *pub_key_bin = hex2bin(pub_key_hex); + + IdentityVerificationResult *result = verify_full_identity_by_public_key_hash(proof_bin, 1038, pub_key_bin); + assert(result->is_valid); + + uint8_t expected_root_hash[32] = {72,72,215,200,156,21,128,156,166,182,110,57,113,232,229,242,193,199,240,135,222,102,246,165,181,68,81,221,120,195,236,199}; + assert(is_array_equal(result->root_hash, expected_root_hash,32)); + + assert(result->has_identity); + + Identity *identity = result->identity; + assert(identity->protocol_version == 1); + + uint8_t id[32] = {62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, 67, 22, 47, 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162}; + assert(is_array_equal(*identity->id, id, 32)); + + // Confirm identity has 3 public keys + assert(identity->public_keys_count == 3); + + // Assert on the first public key + assert(identity->public_keys[0]->key == 0); + IdentityPublicKey *first = identity->public_keys[0]->public_key; + assert(first->id == 0); + assert(first->purpose == 0); + assert(first->security_level == 1); + assert(first->key_type == 2); + assert(first->read_only == false); + assert(first->has_disabled_at == false); + uint8_t first_public_key[20] = {68, 99, 161, 169, 148, 213, 4, 14, 105, 192, 144, 182, 152, 93, 122, 242, 149, 191, 209, 26}; + assert(is_array_equal(first->data, first_public_key, first->data_length)); + + // Assert on the second public key + assert(identity->public_keys[1]->key == 1); + IdentityPublicKey *second = identity->public_keys[1]->public_key; + assert(second->id == 1); + assert(second->purpose == 0); + assert(second->security_level == 2); + assert(second->key_type == 1); + assert(second->read_only == false); + assert(second->has_disabled_at == false); + unsigned char second_public_key[50] = {151, 57, 136, 178, 145, 253, 27, 202, 134, 217, 6, 114, 62, 51, 91, 223, 19, 211, 235, 186, 223, 234, 49, 221, 22, 75, 60, 103, 44, 22, 218, 114, 175, 142, 110, 223, 192, 186, 196, 75, 146, 184, 197, 54, 215, 8, 220, 51}; + assert(is_array_equal(second->data,second_public_key, second->data_length)); + + // Assert on the third public key + assert(identity->public_keys[0]->key == 0); + IdentityPublicKey *third = identity->public_keys[2]->public_key; + assert(third->id == 2); + assert(third->purpose == 0); + assert(third->security_level == 3); + assert(third->key_type == 0); + assert(third->read_only == false); + assert(third->has_disabled_at == false); + unsigned char third_public_key[33] = {3, 96, 218, 121, 197, 137, 149, 228, 236, 136, 81, 42, 249, 164, 68, 12, 164, 242, 215, 191, 232, 66, 64, 225, 126, 255, 196, 221, 140, 233, 64, 51, 162}; + assert(is_array_equal(third->data,third_public_key, third->data_length)); + + assert(identity->balance == 11077485418638); + assert(identity->revision == 16); + assert(!identity->has_metadata); + assert(!identity->has_asset_lock_proof); + +} + +void test_verify_full_identities_by_public_key_hashes() { + char *multiple_identity_proof_hex = "06000100a603014a75cf3f535e81c4680f8137a2208dbcb2652ffd7e715bd4290cc5c560b2cc6102cfbe0535bd2defe586b863b9ccb92d0d66fb2b810d730e7ba2cb7e2fb302613b1004011800180201145e0e49d808ad21d01d07dd799a75bd1b472788a7008c10aa4c1d19e2e7e42fe0b1a7f6d93d4c0b6992ef63ea985c16447cada4629511040120002402012035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c3000ba56cfb1d87ef47857f6b1cd7fb918406fd50f81966619777dd4c1b595a1a26e100169931838564707dbf11e90a059fd7dd453cc7e68adb7d2c2375bae53566664e711025670752cc3d883200a7598b65cd74b41a760cc0be57cda5536f15f03c8783aa81001c33635136e502e9ac5244b15a20a757e0759ce0a90823cd37f893f6a49556d26040160002d04012035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c30fdbafd6833aeb700000012b27f4a0a7cfd06e3387b33a5bca6682953512e21621ae9cf6d633d9041771910011e0c1443d0925f781132f4c506747202dbffa3ca3ded4d2387d4b7e40e0303e31111020118b3080132c9d35844d5ce2a8e0f377cee23c143a53396073dea86c494b86ba4c4af0b3903141f0815269afc012de44260ceb28a4496d3184184002300200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb00100168576e24521e03ba4b624912bb07833767c81102310b87d8ea1caf2795c68f921102e8b7eb376f0f7993badf93971f690be8a48f09db0711f052a2ed48471497b9d01003144463a1a994d5040e69c090b6985d7af295bfd11a002300203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a20002254bf0a990beb721c21f21e8dbab50e33cd9cf09618fc27c9f7450c673516aee1001c6adfe081809218ee07461f95f53ce6ce462ec379f97a71f1be40f7218cb50af111103145e0e49d808ad21d01d07dd799a75bd1b472788a70023002035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c30001001a4b31998d47c30e390f4fa56f28f19c62f114f17a704d29c56e28b6fdb47f101031467892af390cd2b7653a918c7b692c85b87b44d3200230020399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f80001001eb4da977338a3da4204eaaac0c8856bdfd51d9b25ceef04b40bb38eff79ab11011021f22102429dbe1bc0ca714847b08187d9a874cc43329aaa79647fb9aa0834d691003149a061f31734c5f5f0b119ab72d433c9af133d3a600230020e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae4330002644c601e67692188cf5a975c2207caba899d99f1bbd4b62e5fe856850b9d7286100314a54921bb29b67e31898efebc29f241b1aefa4dca002300207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e510010029e6f2d33b1580030e3b6030e3c25016ab7253965682556059dcc243b75c7fa6d0314b3bfce478de96fe30cd3713bf88ce7728687da8a00230020a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be40001111110314bb3df025e32fd90d1feee7dca4b83321c683292d0023002003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d3001001ee0847805b145b5fb500b139fe12767ee681fc310a21d6e9814619df5187470802a0de352fe6767da7bf4c33ba7d2da8db0440457835d3c2992473210e02b6312c1001e09f88cd09cc595d524892b3e642b939f2827995605703c49c861f653001d5e1029a563c983d202520c1a94f4c6ba99750373450aaf9dcb2a62ef50e9877646043100314ed738aaadd75d1677fefeccadd033f126cfee76a0023002097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef000314fbd9daa5993de56a2e4346b7c72ff5585efffaab002300201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb0010111111110101208b06042003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d300090201010201030000006c4bfcf223cd4fe5c1cac82e1a9e2c73eb0e7f34cebabdd7630e24cb192f975804200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb000902010102010300000080da62acb8c49f901d6bf84a2a2af15431e69e29069abf8d02f2c113c6099ba61004201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb000902010102010300000051a23049efbcde3a0e9c85ea7af05a28d4de31f90ae44a07c5fa18090128237011042035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c3000090201010201030000002a390761b997897afe51540c39dfeb5c78d00781a547d2b83b1e72259894dea5100420399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f800009020101020103000000b3f28f9cc26df90ea49e13e3cd97c01d772e9d6609453e91d4369ef78e3880a004203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a200090201010201030000007fae89b888b23f4fbdaed2fb990a1f42727aef5bd2a8b91f8cb970570909ab391004207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e5100090201010201030000001d64a3f9270bf8b8104305ba76829472f3aac2b6fff20b98ac10361ec5473fbb11042097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef0009020101020103000000adb76570d64f89650686df5819414e5e42cf7eedab24605aa63c4b8e26e90eda100420a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be400009020101020103000000a5a8530416d9462521b6fd932723d8971684b4620e4254caf09c75289e0e64700420e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae4330009020101020103000000fa1d907f967c48292a5af3d4c3aad435c2ee9237119614d612aee3b4f52e3614111111012003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d37f030100000b0008000000000000003d000401010005020101010072cc451270c61384d358f7d41135b78788011830301a697b97a3714c203a36dc100214105bdf191491b67249d321f3d9bebdf82c9a3395fef336c60b3701af0593e7100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df11020101a301030100001e001b000002030014bb3df025e32fd90d1feee7dca4b83321c683292d0000030101003a0037010001010030a5fd02c96d5f60eb54b15b043a84ed80a0af804eff4a2bfea1fc9fed323232c7ab12072368097e556439d08aa0a6866c000010030102003a003702000001003085ff00e6339367d3e31e27cbc33c13c3cd0c6e973a5b902e76668d7a6daf83c129257cc7f9cc35e1c0689a6df03a891d00001101200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb7f030100000b000800000000000000030004010100050201010100783a62676dbffd012f9343ef0af71c1b800cda19801689dfb7e2372cccc3ed9d10027f0e94e54c63ffdcd3d3d9017a63e82f9984ade5c4faa59d2479c11007932524100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df1102010178030100002b002800000200002103fe65fcdcfe242dc2e43d654274ec9ce1bbbc9dd5a1c88945eeef18cc93151f7f0000030101001e001b010001030014c94f46cf38b83862990f782c84acbc178d7b02da000010030102001e001b02000203001426d387d9884862f96160dd59ca596bdce82da74600001101201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb7f030100000b000800000000000000600004010100050201010100ca3a10eab3b889465bba51bc5354131aee1044e510d9ed4a7068d1181c7dbfcd10029626ec2b4e8861c675b20bc4456333d8c41fd0c0b9c9f0b78047c6634ebab8ef100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018501030100001e001b000003020014fbd9daa5993de56a2e4346b7c72ff5585efffaab0000030101002b002801000300002103fdc9403eb6f005db700e7841627f4f92e7c65d167384cd57a4f4e46583c21afe000010030102002b002802000000002103703446f77c8db1fbac6f3422c8e045098adb662c0b620a15b8c4d9ecd2a3defa000011012035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c307f030100000b000800000000000000420004010100050201010100d7f397a816f23f32e9a6cd2ab5b03d5b6d30742cf0b58517f276d9f75c1c4d611002bfd5686ae0d2a7684c2f6ed3a7419a436a5389afc9a84a1bb22a1decdfa7625d100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df11020101a301030100001e001b0000020300145e0e49d808ad21d01d07dd799a75bd1b472788a70000030101003a003701000001003097c8d8102d216818c693dc46614ce9242b8e54e05a8ff1f520a3694b9481091d92906b13b9b2762b127ee4f07e91119e000010030102003a00370200030100308949c96dda849268044e176dbdba458fb5deac81e9918793bdb837f5afee0c2496a5930d46d1fe37ce536cbef8e95bb40000110120399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f807f030100000b0008000000000000000c0004010100050201010100a9403aa408af35d267980dfff1706d70a59b6dba867d0b568ca8c5b77560d67a100276cbfe822d7b9f6863f9a06b668097458e123ff385e31c29d830d03f1148973a100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018701030100003a0037000000010030b79d4caa865f84207124c3d304430372f39d7c18a237df3a71e3c4fb7ba9ab9816439a809beb8606c3bb52d53a5364590000030101001e001b0100030200146406a5082b231340726d4cd0de2452bc73a33003000010030102001e001b0200010300144ac7b42f524e1d1b22098f85adfca752600ef9a000001101203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a27f030100000b000800000000000000100004010100050201010100d651221796b5206a5b9678a4d9995d519d8b9e75e87d85e57effb91f82a23e8d1002bcf84a882c0f72dd0d520a6954b3e1887fa55b7dc67635b44516856b31fd20a8100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201019401030100001e001b0000010200144463a1a994d5040e69c090b6985d7af295bfd11a0000030101003a0037010002010030973988b291fd1bca86d906723e335bdf13d3ebbadfea31dd164b3c672c16da72af8e6edfc0bac44b92b8c536d708dc33000010030102002b00280200030000210360da79c58995e4ec88512af9a4440ca4f2d7bfe84240e17effc4dd8ce94033a200001101207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e517f030100000b000800000000000000580004010100050201010100d95ff983db933edc675487a6f4e388fcf2db59313aeab5f45991a7f2471774471002355f98c38fd87ca5775e5e451243eb11300ed91fc950ea204c0a74b9a1991a25100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df11020101a301030100003a0037000003010030b3423844bae8a591bbfb437b55566b5d61e54ee64f93351b0a3b9d4b731445d25ce367f7aedfcb32bd3cd14308a54cf50000030101003a0037010001010030a154c19082ac6b5fec72b81f6488550fec7149d52f66b4463915a61179c4f1f8507d366614b454dabf2c942235caad01000010030102001e001b0200030300140a8c14745c982f9fdc43aa985c02b1e5bff6c403000011012097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef7f030100000b000800000000000000620004010100050201010100412c1e7de2394dcd009223eb8c3a24e34b93a7c48df0bb86499160a31ea9dbdb1002180590eec33397034675f379cf17f62c0e77d17724a03238dcd3f216a4bc9509100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201019401030100003a0037000002010030afa4370aa5a48ab2f3ab510ccaba3b6d8cf51752304507e6a341c4e4ff6aa7c07610a503b42f479834b032d25dd160590000030101002b002801000300002103a9584c4580d165d2744ba49a70472653915bfdbec4bef471e26ce4c1c9e6c6ab000010030102001e001b02000102001445d04558a26b8ca04b486957c8abf5abf24ec76f0000110120a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be407f030100000b0008000000000000005d0004010100050201010100f140186a6bd413a50814db484b00398c2e7e6da9fbe2cb536728e880deb7506010027767f75fded47f94a6f81c671d448beddb6c2727f1f209ba015bf8de7331c13c100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018501030100002b002800000000002102eebd2f91818a234e1879f8a55652f1e52419ad168b8f27b91be6b79958f7a5510000030101002b00280100020000210214a91dfcb36718209a5ee79c290029b849f1ce2feef6585a3b3fa37d04fb62b7000010030102001e001b0200030200144ee490084160fc8b1e73361d5a4c055beee77d8c0000110120e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae4337f030100000b0008000000000000004f0004010100050201010100eb90b3c6d9a547e3b8a1111f621e0dbe5bfc68a8196371d497cb2912fa809d001002ab80ce7b6ca4875dbc7dc1f0d902551628c97b2383c27d04538d46a97d3cad43100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018701030100001e001b0000020300149a061f31734c5f5f0b119ab72d433c9af133d3a60000030101003a003701000101003096e1fc631934a14acd313ff28ca29c9e9b43181b8df29386702b1a2d65a7cc823683f5733e296fb40c73648bc9cbf625000010030102001e001b02000102001474f185aa527f31202442d208cdb2905fa71403290000110201609f06042003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d3000b03fda4d93a40301700000095840c6be056ed3d199dedf5265a5d3dafd195aa4cb54ca26943f7e092a5f06904200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb000b03fd62132d410718000000b9dec92595e5eabb6de045782827bd98b60b9252287eec0f8e3450ea7c59619b1004201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb000b03fd8e29761d5405000000380d1a8cb3511b3ecf770a1d81f40c293182d29cf0574962db50c4cfb626fdb711042035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c30000b03fd62b9bfe135190000002eefa752386580c31084b54f2119973ccef6ac92fc38607e8609e896c9994c3e100420399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f80000b03fdd0a1b7eee2140000002271b648a8925b8c717543453a59a3a20a3c52ce9b3e5fc793983c64f9f6fea004203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a2000b03fd1cb12a5b261400000068f31829eaec02f7e5eddada129d4981a99bda0e5c0fd4eff3c23eafc2c79a021004207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e51000b03fd586ba25d1f1a0000005e3b38a6d9bede250ed0b612d01915a78182ee18d819d07d43ae925728b42d2d11042097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef000b03fd120b51f4ea10000000be380b13cfd7149332e5ac818ae84d31e4be119bc0ebd717475630f6f38b6e90100420a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be40000b03fd505d6c926f0d0000009500204c698cc12fc96774a34f77415c37376ff17b492838e414d774b5b7bec10420e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae433000b03fd1e078984690800000064d995f4b5b62c480a04f1b8fb4c7a30b607f12da4abc357993ee7505be19b26111111"; + char *pub_key_hash_one_hex = "1f0815269afc012de44260ceb28a4496d3184184"; + char *pub_key_hash_two_hex = "4463a1a994d5040e69c090b6985d7af295bfd11a"; + char *pub_key_hash_three_hex = "5e0e49d808ad21d01d07dd799a75bd1b472788a7"; + + unsigned char *multiple_identity_proof_bin = hex2bin(multiple_identity_proof_hex); + unsigned char *pub_key_hashes[3] = { + hex2bin(pub_key_hash_one_hex), + hex2bin(pub_key_hash_two_hex), + hex2bin(pub_key_hash_three_hex), + }; + MultipleIdentityVerificationResult *multi_iden_result = verify_full_identities_by_public_key_hashes(multiple_identity_proof_bin, 6206, pub_key_hashes, 3); + assert(multi_iden_result->is_valid); + + uint8_t expected_root_hash[32] = {202, 84, 121, 98, 165, 168, 181, 237, 228, 130, 249, 5, 45, 10, 35, 77, 17, 60, 42, 121, 141, 6, 90, 21, 12, 231, 68, 33, 156, 219, 114, 132}; + assert(is_array_equal(expected_root_hash, *multi_iden_result->root_hash, 32)); + + assert(multi_iden_result->map_size == 3); + + uint8_t iden_one_pk_hash[20] = { 31, + 8, + 21, + 38, + 154, + 252, + 1, + 45, + 228, + 66, + 96, + 206, + 178, + 138, + 68, + 150, + 211, + 24, + 65, + 132}; + assert(is_array_equal(iden_one_pk_hash, multi_iden_result-> public_key_hash_identity_map[0]->public_key_hash, multi_iden_result->public_key_hash_identity_map[0]->public_key_hash_length)); + assert(multi_iden_result->public_key_hash_identity_map[0]->has_identity); + + uint8_t iden_two_pk_hash[20] = { 68, + 99, + 161, + 169, + 148, + 213, + 4, + 14, + 105, + 192, + 144, + 182, + 152, + 93, + 122, + 242, + 149, + 191, + 209, + 26}; + assert(is_array_equal(iden_two_pk_hash, multi_iden_result-> public_key_hash_identity_map[1]->public_key_hash, multi_iden_result->public_key_hash_identity_map[1]->public_key_hash_length)); + assert(multi_iden_result->public_key_hash_identity_map[1]->has_identity); + + uint8_t iden_three_pk_hash[20] = { 94, + 14, + 73, + 216, 8, + 173, + 33, + 208, + 29, + 7, + 221, + 121, + 154, + 117, + 189, + 27, + 71, + 39, + 136, + 167}; + assert(is_array_equal(iden_three_pk_hash, multi_iden_result-> public_key_hash_identity_map[2]->public_key_hash, multi_iden_result->public_key_hash_identity_map[2]->public_key_hash_length)); + assert(multi_iden_result->public_key_hash_identity_map[2]->has_identity); +} + +void test_verify_full_identity_by_identity_id() { + char *proof_hex = "06000100a603014a75cf3f535e81c4680f8137a2208dbcb2652ffd7e715bd4290cc5c560b2cc6102cfbe0535bd2defe586b863b9ccb92d0d66fb2b810d730e7ba2cb7e2fb302613b100401180018020114aee302720896bba837dcf3f2d674f546fd25496f00ca359aa1b2032e3158ae5e5c489f7d46722f29644a15e1cf7c3935b30606def61104012000240201203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a200a0d5a4f6418663468515cd75189be3e1034bbfa9a1807eb81d964ba7442a0b1e100169931838564707dbf11e90a059fd7dd453cc7e68adb7d2c2375bae53566664e711025670752cc3d883200a7598b65cd74b41a760cc0be57cda5536f15f03c8783aa81001c33635136e502e9ac5244b15a20a757e0759ce0a90823cd37f893f6a49556d26040160002d0401203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a2fd1cb12a5b2614000000fbbd3be097e7f07d5619dd69e7767884d116f95ae9a5fcdb651e71727902cc1e10011e0c1443d0925f781132f4c506747202dbffa3ca3ded4d2387d4b7e40e0303e311110201187f03144463a1a994d5040e69c090b6985d7af295bfd11a002300203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a200029e6f2d33b1580030e3b6030e3c25016ab7253965682556059dcc243b75c7fa6d1001e09f88cd09cc595d524892b3e642b939f2827995605703c49c861f653001d5e1110101204d04203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a200090201010201030000007fae89b888b23f4fbdaed2fb990a1f42727aef5bd2a8b91f8cb970570909ab3901203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a27f030100000b000800000000000000100004010100050201010100d651221796b5206a5b9678a4d9995d519d8b9e75e87d85e57effb91f82a23e8d1002bcf84a882c0f72dd0d520a6954b3e1887fa55b7dc67635b44516856b31fd20a8100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201019401030100001e001b0000010200144463a1a994d5040e69c090b6985d7af295bfd11a0000030101003a0037010002010030973988b291fd1bca86d906723e335bdf13d3ebbadfea31dd164b3c672c16da72af8e6edfc0bac44b92b8c536d708dc33000010030102002b00280200030000210360da79c58995e4ec88512af9a4440ca4f2d7bfe84240e17effc4dd8ce94033a20000110201604f04203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a2000b03fd1cb12a5b261400000068f31829eaec02f7e5eddada129d4981a99bda0e5c0fd4eff3c23eafc2c79a02"; + char *identity_id_hex = "3eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a2"; + + unsigned char *proof_bin = hex2bin(proof_hex); + unsigned char *identity_id_bin = hex2bin(identity_id_hex); + + IdentityVerificationResult *result = verify_full_identity_by_identity_id(proof_bin, 1038, true, identity_id_bin); + assert(result->is_valid); + + uint8_t expected_root_hash[32] = {72,72,215,200,156,21,128,156,166,182,110,57,113,232,229,242,193,199,240,135,222,102,246,165,181,68,81,221,120,195,236,199}; + assert(is_array_equal(result->root_hash, expected_root_hash,32)); + + assert(result->has_identity); +} + +void test_verify_identity_id_by_public_key_hash() { + char *multiple_identity_proof_hex = "06000100a603014a75cf3f535e81c4680f8137a2208dbcb2652ffd7e715bd4290cc5c560b2cc6102cfbe0535bd2defe586b863b9ccb92d0d66fb2b810d730e7ba2cb7e2fb302613b1004011800180201145e0e49d808ad21d01d07dd799a75bd1b472788a7008c10aa4c1d19e2e7e42fe0b1a7f6d93d4c0b6992ef63ea985c16447cada4629511040120002402012035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c3000ba56cfb1d87ef47857f6b1cd7fb918406fd50f81966619777dd4c1b595a1a26e100169931838564707dbf11e90a059fd7dd453cc7e68adb7d2c2375bae53566664e711025670752cc3d883200a7598b65cd74b41a760cc0be57cda5536f15f03c8783aa81001c33635136e502e9ac5244b15a20a757e0759ce0a90823cd37f893f6a49556d26040160002d04012035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c30fdbafd6833aeb700000012b27f4a0a7cfd06e3387b33a5bca6682953512e21621ae9cf6d633d9041771910011e0c1443d0925f781132f4c506747202dbffa3ca3ded4d2387d4b7e40e0303e31111020118b3080132c9d35844d5ce2a8e0f377cee23c143a53396073dea86c494b86ba4c4af0b3903141f0815269afc012de44260ceb28a4496d3184184002300200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb00100168576e24521e03ba4b624912bb07833767c81102310b87d8ea1caf2795c68f921102e8b7eb376f0f7993badf93971f690be8a48f09db0711f052a2ed48471497b9d01003144463a1a994d5040e69c090b6985d7af295bfd11a002300203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a20002254bf0a990beb721c21f21e8dbab50e33cd9cf09618fc27c9f7450c673516aee1001c6adfe081809218ee07461f95f53ce6ce462ec379f97a71f1be40f7218cb50af111103145e0e49d808ad21d01d07dd799a75bd1b472788a70023002035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c30001001a4b31998d47c30e390f4fa56f28f19c62f114f17a704d29c56e28b6fdb47f101031467892af390cd2b7653a918c7b692c85b87b44d3200230020399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f80001001eb4da977338a3da4204eaaac0c8856bdfd51d9b25ceef04b40bb38eff79ab11011021f22102429dbe1bc0ca714847b08187d9a874cc43329aaa79647fb9aa0834d691003149a061f31734c5f5f0b119ab72d433c9af133d3a600230020e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae4330002644c601e67692188cf5a975c2207caba899d99f1bbd4b62e5fe856850b9d7286100314a54921bb29b67e31898efebc29f241b1aefa4dca002300207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e510010029e6f2d33b1580030e3b6030e3c25016ab7253965682556059dcc243b75c7fa6d0314b3bfce478de96fe30cd3713bf88ce7728687da8a00230020a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be40001111110314bb3df025e32fd90d1feee7dca4b83321c683292d0023002003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d3001001ee0847805b145b5fb500b139fe12767ee681fc310a21d6e9814619df5187470802a0de352fe6767da7bf4c33ba7d2da8db0440457835d3c2992473210e02b6312c1001e09f88cd09cc595d524892b3e642b939f2827995605703c49c861f653001d5e1029a563c983d202520c1a94f4c6ba99750373450aaf9dcb2a62ef50e9877646043100314ed738aaadd75d1677fefeccadd033f126cfee76a0023002097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef000314fbd9daa5993de56a2e4346b7c72ff5585efffaab002300201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb0010111111110101208b06042003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d300090201010201030000006c4bfcf223cd4fe5c1cac82e1a9e2c73eb0e7f34cebabdd7630e24cb192f975804200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb000902010102010300000080da62acb8c49f901d6bf84a2a2af15431e69e29069abf8d02f2c113c6099ba61004201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb000902010102010300000051a23049efbcde3a0e9c85ea7af05a28d4de31f90ae44a07c5fa18090128237011042035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c3000090201010201030000002a390761b997897afe51540c39dfeb5c78d00781a547d2b83b1e72259894dea5100420399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f800009020101020103000000b3f28f9cc26df90ea49e13e3cd97c01d772e9d6609453e91d4369ef78e3880a004203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a200090201010201030000007fae89b888b23f4fbdaed2fb990a1f42727aef5bd2a8b91f8cb970570909ab391004207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e5100090201010201030000001d64a3f9270bf8b8104305ba76829472f3aac2b6fff20b98ac10361ec5473fbb11042097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef0009020101020103000000adb76570d64f89650686df5819414e5e42cf7eedab24605aa63c4b8e26e90eda100420a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be400009020101020103000000a5a8530416d9462521b6fd932723d8971684b4620e4254caf09c75289e0e64700420e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae4330009020101020103000000fa1d907f967c48292a5af3d4c3aad435c2ee9237119614d612aee3b4f52e3614111111012003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d37f030100000b0008000000000000003d000401010005020101010072cc451270c61384d358f7d41135b78788011830301a697b97a3714c203a36dc100214105bdf191491b67249d321f3d9bebdf82c9a3395fef336c60b3701af0593e7100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df11020101a301030100001e001b000002030014bb3df025e32fd90d1feee7dca4b83321c683292d0000030101003a0037010001010030a5fd02c96d5f60eb54b15b043a84ed80a0af804eff4a2bfea1fc9fed323232c7ab12072368097e556439d08aa0a6866c000010030102003a003702000001003085ff00e6339367d3e31e27cbc33c13c3cd0c6e973a5b902e76668d7a6daf83c129257cc7f9cc35e1c0689a6df03a891d00001101200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb7f030100000b000800000000000000030004010100050201010100783a62676dbffd012f9343ef0af71c1b800cda19801689dfb7e2372cccc3ed9d10027f0e94e54c63ffdcd3d3d9017a63e82f9984ade5c4faa59d2479c11007932524100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df1102010178030100002b002800000200002103fe65fcdcfe242dc2e43d654274ec9ce1bbbc9dd5a1c88945eeef18cc93151f7f0000030101001e001b010001030014c94f46cf38b83862990f782c84acbc178d7b02da000010030102001e001b02000203001426d387d9884862f96160dd59ca596bdce82da74600001101201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb7f030100000b000800000000000000600004010100050201010100ca3a10eab3b889465bba51bc5354131aee1044e510d9ed4a7068d1181c7dbfcd10029626ec2b4e8861c675b20bc4456333d8c41fd0c0b9c9f0b78047c6634ebab8ef100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018501030100001e001b000003020014fbd9daa5993de56a2e4346b7c72ff5585efffaab0000030101002b002801000300002103fdc9403eb6f005db700e7841627f4f92e7c65d167384cd57a4f4e46583c21afe000010030102002b002802000000002103703446f77c8db1fbac6f3422c8e045098adb662c0b620a15b8c4d9ecd2a3defa000011012035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c307f030100000b000800000000000000420004010100050201010100d7f397a816f23f32e9a6cd2ab5b03d5b6d30742cf0b58517f276d9f75c1c4d611002bfd5686ae0d2a7684c2f6ed3a7419a436a5389afc9a84a1bb22a1decdfa7625d100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df11020101a301030100001e001b0000020300145e0e49d808ad21d01d07dd799a75bd1b472788a70000030101003a003701000001003097c8d8102d216818c693dc46614ce9242b8e54e05a8ff1f520a3694b9481091d92906b13b9b2762b127ee4f07e91119e000010030102003a00370200030100308949c96dda849268044e176dbdba458fb5deac81e9918793bdb837f5afee0c2496a5930d46d1fe37ce536cbef8e95bb40000110120399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f807f030100000b0008000000000000000c0004010100050201010100a9403aa408af35d267980dfff1706d70a59b6dba867d0b568ca8c5b77560d67a100276cbfe822d7b9f6863f9a06b668097458e123ff385e31c29d830d03f1148973a100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018701030100003a0037000000010030b79d4caa865f84207124c3d304430372f39d7c18a237df3a71e3c4fb7ba9ab9816439a809beb8606c3bb52d53a5364590000030101001e001b0100030200146406a5082b231340726d4cd0de2452bc73a33003000010030102001e001b0200010300144ac7b42f524e1d1b22098f85adfca752600ef9a000001101203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a27f030100000b000800000000000000100004010100050201010100d651221796b5206a5b9678a4d9995d519d8b9e75e87d85e57effb91f82a23e8d1002bcf84a882c0f72dd0d520a6954b3e1887fa55b7dc67635b44516856b31fd20a8100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201019401030100001e001b0000010200144463a1a994d5040e69c090b6985d7af295bfd11a0000030101003a0037010002010030973988b291fd1bca86d906723e335bdf13d3ebbadfea31dd164b3c672c16da72af8e6edfc0bac44b92b8c536d708dc33000010030102002b00280200030000210360da79c58995e4ec88512af9a4440ca4f2d7bfe84240e17effc4dd8ce94033a200001101207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e517f030100000b000800000000000000580004010100050201010100d95ff983db933edc675487a6f4e388fcf2db59313aeab5f45991a7f2471774471002355f98c38fd87ca5775e5e451243eb11300ed91fc950ea204c0a74b9a1991a25100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df11020101a301030100003a0037000003010030b3423844bae8a591bbfb437b55566b5d61e54ee64f93351b0a3b9d4b731445d25ce367f7aedfcb32bd3cd14308a54cf50000030101003a0037010001010030a154c19082ac6b5fec72b81f6488550fec7149d52f66b4463915a61179c4f1f8507d366614b454dabf2c942235caad01000010030102001e001b0200030300140a8c14745c982f9fdc43aa985c02b1e5bff6c403000011012097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef7f030100000b000800000000000000620004010100050201010100412c1e7de2394dcd009223eb8c3a24e34b93a7c48df0bb86499160a31ea9dbdb1002180590eec33397034675f379cf17f62c0e77d17724a03238dcd3f216a4bc9509100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201019401030100003a0037000002010030afa4370aa5a48ab2f3ab510ccaba3b6d8cf51752304507e6a341c4e4ff6aa7c07610a503b42f479834b032d25dd160590000030101002b002801000300002103a9584c4580d165d2744ba49a70472653915bfdbec4bef471e26ce4c1c9e6c6ab000010030102001e001b02000102001445d04558a26b8ca04b486957c8abf5abf24ec76f0000110120a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be407f030100000b0008000000000000005d0004010100050201010100f140186a6bd413a50814db484b00398c2e7e6da9fbe2cb536728e880deb7506010027767f75fded47f94a6f81c671d448beddb6c2727f1f209ba015bf8de7331c13c100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018501030100002b002800000000002102eebd2f91818a234e1879f8a55652f1e52419ad168b8f27b91be6b79958f7a5510000030101002b00280100020000210214a91dfcb36718209a5ee79c290029b849f1ce2feef6585a3b3fa37d04fb62b7000010030102001e001b0200030200144ee490084160fc8b1e73361d5a4c055beee77d8c0000110120e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae4337f030100000b0008000000000000004f0004010100050201010100eb90b3c6d9a547e3b8a1111f621e0dbe5bfc68a8196371d497cb2912fa809d001002ab80ce7b6ca4875dbc7dc1f0d902551628c97b2383c27d04538d46a97d3cad43100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018701030100001e001b0000020300149a061f31734c5f5f0b119ab72d433c9af133d3a60000030101003a003701000101003096e1fc631934a14acd313ff28ca29c9e9b43181b8df29386702b1a2d65a7cc823683f5733e296fb40c73648bc9cbf625000010030102001e001b02000102001474f185aa527f31202442d208cdb2905fa71403290000110201609f06042003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d3000b03fda4d93a40301700000095840c6be056ed3d199dedf5265a5d3dafd195aa4cb54ca26943f7e092a5f06904200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb000b03fd62132d410718000000b9dec92595e5eabb6de045782827bd98b60b9252287eec0f8e3450ea7c59619b1004201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb000b03fd8e29761d5405000000380d1a8cb3511b3ecf770a1d81f40c293182d29cf0574962db50c4cfb626fdb711042035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c30000b03fd62b9bfe135190000002eefa752386580c31084b54f2119973ccef6ac92fc38607e8609e896c9994c3e100420399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f80000b03fdd0a1b7eee2140000002271b648a8925b8c717543453a59a3a20a3c52ce9b3e5fc793983c64f9f6fea004203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a2000b03fd1cb12a5b261400000068f31829eaec02f7e5eddada129d4981a99bda0e5c0fd4eff3c23eafc2c79a021004207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e51000b03fd586ba25d1f1a0000005e3b38a6d9bede250ed0b612d01915a78182ee18d819d07d43ae925728b42d2d11042097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef000b03fd120b51f4ea10000000be380b13cfd7149332e5ac818ae84d31e4be119bc0ebd717475630f6f38b6e90100420a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be40000b03fd505d6c926f0d0000009500204c698cc12fc96774a34f77415c37376ff17b492838e414d774b5b7bec10420e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae433000b03fd1e078984690800000064d995f4b5b62c480a04f1b8fb4c7a30b607f12da4abc357993ee7505be19b26111111"; + char *pub_key_hash_hex = "1f0815269afc012de44260ceb28a4496d3184184"; + + unsigned char *proof = hex2bin(multiple_identity_proof_hex); + unsigned char *pub_key_hash = hex2bin(pub_key_hash_hex); + + IdentityIdVerificationResult *result = verify_identity_id_by_public_key_hash(proof, 6206, true, pub_key_hash); + uint8_t expected_identity_id[32] = {15, 126, 159, 152, 150, 254, 206, 186, 180, 193, 157, 65, 233, 215, 241, 108, 23, 39, + 205, 99, 217, 219, 86, 244, 213, 176, 67, 34, 242, 146, 86, 203,}; + assert(result->is_valid); + assert(result->has_identity_id); + assert(result->id_size == 32); + assert(is_array_equal(expected_identity_id, result->identity_id, result->id_size)); +} + +void test_verify_identity_balances_by_identity_ids() { + char *multiple_identity_proof_hex = "06000100a603014a75cf3f535e81c4680f8137a2208dbcb2652ffd7e715bd4290cc5c560b2cc6102cfbe0535bd2defe586b863b9ccb92d0d66fb2b810d730e7ba2cb7e2fb302613b1004011800180201145e0e49d808ad21d01d07dd799a75bd1b472788a7008c10aa4c1d19e2e7e42fe0b1a7f6d93d4c0b6992ef63ea985c16447cada4629511040120002402012035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c3000ba56cfb1d87ef47857f6b1cd7fb918406fd50f81966619777dd4c1b595a1a26e100169931838564707dbf11e90a059fd7dd453cc7e68adb7d2c2375bae53566664e711025670752cc3d883200a7598b65cd74b41a760cc0be57cda5536f15f03c8783aa81001c33635136e502e9ac5244b15a20a757e0759ce0a90823cd37f893f6a49556d26040160002d04012035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c30fdbafd6833aeb700000012b27f4a0a7cfd06e3387b33a5bca6682953512e21621ae9cf6d633d9041771910011e0c1443d0925f781132f4c506747202dbffa3ca3ded4d2387d4b7e40e0303e31111020118b3080132c9d35844d5ce2a8e0f377cee23c143a53396073dea86c494b86ba4c4af0b3903141f0815269afc012de44260ceb28a4496d3184184002300200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb00100168576e24521e03ba4b624912bb07833767c81102310b87d8ea1caf2795c68f921102e8b7eb376f0f7993badf93971f690be8a48f09db0711f052a2ed48471497b9d01003144463a1a994d5040e69c090b6985d7af295bfd11a002300203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a20002254bf0a990beb721c21f21e8dbab50e33cd9cf09618fc27c9f7450c673516aee1001c6adfe081809218ee07461f95f53ce6ce462ec379f97a71f1be40f7218cb50af111103145e0e49d808ad21d01d07dd799a75bd1b472788a70023002035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c30001001a4b31998d47c30e390f4fa56f28f19c62f114f17a704d29c56e28b6fdb47f101031467892af390cd2b7653a918c7b692c85b87b44d3200230020399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f80001001eb4da977338a3da4204eaaac0c8856bdfd51d9b25ceef04b40bb38eff79ab11011021f22102429dbe1bc0ca714847b08187d9a874cc43329aaa79647fb9aa0834d691003149a061f31734c5f5f0b119ab72d433c9af133d3a600230020e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae4330002644c601e67692188cf5a975c2207caba899d99f1bbd4b62e5fe856850b9d7286100314a54921bb29b67e31898efebc29f241b1aefa4dca002300207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e510010029e6f2d33b1580030e3b6030e3c25016ab7253965682556059dcc243b75c7fa6d0314b3bfce478de96fe30cd3713bf88ce7728687da8a00230020a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be40001111110314bb3df025e32fd90d1feee7dca4b83321c683292d0023002003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d3001001ee0847805b145b5fb500b139fe12767ee681fc310a21d6e9814619df5187470802a0de352fe6767da7bf4c33ba7d2da8db0440457835d3c2992473210e02b6312c1001e09f88cd09cc595d524892b3e642b939f2827995605703c49c861f653001d5e1029a563c983d202520c1a94f4c6ba99750373450aaf9dcb2a62ef50e9877646043100314ed738aaadd75d1677fefeccadd033f126cfee76a0023002097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef000314fbd9daa5993de56a2e4346b7c72ff5585efffaab002300201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb0010111111110101208b06042003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d300090201010201030000006c4bfcf223cd4fe5c1cac82e1a9e2c73eb0e7f34cebabdd7630e24cb192f975804200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb000902010102010300000080da62acb8c49f901d6bf84a2a2af15431e69e29069abf8d02f2c113c6099ba61004201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb000902010102010300000051a23049efbcde3a0e9c85ea7af05a28d4de31f90ae44a07c5fa18090128237011042035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c3000090201010201030000002a390761b997897afe51540c39dfeb5c78d00781a547d2b83b1e72259894dea5100420399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f800009020101020103000000b3f28f9cc26df90ea49e13e3cd97c01d772e9d6609453e91d4369ef78e3880a004203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a200090201010201030000007fae89b888b23f4fbdaed2fb990a1f42727aef5bd2a8b91f8cb970570909ab391004207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e5100090201010201030000001d64a3f9270bf8b8104305ba76829472f3aac2b6fff20b98ac10361ec5473fbb11042097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef0009020101020103000000adb76570d64f89650686df5819414e5e42cf7eedab24605aa63c4b8e26e90eda100420a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be400009020101020103000000a5a8530416d9462521b6fd932723d8971684b4620e4254caf09c75289e0e64700420e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae4330009020101020103000000fa1d907f967c48292a5af3d4c3aad435c2ee9237119614d612aee3b4f52e3614111111012003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d37f030100000b0008000000000000003d000401010005020101010072cc451270c61384d358f7d41135b78788011830301a697b97a3714c203a36dc100214105bdf191491b67249d321f3d9bebdf82c9a3395fef336c60b3701af0593e7100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df11020101a301030100001e001b000002030014bb3df025e32fd90d1feee7dca4b83321c683292d0000030101003a0037010001010030a5fd02c96d5f60eb54b15b043a84ed80a0af804eff4a2bfea1fc9fed323232c7ab12072368097e556439d08aa0a6866c000010030102003a003702000001003085ff00e6339367d3e31e27cbc33c13c3cd0c6e973a5b902e76668d7a6daf83c129257cc7f9cc35e1c0689a6df03a891d00001101200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb7f030100000b000800000000000000030004010100050201010100783a62676dbffd012f9343ef0af71c1b800cda19801689dfb7e2372cccc3ed9d10027f0e94e54c63ffdcd3d3d9017a63e82f9984ade5c4faa59d2479c11007932524100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df1102010178030100002b002800000200002103fe65fcdcfe242dc2e43d654274ec9ce1bbbc9dd5a1c88945eeef18cc93151f7f0000030101001e001b010001030014c94f46cf38b83862990f782c84acbc178d7b02da000010030102001e001b02000203001426d387d9884862f96160dd59ca596bdce82da74600001101201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb7f030100000b000800000000000000600004010100050201010100ca3a10eab3b889465bba51bc5354131aee1044e510d9ed4a7068d1181c7dbfcd10029626ec2b4e8861c675b20bc4456333d8c41fd0c0b9c9f0b78047c6634ebab8ef100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018501030100001e001b000003020014fbd9daa5993de56a2e4346b7c72ff5585efffaab0000030101002b002801000300002103fdc9403eb6f005db700e7841627f4f92e7c65d167384cd57a4f4e46583c21afe000010030102002b002802000000002103703446f77c8db1fbac6f3422c8e045098adb662c0b620a15b8c4d9ecd2a3defa000011012035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c307f030100000b000800000000000000420004010100050201010100d7f397a816f23f32e9a6cd2ab5b03d5b6d30742cf0b58517f276d9f75c1c4d611002bfd5686ae0d2a7684c2f6ed3a7419a436a5389afc9a84a1bb22a1decdfa7625d100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df11020101a301030100001e001b0000020300145e0e49d808ad21d01d07dd799a75bd1b472788a70000030101003a003701000001003097c8d8102d216818c693dc46614ce9242b8e54e05a8ff1f520a3694b9481091d92906b13b9b2762b127ee4f07e91119e000010030102003a00370200030100308949c96dda849268044e176dbdba458fb5deac81e9918793bdb837f5afee0c2496a5930d46d1fe37ce536cbef8e95bb40000110120399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f807f030100000b0008000000000000000c0004010100050201010100a9403aa408af35d267980dfff1706d70a59b6dba867d0b568ca8c5b77560d67a100276cbfe822d7b9f6863f9a06b668097458e123ff385e31c29d830d03f1148973a100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018701030100003a0037000000010030b79d4caa865f84207124c3d304430372f39d7c18a237df3a71e3c4fb7ba9ab9816439a809beb8606c3bb52d53a5364590000030101001e001b0100030200146406a5082b231340726d4cd0de2452bc73a33003000010030102001e001b0200010300144ac7b42f524e1d1b22098f85adfca752600ef9a000001101203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a27f030100000b000800000000000000100004010100050201010100d651221796b5206a5b9678a4d9995d519d8b9e75e87d85e57effb91f82a23e8d1002bcf84a882c0f72dd0d520a6954b3e1887fa55b7dc67635b44516856b31fd20a8100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201019401030100001e001b0000010200144463a1a994d5040e69c090b6985d7af295bfd11a0000030101003a0037010002010030973988b291fd1bca86d906723e335bdf13d3ebbadfea31dd164b3c672c16da72af8e6edfc0bac44b92b8c536d708dc33000010030102002b00280200030000210360da79c58995e4ec88512af9a4440ca4f2d7bfe84240e17effc4dd8ce94033a200001101207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e517f030100000b000800000000000000580004010100050201010100d95ff983db933edc675487a6f4e388fcf2db59313aeab5f45991a7f2471774471002355f98c38fd87ca5775e5e451243eb11300ed91fc950ea204c0a74b9a1991a25100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df11020101a301030100003a0037000003010030b3423844bae8a591bbfb437b55566b5d61e54ee64f93351b0a3b9d4b731445d25ce367f7aedfcb32bd3cd14308a54cf50000030101003a0037010001010030a154c19082ac6b5fec72b81f6488550fec7149d52f66b4463915a61179c4f1f8507d366614b454dabf2c942235caad01000010030102001e001b0200030300140a8c14745c982f9fdc43aa985c02b1e5bff6c403000011012097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef7f030100000b000800000000000000620004010100050201010100412c1e7de2394dcd009223eb8c3a24e34b93a7c48df0bb86499160a31ea9dbdb1002180590eec33397034675f379cf17f62c0e77d17724a03238dcd3f216a4bc9509100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201019401030100003a0037000002010030afa4370aa5a48ab2f3ab510ccaba3b6d8cf51752304507e6a341c4e4ff6aa7c07610a503b42f479834b032d25dd160590000030101002b002801000300002103a9584c4580d165d2744ba49a70472653915bfdbec4bef471e26ce4c1c9e6c6ab000010030102001e001b02000102001445d04558a26b8ca04b486957c8abf5abf24ec76f0000110120a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be407f030100000b0008000000000000005d0004010100050201010100f140186a6bd413a50814db484b00398c2e7e6da9fbe2cb536728e880deb7506010027767f75fded47f94a6f81c671d448beddb6c2727f1f209ba015bf8de7331c13c100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018501030100002b002800000000002102eebd2f91818a234e1879f8a55652f1e52419ad168b8f27b91be6b79958f7a5510000030101002b00280100020000210214a91dfcb36718209a5ee79c290029b849f1ce2feef6585a3b3fa37d04fb62b7000010030102001e001b0200030200144ee490084160fc8b1e73361d5a4c055beee77d8c0000110120e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae4337f030100000b0008000000000000004f0004010100050201010100eb90b3c6d9a547e3b8a1111f621e0dbe5bfc68a8196371d497cb2912fa809d001002ab80ce7b6ca4875dbc7dc1f0d902551628c97b2383c27d04538d46a97d3cad43100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018701030100001e001b0000020300149a061f31734c5f5f0b119ab72d433c9af133d3a60000030101003a003701000101003096e1fc631934a14acd313ff28ca29c9e9b43181b8df29386702b1a2d65a7cc823683f5733e296fb40c73648bc9cbf625000010030102001e001b02000102001474f185aa527f31202442d208cdb2905fa71403290000110201609f06042003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d3000b03fda4d93a40301700000095840c6be056ed3d199dedf5265a5d3dafd195aa4cb54ca26943f7e092a5f06904200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb000b03fd62132d410718000000b9dec92595e5eabb6de045782827bd98b60b9252287eec0f8e3450ea7c59619b1004201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb000b03fd8e29761d5405000000380d1a8cb3511b3ecf770a1d81f40c293182d29cf0574962db50c4cfb626fdb711042035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c30000b03fd62b9bfe135190000002eefa752386580c31084b54f2119973ccef6ac92fc38607e8609e896c9994c3e100420399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f80000b03fdd0a1b7eee2140000002271b648a8925b8c717543453a59a3a20a3c52ce9b3e5fc793983c64f9f6fea004203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a2000b03fd1cb12a5b261400000068f31829eaec02f7e5eddada129d4981a99bda0e5c0fd4eff3c23eafc2c79a021004207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e51000b03fd586ba25d1f1a0000005e3b38a6d9bede250ed0b612d01915a78182ee18d819d07d43ae925728b42d2d11042097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef000b03fd120b51f4ea10000000be380b13cfd7149332e5ac818ae84d31e4be119bc0ebd717475630f6f38b6e90100420a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be40000b03fd505d6c926f0d0000009500204c698cc12fc96774a34f77415c37376ff17b492838e414d774b5b7bec10420e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae433000b03fd1e078984690800000064d995f4b5b62c480a04f1b8fb4c7a30b607f12da4abc357993ee7505be19b26111111"; + char *iden_one_hex = "3eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a2"; + char *iden_two_hex = "97ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef"; + + unsigned char *proof = hex2bin(multiple_identity_proof_hex); + unsigned char *iden_ids[2] = { + hex2bin(iden_one_hex), + hex2bin(iden_two_hex), + }; + MultipleIdentityBalanceVerificationResult *result = verify_identity_balances_by_identity_ids(proof, 6206, true, iden_ids, 2); + assert(result->is_valid); + assert(result->map_size == 2); + assert(result->identity_id_balance_map[0]->has_balance); + assert(result->identity_id_balance_map[0]->balance == 11077485418638); + uint8_t expected_iden_one_bin[32] = {62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, 67, 22, + 47, 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162}; + assert(is_array_equal(expected_iden_one_bin, result->identity_id_balance_map[0]->identity_id, result->identity_id_balance_map[0]->id_size)); + + assert(result->identity_id_balance_map[1]->has_balance); + assert(result->identity_id_balance_map[1]->balance == 9300653671817); + uint8_t expected_iden_two_bin[32] = {151, 172, 124, 81, 243, 147, 225, 5, 188, 204, 9, 152, 150, 127, 129, 13, 246, 19, + 141, 93, 239, 8, 214, 194, 123, 127, 177, 23, 144, 211, 189, 239,}; + assert(is_array_equal(expected_iden_two_bin, result->identity_id_balance_map[1]->identity_id, result->identity_id_balance_map[1]->id_size)); +} + +void test_verify_identity_ids_by_public_key_hashes() { + char *multiple_identity_proof_hex = "06000100a603014a75cf3f535e81c4680f8137a2208dbcb2652ffd7e715bd4290cc5c560b2cc6102cfbe0535bd2defe586b863b9ccb92d0d66fb2b810d730e7ba2cb7e2fb302613b1004011800180201145e0e49d808ad21d01d07dd799a75bd1b472788a7008c10aa4c1d19e2e7e42fe0b1a7f6d93d4c0b6992ef63ea985c16447cada4629511040120002402012035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c3000ba56cfb1d87ef47857f6b1cd7fb918406fd50f81966619777dd4c1b595a1a26e100169931838564707dbf11e90a059fd7dd453cc7e68adb7d2c2375bae53566664e711025670752cc3d883200a7598b65cd74b41a760cc0be57cda5536f15f03c8783aa81001c33635136e502e9ac5244b15a20a757e0759ce0a90823cd37f893f6a49556d26040160002d04012035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c30fdbafd6833aeb700000012b27f4a0a7cfd06e3387b33a5bca6682953512e21621ae9cf6d633d9041771910011e0c1443d0925f781132f4c506747202dbffa3ca3ded4d2387d4b7e40e0303e31111020118b3080132c9d35844d5ce2a8e0f377cee23c143a53396073dea86c494b86ba4c4af0b3903141f0815269afc012de44260ceb28a4496d3184184002300200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb00100168576e24521e03ba4b624912bb07833767c81102310b87d8ea1caf2795c68f921102e8b7eb376f0f7993badf93971f690be8a48f09db0711f052a2ed48471497b9d01003144463a1a994d5040e69c090b6985d7af295bfd11a002300203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a20002254bf0a990beb721c21f21e8dbab50e33cd9cf09618fc27c9f7450c673516aee1001c6adfe081809218ee07461f95f53ce6ce462ec379f97a71f1be40f7218cb50af111103145e0e49d808ad21d01d07dd799a75bd1b472788a70023002035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c30001001a4b31998d47c30e390f4fa56f28f19c62f114f17a704d29c56e28b6fdb47f101031467892af390cd2b7653a918c7b692c85b87b44d3200230020399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f80001001eb4da977338a3da4204eaaac0c8856bdfd51d9b25ceef04b40bb38eff79ab11011021f22102429dbe1bc0ca714847b08187d9a874cc43329aaa79647fb9aa0834d691003149a061f31734c5f5f0b119ab72d433c9af133d3a600230020e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae4330002644c601e67692188cf5a975c2207caba899d99f1bbd4b62e5fe856850b9d7286100314a54921bb29b67e31898efebc29f241b1aefa4dca002300207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e510010029e6f2d33b1580030e3b6030e3c25016ab7253965682556059dcc243b75c7fa6d0314b3bfce478de96fe30cd3713bf88ce7728687da8a00230020a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be40001111110314bb3df025e32fd90d1feee7dca4b83321c683292d0023002003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d3001001ee0847805b145b5fb500b139fe12767ee681fc310a21d6e9814619df5187470802a0de352fe6767da7bf4c33ba7d2da8db0440457835d3c2992473210e02b6312c1001e09f88cd09cc595d524892b3e642b939f2827995605703c49c861f653001d5e1029a563c983d202520c1a94f4c6ba99750373450aaf9dcb2a62ef50e9877646043100314ed738aaadd75d1677fefeccadd033f126cfee76a0023002097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef000314fbd9daa5993de56a2e4346b7c72ff5585efffaab002300201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb0010111111110101208b06042003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d300090201010201030000006c4bfcf223cd4fe5c1cac82e1a9e2c73eb0e7f34cebabdd7630e24cb192f975804200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb000902010102010300000080da62acb8c49f901d6bf84a2a2af15431e69e29069abf8d02f2c113c6099ba61004201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb000902010102010300000051a23049efbcde3a0e9c85ea7af05a28d4de31f90ae44a07c5fa18090128237011042035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c3000090201010201030000002a390761b997897afe51540c39dfeb5c78d00781a547d2b83b1e72259894dea5100420399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f800009020101020103000000b3f28f9cc26df90ea49e13e3cd97c01d772e9d6609453e91d4369ef78e3880a004203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a200090201010201030000007fae89b888b23f4fbdaed2fb990a1f42727aef5bd2a8b91f8cb970570909ab391004207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e5100090201010201030000001d64a3f9270bf8b8104305ba76829472f3aac2b6fff20b98ac10361ec5473fbb11042097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef0009020101020103000000adb76570d64f89650686df5819414e5e42cf7eedab24605aa63c4b8e26e90eda100420a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be400009020101020103000000a5a8530416d9462521b6fd932723d8971684b4620e4254caf09c75289e0e64700420e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae4330009020101020103000000fa1d907f967c48292a5af3d4c3aad435c2ee9237119614d612aee3b4f52e3614111111012003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d37f030100000b0008000000000000003d000401010005020101010072cc451270c61384d358f7d41135b78788011830301a697b97a3714c203a36dc100214105bdf191491b67249d321f3d9bebdf82c9a3395fef336c60b3701af0593e7100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df11020101a301030100001e001b000002030014bb3df025e32fd90d1feee7dca4b83321c683292d0000030101003a0037010001010030a5fd02c96d5f60eb54b15b043a84ed80a0af804eff4a2bfea1fc9fed323232c7ab12072368097e556439d08aa0a6866c000010030102003a003702000001003085ff00e6339367d3e31e27cbc33c13c3cd0c6e973a5b902e76668d7a6daf83c129257cc7f9cc35e1c0689a6df03a891d00001101200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb7f030100000b000800000000000000030004010100050201010100783a62676dbffd012f9343ef0af71c1b800cda19801689dfb7e2372cccc3ed9d10027f0e94e54c63ffdcd3d3d9017a63e82f9984ade5c4faa59d2479c11007932524100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df1102010178030100002b002800000200002103fe65fcdcfe242dc2e43d654274ec9ce1bbbc9dd5a1c88945eeef18cc93151f7f0000030101001e001b010001030014c94f46cf38b83862990f782c84acbc178d7b02da000010030102001e001b02000203001426d387d9884862f96160dd59ca596bdce82da74600001101201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb7f030100000b000800000000000000600004010100050201010100ca3a10eab3b889465bba51bc5354131aee1044e510d9ed4a7068d1181c7dbfcd10029626ec2b4e8861c675b20bc4456333d8c41fd0c0b9c9f0b78047c6634ebab8ef100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018501030100001e001b000003020014fbd9daa5993de56a2e4346b7c72ff5585efffaab0000030101002b002801000300002103fdc9403eb6f005db700e7841627f4f92e7c65d167384cd57a4f4e46583c21afe000010030102002b002802000000002103703446f77c8db1fbac6f3422c8e045098adb662c0b620a15b8c4d9ecd2a3defa000011012035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c307f030100000b000800000000000000420004010100050201010100d7f397a816f23f32e9a6cd2ab5b03d5b6d30742cf0b58517f276d9f75c1c4d611002bfd5686ae0d2a7684c2f6ed3a7419a436a5389afc9a84a1bb22a1decdfa7625d100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df11020101a301030100001e001b0000020300145e0e49d808ad21d01d07dd799a75bd1b472788a70000030101003a003701000001003097c8d8102d216818c693dc46614ce9242b8e54e05a8ff1f520a3694b9481091d92906b13b9b2762b127ee4f07e91119e000010030102003a00370200030100308949c96dda849268044e176dbdba458fb5deac81e9918793bdb837f5afee0c2496a5930d46d1fe37ce536cbef8e95bb40000110120399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f807f030100000b0008000000000000000c0004010100050201010100a9403aa408af35d267980dfff1706d70a59b6dba867d0b568ca8c5b77560d67a100276cbfe822d7b9f6863f9a06b668097458e123ff385e31c29d830d03f1148973a100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018701030100003a0037000000010030b79d4caa865f84207124c3d304430372f39d7c18a237df3a71e3c4fb7ba9ab9816439a809beb8606c3bb52d53a5364590000030101001e001b0100030200146406a5082b231340726d4cd0de2452bc73a33003000010030102001e001b0200010300144ac7b42f524e1d1b22098f85adfca752600ef9a000001101203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a27f030100000b000800000000000000100004010100050201010100d651221796b5206a5b9678a4d9995d519d8b9e75e87d85e57effb91f82a23e8d1002bcf84a882c0f72dd0d520a6954b3e1887fa55b7dc67635b44516856b31fd20a8100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201019401030100001e001b0000010200144463a1a994d5040e69c090b6985d7af295bfd11a0000030101003a0037010002010030973988b291fd1bca86d906723e335bdf13d3ebbadfea31dd164b3c672c16da72af8e6edfc0bac44b92b8c536d708dc33000010030102002b00280200030000210360da79c58995e4ec88512af9a4440ca4f2d7bfe84240e17effc4dd8ce94033a200001101207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e517f030100000b000800000000000000580004010100050201010100d95ff983db933edc675487a6f4e388fcf2db59313aeab5f45991a7f2471774471002355f98c38fd87ca5775e5e451243eb11300ed91fc950ea204c0a74b9a1991a25100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df11020101a301030100003a0037000003010030b3423844bae8a591bbfb437b55566b5d61e54ee64f93351b0a3b9d4b731445d25ce367f7aedfcb32bd3cd14308a54cf50000030101003a0037010001010030a154c19082ac6b5fec72b81f6488550fec7149d52f66b4463915a61179c4f1f8507d366614b454dabf2c942235caad01000010030102001e001b0200030300140a8c14745c982f9fdc43aa985c02b1e5bff6c403000011012097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef7f030100000b000800000000000000620004010100050201010100412c1e7de2394dcd009223eb8c3a24e34b93a7c48df0bb86499160a31ea9dbdb1002180590eec33397034675f379cf17f62c0e77d17724a03238dcd3f216a4bc9509100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201019401030100003a0037000002010030afa4370aa5a48ab2f3ab510ccaba3b6d8cf51752304507e6a341c4e4ff6aa7c07610a503b42f479834b032d25dd160590000030101002b002801000300002103a9584c4580d165d2744ba49a70472653915bfdbec4bef471e26ce4c1c9e6c6ab000010030102001e001b02000102001445d04558a26b8ca04b486957c8abf5abf24ec76f0000110120a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be407f030100000b0008000000000000005d0004010100050201010100f140186a6bd413a50814db484b00398c2e7e6da9fbe2cb536728e880deb7506010027767f75fded47f94a6f81c671d448beddb6c2727f1f209ba015bf8de7331c13c100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018501030100002b002800000000002102eebd2f91818a234e1879f8a55652f1e52419ad168b8f27b91be6b79958f7a5510000030101002b00280100020000210214a91dfcb36718209a5ee79c290029b849f1ce2feef6585a3b3fa37d04fb62b7000010030102001e001b0200030200144ee490084160fc8b1e73361d5a4c055beee77d8c0000110120e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae4337f030100000b0008000000000000004f0004010100050201010100eb90b3c6d9a547e3b8a1111f621e0dbe5bfc68a8196371d497cb2912fa809d001002ab80ce7b6ca4875dbc7dc1f0d902551628c97b2383c27d04538d46a97d3cad43100146197ba2d1d89ae65f0e38b4207166d2b6d52014cc704c567082bf1dbd65f9df110201018701030100001e001b0000020300149a061f31734c5f5f0b119ab72d433c9af133d3a60000030101003a003701000101003096e1fc631934a14acd313ff28ca29c9e9b43181b8df29386702b1a2d65a7cc823683f5733e296fb40c73648bc9cbf625000010030102001e001b02000102001474f185aa527f31202442d208cdb2905fa71403290000110201609f06042003c1211aa9d26239c6dca1e6cb1dbb8266fe2b9500f8699c84aa90d623f7b1d3000b03fda4d93a40301700000095840c6be056ed3d199dedf5265a5d3dafd195aa4cb54ca26943f7e092a5f06904200f7e9f9896fecebab4c19d41e9d7f16c1727cd63d9db56f4d5b04322f29256cb000b03fd62132d410718000000b9dec92595e5eabb6de045782827bd98b60b9252287eec0f8e3450ea7c59619b1004201b240fcb4e632e7856bff58b784dcebc19398c734ec600c465fde95a2366dbeb000b03fd8e29761d5405000000380d1a8cb3511b3ecf770a1d81f40c293182d29cf0574962db50c4cfb626fdb711042035a8dd6a65ed429912d2db054462c7e8c011965aa76a76356a69b4c881808c30000b03fd62b9bfe135190000002eefa752386580c31084b54f2119973ccef6ac92fc38607e8609e896c9994c3e100420399474f653ba6b7b3839a43ed0fa35ffcddd5efa1d0e7082941bd6240c219f80000b03fdd0a1b7eee2140000002271b648a8925b8c717543453a59a3a20a3c52ce9b3e5fc793983c64f9f6fea004203eab8233e9132dbfc2b700abb64d5d46d843162f27199c92236c638522bbf3a2000b03fd1cb12a5b261400000068f31829eaec02f7e5eddada129d4981a99bda0e5c0fd4eff3c23eafc2c79a021004207f3dfd2ccb054f410ee77eb02ee7b4ea960795d89746cdc226ddd899e6ac4e51000b03fd586ba25d1f1a0000005e3b38a6d9bede250ed0b612d01915a78182ee18d819d07d43ae925728b42d2d11042097ac7c51f393e105bccc0998967f810df6138d5def08d6c27b7fb11790d3bdef000b03fd120b51f4ea10000000be380b13cfd7149332e5ac818ae84d31e4be119bc0ebd717475630f6f38b6e90100420a89ba1a7b2bd5b99fc1beee05aca5587ae3cfb4628d2a0358f208252b7e8be40000b03fd505d6c926f0d0000009500204c698cc12fc96774a34f77415c37376ff17b492838e414d774b5b7bec10420e8f1eaea303ab85c0a20dc6e80ba551e3fab2b85702319a122e550a8734ae433000b03fd1e078984690800000064d995f4b5b62c480a04f1b8fb4c7a30b607f12da4abc357993ee7505be19b26111111"; + char *pub_key_hash_one_hex = "1f0815269afc012de44260ceb28a4496d3184184"; + char *pub_key_hash_two_hex = "4463a1a994d5040e69c090b6985d7af295bfd11a"; + char *pub_key_hash_three_hex = "5e0e49d808ad21d01d07dd799a75bd1b472788a7"; + + unsigned char *multiple_identity_proof_bin = hex2bin(multiple_identity_proof_hex); + unsigned char *pub_key_hashes[3] = { + hex2bin(pub_key_hash_one_hex), + hex2bin(pub_key_hash_two_hex), + hex2bin(pub_key_hash_three_hex), + }; + MultipleIdentityIdVerificationResult *result = verify_identity_ids_by_public_key_hashes(multiple_identity_proof_bin, 6206, true, pub_key_hashes, 3); + assert(result->is_valid); + assert(result->map_size == 3); + + assert(result->public_key_hash_identity_id_map[0]->has_identity_id); + assert(result->public_key_hash_identity_id_map[0]->id_size == 32); + uint8_t expected_id_one[32] = {15, 126, 159, 152, 150, 254, 206, 186, 180, 193, 157, 65, 233, 215, 241, 108, 23, + 39, 205, 99, 217, 219, 86, 244, 213, 176, 67, 34, 242, 146, 86, 203}; + assert(is_array_equal(expected_id_one, result->public_key_hash_identity_id_map[0]->identity_id, result->public_key_hash_identity_id_map[0]->id_size)); + + assert(result->public_key_hash_identity_id_map[1]->has_identity_id); + assert(result->public_key_hash_identity_id_map[1]->id_size == 32); + uint8_t expected_id_two[32] = {62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, 67, 22, + 47, 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162}; + assert(is_array_equal(expected_id_two, result->public_key_hash_identity_id_map[1]->identity_id, result->public_key_hash_identity_id_map[0]->id_size)); + + assert(result->public_key_hash_identity_id_map[2]->has_identity_id); + assert(result->public_key_hash_identity_id_map[2]->id_size == 32); + uint8_t expected_id_three[32] = {53, 168, 221, 106, 101, 237, 66, 153, 18, 210, 219, 5, 68, 98, 199, 232, 192, 17, + 150, 90, 167, 106, 118, 53, 106, 105, 180, 200, 129, 128, 140, 48,}; + assert(is_array_equal(expected_id_three, result->public_key_hash_identity_id_map[2]->identity_id, result->public_key_hash_identity_id_map[2]->id_size)); +} + + +int main() { + test_verify_full_identity_by_public_key_hash(); + test_verify_full_identities_by_public_key_hashes(); + test_verify_full_identity_by_identity_id(); + test_verify_identity_id_by_public_key_hash(); + test_verify_identity_balances_by_identity_ids(); + test_verify_identity_ids_by_public_key_hashes(); + + printf("All assertions passed!!"); +} diff --git a/packages/rs-drive-verify-c-binding/c/utils.c b/packages/rs-drive-verify-c-binding/c/utils.c new file mode 100644 index 00000000000..db766ffb833 --- /dev/null +++ b/packages/rs-drive-verify-c-binding/c/utils.c @@ -0,0 +1,87 @@ +// +// Created by anton on 05.10.2021. +// + +#include + +char *bin2hex(unsigned char *p, int len) +{ + char *hex = malloc(((2*len) + 1)); + char *r = hex; + + while(len && p) + { + (*r) = ((*p) & 0xF0) >> 4; + (*r) = ((*r) <= 9 ? '0' + (*r) : 'a' - 10 + (*r)); + r++; + (*r) = ((*p) & 0x0F); + (*r) = ((*r) <= 9 ? '0' + (*r) : 'a' - 10 + (*r)); + r++; + p++; + len--; + } + *r = '\0'; + + return hex; +} + +unsigned char *hex2bin(const char *str) +{ + int len, h; + unsigned char *result, *err, *p, c; + + err = malloc(1); + *err = 0; + + if (!str) + return err; + + if (!*str) + return err; + + len = 0; + p = (unsigned char*) str; + while (*p++) + len++; + + result = malloc((len/2)+1); + h = !(len%2) * 4; + p = result; + *p = 0; + + c = *str; + while(c) + { + if(('0' <= c) && (c <= '9')) + *p += (c - '0') << h; + else if(('A' <= c) && (c <= 'F')) + *p += (c - 'A' + 10) << h; + else if(('a' <= c) && (c <= 'f')) + *p += (c - 'a' + 10) << h; + else + return err; + + str++; + c = *str; + + if (h) + h = 0; + else + { + h = 4; + p++; + *p = 0; + } + } + + return result; +} + +bool is_array_equal(uint8_t a[], uint8_t b[], int size) { + for (int i = 0; i < size; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; +} diff --git a/packages/rs-drive-verify-c-binding/cbindgen.toml b/packages/rs-drive-verify-c-binding/cbindgen.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rs-drive-verify-c-binding/src/lib.rs b/packages/rs-drive-verify-c-binding/src/lib.rs new file mode 100644 index 00000000000..ebf54d86c68 --- /dev/null +++ b/packages/rs-drive-verify-c-binding/src/lib.rs @@ -0,0 +1,780 @@ +mod types; +mod util; + +use crate::types::{ + IdentityIdBalanceMap, IdentityIdVerificationResult, IdentityVerificationResult, + MultipleIdentityBalanceVerificationResult, MultipleIdentityIdVerificationResult, + MultipleIdentityVerificationResult, PublicKeyHash, PublicKeyHashIdentityIdMap, + PublicKeyHashIdentityMap, +}; +use crate::util::{build_c_identity_struct, extract_vector_from_pointer, vec_to_pointer}; +use drive::drive::verify::Identity as DppIdentity; +use drive::drive::verify::{AssetLockProof as DppAssetLockProof}; +use drive::drive::Drive; +use std::collections::BTreeMap; +use std::{slice}; + +#[no_mangle] +pub unsafe extern "C" fn verify_full_identity_by_public_key_hash( + proof_array: *const u8, + proof_len: usize, + public_key_hash: *const PublicKeyHash, +) -> *const IdentityVerificationResult { + let proof = unsafe { slice::from_raw_parts(proof_array, proof_len) }; + let public_key_hash = unsafe { std::ptr::read(public_key_hash) }; + + let verification_result = + Drive::verify_full_identity_by_public_key_hash(proof, public_key_hash); + + match verification_result { + Ok((root_hash, maybe_identity)) => Box::into_raw(Box::from(IdentityVerificationResult { + root_hash: Box::into_raw(Box::from(root_hash)), + is_valid: true, + has_identity: maybe_identity.is_some(), + identity: build_c_identity_struct(maybe_identity), + })), + Err(..) => Box::into_raw(Box::from(IdentityVerificationResult::default())), + } +} + +#[no_mangle] +pub unsafe extern "C" fn verify_full_identities_by_public_key_hashes( + proof_array: *const u8, + proof_len: usize, + public_key_hashes_c: *const *const u8, + public_key_hash_count: usize, +) -> *const MultipleIdentityVerificationResult { + let proof = unsafe { slice::from_raw_parts(proof_array, proof_len) }; + let public_key_hashes = + extract_vector_from_pointer::<[u8; 20]>(public_key_hashes_c, public_key_hash_count); + + let verification_result = Drive::verify_full_identities_by_public_key_hashes::< + BTreeMap>, + >(proof, &public_key_hashes); + + match verification_result { + Ok((root_hash, hash_identity_map)) => { + let mut pkhash_identity_map_as_vec: Vec<*const PublicKeyHashIdentityMap> = Vec::new(); + for (public_key_hash, maybe_identity) in hash_identity_map { + pkhash_identity_map_as_vec.push(Box::into_raw(Box::from( + PublicKeyHashIdentityMap { + public_key_hash: vec_to_pointer(public_key_hash.to_vec()), + public_key_hash_length: public_key_hash.len(), + has_identity: maybe_identity.is_some(), + identity: build_c_identity_struct(maybe_identity), + }, + ))); + } + + Box::into_raw(Box::from(MultipleIdentityVerificationResult { + is_valid: true, + root_hash: Box::into_raw(Box::from(root_hash)), + map_size: pkhash_identity_map_as_vec.len(), + public_key_hash_identity_map: vec_to_pointer(pkhash_identity_map_as_vec), + })) + } + Err(..) => Box::into_raw(Box::from(MultipleIdentityVerificationResult::default())), + } +} + +#[no_mangle] +pub unsafe extern "C" fn verify_full_identity_by_identity_id( + proof_array: *const u8, + proof_len: usize, + is_proof_subset: bool, + identity_id: *const [u8; 32], +) -> *const IdentityVerificationResult { + let proof = unsafe { slice::from_raw_parts(proof_array, proof_len) }; + let identity_id: [u8; 32] = unsafe { std::ptr::read(identity_id) }; + let verification_result = + Drive::verify_full_identity_by_identity_id(proof, is_proof_subset, identity_id); + match verification_result { + Ok((root_hash, maybe_identity)) => Box::into_raw(Box::from(IdentityVerificationResult { + root_hash: Box::into_raw(Box::from(root_hash)), + is_valid: true, + has_identity: maybe_identity.is_some(), + identity: build_c_identity_struct(maybe_identity), + })), + Err(..) => Box::into_raw(Box::from(IdentityVerificationResult::default())), + } +} + +#[no_mangle] +pub unsafe extern "C" fn verify_identity_id_by_public_key_hash( + proof_array: *const u8, + proof_len: usize, + is_proof_subset: bool, + public_key_hash: *const PublicKeyHash, +) -> *const IdentityIdVerificationResult { + let proof = unsafe { slice::from_raw_parts(proof_array, proof_len) }; + let public_key_hash = unsafe { std::ptr::read(public_key_hash) }; + + let verification_result = + Drive::verify_identity_id_by_public_key_hash(proof, is_proof_subset, public_key_hash); + + match verification_result { + Ok((root_hash, maybe_identity_id)) => { + Box::into_raw(Box::from(IdentityIdVerificationResult { + root_hash: Box::into_raw(Box::from(root_hash)), + is_valid: true, + has_identity_id: maybe_identity_id.is_some(), + identity_id: maybe_identity_id + .map(|id| vec_to_pointer(id.to_vec())) + .unwrap_or(std::ptr::null()), + id_size: maybe_identity_id.map(|id| id.len()).unwrap_or(0), + })) + } + Err(..) => Box::into_raw(Box::from(IdentityIdVerificationResult::default())), + } +} + +#[no_mangle] +pub unsafe extern "C" fn verify_identity_balances_by_identity_ids( + proof_array: *const u8, + proof_len: usize, + is_proof_subset: bool, + identity_ids: *const *const u8, + id_size: usize, +) -> *const MultipleIdentityBalanceVerificationResult { + let proof = unsafe { slice::from_raw_parts(proof_array, proof_len) }; + let identity_ids = extract_vector_from_pointer::<[u8; 32]>(identity_ids, id_size); + + let verification_result = Drive::verify_identity_balances_for_identity_ids::< + Vec<([u8; 32], Option)>, + >(proof, is_proof_subset, identity_ids.as_slice()); + + match verification_result { + Ok((root_hash, identity_id_balance_map)) => { + let mut identity_id_balance_map_as_vec: Vec<*const IdentityIdBalanceMap> = Vec::new(); + for (identity_id, maybe_balance) in identity_id_balance_map { + identity_id_balance_map_as_vec.push(Box::into_raw(Box::from( + IdentityIdBalanceMap { + identity_id: vec_to_pointer(identity_id.to_vec()), + id_size: 32, + has_balance: maybe_balance.is_some(), + balance: maybe_balance.unwrap_or(0), + }, + ))); + } + Box::into_raw(Box::from(MultipleIdentityBalanceVerificationResult { + is_valid: true, + root_hash: Box::into_raw(Box::from(root_hash)), + map_size: identity_id_balance_map_as_vec.len(), + identity_id_balance_map: vec_to_pointer(identity_id_balance_map_as_vec), + })) + } + Err(..) => Box::into_raw(Box::from( + MultipleIdentityBalanceVerificationResult::default(), + )), + } +} + +#[no_mangle] +pub unsafe extern "C" fn verify_identity_ids_by_public_key_hashes( + proof_array: *const u8, + proof_len: usize, + is_proof_subset: bool, + public_key_hashes_c: *const *const u8, + public_key_hash_count: usize, +) -> *const MultipleIdentityIdVerificationResult { + let proof = unsafe { slice::from_raw_parts(proof_array, proof_len) }; + let public_key_hashes = + extract_vector_from_pointer::<[u8; 20]>(public_key_hashes_c, public_key_hash_count); + + let verification_result = Drive::verify_identity_ids_by_public_key_hashes::< + Vec<(PublicKeyHash, Option<[u8; 32]>)>, + >(proof, is_proof_subset, public_key_hashes.as_slice()); + + match verification_result { + Ok((root_hash, public_key_hash_identity_id_map)) => { + let mut pkhash_identity_id_map_as_vec: Vec<*const PublicKeyHashIdentityIdMap> = + Vec::new(); + for (public_key_hash, maybe_identity_id) in &public_key_hash_identity_id_map { + pkhash_identity_id_map_as_vec.push(Box::into_raw(Box::from( + PublicKeyHashIdentityIdMap { + public_key_hash: vec_to_pointer(public_key_hash.to_vec()), + public_key_hash_size: public_key_hash.len(), + has_identity_id: maybe_identity_id.is_some(), + identity_id: maybe_identity_id + .map(|id| vec_to_pointer(id.to_vec())) + .unwrap_or(std::ptr::null()), + id_size: maybe_identity_id.map(|id| id.len()).unwrap_or(0), + }, + ))) + } + Box::into_raw(Box::from(MultipleIdentityIdVerificationResult { + is_valid: true, + root_hash: Box::into_raw(Box::from(root_hash)), + map_size: public_key_hash_identity_id_map.len(), + public_key_hash_identity_id_map: vec_to_pointer(pkhash_identity_id_map_as_vec), + })) + } + Err(..) => Box::into_raw(Box::from(MultipleIdentityIdVerificationResult::default())), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use drive::drive::Drive; + use std::collections::BTreeMap; + + fn single_identity_proof() -> &'static [u8] { + &[ + 6, 0, 1, 0, 166, 3, 1, 74, 117, 207, 63, 83, 94, 129, 196, 104, 15, 129, 55, 162, 32, + 141, 188, 178, 101, 47, 253, 126, 113, 91, 212, 41, 12, 197, 197, 96, 178, 204, 97, 2, + 207, 190, 5, 53, 189, 45, 239, 229, 134, 184, 99, 185, 204, 185, 45, 13, 102, 251, 43, + 129, 13, 115, 14, 123, 162, 203, 126, 47, 179, 2, 97, 59, 16, 4, 1, 24, 0, 24, 2, 1, + 20, 174, 227, 2, 114, 8, 150, 187, 168, 55, 220, 243, 242, 214, 116, 245, 70, 253, 37, + 73, 111, 0, 202, 53, 154, 161, 178, 3, 46, 49, 88, 174, 94, 92, 72, 159, 125, 70, 114, + 47, 41, 100, 74, 21, 225, 207, 124, 57, 53, 179, 6, 6, 222, 246, 17, 4, 1, 32, 0, 36, + 2, 1, 32, 62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, + 67, 22, 47, 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162, 0, 160, 213, 164, + 246, 65, 134, 99, 70, 133, 21, 205, 117, 24, 155, 227, 225, 3, 75, 191, 169, 161, 128, + 126, 184, 29, 150, 75, 167, 68, 42, 11, 30, 16, 1, 105, 147, 24, 56, 86, 71, 7, 219, + 241, 30, 144, 160, 89, 253, 125, 212, 83, 204, 126, 104, 173, 183, 210, 194, 55, 91, + 174, 83, 86, 102, 100, 231, 17, 2, 86, 112, 117, 44, 195, 216, 131, 32, 10, 117, 152, + 182, 92, 215, 75, 65, 167, 96, 204, 11, 229, 124, 218, 85, 54, 241, 95, 3, 200, 120, + 58, 168, 16, 1, 195, 54, 53, 19, 110, 80, 46, 154, 197, 36, 75, 21, 162, 10, 117, 126, + 7, 89, 206, 10, 144, 130, 60, 211, 127, 137, 63, 106, 73, 85, 109, 38, 4, 1, 96, 0, 45, + 4, 1, 32, 62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, + 67, 22, 47, 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162, 253, 28, 177, 42, + 91, 38, 20, 0, 0, 0, 251, 189, 59, 224, 151, 231, 240, 125, 86, 25, 221, 105, 231, 118, + 120, 132, 209, 22, 249, 90, 233, 165, 252, 219, 101, 30, 113, 114, 121, 2, 204, 30, 16, + 1, 30, 12, 20, 67, 208, 146, 95, 120, 17, 50, 244, 197, 6, 116, 114, 2, 219, 255, 163, + 202, 61, 237, 77, 35, 135, 212, 183, 228, 14, 3, 3, 227, 17, 17, 2, 1, 24, 127, 3, 20, + 68, 99, 161, 169, 148, 213, 4, 14, 105, 192, 144, 182, 152, 93, 122, 242, 149, 191, + 209, 26, 0, 35, 0, 32, 62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, 77, + 93, 70, 216, 67, 22, 47, 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162, 0, 2, + 158, 111, 45, 51, 177, 88, 0, 48, 227, 182, 3, 14, 60, 37, 1, 106, 183, 37, 57, 101, + 104, 37, 86, 5, 157, 204, 36, 59, 117, 199, 250, 109, 16, 1, 224, 159, 136, 205, 9, + 204, 89, 93, 82, 72, 146, 179, 230, 66, 185, 57, 242, 130, 121, 149, 96, 87, 3, 196, + 156, 134, 31, 101, 48, 1, 213, 225, 17, 1, 1, 32, 77, 4, 32, 62, 171, 130, 51, 233, 19, + 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, 67, 22, 47, 39, 25, 156, 146, 35, 108, + 99, 133, 34, 187, 243, 162, 0, 9, 2, 1, 1, 2, 1, 3, 0, 0, 0, 127, 174, 137, 184, 136, + 178, 63, 79, 189, 174, 210, 251, 153, 10, 31, 66, 114, 122, 239, 91, 210, 168, 185, 31, + 140, 185, 112, 87, 9, 9, 171, 57, 1, 32, 62, 171, 130, 51, 233, 19, 45, 191, 194, 183, + 0, 171, 182, 77, 93, 70, 216, 67, 22, 47, 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, + 243, 162, 127, 3, 1, 0, 0, 11, 0, 8, 0, 0, 0, 0, 0, 0, 0, 16, 0, 4, 1, 1, 0, 5, 2, 1, + 1, 1, 0, 214, 81, 34, 23, 150, 181, 32, 106, 91, 150, 120, 164, 217, 153, 93, 81, 157, + 139, 158, 117, 232, 125, 133, 229, 126, 255, 185, 31, 130, 162, 62, 141, 16, 2, 188, + 248, 74, 136, 44, 15, 114, 221, 13, 82, 10, 105, 84, 179, 225, 136, 127, 165, 91, 125, + 198, 118, 53, 180, 69, 22, 133, 107, 49, 253, 32, 168, 16, 1, 70, 25, 123, 162, 209, + 216, 154, 230, 95, 14, 56, 180, 32, 113, 102, 210, 182, 213, 32, 20, 204, 112, 76, 86, + 112, 130, 191, 29, 189, 101, 249, 223, 17, 2, 1, 1, 148, 1, 3, 1, 0, 0, 30, 0, 27, 0, + 0, 1, 2, 0, 20, 68, 99, 161, 169, 148, 213, 4, 14, 105, 192, 144, 182, 152, 93, 122, + 242, 149, 191, 209, 26, 0, 0, 3, 1, 1, 0, 58, 0, 55, 1, 0, 2, 1, 0, 48, 151, 57, 136, + 178, 145, 253, 27, 202, 134, 217, 6, 114, 62, 51, 91, 223, 19, 211, 235, 186, 223, 234, + 49, 221, 22, 75, 60, 103, 44, 22, 218, 114, 175, 142, 110, 223, 192, 186, 196, 75, 146, + 184, 197, 54, 215, 8, 220, 51, 0, 0, 16, 3, 1, 2, 0, 43, 0, 40, 2, 0, 3, 0, 0, 33, 3, + 96, 218, 121, 197, 137, 149, 228, 236, 136, 81, 42, 249, 164, 68, 12, 164, 242, 215, + 191, 232, 66, 64, 225, 126, 255, 196, 221, 140, 233, 64, 51, 162, 0, 0, 17, 2, 1, 96, + 79, 4, 32, 62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, + 67, 22, 47, 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162, 0, 11, 3, 253, 28, + 177, 42, 91, 38, 20, 0, 0, 0, 104, 243, 24, 41, 234, 236, 2, 247, 229, 237, 218, 218, + 18, 157, 73, 129, 169, 155, 218, 14, 92, 15, 212, 239, 243, 194, 62, 175, 194, 199, + 154, 2, + ] + } + + fn multiple_identity_proof() -> &'static [u8] { + &[ + 6, 0, 1, 0, 166, 3, 1, 74, 117, 207, 63, 83, 94, 129, 196, 104, 15, 129, 55, 162, 32, + 141, 188, 178, 101, 47, 253, 126, 113, 91, 212, 41, 12, 197, 197, 96, 178, 204, 97, 2, + 207, 190, 5, 53, 189, 45, 239, 229, 134, 184, 99, 185, 204, 185, 45, 13, 102, 251, 43, + 129, 13, 115, 14, 123, 162, 203, 126, 47, 179, 2, 97, 59, 16, 4, 1, 24, 0, 24, 2, 1, + 20, 94, 14, 73, 216, 8, 173, 33, 208, 29, 7, 221, 121, 154, 117, 189, 27, 71, 39, 136, + 167, 0, 140, 16, 170, 76, 29, 25, 226, 231, 228, 47, 224, 177, 167, 246, 217, 61, 76, + 11, 105, 146, 239, 99, 234, 152, 92, 22, 68, 124, 173, 164, 98, 149, 17, 4, 1, 32, 0, + 36, 2, 1, 32, 53, 168, 221, 106, 101, 237, 66, 153, 18, 210, 219, 5, 68, 98, 199, 232, + 192, 17, 150, 90, 167, 106, 118, 53, 106, 105, 180, 200, 129, 128, 140, 48, 0, 186, 86, + 207, 177, 216, 126, 244, 120, 87, 246, 177, 205, 127, 185, 24, 64, 111, 213, 15, 129, + 150, 102, 25, 119, 125, 212, 193, 181, 149, 161, 162, 110, 16, 1, 105, 147, 24, 56, 86, + 71, 7, 219, 241, 30, 144, 160, 89, 253, 125, 212, 83, 204, 126, 104, 173, 183, 210, + 194, 55, 91, 174, 83, 86, 102, 100, 231, 17, 2, 86, 112, 117, 44, 195, 216, 131, 32, + 10, 117, 152, 182, 92, 215, 75, 65, 167, 96, 204, 11, 229, 124, 218, 85, 54, 241, 95, + 3, 200, 120, 58, 168, 16, 1, 195, 54, 53, 19, 110, 80, 46, 154, 197, 36, 75, 21, 162, + 10, 117, 126, 7, 89, 206, 10, 144, 130, 60, 211, 127, 137, 63, 106, 73, 85, 109, 38, 4, + 1, 96, 0, 45, 4, 1, 32, 53, 168, 221, 106, 101, 237, 66, 153, 18, 210, 219, 5, 68, 98, + 199, 232, 192, 17, 150, 90, 167, 106, 118, 53, 106, 105, 180, 200, 129, 128, 140, 48, + 253, 186, 253, 104, 51, 174, 183, 0, 0, 0, 18, 178, 127, 74, 10, 124, 253, 6, 227, 56, + 123, 51, 165, 188, 166, 104, 41, 83, 81, 46, 33, 98, 26, 233, 207, 109, 99, 61, 144, + 65, 119, 25, 16, 1, 30, 12, 20, 67, 208, 146, 95, 120, 17, 50, 244, 197, 6, 116, 114, + 2, 219, 255, 163, 202, 61, 237, 77, 35, 135, 212, 183, 228, 14, 3, 3, 227, 17, 17, 2, + 1, 24, 179, 8, 1, 50, 201, 211, 88, 68, 213, 206, 42, 142, 15, 55, 124, 238, 35, 193, + 67, 165, 51, 150, 7, 61, 234, 134, 196, 148, 184, 107, 164, 196, 175, 11, 57, 3, 20, + 31, 8, 21, 38, 154, 252, 1, 45, 228, 66, 96, 206, 178, 138, 68, 150, 211, 24, 65, 132, + 0, 35, 0, 32, 15, 126, 159, 152, 150, 254, 206, 186, 180, 193, 157, 65, 233, 215, 241, + 108, 23, 39, 205, 99, 217, 219, 86, 244, 213, 176, 67, 34, 242, 146, 86, 203, 0, 16, 1, + 104, 87, 110, 36, 82, 30, 3, 186, 75, 98, 73, 18, 187, 7, 131, 55, 103, 200, 17, 2, 49, + 11, 135, 216, 234, 28, 175, 39, 149, 198, 143, 146, 17, 2, 232, 183, 235, 55, 111, 15, + 121, 147, 186, 223, 147, 151, 31, 105, 11, 232, 164, 143, 9, 219, 7, 17, 240, 82, 162, + 237, 72, 71, 20, 151, 185, 208, 16, 3, 20, 68, 99, 161, 169, 148, 213, 4, 14, 105, 192, + 144, 182, 152, 93, 122, 242, 149, 191, 209, 26, 0, 35, 0, 32, 62, 171, 130, 51, 233, + 19, 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, 67, 22, 47, 39, 25, 156, 146, 35, + 108, 99, 133, 34, 187, 243, 162, 0, 2, 37, 75, 240, 169, 144, 190, 183, 33, 194, 31, + 33, 232, 219, 171, 80, 227, 60, 217, 207, 9, 97, 143, 194, 124, 159, 116, 80, 198, 115, + 81, 106, 238, 16, 1, 198, 173, 254, 8, 24, 9, 33, 142, 224, 116, 97, 249, 95, 83, 206, + 108, 228, 98, 236, 55, 159, 151, 167, 31, 27, 228, 15, 114, 24, 203, 80, 175, 17, 17, + 3, 20, 94, 14, 73, 216, 8, 173, 33, 208, 29, 7, 221, 121, 154, 117, 189, 27, 71, 39, + 136, 167, 0, 35, 0, 32, 53, 168, 221, 106, 101, 237, 66, 153, 18, 210, 219, 5, 68, 98, + 199, 232, 192, 17, 150, 90, 167, 106, 118, 53, 106, 105, 180, 200, 129, 128, 140, 48, + 0, 16, 1, 164, 179, 25, 152, 212, 124, 48, 227, 144, 244, 250, 86, 242, 143, 25, 198, + 47, 17, 79, 23, 167, 4, 210, 156, 86, 226, 139, 111, 219, 71, 241, 1, 3, 20, 103, 137, + 42, 243, 144, 205, 43, 118, 83, 169, 24, 199, 182, 146, 200, 91, 135, 180, 77, 50, 0, + 35, 0, 32, 57, 148, 116, 246, 83, 186, 107, 123, 56, 57, 164, 62, 208, 250, 53, 255, + 205, 221, 94, 250, 29, 14, 112, 130, 148, 27, 214, 36, 12, 33, 159, 128, 0, 16, 1, 235, + 77, 169, 119, 51, 138, 61, 164, 32, 78, 170, 172, 12, 136, 86, 189, 253, 81, 217, 178, + 92, 238, 240, 75, 64, 187, 56, 239, 247, 154, 177, 16, 17, 2, 31, 34, 16, 36, 41, 219, + 225, 188, 12, 167, 20, 132, 123, 8, 24, 125, 154, 135, 76, 196, 51, 41, 170, 167, 150, + 71, 251, 154, 160, 131, 77, 105, 16, 3, 20, 154, 6, 31, 49, 115, 76, 95, 95, 11, 17, + 154, 183, 45, 67, 60, 154, 241, 51, 211, 166, 0, 35, 0, 32, 232, 241, 234, 234, 48, 58, + 184, 92, 10, 32, 220, 110, 128, 186, 85, 30, 63, 171, 43, 133, 112, 35, 25, 161, 34, + 229, 80, 168, 115, 74, 228, 51, 0, 2, 100, 76, 96, 30, 103, 105, 33, 136, 207, 90, 151, + 92, 34, 7, 202, 186, 137, 157, 153, 241, 187, 212, 182, 46, 95, 232, 86, 133, 11, 157, + 114, 134, 16, 3, 20, 165, 73, 33, 187, 41, 182, 126, 49, 137, 142, 254, 188, 41, 242, + 65, 177, 174, 250, 77, 202, 0, 35, 0, 32, 127, 61, 253, 44, 203, 5, 79, 65, 14, 231, + 126, 176, 46, 231, 180, 234, 150, 7, 149, 216, 151, 70, 205, 194, 38, 221, 216, 153, + 230, 172, 78, 81, 0, 16, 2, 158, 111, 45, 51, 177, 88, 0, 48, 227, 182, 3, 14, 60, 37, + 1, 106, 183, 37, 57, 101, 104, 37, 86, 5, 157, 204, 36, 59, 117, 199, 250, 109, 3, 20, + 179, 191, 206, 71, 141, 233, 111, 227, 12, 211, 113, 59, 248, 140, 231, 114, 134, 135, + 218, 138, 0, 35, 0, 32, 168, 155, 161, 167, 178, 189, 91, 153, 252, 27, 238, 224, 90, + 202, 85, 135, 174, 60, 251, 70, 40, 210, 160, 53, 143, 32, 130, 82, 183, 232, 190, 64, + 0, 17, 17, 17, 3, 20, 187, 61, 240, 37, 227, 47, 217, 13, 31, 238, 231, 220, 164, 184, + 51, 33, 198, 131, 41, 45, 0, 35, 0, 32, 3, 193, 33, 26, 169, 210, 98, 57, 198, 220, + 161, 230, 203, 29, 187, 130, 102, 254, 43, 149, 0, 248, 105, 156, 132, 170, 144, 214, + 35, 247, 177, 211, 0, 16, 1, 238, 8, 71, 128, 91, 20, 91, 95, 181, 0, 177, 57, 254, 18, + 118, 126, 230, 129, 252, 49, 10, 33, 214, 233, 129, 70, 25, 223, 81, 135, 71, 8, 2, + 160, 222, 53, 47, 230, 118, 125, 167, 191, 76, 51, 186, 125, 45, 168, 219, 4, 64, 69, + 120, 53, 211, 194, 153, 36, 115, 33, 14, 2, 182, 49, 44, 16, 1, 224, 159, 136, 205, 9, + 204, 89, 93, 82, 72, 146, 179, 230, 66, 185, 57, 242, 130, 121, 149, 96, 87, 3, 196, + 156, 134, 31, 101, 48, 1, 213, 225, 2, 154, 86, 60, 152, 61, 32, 37, 32, 193, 169, 79, + 76, 107, 169, 151, 80, 55, 52, 80, 170, 249, 220, 178, 166, 46, 245, 14, 152, 119, 100, + 96, 67, 16, 3, 20, 237, 115, 138, 170, 221, 117, 209, 103, 127, 239, 236, 202, 221, 3, + 63, 18, 108, 254, 231, 106, 0, 35, 0, 32, 151, 172, 124, 81, 243, 147, 225, 5, 188, + 204, 9, 152, 150, 127, 129, 13, 246, 19, 141, 93, 239, 8, 214, 194, 123, 127, 177, 23, + 144, 211, 189, 239, 0, 3, 20, 251, 217, 218, 165, 153, 61, 229, 106, 46, 67, 70, 183, + 199, 47, 245, 88, 94, 255, 250, 171, 0, 35, 0, 32, 27, 36, 15, 203, 78, 99, 46, 120, + 86, 191, 245, 139, 120, 77, 206, 188, 25, 57, 140, 115, 78, 198, 0, 196, 101, 253, 233, + 90, 35, 102, 219, 235, 0, 16, 17, 17, 17, 17, 1, 1, 32, 139, 6, 4, 32, 3, 193, 33, 26, + 169, 210, 98, 57, 198, 220, 161, 230, 203, 29, 187, 130, 102, 254, 43, 149, 0, 248, + 105, 156, 132, 170, 144, 214, 35, 247, 177, 211, 0, 9, 2, 1, 1, 2, 1, 3, 0, 0, 0, 108, + 75, 252, 242, 35, 205, 79, 229, 193, 202, 200, 46, 26, 158, 44, 115, 235, 14, 127, 52, + 206, 186, 189, 215, 99, 14, 36, 203, 25, 47, 151, 88, 4, 32, 15, 126, 159, 152, 150, + 254, 206, 186, 180, 193, 157, 65, 233, 215, 241, 108, 23, 39, 205, 99, 217, 219, 86, + 244, 213, 176, 67, 34, 242, 146, 86, 203, 0, 9, 2, 1, 1, 2, 1, 3, 0, 0, 0, 128, 218, + 98, 172, 184, 196, 159, 144, 29, 107, 248, 74, 42, 42, 241, 84, 49, 230, 158, 41, 6, + 154, 191, 141, 2, 242, 193, 19, 198, 9, 155, 166, 16, 4, 32, 27, 36, 15, 203, 78, 99, + 46, 120, 86, 191, 245, 139, 120, 77, 206, 188, 25, 57, 140, 115, 78, 198, 0, 196, 101, + 253, 233, 90, 35, 102, 219, 235, 0, 9, 2, 1, 1, 2, 1, 3, 0, 0, 0, 81, 162, 48, 73, 239, + 188, 222, 58, 14, 156, 133, 234, 122, 240, 90, 40, 212, 222, 49, 249, 10, 228, 74, 7, + 197, 250, 24, 9, 1, 40, 35, 112, 17, 4, 32, 53, 168, 221, 106, 101, 237, 66, 153, 18, + 210, 219, 5, 68, 98, 199, 232, 192, 17, 150, 90, 167, 106, 118, 53, 106, 105, 180, 200, + 129, 128, 140, 48, 0, 9, 2, 1, 1, 2, 1, 3, 0, 0, 0, 42, 57, 7, 97, 185, 151, 137, 122, + 254, 81, 84, 12, 57, 223, 235, 92, 120, 208, 7, 129, 165, 71, 210, 184, 59, 30, 114, + 37, 152, 148, 222, 165, 16, 4, 32, 57, 148, 116, 246, 83, 186, 107, 123, 56, 57, 164, + 62, 208, 250, 53, 255, 205, 221, 94, 250, 29, 14, 112, 130, 148, 27, 214, 36, 12, 33, + 159, 128, 0, 9, 2, 1, 1, 2, 1, 3, 0, 0, 0, 179, 242, 143, 156, 194, 109, 249, 14, 164, + 158, 19, 227, 205, 151, 192, 29, 119, 46, 157, 102, 9, 69, 62, 145, 212, 54, 158, 247, + 142, 56, 128, 160, 4, 32, 62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, + 77, 93, 70, 216, 67, 22, 47, 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162, 0, + 9, 2, 1, 1, 2, 1, 3, 0, 0, 0, 127, 174, 137, 184, 136, 178, 63, 79, 189, 174, 210, 251, + 153, 10, 31, 66, 114, 122, 239, 91, 210, 168, 185, 31, 140, 185, 112, 87, 9, 9, 171, + 57, 16, 4, 32, 127, 61, 253, 44, 203, 5, 79, 65, 14, 231, 126, 176, 46, 231, 180, 234, + 150, 7, 149, 216, 151, 70, 205, 194, 38, 221, 216, 153, 230, 172, 78, 81, 0, 9, 2, 1, + 1, 2, 1, 3, 0, 0, 0, 29, 100, 163, 249, 39, 11, 248, 184, 16, 67, 5, 186, 118, 130, + 148, 114, 243, 170, 194, 182, 255, 242, 11, 152, 172, 16, 54, 30, 197, 71, 63, 187, 17, + 4, 32, 151, 172, 124, 81, 243, 147, 225, 5, 188, 204, 9, 152, 150, 127, 129, 13, 246, + 19, 141, 93, 239, 8, 214, 194, 123, 127, 177, 23, 144, 211, 189, 239, 0, 9, 2, 1, 1, 2, + 1, 3, 0, 0, 0, 173, 183, 101, 112, 214, 79, 137, 101, 6, 134, 223, 88, 25, 65, 78, 94, + 66, 207, 126, 237, 171, 36, 96, 90, 166, 60, 75, 142, 38, 233, 14, 218, 16, 4, 32, 168, + 155, 161, 167, 178, 189, 91, 153, 252, 27, 238, 224, 90, 202, 85, 135, 174, 60, 251, + 70, 40, 210, 160, 53, 143, 32, 130, 82, 183, 232, 190, 64, 0, 9, 2, 1, 1, 2, 1, 3, 0, + 0, 0, 165, 168, 83, 4, 22, 217, 70, 37, 33, 182, 253, 147, 39, 35, 216, 151, 22, 132, + 180, 98, 14, 66, 84, 202, 240, 156, 117, 40, 158, 14, 100, 112, 4, 32, 232, 241, 234, + 234, 48, 58, 184, 92, 10, 32, 220, 110, 128, 186, 85, 30, 63, 171, 43, 133, 112, 35, + 25, 161, 34, 229, 80, 168, 115, 74, 228, 51, 0, 9, 2, 1, 1, 2, 1, 3, 0, 0, 0, 250, 29, + 144, 127, 150, 124, 72, 41, 42, 90, 243, 212, 195, 170, 212, 53, 194, 238, 146, 55, 17, + 150, 20, 214, 18, 174, 227, 180, 245, 46, 54, 20, 17, 17, 17, 1, 32, 3, 193, 33, 26, + 169, 210, 98, 57, 198, 220, 161, 230, 203, 29, 187, 130, 102, 254, 43, 149, 0, 248, + 105, 156, 132, 170, 144, 214, 35, 247, 177, 211, 127, 3, 1, 0, 0, 11, 0, 8, 0, 0, 0, 0, + 0, 0, 0, 61, 0, 4, 1, 1, 0, 5, 2, 1, 1, 1, 0, 114, 204, 69, 18, 112, 198, 19, 132, 211, + 88, 247, 212, 17, 53, 183, 135, 136, 1, 24, 48, 48, 26, 105, 123, 151, 163, 113, 76, + 32, 58, 54, 220, 16, 2, 20, 16, 91, 223, 25, 20, 145, 182, 114, 73, 211, 33, 243, 217, + 190, 189, 248, 44, 154, 51, 149, 254, 243, 54, 198, 11, 55, 1, 175, 5, 147, 231, 16, 1, + 70, 25, 123, 162, 209, 216, 154, 230, 95, 14, 56, 180, 32, 113, 102, 210, 182, 213, 32, + 20, 204, 112, 76, 86, 112, 130, 191, 29, 189, 101, 249, 223, 17, 2, 1, 1, 163, 1, 3, 1, + 0, 0, 30, 0, 27, 0, 0, 2, 3, 0, 20, 187, 61, 240, 37, 227, 47, 217, 13, 31, 238, 231, + 220, 164, 184, 51, 33, 198, 131, 41, 45, 0, 0, 3, 1, 1, 0, 58, 0, 55, 1, 0, 1, 1, 0, + 48, 165, 253, 2, 201, 109, 95, 96, 235, 84, 177, 91, 4, 58, 132, 237, 128, 160, 175, + 128, 78, 255, 74, 43, 254, 161, 252, 159, 237, 50, 50, 50, 199, 171, 18, 7, 35, 104, 9, + 126, 85, 100, 57, 208, 138, 160, 166, 134, 108, 0, 0, 16, 3, 1, 2, 0, 58, 0, 55, 2, 0, + 0, 1, 0, 48, 133, 255, 0, 230, 51, 147, 103, 211, 227, 30, 39, 203, 195, 60, 19, 195, + 205, 12, 110, 151, 58, 91, 144, 46, 118, 102, 141, 122, 109, 175, 131, 193, 41, 37, + 124, 199, 249, 204, 53, 225, 192, 104, 154, 109, 240, 58, 137, 29, 0, 0, 17, 1, 32, 15, + 126, 159, 152, 150, 254, 206, 186, 180, 193, 157, 65, 233, 215, 241, 108, 23, 39, 205, + 99, 217, 219, 86, 244, 213, 176, 67, 34, 242, 146, 86, 203, 127, 3, 1, 0, 0, 11, 0, 8, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 4, 1, 1, 0, 5, 2, 1, 1, 1, 0, 120, 58, 98, 103, 109, 191, + 253, 1, 47, 147, 67, 239, 10, 247, 28, 27, 128, 12, 218, 25, 128, 22, 137, 223, 183, + 226, 55, 44, 204, 195, 237, 157, 16, 2, 127, 14, 148, 229, 76, 99, 255, 220, 211, 211, + 217, 1, 122, 99, 232, 47, 153, 132, 173, 229, 196, 250, 165, 157, 36, 121, 193, 16, 7, + 147, 37, 36, 16, 1, 70, 25, 123, 162, 209, 216, 154, 230, 95, 14, 56, 180, 32, 113, + 102, 210, 182, 213, 32, 20, 204, 112, 76, 86, 112, 130, 191, 29, 189, 101, 249, 223, + 17, 2, 1, 1, 120, 3, 1, 0, 0, 43, 0, 40, 0, 0, 2, 0, 0, 33, 3, 254, 101, 252, 220, 254, + 36, 45, 194, 228, 61, 101, 66, 116, 236, 156, 225, 187, 188, 157, 213, 161, 200, 137, + 69, 238, 239, 24, 204, 147, 21, 31, 127, 0, 0, 3, 1, 1, 0, 30, 0, 27, 1, 0, 1, 3, 0, + 20, 201, 79, 70, 207, 56, 184, 56, 98, 153, 15, 120, 44, 132, 172, 188, 23, 141, 123, + 2, 218, 0, 0, 16, 3, 1, 2, 0, 30, 0, 27, 2, 0, 2, 3, 0, 20, 38, 211, 135, 217, 136, 72, + 98, 249, 97, 96, 221, 89, 202, 89, 107, 220, 232, 45, 167, 70, 0, 0, 17, 1, 32, 27, 36, + 15, 203, 78, 99, 46, 120, 86, 191, 245, 139, 120, 77, 206, 188, 25, 57, 140, 115, 78, + 198, 0, 196, 101, 253, 233, 90, 35, 102, 219, 235, 127, 3, 1, 0, 0, 11, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 96, 0, 4, 1, 1, 0, 5, 2, 1, 1, 1, 0, 202, 58, 16, 234, 179, 184, 137, 70, + 91, 186, 81, 188, 83, 84, 19, 26, 238, 16, 68, 229, 16, 217, 237, 74, 112, 104, 209, + 24, 28, 125, 191, 205, 16, 2, 150, 38, 236, 43, 78, 136, 97, 198, 117, 178, 11, 196, + 69, 99, 51, 216, 196, 31, 208, 192, 185, 201, 240, 183, 128, 71, 198, 99, 78, 186, 184, + 239, 16, 1, 70, 25, 123, 162, 209, 216, 154, 230, 95, 14, 56, 180, 32, 113, 102, 210, + 182, 213, 32, 20, 204, 112, 76, 86, 112, 130, 191, 29, 189, 101, 249, 223, 17, 2, 1, 1, + 133, 1, 3, 1, 0, 0, 30, 0, 27, 0, 0, 3, 2, 0, 20, 251, 217, 218, 165, 153, 61, 229, + 106, 46, 67, 70, 183, 199, 47, 245, 88, 94, 255, 250, 171, 0, 0, 3, 1, 1, 0, 43, 0, 40, + 1, 0, 3, 0, 0, 33, 3, 253, 201, 64, 62, 182, 240, 5, 219, 112, 14, 120, 65, 98, 127, + 79, 146, 231, 198, 93, 22, 115, 132, 205, 87, 164, 244, 228, 101, 131, 194, 26, 254, 0, + 0, 16, 3, 1, 2, 0, 43, 0, 40, 2, 0, 0, 0, 0, 33, 3, 112, 52, 70, 247, 124, 141, 177, + 251, 172, 111, 52, 34, 200, 224, 69, 9, 138, 219, 102, 44, 11, 98, 10, 21, 184, 196, + 217, 236, 210, 163, 222, 250, 0, 0, 17, 1, 32, 53, 168, 221, 106, 101, 237, 66, 153, + 18, 210, 219, 5, 68, 98, 199, 232, 192, 17, 150, 90, 167, 106, 118, 53, 106, 105, 180, + 200, 129, 128, 140, 48, 127, 3, 1, 0, 0, 11, 0, 8, 0, 0, 0, 0, 0, 0, 0, 66, 0, 4, 1, 1, + 0, 5, 2, 1, 1, 1, 0, 215, 243, 151, 168, 22, 242, 63, 50, 233, 166, 205, 42, 181, 176, + 61, 91, 109, 48, 116, 44, 240, 181, 133, 23, 242, 118, 217, 247, 92, 28, 77, 97, 16, 2, + 191, 213, 104, 106, 224, 210, 167, 104, 76, 47, 110, 211, 167, 65, 154, 67, 106, 83, + 137, 175, 201, 168, 74, 27, 178, 42, 29, 236, 223, 167, 98, 93, 16, 1, 70, 25, 123, + 162, 209, 216, 154, 230, 95, 14, 56, 180, 32, 113, 102, 210, 182, 213, 32, 20, 204, + 112, 76, 86, 112, 130, 191, 29, 189, 101, 249, 223, 17, 2, 1, 1, 163, 1, 3, 1, 0, 0, + 30, 0, 27, 0, 0, 2, 3, 0, 20, 94, 14, 73, 216, 8, 173, 33, 208, 29, 7, 221, 121, 154, + 117, 189, 27, 71, 39, 136, 167, 0, 0, 3, 1, 1, 0, 58, 0, 55, 1, 0, 0, 1, 0, 48, 151, + 200, 216, 16, 45, 33, 104, 24, 198, 147, 220, 70, 97, 76, 233, 36, 43, 142, 84, 224, + 90, 143, 241, 245, 32, 163, 105, 75, 148, 129, 9, 29, 146, 144, 107, 19, 185, 178, 118, + 43, 18, 126, 228, 240, 126, 145, 17, 158, 0, 0, 16, 3, 1, 2, 0, 58, 0, 55, 2, 0, 3, 1, + 0, 48, 137, 73, 201, 109, 218, 132, 146, 104, 4, 78, 23, 109, 189, 186, 69, 143, 181, + 222, 172, 129, 233, 145, 135, 147, 189, 184, 55, 245, 175, 238, 12, 36, 150, 165, 147, + 13, 70, 209, 254, 55, 206, 83, 108, 190, 248, 233, 91, 180, 0, 0, 17, 1, 32, 57, 148, + 116, 246, 83, 186, 107, 123, 56, 57, 164, 62, 208, 250, 53, 255, 205, 221, 94, 250, 29, + 14, 112, 130, 148, 27, 214, 36, 12, 33, 159, 128, 127, 3, 1, 0, 0, 11, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 12, 0, 4, 1, 1, 0, 5, 2, 1, 1, 1, 0, 169, 64, 58, 164, 8, 175, 53, 210, + 103, 152, 13, 255, 241, 112, 109, 112, 165, 155, 109, 186, 134, 125, 11, 86, 140, 168, + 197, 183, 117, 96, 214, 122, 16, 2, 118, 203, 254, 130, 45, 123, 159, 104, 99, 249, + 160, 107, 102, 128, 151, 69, 142, 18, 63, 243, 133, 227, 28, 41, 216, 48, 208, 63, 17, + 72, 151, 58, 16, 1, 70, 25, 123, 162, 209, 216, 154, 230, 95, 14, 56, 180, 32, 113, + 102, 210, 182, 213, 32, 20, 204, 112, 76, 86, 112, 130, 191, 29, 189, 101, 249, 223, + 17, 2, 1, 1, 135, 1, 3, 1, 0, 0, 58, 0, 55, 0, 0, 0, 1, 0, 48, 183, 157, 76, 170, 134, + 95, 132, 32, 113, 36, 195, 211, 4, 67, 3, 114, 243, 157, 124, 24, 162, 55, 223, 58, + 113, 227, 196, 251, 123, 169, 171, 152, 22, 67, 154, 128, 155, 235, 134, 6, 195, 187, + 82, 213, 58, 83, 100, 89, 0, 0, 3, 1, 1, 0, 30, 0, 27, 1, 0, 3, 2, 0, 20, 100, 6, 165, + 8, 43, 35, 19, 64, 114, 109, 76, 208, 222, 36, 82, 188, 115, 163, 48, 3, 0, 0, 16, 3, + 1, 2, 0, 30, 0, 27, 2, 0, 1, 3, 0, 20, 74, 199, 180, 47, 82, 78, 29, 27, 34, 9, 143, + 133, 173, 252, 167, 82, 96, 14, 249, 160, 0, 0, 17, 1, 32, 62, 171, 130, 51, 233, 19, + 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, 67, 22, 47, 39, 25, 156, 146, 35, 108, + 99, 133, 34, 187, 243, 162, 127, 3, 1, 0, 0, 11, 0, 8, 0, 0, 0, 0, 0, 0, 0, 16, 0, 4, + 1, 1, 0, 5, 2, 1, 1, 1, 0, 214, 81, 34, 23, 150, 181, 32, 106, 91, 150, 120, 164, 217, + 153, 93, 81, 157, 139, 158, 117, 232, 125, 133, 229, 126, 255, 185, 31, 130, 162, 62, + 141, 16, 2, 188, 248, 74, 136, 44, 15, 114, 221, 13, 82, 10, 105, 84, 179, 225, 136, + 127, 165, 91, 125, 198, 118, 53, 180, 69, 22, 133, 107, 49, 253, 32, 168, 16, 1, 70, + 25, 123, 162, 209, 216, 154, 230, 95, 14, 56, 180, 32, 113, 102, 210, 182, 213, 32, 20, + 204, 112, 76, 86, 112, 130, 191, 29, 189, 101, 249, 223, 17, 2, 1, 1, 148, 1, 3, 1, 0, + 0, 30, 0, 27, 0, 0, 1, 2, 0, 20, 68, 99, 161, 169, 148, 213, 4, 14, 105, 192, 144, 182, + 152, 93, 122, 242, 149, 191, 209, 26, 0, 0, 3, 1, 1, 0, 58, 0, 55, 1, 0, 2, 1, 0, 48, + 151, 57, 136, 178, 145, 253, 27, 202, 134, 217, 6, 114, 62, 51, 91, 223, 19, 211, 235, + 186, 223, 234, 49, 221, 22, 75, 60, 103, 44, 22, 218, 114, 175, 142, 110, 223, 192, + 186, 196, 75, 146, 184, 197, 54, 215, 8, 220, 51, 0, 0, 16, 3, 1, 2, 0, 43, 0, 40, 2, + 0, 3, 0, 0, 33, 3, 96, 218, 121, 197, 137, 149, 228, 236, 136, 81, 42, 249, 164, 68, + 12, 164, 242, 215, 191, 232, 66, 64, 225, 126, 255, 196, 221, 140, 233, 64, 51, 162, 0, + 0, 17, 1, 32, 127, 61, 253, 44, 203, 5, 79, 65, 14, 231, 126, 176, 46, 231, 180, 234, + 150, 7, 149, 216, 151, 70, 205, 194, 38, 221, 216, 153, 230, 172, 78, 81, 127, 3, 1, 0, + 0, 11, 0, 8, 0, 0, 0, 0, 0, 0, 0, 88, 0, 4, 1, 1, 0, 5, 2, 1, 1, 1, 0, 217, 95, 249, + 131, 219, 147, 62, 220, 103, 84, 135, 166, 244, 227, 136, 252, 242, 219, 89, 49, 58, + 234, 181, 244, 89, 145, 167, 242, 71, 23, 116, 71, 16, 2, 53, 95, 152, 195, 143, 216, + 124, 165, 119, 94, 94, 69, 18, 67, 235, 17, 48, 14, 217, 31, 201, 80, 234, 32, 76, 10, + 116, 185, 161, 153, 26, 37, 16, 1, 70, 25, 123, 162, 209, 216, 154, 230, 95, 14, 56, + 180, 32, 113, 102, 210, 182, 213, 32, 20, 204, 112, 76, 86, 112, 130, 191, 29, 189, + 101, 249, 223, 17, 2, 1, 1, 163, 1, 3, 1, 0, 0, 58, 0, 55, 0, 0, 3, 1, 0, 48, 179, 66, + 56, 68, 186, 232, 165, 145, 187, 251, 67, 123, 85, 86, 107, 93, 97, 229, 78, 230, 79, + 147, 53, 27, 10, 59, 157, 75, 115, 20, 69, 210, 92, 227, 103, 247, 174, 223, 203, 50, + 189, 60, 209, 67, 8, 165, 76, 245, 0, 0, 3, 1, 1, 0, 58, 0, 55, 1, 0, 1, 1, 0, 48, 161, + 84, 193, 144, 130, 172, 107, 95, 236, 114, 184, 31, 100, 136, 85, 15, 236, 113, 73, + 213, 47, 102, 180, 70, 57, 21, 166, 17, 121, 196, 241, 248, 80, 125, 54, 102, 20, 180, + 84, 218, 191, 44, 148, 34, 53, 202, 173, 1, 0, 0, 16, 3, 1, 2, 0, 30, 0, 27, 2, 0, 3, + 3, 0, 20, 10, 140, 20, 116, 92, 152, 47, 159, 220, 67, 170, 152, 92, 2, 177, 229, 191, + 246, 196, 3, 0, 0, 17, 1, 32, 151, 172, 124, 81, 243, 147, 225, 5, 188, 204, 9, 152, + 150, 127, 129, 13, 246, 19, 141, 93, 239, 8, 214, 194, 123, 127, 177, 23, 144, 211, + 189, 239, 127, 3, 1, 0, 0, 11, 0, 8, 0, 0, 0, 0, 0, 0, 0, 98, 0, 4, 1, 1, 0, 5, 2, 1, + 1, 1, 0, 65, 44, 30, 125, 226, 57, 77, 205, 0, 146, 35, 235, 140, 58, 36, 227, 75, 147, + 167, 196, 141, 240, 187, 134, 73, 145, 96, 163, 30, 169, 219, 219, 16, 2, 24, 5, 144, + 238, 195, 51, 151, 3, 70, 117, 243, 121, 207, 23, 246, 44, 14, 119, 209, 119, 36, 160, + 50, 56, 220, 211, 242, 22, 164, 188, 149, 9, 16, 1, 70, 25, 123, 162, 209, 216, 154, + 230, 95, 14, 56, 180, 32, 113, 102, 210, 182, 213, 32, 20, 204, 112, 76, 86, 112, 130, + 191, 29, 189, 101, 249, 223, 17, 2, 1, 1, 148, 1, 3, 1, 0, 0, 58, 0, 55, 0, 0, 2, 1, 0, + 48, 175, 164, 55, 10, 165, 164, 138, 178, 243, 171, 81, 12, 202, 186, 59, 109, 140, + 245, 23, 82, 48, 69, 7, 230, 163, 65, 196, 228, 255, 106, 167, 192, 118, 16, 165, 3, + 180, 47, 71, 152, 52, 176, 50, 210, 93, 209, 96, 89, 0, 0, 3, 1, 1, 0, 43, 0, 40, 1, 0, + 3, 0, 0, 33, 3, 169, 88, 76, 69, 128, 209, 101, 210, 116, 75, 164, 154, 112, 71, 38, + 83, 145, 91, 253, 190, 196, 190, 244, 113, 226, 108, 228, 193, 201, 230, 198, 171, 0, + 0, 16, 3, 1, 2, 0, 30, 0, 27, 2, 0, 1, 2, 0, 20, 69, 208, 69, 88, 162, 107, 140, 160, + 75, 72, 105, 87, 200, 171, 245, 171, 242, 78, 199, 111, 0, 0, 17, 1, 32, 168, 155, 161, + 167, 178, 189, 91, 153, 252, 27, 238, 224, 90, 202, 85, 135, 174, 60, 251, 70, 40, 210, + 160, 53, 143, 32, 130, 82, 183, 232, 190, 64, 127, 3, 1, 0, 0, 11, 0, 8, 0, 0, 0, 0, 0, + 0, 0, 93, 0, 4, 1, 1, 0, 5, 2, 1, 1, 1, 0, 241, 64, 24, 106, 107, 212, 19, 165, 8, 20, + 219, 72, 75, 0, 57, 140, 46, 126, 109, 169, 251, 226, 203, 83, 103, 40, 232, 128, 222, + 183, 80, 96, 16, 2, 119, 103, 247, 95, 222, 212, 127, 148, 166, 248, 28, 103, 29, 68, + 139, 237, 219, 108, 39, 39, 241, 242, 9, 186, 1, 91, 248, 222, 115, 49, 193, 60, 16, 1, + 70, 25, 123, 162, 209, 216, 154, 230, 95, 14, 56, 180, 32, 113, 102, 210, 182, 213, 32, + 20, 204, 112, 76, 86, 112, 130, 191, 29, 189, 101, 249, 223, 17, 2, 1, 1, 133, 1, 3, 1, + 0, 0, 43, 0, 40, 0, 0, 0, 0, 0, 33, 2, 238, 189, 47, 145, 129, 138, 35, 78, 24, 121, + 248, 165, 86, 82, 241, 229, 36, 25, 173, 22, 139, 143, 39, 185, 27, 230, 183, 153, 88, + 247, 165, 81, 0, 0, 3, 1, 1, 0, 43, 0, 40, 1, 0, 2, 0, 0, 33, 2, 20, 169, 29, 252, 179, + 103, 24, 32, 154, 94, 231, 156, 41, 0, 41, 184, 73, 241, 206, 47, 238, 246, 88, 90, 59, + 63, 163, 125, 4, 251, 98, 183, 0, 0, 16, 3, 1, 2, 0, 30, 0, 27, 2, 0, 3, 2, 0, 20, 78, + 228, 144, 8, 65, 96, 252, 139, 30, 115, 54, 29, 90, 76, 5, 91, 238, 231, 125, 140, 0, + 0, 17, 1, 32, 232, 241, 234, 234, 48, 58, 184, 92, 10, 32, 220, 110, 128, 186, 85, 30, + 63, 171, 43, 133, 112, 35, 25, 161, 34, 229, 80, 168, 115, 74, 228, 51, 127, 3, 1, 0, + 0, 11, 0, 8, 0, 0, 0, 0, 0, 0, 0, 79, 0, 4, 1, 1, 0, 5, 2, 1, 1, 1, 0, 235, 144, 179, + 198, 217, 165, 71, 227, 184, 161, 17, 31, 98, 30, 13, 190, 91, 252, 104, 168, 25, 99, + 113, 212, 151, 203, 41, 18, 250, 128, 157, 0, 16, 2, 171, 128, 206, 123, 108, 164, 135, + 93, 188, 125, 193, 240, 217, 2, 85, 22, 40, 201, 123, 35, 131, 194, 125, 4, 83, 141, + 70, 169, 125, 60, 173, 67, 16, 1, 70, 25, 123, 162, 209, 216, 154, 230, 95, 14, 56, + 180, 32, 113, 102, 210, 182, 213, 32, 20, 204, 112, 76, 86, 112, 130, 191, 29, 189, + 101, 249, 223, 17, 2, 1, 1, 135, 1, 3, 1, 0, 0, 30, 0, 27, 0, 0, 2, 3, 0, 20, 154, 6, + 31, 49, 115, 76, 95, 95, 11, 17, 154, 183, 45, 67, 60, 154, 241, 51, 211, 166, 0, 0, 3, + 1, 1, 0, 58, 0, 55, 1, 0, 1, 1, 0, 48, 150, 225, 252, 99, 25, 52, 161, 74, 205, 49, 63, + 242, 140, 162, 156, 158, 155, 67, 24, 27, 141, 242, 147, 134, 112, 43, 26, 45, 101, + 167, 204, 130, 54, 131, 245, 115, 62, 41, 111, 180, 12, 115, 100, 139, 201, 203, 246, + 37, 0, 0, 16, 3, 1, 2, 0, 30, 0, 27, 2, 0, 1, 2, 0, 20, 116, 241, 133, 170, 82, 127, + 49, 32, 36, 66, 210, 8, 205, 178, 144, 95, 167, 20, 3, 41, 0, 0, 17, 2, 1, 96, 159, 6, + 4, 32, 3, 193, 33, 26, 169, 210, 98, 57, 198, 220, 161, 230, 203, 29, 187, 130, 102, + 254, 43, 149, 0, 248, 105, 156, 132, 170, 144, 214, 35, 247, 177, 211, 0, 11, 3, 253, + 164, 217, 58, 64, 48, 23, 0, 0, 0, 149, 132, 12, 107, 224, 86, 237, 61, 25, 157, 237, + 245, 38, 90, 93, 61, 175, 209, 149, 170, 76, 181, 76, 162, 105, 67, 247, 224, 146, 165, + 240, 105, 4, 32, 15, 126, 159, 152, 150, 254, 206, 186, 180, 193, 157, 65, 233, 215, + 241, 108, 23, 39, 205, 99, 217, 219, 86, 244, 213, 176, 67, 34, 242, 146, 86, 203, 0, + 11, 3, 253, 98, 19, 45, 65, 7, 24, 0, 0, 0, 185, 222, 201, 37, 149, 229, 234, 187, 109, + 224, 69, 120, 40, 39, 189, 152, 182, 11, 146, 82, 40, 126, 236, 15, 142, 52, 80, 234, + 124, 89, 97, 155, 16, 4, 32, 27, 36, 15, 203, 78, 99, 46, 120, 86, 191, 245, 139, 120, + 77, 206, 188, 25, 57, 140, 115, 78, 198, 0, 196, 101, 253, 233, 90, 35, 102, 219, 235, + 0, 11, 3, 253, 142, 41, 118, 29, 84, 5, 0, 0, 0, 56, 13, 26, 140, 179, 81, 27, 62, 207, + 119, 10, 29, 129, 244, 12, 41, 49, 130, 210, 156, 240, 87, 73, 98, 219, 80, 196, 207, + 182, 38, 253, 183, 17, 4, 32, 53, 168, 221, 106, 101, 237, 66, 153, 18, 210, 219, 5, + 68, 98, 199, 232, 192, 17, 150, 90, 167, 106, 118, 53, 106, 105, 180, 200, 129, 128, + 140, 48, 0, 11, 3, 253, 98, 185, 191, 225, 53, 25, 0, 0, 0, 46, 239, 167, 82, 56, 101, + 128, 195, 16, 132, 181, 79, 33, 25, 151, 60, 206, 246, 172, 146, 252, 56, 96, 126, 134, + 9, 232, 150, 201, 153, 76, 62, 16, 4, 32, 57, 148, 116, 246, 83, 186, 107, 123, 56, 57, + 164, 62, 208, 250, 53, 255, 205, 221, 94, 250, 29, 14, 112, 130, 148, 27, 214, 36, 12, + 33, 159, 128, 0, 11, 3, 253, 208, 161, 183, 238, 226, 20, 0, 0, 0, 34, 113, 182, 72, + 168, 146, 91, 140, 113, 117, 67, 69, 58, 89, 163, 162, 10, 60, 82, 206, 155, 62, 95, + 199, 147, 152, 60, 100, 249, 246, 254, 160, 4, 32, 62, 171, 130, 51, 233, 19, 45, 191, + 194, 183, 0, 171, 182, 77, 93, 70, 216, 67, 22, 47, 39, 25, 156, 146, 35, 108, 99, 133, + 34, 187, 243, 162, 0, 11, 3, 253, 28, 177, 42, 91, 38, 20, 0, 0, 0, 104, 243, 24, 41, + 234, 236, 2, 247, 229, 237, 218, 218, 18, 157, 73, 129, 169, 155, 218, 14, 92, 15, 212, + 239, 243, 194, 62, 175, 194, 199, 154, 2, 16, 4, 32, 127, 61, 253, 44, 203, 5, 79, 65, + 14, 231, 126, 176, 46, 231, 180, 234, 150, 7, 149, 216, 151, 70, 205, 194, 38, 221, + 216, 153, 230, 172, 78, 81, 0, 11, 3, 253, 88, 107, 162, 93, 31, 26, 0, 0, 0, 94, 59, + 56, 166, 217, 190, 222, 37, 14, 208, 182, 18, 208, 25, 21, 167, 129, 130, 238, 24, 216, + 25, 208, 125, 67, 174, 146, 87, 40, 180, 45, 45, 17, 4, 32, 151, 172, 124, 81, 243, + 147, 225, 5, 188, 204, 9, 152, 150, 127, 129, 13, 246, 19, 141, 93, 239, 8, 214, 194, + 123, 127, 177, 23, 144, 211, 189, 239, 0, 11, 3, 253, 18, 11, 81, 244, 234, 16, 0, 0, + 0, 190, 56, 11, 19, 207, 215, 20, 147, 50, 229, 172, 129, 138, 232, 77, 49, 228, 190, + 17, 155, 192, 235, 215, 23, 71, 86, 48, 246, 243, 139, 110, 144, 16, 4, 32, 168, 155, + 161, 167, 178, 189, 91, 153, 252, 27, 238, 224, 90, 202, 85, 135, 174, 60, 251, 70, 40, + 210, 160, 53, 143, 32, 130, 82, 183, 232, 190, 64, 0, 11, 3, 253, 80, 93, 108, 146, + 111, 13, 0, 0, 0, 149, 0, 32, 76, 105, 140, 193, 47, 201, 103, 116, 163, 79, 119, 65, + 92, 55, 55, 111, 241, 123, 73, 40, 56, 228, 20, 215, 116, 181, 183, 190, 193, 4, 32, + 232, 241, 234, 234, 48, 58, 184, 92, 10, 32, 220, 110, 128, 186, 85, 30, 63, 171, 43, + 133, 112, 35, 25, 161, 34, 229, 80, 168, 115, 74, 228, 51, 0, 11, 3, 253, 30, 7, 137, + 132, 105, 8, 0, 0, 0, 100, 217, 149, 244, 181, 182, 44, 72, 10, 4, 241, 184, 251, 76, + 122, 48, 182, 7, 241, 45, 164, 171, 195, 87, 153, 62, 231, 80, 91, 225, 155, 38, 17, + 17, 17, + ] + } + + #[test] + fn test_verify_full_identity_by_public_key_hash() { + let proof: &[u8] = single_identity_proof(); + let key_hash: PublicKeyHash = [ + 68, 99, 161, 169, 148, 213, 4, 14, 105, 192, 144, 182, 152, 93, 122, 242, 149, 191, + 209, 26, + ]; + let (_root_hash, proved_identity) = + Drive::verify_full_identity_by_public_key_hash(proof, key_hash).expect("should verify"); + // verify part of the identity, make sure it's the correct one + assert!(proved_identity.is_some()); + let proved_identity = proved_identity.unwrap(); + assert_eq!(proved_identity.protocol_version, 1); + assert_eq!(proved_identity.public_keys.len(), 3); + assert_eq!(proved_identity.balance, 11077485418638); + } + + #[test] + fn multiple_identity_proofs() { + let proof = multiple_identity_proof(); + let key_hashes: &[PublicKeyHash] = &[ + [ + 31, 8, 21, 38, 154, 252, 1, 45, 228, 66, 96, 206, 178, 138, 68, 150, 211, 24, 65, + 132, + ], + [ + 68, 99, 161, 169, 148, 213, 4, 14, 105, 192, 144, 182, 152, 93, 122, 242, 149, 191, + 209, 26, + ], + [ + 94, 14, 73, 216, 8, 173, 33, 208, 29, 7, 221, 121, 154, 117, 189, 27, 71, 39, 136, + 167, + ], + [ + 103, 137, 42, 243, 144, 205, 43, 118, 83, 169, 24, 199, 182, 146, 200, 91, 135, + 180, 77, 50, + ], + [ + 154, 6, 31, 49, 115, 76, 95, 95, 11, 17, 154, 183, 45, 67, 60, 154, 241, 51, 211, + 166, + ], + [ + 165, 73, 33, 187, 41, 182, 126, 49, 137, 142, 254, 188, 41, 242, 65, 177, 174, 250, + 77, 202, + ], + [ + 179, 191, 206, 71, 141, 233, 111, 227, 12, 211, 113, 59, 248, 140, 231, 114, 134, + 135, 218, 138, + ], + [ + 187, 61, 240, 37, 227, 47, 217, 13, 31, 238, 231, 220, 164, 184, 51, 33, 198, 131, + 41, 45, + ], + [ + 237, 115, 138, 170, 221, 117, 209, 103, 127, 239, 236, 202, 221, 3, 63, 18, 108, + 254, 231, 106, + ], + [ + 251, 217, 218, 165, 153, 61, 229, 106, 46, 67, 70, 183, 199, 47, 245, 88, 94, 255, + 250, 171, + ], + ]; + + let (_, proved_identities): ([u8; 32], BTreeMap>) = + Drive::verify_full_identities_by_public_key_hashes(proof, key_hashes) + .expect("expect that this be verified"); + assert_eq!(proved_identities.len(), 10); + dbg!(proved_identities.values()); + } + + #[test] + fn verify_full_identity_by_identity_id() { + let proof = single_identity_proof(); + let identity_id: [u8; 32] = [ + 62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, 67, 22, 47, + 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162, + ]; + dbg!(hex::encode(identity_id)); + let (_root_hash, maybe_identity) = + Drive::verify_full_identity_by_identity_id(proof, true, identity_id) + .expect("verification failed"); + let identity = maybe_identity.expect("couldn't get identity"); + assert_eq!(identity.protocol_version, 1); + assert_eq!(identity.public_keys.len(), 3); + assert_eq!(identity.balance, 11077485418638); + } + + #[test] + fn verify_identity_id_by_public_key_hash() { + let proof = multiple_identity_proof(); + let public_key_hash: PublicKeyHash = [ + 31, 8, 21, 38, 154, 252, 1, 45, 228, 66, 96, 206, 178, 138, 68, 150, 211, 24, 65, 132, + ]; + let (_root_hash, maybe_identity_id) = + Drive::verify_identity_id_by_public_key_hash(proof, true, public_key_hash) + .expect("should verify"); + let expected_identity_id: [u8; 32] = [ + 15, 126, 159, 152, 150, 254, 206, 186, 180, 193, 157, 65, 233, 215, 241, 108, 23, 39, + 205, 99, 217, 219, 86, 244, 213, 176, 67, 34, 242, 146, 86, 203, + ]; + let actual_identity_id = maybe_identity_id.expect("should have identity id"); + assert_eq!(expected_identity_id, actual_identity_id); + } + + #[ignore] + #[test] + fn verify_identity_balance_by_identity_id() { + // TODO: given identity proof is a subset proof but this verify function expects non-subset proof + let proof = single_identity_proof(); + let identity_id: [u8; 32] = [ + 62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, 67, 22, 47, + 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162, + ]; + let (_root_hash, maybe_balance) = + Drive::verify_identity_balance_for_identity_id(proof, identity_id) + .expect("should verify"); + let actual_balance = maybe_balance.expect("should have balance"); + assert_eq!(actual_balance, 11077485418639); + } + + #[test] + fn verify_identity_balances_by_identity_ids() { + let proof = multiple_identity_proof(); + let identity_ids: &[[u8; 32]] = &[ + [ + 62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, 67, 22, + 47, 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162, + ], + [ + 151, 172, 124, 81, 243, 147, 225, 5, 188, 204, 9, 152, 150, 127, 129, 13, 246, 19, + 141, 93, 239, 8, 214, 194, 123, 127, 177, 23, 144, 211, 189, 239, + ], + ]; + let (_, balances): (RootHash, Vec<([u8; 32], Option)>) = + Drive::verify_identity_balances_for_identity_ids(proof, true, identity_ids) + .expect("should verify"); + assert_eq!(balances.len(), 2); + assert_eq!(balances[0].1.unwrap(), 11077485418638); + assert_eq!(balances[1].1.unwrap(), 9300653671817); + } + + #[test] + fn verify_identity_ids_by_public_key_hashes() { + let proof = multiple_identity_proof(); + let public_key_hashes: &[PublicKeyHash] = &[ + [ + 31, 8, 21, 38, 154, 252, 1, 45, 228, 66, 96, 206, 178, 138, 68, 150, 211, 24, 65, + 132, + ], + [ + 68, 99, 161, 169, 148, 213, 4, 14, 105, 192, 144, 182, 152, 93, 122, 242, 149, 191, + 209, 26, + ], + [ + 94, 14, 73, 216, 8, 173, 33, 208, 29, 7, 221, 121, 154, 117, 189, 27, 71, 39, 136, + 167, + ], + ]; + let (_, ids): (RootHash, Vec<([u8; 20], Option<[u8; 32]>)>) = + Drive::verify_identity_ids_by_public_key_hashes(proof, true, public_key_hashes) + .expect("should verify"); + assert_eq!(ids.len(), 3); + assert_eq!( + ids[0].1.unwrap(), + [ + 15, 126, 159, 152, 150, 254, 206, 186, 180, 193, 157, 65, 233, 215, 241, 108, 23, + 39, 205, 99, 217, 219, 86, 244, 213, 176, 67, 34, 242, 146, 86, 203 + ] + ); + assert_eq!( + ids[1].1.unwrap(), + [ + 62, 171, 130, 51, 233, 19, 45, 191, 194, 183, 0, 171, 182, 77, 93, 70, 216, 67, 22, + 47, 39, 25, 156, 146, 35, 108, 99, 133, 34, 187, 243, 162, + ] + ); + assert_eq!( + ids[2].1.unwrap(), + [ + 53, 168, 221, 106, 101, 237, 66, 153, 18, 210, 219, 5, 68, 98, 199, 232, 192, 17, + 150, 90, 167, 106, 118, 53, 106, 105, 180, 200, 129, 128, 140, 48, + ] + ); + } +} diff --git a/packages/rs-drive-verify-c-binding/src/types.rs b/packages/rs-drive-verify-c-binding/src/types.rs new file mode 100644 index 00000000000..66b42773b73 --- /dev/null +++ b/packages/rs-drive-verify-c-binding/src/types.rs @@ -0,0 +1,203 @@ +/// Type alias for a public key hash +pub(crate) type PublicKeyHash = [u8; 20]; + +/// Represents proof verification result + full identity +#[repr(C)] +pub struct IdentityVerificationResult { + pub is_valid: bool, + pub root_hash: *const [u8; 32], + pub has_identity: bool, + pub identity: *const Identity, +} + +impl Default for IdentityVerificationResult { + fn default() -> Self { + Self { + is_valid: false, + root_hash: std::ptr::null(), + has_identity: false, + identity: std::ptr::null(), + } + } +} + +/// Represent proof verification result + multiple identities +#[repr(C)] +pub struct MultipleIdentityVerificationResult { + pub is_valid: bool, + pub root_hash: *const [u8; 32], + pub public_key_hash_identity_map: *const *const PublicKeyHashIdentityMap, + pub map_size: usize, +} + +impl Default for MultipleIdentityVerificationResult { + fn default() -> Self { + Self { + is_valid: false, + root_hash: std::ptr::null(), + public_key_hash_identity_map: std::ptr::null(), + map_size: 0, + } + } +} + +/// Maps a public key hash to an identity +#[repr(C)] +pub struct PublicKeyHashIdentityMap { + pub public_key_hash: *const u8, + pub public_key_hash_length: usize, + pub has_identity: bool, + pub identity: *const Identity, +} + +/// Represents proof verification result + identity id result +#[repr(C)] +pub struct IdentityIdVerificationResult { + pub is_valid: bool, + pub root_hash: *const [u8; 32], + pub has_identity_id: bool, + pub identity_id: *const u8, + pub id_size: usize, +} + +impl Default for IdentityIdVerificationResult { + fn default() -> Self { + Self { + is_valid: false, + root_hash: std::ptr::null(), + has_identity_id: false, + identity_id: std::ptr::null(), + id_size: 0, + } + } +} + +/// Represent proof verification result + multiple identity balance result +#[repr(C)] +pub struct MultipleIdentityBalanceVerificationResult { + pub is_valid: bool, + pub root_hash: *const [u8; 32], + pub identity_id_balance_map: *const *const IdentityIdBalanceMap, + pub map_size: usize, +} + +impl Default for MultipleIdentityBalanceVerificationResult { + fn default() -> Self { + Self { + is_valid: true, + root_hash: std::ptr::null(), + identity_id_balance_map: std::ptr::null(), + map_size: 0, + } + } +} + +/// Maps from an identity id to an optional balance +#[repr(C)] +pub struct IdentityIdBalanceMap { + pub identity_id: *const u8, + pub id_size: usize, + pub has_balance: bool, + pub balance: u64, +} + +/// Represents proof verification result + multiple identity id result +#[repr(C)] +pub struct MultipleIdentityIdVerificationResult { + pub is_valid: bool, + pub root_hash: *const [u8; 32], + pub map_size: usize, + pub public_key_hash_identity_id_map: *const *const PublicKeyHashIdentityIdMap, +} + +impl Default for MultipleIdentityIdVerificationResult { + fn default() -> Self { + Self { + is_valid: true, + root_hash: std::ptr::null(), + map_size: 0, + public_key_hash_identity_id_map: std::ptr::null(), + } + } +} + +/// Maps a public key hash to an identity id +#[repr(C)] +pub struct PublicKeyHashIdentityIdMap { + pub public_key_hash: *const u8, + pub public_key_hash_size: usize, + pub has_identity_id: bool, + pub identity_id: *const u8, + pub id_size: usize, +} + +/// Represents an identity +#[repr(C)] +pub struct Identity { + pub protocol_version: u32, + pub id: *const [u8; 32], + pub public_keys_count: usize, + pub public_keys: *const *const IdPublicKeyMap, + pub balance: u64, + pub revision: u64, + pub has_asset_lock_proof: bool, + pub asset_lock_proof: *const AssetLockProof, + pub has_metadata: bool, + pub meta_data: *const MetaData, +} + +/// Maps a key id to a public key +#[repr(C)] +pub struct IdPublicKeyMap { + pub key: u32, + pub public_key: *const IdentityPublicKey, +} + +/// Represents an identity public key +#[repr(C)] +pub struct IdentityPublicKey { + pub id: u32, + + // AUTHENTICATION = 0, + // ENCRYPTION = 1, + // DECRYPTION = 2, + // WITHDRAW = 3 + pub purpose: u8, + + // MASTER = 0, + // CRITICAL = 1, + // HIGH = 2, + // MEDIUM = 3 + pub security_level: u8, + + // ECDSA_SECP256K1 = 0, + // BLS312_381 = 1, + // ECDSA_HASH160 = 2, + // BIP13_SCRIPT_HASH = 3 + pub key_type: u8, + + pub read_only: bool, + pub data_length: usize, + pub data: *const u8, + pub has_disabled_at: bool, + pub disabled_at: u64, +} + +/// Represents an asset lock proof +// TODO: add the actual asset lock types +#[repr(C)] +pub struct AssetLockProof { + pub is_instant: bool, + // pub instant_asset_lock_proof: *const InstantAssetLocKProof, + pub is_chain: bool, + // pub chain_asset_lock_proof: *const ChainAssetLockProof, +} + +/// Represents identity metat data +#[repr(C)] +pub struct MetaData { + pub block_height: u64, + pub core_chain_locked_height: u64, + pub time_ms: u64, + pub protocol_version: u32, +} diff --git a/packages/rs-drive-verify-c-binding/src/util.rs b/packages/rs-drive-verify-c-binding/src/util.rs new file mode 100644 index 00000000000..3d8cf531096 --- /dev/null +++ b/packages/rs-drive-verify-c-binding/src/util.rs @@ -0,0 +1,99 @@ +use crate::types::{ + AssetLockProof, IdPublicKeyMap, Identity, IdentityPublicKey, MetaData, +}; +use crate::{DppAssetLockProof, DppIdentity}; +use std::{mem, slice}; + +pub(crate) fn build_c_identity_struct(maybe_identity: Option) -> *mut Identity { + maybe_identity + .map(|identity| { + Box::into_raw(Box::from(Identity { + protocol_version: identity.protocol_version, + id: Box::into_raw(Box::from(identity.id.0 .0)), + public_keys_count: identity.public_keys.len(), + public_keys: build_c_public_keys_struct(&identity), + balance: identity.balance, + revision: identity.revision, + has_asset_lock_proof: identity.asset_lock_proof.is_some(), + asset_lock_proof: build_c_asset_lock_proof_struct(&identity), + has_metadata: identity.metadata.is_some(), + meta_data: build_c_metadata_struct(&identity), + })) + }) + .unwrap_or(std::ptr::null_mut()) +} + +pub(crate) fn build_c_public_keys_struct(identity: &DppIdentity) -> *const *const IdPublicKeyMap { + let mut id_public_key_map_as_vec: Vec<*const IdPublicKeyMap> = vec![]; + for (key_id, identity_public_key) in &identity.public_keys { + id_public_key_map_as_vec.push(Box::into_raw(Box::from(IdPublicKeyMap { + key: *key_id, + public_key: Box::into_raw(Box::from(IdentityPublicKey { + id: identity_public_key.id, + purpose: identity_public_key.purpose as u8, + security_level: identity_public_key.security_level as u8, + key_type: identity_public_key.key_type as u8, + read_only: identity_public_key.read_only, + data_length: identity_public_key.data.len(), + data: vec_to_pointer(identity_public_key.data.to_vec()), + has_disabled_at: identity_public_key.disabled_at.is_some(), + disabled_at: identity_public_key.disabled_at.unwrap_or(0), + })), + }))) + } + let pointer = id_public_key_map_as_vec.as_ptr(); + mem::forget(id_public_key_map_as_vec); + pointer +} + +pub(crate) fn build_c_asset_lock_proof_struct(identity: &DppIdentity) -> *const AssetLockProof { + let asset_lock_proof = &identity.asset_lock_proof; + if let Some(asset_lock_proof) = asset_lock_proof { + // TODO: construct the actual asset lock proofs + match asset_lock_proof { + DppAssetLockProof::Instant(..) => Box::into_raw(Box::from(AssetLockProof { + is_chain: false, + is_instant: true, + })), + DppAssetLockProof::Chain(..) => Box::into_raw(Box::from(AssetLockProof { + is_chain: true, + is_instant: false, + })), + } + } else { + Box::into_raw(Box::from(AssetLockProof { + is_chain: false, + is_instant: false, + })) + } +} + +pub(crate) fn build_c_metadata_struct(identity: &DppIdentity) -> *const MetaData { + let metadata = &identity.metadata; + if let Some(metadata) = metadata { + Box::into_raw(Box::from(MetaData { + block_height: metadata.block_height, + core_chain_locked_height: metadata.core_chain_locked_height, + time_ms: metadata.time_ms, + protocol_version: metadata.protocol_version, + })) + } else { + std::ptr::null() + } +} + +pub(crate) fn extract_vector_from_pointer(ptr: *const *const u8, count: usize) -> Vec { + let mut result = Vec::new(); + let inner_pointers = unsafe { slice::from_raw_parts(ptr, count) }; + for i in 0..count { + let inner_item: T = unsafe { std::ptr::read(inner_pointers[i] as *const T) }; + result.push(inner_item); + } + result +} + +pub(crate) fn vec_to_pointer(a: Vec) -> *const T { + let ptr = a.as_ptr(); + mem::forget(a); + ptr +} diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 40bc6df39ec..02d54ce94ce 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -40,6 +40,7 @@ mockall= { version ="0.11", optional = true } git = "https://github.com/dashpay/grovedb" branch = "develop" optional = true +default-features = false [dependencies.storage] git = "https://github.com/dashpay/grovedb" diff --git a/packages/rs-drive/src/drive/verify/mod.rs b/packages/rs-drive/src/drive/verify/mod.rs index 17839255002..e2084ed5186 100644 --- a/packages/rs-drive/src/drive/verify/mod.rs +++ b/packages/rs-drive/src/drive/verify/mod.rs @@ -8,7 +8,7 @@ use crate::error::proof::ProofError; use crate::error::Error; use dpp::identifier::Identifier; use dpp::identity::{IdentityPublicKey, KeyID}; -use dpp::prelude::{Identity, Revision}; +pub use dpp::prelude::{AssetLockProof, Identity, Revision}; use crate::fee::credits::Credits; use dpp::serialization_traits::PlatformDeserializable; From 9e5255144486a567b040fa3da6fa2bbabb3e71f4 Mon Sep 17 00:00:00 2001 From: strophy <32928115+strophy@users.noreply.github.com> Date: Wed, 5 Apr 2023 01:36:11 +1000 Subject: [PATCH 04/21] fix: llmqType must be equal to one of the allowed values (#884) --- packages/dashmate/configs/schema/configJsonSchema.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dashmate/configs/schema/configJsonSchema.js b/packages/dashmate/configs/schema/configJsonSchema.js index c563306f784..185ac3cbbd3 100644 --- a/packages/dashmate/configs/schema/configJsonSchema.js +++ b/packages/dashmate/configs/schema/configJsonSchema.js @@ -407,8 +407,8 @@ module.exports = { properties: { llmqType: { type: 'number', - // https://github.com/dashevo/dashcore-lib/blob/286c33a9d29d33f05d874c47a9b33764a0be0cf1/lib/constants/index.js#L42-L57 - enum: [1, 2, 3, 4, 100, 101, 102, 106, 107], + // https://github.com/dashpay/dashcore-lib/blob/843176fed9fc81feae43ccf319d99e2dd942fe1f/lib/constants/index.js#L50-L99 + enum: [1, 2, 3, 4, 5, 6, 100, 101, 102, 103, 104, 105, 106, 107], }, }, additionalProperties: false, From 38c3b578311d12f32a5169a0199241e74a129252 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Wed, 5 Apr 2023 20:34:45 +0700 Subject: [PATCH 05/21] feat(dashmate): build linux tarballs (#887) --- .github/workflows/release.yml | 2 ++ .pnp.cjs | 34 +++++++++--------- ...oclif-npm-1.0.2-838469dc60-b421acd414.zip} | Bin 67749 -> 67751 bytes packages/dashmate/package.json | 2 +- yarn.lock | 10 +++--- 5 files changed, 25 insertions(+), 23 deletions(-) rename .yarn/cache/{@dashevo-oclif-npm-1.0.1-f30fea1a8a-1a9b7871f8.zip => @dashevo-oclif-npm-1.0.2-838469dc60-b421acd414.zip} (88%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a8b76b3b632..8e91aae1cea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -770,6 +770,8 @@ jobs: fail-fast: false matrix: include: + - package_type: tarballs + os: ubuntu-22.04 - package_type: win os: ubuntu-22.04 - package_type: deb diff --git a/.pnp.cjs b/.pnp.cjs index fcffa1832f4..ad498c63b29 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -2694,10 +2694,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@dashevo/oclif", [\ - ["npm:1.0.1", {\ - "packageLocation": "./.yarn/cache/@dashevo-oclif-npm-1.0.1-f30fea1a8a-1a9b7871f8.zip/node_modules/@dashevo/oclif/",\ + ["npm:1.0.2", {\ + "packageLocation": "./.yarn/cache/@dashevo-oclif-npm-1.0.2-838469dc60-b421acd414.zip/node_modules/@dashevo/oclif/",\ "packageDependencies": [\ - ["@dashevo/oclif", "npm:1.0.1"],\ + ["@dashevo/oclif", "npm:1.0.2"],\ ["@oclif/core", "npm:1.22.0"],\ ["@oclif/plugin-help", "npm:5.1.20"],\ ["@oclif/plugin-not-found", "npm:2.3.11"],\ @@ -2713,8 +2713,8 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["normalize-package-data", "npm:3.0.3"],\ ["semver", "npm:7.3.8"],\ ["tslib", "npm:2.4.1"],\ - ["yeoman-environment", "virtual:f30fea1a8a1e96cd95aaa42203f6e54f27d334aca599a6a058bdf580a493e4506d84d23c55f845dc694b6895d6ebbf513119b16b84b915ea59e5faf03cf40f96#npm:3.13.0"],\ - ["yeoman-generator", "virtual:f30fea1a8a1e96cd95aaa42203f6e54f27d334aca599a6a058bdf580a493e4506d84d23c55f845dc694b6895d6ebbf513119b16b84b915ea59e5faf03cf40f96#npm:5.6.1"],\ + ["yeoman-environment", "virtual:838469dc6055e1f2cba01fd72be9fbca6706b01748bd0e84ae73ecebec5159ccf815131e67deb9faaa4a6ec339ab1a43a8cfcd7805ad0d0047211e293bb5941b#npm:3.13.0"],\ + ["yeoman-generator", "virtual:838469dc6055e1f2cba01fd72be9fbca6706b01748bd0e84ae73ecebec5159ccf815131e67deb9faaa4a6ec339ab1a43a8cfcd7805ad0d0047211e293bb5941b#npm:5.6.1"],\ ["yosay", "npm:2.0.2"]\ ],\ "linkType": "HARD"\ @@ -7753,7 +7753,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@dashevo/dpp", "workspace:packages/js-dpp"],\ ["@dashevo/feature-flags-contract", "workspace:packages/feature-flags-contract"],\ ["@dashevo/masternode-reward-shares-contract", "workspace:packages/masternode-reward-shares-contract"],\ - ["@dashevo/oclif", "npm:1.0.1"],\ + ["@dashevo/oclif", "npm:1.0.2"],\ ["@dashevo/wallet-lib", "workspace:packages/wallet-lib"],\ ["@dashevo/withdrawals-contract", "workspace:packages/withdrawals-contract"],\ ["@oclif/core", "npm:1.22.0"],\ @@ -12705,10 +12705,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ - ["virtual:d57a3bd532041f95d204482eda0a956044e32c7f872cadfef73b29f820fc5c3d8536a7bada8e0e465272cceec08d4ac9da54258525d634de10eba0af9cc6dfe1#npm:9.4.0", {\ - "packageLocation": "./.yarn/__virtual__/mem-fs-editor-virtual-96decfce1f/0/cache/mem-fs-editor-npm-9.4.0-97c608fb01-427b71d59a.zip/node_modules/mem-fs-editor/",\ + ["virtual:29cd966a3bf4a1d402f9cda58cb039a316be3657f4e6b8ac58634c33284e2ea8a4372e13f710cd2a2d785a107d9d65ea8ec3877ffec3f4046f107c19d1df875e#npm:9.4.0", {\ + "packageLocation": "./.yarn/__virtual__/mem-fs-editor-virtual-1238782479/0/cache/mem-fs-editor-npm-9.4.0-97c608fb01-427b71d59a.zip/node_modules/mem-fs-editor/",\ "packageDependencies": [\ - ["mem-fs-editor", "virtual:d57a3bd532041f95d204482eda0a956044e32c7f872cadfef73b29f820fc5c3d8536a7bada8e0e465272cceec08d4ac9da54258525d634de10eba0af9cc6dfe1#npm:9.4.0"],\ + ["mem-fs-editor", "virtual:29cd966a3bf4a1d402f9cda58cb039a316be3657f4e6b8ac58634c33284e2ea8a4372e13f710cd2a2d785a107d9d65ea8ec3877ffec3f4046f107c19d1df875e#npm:9.4.0"],\ ["@types/mem-fs", null],\ ["binaryextensions", "npm:4.18.0"],\ ["commondir", "npm:1.0.1"],\ @@ -18775,10 +18775,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ - ["virtual:f30fea1a8a1e96cd95aaa42203f6e54f27d334aca599a6a058bdf580a493e4506d84d23c55f845dc694b6895d6ebbf513119b16b84b915ea59e5faf03cf40f96#npm:3.13.0", {\ - "packageLocation": "./.yarn/__virtual__/yeoman-environment-virtual-d57a3bd532/0/cache/yeoman-environment-npm-3.13.0-f7ad653f8e-2d622d18d2.zip/node_modules/yeoman-environment/",\ + ["virtual:838469dc6055e1f2cba01fd72be9fbca6706b01748bd0e84ae73ecebec5159ccf815131e67deb9faaa4a6ec339ab1a43a8cfcd7805ad0d0047211e293bb5941b#npm:3.13.0", {\ + "packageLocation": "./.yarn/__virtual__/yeoman-environment-virtual-29cd966a3b/0/cache/yeoman-environment-npm-3.13.0-f7ad653f8e-2d622d18d2.zip/node_modules/yeoman-environment/",\ "packageDependencies": [\ - ["yeoman-environment", "virtual:f30fea1a8a1e96cd95aaa42203f6e54f27d334aca599a6a058bdf580a493e4506d84d23c55f845dc694b6895d6ebbf513119b16b84b915ea59e5faf03cf40f96#npm:3.13.0"],\ + ["yeoman-environment", "virtual:838469dc6055e1f2cba01fd72be9fbca6706b01748bd0e84ae73ecebec5159ccf815131e67deb9faaa4a6ec339ab1a43a8cfcd7805ad0d0047211e293bb5941b#npm:3.13.0"],\ ["@npmcli/arborist", "npm:4.3.1"],\ ["@types/mem-fs", null],\ ["@types/mem-fs-editor", null],\ @@ -18803,7 +18803,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["lodash", "npm:4.17.21"],\ ["log-symbols", "npm:4.1.0"],\ ["mem-fs", "npm:2.2.1"],\ - ["mem-fs-editor", "virtual:d57a3bd532041f95d204482eda0a956044e32c7f872cadfef73b29f820fc5c3d8536a7bada8e0e465272cceec08d4ac9da54258525d634de10eba0af9cc6dfe1#npm:9.4.0"],\ + ["mem-fs-editor", "virtual:29cd966a3bf4a1d402f9cda58cb039a316be3657f4e6b8ac58634c33284e2ea8a4372e13f710cd2a2d785a107d9d65ea8ec3877ffec3f4046f107c19d1df875e#npm:9.4.0"],\ ["minimatch", "npm:3.1.2"],\ ["npmlog", "npm:5.0.1"],\ ["p-queue", "npm:6.6.2"],\ @@ -18833,10 +18833,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ - ["virtual:f30fea1a8a1e96cd95aaa42203f6e54f27d334aca599a6a058bdf580a493e4506d84d23c55f845dc694b6895d6ebbf513119b16b84b915ea59e5faf03cf40f96#npm:5.6.1", {\ - "packageLocation": "./.yarn/__virtual__/yeoman-generator-virtual-ad9d192e11/0/cache/yeoman-generator-npm-5.6.1-a49b7654c4-ef036210b6.zip/node_modules/yeoman-generator/",\ + ["virtual:838469dc6055e1f2cba01fd72be9fbca6706b01748bd0e84ae73ecebec5159ccf815131e67deb9faaa4a6ec339ab1a43a8cfcd7805ad0d0047211e293bb5941b#npm:5.6.1", {\ + "packageLocation": "./.yarn/__virtual__/yeoman-generator-virtual-4147e1a57f/0/cache/yeoman-generator-npm-5.6.1-a49b7654c4-ef036210b6.zip/node_modules/yeoman-generator/",\ "packageDependencies": [\ - ["yeoman-generator", "virtual:f30fea1a8a1e96cd95aaa42203f6e54f27d334aca599a6a058bdf580a493e4506d84d23c55f845dc694b6895d6ebbf513119b16b84b915ea59e5faf03cf40f96#npm:5.6.1"],\ + ["yeoman-generator", "virtual:838469dc6055e1f2cba01fd72be9fbca6706b01748bd0e84ae73ecebec5159ccf815131e67deb9faaa4a6ec339ab1a43a8cfcd7805ad0d0047211e293bb5941b#npm:5.6.1"],\ ["@types/yeoman-environment", null],\ ["chalk", "npm:4.1.2"],\ ["dargs", "npm:7.0.0"],\ @@ -18851,7 +18851,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["shelljs", "npm:0.8.5"],\ ["sort-keys", "npm:4.2.0"],\ ["text-table", "npm:0.2.0"],\ - ["yeoman-environment", "virtual:f30fea1a8a1e96cd95aaa42203f6e54f27d334aca599a6a058bdf580a493e4506d84d23c55f845dc694b6895d6ebbf513119b16b84b915ea59e5faf03cf40f96#npm:3.13.0"]\ + ["yeoman-environment", "virtual:838469dc6055e1f2cba01fd72be9fbca6706b01748bd0e84ae73ecebec5159ccf815131e67deb9faaa4a6ec339ab1a43a8cfcd7805ad0d0047211e293bb5941b#npm:3.13.0"]\ ],\ "packagePeers": [\ "@types/yeoman-environment",\ diff --git a/.yarn/cache/@dashevo-oclif-npm-1.0.1-f30fea1a8a-1a9b7871f8.zip b/.yarn/cache/@dashevo-oclif-npm-1.0.2-838469dc60-b421acd414.zip similarity index 88% rename from .yarn/cache/@dashevo-oclif-npm-1.0.1-f30fea1a8a-1a9b7871f8.zip rename to .yarn/cache/@dashevo-oclif-npm-1.0.2-838469dc60-b421acd414.zip index 59de21db83e59a3faa4db112b728872e286e3f92..bbe43088d5ed4ac797b48f3ee82c702767f1e5ab 100644 GIT binary patch delta 3673 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 HxU2sF?_|jY delta 3721 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 U!hZxH5L5h}eHj8-U;cys14nwlPXGV_ diff --git a/packages/dashmate/package.json b/packages/dashmate/package.json index 511bedebae8..29234537e1c 100644 --- a/packages/dashmate/package.json +++ b/packages/dashmate/package.json @@ -90,7 +90,7 @@ "table": "^5.4.6" }, "devDependencies": { - "@dashevo/oclif": "^1.0.1", + "@dashevo/oclif": "^1.0.2", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "dirty-chai": "^2.0.1", diff --git a/yarn.lock b/yarn.lock index 09d29c4d1e2..ba5de525263 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1677,9 +1677,9 @@ __metadata: languageName: unknown linkType: soft -"@dashevo/oclif@npm:^1.0.1": - version: 1.0.1 - resolution: "@dashevo/oclif@npm:1.0.1" +"@dashevo/oclif@npm:^1.0.2": + version: 1.0.2 + resolution: "@dashevo/oclif@npm:1.0.2" dependencies: "@oclif/core": ^1.20.4 "@oclif/plugin-help": ^5.1.19 @@ -1701,7 +1701,7 @@ __metadata: yosay: ^2.0.2 bin: oclif: bin/run - checksum: 1a9b7871f83f97cd377c5037c67c0527241e13b917c63dd5da102755003eb36ec155326075f93fa0ba6de35e9f5f7dbf24fa6cc73f54caa7a75ae10f500500b9 + checksum: b421acd414231e2691e1dbc31dc40f83245fddd19bba0bd3a269979a9a62e80e12366b88301dda6be3e1a117be901f0c59dff3c48ee870a19f770a8468ee0b87 languageName: node linkType: hard @@ -5745,7 +5745,7 @@ __metadata: "@dashevo/dpp": "workspace:*" "@dashevo/feature-flags-contract": "workspace:*" "@dashevo/masternode-reward-shares-contract": "workspace:*" - "@dashevo/oclif": ^1.0.1 + "@dashevo/oclif": ^1.0.2 "@dashevo/wallet-lib": "workspace:*" "@dashevo/withdrawals-contract": "workspace:*" "@oclif/core": ^1.21.0 From 784fac3084a320bc34f5ff609c9159b9cc947a98 Mon Sep 17 00:00:00 2001 From: Mikhail Pshenichnikov Date: Fri, 7 Apr 2023 10:46:01 +0400 Subject: [PATCH 06/21] feat(dashmate): implement http json rpc api (#888) Co-authored-by: Ivan Shumkov --- .pnp.cjs | 44 ++++++++++++++++- ...-core-npm-1.26.2-6e8f4423c3-1da7f22fff.zip | Bin 0 -> 121620 bytes ...screen-npm-3.0.4-d372fa56f8-6d662c81ed.zip | Bin 0 -> 3024 bytes packages/dashmate/configs/migrations.js | 22 +++++++++ .../configs/schema/configJsonSchema.js | 15 +++++- packages/dashmate/configs/system/base.js | 4 ++ packages/dashmate/configs/system/local.js | 7 +++ packages/dashmate/docker-compose.platform.yml | 2 + packages/dashmate/package.json | 2 +- packages/dashmate/scripts/helper.js | 16 ++++++- packages/dashmate/src/createDIContainer.js | 2 + .../helper/api/createHttpApiServerFactory.js | 38 +++++++++++++++ .../setup/setupLocalPresetTaskFactory.js | 2 + .../src/printers/printArrayOfObjects.js | 2 + packages/dashmate/src/printers/printObject.js | 2 + yarn.lock | 45 +++++++++++++++++- 16 files changed, 197 insertions(+), 6 deletions(-) create mode 100644 .yarn/cache/@oclif-core-npm-1.26.2-6e8f4423c3-1da7f22fff.zip create mode 100644 .yarn/cache/@oclif-screen-npm-3.0.4-d372fa56f8-6d662c81ed.zip create mode 100644 packages/dashmate/src/helper/api/createHttpApiServerFactory.js diff --git a/.pnp.cjs b/.pnp.cjs index ad498c63b29..a9b196e03cf 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -3506,6 +3506,41 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["wrap-ansi", "npm:7.0.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.26.2", {\ + "packageLocation": "./.yarn/cache/@oclif-core-npm-1.26.2-6e8f4423c3-1da7f22fff.zip/node_modules/@oclif/core/",\ + "packageDependencies": [\ + ["@oclif/core", "npm:1.26.2"],\ + ["@oclif/linewrap", "npm:1.0.0"],\ + ["@oclif/screen", "npm:3.0.4"],\ + ["ansi-escapes", "npm:4.3.2"],\ + ["ansi-styles", "npm:4.3.0"],\ + ["cardinal", "npm:2.1.1"],\ + ["chalk", "npm:4.1.2"],\ + ["clean-stack", "npm:3.0.1"],\ + ["cli-progress", "npm:3.10.0"],\ + ["debug", "virtual:da6319fb8ee6999f0780eb633cb928f5c3591495496bf4772d10fe1fe1a6bdb4d9da75848709ecbee87ebbe810b9042e34151e541b86413683541ebf018679fa#npm:4.3.4"],\ + ["ejs", "npm:3.1.8"],\ + ["fs-extra", "npm:9.1.0"],\ + ["get-package-type", "npm:0.1.0"],\ + ["globby", "npm:11.1.0"],\ + ["hyperlinker", "npm:1.0.0"],\ + ["indent-string", "npm:4.0.0"],\ + ["is-wsl", "npm:2.2.0"],\ + ["js-yaml", "npm:3.14.1"],\ + ["natural-orderby", "npm:2.0.3"],\ + ["object-treeify", "npm:1.1.33"],\ + ["password-prompt", "npm:1.1.2"],\ + ["semver", "npm:7.3.8"],\ + ["string-width", "npm:4.2.3"],\ + ["strip-ansi", "npm:6.0.1"],\ + ["supports-color", "npm:8.1.1"],\ + ["supports-hyperlinks", "npm:2.2.0"],\ + ["tslib", "npm:2.4.1"],\ + ["widest-line", "npm:3.1.0"],\ + ["wrap-ansi", "npm:7.0.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@oclif/linewrap", [\ @@ -3563,6 +3598,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@oclif/screen", "npm:3.0.3"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:3.0.4", {\ + "packageLocation": "./.yarn/cache/@oclif-screen-npm-3.0.4-d372fa56f8-6d662c81ed.zip/node_modules/@oclif/screen/",\ + "packageDependencies": [\ + ["@oclif/screen", "npm:3.0.4"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@octokit/auth-token", [\ @@ -7756,7 +7798,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@dashevo/oclif", "npm:1.0.2"],\ ["@dashevo/wallet-lib", "workspace:packages/wallet-lib"],\ ["@dashevo/withdrawals-contract", "workspace:packages/withdrawals-contract"],\ - ["@oclif/core", "npm:1.22.0"],\ + ["@oclif/core", "npm:1.26.2"],\ ["@oclif/plugin-help", "npm:5.1.20"],\ ["ajv", "npm:8.8.1"],\ ["ajv-formats", "virtual:58fb68f2aed20e5e0f2e48520ab903ae9bb3440369bfd5e912034003cf27c5aae368649fc5620dd2acbed578131f3a0975e75b838d77d12335fb0412e24026c6#npm:2.1.1"],\ diff --git a/.yarn/cache/@oclif-core-npm-1.26.2-6e8f4423c3-1da7f22fff.zip b/.yarn/cache/@oclif-core-npm-1.26.2-6e8f4423c3-1da7f22fff.zip new file mode 100644 index 0000000000000000000000000000000000000000..22b0f522690fe972f9b0f2c5604301b0c4ca366e GIT binary patch literal 121620 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�FIkBBTX(rRSSu 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 { + let groupJsonApiPort = systemConfigs.local.dashmate.helper.api.port; + + Object.entries(configFile.configs) + .forEach(([, config]) => { + if (config.group === 'local') { + config.dashmate.helper.api = { + enable: false, + port: groupJsonApiPort, + }; + + groupJsonApiPort += 100; + } else { + config.dashmate.helper.api = { + enable: false, + port: systemConfigs.base.dashmate.helper.api.port, + }; + } + }); + + return configFile; + }, }; diff --git a/packages/dashmate/configs/schema/configJsonSchema.js b/packages/dashmate/configs/schema/configJsonSchema.js index 185ac3cbbd3..e3d2b5df070 100644 --- a/packages/dashmate/configs/schema/configJsonSchema.js +++ b/packages/dashmate/configs/schema/configJsonSchema.js @@ -669,8 +669,21 @@ module.exports = { docker: { $ref: '#/definitions/docker', }, + api: { + type: 'object', + properties: { + enable: { + type: 'boolean', + }, + port: { + $ref: '#/definitions/port', + }, + }, + required: ['enable', 'port'], + additionalProperties: false, + }, }, - required: ['docker'], + required: ['docker', 'api'], additionalProperties: false, }, }, diff --git a/packages/dashmate/configs/system/base.js b/packages/dashmate/configs/system/base.js index a43b467fe78..accf90915a7 100644 --- a/packages/dashmate/configs/system/base.js +++ b/packages/dashmate/configs/system/base.js @@ -235,6 +235,10 @@ module.exports = { docker: { image: 'dashpay/dashmate-helper:0.24-dev', }, + api: { + enable: false, + port: 9000, + }, }, }, externalIp: null, diff --git a/packages/dashmate/configs/system/local.js b/packages/dashmate/configs/system/local.js index 1c9d9e169d1..c3b38e0717a 100644 --- a/packages/dashmate/configs/system/local.js +++ b/packages/dashmate/configs/system/local.js @@ -48,6 +48,13 @@ module.exports = lodashMerge({}, baseConfig, { }, }, }, + dashmate: { + helper: { + api: { + port: 9100, + }, + }, + }, externalIp: null, environment: 'development', network: NETWORK_LOCAL, diff --git a/packages/dashmate/docker-compose.platform.yml b/packages/dashmate/docker-compose.platform.yml index 6cc471198af..2449df7e6b2 100644 --- a/packages/dashmate/docker-compose.platform.yml +++ b/packages/dashmate/docker-compose.platform.yml @@ -114,6 +114,8 @@ services: - DASHMATE_HOME_DIR=/home/dashmate/.dashmate - LOCAL_UID=${LOCAL_UID:?err} - LOCAL_GID=${LOCAL_GID:?err} + ports: + - ${DASHMATE_HELPER_API_PORT:?err}:${DASHMATE_HELPER_API_PORT:?err} command: yarn workspace dashmate helper ${CONFIG_NAME:?err} volumes: - ${DASHMATE_HOME_DIR:?err}:/home/dashmate/.dashmate diff --git a/packages/dashmate/package.json b/packages/dashmate/package.json index 29234537e1c..23803e82392 100644 --- a/packages/dashmate/package.json +++ b/packages/dashmate/package.json @@ -58,7 +58,7 @@ "@dashevo/masternode-reward-shares-contract": "workspace:*", "@dashevo/wallet-lib": "workspace:*", "@dashevo/withdrawals-contract": "workspace:*", - "@oclif/core": "^1.21.0", + "@oclif/core": "^1.25.0", "@oclif/plugin-help": "^5.1.20", "ajv": "^8.6.0", "ajv-formats": "^2.1.1", diff --git a/packages/dashmate/scripts/helper.js b/packages/dashmate/scripts/helper.js index c9bf7174317..c7c9403149b 100644 --- a/packages/dashmate/scripts/helper.js +++ b/packages/dashmate/scripts/helper.js @@ -27,13 +27,13 @@ const createDIContainer = require('../src/createDIContainer'); const configFile = await configFileRepository.read(); + const config = configFile.getConfig(configName); + // Register config collection in the container container.register({ configFile: asValue(configFile), }); - const config = configFile.getConfig(configName); - const provider = config.get('platform.dapi.envoy.ssl.provider'); if (provider === 'zerossl') { @@ -43,4 +43,16 @@ const createDIContainer = require('../src/createDIContainer'); // prevent infinite restarts setInterval(() => {}, 60 * 1000); } + + if (config.get('dashmate.helper.api.enable')) { + const createHttpApiServer = container.resolve('createHttpApiServer'); + + const httpAPIServer = createHttpApiServer(); + + const port = config.get('dashmate.helper.api.port'); + + httpAPIServer + // eslint-disable-next-line no-console + .listen(port, () => console.log(`Dashmate JSON-RPC API started on port ${port}`)); + } }()); diff --git a/packages/dashmate/src/createDIContainer.js b/packages/dashmate/src/createDIContainer.js index 8f2207521f4..7d36704f686 100644 --- a/packages/dashmate/src/createDIContainer.js +++ b/packages/dashmate/src/createDIContainer.js @@ -92,6 +92,7 @@ const scheduleRenewZeroSslCertificateFactory = require('./helper/scheduleRenewZe const registerMasternodeGuideTaskFactory = require('./listr/tasks/setup/regular/registerMasternodeGuideTaskFactory'); const configureNodeTaskFactory = require('./listr/tasks/setup/regular/configureNodeTaskFactory'); const configureSSLCertificateTaskFactory = require('./listr/tasks/setup/regular/configureSSLCertificateTaskFactory'); +const createHttpApiServerFactory = require('./helper/api/createHttpApiServerFactory'); async function createDIContainer() { const container = createAwilixContainer({ @@ -236,6 +237,7 @@ async function createDIContainer() { */ container.register({ scheduleRenewZeroSslCertificate: asFunction(scheduleRenewZeroSslCertificateFactory).singleton(), + createHttpApiServer: asFunction(createHttpApiServerFactory).singleton(), }); return container; diff --git a/packages/dashmate/src/helper/api/createHttpApiServerFactory.js b/packages/dashmate/src/helper/api/createHttpApiServerFactory.js new file mode 100644 index 00000000000..2f2db5f01ed --- /dev/null +++ b/packages/dashmate/src/helper/api/createHttpApiServerFactory.js @@ -0,0 +1,38 @@ +const jayson = require('jayson/promise'); +const oclif = require('@oclif/core'); + +function createHttpApiServerFactory() { + /** + * @return {HttpServer} + */ + function createHttpApiServer() { + const server = new jayson.Server({}, { + router(method, params) { + const argv = method.split(' '); + + // map arguments to argv + if (Array.isArray(params)) { + argv.push(...params); + } else { + for (const [name, value] of Object.entries(params)) { + argv.push(`--${name}=${value}`); + } + } + + return new jayson.Method(async () => { + try { + return await oclif.run([...argv]); + } catch (e) { + throw server.error(501, e.message); + } + }); + }, + }); + + return server.http(); + } + + return createHttpApiServer; +} + +module.exports = createHttpApiServerFactory; diff --git a/packages/dashmate/src/listr/tasks/setup/setupLocalPresetTaskFactory.js b/packages/dashmate/src/listr/tasks/setup/setupLocalPresetTaskFactory.js index 437cda27d52..023affc64cc 100644 --- a/packages/dashmate/src/listr/tasks/setup/setupLocalPresetTaskFactory.js +++ b/packages/dashmate/src/listr/tasks/setup/setupLocalPresetTaskFactory.js @@ -244,6 +244,8 @@ function setupLocalPresetTaskFactory( masternodeRewardSharesDerivedSecondPrivateKey.privateKey .toPublicKey().toString(), ); + + config.set('dashmate.helper.api.port', config.get('dashmate.helper.api.port') + (i * 100)); } }, options: { diff --git a/packages/dashmate/src/printers/printArrayOfObjects.js b/packages/dashmate/src/printers/printArrayOfObjects.js index 63bd50e10ce..26213d2ce34 100644 --- a/packages/dashmate/src/printers/printArrayOfObjects.js +++ b/packages/dashmate/src/printers/printArrayOfObjects.js @@ -36,6 +36,8 @@ function printArrayOfObjects(array, format) { // eslint-disable-next-line no-console console.log(output); + + return output; } module.exports = printArrayOfObjects; diff --git a/packages/dashmate/src/printers/printObject.js b/packages/dashmate/src/printers/printObject.js index 05f2a67d2ae..32149006b61 100644 --- a/packages/dashmate/src/printers/printObject.js +++ b/packages/dashmate/src/printers/printObject.js @@ -29,6 +29,8 @@ function printObject(object, format) { // eslint-disable-next-line no-console console.log(output); + + return output; } module.exports = printObject; diff --git a/yarn.lock b/yarn.lock index ba5de525263..a59dd92c6c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2405,6 +2405,42 @@ __metadata: languageName: node linkType: hard +"@oclif/core@npm:^1.25.0": + version: 1.26.2 + resolution: "@oclif/core@npm:1.26.2" + dependencies: + "@oclif/linewrap": ^1.0.0 + "@oclif/screen": ^3.0.4 + ansi-escapes: ^4.3.2 + ansi-styles: ^4.3.0 + cardinal: ^2.1.1 + chalk: ^4.1.2 + clean-stack: ^3.0.1 + cli-progress: ^3.10.0 + debug: ^4.3.4 + ejs: ^3.1.6 + fs-extra: ^9.1.0 + get-package-type: ^0.1.0 + globby: ^11.1.0 + hyperlinker: ^1.0.0 + indent-string: ^4.0.0 + is-wsl: ^2.2.0 + js-yaml: ^3.14.1 + natural-orderby: ^2.0.3 + object-treeify: ^1.1.33 + password-prompt: ^1.1.2 + semver: ^7.3.7 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + supports-color: ^8.1.1 + supports-hyperlinks: ^2.2.0 + tslib: ^2.4.1 + widest-line: ^3.1.0 + wrap-ansi: ^7.0.0 + checksum: 1da7f22fff1eb4bba10f17f07a97bad308d317a0b591561be7f1171edff2f40bf84830295965b26cfcb80d3c5df7958df35bbbba4ce030e14a68bbc8e3cedc82 + languageName: node + linkType: hard + "@oclif/linewrap@npm:^1.0.0": version: 1.0.0 resolution: "@oclif/linewrap@npm:1.0.0" @@ -2455,6 +2491,13 @@ __metadata: languageName: node linkType: hard +"@oclif/screen@npm:^3.0.4": + version: 3.0.4 + resolution: "@oclif/screen@npm:3.0.4" + checksum: 6d662c81edfbc8acb50a30001e3c2261171e8baffcd6010603c331de375c5d0c9ce6b3230f6b0ce24961bfa3531f99e771265ea74d9d7a0ee37fb9357fb436a7 + languageName: node + linkType: hard + "@octokit/auth-token@npm:^2.4.4": version: 2.5.0 resolution: "@octokit/auth-token@npm:2.5.0" @@ -5748,7 +5791,7 @@ __metadata: "@dashevo/oclif": ^1.0.2 "@dashevo/wallet-lib": "workspace:*" "@dashevo/withdrawals-contract": "workspace:*" - "@oclif/core": ^1.21.0 + "@oclif/core": ^1.25.0 "@oclif/plugin-help": ^5.1.20 ajv: ^8.6.0 ajv-formats: ^2.1.1 From 4843a8ba9b01311a2615a66b5cc1115321baf48a Mon Sep 17 00:00:00 2001 From: Mikhail Pshenichnikov Date: Fri, 7 Apr 2023 21:07:47 +0400 Subject: [PATCH 07/21] fix(dashmate): api binds to all interfaces (#893) Co-authored-by: Ivan Shumkov --- packages/dashmate/Dockerfile | 4 ++-- packages/dashmate/docker-compose.platform.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/dashmate/Dockerfile b/packages/dashmate/Dockerfile index e307e101bba..40e86e888a9 100644 --- a/packages/dashmate/Dockerfile +++ b/packages/dashmate/Dockerfile @@ -11,7 +11,8 @@ RUN apk update && \ alpine-sdk \ make \ cmake \ - g++ + g++ \ + curl # Enable corepack https://github.com/nodejs/corepack RUN corepack enable @@ -60,7 +61,6 @@ RUN apk update && \ LABEL maintainer="Dash Developers " LABEL description="Dashmate Helper Node.JS" - WORKDIR /platform COPY --from=builder /platform /platform diff --git a/packages/dashmate/docker-compose.platform.yml b/packages/dashmate/docker-compose.platform.yml index 2449df7e6b2..0f80d81bb41 100644 --- a/packages/dashmate/docker-compose.platform.yml +++ b/packages/dashmate/docker-compose.platform.yml @@ -115,7 +115,7 @@ services: - LOCAL_UID=${LOCAL_UID:?err} - LOCAL_GID=${LOCAL_GID:?err} ports: - - ${DASHMATE_HELPER_API_PORT:?err}:${DASHMATE_HELPER_API_PORT:?err} + - 127.0.0.1:${DASHMATE_HELPER_API_PORT:?err}:${DASHMATE_HELPER_API_PORT:?err} command: yarn workspace dashmate helper ${CONFIG_NAME:?err} volumes: - ${DASHMATE_HOME_DIR:?err}:/home/dashmate/.dashmate From 5ab086faacf8b5fea06e82bc373933be04d8b808 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sat, 8 Apr 2023 02:17:26 +0800 Subject: [PATCH 08/21] feat(dashmate): build services before restart (#894) --- .../src/listr/tasks/restartNodeTaskFactory.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/dashmate/src/listr/tasks/restartNodeTaskFactory.js b/packages/dashmate/src/listr/tasks/restartNodeTaskFactory.js index 26586b9c990..0b70b602f72 100644 --- a/packages/dashmate/src/listr/tasks/restartNodeTaskFactory.js +++ b/packages/dashmate/src/listr/tasks/restartNodeTaskFactory.js @@ -3,10 +3,10 @@ const { Listr } = require('listr2'); /** * @param {startNodeTask} startNodeTask * @param {stopNodeTask} stopNodeTask - * + * @param {buildServicesTask} buildServicesTask * @return {restartNodeTask} */ -function restartNodeTaskFactory(startNodeTask, stopNodeTask) { +function restartNodeTaskFactory(startNodeTask, stopNodeTask, buildServicesTask) { /** * Restart node * @typedef {restartNodeTask} @@ -17,6 +17,14 @@ function restartNodeTaskFactory(startNodeTask, stopNodeTask) { */ function restartNodeTask(config) { return new Listr([ + { + enabled: () => config.get('platform.enable') && config.get('platform.sourcePath') !== null, + task: (ctx) => { + ctx.skipBuildServices = true; + + return buildServicesTask(config); + }, + }, { task: () => stopNodeTask(config), }, From 9cca6adbbe0319c4904a434580478f7445a46c28 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sat, 8 Apr 2023 02:32:38 +0800 Subject: [PATCH 09/21] fix(dashmate): dashmate helper is running under root user (#895) --- packages/dashmate/docker/entrypoint.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/dashmate/docker/entrypoint.sh b/packages/dashmate/docker/entrypoint.sh index 926223f1325..d6fe6946d9a 100755 --- a/packages/dashmate/docker/entrypoint.sh +++ b/packages/dashmate/docker/entrypoint.sh @@ -31,6 +31,4 @@ fi echo "Starting with: USERNAME: $USERNAME, UID: $USER_ID, GID: $GROUP_ID, USER: $USERNAME, GROUP: $GROUP" -su $USERNAME - -exec "$@" +exec su - $USERNAME -c "cd /platform;$*" From 227971e5af15b648a5042aa273dbf0a2812d2a04 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Sat, 8 Apr 2023 01:33:21 +0700 Subject: [PATCH 10/21] ci: sign MacOs Dashmate release (#890) --- .github/workflows/release.yml | 25 +++++++++++++++++++++++++ packages/dashmate/package.json | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8e91aae1cea..68b256efb4d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -794,6 +794,29 @@ jobs: colima start echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH + - name: Install the Apple certificate + if: runner.os == 'macOS' + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.MACOS_BUILD_CERTIFICATE_BASE64 }} + P12_PASSWORD: ${{ secrets.MACOS_P12_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }} + run: | + # create variables + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + + # import certificate and provisioning profile from secrets + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH + + # create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # import certificate to keychain + security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + - name: Install Linux build deps if: runner.os == 'Linux' run: sudo apt-get install -y nsis @@ -827,6 +850,8 @@ jobs: CARGO_BUILD_PROFILE: release - name: Create package + env: + OSX_KEYCHAIN: $RUNNER_TEMP/app-signing.keychain-db run: "${GITHUB_WORKSPACE}/scripts/pack_dashmate.sh ${{ matrix.package_type }}" - name: Upload artifacts to action summary diff --git a/packages/dashmate/package.json b/packages/dashmate/package.json index 23803e82392..17c8987db07 100644 --- a/packages/dashmate/package.json +++ b/packages/dashmate/package.json @@ -120,7 +120,8 @@ "commands": "./src/commands", "bin": "dashmate", "macos": { - "identifier": "org.dash.dashmate" + "identifier": "org.dash.dashmate", + "sign": "'Developer ID Installer: The Dash Foundation, Inc.'" }, "deb": { "dependencies": "docker-ce (>= 20.10.0) | docker.io (>= 20.10.0)" From 90e1885ce19926e05e6bd0c2676be0710a5628c7 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sat, 8 Apr 2023 03:05:08 +0800 Subject: [PATCH 11/21] feat(dashmate): exit status with 2 if it's not running (#896) --- packages/dashmate/src/commands/status/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/dashmate/src/commands/status/index.js b/packages/dashmate/src/commands/status/index.js index 84f831ae76a..b85066c0d11 100644 --- a/packages/dashmate/src/commands/status/index.js +++ b/packages/dashmate/src/commands/status/index.js @@ -24,7 +24,11 @@ class StatusCommand extends ConfigBaseCommand { config, ) { if (!(await dockerCompose.isServiceRunning(config.toEnvs()))) { - throw new Error('Node is not running, start it with `dashmate start`'); + const error = new Error('Node is not running, start it with `dashmate start`'); + + error.exitCode = 2; + + throw error; } const scope = await getOverviewScope(config); From 5b9a21418e8058e243fedf68d7e2951bfe22702f Mon Sep 17 00:00:00 2001 From: Mikhail Pshenichnikov Date: Mon, 10 Apr 2023 11:23:10 +0400 Subject: [PATCH 12/21] fix(dashmate): dashmate logic doesn't recognize it's ran from helper (#902) --- packages/dashmate/Dockerfile | 1 - packages/dashmate/docker/entrypoint.sh | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/dashmate/Dockerfile b/packages/dashmate/Dockerfile index 40e86e888a9..0eae41df61d 100644 --- a/packages/dashmate/Dockerfile +++ b/packages/dashmate/Dockerfile @@ -52,7 +52,6 @@ FROM node:16-alpine3.16 ARG NODE_ENV=production ENV NODE_ENV ${NODE_ENV} -ENV DASHMATE_HELPER=1 RUN apk update && \ apk --no-cache upgrade && \ diff --git a/packages/dashmate/docker/entrypoint.sh b/packages/dashmate/docker/entrypoint.sh index d6fe6946d9a..d6a1d69ad5c 100755 --- a/packages/dashmate/docker/entrypoint.sh +++ b/packages/dashmate/docker/entrypoint.sh @@ -31,4 +31,4 @@ fi echo "Starting with: USERNAME: $USERNAME, UID: $USER_ID, GID: $GROUP_ID, USER: $USERNAME, GROUP: $GROUP" -exec su - $USERNAME -c "cd /platform;$*" +exec su - $USERNAME -c "cd /platform;DASHMATE_HELPER=1 $*" From acb4e48094afd777a6adbe4f36d9406b793418f1 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Mon, 10 Apr 2023 20:37:47 +0800 Subject: [PATCH 13/21] fix(dapi-client): platform port is ignored from SML (#903) --- .../lib/dapiAddressProvider/ListDAPIAddressProvider.js | 2 +- .../SimplifiedMasternodeListDAPIAddressProvider.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/js-dapi-client/lib/dapiAddressProvider/ListDAPIAddressProvider.js b/packages/js-dapi-client/lib/dapiAddressProvider/ListDAPIAddressProvider.js index d5ad8c8074d..eedfcbfe284 100644 --- a/packages/js-dapi-client/lib/dapiAddressProvider/ListDAPIAddressProvider.js +++ b/packages/js-dapi-client/lib/dapiAddressProvider/ListDAPIAddressProvider.js @@ -30,7 +30,7 @@ class ListDAPIAddressProvider { } // This is a temporary fix for a localhost masternode. - // On mac os, internal docker IP is used to register masternode, and it's + // On macOS, internal docker IP is used to register masternode, and it's // not really possible to bind to that address, so that workaround is introduced. const network = networks.get(this.options.network); if (network && network.regtestEnabled) { diff --git a/packages/js-dapi-client/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js b/packages/js-dapi-client/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js index cacacaec60f..673b3320493 100644 --- a/packages/js-dapi-client/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js +++ b/packages/js-dapi-client/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js @@ -22,7 +22,10 @@ class SimplifiedMasternodeListDAPIAddressProvider { const validMasternodeList = sml.getValidMasternodesList(); const addressesByRegProTxHashes = {}; + let allowSelfSignedCertificate; this.listDAPIAddressProvider.getAllAddresses().forEach((address) => { + allowSelfSignedCertificate = address.isSelfSignedCertificateAllowed(); + if (!address.getProRegTxHash()) { return; } @@ -36,6 +39,8 @@ class SimplifiedMasternodeListDAPIAddressProvider { if (!address) { address = new DAPIAddress({ host: smlEntry.getIp(), + port: smlEntry.platformHTTPPort, + allowSelfSignedCertificate, proRegTxHash: smlEntry.proRegTxHash, }); } else { From 1db73847b26477adaf768a79753e9970b717f6dd Mon Sep 17 00:00:00 2001 From: Mikhail Pshenichnikov Date: Mon, 10 Apr 2023 16:55:31 +0400 Subject: [PATCH 14/21] fix: DAPI still expected on normal masternodes (#904) --- .../SimplifiedMasternodeListDAPIAddressProvider.js | 4 +++- scripts/configure_test_suite_network.sh | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/js-dapi-client/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js b/packages/js-dapi-client/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js index 673b3320493..fa238c87771 100644 --- a/packages/js-dapi-client/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js +++ b/packages/js-dapi-client/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js @@ -19,7 +19,9 @@ class SimplifiedMasternodeListDAPIAddressProvider { */ async getLiveAddress() { const sml = await this.smlProvider.getSimplifiedMNList(); - const validMasternodeList = sml.getValidMasternodesList(); + const validMasternodeList = sml.getValidMasternodesList() + // Keep only HP masternodes + .filter((smlEntry) => smlEntry.nType === 1); const addressesByRegProTxHashes = {}; let allowSelfSignedCertificate; diff --git a/scripts/configure_test_suite_network.sh b/scripts/configure_test_suite_network.sh index d9776969c93..653e600d127 100755 --- a/scripts/configure_test_suite_network.sh +++ b/scripts/configure_test_suite_network.sh @@ -56,7 +56,7 @@ FEATURE_FLAGS_OWNER_PRIVATE_KEY=$(yq .feature_flags_hd_private_key "$CONFIG") MASTERNODE_NAME=$(grep "$DAPI_SEED" "$INVENTORY" | awk '{print $1;}') MASTERNODE_OWNER_PRO_REG_TX_HASH=$(grep "$DAPI_SEED" "$INVENTORY" | awk -F "=" '{print $6;}') -MASTERNODE_OWNER_MASTER_PRIVATE_KEY=$(yq .masternodes."$MASTERNODE_NAME".owner.private_key "$CONFIG") +MASTERNODE_OWNER_MASTER_PRIVATE_KEY=$(yq .hp_masternodes."$MASTERNODE_NAME".owner.private_key "$CONFIG") if [[ "$NETWORK_STRING" == "devnet"* ]]; then NETWORK=devnet From ff0d7b7938cfed9276ec88ecd086ca50a91a0396 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Tue, 11 Apr 2023 19:03:39 +0800 Subject: [PATCH 15/21] feat(dashmate): tenderdash latest block time in status (#906) --- packages/dashmate/src/commands/group/status.js | 2 +- packages/dashmate/src/commands/status/index.js | 2 +- packages/dashmate/src/commands/status/platform.js | 2 +- packages/dashmate/src/status/scopes/platform.js | 15 +++++++++------ .../test/unit/status/scopes/overview.spec.js | 3 ++- .../test/unit/status/scopes/platform.spec.js | 3 ++- 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/dashmate/src/commands/group/status.js b/packages/dashmate/src/commands/group/status.js index a4c24983cb5..c6b29aa08e4 100644 --- a/packages/dashmate/src/commands/group/status.js +++ b/packages/dashmate/src/commands/group/status.js @@ -64,7 +64,7 @@ class GroupStatusCommand extends GroupBaseCommand { } else { plain['Platform Status'] = colors.status(scope.platform.tenderdash.serviceStatus)(scope.platform.tenderdash.serviceStatus); plain['Platform Version'] = scope.platform.tenderdash.version; - plain['Platform Block Height'] = scope.platform.tenderdash.lastBlockHeight; + plain['Platform Block Height'] = scope.platform.tenderdash.latestBlockHeight; plain['Platform Peers'] = scope.platform.tenderdash.peers; plain['Platform Network'] = scope.platform.tenderdash.network; } diff --git a/packages/dashmate/src/commands/status/index.js b/packages/dashmate/src/commands/status/index.js index b85066c0d11..c2ff732a5dc 100644 --- a/packages/dashmate/src/commands/status/index.js +++ b/packages/dashmate/src/commands/status/index.js @@ -89,7 +89,7 @@ class StatusCommand extends ConfigBaseCommand { if (platform.tenderdash.serviceStatus === ServiceStatusEnum.up) { plain['Platform Version'] = platform.tenderdash.version; - plain['Platform Block Height'] = platform.tenderdash.lastBlockHeight; + plain['Platform Block Height'] = platform.tenderdash.latestBlockHeight; plain['Platform Peers'] = platform.tenderdash.peers; plain['Platform Network'] = platform.tenderdash.network; } diff --git a/packages/dashmate/src/commands/status/platform.js b/packages/dashmate/src/commands/status/platform.js index 00bdc8a27ef..5944fdb6283 100644 --- a/packages/dashmate/src/commands/status/platform.js +++ b/packages/dashmate/src/commands/status/platform.js @@ -79,7 +79,7 @@ class PlatformStatusCommand extends ConfigBaseCommand { if (tenderdash.version) { const { version: tenderdashVersion, - lastBlockHeight: platformBlockHeight, + latestBlockHeight: platformBlockHeight, latestAppHash: platformLatestAppHash, peers: platformPeers, network: tenderdashNetwork, diff --git a/packages/dashmate/src/status/scopes/platform.js b/packages/dashmate/src/status/scopes/platform.js index 7ffe430cfef..457a6428424 100644 --- a/packages/dashmate/src/status/scopes/platform.js +++ b/packages/dashmate/src/status/scopes/platform.js @@ -72,8 +72,9 @@ function getPlatformScopeFactory(dockerCompose, version: null, listening: null, catchingUp: null, - lastBlockHash: null, - lastBlockHeight: null, + latestBlockHash: null, + latestBlockHeight: null, + latestBlockTime: null, latestAppHash: null, peers: null, moniker: null, @@ -109,17 +110,19 @@ function getPlatformScopeFactory(dockerCompose, const { version, network, moniker } = tenderdashStatus.node_info; const catchingUp = tenderdashStatus.sync_info.catching_up; - const lastBlockHeight = tenderdashStatus.sync_info.latest_block_height; - const lastBlockHash = tenderdashStatus.sync_info.latest_block_hash; + const latestBlockHeight = tenderdashStatus.sync_info.latest_block_height; + const latestBlockHash = tenderdashStatus.sync_info.latest_block_hash; const latestAppHash = tenderdashStatus.sync_info.latest_app_hash; + const latestBlockTime = tenderdashStatus.sync_info.latest_block_time; const platformPeers = parseInt(tenderdashNetInfo.n_peers, 10); const { listening } = tenderdashNetInfo; platform.tenderdash.version = version; platform.tenderdash.listening = listening; - platform.tenderdash.lastBlockHeight = lastBlockHeight; - platform.tenderdash.lastBlockHash = lastBlockHash; + platform.tenderdash.latestBlockHeight = latestBlockHeight; + platform.tenderdash.latestBlockHash = latestBlockHash; + platform.tenderdash.latestBlockTime = latestBlockTime; platform.tenderdash.catchingUp = catchingUp; platform.tenderdash.peers = platformPeers; platform.tenderdash.moniker = moniker; diff --git a/packages/dashmate/test/unit/status/scopes/overview.spec.js b/packages/dashmate/test/unit/status/scopes/overview.spec.js index b653c5fccef..6fce7913ff1 100644 --- a/packages/dashmate/test/unit/status/scopes/overview.spec.js +++ b/packages/dashmate/test/unit/status/scopes/overview.spec.js @@ -54,8 +54,9 @@ describe('getOverviewScopeFactory', () => { serviceStatus: ServiceStatusEnum.up, version: null, catchingUp: null, - lastBlockHeight: null, + latestBlockHeight: null, latestAppHash: null, + latestBlockTime: null, peers: null, network: null, }, diff --git a/packages/dashmate/test/unit/status/scopes/platform.spec.js b/packages/dashmate/test/unit/status/scopes/platform.spec.js index 43cf6135988..a8ba335abd4 100644 --- a/packages/dashmate/test/unit/status/scopes/platform.spec.js +++ b/packages/dashmate/test/unit/status/scopes/platform.spec.js @@ -128,7 +128,8 @@ describe('getPlatformScopeFactory', () => { expect(scope.tenderdash.serviceStatus).to.be.equal(ServiceStatusEnum.error); expect(scope.tenderdash.version).to.be.equal(null); expect(scope.tenderdash.catchingUp).to.be.equal(null); - expect(scope.tenderdash.lastBlockHeight).to.be.equal(null); + expect(scope.tenderdash.latestBlockHeight).to.be.equal(null); + expect(scope.tenderdash.latestBlockTime).to.be.equal(null); expect(scope.tenderdash.latestAppHash).to.be.equal(null); }); }); From 8a33dad962d87906445712d301478e6ae964a6d2 Mon Sep 17 00:00:00 2001 From: Anton Suprunchuk Date: Wed, 12 Apr 2023 17:34:24 +0700 Subject: [PATCH 16/21] fix(dpp): various fixes in DPP and system contracts (#907) Co-authored-by: Ivan Shumkov --- Cargo.lock | 13 +++ packages/rs-dpp/Cargo.toml | 2 +- .../rs-dpp/src/data_contract/data_contract.rs | 33 ++++++- ...data_contract_update_transition_factory.rs | 2 +- .../src/data_trigger/dpns_triggers/mod.rs | 22 ++--- .../feature_flags_data_triggers/mod.rs | 4 +- .../data_trigger/get_data_triggers_factory.rs | 26 +++--- packages/rs-dpp/src/data_trigger/mod.rs | 4 +- .../reward_share_data_triggers/mod.rs | 85 ++++++++++-------- .../rs-dpp/src/document/document_factory.rs | 12 +++ ...lidate_documents_batch_transition_state.rs | 2 +- .../identity_update_transition.rs | 15 +++- packages/rs-dpp/src/state_repository.rs | 13 +++ ..._transition_fee_from_operations_factory.rs | 77 +++++++++++++++- .../payloads/contract/dashpay-contract.json | 2 +- packages/rs-drive-verify-c-binding/build.rs | 2 - packages/rs-drive-verify-c-binding/src/lib.rs | 5 +- .../rs-drive-verify-c-binding/src/util.rs | 4 +- packages/wasm-dpp/Cargo.toml | 2 + .../lib/test/fixtures/getIdentityFixture.js | 40 +++++---- .../test/fixtures/getInstantLockFixture.js | 53 +++++++++++ packages/wasm-dpp/package.json | 1 + packages/wasm-dpp/scripts/build.sh | 2 +- packages/wasm-dpp/src/buffer.rs | 16 +++- .../wasm-dpp/src/dash_platform_protocol.rs | 4 + .../src/data_contract/data_contract.rs | 17 +++- .../src/data_contract/data_contract_facade.rs | 4 +- .../data_contract_update_transition/mod.rs | 20 ++++- .../src/document/extended_document.rs | 45 +++++++--- packages/wasm-dpp/src/document/mod.rs | 30 +++---- .../document_create_transition.rs | 12 ++- .../document_replace_transition.rs | 6 +- .../document_transition/mod.rs | 32 +++---- .../invalid_identity_public_key_type_error.rs | 13 +++ .../invalid_state_transition_type_error.rs | 7 ++ .../missing_state_transition_type_error.rs | 7 ++ packages/wasm-dpp/src/errors/mod.rs | 2 +- .../generate_temporary_ecdsa_private_key.rs | 19 ++++ .../src/identity/identity_public_key/mod.rs | 18 ++-- packages/wasm-dpp/src/identity/mod.rs | 14 ++- .../identity_public_key_transitions.rs | 4 +- packages/wasm-dpp/src/lib.rs | 1 + packages/wasm-dpp/src/metadata.rs | 2 +- packages/wasm-dpp/src/state_repository.rs | 50 ++++++++++- .../errors/invalid_state_transition_error.rs | 18 +++- .../fee/calculate_operation_fees.rs | 11 +-- ...te_state_transition_fee_from_operations.rs | 29 ++++++ .../wasm-dpp/src/state_transition/fee/mod.rs | 1 + .../operations/pre_calculated_operation.rs | 90 +++++++++++++++++-- .../fee/operations/read_operation.rs | 19 ++++ .../signature_verification_operation.rs | 15 ++++ .../src/state_transition/fee/refunds.rs | 17 ++++ packages/wasm-dpp/src/state_transition/mod.rs | 10 +++ .../state_transition_facade.rs | 26 +++++- packages/wasm-dpp/src/utils.rs | 25 +++++- .../src/validation/validation_result.rs | 37 +++++++- .../dataContract/DataContractFacade.spec.js | 43 ++++++++- .../validateDataContractFactory.spec.js | 4 +- ...entityUpdateTransitionStateFactory.spec.js | 2 +- .../generateTemporaryEcdsaPrivateKey.spec.js | 17 ++++ .../unit/dataContract/DataContract.spec.js | 1 + ...ataContractUpdateTransitionFactory.spec.js | 2 +- .../test/unit/document/Document.spec.js | 20 ++--- .../unit/document/DocumentFactory.spec.js | 2 +- ...plyDocumentsBatchTransitionFactory.spec.js | 2 +- ...cumentsBatchTransitionStateFactory.spec.js | 26 +++--- .../unit/identity/IdentityPublicKey.spec.js | 15 ++-- .../StateTransitionExecutionContext.spec.js | 5 +- .../StateTransitionFactory.spec.js | 3 +- ...TransitionIdentitySignatureFactory.spec.js | 4 +- 70 files changed, 939 insertions(+), 249 deletions(-) create mode 100644 packages/wasm-dpp/lib/test/fixtures/getInstantLockFixture.js create mode 100644 packages/wasm-dpp/src/generate_temporary_ecdsa_private_key.rs create mode 100644 packages/wasm-dpp/src/state_transition/fee/calculate_state_transition_fee_from_operations.rs create mode 100644 packages/wasm-dpp/test/integration/util/generateTemporaryEcdsaPrivateKey.spec.js diff --git a/Cargo.lock b/Cargo.lock index afd7ca339ea..c743c697dd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4056,12 +4056,25 @@ dependencies = [ "dpp", "itertools", "js-sys", + "log", "serde", "serde-wasm-bindgen", "serde_json", "thiserror", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-logger", + "web-sys", +] + +[[package]] +name = "wasm-logger" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718" +dependencies = [ + "log", + "wasm-bindgen", "web-sys", ] diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 8f69a04f78f..60b1c0866ba 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -35,7 +35,7 @@ jsonschema = { git = "https://github.com/fominok/jsonschema-rs", branch = "feat- "draft202012", ] } lazy_static = { version = "1.4" } -log = { version = "0.4" } +log = { version = "0.4.6" } num_enum = "0.5.7" bincode = { version = "2.0.0-rc.3", features = ["serde"] } rand = { version = "0.8.4", features = ["small_rng"] } diff --git a/packages/rs-dpp/src/data_contract/data_contract.rs b/packages/rs-dpp/src/data_contract/data_contract.rs index 80656165350..f9208367ac7 100644 --- a/packages/rs-dpp/src/data_contract/data_contract.rs +++ b/packages/rs-dpp/src/data_contract/data_contract.rs @@ -434,10 +434,37 @@ impl DataContract { self.document_types.get(document_type_name).is_some() } - pub fn set_document_schema(&mut self, doc_type: String, schema: JsonSchema) { + pub fn set_document_schema( + &mut self, + doc_type: String, + schema: JsonSchema, + ) -> Result<(), ProtocolError> { let binary_properties = get_binary_properties(&schema); - self.documents.insert(doc_type.clone(), schema); - self.binary_properties.insert(doc_type, binary_properties); + self.documents.insert(doc_type.clone(), schema.clone()); + self.binary_properties + .insert(doc_type.clone(), binary_properties); + + let document_type_value = platform_value::Value::from(schema); + + // Make sure the document_type_value is a map + let Some(document_type_value_map) = document_type_value.as_map() else { + return Err(ProtocolError::DataContractError(DataContractError::InvalidContractStructure( + "document type data is not a map as expected", + ))); + }; + + let document_type = DocumentType::from_platform_value( + self.id, + &doc_type, + document_type_value_map, + &BTreeMap::new(), + self.config.documents_keep_history_contract_default, + self.config.documents_mutable_contract_default, + )?; + + self.document_types.insert(doc_type, document_type); + + Ok(()) } pub fn get_document_schema(&self, doc_type: &str) -> Result<&JsonSchema, ProtocolError> { diff --git a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/apply_data_contract_update_transition_factory.rs b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/apply_data_contract_update_transition_factory.rs index 888954356ea..b3b7d9f5e00 100644 --- a/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/apply_data_contract_update_transition_factory.rs +++ b/packages/rs-dpp/src/data_contract/state_transition/data_contract_update_transition/apply_data_contract_update_transition_factory.rs @@ -30,7 +30,7 @@ where execution_context: &StateTransitionExecutionContext, ) -> Result<()> { self.state_repository - .store_data_contract( + .update_data_contract( state_transition.data_contract.clone(), Some(execution_context), ) diff --git a/packages/rs-dpp/src/data_trigger/dpns_triggers/mod.rs b/packages/rs-dpp/src/data_trigger/dpns_triggers/mod.rs index d9fd930eaac..178d9690776 100644 --- a/packages/rs-dpp/src/data_trigger/dpns_triggers/mod.rs +++ b/packages/rs-dpp/src/data_trigger/dpns_triggers/mod.rs @@ -77,13 +77,13 @@ where .map_err(ProtocolError::ValueError)?; let mut result = DataTriggerExecutionResult::default(); - let full_domain_name = normalized_label; + let mut full_domain_name = normalized_label.to_string(); if !is_dry_run { if full_domain_name.len() > MAX_PRINTABLE_DOMAIN_NAME_LENGTH { let err = create_error( context, - dt_create, + dt_create.base.id, format!( "Full domain name length can not be more than {} characters long but got {}", MAX_PRINTABLE_DOMAIN_NAME_LENGTH, @@ -96,7 +96,7 @@ where if normalized_label != label.to_lowercase() { let err = create_error( context, - dt_create, + dt_create.base.id, "Normalized label doesn't match label".to_string(), ); result.add_error(err.into()); @@ -109,7 +109,7 @@ where if id != owner_id { let err = create_error( context, - dt_create, + dt_create.base.id, format!( "ownerId {} doesn't match {} {}", owner_id, PROPERTY_DASH_UNIQUE_IDENTITY_ID, id @@ -126,7 +126,7 @@ where if id != owner_id { let err = create_error( context, - dt_create, + dt_create.base.id, format!( "ownerId {} doesn't match {} {}", owner_id, PROPERTY_DASH_ALIAS_IDENTITY_ID, id @@ -139,7 +139,7 @@ where if normalized_parent_domain_name.is_empty() && context.owner_id != top_level_identity { let err = create_error( context, - dt_create, + dt_create.base.id, "Can't create top level domain for this identity".to_string(), ); result.add_error(err.into()) @@ -147,6 +147,8 @@ where } if !normalized_parent_domain_name.is_empty() { + full_domain_name = format!("{full_domain_name}.{normalized_parent_domain_name}"); + //? What is the `normalized_parent_name`. Are we sure the content is a valid dot-separated data let mut parent_domain_segments = normalized_parent_domain_name.split('.'); let parent_domain_label = parent_domain_segments.next().unwrap().to_string(); @@ -175,7 +177,7 @@ where if documents.is_empty() { let err = create_error( context, - dt_create, + dt_create.base.id, "Parent domain is not present".to_string(), ); result.add_error(err.into()); @@ -186,7 +188,7 @@ where if rule_allow_subdomains { let err = create_error( context, - dt_create, + dt_create.base.id, "Allowing subdomains registration is forbidden for non top level domains" .to_string(), ); @@ -200,7 +202,7 @@ where { let err = create_error( context, - dt_create, + dt_create.base.id, "The subdomain can be created only by the parent domain owner".to_string(), ); result.add_error(err.into()); @@ -238,7 +240,7 @@ where if preorder_documents.is_empty() { let err = create_error( context, - dt_create, + dt_create.base.id, "preorderDocument was not found".to_string(), ); result.add_error(err.into()) diff --git a/packages/rs-dpp/src/data_trigger/feature_flags_data_triggers/mod.rs b/packages/rs-dpp/src/data_trigger/feature_flags_data_triggers/mod.rs index eed45ccf499..65ba4b16cd1 100644 --- a/packages/rs-dpp/src/data_trigger/feature_flags_data_triggers/mod.rs +++ b/packages/rs-dpp/src/data_trigger/feature_flags_data_triggers/mod.rs @@ -55,7 +55,7 @@ where if enable_at_height < block_height { let err = create_error( context, - dt_create, + dt_create.base.id, "This identity can't activate selected feature flag".to_string(), ); result.add_error(err.into()); @@ -65,7 +65,7 @@ where if context.owner_id != top_level_identity { let err = create_error( context, - dt_create, + dt_create.base.id, "This Identity can't activate selected feature flag".to_string(), ); result.add_error(err.into()); diff --git a/packages/rs-dpp/src/data_trigger/get_data_triggers_factory.rs b/packages/rs-dpp/src/data_trigger/get_data_triggers_factory.rs index 87837735cbe..d01fbd163be 100644 --- a/packages/rs-dpp/src/data_trigger/get_data_triggers_factory.rs +++ b/packages/rs-dpp/src/data_trigger/get_data_triggers_factory.rs @@ -15,6 +15,12 @@ use crate::{ use super::{DataTrigger, DataTriggerKind}; +// TODO: Move to system contract crates +pub const REWARD_SHARE_DOCUMENT_TYPE: &str = "rewardShare"; +pub const CONTACT_REQUEST_DOCUMENT_TYPE: &str = "contactRequest"; +pub const DOMAIN_DOCUMENT_TYPE: &str = "domain"; +pub const PREORDER_DOCUMENT_TYPE: &str = "preorder"; + /// returns Date Triggers filtered out by dataContractId, documentType, transactionAction pub fn get_data_triggers<'a>( data_contract_id: &'a Identifier, @@ -58,56 +64,56 @@ pub fn data_triggers() -> Result, ProtocolError> { let data_triggers = vec![ DataTrigger { data_contract_id: dpns_data_contract_id, - document_type: "domain".to_string(), + document_type: DOMAIN_DOCUMENT_TYPE.to_string(), transition_action: Action::Create, data_trigger_kind: DataTriggerKind::DataTriggerCreateDomain, top_level_identity: Some(dpns_owner_id), }, DataTrigger { data_contract_id: dpns_data_contract_id, - document_type: "domain".to_string(), + document_type: DOMAIN_DOCUMENT_TYPE.to_string(), transition_action: Action::Replace, data_trigger_kind: DataTriggerKind::DataTriggerReject, top_level_identity: None, }, DataTrigger { data_contract_id: dpns_data_contract_id, - document_type: "domain".to_string(), + document_type: DOMAIN_DOCUMENT_TYPE.to_string(), transition_action: Action::Delete, data_trigger_kind: DataTriggerKind::DataTriggerReject, top_level_identity: None, }, DataTrigger { data_contract_id: dpns_data_contract_id, - document_type: "preorder".to_string(), + document_type: PREORDER_DOCUMENT_TYPE.to_string(), transition_action: Action::Delete, data_trigger_kind: DataTriggerKind::DataTriggerReject, top_level_identity: None, }, DataTrigger { data_contract_id: dpns_data_contract_id, - document_type: "preorder".to_string(), + document_type: PREORDER_DOCUMENT_TYPE.to_string(), transition_action: Action::Delete, data_trigger_kind: DataTriggerKind::DataTriggerReject, top_level_identity: None, }, DataTrigger { data_contract_id: dashpay_data_contract_id, - document_type: "contactRequest".to_string(), + document_type: CONTACT_REQUEST_DOCUMENT_TYPE.to_string(), transition_action: Action::Create, data_trigger_kind: DataTriggerKind::CreateDataContractRequest, top_level_identity: None, }, DataTrigger { data_contract_id: dashpay_data_contract_id, - document_type: "contactRequest".to_string(), + document_type: CONTACT_REQUEST_DOCUMENT_TYPE.to_string(), transition_action: Action::Replace, data_trigger_kind: DataTriggerKind::DataTriggerReject, top_level_identity: None, }, DataTrigger { data_contract_id: dashpay_data_contract_id, - document_type: "contactRequest".to_string(), + document_type: CONTACT_REQUEST_DOCUMENT_TYPE.to_string(), transition_action: Action::Delete, data_trigger_kind: DataTriggerKind::DataTriggerReject, top_level_identity: None, @@ -135,14 +141,14 @@ pub fn data_triggers() -> Result, ProtocolError> { }, DataTrigger { data_contract_id: master_node_reward_shares_contract_id, - document_type: feature_flags_contract::types::UPDATE_CONSENSUS_PARAMS.to_string(), + document_type: REWARD_SHARE_DOCUMENT_TYPE.to_string(), transition_action: Action::Create, data_trigger_kind: DataTriggerKind::DataTriggerRewardShare, top_level_identity: None, }, DataTrigger { data_contract_id: master_node_reward_shares_contract_id, - document_type: "rewardShare".to_string(), + document_type: REWARD_SHARE_DOCUMENT_TYPE.to_string(), transition_action: Action::Replace, data_trigger_kind: DataTriggerKind::DataTriggerRewardShare, top_level_identity: None, diff --git a/packages/rs-dpp/src/data_trigger/mod.rs b/packages/rs-dpp/src/data_trigger/mod.rs index e7573394ccf..c1abf50f336 100644 --- a/packages/rs-dpp/src/data_trigger/mod.rs +++ b/packages/rs-dpp/src/data_trigger/mod.rs @@ -147,11 +147,11 @@ where fn create_error( context: &DataTriggerExecutionContext, - dt_create: &DocumentCreateTransition, + transition_id: Identifier, msg: String, ) -> DataTriggerError where SR: StateRepositoryLike, { - DataTriggerConditionError::new(context.data_contract.id, dt_create.base.id, msg).into() + DataTriggerConditionError::new(context.data_contract.id, transition_id, msg).into() } diff --git a/packages/rs-dpp/src/data_trigger/reward_share_data_triggers/mod.rs b/packages/rs-dpp/src/data_trigger/reward_share_data_triggers/mod.rs index 65df3398e75..f6d205d7cc6 100644 --- a/packages/rs-dpp/src/data_trigger/reward_share_data_triggers/mod.rs +++ b/packages/rs-dpp/src/data_trigger/reward_share_data_triggers/mod.rs @@ -32,17 +32,24 @@ where let is_dry_run = context.state_transition_execution_context.is_dry_run(); let owner_id = context.owner_id.to_string(Encoding::Base58); - let document_create_transition = match document_transition { - DocumentTransition::Create(document_create_transition) => document_create_transition, + let (transition_data, transition_base) = match document_transition { + DocumentTransition::Create(document_create_transition) => ( + document_create_transition.data.as_ref(), + &document_create_transition.base, + ), + DocumentTransition::Replace(document_replace_transition) => ( + document_replace_transition.data.as_ref(), + &document_replace_transition.base, + ), _ => bail!( - "the Document Transition {} isn't 'CREATE'", + "the Document Transition {} isn't 'CREATE or REPLACE'", get_from_transition!(document_transition, id) ), }; - let properties = document_create_transition.data.as_ref().ok_or_else(|| { + let properties = transition_data.ok_or_else(|| { anyhow!( "data isn't defined in Data Transition '{}'", - document_create_transition.base.id + transition_base.id ) })?; @@ -50,20 +57,26 @@ where let percentage = properties.get_integer(PROPERTY_PERCENTAGE)?; if !is_dry_run { - // Do not allow creating document if ownerId is not in SML - let sml_store: SMLStore = context.state_repository.fetch_sml_store().await?; - - let valid_master_nodes_list = sml_store.get_current_sml()?.get_valid_master_nodes(); - - let owner_id_in_sml = valid_master_nodes_list.iter().any(|entry| { - hex::decode(&entry.pro_reg_tx_hash).expect("invalid hex value") - == context.owner_id.to_buffer() - }); - - if !owner_id_in_sml { + let is_valid_master_node = context + .state_repository + .is_in_the_valid_master_nodes_list(context.owner_id.to_buffer()) + .await?; + + // TODO: bring it back once the SML store is implemented + // // Do not allow creating document if ownerId is not in SML + // let sml_store: SMLStore = context.state_repository.fetch_sml_store().await?; + // + // let valid_master_nodes_list = sml_store.get_current_sml()?.get_valid_master_nodes(); + // + // let owner_id_in_sml = valid_master_nodes_list.iter().any(|entry| { + // hex::decode(&entry.pro_reg_tx_hash).expect("invalid hex value") + // == context.owner_id.to_buffer() + // }); + + if !is_valid_master_node { let err = create_error( context, - document_create_transition, + transition_base.id, "Only masternode identities can share rewards".to_string(), ); result.add_error(err.into()); @@ -83,19 +96,20 @@ where if !is_dry_run && maybe_identity.is_none() { let err = create_error( context, - document_create_transition, + transition_base.id, format!("Identity '{}' doesn't exist", pay_to_identifier), ); - result.add_error(err.into()) + result.add_error(err.into()); + return Ok(result); } let documents_data = context .state_repository .fetch_documents( &context.data_contract.id, - &document_create_transition.base.document_type_name, + &transition_base.document_type_name, platform_value!({ - "where" : [ [ "$owner_id", "==", owner_id ]] + "where" : [ [ "$ownerId", "==", owner_id ]] }), Some(context.state_transition_execution_context), ) @@ -112,7 +126,7 @@ where if documents.len() >= MAX_DOCUMENTS { let err = create_error( context, - document_create_transition, + transition_base.id, format!( "Reward shares cannot contain more than {} identities", MAX_DOCUMENTS @@ -130,7 +144,7 @@ where if total_percent > MAX_PERCENTAGE { let err = create_error( context, - document_create_transition, + transition_base.id, format!("Percentage can not be more than {}", MAX_PERCENTAGE), ); result.add_error(err.into()); @@ -246,6 +260,7 @@ mod test { sml_store, data_contract, top_level_identifier, + identity, .. } = setup_test(); @@ -257,11 +272,11 @@ mod test { let mut state_repository_mock = MockStateRepositoryLike::new(); state_repository_mock - .expect_fetch_sml_store() - .returning(move || Ok(sml_store.clone())); + .expect_is_in_the_valid_master_nodes_list() + .returning(move |_| Ok(true)); state_repository_mock .expect_fetch_identity() - .returning(|_, _| Ok(None)); + .returning(move |_, _| Ok(Some(identity.clone()))); state_repository_mock .expect_fetch_documents() .returning(move |_, _, _, _| Ok(documents.clone())); @@ -281,7 +296,7 @@ mod test { create_masternode_reward_shares_data_trigger(&document_transition, &context, None) .await; - let percentage_error = get_data_trigger_error(&result, 1); + let percentage_error = get_data_trigger_error(&result, 0); assert_eq!( "Percentage can not be more than 10000", percentage_error.to_string() @@ -300,8 +315,8 @@ mod test { let mut state_repository_mock = MockStateRepositoryLike::new(); state_repository_mock - .expect_fetch_sml_store() - .returning(move || Ok(sml_store.clone())); + .expect_is_in_the_valid_master_nodes_list() + .returning(move |_| Ok(true)); state_repository_mock .expect_fetch_identity() .returning(move |_, _| Ok(None)); @@ -345,8 +360,8 @@ mod test { let mut state_repository_mock = MockStateRepositoryLike::new(); state_repository_mock - .expect_fetch_sml_store() - .returning(move || Ok(sml_store.clone())); + .expect_is_in_the_valid_master_nodes_list() + .returning(move |_| Ok(false)); state_repository_mock .expect_fetch_identity() .returning(move |_, _| Ok(None)); @@ -385,8 +400,8 @@ mod test { let mut state_repository_mock = MockStateRepositoryLike::new(); state_repository_mock - .expect_fetch_sml_store() - .returning(move || Ok(sml_store.clone())); + .expect_is_in_the_valid_master_nodes_list() + .returning(move |_| Ok(true)); state_repository_mock .expect_fetch_identity() .returning(move |_, _| Ok(Some(identity.clone()))); @@ -421,8 +436,8 @@ mod test { let mut state_repository_mock = MockStateRepositoryLike::new(); state_repository_mock - .expect_fetch_sml_store() - .returning(move || Ok(sml_store.clone())); + .expect_is_in_the_valid_master_nodes_list() + .returning(move |_| Ok(true)); state_repository_mock .expect_fetch_identity() .returning(move |_, _| Ok(Some(identity.clone()))); diff --git a/packages/rs-dpp/src/document/document_factory.rs b/packages/rs-dpp/src/document/document_factory.rs index e13f62fc2ca..c195feb6681 100644 --- a/packages/rs-dpp/src/document/document_factory.rs +++ b/packages/rs-dpp/src/document/document_factory.rs @@ -404,6 +404,18 @@ where let new_revision = document_revision + 1; map.insert(PROPERTY_REVISION.to_string(), Value::U64(new_revision)); + // If document have an originally set `updatedAt` + // we should update it then + let contains_updated_at = document + .document_type()? + .required_fields + .contains(PROPERTY_UPDATED_AT); + + if contains_updated_at { + let now = Utc::now().timestamp_millis() as TimestampMillis; + map.insert(PROPERTY_UPDATED_AT.to_string(), Value::U64(now)); + } + raw_transitions.push(map.into()); } Ok(raw_transitions) diff --git a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs index 63e3384d528..6001f612bd0 100644 --- a/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs +++ b/packages/rs-dpp/src/document/state_transition/documents_batch_transition/validation/state/validate_documents_batch_transition_state.rs @@ -152,7 +152,7 @@ pub async fn validate_document_transitions( // Data Contract must exist let data_contract = state_repository - .fetch_data_contract(data_contract_id, None) + .fetch_data_contract(data_contract_id, Some(&tmp_execution_context)) .await? .map(TryInto::try_into) .transpose() diff --git a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs index e409c3aabb5..8bf5d44a26c 100644 --- a/packages/rs-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs +++ b/packages/rs-dpp/src/identity/state_transition/identity_update_transition/identity_update_transition.rs @@ -347,10 +347,12 @@ impl StateTransitionConvert for IdentityUpdateTransition { add_public_keys.push(key.to_raw_object(skip_signature)?); } - value.insert( - property_names::ADD_PUBLIC_KEYS.to_owned(), - Value::Array(add_public_keys), - )?; + if !add_public_keys.is_empty() { + value.insert_at_end( + property_names::ADD_PUBLIC_KEYS.to_owned(), + Value::Array(add_public_keys), + )?; + } Ok(value) } @@ -389,6 +391,11 @@ impl StateTransitionConvert for IdentityUpdateTransition { Ok(value) } + + // Override to_canonical_cleaned_object to manage add_public_keys individually + fn to_canonical_cleaned_object(&self, skip_signature: bool) -> Result { + self.to_cleaned_object(skip_signature) + } } impl StateTransitionLike for IdentityUpdateTransition { diff --git a/packages/rs-dpp/src/state_repository.rs b/packages/rs-dpp/src/state_repository.rs index 7dc4c529382..38d5cf0ede6 100644 --- a/packages/rs-dpp/src/state_repository.rs +++ b/packages/rs-dpp/src/state_repository.rs @@ -54,6 +54,7 @@ pub trait StateRepositoryLike: Sync { execution_context: Option<&'a StateTransitionExecutionContext>, ) -> AnyResult>; + // TODO(wasm-dpp): rename to `create_data_contract` /// Store Data Contract async fn store_data_contract<'a>( &self, @@ -61,6 +62,12 @@ pub trait StateRepositoryLike: Sync { execution_context: Option<&'a StateTransitionExecutionContext>, ) -> AnyResult<()>; + async fn update_data_contract<'a>( + &self, + data_contract: DataContract, + execution_context: Option<&'a StateTransitionExecutionContext>, + ) -> AnyResult<()>; + /// Fetch Documents by Data Contract Id and type /// By default, the method should return data as bytes (`Vec`), but the deserialization to [`Document`] should be also possible async fn fetch_documents<'a>( @@ -225,6 +232,12 @@ pub trait StateRepositoryLike: Sync { where T: for<'de> serde::de::Deserialize<'de> + 'static; + /// Check if AssetLock Transaction outPoint exists in spent list + async fn is_in_the_valid_master_nodes_list( + &self, + out_point_buffer: [u8; 32], + ) -> AnyResult; + // Get latest (in a queue) withdrawal transaction index async fn fetch_latest_withdrawal_transaction_index(&self) -> AnyResult; diff --git a/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_from_operations_factory.rs b/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_from_operations_factory.rs index 89eee9ed9b1..59bcae604dd 100644 --- a/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_from_operations_factory.rs +++ b/packages/rs-dpp/src/state_transition/fee/calculate_state_transition_fee_from_operations_factory.rs @@ -40,8 +40,19 @@ fn calculate_state_transition_fee_from_operations_with_custom_calculator( .fold(0, |sum, (_, credits)| sum + credits); } - let required_amount = (storage_fee - total_refunds) + DEFAULT_USER_TIP; - let desired_amount = (storage_fee + processing_fee - total_refunds) + DEFAULT_USER_TIP; + let required_amount = if storage_fee > total_refunds { + (storage_fee - total_refunds) + DEFAULT_USER_TIP + } else { + 0 + }; + + let fee_sum = storage_fee + processing_fee; + + let desired_amount = if fee_sum > total_refunds { + (fee_sum - total_refunds) + DEFAULT_USER_TIP + } else { + 0 + }; Ok(FeeResult { storage_fee, @@ -55,8 +66,15 @@ fn calculate_state_transition_fee_from_operations_with_custom_calculator( #[cfg(test)] mod test { + use dashcore::blockdata::script::Instruction::Op; + use platform_value::Identifier; use std::collections::HashMap; + use crate::identity::KeyType::ECDSA_SECP256K1; + use crate::state_transition::fee::calculate_operation_fees::calculate_operation_fees; + use crate::state_transition::fee::operations::{ + PreCalculatedOperation, SignatureVerificationOperation, + }; use crate::{ state_transition::fee::{ operations::Operation, Credits, DummyFeesResult, FeeResult, Refunds, @@ -109,4 +127,59 @@ mod test { }; assert_eq!(expected, result); } + + #[test] + fn test_with_negative_credits() { + // Set of operations that produced by Document Remove Transition + let operations = vec![ + Operation::PreCalculated(PreCalculatedOperation { + storage_cost: 0, + processing_cost: 551320, + fee_refunds: vec![], + }), + Operation::SignatureVerification(SignatureVerificationOperation { + signature_type: ECDSA_SECP256K1, + }), + Operation::PreCalculated(PreCalculatedOperation { + storage_cost: 0, + processing_cost: 551320, + fee_refunds: vec![], + }), + Operation::PreCalculated(PreCalculatedOperation { + storage_cost: 0, + processing_cost: 191260, + fee_refunds: vec![], + }), + Operation::PreCalculated(PreCalculatedOperation { + storage_cost: 0, + processing_cost: 16870910, + fee_refunds: vec![Refunds { + identifier: Identifier::new([ + 130, 188, 56, 7, 78, 143, 58, 212, 133, 162, 145, 56, 186, 219, 191, 75, + 64, 112, 236, 226, 135, 75, 132, 170, 135, 243, 180, 110, 103, 161, 153, + 252, + ]), + credits_per_epoch: [("0".to_string(), 114301030)].iter().cloned().collect(), + }], + }), + ]; + + let identifier = Identifier::new([ + 130, 188, 56, 7, 78, 143, 58, 212, 133, 162, 145, 56, 186, 219, 191, 75, 64, 112, 236, + 226, 135, 75, 132, 170, 135, 243, 180, 110, 103, 161, 153, 252, + ]); + + // Panics because of negative credits + let result = calculate_state_transition_fee_from_operations_with_custom_calculator( + &operations, + &identifier, + calculate_operation_fees, + ); + + assert!(matches!(result, Ok(FeeResult { + desired_amount, + required_amount, + .. + }) if desired_amount == 0 && required_amount == 0)); + } } diff --git a/packages/rs-dpp/src/tests/payloads/contract/dashpay-contract.json b/packages/rs-dpp/src/tests/payloads/contract/dashpay-contract.json index d6459d7e807..48a48db48c2 100644 --- a/packages/rs-dpp/src/tests/payloads/contract/dashpay-contract.json +++ b/packages/rs-dpp/src/tests/payloads/contract/dashpay-contract.json @@ -192,4 +192,4 @@ "additionalProperties": false } } -} \ No newline at end of file +} diff --git a/packages/rs-drive-verify-c-binding/build.rs b/packages/rs-drive-verify-c-binding/build.rs index 9369ce57341..1d94716d7fc 100644 --- a/packages/rs-drive-verify-c-binding/build.rs +++ b/packages/rs-drive-verify-c-binding/build.rs @@ -1,5 +1,3 @@ - - use std::env; fn main() { diff --git a/packages/rs-drive-verify-c-binding/src/lib.rs b/packages/rs-drive-verify-c-binding/src/lib.rs index ebf54d86c68..047b90961cc 100644 --- a/packages/rs-drive-verify-c-binding/src/lib.rs +++ b/packages/rs-drive-verify-c-binding/src/lib.rs @@ -8,11 +8,11 @@ use crate::types::{ PublicKeyHashIdentityMap, }; use crate::util::{build_c_identity_struct, extract_vector_from_pointer, vec_to_pointer}; +use drive::drive::verify::AssetLockProof as DppAssetLockProof; use drive::drive::verify::Identity as DppIdentity; -use drive::drive::verify::{AssetLockProof as DppAssetLockProof}; use drive::drive::Drive; use std::collections::BTreeMap; -use std::{slice}; +use std::slice; #[no_mangle] pub unsafe extern "C" fn verify_full_identity_by_public_key_hash( @@ -216,6 +216,7 @@ pub unsafe extern "C" fn verify_identity_ids_by_public_key_hashes( #[cfg(test)] mod tests { use super::*; + use drive::drive::verify::RootHash; use drive::drive::Drive; use std::collections::BTreeMap; diff --git a/packages/rs-drive-verify-c-binding/src/util.rs b/packages/rs-drive-verify-c-binding/src/util.rs index 3d8cf531096..1a0f6dee355 100644 --- a/packages/rs-drive-verify-c-binding/src/util.rs +++ b/packages/rs-drive-verify-c-binding/src/util.rs @@ -1,6 +1,4 @@ -use crate::types::{ - AssetLockProof, IdPublicKeyMap, Identity, IdentityPublicKey, MetaData, -}; +use crate::types::{AssetLockProof, IdPublicKeyMap, Identity, IdentityPublicKey, MetaData}; use crate::{DppAssetLockProof, DppIdentity}; use std::{mem, slice}; diff --git a/packages/wasm-dpp/Cargo.toml b/packages/wasm-dpp/Cargo.toml index b0fd96745d2..911271d11b7 100644 --- a/packages/wasm-dpp/Cargo.toml +++ b/packages/wasm-dpp/Cargo.toml @@ -18,6 +18,8 @@ serde-wasm-bindgen = { git="https://github.com/QuantumExplorer/serde-wasm-bindge dpp = { path = "../rs-dpp", default-features = false, features=["cbor"] } itertools = { version="0.10.5"} console_error_panic_hook = { version="0.1.7"} +log = { version = "0.4.6" } +wasm-logger = { version = "0.2.0" } wasm-bindgen-futures = "0.4.33" async-trait = "0.1.59" diff --git a/packages/wasm-dpp/lib/test/fixtures/getIdentityFixture.js b/packages/wasm-dpp/lib/test/fixtures/getIdentityFixture.js index b1029f6f74d..5af5cbad60c 100644 --- a/packages/wasm-dpp/lib/test/fixtures/getIdentityFixture.js +++ b/packages/wasm-dpp/lib/test/fixtures/getIdentityFixture.js @@ -7,7 +7,7 @@ let staticId = null; /** * @return {Identity} */ -module.exports = async function getIdentityFixture(id = staticId) { +module.exports = async function getIdentityFixture(id = staticId, publicKeys = undefined) { ({ Identity, IdentityPublicKey } = await loadWasmDpp()); if (!staticId) { @@ -19,30 +19,32 @@ module.exports = async function getIdentityFixture(id = staticId) { id = staticId; } + const preCreatedPublicKeys = [ + { + id: 0, + type: IdentityPublicKey.TYPES.ECDSA_SECP256K1, + data: Buffer.from('AuryIuMtRrl/VviQuyLD1l4nmxi9ogPzC9LT7tdpo0di', 'base64'), + purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, + securityLevel: IdentityPublicKey.SECURITY_LEVELS.MASTER, + readOnly: false, + }, + { + id: 1, + type: IdentityPublicKey.TYPES.ECDSA_SECP256K1, + data: Buffer.from('A8AK95PYMVX5VQKzOhcVQRCUbc9pyg3RiL7jttEMDU+L', 'base64'), + purpose: IdentityPublicKey.PURPOSES.ENCRYPTION, + securityLevel: IdentityPublicKey.SECURITY_LEVELS.MEDIUM, + readOnly: false, + }, + ]; + const rawIdentity = { // TODO: obtain latest version from some wasm binding? protocolVersion: 1, id, // TODO: should be probably id.toBuffer(), but it causes panic in IdentityWasm balance: 10, revision: 0, - publicKeys: [ - { - id: 0, - type: IdentityPublicKey.TYPES.ECDSA_SECP256K1, - data: Buffer.from('AuryIuMtRrl/VviQuyLD1l4nmxi9ogPzC9LT7tdpo0di', 'base64'), - purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, - securityLevel: IdentityPublicKey.SECURITY_LEVELS.MASTER, - readOnly: false, - }, - { - id: 1, - type: IdentityPublicKey.TYPES.ECDSA_SECP256K1, - data: Buffer.from('A8AK95PYMVX5VQKzOhcVQRCUbc9pyg3RiL7jttEMDU+L', 'base64'), - purpose: IdentityPublicKey.PURPOSES.ENCRYPTION, - securityLevel: IdentityPublicKey.SECURITY_LEVELS.MEDIUM, - readOnly: false, - }, - ], + publicKeys: publicKeys === undefined ? preCreatedPublicKeys : publicKeys, }; return new Identity(rawIdentity); diff --git a/packages/wasm-dpp/lib/test/fixtures/getInstantLockFixture.js b/packages/wasm-dpp/lib/test/fixtures/getInstantLockFixture.js new file mode 100644 index 00000000000..ec26db769bc --- /dev/null +++ b/packages/wasm-dpp/lib/test/fixtures/getInstantLockFixture.js @@ -0,0 +1,53 @@ +const { + Transaction, + InstantLock, + PrivateKey, + Script, + Opcode, +} = require('@dashevo/dashcore-lib'); + +/** + * @param {PrivateKey} [oneTimePrivateKey] + */ +function getInstantLockFixture(oneTimePrivateKey = new PrivateKey()) { + const privateKeyHex = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY'; + const privateKey = new PrivateKey(privateKeyHex); + const fromAddress = privateKey.toAddress(); + + const oneTimePublicKey = oneTimePrivateKey.toPublicKey(); + + const transaction = new Transaction() + .from({ + address: fromAddress, + txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458', + outputIndex: 0, + script: Script.buildPublicKeyHashOut(fromAddress) + .toString(), + satoshis: 100000, + }) + // eslint-disable-next-line no-underscore-dangle + .addBurnOutput(90000, oneTimePublicKey._getID()) + .to(fromAddress, 5000) + .addOutput(Transaction.Output({ + satoshis: 5000, + script: Script() + .add(Opcode.OP_RETURN) + .add(Buffer.from([1, 2, 3])), + })) + .sign(privateKey); + + return new InstantLock({ + version: 1, + inputs: [ + { + outpointHash: '6e200d059fb567ba19e92f5c2dcd3dde522fd4e0a50af223752db16158dabb1d', + outpointIndex: 0, + }, + ], + txid: transaction.id, + cyclehash: '7c30826123d0f29fe4c4a8895d7ba4eb469b1fafa6ad7b23896a1a591766a536', + signature: '8967c46529a967b3822e1ba8a173066296d02593f0f59b3a78a30a7eef9c8a120847729e62e4a32954339286b79fe7590221331cd28d576887a263f45b595d499272f656c3f5176987c976239cac16f972d796ad82931d532102a4f95eec7d80', + }); +} + +module.exports = getInstantLockFixture; diff --git a/packages/wasm-dpp/package.json b/packages/wasm-dpp/package.json index b3e998f386b..3d94f00fd43 100644 --- a/packages/wasm-dpp/package.json +++ b/packages/wasm-dpp/package.json @@ -9,6 +9,7 @@ "test": "yarn run test:node && yarn run test:browsers", "test:browsers": "karma start ./karma.conf.js --single-run", "test:node": "NODE_ENV=test mocha", + "tsc": "tsc", "lint": "eslint .", "lint:fix": "eslint . --fix", "clean": "yarn exec scripts/clean.sh" diff --git a/packages/wasm-dpp/scripts/build.sh b/packages/wasm-dpp/scripts/build.sh index cab572ac793..ec83e49c0e9 100755 --- a/packages/wasm-dpp/scripts/build.sh +++ b/packages/wasm-dpp/scripts/build.sh @@ -18,7 +18,7 @@ fi OUTPUT_DIR="$PWD/wasm" OUTPUT_FILE="$OUTPUT_DIR/wasm_dpp_bg.wasm" OUTPUT_FILE_JS="$OUTPUT_DIR/wasm_dpp_bg.js" -BUILD_COMMAND="cargo build --target=$TARGET $PROFILE_ARG" +BUILD_COMMAND="cargo build --config net.git-fetch-with-cli=true --target=$TARGET $PROFILE_ARG" BINDGEN_COMMAND="wasm-bindgen --out-dir=$OUTPUT_DIR --target=web --omit-default-module-path ../../target/$TARGET/$PROFILE/wasm_dpp.wasm" DIST_TYPINGS="$PWD/dist/wasm/wasm_dpp.d.ts" diff --git a/packages/wasm-dpp/src/buffer.rs b/packages/wasm-dpp/src/buffer.rs index c0b6db19c9f..efae80aaccc 100644 --- a/packages/wasm-dpp/src/buffer.rs +++ b/packages/wasm-dpp/src/buffer.rs @@ -1,3 +1,4 @@ +use js_sys::ArrayBuffer; use wasm_bindgen::prelude::*; #[wasm_bindgen] @@ -7,12 +8,21 @@ extern "C" { #[wasm_bindgen(constructor)] pub fn new() -> Buffer; - #[wasm_bindgen(constructor, js_name = "from")] + #[wasm_bindgen(static_method_of = Buffer, js_name = from)] pub fn from_bytes(js_sys: &[u8]) -> Buffer; - #[wasm_bindgen(constructor, js_name = "from")] + #[wasm_bindgen(static_method_of = Buffer, js_name = from)] pub fn from_bytes_owned(js_sys: Vec) -> Buffer; - #[wasm_bindgen(constructor, js_name = "from")] + #[wasm_bindgen(static_method_of = Buffer, js_name = from)] pub fn from_string(js_sys: String) -> Buffer; + + #[wasm_bindgen(method, getter)] + pub fn buffer(this: &Buffer) -> ArrayBuffer; + + #[wasm_bindgen(method, getter, js_name = byteOffset)] + pub fn byte_offset(this: &Buffer) -> u32; + + #[wasm_bindgen(method, getter)] + pub fn length(this: &Buffer) -> u32; } diff --git a/packages/wasm-dpp/src/dash_platform_protocol.rs b/packages/wasm-dpp/src/dash_platform_protocol.rs index c92ace32451..aeed22ba06d 100644 --- a/packages/wasm-dpp/src/dash_platform_protocol.rs +++ b/packages/wasm-dpp/src/dash_platform_protocol.rs @@ -45,6 +45,10 @@ impl DashPlatformProtocol { entropy_generator: ExternalEntropyGenerator, maybe_protocol_version: Option, ) -> Result { + if cfg!(debug_assertions) { + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + } + let bls = BlsAdapter(bls_adapter); let protocol_version = maybe_protocol_version.unwrap_or(LATEST_VERSION); let public_keys_validator = Arc::new(PublicKeysValidator::new(bls.clone()).unwrap()); diff --git a/packages/wasm-dpp/src/data_contract/data_contract.rs b/packages/wasm-dpp/src/data_contract/data_contract.rs index 6d44bbda575..43f7c28b65f 100644 --- a/packages/wasm-dpp/src/data_contract/data_contract.rs +++ b/packages/wasm-dpp/src/data_contract/data_contract.rs @@ -16,7 +16,7 @@ use dpp::{platform_value, Convertible}; use crate::errors::RustConversionError; use crate::identifier::identifier_from_js_value; use crate::metadata::MetadataWasm; -use crate::utils::WithJsError; +use crate::utils::{IntoWasm, WithJsError}; use crate::{bail_js, with_js_error}; use crate::{buffer::Buffer, identifier::IdentifierWrapper}; @@ -181,7 +181,9 @@ impl DataContractWasm { schema: JsValue, ) -> Result<(), JsValue> { let json_schema: JsonValue = with_js_error!(serde_wasm_bindgen::from_value(schema))?; - self.0.set_document_schema(doc_type, json_schema); + self.0 + .set_document_schema(doc_type, json_schema) + .with_js_error()?; Ok(()) } @@ -260,8 +262,15 @@ impl DataContractWasm { } #[wasm_bindgen(js_name=setMetadata)] - pub fn set_metadata(&mut self, metadata: MetadataWasm) { - self.0.metadata = Some(metadata.into()); + pub fn set_metadata(&mut self, metadata: JsValue) -> Result<(), JsValue> { + self.0.metadata = if !metadata.is_falsy() { + let metadata = metadata.to_wasm::("Metadata")?; + Some(metadata.to_owned().into()) + } else { + None + }; + + Ok(()) } #[wasm_bindgen(js_name=toObject)] diff --git a/packages/wasm-dpp/src/data_contract/data_contract_facade.rs b/packages/wasm-dpp/src/data_contract/data_contract_facade.rs index 11227b95e86..3e7563a32e9 100644 --- a/packages/wasm-dpp/src/data_contract/data_contract_facade.rs +++ b/packages/wasm-dpp/src/data_contract/data_contract_facade.rs @@ -119,10 +119,10 @@ impl DataContractFacadeWasm { #[wasm_bindgen(js_name=createDataContractUpdateTransition)] pub fn create_data_contract_update_transition( &self, - data_contract: DataContractWasm, + data_contract: &DataContractWasm, ) -> Result { self.0 - .create_data_contract_update_transition(data_contract.into()) + .create_data_contract_update_transition(data_contract.to_owned().into()) .map(Into::into) .map_err(from_protocol_error) } diff --git a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs index 01f5f90da16..4b89def776f 100644 --- a/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs +++ b/packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs @@ -156,13 +156,31 @@ impl DataContractUpdateTransitionWasm { pub fn to_object(&self, skip_signature: Option) -> Result { let serde_object = self .0 - .to_object(skip_signature.unwrap_or(false)) + .to_cleaned_object(skip_signature.unwrap_or(false)) .map_err(from_protocol_error)?; serde_object .serialize(&serde_wasm_bindgen::Serializer::json_compatible()) .map_err(|e| e.into()) } + #[wasm_bindgen] + pub fn sign( + &mut self, + identity_public_key: &IdentityPublicKeyWasm, + private_key: Vec, + bls: JsBlsAdapter, + ) -> Result<(), JsValue> { + let bls_adapter = BlsAdapter(bls); + + self.0 + .sign( + &identity_public_key.to_owned().into(), + &private_key, + &bls_adapter, + ) + .with_js_error() + } + #[wasm_bindgen(js_name=verifySignature)] pub fn verify_signature( &self, diff --git a/packages/wasm-dpp/src/document/extended_document.rs b/packages/wasm-dpp/src/document/extended_document.rs index 91d53f5ad1a..662018c8c3c 100644 --- a/packages/wasm-dpp/src/document/extended_document.rs +++ b/packages/wasm-dpp/src/document/extended_document.rs @@ -2,7 +2,7 @@ use dpp::document::document_transition::document_base_transition::JsonValue; use dpp::document::{ExtendedDocument, EXTENDED_DOCUMENT_IDENTIFIER_FIELDS}; use dpp::platform_value::{Bytes32, Value}; -use dpp::prelude::{Identifier, Revision}; +use dpp::prelude::{Identifier, Revision, TimestampMillis}; use dpp::util::json_schema::JsonSchemaExt; use dpp::util::json_value::JsonValueExt; @@ -17,7 +17,7 @@ use crate::document::BinaryType; use crate::errors::RustConversionError; use crate::identifier::{identifier_from_js_value, IdentifierWrapper}; use crate::lodash::lodash_set; -use crate::utils::{with_serde_to_platform_value, ToSerdeJSONExt, WithJsError}; +use crate::utils::{with_serde_to_platform_value, IntoWasm, ToSerdeJSONExt, WithJsError}; use crate::{with_js_error, ConversionOptions, DocumentWasm}; use crate::{DataContractWasm, MetadataWasm}; @@ -220,23 +220,37 @@ impl ExtendedDocumentWasm { } #[wasm_bindgen(js_name=setCreatedAt)] - pub fn set_created_at(&mut self, ts: f64) { - self.0.document.created_at = Some(ts as u64); + pub fn set_created_at(&mut self, ts: Option) { + if let Some(ts) = ts { + self.0.document.created_at = Some(ts.get_time() as TimestampMillis); + } else { + self.0.document.created_at = None; + } } #[wasm_bindgen(js_name=setUpdatedAt)] - pub fn set_updated_at(&mut self, ts: f64) { - self.0.document.updated_at = Some(ts as u64); + pub fn set_updated_at(&mut self, ts: Option) { + if let Some(ts) = ts { + self.0.document.updated_at = Some(ts.get_time() as TimestampMillis); + } else { + self.0.document.updated_at = None; + } } #[wasm_bindgen(js_name=getCreatedAt)] - pub fn get_created_at(&self) -> Option { - self.0.document.created_at.map(|v| v as f64) + pub fn get_created_at(&self) -> Option { + self.0 + .document + .created_at + .map(|v| js_sys::Date::new(&JsValue::from_f64(v as f64))) } #[wasm_bindgen(js_name=getUpdatedAt)] - pub fn get_updated_at(&self) -> Option { - self.0.document.updated_at.map(|v| v as f64) + pub fn get_updated_at(&self) -> Option { + self.0 + .document + .updated_at + .map(|v| js_sys::Date::new(&JsValue::from_f64(v as f64))) } #[wasm_bindgen(js_name=getMetadata)] @@ -245,8 +259,15 @@ impl ExtendedDocumentWasm { } #[wasm_bindgen(js_name=setMetadata)] - pub fn set_metadata(&mut self, metadata: &MetadataWasm) { - self.0.metadata = Some(metadata.to_owned().into()); + pub fn set_metadata(&mut self, metadata: JsValue) -> Result<(), JsValue> { + self.0.metadata = if !metadata.is_falsy() { + let metadata = metadata.to_wasm::("Metadata")?; + Some(metadata.to_owned().into()) + } else { + None + }; + + Ok(()) } #[wasm_bindgen(js_name=toObject)] diff --git a/packages/wasm-dpp/src/document/mod.rs b/packages/wasm-dpp/src/document/mod.rs index 2f798a16e0e..5231ae57974 100644 --- a/packages/wasm-dpp/src/document/mod.rs +++ b/packages/wasm-dpp/src/document/mod.rs @@ -33,6 +33,7 @@ use dpp::document::{Document, EXTENDED_DOCUMENT_IDENTIFIER_FIELDS, IDENTIFIER_FI pub use extended_document::ExtendedDocumentWasm; use dpp::document::extended_document::property_names; +use dpp::identity::TimestampMillis; use dpp::platform_value::btreemap_extensions::BTreeValueMapReplacementPathHelper; use dpp::platform_value::converter::serde_json::BTreeValueJsonConverter; use dpp::platform_value::ReplacementType; @@ -197,32 +198,27 @@ impl DocumentWasm { } #[wasm_bindgen(js_name=setCreatedAt)] - pub fn set_created_at(&mut self, number: JsValue) -> Result<(), JsValue> { - let ts = try_to_u64(number) - .context("setting createdAt in Document") - .with_js_error()?; - - self.0.created_at = Some(ts); - Ok(()) + pub fn set_created_at(&mut self, created_at: Option) { + self.0.created_at = created_at.map(|timestamp| timestamp.get_time() as TimestampMillis); } #[wasm_bindgen(js_name=setUpdatedAt)] - pub fn set_updated_at(&mut self, number: JsValue) -> Result<(), JsValue> { - let ts = try_to_u64(number) - .context("setting updatedAt in Document") - .with_js_error()?; - self.0.updated_at = Some(ts); - Ok(()) + pub fn set_updated_at(&mut self, updated_at: Option) { + self.0.updated_at = updated_at.map(|timestamp| timestamp.get_time() as TimestampMillis); } #[wasm_bindgen(js_name=getCreatedAt)] - pub fn get_created_at(&self) -> Option { - self.0.created_at.map(|v| v as f64) + pub fn get_created_at(&self) -> Option { + self.0 + .created_at + .map(|v| js_sys::Date::new(&JsValue::from_f64(v as f64))) } #[wasm_bindgen(js_name=getUpdatedAt)] - pub fn get_updated_at(&self) -> Option { - self.0.updated_at.map(|v| v as f64) + pub fn get_updated_at(&self) -> Option { + self.0 + .updated_at + .map(|v| js_sys::Date::new(&JsValue::from_f64(v as f64))) } #[wasm_bindgen(js_name=toObject)] diff --git a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/document_create_transition.rs b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/document_create_transition.rs index 33d6584ba7d..c5f0c771f2f 100644 --- a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/document_create_transition.rs +++ b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/document_create_transition.rs @@ -81,13 +81,17 @@ impl DocumentCreateTransitionWasm { } #[wasm_bindgen(js_name=getCreatedAt)] - pub fn created_at(&self) -> Option { - self.inner.created_at + pub fn created_at(&self) -> Option { + self.inner + .created_at + .map(|timestamp| js_sys::Date::new(&JsValue::from_f64(timestamp as f64))) } #[wasm_bindgen(js_name=getUpdatedAt)] - pub fn updated_at(&self) -> Option { - self.inner.updated_at + pub fn updated_at(&self) -> Option { + self.inner + .updated_at + .map(|timestamp| js_sys::Date::new(&JsValue::from_f64(timestamp as f64))) } #[wasm_bindgen(js_name=getRevision)] diff --git a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/document_replace_transition.rs b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/document_replace_transition.rs index 051c616ab06..49dda970b4c 100644 --- a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/document_replace_transition.rs +++ b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/document_replace_transition.rs @@ -84,8 +84,10 @@ impl DocumentReplaceTransitionWasm { } #[wasm_bindgen(js_name=getUpdatedAt)] - pub fn updated_at(&self) -> Option { - self.inner.updated_at + pub fn updated_at(&self) -> Option { + self.inner + .updated_at + .map(|timestamp| js_sys::Date::new(&JsValue::from_f64(timestamp as f64))) } #[wasm_bindgen(js_name=toObject)] diff --git a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/mod.rs b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/mod.rs index e5fc1b79795..3a983bd9140 100644 --- a/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/mod.rs +++ b/packages/wasm-dpp/src/document/state_transition/document_batch_transition/document_transition/mod.rs @@ -8,6 +8,7 @@ pub use document_delete_transition::*; pub use document_replace_transition::*; use dpp::platform_value::Value; +use dpp::prelude::TimestampMillis; use dpp::{ document::document_transition::{ DocumentCreateTransition, DocumentDeleteTransition, DocumentReplaceTransition, @@ -79,46 +80,33 @@ impl DocumentTransitionWasm { #[wasm_bindgen(js_name=getCreatedAt)] pub fn get_created_at(&self) -> JsValue { if let Some(created_at) = self.0.get_created_at() { - (created_at as f64).into() + js_sys::Date::new(&JsValue::from_f64(created_at as f64)).into() } else { JsValue::NULL } } + #[wasm_bindgen(js_name=getUpdatedAt)] pub fn get_updated_at(&self) -> JsValue { if let Some(updated_at) = self.0.get_updated_at() { - (updated_at as f64).into() + js_sys::Date::new(&JsValue::from_f64(updated_at as f64)).into() } else { JsValue::NULL } } #[wasm_bindgen(js_name=setUpdatedAt)] - pub fn set_updated_at(&mut self, js_timestamp_millis: JsValue) -> Result<(), JsValue> { - if js_timestamp_millis.is_undefined() || js_timestamp_millis.is_null() { - self.0.set_updated_at(None); - return Ok(()); - } - let timestamp_millis = try_to_u64(js_timestamp_millis) - .context("setting updatedAt in DocumentsBatchTransition") - .with_js_error()?; - self.0.set_updated_at(Some(timestamp_millis)); + pub fn set_updated_at(&mut self, updated_at: Option) -> Result<(), JsValue> { + self.0 + .set_updated_at(updated_at.map(|timestamp| timestamp.get_time() as TimestampMillis)); Ok(()) } #[wasm_bindgen(js_name=setCreatedAt)] - pub fn set_created_at(&mut self, js_timestamp_millis: JsValue) -> Result<(), JsValue> { - if js_timestamp_millis.is_undefined() || js_timestamp_millis.is_null() { - self.0.set_created_at(None); - return Ok(()); - } - let timestamp_millis = try_to_u64(js_timestamp_millis) - .context("setting createdAt in DocumentsBatchTransition") - .with_js_error()?; - self.0.set_created_at(Some(timestamp_millis)); - - Ok(()) + pub fn set_created_at(&mut self, created_at: Option) { + self.0 + .set_created_at(created_at.map(|timestamp| timestamp.get_time() as TimestampMillis)); } #[wasm_bindgen(js_name=getData)] diff --git a/packages/wasm-dpp/src/errors/consensus/basic/identity/invalid_identity_public_key_type_error.rs b/packages/wasm-dpp/src/errors/consensus/basic/identity/invalid_identity_public_key_type_error.rs index 1fe0755ae78..9e623178ead 100644 --- a/packages/wasm-dpp/src/errors/consensus/basic/identity/invalid_identity_public_key_type_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/basic/identity/invalid_identity_public_key_type_error.rs @@ -1,7 +1,11 @@ +use std::convert::TryFrom; + use crate::buffer::Buffer; +use crate::utils::WithJsError; use dpp::consensus::codes::ErrorWithCode; use dpp::consensus::signature::InvalidIdentityPublicKeyTypeError; use dpp::consensus::ConsensusError; +use dpp::identity::KeyType; use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name=InvalidIdentityPublicKeyTypeError)] @@ -17,6 +21,15 @@ impl From<&InvalidIdentityPublicKeyTypeError> for InvalidIdentityPublicKeyTypeEr #[wasm_bindgen(js_class=InvalidIdentityPublicKeyTypeError)] impl InvalidIdentityPublicKeyTypeErrorWasm { + #[wasm_bindgen(constructor)] + pub fn new(key_type: u8) -> Result { + Ok(Self { + inner: InvalidIdentityPublicKeyTypeError::new( + KeyType::try_from(key_type).with_js_error()?, + ), + }) + } + #[wasm_bindgen(js_name=getPublicKeyType)] pub fn get_public_key_type(&self) -> u8 { self.inner.public_key_type() as u8 diff --git a/packages/wasm-dpp/src/errors/consensus/basic/state_transition/invalid_state_transition_type_error.rs b/packages/wasm-dpp/src/errors/consensus/basic/state_transition/invalid_state_transition_type_error.rs index a2deaa122a3..c55cb460d56 100644 --- a/packages/wasm-dpp/src/errors/consensus/basic/state_transition/invalid_state_transition_type_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/basic/state_transition/invalid_state_transition_type_error.rs @@ -17,6 +17,13 @@ impl From<&InvalidStateTransitionTypeError> for InvalidStateTransitionTypeErrorW #[wasm_bindgen(js_class=InvalidStateTransitionTypeError)] impl InvalidStateTransitionTypeErrorWasm { + #[wasm_bindgen(constructor)] + pub fn new(transition_type: u8) -> Self { + Self { + inner: InvalidStateTransitionTypeError::new(transition_type), + } + } + #[wasm_bindgen(js_name=getType)] pub fn get_type(&self) -> u8 { self.inner.transition_type() diff --git a/packages/wasm-dpp/src/errors/consensus/basic/state_transition/missing_state_transition_type_error.rs b/packages/wasm-dpp/src/errors/consensus/basic/state_transition/missing_state_transition_type_error.rs index caeebd8acdb..94251e6eae7 100644 --- a/packages/wasm-dpp/src/errors/consensus/basic/state_transition/missing_state_transition_type_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/basic/state_transition/missing_state_transition_type_error.rs @@ -17,6 +17,13 @@ impl From<&MissingStateTransitionTypeError> for MissingStateTransitionTypeErrorW #[wasm_bindgen(js_class=MissingStateTransitionTypeError)] impl MissingStateTransitionTypeErrorWasm { + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self { + inner: MissingStateTransitionTypeError::new(), + } + } + #[wasm_bindgen(js_name=getCode)] pub fn get_code(&self) -> u32 { ConsensusError::from(self.inner.clone()).code() diff --git a/packages/wasm-dpp/src/errors/mod.rs b/packages/wasm-dpp/src/errors/mod.rs index fd8e6193505..9e517961adf 100644 --- a/packages/wasm-dpp/src/errors/mod.rs +++ b/packages/wasm-dpp/src/errors/mod.rs @@ -13,5 +13,5 @@ pub use public_key_validation_error::*; mod compatible_protocol_version_is_not_defined_error; pub mod data_contract_not_present_error; pub mod dpp_error; -mod value_error; +pub mod value_error; pub use compatible_protocol_version_is_not_defined_error::*; diff --git a/packages/wasm-dpp/src/generate_temporary_ecdsa_private_key.rs b/packages/wasm-dpp/src/generate_temporary_ecdsa_private_key.rs new file mode 100644 index 00000000000..2fa33a91d21 --- /dev/null +++ b/packages/wasm-dpp/src/generate_temporary_ecdsa_private_key.rs @@ -0,0 +1,19 @@ +use crate::buffer::Buffer; +use dpp::dashcore::secp256k1::rand::thread_rng; +use dpp::dashcore::secp256k1::Secp256k1; +use dpp::dashcore::{ + secp256k1::SecretKey, InstantLock, Network, OutPoint, PrivateKey, Script, Transaction, TxIn, + TxOut, Txid, +}; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; + +#[wasm_bindgen(js_name = generateTemporaryEcdsaPrivateKey)] +pub fn generate_temporary_ecdsa_private_key() -> JsValue { + let mut rng = thread_rng(); + + let secret_key = SecretKey::new(&mut rng); + let one_time_private_key = PrivateKey::new(secret_key, Network::Testnet).to_string(); + + JsValue::from_str(&one_time_private_key) +} diff --git a/packages/wasm-dpp/src/identity/identity_public_key/mod.rs b/packages/wasm-dpp/src/identity/identity_public_key/mod.rs index 4f5ab91492c..92500dd1604 100644 --- a/packages/wasm-dpp/src/identity/identity_public_key/mod.rs +++ b/packages/wasm-dpp/src/identity/identity_public_key/mod.rs @@ -6,7 +6,7 @@ use wasm_bindgen::prelude::*; use crate::utils::{Inner, WithJsError}; use crate::{buffer::Buffer, utils, with_js_error}; -use dpp::identity::{IdentityPublicKey, KeyID}; +use dpp::identity::{IdentityPublicKey, KeyID, TimestampMillis}; use dpp::platform_value::BinaryData; use dpp::Convertible; @@ -67,8 +67,8 @@ impl IdentityPublicKeyWasm { } #[wasm_bindgen(js_name=getData)] - pub fn get_data(&self) -> Vec { - self.0.data.to_vec() + pub fn get_data(&self) -> Buffer { + Buffer::from_bytes_owned(self.0.data.to_vec()) } #[wasm_bindgen(js_name=setPurpose)] @@ -108,14 +108,16 @@ impl IdentityPublicKeyWasm { } #[wasm_bindgen(js_name=setDisabledAt)] - pub fn set_disabled_at(&mut self, timestamp: u32) { - // TODO: It's not gonna work, must be BigInt - self.0.set_disabled_at(timestamp as u64); + pub fn set_disabled_at(&mut self, timestamp: js_sys::Date) { + self.0 + .set_disabled_at(timestamp.get_time() as TimestampMillis); } #[wasm_bindgen(js_name=getDisabledAt)] - pub fn get_disabled_at(&self) -> Option { - self.0.disabled_at.map(|timestamp| timestamp as f64) + pub fn get_disabled_at(&self) -> Option { + self.0 + .disabled_at + .map(|timestamp| js_sys::Date::new(&JsValue::from_f64(timestamp as f64))) } #[wasm_bindgen(js_name=hash)] diff --git a/packages/wasm-dpp/src/identity/mod.rs b/packages/wasm-dpp/src/identity/mod.rs index 618dfeb7c1a..2deaf9968e8 100644 --- a/packages/wasm-dpp/src/identity/mod.rs +++ b/packages/wasm-dpp/src/identity/mod.rs @@ -15,12 +15,13 @@ use dpp::metadata::Metadata; use dpp::{Convertible, ProtocolError}; use crate::identifier::IdentifierWrapper; -use crate::utils::{to_vec_of_serde_values, WithJsError}; +use crate::utils::{to_vec_of_serde_values, IntoWasm, WithJsError}; use crate::MetadataWasm; use crate::{utils, with_js_error}; pub use identity_public_key::*; use crate::buffer::Buffer; +use crate::errors::from_dpp_err; pub use state_transition::*; pub mod errors; @@ -50,7 +51,7 @@ impl IdentityWasm { let raw_identity: Value = serde_json::from_str(&identity_json).map_err(|e| e.to_string())?; - let identity = Identity::from_json(raw_identity).unwrap(); + let identity = Identity::from_json(raw_identity).map_err(from_dpp_err)?; Ok(IdentityWasm(identity)) } @@ -148,8 +149,13 @@ impl IdentityWasm { } #[wasm_bindgen(js_name=setMetadata)] - pub fn set_metadata(&mut self, metadata: MetadataWasm) { - self.0.set_metadata(metadata.into()); + pub fn set_metadata(&mut self, metadata: JsValue) -> Result<(), JsValue> { + if !metadata.is_falsy() { + let metadata = metadata.to_wasm::("Metadata")?; + self.0.set_metadata(metadata.to_owned().into()) + } + + Ok(()) } #[wasm_bindgen(js_name=getMetadata)] diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_public_key_transitions.rs b/packages/wasm-dpp/src/identity/state_transition/identity_public_key_transitions.rs index 8327698636c..3149d845b6f 100644 --- a/packages/wasm-dpp/src/identity/state_transition/identity_public_key_transitions.rs +++ b/packages/wasm-dpp/src/identity/state_transition/identity_public_key_transitions.rs @@ -64,8 +64,8 @@ impl IdentityPublicKeyWithWitnessWasm { } #[wasm_bindgen(js_name=getData)] - pub fn get_data(&self) -> Vec { - self.0.data.to_vec() + pub fn get_data(&self) -> Buffer { + Buffer::from_bytes_owned(self.0.data.to_vec()) } #[wasm_bindgen(js_name=setPurpose)] diff --git a/packages/wasm-dpp/src/lib.rs b/packages/wasm-dpp/src/lib.rs index 39f810a3b17..fff1a8c5885 100644 --- a/packages/wasm-dpp/src/lib.rs +++ b/packages/wasm-dpp/src/lib.rs @@ -29,5 +29,6 @@ mod bls_adapter; mod buffer; mod decode_protocol_entity; mod entropy_generator; +mod generate_temporary_ecdsa_private_key; mod lodash; mod validation; diff --git a/packages/wasm-dpp/src/metadata.rs b/packages/wasm-dpp/src/metadata.rs index e0e62ab7c56..b482473f08e 100644 --- a/packages/wasm-dpp/src/metadata.rs +++ b/packages/wasm-dpp/src/metadata.rs @@ -9,7 +9,7 @@ use dpp::util::deserializer::ProtocolVersion; use dpp::util::json_value::JsonValueExt; #[wasm_bindgen(js_name=Metadata)] -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct MetadataWasm(Metadata); impl std::convert::From for MetadataWasm { diff --git a/packages/wasm-dpp/src/state_repository.rs b/packages/wasm-dpp/src/state_repository.rs index 9915b5cf493..c6611c81d93 100644 --- a/packages/wasm-dpp/src/state_repository.rs +++ b/packages/wasm-dpp/src/state_repository.rs @@ -55,6 +55,13 @@ extern "C" { execution_context: &JsValue, ) -> Result<(), JsValue>; + #[wasm_bindgen(catch, structural, method, js_name=updateDataContract)] + pub async fn update_data_contract( + this: &ExternalStateRepositoryLike, + data_contract: DataContractWasm, + execution_context: &JsValue, + ) -> Result<(), JsValue>; + #[wasm_bindgen(catch, structural, method, js_name=createDocument)] pub async fn create_document( this: &ExternalStateRepositoryLike, @@ -217,6 +224,15 @@ extern "C" { execution_context: &JsValue, ) -> Result<(), JsValue>; + #[wasm_bindgen(catch, structural, method, js_name=fetchSMLStore)] + pub async fn fetch_sml_store(this: &ExternalStateRepositoryLike) -> Result; + + #[wasm_bindgen(catch, structural, method, js_name=isInTheValidMasterNodesList)] + async fn is_in_the_valid_master_nodes_list( + this: &ExternalStateRepositoryLike, + id: Buffer, + ) -> Result; + #[wasm_bindgen(catch, structural, method, js_name=fetchLatestPlatformBlockHeader)] pub async fn fetch_latest_platform_block_header( this: &ExternalStateRepositoryLike, @@ -324,6 +340,17 @@ impl StateRepositoryLike for ExternalStateRepositoryLikeWrapper { .map_err(from_js_error) } + async fn update_data_contract<'a>( + &self, + data_contract: DataContract, + execution_context: Option<&'a StateTransitionExecutionContext>, + ) -> anyhow::Result<()> { + self.0 + .update_data_contract(data_contract.into(), &ctx_to_js_value(execution_context)) + .await + .map_err(from_js_error) + } + async fn fetch_documents<'a>( &self, contract_id: &Identifier, @@ -455,7 +482,7 @@ impl StateRepositoryLike for ExternalStateRepositoryLikeWrapper { .await .map_err(from_js_error)?; - if maybe_identity.is_undefined() { + if maybe_identity.is_undefined() || maybe_identity.is_null() { return Ok(None); } @@ -543,7 +570,7 @@ impl StateRepositoryLike for ExternalStateRepositoryLikeWrapper { .await .map_err(from_js_error)?; - if maybe_balance.is_undefined() { + if maybe_balance.is_undefined() || maybe_balance.is_null() { return Ok(None); } @@ -568,7 +595,7 @@ impl StateRepositoryLike for ExternalStateRepositoryLikeWrapper { .await .map_err(from_js_error)?; - if maybe_balance.is_undefined() { + if maybe_balance.is_undefined() || maybe_balance.is_null() { return Ok(None); } @@ -630,6 +657,7 @@ impl StateRepositoryLike for ExternalStateRepositoryLikeWrapper { _amount: u64, _execution_context: Option<&'a StateTransitionExecutionContext>, ) -> anyhow::Result<()> { + // TODO(wasm-dpp): !!!Missing implementation will hang js-drive and no one will have a clue what's going on todo!() } @@ -650,7 +678,7 @@ impl StateRepositoryLike for ExternalStateRepositoryLikeWrapper { .await .map_err(from_js_error)?; - if maybe_height.is_undefined() { + if maybe_height.is_undefined() || maybe_height.is_null() { return Ok(None); } @@ -731,7 +759,20 @@ impl StateRepositoryLike for ExternalStateRepositoryLikeWrapper { todo!() } + async fn is_in_the_valid_master_nodes_list(&self, id: [u8; 32]) -> anyhow::Result { + let is_valid = self + .0 + .is_in_the_valid_master_nodes_list(Buffer::from_bytes(&id)) + .await + .map_err(from_js_error)?; + + is_valid + .as_bool() + .ok_or_else(|| anyhow!("Value is not a bool")) + } + async fn fetch_latest_withdrawal_transaction_index(&self) -> anyhow::Result { + // TODO(wasm-dpp): !!!Missing implementation will hang js-drive and no one will have a clue what's going on todo!() } @@ -740,6 +781,7 @@ impl StateRepositoryLike for ExternalStateRepositoryLikeWrapper { _index: u64, _transaction_bytes: Vec, ) -> anyhow::Result<()> { + // TODO(wasm-dpp): !!!Missing implementation will hang js-drive and no one will have a clue what's going on todo!() } diff --git a/packages/wasm-dpp/src/state_transition/errors/invalid_state_transition_error.rs b/packages/wasm-dpp/src/state_transition/errors/invalid_state_transition_error.rs index 470c112a6ea..90f201e2cfb 100644 --- a/packages/wasm-dpp/src/state_transition/errors/invalid_state_transition_error.rs +++ b/packages/wasm-dpp/src/state_transition/errors/invalid_state_transition_error.rs @@ -1,7 +1,10 @@ use dpp::consensus::ConsensusError; use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; -use crate::errors::consensus::consensus_error::from_consensus_error_ref; +use crate::{ + buffer::Buffer, errors::consensus::consensus_error::from_consensus_error_ref, + utils::consensus_errors_from_buffers, +}; #[wasm_bindgen(js_name = InvalidStateTransitionError)] pub struct InvalidStateTransitionErrorWasm { @@ -20,6 +23,19 @@ impl InvalidStateTransitionErrorWasm { #[wasm_bindgen(js_class = InvalidStateTransitionError)] impl InvalidStateTransitionErrorWasm { + #[wasm_bindgen(constructor)] + pub fn new_wasm( + error_buffers: Vec, + raw_state_transition: JsValue, + ) -> Result { + let consensus_errors = consensus_errors_from_buffers(error_buffers)?; + + Ok(InvalidStateTransitionErrorWasm::new( + consensus_errors, + raw_state_transition, + )) + } + #[wasm_bindgen(js_name = getErrors)] pub fn get_errors(&self) -> Vec { self.errors.iter().map(from_consensus_error_ref).collect() diff --git a/packages/wasm-dpp/src/state_transition/fee/calculate_operation_fees.rs b/packages/wasm-dpp/src/state_transition/fee/calculate_operation_fees.rs index 8a270bbede5..4e24ee92e26 100644 --- a/packages/wasm-dpp/src/state_transition/fee/calculate_operation_fees.rs +++ b/packages/wasm-dpp/src/state_transition/fee/calculate_operation_fees.rs @@ -6,12 +6,9 @@ use dpp::{ }; use wasm_bindgen::prelude::*; -use crate::{ - fee::dummy_fee_result::DummyFeesResultWasm, - utils::{Inner, IntoWasm, WithJsError}, -}; +use crate::{fee::dummy_fee_result::DummyFeesResultWasm, utils::WithJsError}; -use super::OperationWasm; +use crate::state_transition::conversion::create_operation_from_wasm_instance; #[wasm_bindgen(js_name=calculateOperationFees)] pub fn calculate_operation_fees_wasm( @@ -19,8 +16,8 @@ pub fn calculate_operation_fees_wasm( ) -> Result { let mut inner_operations: Vec = vec![]; for operation in operations.iter() { - let operation = operation.to_wasm::("Operation")?.to_owned(); - inner_operations.push(operation.into_inner()) + let operation = create_operation_from_wasm_instance(&operation)?; + inner_operations.push(operation); } Ok(calculate_operation_fees(&inner_operations) diff --git a/packages/wasm-dpp/src/state_transition/fee/calculate_state_transition_fee_from_operations.rs b/packages/wasm-dpp/src/state_transition/fee/calculate_state_transition_fee_from_operations.rs new file mode 100644 index 00000000000..7303c0fcb8d --- /dev/null +++ b/packages/wasm-dpp/src/state_transition/fee/calculate_state_transition_fee_from_operations.rs @@ -0,0 +1,29 @@ +use dpp::state_transition::fee::calculate_state_transition_fee_from_operations_factory::calculate_state_transition_fee_from_operations; +use dpp::state_transition::fee::operations::Operation; +use dpp::ProtocolError; +use wasm_bindgen::prelude::*; + +use crate::fee::fee_result::FeeResultWasm; +use crate::identifier::IdentifierWrapper; +use crate::state_transition::conversion::create_operation_from_wasm_instance; +use crate::utils::WithJsError; + +#[wasm_bindgen(js_name=calculateStateTransitionFeeFromOperations)] +pub fn calculate_state_transition_fee_from_operations_wasm( + operations: js_sys::Array, + identity_id: &IdentifierWrapper, +) -> Result { + let mut inner_operations: Vec = vec![]; + for operation in operations.iter() { + let operation = create_operation_from_wasm_instance(&operation)?; + inner_operations.push(operation); + } + + Ok(calculate_state_transition_fee_from_operations( + &inner_operations, + &(identity_id.to_owned()).into(), + ) + .map_err(ProtocolError::from) + .with_js_error()? + .into()) +} diff --git a/packages/wasm-dpp/src/state_transition/fee/mod.rs b/packages/wasm-dpp/src/state_transition/fee/mod.rs index c87ea551280..ccecd5249a0 100644 --- a/packages/wasm-dpp/src/state_transition/fee/mod.rs +++ b/packages/wasm-dpp/src/state_transition/fee/mod.rs @@ -4,6 +4,7 @@ use wasm_bindgen::prelude::*; use crate::utils::Inner; mod calculate_operation_fees; mod calculate_state_transition_fee; +mod calculate_state_transition_fee_from_operations; mod dummy_fee_result; mod fee_result; mod operations; diff --git a/packages/wasm-dpp/src/state_transition/fee/operations/pre_calculated_operation.rs b/packages/wasm-dpp/src/state_transition/fee/operations/pre_calculated_operation.rs index 640c05606e2..7d29f492a4f 100644 --- a/packages/wasm-dpp/src/state_transition/fee/operations/pre_calculated_operation.rs +++ b/packages/wasm-dpp/src/state_transition/fee/operations/pre_calculated_operation.rs @@ -1,17 +1,21 @@ use crate::{fee::dummy_fee_result::DummyFeesResultWasm, utils::Inner}; +use dpp::platform_value::Error as PlatformValueError; use dpp::{ state_transition::fee::{ operations::{OperationLike, PreCalculatedOperation}, - Refunds, + Credits, Refunds, }, ProtocolError, }; use js_sys::{Array, BigInt}; +use std::collections::HashMap; use wasm_bindgen::prelude::*; +use crate::errors::value_error::PlatformValueErrorWasm; +use crate::utils::ToSerdeJSONExt; use crate::{ fee::refunds::RefundsWasm, - utils::{try_to_u64, IntoWasm, WithJsError}, + utils::{try_to_u64, WithJsError}, }; #[wasm_bindgen(js_name = "PreCalculatedOperation")] @@ -42,12 +46,40 @@ impl PreCalculatedOperationWasm { let processing_cost = try_to_u64(processing_cost).with_js_error()?; let mut refunds = vec![]; + // TODO(wasm-dpp): any chance to make this parsing simpler? :) for refund in js_fee_refunds.iter() { - let transition: Refunds = refund - .to_wasm::("Refunds")? - .to_owned() - .into_inner(); - refunds.push(transition); + let parsed_refund = refund.with_serde_to_platform_value()?; + let identifier = parsed_refund + .get_identifier("identifier") + .map_err(PlatformValueErrorWasm::from)?; + + let mut credits_per_epoch: HashMap = HashMap::new(); + if let Some(credits_per_epoch_value) = parsed_refund + .get("creditsPerEpoch") + .map_err(PlatformValueErrorWasm::from)? + { + let credits_per_epoch_map = credits_per_epoch_value.as_map().ok_or_else(|| { + let error = + PlatformValueError::PathError("Credits per epoch is not a map".to_string()); + PlatformValueErrorWasm::from(error) + })?; + + for (epoch, credits) in credits_per_epoch_map { + let epoch = epoch.to_str().map_err(PlatformValueErrorWasm::from)?; + let credits = + credits + .as_integer::() + .ok_or(PlatformValueErrorWasm::from(PlatformValueError::PathError( + "Credits per epoch is not an integer".to_string(), + )))?; + + credits_per_epoch.insert(String::from(epoch), credits); + } + } + refunds.push(Refunds { + identifier, + credits_per_epoch, + }); } Ok(PreCalculatedOperation::new(storage_cost, processing_cost, refunds).into()) @@ -92,6 +124,50 @@ impl PreCalculatedOperationWasm { None } } + + pub fn refunds_as_objects(&self) -> Result, JsValue> { + let array_refunds = Array::new(); + if let Some(refunds) = self.0.get_refunds() { + for refund in refunds { + let refund_wasm: RefundsWasm = refund.into(); + array_refunds.push(&refund_wasm.to_object()?); + } + Ok(Some(array_refunds)) + } else { + Ok(None) + } + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + let json = js_sys::Object::new(); + + js_sys::Reflect::set( + &json, + &JsValue::from_str("type"), + &JsValue::from_str("preCalculated"), + )?; + + js_sys::Reflect::set( + &json, + &JsValue::from_str("storageCost"), + &JsValue::from(self.storage_cost()?), + )?; + + js_sys::Reflect::set( + &json, + &JsValue::from_str("processingCost"), + &JsValue::from(self.processing_cost()?), + )?; + + js_sys::Reflect::set( + &json, + &JsValue::from_str("feeRefunds"), + &JsValue::from(self.refunds_as_objects()?.unwrap_or(Array::new())), + )?; + + Ok(json.into()) + } } impl Inner for PreCalculatedOperationWasm { diff --git a/packages/wasm-dpp/src/state_transition/fee/operations/read_operation.rs b/packages/wasm-dpp/src/state_transition/fee/operations/read_operation.rs index 9d980bfbdb3..e3235f9e37b 100644 --- a/packages/wasm-dpp/src/state_transition/fee/operations/read_operation.rs +++ b/packages/wasm-dpp/src/state_transition/fee/operations/read_operation.rs @@ -67,6 +67,25 @@ impl ReadOperationWasm { None } } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + let json = js_sys::Object::new(); + + js_sys::Reflect::set( + &json, + &JsValue::from_str("type"), + &JsValue::from_str("read"), + )?; + + js_sys::Reflect::set( + &json, + &JsValue::from_str("valueSize"), + &JsValue::from(self.0.value_size), + )?; + + Ok(json.into()) + } } impl Inner for ReadOperationWasm { diff --git a/packages/wasm-dpp/src/state_transition/fee/operations/signature_verification_operation.rs b/packages/wasm-dpp/src/state_transition/fee/operations/signature_verification_operation.rs index 85fbc7cd360..3b747de4a6b 100644 --- a/packages/wasm-dpp/src/state_transition/fee/operations/signature_verification_operation.rs +++ b/packages/wasm-dpp/src/state_transition/fee/operations/signature_verification_operation.rs @@ -74,6 +74,21 @@ impl SignatureVerificationOperationWasm { None } } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + let json = js_sys::Object::new(); + + js_sys::Reflect::set(&json, &"type".into(), &"signatureVerification".into())?; + + js_sys::Reflect::set( + &json, + &"signatureType".into(), + &JsValue::from(self.0.signature_type as u8), + )?; + + Ok(json.into()) + } } impl Inner for SignatureVerificationOperationWasm { diff --git a/packages/wasm-dpp/src/state_transition/fee/refunds.rs b/packages/wasm-dpp/src/state_transition/fee/refunds.rs index 10cbd37b5cd..6013f526ae9 100644 --- a/packages/wasm-dpp/src/state_transition/fee/refunds.rs +++ b/packages/wasm-dpp/src/state_transition/fee/refunds.rs @@ -4,6 +4,7 @@ use dpp::state_transition::fee::Refunds; use js_sys::BigInt; use wasm_bindgen::prelude::*; +use crate::buffer::Buffer; use crate::{identifier::IdentifierWrapper, utils::Inner}; #[derive(Clone)] @@ -21,6 +22,22 @@ impl RefundsWasm { pub fn credits_per_epoch(&self) -> js_sys::Map { convert_hashmap_to_jsmap(&self.0.credits_per_epoch) } + + #[wasm_bindgen(js_name=toObject)] + pub fn to_object(&self) -> Result { + let object = js_sys::Object::new(); + + let identifier = Buffer::from_bytes(self.0.identifier.as_slice()); + + js_sys::Reflect::set(&object, &"identifier".into(), &identifier)?; + js_sys::Reflect::set( + &object, + &"creditsPerEpoch".into(), + &self.credits_per_epoch(), + )?; + + Ok(object.into()) + } } impl From for RefundsWasm { diff --git a/packages/wasm-dpp/src/state_transition/mod.rs b/packages/wasm-dpp/src/state_transition/mod.rs index 1dc022b9d63..c85f84b4408 100644 --- a/packages/wasm-dpp/src/state_transition/mod.rs +++ b/packages/wasm-dpp/src/state_transition/mod.rs @@ -73,6 +73,11 @@ impl StateTransitionExecutionContextWasm { self.0.disable_dry_run(); } + #[wasm_bindgen(js_name=isDryRun)] + pub fn is_dry_run(&self) -> bool { + self.0.is_dry_run() + } + #[wasm_bindgen(js_name=addOperation)] pub fn add_operation(&self, operation: JsValue) -> Result<(), JsValue> { let operation = create_operation_from_wasm_instance(&operation)?; @@ -96,6 +101,11 @@ impl StateTransitionExecutionContextWasm { }) .collect() } + + #[wasm_bindgen(js_name=clearDryOperations)] + pub fn clear_dry_run_operations(&self) { + self.0.clear_dry_run_operations(); + } } impl Inner for StateTransitionExecutionContextWasm { diff --git a/packages/wasm-dpp/src/state_transition/state_transition_facade.rs b/packages/wasm-dpp/src/state_transition/state_transition_facade.rs index 0096f9b5a17..0f337b12d7b 100644 --- a/packages/wasm-dpp/src/state_transition/state_transition_facade.rs +++ b/packages/wasm-dpp/src/state_transition/state_transition_facade.rs @@ -1,4 +1,5 @@ use crate::bls_adapter::BlsAdapter; +use std::convert::TryInto; use crate::state_repository::{ExternalStateRepositoryLike, ExternalStateRepositoryLikeWrapper}; use crate::state_transition_factory::StateTransitionFactoryWasm; @@ -6,11 +7,18 @@ use crate::utils::{ToSerdeJSONExt, WithJsError}; use crate::validation::ValidationResultWasm; use crate::{with_js_error, StateTransitionExecutionContextWasm}; use dpp::state_transition::state_transition_execution_context::StateTransitionExecutionContext; -use dpp::state_transition::{StateTransitionConvert, StateTransitionFacade, ValidateOptions}; +use dpp::state_transition::{ + StateTransitionConvert, StateTransitionFacade, StateTransitionLike, StateTransitionType, + ValidateOptions, +}; use dpp::version::ProtocolVersionValidator; use serde::Deserialize; use crate::errors::protocol_error::from_protocol_error; +use crate::errors::value_error::PlatformValueErrorWasm; +use dpp::data_contract::state_transition::data_contract_update_transition::DataContractUpdateTransition; +use dpp::document::DocumentsBatchTransition; +use dpp::ProtocolError; use std::sync::Arc; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; @@ -54,7 +62,21 @@ impl StateTransitionFacadeWasm { Default::default() }; - let raw_state_transition = raw_state_transition.with_serde_to_platform_value()?; + let mut raw_state_transition = raw_state_transition.with_serde_to_platform_value()?; + let state_transition_type: StateTransitionType = raw_state_transition + .get_integer::("type") + .map_err(|e| PlatformValueErrorWasm::from(e))? + .try_into() + .map_err(|_| JsValue::from_str("Invalid state transition type"))?; + + // TODO(wasm-dpp): clean values of other transitions as well? + match state_transition_type { + StateTransitionType::DataContractUpdate => { + DataContractUpdateTransition::clean_value(&mut raw_state_transition) + .map_err(|e| PlatformValueErrorWasm::from(e))?; + } + _ => {} + } let result = self .0 diff --git a/packages/wasm-dpp/src/utils.rs b/packages/wasm-dpp/src/utils.rs index 4a7b90061af..aee52dcb73a 100644 --- a/packages/wasm-dpp/src/utils.rs +++ b/packages/wasm-dpp/src/utils.rs @@ -3,17 +3,21 @@ use std::convert::TryInto; use anyhow::{anyhow, bail}; use dpp::{ + consensus::ConsensusError, dashcore::{anyhow, anyhow::Context}, ProtocolError, }; use dpp::platform_value::Value; -use js_sys::Function; +use js_sys::{Function, Uint8Array}; use serde::de::DeserializeOwned; use serde_json::Value as JsonValue; use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*}; -use crate::errors::{from_dpp_err, RustConversionError}; +use crate::{ + buffer::Buffer, + errors::{from_dpp_err, RustConversionError}, +}; pub trait ToSerdeJSONExt { fn with_serde_to_json_value(&self) -> Result; @@ -280,3 +284,20 @@ pub(crate) trait Inner { fn inner(&self) -> &Self::InnerItem; fn inner_mut(&mut self) -> &mut Self::InnerItem; } + +pub(crate) fn consensus_errors_from_buffers( + errors: Vec, +) -> Result, JsValue> { + errors + .iter() + .map(|error_buffer| { + Uint8Array::new_with_byte_offset_and_length( + &error_buffer.buffer(), + error_buffer.byte_offset(), + error_buffer.length(), + ) + .to_vec() + }) + .map(|error_bytes| ConsensusError::deserialize(&error_bytes.to_vec()).with_js_error()) + .collect::, JsValue>>() +} diff --git a/packages/wasm-dpp/src/validation/validation_result.rs b/packages/wasm-dpp/src/validation/validation_result.rs index 74538528ae5..b23856454ea 100644 --- a/packages/wasm-dpp/src/validation/validation_result.rs +++ b/packages/wasm-dpp/src/validation/validation_result.rs @@ -1,6 +1,10 @@ -use crate::errors::consensus::consensus_error::from_consensus_error_ref; +use crate::{ + buffer::Buffer, + errors::consensus::consensus_error::from_consensus_error_ref, + utils::{consensus_errors_from_buffers, WithJsError}, +}; use dpp::{consensus::ConsensusError, validation::ConsensusValidationResult}; -use js_sys::JsString; +use js_sys::{JsString, Uint8Array}; use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name=ValidationResult)] @@ -18,6 +22,19 @@ where #[wasm_bindgen(js_class=ValidationResult)] impl ValidationResultWasm { + #[wasm_bindgen(constructor)] + pub fn new(errors_option: Option>) -> Result { + if let Some(errors) = errors_option { + let consensus_errors: Vec = consensus_errors_from_buffers(errors)?; + + return Ok(Self(ConsensusValidationResult::new_with_errors( + consensus_errors, + ))); + } + + Ok(Self(ConsensusValidationResult::new_with_errors(vec![]))) + } + /// This is just a test method - doesn't need to be in the resulted binding. Please /// remove before shipping #[wasm_bindgen(js_name=errorsText)] @@ -51,6 +68,22 @@ impl ValidationResultWasm { JsValue::undefined() } } + + #[wasm_bindgen(js_name = addError)] + pub fn add_error_wasm(&mut self, error_buffer: Buffer) -> Result { + let error_bytes: Vec = Uint8Array::new_with_byte_offset_and_length( + &error_buffer.buffer(), + error_buffer.byte_offset(), + error_buffer.length(), + ) + .to_vec(); + + let consensus_error = ConsensusError::deserialize(&error_bytes).with_js_error()?; + + self.0.add_error(consensus_error); + + Ok(JsValue::undefined()) + } } impl ValidationResultWasm { diff --git a/packages/wasm-dpp/test/integration/dataContract/DataContractFacade.spec.js b/packages/wasm-dpp/test/integration/dataContract/DataContractFacade.spec.js index 8124b8c688f..e10b0524666 100644 --- a/packages/wasm-dpp/test/integration/dataContract/DataContractFacade.spec.js +++ b/packages/wasm-dpp/test/integration/dataContract/DataContractFacade.spec.js @@ -1,11 +1,13 @@ const getDataContractJSFixture = require('@dashevo/dpp/lib/test/fixtures/getDataContractFixture'); const crypto = require('crypto'); const getBlsAdapterMock = require('../../../lib/test/mocks/getBlsAdapterMock'); +const createStateRepositoryMock = require('../../../lib/test/mocks/createStateRepositoryMock'); +const getPrivateAndPublicKey = require('../../../lib/test/fixtures/getPrivateAndPublicKeyForSigningFixture'); const { default: loadWasmDpp } = require('../../..'); let { DashPlatformProtocol, DataContract, ValidationResult, DataContractValidator, - DataContractFactory, DataContractCreateTransition, + DataContractFactory, DataContractCreateTransition, DataContractUpdateTransition, } = require('../../..'); describe('DataContractFacade', () => { @@ -15,18 +17,21 @@ describe('DataContractFacade', () => { let blsAdapter; let rawDataContract; let dataContractWasm; + let stateTransitionMock; before(async () => { ({ DashPlatformProtocol, DataContract, ValidationResult, DataContractValidator, DataContractFactory, DataContractCreateTransition, + DataContractUpdateTransition, } = await loadWasmDpp()); }); - beforeEach(async () => { + beforeEach(async function beforeEach() { blsAdapter = await getBlsAdapterMock(); + stateTransitionMock = createStateRepositoryMock(this.sinonSandbox); dpp = new DashPlatformProtocol( - blsAdapter, {}, { generate: () => crypto.randomBytes(32) }, 1, + blsAdapter, stateTransitionMock, { generate: () => crypto.randomBytes(32) }, 1, ); dataContractJs = await getDataContractJSFixture(); @@ -92,6 +97,38 @@ describe('DataContractFacade', () => { }); }); + describe('createDataContractUpdateTransition', () => { + it('should create DataContractUpdateTransition from buffer', async () => { + const dataContractBuffer = Buffer.from('01a56324696458205a3c9565f215156cfb330ef3a4a4400a1dad7415f4f1480e072d3419bb29c12c6724736368656d61783468747470733a2f2f736368656d612e646173682e6f72672f6470702d302d342d302f6d6574612f646174612d636f6e7472616374676f776e657249645820d5199a5b19334554d5dd483af53877251584ab9a66ca777bd1b2e8d2ec4808346776657273696f6e0169646f63756d656e7473a36c6e696365446f63756d656e74a46474797065666f626a656374687265717569726564816a246372656174656441746a70726f70657274696573a1646e616d65a1647479706566737472696e67746164646974696f6e616c50726f70657274696573f46e7769746842797465417272617973a56474797065666f626a65637467696e646963657381a2646e616d6566696e646578316a70726f7065727469657381a16e6279746541727261794669656c6463617363687265717569726564816e6279746541727261794669656c646a70726f70657274696573a26e6279746541727261794669656c64a36474797065656172726179686d61784974656d731069627974654172726179f56f6964656e7469666965724669656c64a56474797065656172726179686d61784974656d731820686d696e4974656d73182069627974654172726179f570636f6e74656e744d656469615479706578216170706c69636174696f6e2f782e646173682e6470702e6964656e746966696572746164646974696f6e616c50726f70657274696573f46f696e6465786564446f63756d656e74a56474797065666f626a65637467696e646963657386a3646e616d6566696e646578316a70726f7065727469657382a168246f776e6572496463617363a16966697273744e616d656361736366756e69717565f5a3646e616d6566696e646578326a70726f7065727469657382a168246f776e6572496463617363a1686c6173744e616d656361736366756e69717565f5a2646e616d6566696e646578336a70726f7065727469657381a1686c6173744e616d6563617363a2646e616d6566696e646578346a70726f7065727469657382a16a2463726561746564417463617363a16a2475706461746564417463617363a2646e616d6566696e646578356a70726f7065727469657381a16a2475706461746564417463617363a2646e616d6566696e646578366a70726f7065727469657381a16a2463726561746564417463617363687265717569726564846966697273744e616d656a246372656174656441746a24757064617465644174686c6173744e616d656a70726f70657274696573a2686c6173744e616d65a2647479706566737472696e67696d61784c656e677468183f6966697273744e616d65a2647479706566737472696e67696d61784c656e677468183f746164646974696f6e616c50726f70657274696573f4', 'hex'); + + const dataContract = await dpp.dataContract.createFromBuffer(dataContractBuffer); + + const updatedDataContract = await dpp.dataContract.createFromObject( + dataContract.toObject(), + ); + + updatedDataContract.incrementVersion(); + + const dataContractUpdateTransition = dpp.dataContract + .createDataContractUpdateTransition(updatedDataContract); + + const { identityPublicKey, privateKey } = await getPrivateAndPublicKey(); + + dataContractUpdateTransition.sign( + identityPublicKey, + privateKey, + await getBlsAdapterMock(), + ); + + const buf = dataContractUpdateTransition.toBuffer(); + stateTransitionMock.fetchDataContract.resolves(dataContract); + + const st = await dpp.stateTransition.createFromBuffer(buf); + + expect(st).to.be.an.instanceOf(DataContractUpdateTransition); + }); + }); + describe('validate', () => { it('should validate DataContract', async () => { const result = await dpp.dataContract.validate(rawDataContract); diff --git a/packages/wasm-dpp/test/integration/dataContract/validation/validateDataContractFactory.spec.js b/packages/wasm-dpp/test/integration/dataContract/validation/validateDataContractFactory.spec.js index d380a0b4cbd..052d6602d93 100644 --- a/packages/wasm-dpp/test/integration/dataContract/validation/validateDataContractFactory.spec.js +++ b/packages/wasm-dpp/test/integration/dataContract/validation/validateDataContractFactory.spec.js @@ -1072,7 +1072,7 @@ describe('validateDataContractFactory', () => { properties: { something: { type: 'string', - format: 'url', + format: 'uri', }, }, additionalProperties: false, @@ -1096,7 +1096,7 @@ describe('validateDataContractFactory', () => { properties: { something: { type: 'string', - format: 'url', + format: 'uri', maxLength: 60000, }, }, diff --git a/packages/wasm-dpp/test/integration/identity/stateTransition/IdentityUpdateTransition/validation/state/validateIdentityUpdateTransitionStateFactory.spec.js b/packages/wasm-dpp/test/integration/identity/stateTransition/IdentityUpdateTransition/validation/state/validateIdentityUpdateTransitionStateFactory.spec.js index 96a32444696..8c59c969322 100644 --- a/packages/wasm-dpp/test/integration/identity/stateTransition/IdentityUpdateTransition/validation/state/validateIdentityUpdateTransitionStateFactory.spec.js +++ b/packages/wasm-dpp/test/integration/identity/stateTransition/IdentityUpdateTransition/validation/state/validateIdentityUpdateTransitionStateFactory.spec.js @@ -109,7 +109,7 @@ describe('validateIdentityUpdateTransitionStateFactory', () => { it('should return IdentityPublicKeyIsDisabledError if disabling public key is already disabled', async () => { const keys = identity.getPublicKeys(); - keys[0].setDisabledAt(new Date().getTime()); + keys[0].setDisabledAt(new Date()); identity.setPublicKeys(keys); stateTransition.setPublicKeyIdsToDisable([0]); diff --git a/packages/wasm-dpp/test/integration/util/generateTemporaryEcdsaPrivateKey.spec.js b/packages/wasm-dpp/test/integration/util/generateTemporaryEcdsaPrivateKey.spec.js new file mode 100644 index 00000000000..ca4bb7182af --- /dev/null +++ b/packages/wasm-dpp/test/integration/util/generateTemporaryEcdsaPrivateKey.spec.js @@ -0,0 +1,17 @@ +const { PrivateKey } = require('@dashevo/dashcore-lib'); +const { default: loadWasmDpp } = require('../../..'); + +let { generateTemporaryEcdsaPrivateKey } = require('../../..'); + +describe('generateTemporaryEcdsaPrivateKey', () => { + beforeEach(async () => { + ({ generateTemporaryEcdsaPrivateKey } = await loadWasmDpp()); + }); + + it('should generate a valid private key', () => { + const keyBase64 = generateTemporaryEcdsaPrivateKey(); + + // eslint-disable-next-line + const _key = new PrivateKey(keyBase64); + }); +}); diff --git a/packages/wasm-dpp/test/unit/dataContract/DataContract.spec.js b/packages/wasm-dpp/test/unit/dataContract/DataContract.spec.js index 5d8072feb55..8197e3be90f 100644 --- a/packages/wasm-dpp/test/unit/dataContract/DataContract.spec.js +++ b/packages/wasm-dpp/test/unit/dataContract/DataContract.spec.js @@ -189,6 +189,7 @@ describe('DataContract', () => { expect(anotherDocuments).to.have.property(anotherType); expect(anotherDocuments[anotherType]).to.deep.equal(anotherDefinition); + expect(dataContract.isDocumentDefined(anotherType)).to.be.true(); }); }); diff --git a/packages/wasm-dpp/test/unit/dataContract/stateTransition/DataContractUpdateTransition/applyDataContractUpdateTransitionFactory.spec.js b/packages/wasm-dpp/test/unit/dataContract/stateTransition/DataContractUpdateTransition/applyDataContractUpdateTransitionFactory.spec.js index 7aee611e0f5..a17392636d9 100644 --- a/packages/wasm-dpp/test/unit/dataContract/stateTransition/DataContractUpdateTransition/applyDataContractUpdateTransitionFactory.spec.js +++ b/packages/wasm-dpp/test/unit/dataContract/stateTransition/DataContractUpdateTransition/applyDataContractUpdateTransitionFactory.spec.js @@ -32,7 +32,7 @@ describe('applyDataContractUpdateTransitionFactory', () => { executionContext = new StateTransitionExecutionContext(); const stateRepositoryLike = { - storeDataContract: async () => { + updateDataContract: async () => { dataContractStored = true; }, }; diff --git a/packages/wasm-dpp/test/unit/document/Document.spec.js b/packages/wasm-dpp/test/unit/document/Document.spec.js index c366066d243..a728c43eef0 100644 --- a/packages/wasm-dpp/test/unit/document/Document.spec.js +++ b/packages/wasm-dpp/test/unit/document/Document.spec.js @@ -277,7 +277,7 @@ describe('Document', () => { document = new ExtendedDocument(rawDocument, dataContract); - expect(document.getCreatedAt()).to.equal(rawDocument.$createdAt); + expect(document.getCreatedAt().getTime()).to.equal(rawDocument.$createdAt); }); it('should create Document with $updatedAt and data if present', async () => { @@ -298,7 +298,7 @@ describe('Document', () => { document = new ExtendedDocument(rawDocument, dataContract); - expect(document.getUpdatedAt()).to.equal(rawDocument.$updatedAt); + expect(document.getUpdatedAt().getTime()).to.equal(rawDocument.$updatedAt); }); }); @@ -461,41 +461,41 @@ describe('Document', () => { describe('#setCreatedAt', () => { it('should set $createdAt', () => { - const time = new Date().getTime(); + const time = new Date(); document.setCreatedAt(time); - expect(document.getCreatedAt()).to.equal(time); + expect(document.getCreatedAt()).to.deep.equal(time); }); }); describe('#getCreatedAt', () => { it('should return $createdAt', () => { - const time = new Date().getTime(); + const time = new Date(); document.setCreatedAt(time); - expect(document.getCreatedAt()).to.equal(time); + expect(document.getCreatedAt()).to.deep.equal(time); }); }); describe('#setUpdatedAt', () => { it('should set $updatedAt', () => { - const time = new Date().getTime(); + const time = new Date(); document.setUpdatedAt(time); - expect(document.getUpdatedAt()).to.equal(time); + expect(document.getUpdatedAt()).to.deep.equal(time); }); }); describe('#getUpdatedAt', () => { it('should return $updatedAt', () => { - const time = new Date().getTime(); + const time = new Date(); document.setUpdatedAt(time); - expect(document.getUpdatedAt()).to.equal(time); + expect(document.getUpdatedAt()).to.deep.equal(time); }); }); }); diff --git a/packages/wasm-dpp/test/unit/document/DocumentFactory.spec.js b/packages/wasm-dpp/test/unit/document/DocumentFactory.spec.js index 22177e253b0..b8102c8dc8e 100644 --- a/packages/wasm-dpp/test/unit/document/DocumentFactory.spec.js +++ b/packages/wasm-dpp/test/unit/document/DocumentFactory.spec.js @@ -154,7 +154,7 @@ describe('DocumentFactory', () => { // in a true unit test. Not here. expect(newDocument.getEntropy()).not.to.deep.be.equal(Buffer.alloc(32)); - expect(newDocument.getCreatedAt()).to.be.an('number'); + expect(newDocument.getCreatedAt()).to.be.an.instanceOf(Date); }); it('should throw an error if type is not defined', () => { diff --git a/packages/wasm-dpp/test/unit/document/stateTransition/DocumetsBatchTransition/applyDocumentsBatchTransitionFactory.spec.js b/packages/wasm-dpp/test/unit/document/stateTransition/DocumetsBatchTransition/applyDocumentsBatchTransitionFactory.spec.js index efb2e6f8e3a..37486ad7888 100644 --- a/packages/wasm-dpp/test/unit/document/stateTransition/DocumetsBatchTransition/applyDocumentsBatchTransitionFactory.spec.js +++ b/packages/wasm-dpp/test/unit/document/stateTransition/DocumetsBatchTransition/applyDocumentsBatchTransitionFactory.spec.js @@ -241,7 +241,7 @@ describe('applyDocumentsBatchTransitionFactory', () => { $type: documentTransition.getType(), $dataContractId: documentTransition.getDataContractId(), $ownerId: ownerId, - $createdAt: documentTransition.getUpdatedAt(), + $createdAt: documentTransition.getUpdatedAt().getTime(), ...documentTransition.getData(), }, documentTransition.getDataContract()); diff --git a/packages/wasm-dpp/test/unit/document/stateTransition/DocumetsBatchTransition/validation/state/validateDocumentsBatchTransitionStateFactory.spec.js b/packages/wasm-dpp/test/unit/document/stateTransition/DocumetsBatchTransition/validation/state/validateDocumentsBatchTransitionStateFactory.spec.js index e880ec179a6..43f380815e0 100644 --- a/packages/wasm-dpp/test/unit/document/stateTransition/DocumetsBatchTransition/validation/state/validateDocumentsBatchTransitionStateFactory.spec.js +++ b/packages/wasm-dpp/test/unit/document/stateTransition/DocumetsBatchTransition/validation/state/validateDocumentsBatchTransitionStateFactory.spec.js @@ -234,7 +234,7 @@ describe('validateDocumentsBatchTransitionStateFactory', () => { transitions: documentTransitionsJs.map((t) => t.toObject()), }, [dataContract]); - documents[0].setCreatedAt(replaceDocument.getCreatedAt().getMilliseconds()); + documents[0].setCreatedAt(replaceDocument.getCreatedAt()); stateRepositoryMock.fetchDocuments.resolves([documents[0].getDocument()]); const result = await validateDocumentsBatchTransitionState( @@ -354,7 +354,7 @@ describe('validateDocumentsBatchTransitionStateFactory', () => { const transitions = stateTransition.getTransitions(); transitions.forEach((t) => { // eslint-disable-next-line no-param-reassign - t.setUpdatedAt(new Date().getMilliseconds()); + t.setUpdatedAt(new Date()); }); stateTransition.setTransitions(transitions); @@ -382,8 +382,8 @@ describe('validateDocumentsBatchTransitionStateFactory', () => { const transitions = stateTransition.getTransitions(); transitions.forEach((t) => { - const createdAtMinus6Mins = t.getCreatedAt() - (6 * 60 * 1000); - t.setCreatedAt(createdAtMinus6Mins); + const createdAtMinus6Mins = t.getCreatedAt().getTime() - (6 * 60 * 1000); + t.setCreatedAt(new Date(createdAtMinus6Mins)); t.setUpdatedAt(undefined); }); stateTransition.setTransitions(transitions); @@ -420,8 +420,8 @@ describe('validateDocumentsBatchTransitionStateFactory', () => { const transitions = stateTransition.getTransitions(); transitions.forEach((t) => { - const createdAtMinus6Mins = t.getUpdatedAt() - (6 * 60 * 1000); - t.setUpdatedAt(createdAtMinus6Mins); + const createdAtMinus6Mins = t.getUpdatedAt().getTime() - (6 * 60 * 1000); + t.setUpdatedAt(new Date(createdAtMinus6Mins)); t.setCreatedAt(undefined); }); stateTransition.setTransitions(transitions); @@ -468,8 +468,8 @@ describe('validateDocumentsBatchTransitionStateFactory', () => { const transitions = stateTransition.getTransitions(); transitions.forEach((t) => { - const createdAtMinus6Mins = t.getUpdatedAt() - (6 * 60 * 1000); - t.setUpdatedAt(createdAtMinus6Mins); + const createdAtMinus6Mins = t.getUpdatedAt().getTime() - (6 * 60 * 1000); + t.setUpdatedAt(new Date(createdAtMinus6Mins)); t.setCreatedAt(undefined); }); stateTransition.setTransitions(transitions); @@ -501,7 +501,7 @@ describe('validateDocumentsBatchTransitionStateFactory', () => { const transitions = stateTransition.getTransitions(); transitions.forEach((t) => { // eslint-disable-next-line no-param-reassign - t.setUpdatedAt(new Date().getMilliseconds()); + t.setUpdatedAt(new Date()); }); executionContext.enableDryRun(); @@ -536,8 +536,8 @@ describe('validateDocumentsBatchTransitionStateFactory', () => { const transitions = stateTransition.getTransitions(); transitions.forEach((t) => { - const createdAtMinus6Mins = t.getUpdatedAt() - (6 * 60 * 1000); - t.setUpdatedAt(createdAtMinus6Mins); + const createdAtMinus6Mins = t.getUpdatedAt().getTime() - (6 * 60 * 1000); + t.setUpdatedAt(new Date(createdAtMinus6Mins)); }); stateTransition.setTransitions(transitions); @@ -583,8 +583,8 @@ describe('validateDocumentsBatchTransitionStateFactory', () => { stateRepositoryMock.fetchExtendedDocuments.resolves([documentToReturn]); const transitions = stateTransition.getTransitions(); transitions.forEach((t) => { - const createdAtMinus6Mins = t.getUpdatedAt() - (6 * 60 * 1000); - t.setUpdatedAt(createdAtMinus6Mins); + const createdAtMinus6Mins = t.getUpdatedAt().getTime() - (6 * 60 * 1000); + t.setUpdatedAt(new Date(createdAtMinus6Mins)); }); stateTransition.setTransitions(transitions); executionContext.enableDryRun(); diff --git a/packages/wasm-dpp/test/unit/identity/IdentityPublicKey.spec.js b/packages/wasm-dpp/test/unit/identity/IdentityPublicKey.spec.js index 5652950a6ef..ff56b095a39 100644 --- a/packages/wasm-dpp/test/unit/identity/IdentityPublicKey.spec.js +++ b/packages/wasm-dpp/test/unit/identity/IdentityPublicKey.spec.js @@ -119,17 +119,19 @@ describe('IdentityPublicKey', () => { describe('#setDisabledAt', () => { it('should set disabledAt', () => { - publicKey.setDisabledAt(123); + const now = new Date(); + publicKey.setDisabledAt(now); - expect(publicKey.getDisabledAt()).to.equal(123); + expect(publicKey.getDisabledAt()).to.deep.equal(now); }); }); describe('#getDisabledAt', () => { it('should return disabledAt', () => { - publicKey.setDisabledAt(42); + const now = new Date(); + publicKey.setDisabledAt(now); - expect(publicKey.getDisabledAt()).to.equal(42); + expect(publicKey.getDisabledAt()).to.deep.equal(now); }); }); @@ -218,7 +220,8 @@ describe('IdentityPublicKey', () => { }); it('should return JSON representation with optional properties', () => { - publicKey.setDisabledAt(42); + const now = new Date(); + publicKey.setDisabledAt(now); const jsonPublicKey = publicKey.toJSON(); @@ -229,7 +232,7 @@ describe('IdentityPublicKey', () => { purpose: IdentityPublicKey.PURPOSES.AUTHENTICATION, securityLevel: IdentityPublicKey.SECURITY_LEVELS.MASTER, readOnly: false, - disabledAt: 42, + disabledAt: now.getTime(), }); }); }); diff --git a/packages/wasm-dpp/test/unit/stateTransition/StateTransitionExecutionContext.spec.js b/packages/wasm-dpp/test/unit/stateTransition/StateTransitionExecutionContext.spec.js index 7975bd4690c..1c93b9f9e07 100644 --- a/packages/wasm-dpp/test/unit/stateTransition/StateTransitionExecutionContext.spec.js +++ b/packages/wasm-dpp/test/unit/stateTransition/StateTransitionExecutionContext.spec.js @@ -36,7 +36,10 @@ describe('StateTransitionExecutionContext', () => { }); it('should add PreCalculatedOperation', () => { - executionContext.addOperation(new PreCalculatedOperation(1, 1, [])); + executionContext.addOperation(new PreCalculatedOperation(1, 1, [{ + identifier: Buffer.alloc(32).fill(32), + creditsPerEpoch: { 0: 9991498 }, + }])); const operations = executionContext.getOperations(); expect(operations[0]).to.be.instanceOf(PreCalculatedOperation); }); diff --git a/packages/wasm-dpp/test/unit/stateTransition/StateTransitionFactory.spec.js b/packages/wasm-dpp/test/unit/stateTransition/StateTransitionFactory.spec.js index b12036924b0..6cc06da0d66 100644 --- a/packages/wasm-dpp/test/unit/stateTransition/StateTransitionFactory.spec.js +++ b/packages/wasm-dpp/test/unit/stateTransition/StateTransitionFactory.spec.js @@ -88,8 +88,9 @@ describe('StateTransitionFactory', function main() { expect(result.toObject()).to.deep.equal(stateTransition.toObject()); }); - it('should throw InvalidStateTransition error in case validation failed', async () => { + it.skip('should throw InvalidStateTransition error in case validation failed', async () => { // CBOR that have invalid "type" property + // TODO: make it an actually missing field isntead of an invalid value const stateTransitionHex = '01a56c64617461436f6e7472616374a66f70726f746f636f6c56657273696f6e0163246964982018a318b611186f184718781848187d186318441830182c185518fe18e318ff188b18eb185918490218ac1831187b1873184a182c18d9189b1886182418bf6724736368656d61783468747470733a2f2f736368656d612e646173682e6f72672f6470702d302d342d302f6d6574612f646174612d636f6e74726163746776657273696f6e01676f776e657249649820187618a6185c189e18651833183d18aa02189e0d18a61869182818c107183e188908184f183818d018220418c01839161860187418ae18dd189969646f63756d656e7473a76f696e6465786564446f63756d656e74a56474797065666f626a65637467696e646963657386a3646e616d6566696e646578316a70726f7065727469657382a168246f776e6572496463617363a16966697273744e616d656361736366756e69717565f5a3646e616d6566696e646578326a70726f7065727469657382a168246f776e6572496463617363a1686c6173744e616d656361736366756e69717565f5a3646e616d6566696e646578336a70726f7065727469657381a1686c6173744e616d656361736366756e69717565f4a2646e616d6566696e646578346a70726f7065727469657382a16a2463726561746564417463617363a16a2475706461746564417463617363a2646e616d6566696e646578356a70726f7065727469657381a16a2475706461746564417463617363a2646e616d6566696e646578366a70726f7065727469657381a16a24637265617465644174636173636a70726f70657274696573a36966697273744e616d65a2647479706566737472696e67696d61784c656e677468183f686c6173744e616d65a2647479706566737472696e67696d61784c656e677468183f6d6f7468657250726f7065727479a2647479706566737472696e67696d61784c656e677468182a687265717569726564846966697273744e616d656a246372656174656441746a24757064617465644174686c6173744e616d65746164646974696f6e616c50726f70657274696573f46c6e696365446f63756d656e74a46474797065666f626a6563746a70726f70657274696573a1646e616d65a1647479706566737472696e67687265717569726564816a24637265617465644174746164646974696f6e616c50726f70657274696573f46e6e6f54696d65446f63756d656e74a36474797065666f626a6563746a70726f70657274696573a1646e616d65a1647479706566737472696e67746164646974696f6e616c50726f70657274696573f4781d6f7074696f6e616c556e69717565496e6465786564446f63756d656e74a56474797065666f626a6563746a70726f70657274696573a46966697273744e616d65a2647479706566737472696e67696d61784c656e677468183f686c6173744e616d65a2647479706566737472696e67696d61784c656e677468183f67636f756e747279a2647479706566737472696e67696d61784c656e677468183f6463697479a2647479706566737472696e67696d61784c656e677468183f67696e646963657383a3646e616d6566696e646578316a70726f7065727469657381a16966697273744e616d656361736366756e69717565f5a3646e616d6566696e646578326a70726f7065727469657383a168246f776e6572496463617363a16966697273744e616d6563617363a1686c6173744e616d656361736366756e69717565f5a3646e616d6566696e646578336a70726f7065727469657382a167636f756e74727963617363a164636974796361736366756e69717565f5687265717569726564826966697273744e616d65686c6173744e616d65746164646974696f6e616c50726f70657274696573f46e707265747479446f63756d656e74a46474797065666f626a6563746a70726f70657274696573a1686c6173744e616d65a1647479706566737472696e6768726571756972656482686c6173744e616d656a24757064617465644174746164646974696f6e616c50726f70657274696573f46b756e697175654461746573a56474797065666f626a65637467696e646963657382a3646e616d6566696e646578316a70726f7065727469657382a16a2463726561746564417463617363a16a247570646174656441746361736366756e69717565f5a2646e616d6566696e646578326a70726f7065727469657381a16a24757064617465644174636173636a70726f70657274696573a26966697273744e616d65a1647479706566737472696e67686c6173744e616d65a1647479706566737472696e67687265717569726564836966697273744e616d656a246372656174656441746a24757064617465644174746164646974696f6e616c50726f70657274696573f46e7769746842797465417272617973a56474797065666f626a65637467696e646963657381a2646e616d6566696e646578316a70726f7065727469657381a16e6279746541727261794669656c64636173636a70726f70657274696573a26e6279746541727261794669656c64a3647479706565617272617969627974654172726179f5686d61784974656d73106f6964656e7469666965724669656c64a5647479706565617272617969627974654172726179f570636f6e74656e744d656469615479706578216170706c69636174696f6e2f782e646173682e6470702e6964656e746966696572686d696e4974656d731820686d61784974656d731820687265717569726564816e6279746541727261794669656c64746164646974696f6e616c50726f70657274696573f464747970652267656e74726f70799820189618d10418b2071882188e188818fc18b7189818dd185c18811881189b1867187912183d184d187118ff1866181d185f1839188718ea1824185113747369676e61747572655075626c69634b6579496400697369676e617475726598410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; const stateTransitionBuffer = Buffer.from(stateTransitionHex, 'hex'); diff --git a/packages/wasm-dpp/test/unit/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.spec.js b/packages/wasm-dpp/test/unit/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.spec.js index 6ea8d48c3ea..9200f17f087 100644 --- a/packages/wasm-dpp/test/unit/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.spec.js +++ b/packages/wasm-dpp/test/unit/stateTransition/validation/validateStateTransitionIdentitySignatureFactory.spec.js @@ -275,7 +275,7 @@ describe('validateStateTransitionIdentitySignatureFactory', () => { it('should return PublicKeyIsDisabledConsensusError if PublicKeyIsDisabledError was thrown', async () => { const publicKeys = identity.getPublicKeys(); - publicKeys[2].setDisabledAt(Date.now()); + publicKeys[2].setDisabledAt(new Date()); identity.setPublicKeys(publicKeys); const result = await validateStateTransitionIdentitySignature( @@ -315,7 +315,7 @@ describe('validateStateTransitionIdentitySignatureFactory', () => { it('should not verify signature on dry run', async () => { // This will produce an error during signature validation const publicKeys = identity.getPublicKeys(); - publicKeys[2].setDisabledAt(Date.now()); + publicKeys[2].setDisabledAt(new Date()); identity.setPublicKeys(publicKeys); executionContext.enableDryRun(); From efb4b9cd7f0348980607a77402083235eabc9859 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Fri, 14 Apr 2023 22:19:25 +0700 Subject: [PATCH 17/21] test(dapi-client): fix broken SimplifiedMasternodeListDAPIAddressProvider test (#916) --- ...dMasternodeListDAPIAddressProvider.spec.js | 59 +++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/packages/js-dapi-client/test/unit/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.spec.js b/packages/js-dapi-client/test/unit/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.spec.js index f1cad40085e..f130795e44e 100644 --- a/packages/js-dapi-client/test/unit/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.spec.js +++ b/packages/js-dapi-client/test/unit/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.spec.js @@ -1,6 +1,5 @@ const SimplifiedMNListEntry = require('@dashevo/dashcore-lib/lib/deterministicmnlist/SimplifiedMNListEntry'); -const getMNListDiffsFixture = require('../../../lib/test/fixtures/getMNListDiffsFixture'); const DAPIAddress = require('../../../lib/dapiAddressProvider/DAPIAddress'); const SimplifiedMasternodeListDAPIAddressProvider = require('../../../lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider'); @@ -14,22 +13,60 @@ describe('SimplifiedMasternodeListDAPIAddressProvider', () => { let addresses; beforeEach(function beforeEach() { - const [mnListDiffFixture] = getMNListDiffsFixture(); + const mnListDiffFixture = [{ + proRegTxHash: 'f5ec54aed788c434da2fc535ea6b125ec6fc54e58bc0a00a005d1a8d5e477a90', + confirmedHash: '53125505b0e9d11b371cf3e12c92d164296dfa215fde6201d28ea44bed992187', + service: '192.168.65.2:20101', + pubKeyOperator: '951a3208ba531ea75aedd2dc0a9efc75f2c4d9492f1ee0a989b593bcd9722b1a101774d80a426552a9f91d24eb55af6e', + votingAddress: 'yYH1rgZsgvkmT8bSSSw1cKCjyVPnFpTBCw', + isValid: true, + nVersion: 2, + nType: 1, + payoutAddress: 'yZv7wf496sjqJVgnEUAtYKozWQhVpoHRh9', + platformHTTPPort: 3200, + platformNodeID: 'fe84df23e1a7f1db40e8e3fd3a4d88662bf0d89d', + }, { + proRegTxHash: 'a2c9b34ef525271d84f70a0d4d2c107e8a2f81cd4d8256dc7b3911ed253d5611', + confirmedHash: '29ff8afb463604ba7d984b483e92dfefa4e80e12de3acae6d75f9b910df9eab6', + service: '192.168.65.2:20201', + pubKeyOperator: 'a5ad6d8cad7b233210b718a5fc9ec3cea18aeebe38b2e3122deb581e430aa28875fe7336c283871db42808f8d4107745', + votingAddress: 'yRXtaRmQ7LCmT5XcgzQdLwPEf31dycBaeY', + isValid: true, + nVersion: 2, + nType: 1, + payoutAddress: 'yiBP17AgHGit2TE9p9FpHEh4ouowNSxMxg', + platformHTTPPort: 3200, + platformNodeID: 'fe84df23e1a7f1db40e8e3fd3a4d88662bf0d89d', + }, { + proRegTxHash: '1c81a5faa2c0e0d96eb59c58a10fcbc87f431bb6cd880d960b43b269e682d2d2', + confirmedHash: '03cc2acc135ab51304d3cff42215c7a8041902fa3f19451d5562a03b38143e8f', + service: '192.168.65.2:20001', + pubKeyOperator: '96f83eedc8a7b87663e591987f051ce341a6fb88989322c64bbbf56d205e4e77d2cb7d839d8b4106a8a1f5d5cf7cfa57', + votingAddress: 'ybJfuKs59MJWkPEnS8qNmtvdisHrCy7Njn', + isValid: true, + nVersion: 2, + nType: 1, + payoutAddress: 'yd3AnRA5YRtN1jsv7jqUK8egA6Mk9e8HoS', + platformHTTPPort: 3200, + platformNodeID: 'fe84df23e1a7f1db40e8e3fd3a4d88662bf0d89d', + }]; validMasternodeList = [ - new SimplifiedMNListEntry(mnListDiffFixture.mnList[0]), - new SimplifiedMNListEntry(mnListDiffFixture.mnList[1]), - new SimplifiedMNListEntry(mnListDiffFixture.mnList[2]), + new SimplifiedMNListEntry(mnListDiffFixture[0]), + new SimplifiedMNListEntry(mnListDiffFixture[1]), + new SimplifiedMNListEntry(mnListDiffFixture[2]), ]; addresses = [ new DAPIAddress({ host: validMasternodeList[0].getIp(), proRegTxHash: validMasternodeList[0].proRegTxHash, + port: validMasternodeList[0].platformHTTPPort, }), new DAPIAddress({ host: '127.0.0.1', proRegTxHash: validMasternodeList[1].proRegTxHash, + port: validMasternodeList[1].platformHTTPPort, }), new DAPIAddress({ host: '127.0.0.1', @@ -80,7 +117,7 @@ describe('SimplifiedMasternodeListDAPIAddressProvider', () => { expect(firstAddress).to.equal(addresses[0]); expect(firstAddress.toJSON()).to.deep.equal({ host: validMasternodeList[0].getIp(), - port: DAPIAddress.DEFAULT_PORT, + port: validMasternodeList[0].platformHTTPPort, proRegTxHash: validMasternodeList[0].proRegTxHash, protocol: DAPIAddress.DEFAULT_PROTOCOL, allowSelfSignedCertificate: false, @@ -90,7 +127,7 @@ describe('SimplifiedMasternodeListDAPIAddressProvider', () => { expect(secondAddress).to.deep.equal(addresses[1]); expect(secondAddress.toJSON()).to.deep.equal({ host: validMasternodeList[1].getIp(), - port: DAPIAddress.DEFAULT_PORT, + port: validMasternodeList[1].platformHTTPPort, proRegTxHash: validMasternodeList[1].proRegTxHash, protocol: DAPIAddress.DEFAULT_PROTOCOL, allowSelfSignedCertificate: false, @@ -100,7 +137,7 @@ describe('SimplifiedMasternodeListDAPIAddressProvider', () => { expect(thirdAddress).to.not.equal(addresses[2]); expect(thirdAddress.toJSON()).to.deep.equal({ host: validMasternodeList[2].getIp(), - port: DAPIAddress.DEFAULT_PORT, + port: validMasternodeList[2].platformHTTPPort, proRegTxHash: validMasternodeList[2].proRegTxHash, protocol: DAPIAddress.DEFAULT_PROTOCOL, allowSelfSignedCertificate: false, @@ -116,7 +153,7 @@ describe('SimplifiedMasternodeListDAPIAddressProvider', () => { smlDAPIAddressProvider = new SimplifiedMasternodeListDAPIAddressProvider( smlProviderMock, listDAPIAddressProviderMock, - [new DAPIAddress(validMasternodeList[1].getIp())], + [new DAPIAddress(`${validMasternodeList[1].getIp()}:${validMasternodeList[1].platformHTTPPort}`)], ); await smlDAPIAddressProvider.getLiveAddress(); @@ -136,7 +173,7 @@ describe('SimplifiedMasternodeListDAPIAddressProvider', () => { expect(secondAddress).to.equal(addresses[0]); expect(secondAddress.toJSON()).to.deep.equal({ host: validMasternodeList[0].getIp(), - port: DAPIAddress.DEFAULT_PORT, + port: validMasternodeList[0].platformHTTPPort, proRegTxHash: validMasternodeList[0].proRegTxHash, protocol: DAPIAddress.DEFAULT_PROTOCOL, allowSelfSignedCertificate: false, @@ -146,7 +183,7 @@ describe('SimplifiedMasternodeListDAPIAddressProvider', () => { expect(thirdAddress).to.not.equal(addresses[2]); expect(thirdAddress.toJSON()).to.deep.equal({ host: validMasternodeList[1].getIp(), - port: DAPIAddress.DEFAULT_PORT, + port: validMasternodeList[1].platformHTTPPort, proRegTxHash: validMasternodeList[1].proRegTxHash, protocol: DAPIAddress.DEFAULT_PROTOCOL, allowSelfSignedCertificate: false, From 7d04332d5502da19250a613e97daccdb6816cf2d Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Fri, 14 Apr 2023 23:22:36 +0700 Subject: [PATCH 18/21] chore(release): update changelog and bump version to 0.24.0-dev.18 (#917) --- CHANGELOG.md | 41 +++++++++++++++++++ package.json | 2 +- packages/bench-suite/package.json | 2 +- packages/dapi-grpc/package.json | 2 +- packages/dapi/package.json | 2 +- packages/dash-spv/package.json | 2 +- packages/dashmate/package.json | 2 +- packages/dashpay-contract/package.json | 2 +- packages/dpns-contract/package.json | 2 +- packages/feature-flags-contract/package.json | 2 +- packages/js-dapi-client/package.json | 2 +- packages/js-dash-sdk/package.json | 2 +- packages/js-dpp/package.json | 2 +- packages/js-grpc-common/package.json | 2 +- .../package.json | 2 +- packages/platform-test-suite/package.json | 2 +- packages/wallet-lib/package.json | 2 +- packages/wasm-dpp/package.json | 2 +- packages/withdrawals-contract/package.json | 2 +- 19 files changed, 59 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 715e3616234..0e393c22ec9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,44 @@ +## [0.24.0-dev.18](https://github.com/dashpay/platform/compare/v0.24.0-dev.17...v0.24.0-dev.18) (2023-04-14) + + +### Features + +* **dashmate:** build linux tarballs ([#887](https://github.com/dashpay/platform/issues/887)) +* **dashmate:** build services before restart ([#894](https://github.com/dashpay/platform/issues/894)) +* **dashmate:** exit status with 2 if it's not running ([#896](https://github.com/dashpay/platform/issues/896)) +* **dashmate:** implement http json rpc api ([#888](https://github.com/dashpay/platform/issues/888)) +* **dashmate:** tenderdash latest block time in status ([#906](https://github.com/dashpay/platform/issues/906)) +* **dpp:** serialize consensus errors ([#871](https://github.com/dashpay/platform/issues/871)) +* drive verification c bindings ([#860](https://github.com/dashpay/platform/issues/860)) + + +### Bug Fixes + +* DAPI still expected on normal masternodes ([#904](https://github.com/dashpay/platform/issues/904)) +* **dapi-client:** platform port is ignored from SML ([#903](https://github.com/dashpay/platform/issues/903)) +* **dashmate:** api binds to all interfaces ([#893](https://github.com/dashpay/platform/issues/893)) +* **dashmate:** dashmate helper is running under root user ([#895](https://github.com/dashpay/platform/issues/895)) +* **dashmate:** dashmate logic doesn't recognize it's ran from helper ([#902](https://github.com/dashpay/platform/issues/902)) +* **dashmate:** missing rawblock zmq message in core config ([#770](https://github.com/dashpay/platform/issues/770)) +* **dashmate:** undefined wallet for dash-cli ([#786](https://github.com/dashpay/platform/issues/786)) +* **dpp:** various fixes in DPP and system contracts ([#907](https://github.com/dashpay/platform/issues/907)) +* **drive:** non-deterministic run of mn identities sync ([#910](https://github.com/dashpay/platform/issues/910)) +* **drive:** total HPMNs contains all masternodes ([#911](https://github.com/dashpay/platform/issues/911)) +* identifier deserialization doesn't work for bincode ([#885](https://github.com/dashpay/platform/issues/885)) +* llmqType must be equal to one of the allowed values ([#884](https://github.com/dashpay/platform/issues/884)) +* possible overflow issues ([#877](https://github.com/dashpay/platform/issues/877)) + + +### Miscellaneous Chores + +* **dashmate:** update production dashcore versions for mainnet and testnet ([#840](https://github.com/dashpay/platform/issues/840)) +* **sdk:** add eslint ([#829](https://github.com/dashpay/platform/issues/829)) + + +### Continuous Integration + +* sign MacOs Dashmate release ([#890](https://github.com/dashpay/platform/issues/890)) + ## [0.24.0-dev.17](https://github.com/dashpay/platform/compare/v0.24.0-dev.16...v0.24.0-dev.17) (2023-04-04) diff --git a/package.json b/package.json index 87aad11ad10..c51bad785f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/platform", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "private": true, "scripts": { "setup": "yarn install && yarn run build && yarn run configure", diff --git a/packages/bench-suite/package.json b/packages/bench-suite/package.json index 946086cacaf..59578f3ec25 100644 --- a/packages/bench-suite/package.json +++ b/packages/bench-suite/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/bench-suite", "private": true, - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "Dash Platform benchmark tool", "scripts": { "bench": "node ./bin/bench.js", diff --git a/packages/dapi-grpc/package.json b/packages/dapi-grpc/package.json index 7410dda4a1b..46633b799fe 100644 --- a/packages/dapi-grpc/package.json +++ b/packages/dapi-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi-grpc", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "DAPI GRPC definition file and generated clients", "browser": "browser.js", "main": "node.js", diff --git a/packages/dapi/package.json b/packages/dapi/package.json index 19788940e5f..c49cf71894b 100644 --- a/packages/dapi/package.json +++ b/packages/dapi/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/dapi", "private": true, - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "A decentralized API for the Dash network", "scripts": { "api": "node scripts/api.js", diff --git a/packages/dash-spv/package.json b/packages/dash-spv/package.json index bd1e930a67c..3776e647364 100644 --- a/packages/dash-spv/package.json +++ b/packages/dash-spv/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dash-spv", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "Repository containing SPV functions used by @dashevo", "main": "index.js", "scripts": { diff --git a/packages/dashmate/package.json b/packages/dashmate/package.json index 17c8987db07..9dfdeadb72f 100644 --- a/packages/dashmate/package.json +++ b/packages/dashmate/package.json @@ -1,6 +1,6 @@ { "name": "dashmate", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "Distribution package for Dash node installation", "scripts": { "lint": "eslint .", diff --git a/packages/dashpay-contract/package.json b/packages/dashpay-contract/package.json index eb0d6f712a9..e23fc2f5110 100644 --- a/packages/dashpay-contract/package.json +++ b/packages/dashpay-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dashpay-contract", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "Reference contract of the DashPay DPA on Dash Evolution", "scripts": { "lint": "eslint .", diff --git a/packages/dpns-contract/package.json b/packages/dpns-contract/package.json index c0d2f79b968..0d9f8268955 100644 --- a/packages/dpns-contract/package.json +++ b/packages/dpns-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dpns-contract", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "A contract and helper scripts for DPNS DApp", "scripts": { "lint": "eslint .", diff --git a/packages/feature-flags-contract/package.json b/packages/feature-flags-contract/package.json index ea38f9ad1a8..194b8c2d359 100644 --- a/packages/feature-flags-contract/package.json +++ b/packages/feature-flags-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/feature-flags-contract", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "Data Contract to store Dash Platform feature flags", "scripts": { "build": "", diff --git a/packages/js-dapi-client/package.json b/packages/js-dapi-client/package.json index 7338949d80d..29124a5e05f 100644 --- a/packages/js-dapi-client/package.json +++ b/packages/js-dapi-client/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi-client", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "Client library used to access Dash DAPI endpoints", "main": "lib/index.js", "contributors": [ diff --git a/packages/js-dash-sdk/package.json b/packages/js-dash-sdk/package.json index ef3ba451c93..f0568a0f2cb 100644 --- a/packages/js-dash-sdk/package.json +++ b/packages/js-dash-sdk/package.json @@ -1,6 +1,6 @@ { "name": "dash", - "version": "3.24.0-dev.17", + "version": "3.24.0-dev.18", "description": "Dash library for JavaScript/TypeScript ecosystem (Wallet, DAPI, Primitives, BLS, ...)", "main": "build/src/index.js", "unpkg": "dist/dash.min.js", diff --git a/packages/js-dpp/package.json b/packages/js-dpp/package.json index ab3285372b1..c732149cb1f 100644 --- a/packages/js-dpp/package.json +++ b/packages/js-dpp/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dpp", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "The JavaScript implementation of the Dash Platform Protocol", "scripts": { "lint": "eslint .", diff --git a/packages/js-grpc-common/package.json b/packages/js-grpc-common/package.json index 3a2e8646ed2..4d10063845e 100644 --- a/packages/js-grpc-common/package.json +++ b/packages/js-grpc-common/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/grpc-common", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "Common GRPC library", "main": "index.js", "scripts": { diff --git a/packages/masternode-reward-shares-contract/package.json b/packages/masternode-reward-shares-contract/package.json index 5038793e762..bf4477c777e 100644 --- a/packages/masternode-reward-shares-contract/package.json +++ b/packages/masternode-reward-shares-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/masternode-reward-shares-contract", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "A contract and helper scripts for reward sharing", "scripts": { "lint": "eslint .", diff --git a/packages/platform-test-suite/package.json b/packages/platform-test-suite/package.json index 35039a27973..bf54061cd3e 100644 --- a/packages/platform-test-suite/package.json +++ b/packages/platform-test-suite/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/platform-test-suite", "private": true, - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "Dash Network end-to-end tests", "scripts": { "test": "yarn exec bin/test.sh", diff --git a/packages/wallet-lib/package.json b/packages/wallet-lib/package.json index b3dec403d46..5a5b746e8fd 100644 --- a/packages/wallet-lib/package.json +++ b/packages/wallet-lib/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wallet-lib", - "version": "7.24.0-dev.17", + "version": "7.24.0-dev.18", "description": "Light wallet library for Dash", "main": "src/index.js", "unpkg": "dist/wallet-lib.min.js", diff --git a/packages/wasm-dpp/package.json b/packages/wasm-dpp/package.json index 3d94f00fd43..3bace8993af 100644 --- a/packages/wasm-dpp/package.json +++ b/packages/wasm-dpp/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-dpp", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "The JavaScript implementation of the Dash Platform Protocol", "main": "dist/index.js", "types": "dist/lib/index.d.ts", diff --git a/packages/withdrawals-contract/package.json b/packages/withdrawals-contract/package.json index db99137e814..eaafb3d7418 100644 --- a/packages/withdrawals-contract/package.json +++ b/packages/withdrawals-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/withdrawals-contract", - "version": "0.24.0-dev.17", + "version": "0.24.0-dev.18", "description": "Data Contract to manipulate and track withdrawals", "scripts": { "build": "", From 6e575f9460b5962c33acec780b658884d9486133 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Mon, 17 Apr 2023 20:19:43 +0700 Subject: [PATCH 19/21] chore(release): update changelog and bump version to 0.24.0-dev.19 (#919) --- CHANGELOG.md | 2 ++ package.json | 2 +- packages/bench-suite/package.json | 2 +- packages/dapi-grpc/package.json | 2 +- packages/dapi/package.json | 2 +- packages/dash-spv/package.json | 2 +- packages/dashmate/package.json | 2 +- packages/dashpay-contract/package.json | 2 +- packages/dpns-contract/package.json | 2 +- packages/feature-flags-contract/package.json | 2 +- packages/js-dapi-client/package.json | 2 +- packages/js-dash-sdk/package.json | 2 +- packages/js-dpp/package.json | 2 +- packages/js-grpc-common/package.json | 2 +- packages/masternode-reward-shares-contract/package.json | 2 +- packages/platform-test-suite/package.json | 2 +- packages/wallet-lib/package.json | 2 +- packages/wasm-dpp/package.json | 2 +- packages/withdrawals-contract/package.json | 2 +- 19 files changed, 20 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e393c22ec9..77f0af4501d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## [0.24.0-dev.19](https://github.com/dashpay/platform/compare/v0.24.0-dev.18...v0.24.0-dev.19) (2023-04-17) + ## [0.24.0-dev.18](https://github.com/dashpay/platform/compare/v0.24.0-dev.17...v0.24.0-dev.18) (2023-04-14) diff --git a/package.json b/package.json index c51bad785f5..16fef79de1c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/platform", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "private": true, "scripts": { "setup": "yarn install && yarn run build && yarn run configure", diff --git a/packages/bench-suite/package.json b/packages/bench-suite/package.json index 59578f3ec25..8d3c72bbbfa 100644 --- a/packages/bench-suite/package.json +++ b/packages/bench-suite/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/bench-suite", "private": true, - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "Dash Platform benchmark tool", "scripts": { "bench": "node ./bin/bench.js", diff --git a/packages/dapi-grpc/package.json b/packages/dapi-grpc/package.json index 46633b799fe..7a43dd72c4b 100644 --- a/packages/dapi-grpc/package.json +++ b/packages/dapi-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi-grpc", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "DAPI GRPC definition file and generated clients", "browser": "browser.js", "main": "node.js", diff --git a/packages/dapi/package.json b/packages/dapi/package.json index c49cf71894b..0e6cfbcf7a2 100644 --- a/packages/dapi/package.json +++ b/packages/dapi/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/dapi", "private": true, - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "A decentralized API for the Dash network", "scripts": { "api": "node scripts/api.js", diff --git a/packages/dash-spv/package.json b/packages/dash-spv/package.json index 3776e647364..8b13884d9cc 100644 --- a/packages/dash-spv/package.json +++ b/packages/dash-spv/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dash-spv", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "Repository containing SPV functions used by @dashevo", "main": "index.js", "scripts": { diff --git a/packages/dashmate/package.json b/packages/dashmate/package.json index 9dfdeadb72f..071ae2d7a9f 100644 --- a/packages/dashmate/package.json +++ b/packages/dashmate/package.json @@ -1,6 +1,6 @@ { "name": "dashmate", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "Distribution package for Dash node installation", "scripts": { "lint": "eslint .", diff --git a/packages/dashpay-contract/package.json b/packages/dashpay-contract/package.json index e23fc2f5110..08a0069b117 100644 --- a/packages/dashpay-contract/package.json +++ b/packages/dashpay-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dashpay-contract", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "Reference contract of the DashPay DPA on Dash Evolution", "scripts": { "lint": "eslint .", diff --git a/packages/dpns-contract/package.json b/packages/dpns-contract/package.json index 0d9f8268955..26704ee9cf7 100644 --- a/packages/dpns-contract/package.json +++ b/packages/dpns-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dpns-contract", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "A contract and helper scripts for DPNS DApp", "scripts": { "lint": "eslint .", diff --git a/packages/feature-flags-contract/package.json b/packages/feature-flags-contract/package.json index 194b8c2d359..a963f78c32e 100644 --- a/packages/feature-flags-contract/package.json +++ b/packages/feature-flags-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/feature-flags-contract", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "Data Contract to store Dash Platform feature flags", "scripts": { "build": "", diff --git a/packages/js-dapi-client/package.json b/packages/js-dapi-client/package.json index 29124a5e05f..cf869981f4e 100644 --- a/packages/js-dapi-client/package.json +++ b/packages/js-dapi-client/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi-client", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "Client library used to access Dash DAPI endpoints", "main": "lib/index.js", "contributors": [ diff --git a/packages/js-dash-sdk/package.json b/packages/js-dash-sdk/package.json index f0568a0f2cb..f8b04fd5b52 100644 --- a/packages/js-dash-sdk/package.json +++ b/packages/js-dash-sdk/package.json @@ -1,6 +1,6 @@ { "name": "dash", - "version": "3.24.0-dev.18", + "version": "3.24.0-dev.19", "description": "Dash library for JavaScript/TypeScript ecosystem (Wallet, DAPI, Primitives, BLS, ...)", "main": "build/src/index.js", "unpkg": "dist/dash.min.js", diff --git a/packages/js-dpp/package.json b/packages/js-dpp/package.json index c732149cb1f..b71a41497ca 100644 --- a/packages/js-dpp/package.json +++ b/packages/js-dpp/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dpp", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "The JavaScript implementation of the Dash Platform Protocol", "scripts": { "lint": "eslint .", diff --git a/packages/js-grpc-common/package.json b/packages/js-grpc-common/package.json index 4d10063845e..bbf52302ff1 100644 --- a/packages/js-grpc-common/package.json +++ b/packages/js-grpc-common/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/grpc-common", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "Common GRPC library", "main": "index.js", "scripts": { diff --git a/packages/masternode-reward-shares-contract/package.json b/packages/masternode-reward-shares-contract/package.json index bf4477c777e..624ff11c36c 100644 --- a/packages/masternode-reward-shares-contract/package.json +++ b/packages/masternode-reward-shares-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/masternode-reward-shares-contract", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "A contract and helper scripts for reward sharing", "scripts": { "lint": "eslint .", diff --git a/packages/platform-test-suite/package.json b/packages/platform-test-suite/package.json index bf54061cd3e..f808b3ec78b 100644 --- a/packages/platform-test-suite/package.json +++ b/packages/platform-test-suite/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/platform-test-suite", "private": true, - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "Dash Network end-to-end tests", "scripts": { "test": "yarn exec bin/test.sh", diff --git a/packages/wallet-lib/package.json b/packages/wallet-lib/package.json index 5a5b746e8fd..7ab09841f9a 100644 --- a/packages/wallet-lib/package.json +++ b/packages/wallet-lib/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wallet-lib", - "version": "7.24.0-dev.18", + "version": "7.24.0-dev.19", "description": "Light wallet library for Dash", "main": "src/index.js", "unpkg": "dist/wallet-lib.min.js", diff --git a/packages/wasm-dpp/package.json b/packages/wasm-dpp/package.json index 3bace8993af..e15fd588a49 100644 --- a/packages/wasm-dpp/package.json +++ b/packages/wasm-dpp/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-dpp", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "The JavaScript implementation of the Dash Platform Protocol", "main": "dist/index.js", "types": "dist/lib/index.d.ts", diff --git a/packages/withdrawals-contract/package.json b/packages/withdrawals-contract/package.json index eaafb3d7418..e5cfcd33d16 100644 --- a/packages/withdrawals-contract/package.json +++ b/packages/withdrawals-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/withdrawals-contract", - "version": "0.24.0-dev.18", + "version": "0.24.0-dev.19", "description": "Data Contract to manipulate and track withdrawals", "scripts": { "build": "", From 3d6c8cab87d4cf2ea73443aa49ee49d0fd24adf7 Mon Sep 17 00:00:00 2001 From: "markin.io" Date: Wed, 26 Apr 2023 15:52:23 +0100 Subject: [PATCH 20/21] test(rs-drive-abci): fix optional pose_revived_height --- .../execution/data_trigger/reward_share_data_triggers/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rs-drive-abci/src/execution/data_trigger/reward_share_data_triggers/mod.rs b/packages/rs-drive-abci/src/execution/data_trigger/reward_share_data_triggers/mod.rs index 0186a879ddb..ef2b1533c9b 100644 --- a/packages/rs-drive-abci/src/execution/data_trigger/reward_share_data_triggers/mod.rs +++ b/packages/rs-drive-abci/src/execution/data_trigger/reward_share_data_triggers/mod.rs @@ -220,7 +220,7 @@ mod test { state: DMNState { service: SocketAddr::from_str("1.2.3.4:1234").unwrap(), registered_height: 0, - pose_revived_height: 0, + pose_revived_height: Some(0), pose_ban_height: None, revocation_reason: 0, owner_address: [1;20], @@ -251,7 +251,7 @@ mod test { state: DMNState { service: SocketAddr::from_str("1.2.3.5:1234").unwrap(), registered_height: 0, - pose_revived_height: 0, + pose_revived_height: Some(0), pose_ban_height: None, revocation_reason: 0, owner_address: [1;20], From af08d67f429d063aa82b25eecccce592a3d2738c Mon Sep 17 00:00:00 2001 From: "markin.io" Date: Wed, 26 Apr 2023 15:58:05 +0100 Subject: [PATCH 21/21] test(rs-drive-abci): set pose_revived_height to None --- .../execution/data_trigger/reward_share_data_triggers/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rs-drive-abci/src/execution/data_trigger/reward_share_data_triggers/mod.rs b/packages/rs-drive-abci/src/execution/data_trigger/reward_share_data_triggers/mod.rs index ef2b1533c9b..ced0caa9569 100644 --- a/packages/rs-drive-abci/src/execution/data_trigger/reward_share_data_triggers/mod.rs +++ b/packages/rs-drive-abci/src/execution/data_trigger/reward_share_data_triggers/mod.rs @@ -220,7 +220,7 @@ mod test { state: DMNState { service: SocketAddr::from_str("1.2.3.4:1234").unwrap(), registered_height: 0, - pose_revived_height: Some(0), + pose_revived_height: None, pose_ban_height: None, revocation_reason: 0, owner_address: [1;20], @@ -251,7 +251,7 @@ mod test { state: DMNState { service: SocketAddr::from_str("1.2.3.5:1234").unwrap(), registered_height: 0, - pose_revived_height: Some(0), + pose_revived_height: None, pose_ban_height: None, revocation_reason: 0, owner_address: [1;20],