From d771e82ce553daa4fdc87c268dbefc6052f8d0e2 Mon Sep 17 00:00:00 2001 From: Bartek Wrona Date: Sat, 2 Aug 2025 15:06:52 +0200 Subject: [PATCH 01/13] Restored accidently deleted files --- core/hive_protocol_types.hpp | 18 ++++++ core/minimize_required_signatures_helper.cpp | 58 ++++++++++++++++++++ core/minimize_required_signatures_helper.hpp | 34 ++++++++++++ core/signing_keys_collector.cpp | 57 +++++++++++++++++++ core/signing_keys_collector.hpp | 45 +++++++++++++++ python/CMakeLists.txt | 2 + 6 files changed, 214 insertions(+) create mode 100644 core/hive_protocol_types.hpp create mode 100644 core/minimize_required_signatures_helper.cpp create mode 100644 core/minimize_required_signatures_helper.hpp create mode 100644 core/signing_keys_collector.cpp create mode 100644 core/signing_keys_collector.hpp diff --git a/core/hive_protocol_types.hpp b/core/hive_protocol_types.hpp new file mode 100644 index 000000000..5b09cd846 --- /dev/null +++ b/core/hive_protocol_types.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include + +namespace hive::protocol +{ +struct authorities_t + { + authority active; + authority owner; + authority posting; + }; + +using authorities_map_t = std::map; + +} // namespace hive::protocol diff --git a/core/minimize_required_signatures_helper.cpp b/core/minimize_required_signatures_helper.cpp new file mode 100644 index 000000000..f0f2f3e02 --- /dev/null +++ b/core/minimize_required_signatures_helper.cpp @@ -0,0 +1,58 @@ +#include "core/minimize_required_signatures_helper.hpp" + +#include + +using namespace hive::protocol; +using namespace fc; + +namespace cpp +{ + +std::vector minimize_required_signatures_helper::minimize_required_signatures( + const signed_transaction& transaction, + const std::string& chain_id, + const std::vector& available_keys, + const authorities_map_t& authorities_map, + witness_public_key_getter_cb_t get_witness_key_cb, + void* get_witness_key_fn, + std::optional max_recursion, + std::optional max_membership, + std::optional max_account_auths, + bool allow_strict_and_mixed_authorities) +{ + flat_set _available_keys; + for (const auto& key : available_keys) + _available_keys.emplace(key); + auto get_authorities = [&] (const std::string& account_name) -> const authorities_t& { + auto it = authorities_map.find(account_name); + FC_ASSERT( it != authorities_map.end(), + "Tried to access authority for account ${a} but not cached.", ( "a", account_name ) ); + return it->second; + }; + authority_getter get_active = [&] (const std::string& account_name) { + return get_authorities(account_name).active; + }; + authority_getter get_owner = [&] (const std::string& account_name) { + return get_authorities(account_name).owner; + }; + authority_getter get_posting = [&] (const std::string& account_name) { + return get_authorities(account_name).posting; + }; + witness_public_key_getter _get_witness_key = [&] (const std::string& witness_name) { + return public_key_type(get_witness_key_cb(witness_name, get_witness_key_fn)); + }; + uint32_t _max_recursion = max_recursion ? *max_recursion : HIVE_MAX_SIG_CHECK_DEPTH; + uint32_t _max_membership = max_membership ? *max_membership : HIVE_MAX_AUTHORITY_MEMBERSHIP; + uint32_t _max_account_auths = max_account_auths ? *max_account_auths : HIVE_MAX_SIG_CHECK_ACCOUNTS; + + const auto signatures = transaction.minimize_required_signatures(allow_strict_and_mixed_authorities, chain_id_type(chain_id), _available_keys, + get_active, get_owner, get_posting, _get_witness_key, _max_recursion, _max_membership, _max_account_auths); + + std::vector result; + for (const auto& signature : signatures) + result.emplace_back(static_cast(signature)); + + return result; +} + +} // namespace cpp diff --git a/core/minimize_required_signatures_helper.hpp b/core/minimize_required_signatures_helper.hpp new file mode 100644 index 000000000..66a96141e --- /dev/null +++ b/core/minimize_required_signatures_helper.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +#include "core/types.hpp" +#include "core/hive_protocol_types.hpp" + +namespace hive::protocol +{ +struct signed_transaction; +} // namespace hive::protocol + +namespace cpp +{ + +class minimize_required_signatures_helper +{ +public: + static std::vector minimize_required_signatures( + const hive::protocol::signed_transaction& transaction, + const std::string& chain_id, + const std::vector& available_keys, + const hive::protocol::authorities_map_t& authorities_map, + witness_public_key_getter_cb_t get_witness_key_cb, + void* get_witness_key_fn, + std::optional max_recursion, + std::optional max_membership, + std::optional max_account_auths, + bool allow_strict_and_mixed_authorities = false); +}; + +} // namespace cpp diff --git a/core/signing_keys_collector.cpp b/core/signing_keys_collector.cpp new file mode 100644 index 000000000..c480a420a --- /dev/null +++ b/core/signing_keys_collector.cpp @@ -0,0 +1,57 @@ +#include "core/signing_keys_collector.hpp" + +#include "core/hive_protocol_types.hpp" + +using namespace hive::protocol; +using namespace fc; + +namespace cpp +{ + +std::vector signing_keys_collector::collect_signing_keys(const transaction& transaction) +{ + flat_set keys; + hive::protocol::signing_keys_collector::collect_signing_keys(&keys, transaction); + std::vector result; + + result.reserve(keys.size()); + for (const auto& key : keys) + result.emplace_back(static_cast(key)); + + return result; +} + +void signing_keys_collector::prepare_account_authority_data( const std::vector< account_name_type >& accounts ) +{ + std::vector account_names; + for (const auto& account : accounts) + account_names.emplace_back(static_cast(account)); + auto _account_authorities = retrieve_authorities(account_names); + account_authorities.merge(_account_authorities); +} + +const authority& signing_keys_collector::get_active( const account_name_type& account_name ) const +{ + return get_authorities(account_name).active; +} + +const authority& signing_keys_collector::get_owner( const account_name_type& account_name ) const +{ + return get_authorities(account_name).owner; +} + +const authority& signing_keys_collector::get_posting( const account_name_type& account_name ) const +{ + return get_authorities(account_name).posting; +} + +inline +const signing_keys_collector::authorities_t& signing_keys_collector::get_authorities( const account_name_type& account_name ) const +{ + auto it = account_authorities.find( account_name ); + FC_ASSERT( it != account_authorities.end(), + "Tried to access authority for account ${a} but not cached.", ( "a", account_name ) ); + return it->second; +} + +} // namespace cpp diff --git a/core/signing_keys_collector.hpp b/core/signing_keys_collector.hpp new file mode 100644 index 000000000..c080f018d --- /dev/null +++ b/core/signing_keys_collector.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "core/hive_protocol_types.hpp" + +#include + +#include +#include +#include +#include + +namespace cpp +{ + +class signing_keys_collector : public hive::protocol::signing_keys_collector +{ +public: + using authority = hive::protocol::authority; + using account_name_type = hive::protocol::account_name_type; + using authorities_t = hive::protocol::authorities_t; + using authorities_map_t = hive::protocol::authorities_map_t; + using retrieve_authorities_t = std::function&)>; + + signing_keys_collector(const retrieve_authorities_t& _retrieve_authorities) + : retrieve_authorities(_retrieve_authorities) {} + virtual ~signing_keys_collector() {} + + using hive::protocol::signing_keys_collector::collect_signing_keys; + std::vector collect_signing_keys(const hive::protocol::transaction& transaction); + +private: + virtual void prepare_account_authority_data( const std::vector< account_name_type >& accounts ) override; + + virtual const authority& get_active( const account_name_type& account_name ) const override; + virtual const authority& get_owner( const account_name_type& account_name ) const override; + virtual const authority& get_posting( const account_name_type& account_name ) const override; + + inline const authorities_t& get_authorities( const account_name_type& account_name ) const; + +private: + retrieve_authorities_t retrieve_authorities; + std::map account_authorities; +}; + +} // namespace cpp diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 7fd04edc4..a1be7190e 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -55,6 +55,8 @@ add_python_library("cpp_python_bridge" MODULE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../core/foundation.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../core/utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../core/binary_view_helper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../core/signing_keys_collector.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../core/minimize_required_signatures_helper.cpp cpython_interface.cpp ${HEADERS} LINK_LIBRARIES hive_protocol) -- GitLab From 98061ff587f048a3e262eb365288b4465ec65966 Mon Sep 17 00:00:00 2001 From: Bartek Wrona Date: Sat, 2 Aug 2025 15:49:55 +0200 Subject: [PATCH 02/13] Restored cpp_collect_signing_keys and cpp_minimize_required_signatures as part of foundation class (also adjusted to handle implemnetation). --- core/foundation.cpp | 62 +++++++++++++++++++++++++++++++++++++++++++++ core/foundation.hpp | 3 +++ core/types.hpp | 2 +- 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/core/foundation.cpp b/core/foundation.cpp index 22f3e00ee..f7eb46f40 100644 --- a/core/foundation.cpp +++ b/core/foundation.cpp @@ -3,6 +3,8 @@ #include "core/types.hpp" #include "core/utils.hpp" #include "core/binary_view_helper.hpp" +#include "core/signing_keys_collector.hpp" +#include "core/minimize_required_signatures_helper.hpp" #include #include @@ -378,6 +380,19 @@ hive::protocol::authority convert_wax_authority_to_protocol_authority(const wax_ return a; } + +static inline +hive::protocol::authorities_t convert_wax_authorities_to_authorities(const wax_authorities& w_authorities) +{ + using authority = hive::protocol::authority; + + authority active = convert_wax_authority_to_protocol_authority(w_authorities.active); + authority owner = convert_wax_authority_to_protocol_authority(w_authorities.owner); + authority posting = convert_wax_authority_to_protocol_authority(w_authorities.posting); + + return { std::move(active), std::move(owner), std::move(posting) }; +} + hive::protocol::authority_verification_trace foundation::cpp_trace_authority_verification( const required_authority_collection_t& required_authorities, const std::vector& decodedSignaturePublicKeys, @@ -1055,6 +1070,53 @@ std::string foundation::cpp_tx_sig_digest(const hive_transaction_handle& tx_hand }); } +std::vector foundation::cpp_collect_signing_keys(const hive_transaction_handle& tx_handle, retrieve_authorities_cb_t retrieve_authorities_cb, void* retrieve_authorities_fn) const +{ + return cpp::safe_exception_wrapper([&]() -> std::vector { + const auto& tx = tx_handle.get(); + signing_keys_collector::retrieve_authorities_t retrieve_authorities = [&](const std::vector& accounts) + { + const auto wax_authorities_map = retrieve_authorities_cb(accounts, retrieve_authorities_fn); + hive::protocol::authorities_map_t authorities_map; + for (const auto& wax_authorities_info : wax_authorities_map) + { + signing_keys_collector::account_name_type account = wax_authorities_info.first; + signing_keys_collector::authorities_t authorities = convert_wax_authorities_to_authorities(wax_authorities_info.second); + authorities_map.emplace(account, std::move(authorities)); + } + + return authorities_map; + }; + + signing_keys_collector signing_keys_collector(retrieve_authorities); + std::vector result = signing_keys_collector.collect_signing_keys(tx); + + return result; + }); +} + +std::vector foundation::cpp_minimize_required_signatures(const hive_transaction_handle& tx_handle, const minimize_required_signatures_data_t& minimize_required_signatures_data) const +{ + return cpp::safe_exception_wrapper([&]() -> std::vector { + const auto& tx = tx_handle.get(); + hive::protocol::authorities_map_t authorities_map; + for (const auto& wax_authorities_info : minimize_required_signatures_data.authorities_map) + { + hive::protocol::account_name_type account = wax_authorities_info.first; + hive::protocol::authorities_t authorities = convert_wax_authorities_to_authorities(wax_authorities_info.second); + authorities_map.emplace(account, std::move(authorities)); + } + + auto result = minimize_required_signatures_helper::minimize_required_signatures( + tx, minimize_required_signatures_data.chain_id, minimize_required_signatures_data.available_keys, authorities_map, + minimize_required_signatures_data.get_witness_key_cb, minimize_required_signatures_data.get_witness_key_fn, + minimize_required_signatures_data.max_recursion, minimize_required_signatures_data.max_membership, minimize_required_signatures_data.max_account_auths, + minimize_required_signatures_data.allow_strict_and_mixed_authorities); + + return result; + }); +} + void foundation::cpp_tx_validate(const hive_transaction_handle& tx_handle)const { return cpp::safe_exception_wrapper([&]() -> void { diff --git a/core/foundation.hpp b/core/foundation.hpp index 73cc024be..14151a35b 100644 --- a/core/foundation.hpp +++ b/core/foundation.hpp @@ -248,6 +248,9 @@ public: std::vector cpp_tx_impacted_accounts(const hive_transaction_handle& tx_handle)const; std::vector cpp_tx_signature_keys(const hive_transaction_handle& tx_handle, const std::string& chain_id, bool use_hf26_serialization)const; std::string cpp_tx_sig_digest(const hive_transaction_handle& tx_handle, const std::string& chain_id, bool use_hf26_serialization)const; + std::vector cpp_collect_signing_keys(const hive_transaction_handle& tx_handle, retrieve_authorities_cb_t retrieve_authorities_cb, void* retrieve_authorities_fn) const; + std::vector cpp_minimize_required_signatures(const hive_transaction_handle& tx_handle, const minimize_required_signatures_data_t& minimize_required_signatures_data) const; + void cpp_tx_validate(const hive_transaction_handle& tx_handle)const; // protected: // XXX: Temporary remove this, as it may not be supported by emscripten diff --git a/core/types.hpp b/core/types.hpp index f0df49576..36f06f42e 100644 --- a/core/types.hpp +++ b/core/types.hpp @@ -167,7 +167,7 @@ struct minimize_required_signatures_data_t std::vector available_keys; wax_authorities_map_t authorities_map; witness_public_key_getter_cb_t get_witness_key_cb; - void* get_witness_key_fn; + void* get_witness_key_fn = nullptr; std::optional max_recursion; std::optional max_membership; std::optional max_account_auths; -- GitLab From 06ea94905b0e9f990a40d169cf02db1aec575ab8 Mon Sep 17 00:00:00 2001 From: Bartek Wrona Date: Sun, 3 Aug 2025 16:38:45 +0200 Subject: [PATCH 03/13] Restored Cython interface specific to collect_signing_keys and minimize_required_signatures functions --- python/cpp_python_bridge.pxd | 3 ++ python/cpp_python_bridge.pyx | 61 ++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/python/cpp_python_bridge.pxd b/python/cpp_python_bridge.pxd index 5d1f867ff..51e534d2f 100644 --- a/python/cpp_python_bridge.pxd +++ b/python/cpp_python_bridge.pxd @@ -212,6 +212,9 @@ cdef extern from "cpython_interface.hpp" namespace "cpp": witness_set_properties_serialized cpp_serialize_witness_set_properties(witness_set_properties_data value) except + witness_set_properties_data cpp_deserialize_witness_set_properties(witness_set_properties_serialized value) except + + vector[string] cpp_collect_signing_keys( hive_transaction_handle tx_handle, retrieve_authorities_t retrieve_authorities, void* retrieve_authorities_fn ) except + + vector[string] cpp_minimize_required_signatures( hive_transaction_handle tx_handle, minimize_required_signatures_data_t minimize_required_signatures_data ) except + + string cpp_asset_value(json_asset value) except + string cpp_asset_symbol(json_asset value) except + diff --git a/python/cpp_python_bridge.pyx b/python/cpp_python_bridge.pyx index 5ed83ee5c..53364d18c 100644 --- a/python/cpp_python_bridge.pyx +++ b/python/cpp_python_bridge.pyx @@ -34,6 +34,7 @@ from .wax_result import ( python_price, python_authority, python_authorities, + python_minimize_required_signatures_data, ) def return_python_result(foo): @@ -771,6 +772,66 @@ cdef wax_authorities python_authorities_to_wax_authorities(object auths_obj): auths.posting = python_authority_to_wax_authority(auths_obj.posting) return auths +cdef cppmap[cppstring, wax_authorities] retrieve_authorities_cb(vector[cppstring] account_names, void* retrieve_authorities_fn): + cdef object obj = (retrieve_authorities_fn)(account_names) + cdef cppmap[cppstring, wax_authorities] result + for k, v in obj.items(): + auths = python_authorities_to_wax_authorities(v) + result[k] = auths + return result + +def tx_collect_signing_keys(wax_tx: WaxTransactionHandle, retrieve_authorities: Callable[[list[bytes]], dict[bytes, python_authorities]]) -> list[bytes]: + cdef protocol obj + return obj.cpp_collect_signing_keys(wax_tx.hTx, retrieve_authorities_cb, (retrieve_authorities)) + +def collect_signing_keys(transaction: bytes, retrieve_authorities: Callable[[list[bytes]], dict[bytes, python_authorities]]) -> list[bytes]: + tx = json.loads(transaction) + wax_tx = create_wax_transaction(tx, False) + + return tx_collect_signing_keys(wax_tx, retrieve_authorities) + +cdef cppstring get_witness_key_cb(cppstring account_name, void* get_witness_key_fn): + cdef result = (get_witness_key_fn)(account_name) + return result + +def tx_minimize_required_signatures( + wax_tx: WaxTransactionHandle, + minimize_required_signatures_data: python_minimize_required_signatures_data, +) -> list[bytes]: + cdef protocol obj + cdef minimize_required_signatures_data_t wax_minimize_required_signatures_data + cdef uint32_t _uint_helper + + wax_minimize_required_signatures_data.chain_id = minimize_required_signatures_data.chain_id + wax_minimize_required_signatures_data.available_keys = minimize_required_signatures_data.available_keys + for k, v in minimize_required_signatures_data.authorities_map.items(): + auths = python_authorities_to_wax_authorities(v) + wax_minimize_required_signatures_data.authorities_map[k] = auths + wax_minimize_required_signatures_data.get_witness_key_cb = get_witness_key_cb + wax_minimize_required_signatures_data.get_witness_key_fn = minimize_required_signatures_data.get_witness_key + if minimize_required_signatures_data.max_recursion is not None: + _uint_helper = int(minimize_required_signatures_data.max_recursion) + wax_minimize_required_signatures_data.max_recursion = _uint_helper + if minimize_required_signatures_data.max_membership is not None: + _uint_helper = int(minimize_required_signatures_data.max_membership) + wax_minimize_required_signatures_data.max_membership = _uint_helper + if minimize_required_signatures_data.max_account_auths is not None: + _uint_helper = int(minimize_required_signatures_data.max_account_auths) + wax_minimize_required_signatures_data.max_account_auths = _uint_helper + wax_minimize_required_signatures_data.allow_strict_and_mixed_authorities = minimize_required_signatures_data.allow_strict_and_mixed_authorities + + return obj.cpp_minimize_required_signatures(wax_tx.hTx, wax_minimize_required_signatures_data) + +def minimize_required_signatures( + transaction: bytes, + minimize_required_signatures_data: python_minimize_required_signatures_data, +) -> list[bytes]: + + tx = json.loads(transaction) + tx_handle = create_wax_transaction(tx, False) + + return tx_minimize_required_signatures(tx_handle, minimize_required_signatures_data) + def check_memo_for_private_keys(memo: bytes, account: bytes, auths: python_authorities, memo_key: bytes, imported_keys: list[bytes]) -> None: cdef protocol obj cdef wax_authorities wax_auths = python_authorities_to_wax_authorities(auths) -- GitLab From 14527cbc2e764fb8e4b058bdabf39458d56a3ac6 Mon Sep 17 00:00:00 2001 From: Bartek Wrona Date: Sun, 3 Aug 2025 16:40:38 +0200 Subject: [PATCH 04/13] collect_signing_keys, minimize_required_signatures exposed to Python package interface --- python/wax/__init__.py | 6 ++++++ python/wax/cpp_python_bridge.pyi | 8 +++++++- python/wax/wax_result.py | 14 +++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/python/wax/__init__.py b/python/wax/__init__.py index 3d5625f12..f8f199034 100644 --- a/python/wax/__init__.py +++ b/python/wax/__init__.py @@ -22,6 +22,7 @@ from .cpp_python_bridge import ( calculate_vests_to_hp, calculate_witness_votes_hp, check_memo_for_private_keys, + collect_signing_keys, create_wax_operation, create_wax_transaction, decode_encrypted_memo, @@ -40,6 +41,7 @@ from .cpp_python_bridge import ( handle_deserialize_transaction, hbd, hive, + minimize_required_signatures, op_binary, op_impacted_accounts, op_to_binary, @@ -87,6 +89,7 @@ from .wax_result import ( python_brain_key_data, python_error_code, python_json_asset, + python_minimize_required_signatures_data, python_operation_handle, python_price, python_private_key_data, @@ -118,6 +121,7 @@ __all__ = [ "python_witness_set_properties_data", "python_authority", "python_authorities", + "python_minimize_required_signatures_data", "python_price", "python_binary_data", "python_binary_data_node", @@ -167,6 +171,8 @@ __all__ = [ "deserialize_witness_set_properties", "estimate_hive_collateral", "check_memo_for_private_keys", + "collect_signing_keys", + "minimize_required_signatures", "get_hive_protocol_config", "tx_api_to_proto", "tx_proto_to_api", diff --git a/python/wax/cpp_python_bridge.pyi b/python/wax/cpp_python_bridge.pyi index 508618d9c..4f2a3bfc8 100644 --- a/python/wax/cpp_python_bridge.pyi +++ b/python/wax/cpp_python_bridge.pyi @@ -15,7 +15,8 @@ from .wax_result import ( python_operation_handle, python_witness_set_properties_data, python_authorities, - python_price + python_price, + python_minimize_required_signatures_data ) def is_valid_account_name(account_name: bytes) -> bool: ... @@ -131,6 +132,11 @@ def decode_encrypted_memo(encoded_memo: bytes) -> python_encrypted_memo: ... def verify_exception_handling( throw_type: int ) -> None: ... def serialize_witness_set_properties(input_properties: python_witness_set_properties_data) -> dict[bytes, bytes]: ... def deserialize_witness_set_properties(serialized_properties: dict[bytes, bytes]) -> python_witness_set_properties_data: ... +def collect_signing_keys(transaction: bytes, retrieve_authorities: Callable[[list[bytes]], dict[bytes, python_authorities]]) -> list[bytes]: ... +def minimize_required_signatures( + signed_transaction: bytes, + minimize_required_signatures_data: python_minimize_required_signatures_data +) -> list[bytes]: ... def estimate_hive_collateral( current_median_history: python_price, current_min_history: python_price, diff --git a/python/wax/wax_result.py b/python/wax/wax_result.py index 72afdcf6e..e80350528 100644 --- a/python/wax/wax_result.py +++ b/python/wax/wax_result.py @@ -2,7 +2,7 @@ from __future__ import annotations from dataclasses import dataclass from enum import IntEnum -from typing import TYPE_CHECKING, TypeAlias +from typing import TYPE_CHECKING, Callable, TypeAlias if TYPE_CHECKING: string: TypeAlias = bytes # noqa: PYI042 @@ -155,3 +155,15 @@ class python_authorities: # noqa: N801 active: python_authority owner: python_authority posting: python_authority + + +@dataclass +class python_minimize_required_signatures_data: # noqa: N801 + chain_id: bytes + available_keys: list[bytes] + authorities_map: dict[bytes, python_authorities] + get_witness_key: Callable[[bytes], bytes] + max_recursion: int | None = None + max_membership: int | None = None + max_account_auths: int | None = None + allow_strict_and_mixed_authorities: bool = False -- GitLab From 96226110126c5db2d3323eac68b3bcb6cd1ad57c Mon Sep 17 00:00:00 2001 From: Bartek Wrona Date: Sun, 3 Aug 2025 16:47:42 +0200 Subject: [PATCH 05/13] Restored python unit tests covering collect_signing_keys and minimize_required_signatures --- ...est_get_transaction_required_autorities.py | 0 .../protocol/test_collect_signing_keys.py | 46 ++++++++++ .../test_minimize_required_signatures.py | 88 +++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 python/test_get_transaction_required_autorities.py create mode 100644 python/tests/protocol/test_collect_signing_keys.py create mode 100644 python/tests/protocol/test_minimize_required_signatures.py diff --git a/python/test_get_transaction_required_autorities.py b/python/test_get_transaction_required_autorities.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/tests/protocol/test_collect_signing_keys.py b/python/tests/protocol/test_collect_signing_keys.py new file mode 100644 index 000000000..0aafe0753 --- /dev/null +++ b/python/tests/protocol/test_collect_signing_keys.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from typing import Any + +import json + +import pytest + +from wax import collect_signing_keys, python_authority, python_authorities +from tests.utils.refs import ( + ACCOUNT_AUTHS, + SIGNING_KEYS, + API_TRX_SIG1, + API_TRX_SIG5_v0, + API_TRX_SIG5_v1, + API_TRX_SIG5_v2, +) + + +def retrieve_authorities(account_names: list[bytes]) -> dict[bytes, python_authorities]: + authorities_map: dict[bytes, python_authorities] = {} + for account_name in account_names: + print(f"python retrieve_authorities:account_name = '{account_name.decode()}'") + auths = ACCOUNT_AUTHS[account_name.decode()] + authorities_map[account_name] = auths + print(f"python retrieve_authorities:authorities_map = {authorities_map}") + return authorities_map + + +@pytest.mark.parametrize( + "transaction, signing_keys", + [ + (API_TRX_SIG1, SIGNING_KEYS["API_TRX_SIG1"]), + (API_TRX_SIG5_v0, SIGNING_KEYS["API_TRX_SIG5_v0"]), + (API_TRX_SIG5_v1, SIGNING_KEYS["API_TRX_SIG5_v1"]), + (API_TRX_SIG5_v2, SIGNING_KEYS["API_TRX_SIG5_v2"]), + ], +) +def test_collect_signing_keys(transaction: dict, signing_keys: list) -> None: + tx_str = json.dumps(transaction) + keys = collect_signing_keys(tx_str.encode(), retrieve_authorities) + + for key in keys: + print(f"key: {key}") + + assert keys == signing_keys, "Signing keys are incorrect" diff --git a/python/tests/protocol/test_minimize_required_signatures.py b/python/tests/protocol/test_minimize_required_signatures.py new file mode 100644 index 000000000..863a6c7a8 --- /dev/null +++ b/python/tests/protocol/test_minimize_required_signatures.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +from typing import Any + +import json + +import pytest + +from wax import minimize_required_signatures, python_authority, python_authorities, python_minimize_required_signatures_data +from tests.utils.refs import ( + ACCOUNT_AUTHS, + API_TRX_SIG1, + API_TRX_SIG5_v0, + API_TRX_SIG5_v1, + API_TRX_SIG5_v2, +) + + +AVAILABLE_KEYS: Final[list[bytes]] = [ + b"STM5zw6KDtQiiJMhkdkFm8CXxPUEa2QyitHBhkCE1iMJEGmEfd5aE", + b"STM574A9CiTg3EkcsZ7VfXin8tVtFVWqGq5x2wrfoqv5yMfxvx96d", + b"STM57hDwzvNYEYfL4wLj9REhaRgiNxdFt232SxVzPwZYPqiH2ZfNW", + b"STM5dhkPS223F9d3TCXKttuWpdWgqS2Fx8KNRQve6BMGmAvJ5GnJR", + b"STM5khoFYgEg8Mvh989JmXLhgEgwAF78nPRr2xppQgafzWCXe2krQ", + b"STM62fkRnTJSeJoWMLS5r61cgQbxSo3JJ7BoxCgZrkfRuNN71hA1A", + b"STM64Bb5TXsiEbjjLsgVrvVttEDsLNSot9p8zJd41D5zEr5opxcHK", + b"STM64peLPcuSsUk591okRGUwv5rsTadSnnqi9ddMZhrVkxkDaSUzo", + b"STM6v5nwZYvAmyZZUoSdjJvgJ3FwiDHdKuWsAaB4zx3qhuhdyy76s", + b"STM7KDcjUNMqUdohFu9iYjCAqYEyXfM7pjNLx96GhRNpdYscB3aQc", + b"STM7S3wsVtQotgKLN8wFLPNBALe6YHt8MPLEHuTH5CxfxdhpGPBUP", + b"STM8AZuk2ja5vSFySFL2zpB9bNew8wJAg8r4QFtbnoamEX8Jvvq43", + b"STM8jviUDRAefxmTQ9m8wNdiQV5dmCPSMDjSnztPYZpHf1yfaD6Rd", +] + +SIGNING_KEYS: Final[dict[str, list[bytes]]] = { + "API_TRX_SIG1": [b"STM5zw6KDtQiiJMhkdkFm8CXxPUEa2QyitHBhkCE1iMJEGmEfd5aE"], + "API_TRX_SIG5_v0": [ + b'STM5dhkPS223F9d3TCXKttuWpdWgqS2Fx8KNRQve6BMGmAvJ5GnJR', + b'STM64Bb5TXsiEbjjLsgVrvVttEDsLNSot9p8zJd41D5zEr5opxcHK', + b'STM7S3wsVtQotgKLN8wFLPNBALe6YHt8MPLEHuTH5CxfxdhpGPBUP', + b'STM8AZuk2ja5vSFySFL2zpB9bNew8wJAg8r4QFtbnoamEX8Jvvq43', + b'STM8jviUDRAefxmTQ9m8wNdiQV5dmCPSMDjSnztPYZpHf1yfaD6Rd', + ], + "API_TRX_SIG5_v1": [ + b'STM5dhkPS223F9d3TCXKttuWpdWgqS2Fx8KNRQve6BMGmAvJ5GnJR', + b'STM64Bb5TXsiEbjjLsgVrvVttEDsLNSot9p8zJd41D5zEr5opxcHK', + b'STM7S3wsVtQotgKLN8wFLPNBALe6YHt8MPLEHuTH5CxfxdhpGPBUP', + b'STM8AZuk2ja5vSFySFL2zpB9bNew8wJAg8r4QFtbnoamEX8Jvvq43', + b'STM8jviUDRAefxmTQ9m8wNdiQV5dmCPSMDjSnztPYZpHf1yfaD6Rd', + ], + "API_TRX_SIG5_v2": [ + b'STM5dhkPS223F9d3TCXKttuWpdWgqS2Fx8KNRQve6BMGmAvJ5GnJR', + b'STM64Bb5TXsiEbjjLsgVrvVttEDsLNSot9p8zJd41D5zEr5opxcHK', + b'STM7S3wsVtQotgKLN8wFLPNBALe6YHt8MPLEHuTH5CxfxdhpGPBUP', + b'STM8AZuk2ja5vSFySFL2zpB9bNew8wJAg8r4QFtbnoamEX8Jvvq43', + b'STM8jviUDRAefxmTQ9m8wNdiQV5dmCPSMDjSnztPYZpHf1yfaD6Rd', + ], +} + +def get_witness_key(account_name: bytes) -> bytes: + print(f"get_witness_key: {account_name}") + return b"" + +MINIMIZE_REQUIRED_SIGNATURES_DATA = python_minimize_required_signatures_data( + chain_id=b"beeab0de00000000000000000000000000000000000000000000000000000000", + available_keys=AVAILABLE_KEYS, + authorities_map=ACCOUNT_AUTHS, + get_witness_key=get_witness_key, +) + + +@pytest.mark.parametrize( + "transaction, signing_keys", + [ + (API_TRX_SIG1, SIGNING_KEYS["API_TRX_SIG1"]), + (API_TRX_SIG5_v0, SIGNING_KEYS["API_TRX_SIG5_v0"]), + (API_TRX_SIG5_v1, SIGNING_KEYS["API_TRX_SIG5_v1"]), + (API_TRX_SIG5_v2, SIGNING_KEYS["API_TRX_SIG5_v2"]), + ], +) +def test_minimize_required_signatures(transaction: dict, signing_keys: list) -> None: + tx_str = json.dumps(transaction) + keys = minimize_required_signatures(tx_str.encode(), MINIMIZE_REQUIRED_SIGNATURES_DATA) + + for key in keys: + print(f"key: {key}") + + assert keys == signing_keys, "Signing keys are incorrect" -- GitLab From 674de9391b228f48206568a22dffd7cdd6a517ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kudela?= Date: Mon, 4 Aug 2025 16:24:17 +0200 Subject: [PATCH 06/13] Add failed test, for comment_options_operations with missing fields --- .../protocol/test_missing_operation_fields.py | 282 ++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 python/tests/protocol/test_missing_operation_fields.py diff --git a/python/tests/protocol/test_missing_operation_fields.py b/python/tests/protocol/test_missing_operation_fields.py new file mode 100644 index 000000000..0f421d69f --- /dev/null +++ b/python/tests/protocol/test_missing_operation_fields.py @@ -0,0 +1,282 @@ +import json + +from wax import validate_transaction, calculate_transaction_id + +import json + +from wax import validate_transaction + +def test_another_bad_serialization(): + trx = json.dumps( + { + "ref_block_num":10, + "ref_block_prefix":1549820395, + "expiration":"2025-08-06T08:27:33", + "extensions":[ + ], + "signatures":[ + "203e4bbd2d08fcfe484f36492406fe97d07018cf17226e2fa065948986be336499575e1c1206b21d80a33dbfbb93f790ca5204e36b83ec3f5498c8d049f879fbc0" + ], + "operations":[ + { + "type":"account_update_operation", + "value":{ + "account":"bob", + "owner":{ + "weight_threshold":1, + "account_auths":[ + ], + "key_auths":[ + [ + "STM613c9viKXrSGASsJj3yKogatPHCzDhZzASJgM33FjM3ThDLyG6", + 1 + ], + [ + "STM613c9viKXrSGASsJj3yKogatPHCzDhZzASJgM33FjM3ThDLyG6", + 3 + ] + ] + }, + "memo_key":"STM7HGgvzxAsNFxMB1h5pMVqBpixPp2iA4nkZD9YnuDvvdfzAwCVn", + "json_metadata":"{}" + } + } + ] + } + ) + result = calculate_transaction_id(trx.encode()) + assert result.status == result.status.ok + assert result.result == b'f022d1cab57d8e53e5cc15c833496c800f391e0a' + +def test_missing_allow_votes_and_allow_curation_rewards_members_in_comment_options_operation(): + """ + Transaction from hive-python test: + test_is_get_impacted_accounts_operation_collect_accounts_from_the_comment_payout_beneficiaries + """ + + trx = json.dumps( + { + "ref_block_num": 6, + "ref_block_prefix": 4097099125, + "expiration": "2025-08-04T13:33:57", + "extensions": [], + "signatures": [], + "operations": [ + { + "type": "comment_options_operation", + "value": { + "author": "initminer", + "permlink": "test-post", + "max_accepted_payout": {"amount": "100000000", "nai": "@@000000013", "precision": 3}, + "percent_hbd": 10000, + "extensions": [ + { + "type": "comment_payout_beneficiaries", + "value": {"beneficiaries": [{"account": "alice", "weight": 100}]}, + } + ], + # "allow_votes": "true", # todo: missing fields + # "allow_curation_rewards": "true", # todo: missing fields + }, + } + ], + } + ) + + result = validate_transaction(trx.encode()) + assert result.status == result.status.ok + + +def test_missing_decline_field_in_decline_voting_rights_operation(): + """ + Transaction from hive-python test: + test_remove_decline_voting_rights_request + """ + trx = json.dumps( + { + "ref_block_num": 20, + "ref_block_prefix": 2595823944, + "expiration": "2025-08-05T07:32:39", + "extensions": [], + "signatures": [], + "operations": [ + { + "type": "decline_voting_rights_operation", + "value": { + "account": "voter", + # "decline": "true" # todo: missing field + }, + } + ], + } + ) + + result = validate_transaction(trx.encode()) + assert result.status == result.status.ok + + +def test_missing_fill_or_kill_in_limit_order_create_operation(): + """ + Transaction from hive-python test: + test_remove_decline_voting_rights_request + """ + trx = json.dumps( + { + "ref_block_num": 8, + "ref_block_prefix": 2889768484, + "expiration": "2025-08-05T07:37:51", + "extensions": [], + "signatures": [], + "operations": [ + { + "type": "limit_order_create_operation", + "value": { + "owner": "alice", + "orderid": 0, + "amount_to_sell": {"amount": "300000", "nai": "@@000000021", "precision": 3}, + "min_to_receive": {"amount": "30000", "nai": "@@000000013", "precision": 3}, + "expiration": "2025-08-05T08:37:21", + # "fill_or_kill": "false", # todo: missing field + }, + } + ], + } + ) + result = validate_transaction(trx.encode()) + assert result.status == result.status.ok + + +def test_missing_approve_field_in_account_witness_vote_operation(): + """ + Transaction from hive-python test: + test_list_witness_votes + """ + trx = json.dumps( + { + "ref_block_num": 7, + "ref_block_prefix": 1475512945, + "expiration": "2025-08-05T07:49:09", + "extensions": [], + "signatures": [], + "operations": [ + { + "type": "account_witness_vote_operation", + "value": { + "account": "bob", + "witness": "alice", + # "approve": "true", # todo: missing field + }, + } + ], + } + ) + result = validate_transaction(trx.encode()) + assert result.status == result.status.ok + + +def test_missing_max_accepted_payout_field_in_comment_options_operation(): + """ + Transaction from hive-python test: + test_claim_all_calculated_vests_reward + """ + trx = json.dumps( + { + "ref_block_num": 127, + "ref_block_prefix": 2905439095, + "expiration": "2025-08-05T10:35:51", + "extensions": [], + "signatures": [], + "operations": [ + { + "type": "comment_operation", + "value": { + "parent_author": "", + "parent_permlink": "parent-permlink-is-not-empty", + "author": "account-0", + "permlink": "main-permlink-account-0", + "title": "tittle-main-permlink-account-0", + "body": "body-main-permlink-account-0", + "json_metadata": "{}", + }, + }, + { + "type": "comment_options_operation", + "value": { + "author": "account-0", + "permlink": "main-permlink-account-0", + "percent_hbd": 0, + # "max_accepted_payout": { + # "amount": "100000000", + # "nai": "@@000000013", + # "precision": 3, + # }, # todo: missing fields + # "allow_votes": "true", # todo: missing fields + # "allow_curation_rewards": "true", # todo: missing fields + # "extensions": [], # todo: missing fields + }, + }, + ], + } + ) + result = validate_transaction(trx.encode()) + assert result.status == result.status.ok + +def test_bad_array_deserialization(): + + trx = json.dumps( + { + "ref_block_num": 4, + "ref_block_prefix": 1920864452, + "expiration": "2025-08-04T23:16:12", + "operations": [ + { + "type": "update_proposal_votes_operation", + "value": + { + "voter": "initminer", + "proposal_ids": [0], + "approve": True, + "extensions": [] + } + } + ], + "extensions": [], + "signatures": ["20e388a54d7de4c3990f57e8bbe1271c8d47678f571570f7123f32ea805f382a8b3ffd3ce112069ac9a3f07a54d3d9e25ef139e68f7596576e3c42ae33d46239c7"] + } + ) + + result = calculate_transaction_id(trx.encode()) + assert result.status == result.status.ok + assert result.result == b'2c5cdf6bb4bc5a62eb0e81fa52a8e5598a92c075' + + trx = json.dumps( + { + "ref_block_num": 9, + "ref_block_prefix": 1621488055, + "expiration": "2025-08-04T23:10:30", + "operations": [ + { + "type": "account_update_operation", + "value": { + "account": "bob", + "owner": { + "weight_threshold": 1, + "account_auths": [], + "key_auths": [ + ["STM61T5ngsX8DmRxQKBaDTJ1ov6gphGwX9ewCJN8ffKFFwjGd5pxF", 1] + ] + }, + "memo_key": "STM72Ujsx5KJz8S2PGcqavrNPcTzzVd2nXs2GMEuiYondN2MMj6vU", + "json_metadata": "{}" + } + } + ], + "extensions": [], + "signatures": ["2040599ca1e3c4f89463e95dce08cd0498a3125ec3b2a593de41b736689d50581c49ee84561ad99afda32607372feb7f52d76e871f1c03459e669aca817d75a6f1"] + } + ) + + result = calculate_transaction_id(trx.encode()) + assert result.status == result.status.ok + assert result.result == b'9a970e05ccf2ef77a7149f32ed46228b96908b98' + -- GitLab From a7a6f750040f427eb72ecdebf4a876daa590b11e Mon Sep 17 00:00:00 2001 From: Bartek Wrona Date: Mon, 4 Aug 2025 00:12:18 +0200 Subject: [PATCH 07/13] Relaxing the rules for referencing missing fields in deserialized objects (to match fc::from_variant behavior) --- core/val_protocol.hpp | 84 ++++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/core/val_protocol.hpp b/core/val_protocol.hpp index d27bb991a..4f745aaac 100644 --- a/core/val_protocol.hpp +++ b/core/val_protocol.hpp @@ -79,7 +79,7 @@ template< typename ManagedObjectT, typename T > class val_protocol_visitor { public: val_protocol_visitor( ManagedObjectT jsval, T& val, bool is_protobuf ) - : jsval( jsval ), is_protobuf( is_protobuf ), val( val ) + : jsval( jsval ), is_protobuf( is_protobuf ), ignore_missing_fields( is_protobuf == false ), val( val ) {} template< typename Member, class Class, Member( Class::*member ) > @@ -102,6 +102,9 @@ public: void add( const char* name, hive::protocol::asset& v ) const { + if(can_skip_missing_field(name)) + return; + ManagedObjectT amount = jsval[name]; std::string amount_str = amount["amount"].template as(); @@ -119,24 +122,36 @@ public: void add( const char* name, hive::protocol::json_string& v ) const { + if(can_skip_missing_field(name)) + return; + std::string str = jsval[name].template as(); v = hive::protocol::json_string{ str }; } void add( const char* name, fc::ripemd160& v ) const { + if(can_skip_missing_field(name)) + return; + std::string str = jsval[name].template as(); v = fc::ripemd160{ str }; } void add( const char* name, fc::sha256& v ) const { + if(can_skip_missing_field(name)) + return; + std::string str = jsval[name].template as(); v = fc::sha256{ str }; } void add( const char* name, hive::protocol::public_key_type& v ) const { + if(can_skip_missing_field(name)) + return; + std::string str = jsval[name].template as(); v = hive::protocol::public_key_type{ str }; } @@ -144,6 +159,9 @@ public: template void add( const char* name, hive::protocol::fixed_string_impl& v ) const { + if(can_skip_missing_field(name)) + return; + std::string str = jsval[name].template as(); v = str; } @@ -151,6 +169,9 @@ public: template void add( const char* name, fc::safe& v ) const { + if(can_skip_missing_field(name)) + return; + SafeT tmp; this->add(name, tmp); v.value = tmp; @@ -158,6 +179,9 @@ public: void add( const char* name, std::vector& v ) const { + if(can_skip_missing_field(name)) + return; + std::string str; jsval[name].as( str ); v.resize(str.size() / 2); @@ -167,17 +191,26 @@ public: template void add( const char* name, fc::array& v ) const { + if(can_skip_missing_field(name)) + return; + std::string str = jsval[name].template as(); fc::from_hex(str, reinterpret_cast(&v.data[0]), NArr); } void add( const char* name, hive::protocol::legacy_hive_asset& v ) const { + if(can_skip_missing_field(name)) + return; + val_protocol_visitor< ManagedObjectT, hive::protocol::legacy_hive_asset >{ jsval[name], v, is_protobuf }.add( "amount", v.amount ); } void add( const char* name, fc::time_point_sec& v ) const { + if(can_skip_missing_field(name)) + return; + std::string time; jsval[name].as(time); if (time.empty()) @@ -189,6 +222,9 @@ public: template void add( const char* name, hive::protocol::tiny_asset<_SYMBOL>& v ) const { + if(can_skip_missing_field(name)) + return; + int64_t amount_value = jsval[name]["amount"].template as(); v.amount = amount_value; } @@ -251,9 +287,15 @@ public: M item; if constexpr( std::is_same< typename fc::reflector< M >::is_defined, fc::true_type >::value ) + { fc::reflector< M >::visit( val_protocol_visitor< ManagedObjectT, M >{ arr_val[i], item, is_protobuf } ); + } else - val_protocol_visitor< ManagedObjectT, M >{ arr_val, item, is_protobuf }.add( std::to_string( i ).c_str(), item ); + { + val_protocol_visitor< ManagedObjectT, M > visitor{ arr_val, item, is_protobuf }; + visitor.ignore_missing_fields = false; + visitor.add( std::to_string( i ).c_str(), item ); + } v.insert(item); } @@ -262,22 +304,7 @@ public: template void add_array( const char* name, ::flat_set_ex& v ) const { - ManagedObjectT arr_val = jsval[name]; - - auto arr_size = arr_val.array_length(); - v.reserve(arr_size); - - for (size_t i = 0; i < arr_size; ++i) - { - M item; - - if constexpr( std::is_same< typename fc::reflector< M >::is_defined, fc::true_type >::value ) - fc::reflector< M >::visit( val_protocol_visitor< ManagedObjectT, M >{ arr_val[i], item, is_protobuf } ); - else - val_protocol_visitor< ManagedObjectT, M >{ arr_val, item, is_protobuf }.add( std::to_string( i ).c_str(), item ); - - v.insert(item); - } + add_array(name, static_cast&>(v)); } template< typename M > @@ -294,9 +321,15 @@ public: TVal item; if constexpr( std::is_same< typename fc::reflector< TVal >::is_defined, fc::true_type >::value ) + { fc::reflector< TVal >::visit( val_protocol_visitor< ManagedObjectT, TVal >{ arr_val[i], item, is_protobuf } ); + } else - val_protocol_visitor< ManagedObjectT, TVal >{ arr_val, item, is_protobuf }.add( std::to_string( i ).c_str(), item ); + { + val_protocol_visitor< ManagedObjectT, TVal > visitor { arr_val, item, is_protobuf }; + visitor.ignore_missing_fields = false; + visitor.add( std::to_string( i ).c_str(), item ); + } v.emplace_back(item); } @@ -396,12 +429,23 @@ public: template< typename M > void add( const char* key, M& value ) const { + if(can_skip_missing_field(key)) + return; + add_member_impl(typename binary_view::node_type< M >::node(), key, value); } -private: + bool can_skip_missing_field(const char* name) const + { + return ignore_missing_fields && jsval.is_optional_field_present(name) == false; + } + ManagedObjectT jsval; bool is_protobuf; + /** true when missing field in source managedobject should be ignored. + It must match fc::from_variant object initialization, which allows to skip members and use their C++ defaults. + */ + bool ignore_missing_fields; T& val; }; -- GitLab From ef1342697816b0ca3d7d393464fe5dee79ec89d3 Mon Sep 17 00:00:00 2001 From: Bartek Wrona Date: Tue, 5 Aug 2025 00:50:37 +0200 Subject: [PATCH 08/13] Adjusted Python tests due to relaxed rules specific to serialized object members presence. --- .../tests/protocol/test_validate_operation.py | 3 ++- .../protocol/test_validate_transaction.py | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/python/tests/protocol/test_validate_operation.py b/python/tests/protocol/test_validate_operation.py index 8b2b3571d..0d7ab0e4f 100644 --- a/python/tests/protocol/test_validate_operation.py +++ b/python/tests/protocol/test_validate_operation.py @@ -25,5 +25,6 @@ def test_validate_operation(): result = validate_operation(vote_op_str.encode()) assert result.status == result.status.fail assert result.exception_message == ( - b'10 assert_exception: Assert Exception\n!PyErr_Occurred()\nPython function call failed: \'voter\'\n {"pyerr":"\'voter\'"}\n python_managed_object.hpp:63 call_python_function' + b'10 assert_exception: Assert Exception\nvalidity_check_result != account_name_validity::too_short\nAccount name \'\' is too short. ' + b'Use at least 3 characters.\n {"name":"","min":3}\n validation.hpp:22 validate_account_name' ) diff --git a/python/tests/protocol/test_validate_transaction.py b/python/tests/protocol/test_validate_transaction.py index 7b061abfa..dd6fa75eb 100644 --- a/python/tests/protocol/test_validate_transaction.py +++ b/python/tests/protocol/test_validate_transaction.py @@ -30,15 +30,22 @@ def test_validate_transaction(): tx_str = json.dumps(API_REF_TRANSACTION_NO_OPERATIONS) result = validate_transaction(tx_str.encode()) assert result.status == result.status.fail - assert result.exception_message == (b'10 assert_exception: Assert Exception\n!PyErr_Occurred()\nPython function ' - b'call failed: \'operations\'\n {"pyerr":"\'operations\'"}\n python_managed_object.hpp:63 call_python_function') + assert result.exception_message == (b'10 assert_exception: Assert Exception\noperations.size() > 0\nA transactio' + b'n must have at least one operation (unformatted args: ("trx",{"ref_block_num' + b'":19260,"ref_block_prefix":2140466769,"expiration":"2016-09-15T19:47:33","op' + b'erations":[],"extensions":[]}))\n {"trx":{"ref_block_num":19260,"ref_b' + b'lock_prefix":2140466769,"expiration":"2016-09-15T19:47:33","operations":[],"' + b'extensions":[]}}\n transaction.cpp:42 validate' + ) # Negative test tx_str = json.dumps(API_REF_TRANSACTION_EMPTY_OPERATIONS) result = validate_transaction(tx_str.encode()) assert result.status == result.status.fail - assert result.exception_message == ( - b'10 assert_exception: Assert Exception\n!PyErr_Occurred()\nPython function ' - b'call failed: \'extensions\'\n {"pyerr":"\'extensions\'"}\n python_ma' - b'naged_object.hpp:63 call_python_function' + assert result.exception_message == (b'10 assert_exception: Assert Exception\noperations.size() > 0\nA transactio' + b'n must have at least one operation (unformatted args: ("trx",{"ref_block_num' + b'":19260,"ref_block_prefix":2140466769,"expiration":"2016-09-15T19:47:33","op' + b'erations":[],"extensions":[]}))\n {"trx":{"ref_block_num":19260,"ref_b' + b'lock_prefix":2140466769,"expiration":"2016-09-15T19:47:33","operations":[],"' + b'extensions":[]}}\n transaction.cpp:42 validate' ) -- GitLab From 5c9597bde9f44413d9f3547e2ddd36d7ab74c4ce Mon Sep 17 00:00:00 2001 From: Bartek Wrona Date: Wed, 6 Aug 2025 01:11:54 +0200 Subject: [PATCH 09/13] TS transaction validation test adjusted to be able to caught WASM exceptions directly. --- ts/wasm/__tests__/detailed/protocol.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ts/wasm/__tests__/detailed/protocol.ts b/ts/wasm/__tests__/detailed/protocol.ts index 61e6ed817..cd5693e80 100644 --- a/ts/wasm/__tests__/detailed/protocol.ts +++ b/ts/wasm/__tests__/detailed/protocol.ts @@ -806,8 +806,16 @@ test.describe('WASM Protocol', () => { test('Should not crash the program - transaction validation - but fail', async ({ wasmTest }) => { await expect(wasmTest(({ protocol }) => { - const handle = protocol.cpp_create_transaction_handle({}, false); - protocol.cpp_tx_validate(handle); + try { + const handle = protocol.cpp_create_transaction_handle({}, false); + protocol.cpp_tx_validate(handle); + } + catch (error) { + console.error(error, (error as any).message); + + throw new Error((error as any).message); + + } })).rejects.toThrow(); }); -- GitLab From fe94035c3195568d1bf5fe71910f117229a26383 Mon Sep 17 00:00:00 2001 From: Bartek Wrona Date: Wed, 6 Aug 2025 02:20:08 +0200 Subject: [PATCH 10/13] Relaxed Python checks to accept floating numbers without fractional part as integers. --- python/python_managed_object.hpp | 69 ++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/python/python_managed_object.hpp b/python/python_managed_object.hpp index 8f087561c..76f30c568 100644 --- a/python/python_managed_object.hpp +++ b/python/python_managed_object.hpp @@ -323,73 +323,105 @@ public: void as(int64_t& out)const { - FC_ASSERT(PyLong_Check(pyobj), "Cannot convert object to int64_t: ${pyobj}", (pyobj)); + FC_ASSERT(accept_integer_number(), "Cannot convert object to int64_t: ${pyobj}", (pyobj)); + + auto intermediate = call_python_function([&] { + return PyNumber_Long(pyobj); + }); out = call_python_function([&] { - return PyLong_AsLongLong(pyobj); + return PyLong_AsLongLong(intermediate); }); } void as(int32_t& out)const { - FC_ASSERT(PyLong_Check(pyobj), "Cannot convert object to int32_t: ${pyobj}", (pyobj)); + FC_ASSERT(accept_integer_number(), "Cannot convert object to int32_t: ${pyobj}", (pyobj)); + + auto intermediate = call_python_function([&] { + return PyNumber_Long(pyobj); + }); out = static_cast(call_python_function([&] { - return PyLong_AsLong(pyobj); + return PyLong_AsLong(intermediate); })); } void as(int16_t& out)const { - FC_ASSERT(PyLong_Check(pyobj), "Cannot convert object to int16_t: ${pyobj}", (pyobj)); + FC_ASSERT(accept_integer_number(), "Cannot convert object to int16_t: ${pyobj}", (pyobj)); + + auto intermediate = call_python_function([&] { + return PyNumber_Long(pyobj); + }); out = static_cast(call_python_function([&] { - return PyLong_AsLong(pyobj); + return PyLong_AsLong(intermediate); })); } void as(int8_t& out)const { - FC_ASSERT(PyLong_Check(pyobj), "Cannot convert object to int8_t: ${pyobj}", (pyobj)); + FC_ASSERT(accept_integer_number(), "Cannot convert object to int8_t: ${pyobj}", (pyobj)); + + auto intermediate = call_python_function([&] { + return PyNumber_Long(pyobj); + }); out = static_cast(call_python_function([&] { - return PyLong_AsLong(pyobj); + return PyLong_AsLong(intermediate); })); } void as(uint64_t& out)const { - FC_ASSERT(PyLong_Check(pyobj), "Cannot convert object to uint64_t: ${pyobj}", (pyobj)); + FC_ASSERT(accept_integer_number(), "Cannot convert object to uint64_t: ${pyobj}", (pyobj)); + + auto intermediate = call_python_function([&] { + return PyNumber_Long(pyobj); + }); out = call_python_function([&] { - return PyLong_AsUnsignedLongLong(pyobj); + return PyLong_AsUnsignedLongLong(intermediate); }); } void as(uint32_t& out)const { - FC_ASSERT(PyLong_Check(pyobj), "Cannot convert object to uint32_t: ${pyobj}", (pyobj)); + FC_ASSERT(accept_integer_number(), "Cannot convert object to uint32_t: ${pyobj}", (pyobj)); + + auto intermediate = call_python_function([&] { + return PyNumber_Long(pyobj); + }); out = static_cast(call_python_function([&] { - return PyLong_AsUnsignedLong(pyobj); + return PyLong_AsUnsignedLong(intermediate); })); } void as(uint16_t& out)const { - FC_ASSERT(PyLong_Check(pyobj), "Cannot convert object to uint16_t: ${pyobj}", (pyobj)); + FC_ASSERT(accept_integer_number(), "Cannot convert object to uint16_t: ${pyobj}", (pyobj)); + + auto intermediate = call_python_function([&] { + return PyNumber_Long(pyobj); + }); out = static_cast(call_python_function([&] { - return PyLong_AsUnsignedLong(pyobj); + return PyLong_AsUnsignedLong(intermediate); })); } void as(uint8_t& out)const { - FC_ASSERT(PyLong_Check(pyobj), "Cannot convert object to uint8_t: ${pyobj}", (pyobj)); + FC_ASSERT(accept_integer_number(), "Cannot convert object to uint8_t: ${pyobj}", (pyobj)); + + auto intermediate = call_python_function([&] { + return PyNumber_Long(pyobj); + }); out = static_cast(call_python_function([&] { - return PyLong_AsUnsignedLong(pyobj); + return PyLong_AsUnsignedLong(intermediate); })); } @@ -484,6 +516,11 @@ private: return field_desc; } + bool accept_integer_number() const + { + return PyLong_Check(pyobj) || (PyFloat_Check(pyobj) && std::floor(PyFloat_AsDouble(pyobj)) == PyFloat_AsDouble(pyobj)); + } + private: py_object_ptr pyobj; }; -- GitLab From d2bccc6aabe15dbc95b013ad1139752a50441a83 Mon Sep 17 00:00:00 2001 From: Bartek Wrona Date: Thu, 7 Aug 2025 00:21:47 +0200 Subject: [PATCH 11/13] Fixed associative container conversion from managed objects leading to bad behavior vs direct transaction/operation deserialization from json. Also implemented ability to enable debug logging inside val_protocol converter. --- core/val_protocol.hpp | 78 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/core/val_protocol.hpp b/core/val_protocol.hpp index 4f745aaac..4cbd3b451 100644 --- a/core/val_protocol.hpp +++ b/core/val_protocol.hpp @@ -5,6 +5,16 @@ #include "binary_view/node_types.hpp" #include "binary_view/traits.hpp" +//#define VAL_PROTOCOL_LOGGING + +#ifdef VAL_PROTOCOL_LOGGING + #define VAL_PROTOCOL_ILOG( FORMAT, ...) ilog( FORMAT, __VA_ARGS__ ) + #define VAL_PROTOCOL_WLOG( FORMAT, ...) wlog( FORMAT, __VA_ARGS__ ) +#else + #define VAL_PROTOCOL_ILOG( FORMAT, ...) /* nothing */ + #define VAL_PROTOCOL_WLOG( FORMAT, ...) /* nothing */ +#endif /// VAL_PROTOCOL_LOGGING + namespace cpp { namespace { @@ -85,6 +95,7 @@ public: template< typename Member, class Class, Member( Class::*member ) > void operator()( const char* name ) const { + VAL_PROTOCOL_ILOG("Attemptng to visit member ${name} from object ${jsval}", (name)("jsval", jsval.operator std::string())); this->add( name, val.*member ); } @@ -94,10 +105,15 @@ public: { if(jsval.is_optional_field_present(name)) { + VAL_PROTOCOL_ILOG("Processing optional member ${name} from object ${jsval}", (name)("jsval", jsval.operator std::string())); M tmp; this->add( name, tmp ); v = tmp; } + else + { + VAL_PROTOCOL_ILOG("Skipping optional member ${name} from object ${jsval}", (name)("jsval", jsval.operator std::string())); + } } void add( const char* name, hive::protocol::asset& v ) const @@ -282,6 +298,8 @@ public: auto arr_size = arr_val.array_length(); v.reserve(arr_size); + VAL_PROTOCOL_ILOG("Processing ${name} member: Attempting to load ${arr_size} items from ${arr_val} into flat_set container...", ("arr_val", arr_val.operator std::string())(arr_size)(name)); + for (size_t i = 0; i < arr_size; ++i) { M item; @@ -297,8 +315,12 @@ public: visitor.add( std::to_string( i ).c_str(), item ); } + VAL_PROTOCOL_ILOG("Attempting to insert into set another item # ${i}: ${item}", (i)(item)); + v.insert(item); } + + FC_ASSERT(v.size() == arr_size); } template @@ -313,6 +335,9 @@ public: ManagedObjectT arr_val = jsval[name]; auto arr_size = arr_val.array_length(); + + VAL_PROTOCOL_ILOG("Processing ${name} member: Attempting to load ${arr_size} items from ${arr_val} into generic-array container...", ("arr_val", arr_val.operator std::string())(arr_size)(name)); + v.reserve(arr_size); for (size_t i = 0; i < arr_size; ++i) @@ -331,8 +356,11 @@ public: visitor.add( std::to_string( i ).c_str(), item ); } + VAL_PROTOCOL_ILOG("Attempting to push into array another item # ${i}: ${item}", (i)(item)); v.emplace_back(item); } + + FC_ASSERT(v.size() == arr_size); } template @@ -345,20 +373,38 @@ public: for (const auto& key : arr_val.get_map_keys()) { hive::protocol::weight_type weight = arr_val[key].template as(); - v[M{key}] = weight; + + VAL_PROTOCOL_ILOG("Attempting to push into map item ${key}/${weight}", (key)(weight)); + + /// WARNING: According to compatibility to hive::protocol maps serialization (fc from_variant/unpack), duplicates SHALL BE IGNORED, and first key/value association preserved. + auto insert_info = v.emplace(M{ key }, weight); + if (insert_info.second == false) + { + VAL_PROTOCOL_WLOG("Ignored duplicate for item: ${key}", (key)); + } } } else { auto arr_size = arr_val.array_length(); + VAL_PROTOCOL_ILOG("Processing ${name} member: Attempting to load ${arr_size} items from ${arr_val} into flat_map container...", ("arr_val", arr_val.operator std::string())(arr_size)(name)); + for (size_t i = 0; i < arr_size; ++i) { auto el = arr_val[i]; std::string key = el[0].template as(); hive::protocol::weight_type weight = el[1].template as(); - v[M{key}] = weight; + VAL_PROTOCOL_ILOG("Attempting to push into map item # ${i}: ${key}/${weight}", (i)(key)(weight)); + + /// WARNING: According to compatibility to hive::protocol maps serialization (fc from_variant/unpack), duplicates SHALL BE IGNORED, and first key/value association preserved. + auto insert_info = v.emplace(M{key}, weight); + + if (insert_info.second == false) + { + VAL_PROTOCOL_WLOG("Ignored duplicate for item # ${i}: ${key}", (i)(key)); + } } } } @@ -385,13 +431,19 @@ public: value.resize(hex_value.size() / 2); fc::from_hex(hex_value, value.data(), hex_value.size() / 2); - v[key] = value; + auto insert_info = v.emplace(std::move(key), std::move(value)); + if (insert_info.second == false) + { + VAL_PROTOCOL_WLOG("Ignored duplicate for item ${key}", ("key", insert_info.first->first)); + } } } else { auto arr_size = arr_val.array_length(); + VAL_PROTOCOL_ILOG("Processing ${name} member: Attempting to load ${arr_size} items from ${arr_val} into flat_map container...", ("arr_val", arr_val.operator std::string())(arr_size)(name)); + for (size_t i = 0; i < arr_size; ++i) { ManagedObjectT el = arr_val[i]; @@ -403,7 +455,12 @@ public: value.resize(hex_value.size() / 2); fc::from_hex(hex_value, value.data(), hex_value.size() / 2); - v[key] = value; + /// WARNING: According to compatibility to hive::protocol maps serialization (fc from_variant/unpack), duplicates SHALL BE IGNORED, and first value preserved. + auto insert_info = v.emplace(std::move(key), std::move(value)); + if (insert_info.second == false) + { + VAL_PROTOCOL_WLOG("Ignored duplicate for item # ${i}: ${key}", (i)("key", insert_info.first->first)); + } } } } @@ -437,7 +494,13 @@ public: bool can_skip_missing_field(const char* name) const { - return ignore_missing_fields && jsval.is_optional_field_present(name) == false; + if(ignore_missing_fields && jsval.is_optional_field_present(name) == false) + { + VAL_PROTOCOL_WLOG("Skipping missing member: ${name} of object: ${o}", (name)("o", this->jsval.operator std::string())); + return true; + } + + return false; } ManagedObjectT jsval; @@ -456,7 +519,12 @@ typename val_to_static_variant::result_type val_to_static_varian static_assert( !binary_view::is_hive_array< T >::value, "We currently do not support arrays in static_variants when converting from ManagedObjectT" ); static_assert( !std::is_scalar< T >::value, "We only support objects in static_variants when converting from ManagedObjectT" ); + VAL_PROTOCOL_ILOG("Processing SV item: Attempting to load object from ${jsval}", ("jsval", jsval.operator std::string())); + fc::reflector< T >::visit( val_protocol_visitor< ManagedObjectT, T >{ jsval, v, is_protobuf } ); } } // namespac cpp + +#undef VAL_PROTOCOL_ILOG +#undef VAL_PROTOCOL_WLOG -- GitLab From 6ea086659a370272d80ff93e066678512d7e79fd Mon Sep 17 00:00:00 2001 From: Bartek Wrona Date: Thu, 7 Aug 2025 00:47:58 +0200 Subject: [PATCH 12/13] Python tests adjusted to changed C++ assertion location due to idiotic error matching scheme. --- python/tests/proto-protocol/test_calculate_proto_sig_digest.py | 2 +- .../tests/proto-protocol/test_calculate_proto_transaction_id.py | 2 +- python/tests/proto-protocol/test_serialize_proto_transaction.py | 2 +- python/tests/proto-protocol/test_validate_proto_operation.py | 2 +- python/tests/proto-protocol/test_validate_proto_transaction.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/tests/proto-protocol/test_calculate_proto_sig_digest.py b/python/tests/proto-protocol/test_calculate_proto_sig_digest.py index a8f5418e6..9a2bc8064 100644 --- a/python/tests/proto-protocol/test_calculate_proto_sig_digest.py +++ b/python/tests/proto-protocol/test_calculate_proto_sig_digest.py @@ -16,7 +16,7 @@ def test_calculate_proto_sig_digest(): result = calculate_proto_sig_digest(tx_str.encode(), b'beeab0de00000000000000000000000000000000000000000000000000000000') assert result.status == result.status.fail assert result.exception_message == ( - b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:58 from_jsval') + b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:68 from_jsval') def test_calculate_proto_serialization_sensitive_sig_digest(): tx_str = json.dumps(PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION) diff --git a/python/tests/proto-protocol/test_calculate_proto_transaction_id.py b/python/tests/proto-protocol/test_calculate_proto_transaction_id.py index 6f4da0e9c..db87417c2 100644 --- a/python/tests/proto-protocol/test_calculate_proto_transaction_id.py +++ b/python/tests/proto-protocol/test_calculate_proto_transaction_id.py @@ -16,7 +16,7 @@ def test_calculate_proto_transaction_id(): result = calculate_proto_transaction_id(tx_str.encode()) assert result.status == result.status.fail assert result.exception_message == ( - b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:58 from_jsval') + b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:68 from_jsval') def test_calculate_proto_serialization_sensitive_transaction_id(): tx_str = json.dumps(PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION) diff --git a/python/tests/proto-protocol/test_serialize_proto_transaction.py b/python/tests/proto-protocol/test_serialize_proto_transaction.py index 620deddbb..2eb304e73 100644 --- a/python/tests/proto-protocol/test_serialize_proto_transaction.py +++ b/python/tests/proto-protocol/test_serialize_proto_transaction.py @@ -38,4 +38,4 @@ def test_serialize_proto_transaction(): result = serialize_proto_transaction(tx_str.encode()) assert result.status == result.status.fail assert result.exception_message == ( - b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:58 from_jsval') + b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:68 from_jsval') diff --git a/python/tests/proto-protocol/test_validate_proto_operation.py b/python/tests/proto-protocol/test_validate_proto_operation.py index 2fbfe8507..29bf52da8 100644 --- a/python/tests/proto-protocol/test_validate_proto_operation.py +++ b/python/tests/proto-protocol/test_validate_proto_operation.py @@ -19,7 +19,7 @@ def test_validate_proto_operation(): result = validate_proto_operation(vote_op_str.encode()) assert result.status == result.status.fail assert result.exception_message == ( - b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:58 from_jsval') + b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:68 from_jsval') # Negative test vote_op_str = json.dumps(PROTO_REF_VOTE_OP_EMPTY) diff --git a/python/tests/proto-protocol/test_validate_proto_transaction.py b/python/tests/proto-protocol/test_validate_proto_transaction.py index f8fb74823..5190e00bd 100644 --- a/python/tests/proto-protocol/test_validate_proto_transaction.py +++ b/python/tests/proto-protocol/test_validate_proto_transaction.py @@ -24,7 +24,7 @@ def test_validate_proto_transaction(): result = validate_proto_transaction(tx_str.encode()) assert result.status == result.status.fail assert result.exception_message == ( - b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:58 from_jsval') + b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:68 from_jsval') # Negative test tx_str = json.dumps(PROTO_REF_TRANSACTION_NO_OPERATIONS) -- GitLab From 1ee7306d51475651d96de864402cc90fbd1772d8 Mon Sep 17 00:00:00 2001 From: Bartek Wrona Date: Thu, 7 Aug 2025 02:19:46 +0200 Subject: [PATCH 13/13] CRLF->LF --- .../test_calculate_proto_sig_digest.py | 64 +++++++-------- .../test_calculate_proto_transaction_id.py | 64 +++++++-------- .../test_serialize_proto_transaction.py | 82 +++++++++---------- 3 files changed, 105 insertions(+), 105 deletions(-) diff --git a/python/tests/proto-protocol/test_calculate_proto_sig_digest.py b/python/tests/proto-protocol/test_calculate_proto_sig_digest.py index 9a2bc8064..9021a49ac 100644 --- a/python/tests/proto-protocol/test_calculate_proto_sig_digest.py +++ b/python/tests/proto-protocol/test_calculate_proto_sig_digest.py @@ -1,32 +1,32 @@ -import json - -from tests.utils.refs import PROTO_REF_TRANSACTION, PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION, API_REF_TRANSACTION - -from wax import calculate_proto_sig_digest, calculate_proto_legacy_sig_digest - -def test_calculate_proto_sig_digest(): - tx_str = json.dumps(PROTO_REF_TRANSACTION) - result = calculate_proto_sig_digest(tx_str.encode(), b'beeab0de00000000000000000000000000000000000000000000000000000000') - assert result.status == result.status.ok - assert result.exception_message == b'' - assert result.result == b'b31ff450905ad705ed0d7fd5e270c3685442203e15e1b1e7d5e94b35dcdc1693' - - # Negative test - tx_str = json.dumps(API_REF_TRANSACTION) - result = calculate_proto_sig_digest(tx_str.encode(), b'beeab0de00000000000000000000000000000000000000000000000000000000') - assert result.status == result.status.fail - assert result.exception_message == ( - b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:68 from_jsval') - -def test_calculate_proto_serialization_sensitive_sig_digest(): - tx_str = json.dumps(PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION) - result = calculate_proto_sig_digest(tx_str.encode(), b'beeab0de00000000000000000000000000000000000000000000000000000000') - assert result.status == result.status.ok - assert result.exception_message == b'' - assert result.result == b'8758db23c6aea40564697620ff61625b45c3b538cda21ded9fd6ec229caa1ee9' - - tx_str = json.dumps(PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION) - result = calculate_proto_legacy_sig_digest(tx_str.encode(), b'beeab0de00000000000000000000000000000000000000000000000000000000') - assert result.status == result.status.ok - assert result.exception_message == b'' - assert result.result == b'7fbd09ff2c3a90acfc59adce5abffdaa3fc95e33160c5ac237f0f4366f90e2fe' +import json + +from tests.utils.refs import PROTO_REF_TRANSACTION, PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION, API_REF_TRANSACTION + +from wax import calculate_proto_sig_digest, calculate_proto_legacy_sig_digest + +def test_calculate_proto_sig_digest(): + tx_str = json.dumps(PROTO_REF_TRANSACTION) + result = calculate_proto_sig_digest(tx_str.encode(), b'beeab0de00000000000000000000000000000000000000000000000000000000') + assert result.status == result.status.ok + assert result.exception_message == b'' + assert result.result == b'b31ff450905ad705ed0d7fd5e270c3685442203e15e1b1e7d5e94b35dcdc1693' + + # Negative test + tx_str = json.dumps(API_REF_TRANSACTION) + result = calculate_proto_sig_digest(tx_str.encode(), b'beeab0de00000000000000000000000000000000000000000000000000000000') + assert result.status == result.status.fail + assert result.exception_message == ( + b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:68 from_jsval') + +def test_calculate_proto_serialization_sensitive_sig_digest(): + tx_str = json.dumps(PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION) + result = calculate_proto_sig_digest(tx_str.encode(), b'beeab0de00000000000000000000000000000000000000000000000000000000') + assert result.status == result.status.ok + assert result.exception_message == b'' + assert result.result == b'8758db23c6aea40564697620ff61625b45c3b538cda21ded9fd6ec229caa1ee9' + + tx_str = json.dumps(PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION) + result = calculate_proto_legacy_sig_digest(tx_str.encode(), b'beeab0de00000000000000000000000000000000000000000000000000000000') + assert result.status == result.status.ok + assert result.exception_message == b'' + assert result.result == b'7fbd09ff2c3a90acfc59adce5abffdaa3fc95e33160c5ac237f0f4366f90e2fe' diff --git a/python/tests/proto-protocol/test_calculate_proto_transaction_id.py b/python/tests/proto-protocol/test_calculate_proto_transaction_id.py index db87417c2..869fe75bc 100644 --- a/python/tests/proto-protocol/test_calculate_proto_transaction_id.py +++ b/python/tests/proto-protocol/test_calculate_proto_transaction_id.py @@ -1,32 +1,32 @@ -import json - -from tests.utils.refs import PROTO_REF_TRANSACTION, PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION, API_REF_TRANSACTION - -from wax import calculate_proto_transaction_id, calculate_proto_legacy_transaction_id - -def test_calculate_proto_transaction_id(): - tx_str = json.dumps(PROTO_REF_TRANSACTION) - result = calculate_proto_transaction_id(tx_str.encode()) - assert result.status == result.status.ok - assert result.exception_message == b'' - assert result.result == b'4491c7a6362e71cca31e256f69af503e0abc5d3d' - - # Negative test - tx_str = json.dumps(API_REF_TRANSACTION) - result = calculate_proto_transaction_id(tx_str.encode()) - assert result.status == result.status.fail - assert result.exception_message == ( - b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:68 from_jsval') - -def test_calculate_proto_serialization_sensitive_transaction_id(): - tx_str = json.dumps(PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION) - result = calculate_proto_transaction_id(tx_str.encode()) - assert result.status == result.status.ok - assert result.exception_message == b'' - assert result.result == b'3725c81634f152011e2043eb7119911b953d4267' - - tx_str = json.dumps(PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION) - result = calculate_proto_legacy_transaction_id(tx_str.encode()) - assert result.status == result.status.ok - assert result.exception_message == b'' - assert result.result == b'7f34699e9eea49d1bcc10c88f96e38897839ece3' +import json + +from tests.utils.refs import PROTO_REF_TRANSACTION, PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION, API_REF_TRANSACTION + +from wax import calculate_proto_transaction_id, calculate_proto_legacy_transaction_id + +def test_calculate_proto_transaction_id(): + tx_str = json.dumps(PROTO_REF_TRANSACTION) + result = calculate_proto_transaction_id(tx_str.encode()) + assert result.status == result.status.ok + assert result.exception_message == b'' + assert result.result == b'4491c7a6362e71cca31e256f69af503e0abc5d3d' + + # Negative test + tx_str = json.dumps(API_REF_TRANSACTION) + result = calculate_proto_transaction_id(tx_str.encode()) + assert result.status == result.status.fail + assert result.exception_message == ( + b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:68 from_jsval') + +def test_calculate_proto_serialization_sensitive_transaction_id(): + tx_str = json.dumps(PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION) + result = calculate_proto_transaction_id(tx_str.encode()) + assert result.status == result.status.ok + assert result.exception_message == b'' + assert result.result == b'3725c81634f152011e2043eb7119911b953d4267' + + tx_str = json.dumps(PROTO_REF_SERIALIZATION_SENSITIVE_TRANSACTION) + result = calculate_proto_legacy_transaction_id(tx_str.encode()) + assert result.status == result.status.ok + assert result.exception_message == b'' + assert result.result == b'7f34699e9eea49d1bcc10c88f96e38897839ece3' diff --git a/python/tests/proto-protocol/test_serialize_proto_transaction.py b/python/tests/proto-protocol/test_serialize_proto_transaction.py index 2eb304e73..a08d94139 100644 --- a/python/tests/proto-protocol/test_serialize_proto_transaction.py +++ b/python/tests/proto-protocol/test_serialize_proto_transaction.py @@ -1,41 +1,41 @@ -import json - -from google.protobuf.json_format import ParseDict - -from tests.utils.refs import PROTO_REF_TRANSACTION, API_REF_TRANSACTION - -from wax import serialize_proto_transaction, deserialize_proto_transaction - -from wax.proto.transaction import transaction - - -def test_serialize_proto_transaction(): - tx_str = json.dumps(PROTO_REF_TRANSACTION) - result = serialize_proto_transaction(tx_str.encode()) - assert result.status == result.status.ok - assert result.exception_message == b'' - assert result.result == ( - b'3c4b51ee947fd5fada5701000a74616f746568313232310a6f7a63686172746172747f757364' - b'737465656d2d6274632d6461696c792d706f6c6f6e6965782d626974747265782d746563686e' - b'6963616c2d616e616c797369732d6d61726b65742d7265706f72742d7570646174652d34362d' - b'676c6173732d68616c662d66756c6c2d6275742d7468652d626f74746c652d732d6c6566742d' - b'656d7074792d736570741027010001202bd7ff67ba97db6b5fecb389ca279e0c98db9a49fd9f' - b'49acea63ea523ed35ac602933e9bbb0916b6ee137b5550cbe1ae4594c52a27d1505b1adb53f8' - b'b37d3fb3' - ) - - result = deserialize_proto_transaction(result.result) - assert result.status == result.status.ok - assert result.exception_message == b'' - assert result.result.decode() == tx_str - - tx_ref = ParseDict(PROTO_REF_TRANSACTION, transaction()) - tx = ParseDict(json.loads(result.result.decode()), transaction()) - assert(tx_ref == tx) - - # Negative test - tx_str = json.dumps(API_REF_TRANSACTION) - result = serialize_proto_transaction(tx_str.encode()) - assert result.status == result.status.fail - assert result.exception_message == ( - b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:68 from_jsval') +import json + +from google.protobuf.json_format import ParseDict + +from tests.utils.refs import PROTO_REF_TRANSACTION, API_REF_TRANSACTION + +from wax import serialize_proto_transaction, deserialize_proto_transaction + +from wax.proto.transaction import transaction + + +def test_serialize_proto_transaction(): + tx_str = json.dumps(PROTO_REF_TRANSACTION) + result = serialize_proto_transaction(tx_str.encode()) + assert result.status == result.status.ok + assert result.exception_message == b'' + assert result.result == ( + b'3c4b51ee947fd5fada5701000a74616f746568313232310a6f7a63686172746172747f757364' + b'737465656d2d6274632d6461696c792d706f6c6f6e6965782d626974747265782d746563686e' + b'6963616c2d616e616c797369732d6d61726b65742d7265706f72742d7570646174652d34362d' + b'676c6173732d68616c662d66756c6c2d6275742d7468652d626f74746c652d732d6c6566742d' + b'656d7074792d736570741027010001202bd7ff67ba97db6b5fecb389ca279e0c98db9a49fd9f' + b'49acea63ea523ed35ac602933e9bbb0916b6ee137b5550cbe1ae4594c52a27d1505b1adb53f8' + b'b37d3fb3' + ) + + result = deserialize_proto_transaction(result.result) + assert result.status == result.status.ok + assert result.exception_message == b'' + assert result.result.decode() == tx_str + + tx_ref = ParseDict(PROTO_REF_TRANSACTION, transaction()) + tx = ParseDict(json.loads(result.result.decode()), transaction()) + assert(tx_ref == tx) + + # Negative test + tx_str = json.dumps(API_REF_TRANSACTION) + result = serialize_proto_transaction(tx_str.encode()) + assert result.status == result.status.fail + assert result.exception_message == ( + b'10 assert_exception: Assert Exception\nit != to_tag.end()\nCould not find the supported property in static variant: type\n {"nextkey":"type"}\n val_protocol.hpp:68 from_jsval') -- GitLab