зеркало из https://github.com/microsoft/CCF.git
Restore public `ccf::Receipt` type (#3793)
This commit is contained in:
Родитель
de4a3c4586
Коммит
8e0b2c91cf
|
@ -1 +1 @@
|
|||
Piou piou
|
||||
Cui cui cui cui
|
|
@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
### Added
|
||||
|
||||
- Primary node now also reports time at which the ack from each backup node was last received (`GET /node/consensus` endpoint). This can be used by operators to detect one-way partitions between the primary and backup nodes (#3769).
|
||||
- Current receipt format is now exposed to C++ applications as `ccf::Receipt`, retrieved from `describe_receipt_v2`. Note that the previous JSON format is still available, but must be retrieved as a JSON object from `describe_receipt_v1`.
|
||||
|
||||
## [2.0.0-rc7]
|
||||
|
||||
|
|
|
@ -342,6 +342,8 @@ if(BUILD_TESTS)
|
|||
add_unit_test(
|
||||
historical_queries_test
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/historical_queries.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/receipt.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/node/receipt.cpp
|
||||
)
|
||||
target_link_libraries(
|
||||
historical_queries_test PRIVATE http_parser.host sss.host ccf_kv.host
|
||||
|
|
|
@ -172,6 +172,7 @@ set(CCF_ENDPOINTS_SOURCES
|
|||
${CCF_DIR}/src/indexing/strategies/seqnos_by_key_in_memory.cpp
|
||||
${CCF_DIR}/src/indexing/strategies/visit_each_entry_in_map.cpp
|
||||
${CCF_DIR}/src/node/historical_queries_adapter.cpp
|
||||
${CCF_DIR}/src/node/receipt.cpp
|
||||
)
|
||||
|
||||
find_library(CRYPTO_LIBRARY crypto)
|
||||
|
|
|
@ -114,7 +114,7 @@ Historical Queries
|
|||
:project: CCF
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: ccf::TxReceipt
|
||||
.. doxygenstruct:: ccf::Receipt
|
||||
:project: CCF
|
||||
:members:
|
||||
|
||||
|
|
|
@ -158,7 +158,7 @@
|
|||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"receipt": {
|
||||
"$ref": "#/components/schemas/Receipt"
|
||||
"$ref": "#/components/schemas/json"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -196,85 +196,6 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"NodeId": {
|
||||
"format": "hex",
|
||||
"pattern": "^[a-f0-9]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"Pem": {
|
||||
"type": "string"
|
||||
},
|
||||
"Pem_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Pem"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"Receipt": {
|
||||
"properties": {
|
||||
"cert": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"leaf": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"leaf_components": {
|
||||
"$ref": "#/components/schemas/Receipt__LeafComponents"
|
||||
},
|
||||
"node_id": {
|
||||
"$ref": "#/components/schemas/NodeId"
|
||||
},
|
||||
"proof": {
|
||||
"$ref": "#/components/schemas/Receipt__Element_array"
|
||||
},
|
||||
"root": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"service_endorsements": {
|
||||
"$ref": "#/components/schemas/Pem_array"
|
||||
},
|
||||
"signature": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"signature",
|
||||
"proof",
|
||||
"node_id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Receipt__Element": {
|
||||
"properties": {
|
||||
"left": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"right": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Receipt__Element_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Receipt__Element"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"Receipt__LeafComponents": {
|
||||
"properties": {
|
||||
"claims_digest": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"commit_evidence": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"write_set_digest": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"TransactionId": {
|
||||
"pattern": "^[0-9]+\\.[0-9]+$",
|
||||
"type": "string"
|
||||
|
@ -322,7 +243,7 @@
|
|||
"info": {
|
||||
"description": "This CCF sample app implements a simple logging application, securely recording messages at client-specified IDs. It demonstrates most of the features available to CCF apps.",
|
||||
"title": "CCF Sample Logging App",
|
||||
"version": "1.9.2"
|
||||
"version": "1.9.3"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
|
@ -1067,7 +988,7 @@
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Receipt"
|
||||
"$ref": "#/components/schemas/json"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -244,20 +244,9 @@
|
|||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NodeId": {
|
||||
"format": "hex",
|
||||
"pattern": "^[a-f0-9]{64}$",
|
||||
"type": "string"
|
||||
},
|
||||
"Pem": {
|
||||
"type": "string"
|
||||
},
|
||||
"Pem_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Pem"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"Proposal": {
|
||||
"properties": {
|
||||
"actions": {
|
||||
|
@ -340,71 +329,6 @@
|
|||
],
|
||||
"type": "string"
|
||||
},
|
||||
"Receipt": {
|
||||
"properties": {
|
||||
"cert": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"leaf": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"leaf_components": {
|
||||
"$ref": "#/components/schemas/Receipt__LeafComponents"
|
||||
},
|
||||
"node_id": {
|
||||
"$ref": "#/components/schemas/NodeId"
|
||||
},
|
||||
"proof": {
|
||||
"$ref": "#/components/schemas/Receipt__Element_array"
|
||||
},
|
||||
"root": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"service_endorsements": {
|
||||
"$ref": "#/components/schemas/Pem_array"
|
||||
},
|
||||
"signature": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"signature",
|
||||
"proof",
|
||||
"node_id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Receipt__Element": {
|
||||
"properties": {
|
||||
"left": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"right": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Receipt__Element_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Receipt__Element"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"Receipt__LeafComponents": {
|
||||
"properties": {
|
||||
"claims_digest": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"commit_evidence": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"write_set_digest": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"StateDigest": {
|
||||
"properties": {
|
||||
"state_digest": {
|
||||
|
@ -481,7 +405,7 @@
|
|||
"info": {
|
||||
"description": "This API is used to submit and query proposals which affect CCF's public governance tables.",
|
||||
"title": "CCF Governance API",
|
||||
"version": "2.7.1"
|
||||
"version": "2.7.2"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
|
@ -873,7 +797,7 @@
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Receipt"
|
||||
"$ref": "#/components/schemas/json"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -519,12 +519,6 @@
|
|||
"Pem": {
|
||||
"type": "string"
|
||||
},
|
||||
"Pem_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Pem"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"Quote": {
|
||||
"properties": {
|
||||
"endorsements": {
|
||||
|
@ -563,71 +557,6 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"Receipt": {
|
||||
"properties": {
|
||||
"cert": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"leaf": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"leaf_components": {
|
||||
"$ref": "#/components/schemas/Receipt__LeafComponents"
|
||||
},
|
||||
"node_id": {
|
||||
"$ref": "#/components/schemas/NodeId"
|
||||
},
|
||||
"proof": {
|
||||
"$ref": "#/components/schemas/Receipt__Element_array"
|
||||
},
|
||||
"root": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"service_endorsements": {
|
||||
"$ref": "#/components/schemas/Pem_array"
|
||||
},
|
||||
"signature": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"signature",
|
||||
"proof",
|
||||
"node_id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Receipt__Element": {
|
||||
"properties": {
|
||||
"left": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"right": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Receipt__Element_array": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Receipt__Element"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"Receipt__LeafComponents": {
|
||||
"properties": {
|
||||
"claims_digest": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"commit_evidence": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
},
|
||||
"write_set_digest": {
|
||||
"$ref": "#/components/schemas/string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ReconfigurationType": {
|
||||
"enum": [
|
||||
"OneTransaction",
|
||||
|
@ -814,7 +743,7 @@
|
|||
"info": {
|
||||
"description": "This API provides public, uncredentialed access to service and node state.",
|
||||
"title": "CCF Public Node API",
|
||||
"version": "2.17.0"
|
||||
"version": "2.17.1"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
|
@ -1164,7 +1093,7 @@
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Receipt"
|
||||
"$ref": "#/components/schemas/json"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -45,4 +45,24 @@ namespace ccf
|
|||
return (is_set == other.is_set) && (digest == other.digest);
|
||||
}
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const ClaimsDigest& hash)
|
||||
{
|
||||
j = hash.value();
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, ClaimsDigest& hash)
|
||||
{
|
||||
hash.set(j.get<ClaimsDigest::Digest>());
|
||||
}
|
||||
|
||||
inline std::string schema_name(const ClaimsDigest*)
|
||||
{
|
||||
return ds::json::schema_name<ClaimsDigest::Digest>();
|
||||
}
|
||||
|
||||
inline void fill_json_schema(nlohmann::json& schema, const ClaimsDigest*)
|
||||
{
|
||||
ds::json::fill_schema<ClaimsDigest::Digest>(schema);
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@ namespace crypto
|
|||
std::string hex_str() const;
|
||||
|
||||
static Sha256Hash from_hex_string(const std::string& str);
|
||||
static Sha256Hash from_span(const std::span<uint8_t, SIZE>& sp);
|
||||
static Sha256Hash from_span(const std::span<const uint8_t, SIZE>& sp);
|
||||
static Sha256Hash from_representation(const Representation& r);
|
||||
};
|
||||
|
||||
|
@ -48,6 +48,10 @@ namespace crypto
|
|||
|
||||
void from_json(const nlohmann::json& j, Sha256Hash& hash);
|
||||
|
||||
std::string schema_name(const Sha256Hash*);
|
||||
|
||||
void fill_json_schema(nlohmann::json& schema, const Sha256Hash*);
|
||||
|
||||
bool operator==(const Sha256Hash& lhs, const Sha256Hash& rhs);
|
||||
|
||||
bool operator!=(const Sha256Hash& lhs, const Sha256Hash& rhs);
|
||||
|
|
|
@ -11,19 +11,6 @@
|
|||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
namespace ccf
|
||||
{
|
||||
// This is an opaque, incomplete type, but can be summarised to a
|
||||
// JSON-serialisable form by the functions below
|
||||
struct TxReceipt;
|
||||
using TxReceiptPtr = std::shared_ptr<TxReceipt>;
|
||||
|
||||
ccf::Receipt describe_receipt(
|
||||
const TxReceipt& receipt, bool include_root = false);
|
||||
ccf::Receipt describe_receipt(
|
||||
const TxReceiptPtr& receipt_ptr, bool include_root = false);
|
||||
}
|
||||
|
||||
namespace ccf::historical
|
||||
{
|
||||
struct State
|
||||
|
@ -31,13 +18,13 @@ namespace ccf::historical
|
|||
/// Read-only historical store at transaction_id
|
||||
kv::ReadOnlyStorePtr store = nullptr;
|
||||
/// Receipt for ledger entry at transaction_id
|
||||
TxReceiptPtr receipt = nullptr;
|
||||
TxReceiptImplPtr receipt = nullptr;
|
||||
/// View and Sequence Number for the State
|
||||
ccf::TxID transaction_id;
|
||||
|
||||
State(
|
||||
const kv::ReadOnlyStorePtr& store_,
|
||||
const TxReceiptPtr& receipt_,
|
||||
const TxReceiptImplPtr& receipt_,
|
||||
const ccf::TxID& transaction_id_) :
|
||||
store(store_),
|
||||
receipt(receipt_),
|
||||
|
|
|
@ -3,72 +3,196 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "ccf/claims_digest.h"
|
||||
#include "ccf/crypto/pem.h"
|
||||
#include "ccf/crypto/sha256_hash.h"
|
||||
#include "ccf/ds/json.h"
|
||||
#include "ccf/ds/openapi.h"
|
||||
#include "ccf/entity_id.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace ccf
|
||||
{
|
||||
struct Receipt
|
||||
class Receipt
|
||||
{
|
||||
struct Element
|
||||
{
|
||||
std::optional<std::string> left = std::nullopt;
|
||||
std::optional<std::string> right = std::nullopt;
|
||||
};
|
||||
public:
|
||||
virtual ~Receipt() = default;
|
||||
|
||||
struct LeafComponents
|
||||
{
|
||||
std::optional<std::string> write_set_digest = std::nullopt;
|
||||
std::optional<std::string> commit_evidence = std::nullopt;
|
||||
std::optional<std::string> claims_digest = std::nullopt;
|
||||
// Signature over the root digest, signed by the identity described in cert
|
||||
std::vector<uint8_t> signature = {};
|
||||
virtual crypto::Sha256Hash calculate_root() = 0;
|
||||
|
||||
LeafComponents() {}
|
||||
LeafComponents(
|
||||
const std::optional<std::string>& write_set_digest_,
|
||||
const std::optional<std::string>& commit_evidence_,
|
||||
const std::optional<std::string>& claims_digest_) :
|
||||
write_set_digest(write_set_digest_),
|
||||
commit_evidence(commit_evidence_),
|
||||
claims_digest(claims_digest_)
|
||||
{}
|
||||
ccf::NodeId node_id = {};
|
||||
crypto::Pem cert = {};
|
||||
|
||||
bool operator==(const LeafComponents& other) const = default;
|
||||
};
|
||||
std::vector<crypto::Pem> service_endorsements = {};
|
||||
|
||||
/// Signature over the root of the Merkle Tree, by the node private key
|
||||
std::string signature;
|
||||
/// Root of the Merkle Tree
|
||||
std::optional<std::string> root = std::nullopt;
|
||||
/// Merkle proof from the signed root to the leaf components
|
||||
std::vector<Element> proof = {};
|
||||
/// Node identity that produced the signature at the time
|
||||
ccf::NodeId node_id;
|
||||
/// Node identity as a PEM certificate
|
||||
std::optional<std::string> cert = std::nullopt;
|
||||
// In practice, either leaf or leaf_components is set
|
||||
/// Leaf of the Merkle proof, only set on transactions emitted by 1.x
|
||||
/// networks. Corresponds to the write set digest.
|
||||
std::optional<std::string> leaf = std::nullopt;
|
||||
/// Leaf components in transactions emitted by 2.x networks.
|
||||
std::optional<LeafComponents> leaf_components = std::nullopt;
|
||||
|
||||
std::optional<std::vector<crypto::Pem>> service_endorsements = std::nullopt;
|
||||
virtual bool is_signature_transaction() const = 0;
|
||||
};
|
||||
|
||||
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(Receipt::Element)
|
||||
DECLARE_JSON_REQUIRED_FIELDS(Receipt::Element)
|
||||
DECLARE_JSON_OPTIONAL_FIELDS(Receipt::Element, left, right)
|
||||
// Most transactions produce a receipt constructed from a combination of 3
|
||||
// digests. Note that transactions emitted by old code versions may not
|
||||
// include a claims_digest or a commit_evidence_digest, but from 2.0 onwards
|
||||
// every transaction will contain a (potentially default-zero'd) claims digest
|
||||
// and a commit evidence digest.
|
||||
class ProofReceipt : public Receipt
|
||||
{
|
||||
public:
|
||||
struct Components
|
||||
{
|
||||
crypto::Sha256Hash write_set_digest;
|
||||
std::string commit_evidence;
|
||||
ccf::ClaimsDigest claims_digest;
|
||||
};
|
||||
Components leaf_components;
|
||||
|
||||
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(Receipt::LeafComponents)
|
||||
DECLARE_JSON_REQUIRED_FIELDS(Receipt::LeafComponents)
|
||||
DECLARE_JSON_OPTIONAL_FIELDS(
|
||||
Receipt::LeafComponents, write_set_digest, commit_evidence, claims_digest)
|
||||
struct ProofStep
|
||||
{
|
||||
enum
|
||||
{
|
||||
Left,
|
||||
Right
|
||||
} direction;
|
||||
|
||||
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(Receipt)
|
||||
DECLARE_JSON_REQUIRED_FIELDS(Receipt, signature, proof, node_id)
|
||||
DECLARE_JSON_OPTIONAL_FIELDS(
|
||||
Receipt, root, cert, leaf, leaf_components, service_endorsements)
|
||||
crypto::Sha256Hash hash = {};
|
||||
|
||||
bool operator==(const ProofStep& other) const
|
||||
{
|
||||
return direction == other.direction && hash == other.hash;
|
||||
}
|
||||
};
|
||||
using Proof = std::vector<ProofStep>;
|
||||
|
||||
// A merkle-tree path from the leaf digest to the signed root
|
||||
Proof proof = {};
|
||||
|
||||
crypto::Sha256Hash calculate_root() override
|
||||
{
|
||||
auto current = get_leaf_digest();
|
||||
|
||||
for (const auto& element : proof)
|
||||
{
|
||||
if (element.direction == ProofStep::Left)
|
||||
{
|
||||
current = crypto::Sha256Hash(element.hash, current);
|
||||
}
|
||||
else
|
||||
{
|
||||
current = crypto::Sha256Hash(current, element.hash);
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
crypto::Sha256Hash get_leaf_digest()
|
||||
{
|
||||
crypto::Sha256Hash ce_dgst(leaf_components.commit_evidence);
|
||||
if (!leaf_components.claims_digest.empty())
|
||||
{
|
||||
return crypto::Sha256Hash(
|
||||
leaf_components.write_set_digest,
|
||||
ce_dgst,
|
||||
leaf_components.claims_digest.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
return crypto::Sha256Hash(leaf_components.write_set_digest, ce_dgst);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_signature_transaction() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Signature transactions are special, as they contain no proof. They contain
|
||||
// a single root, which is directly signed.
|
||||
class SignatureReceipt : public Receipt
|
||||
{
|
||||
public:
|
||||
crypto::Sha256Hash signed_root = {};
|
||||
|
||||
crypto::Sha256Hash calculate_root() override
|
||||
{
|
||||
return signed_root;
|
||||
};
|
||||
|
||||
bool is_signature_transaction() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
using ReceiptPtr = std::shared_ptr<Receipt>;
|
||||
|
||||
// This is an opaque, incomplete type, but can be summarised to a JSON object
|
||||
// by describe_receipt_v1, or a ccf::ReceiptPtr by describe_receipt_v2
|
||||
struct TxReceiptImpl;
|
||||
using TxReceiptImplPtr = std::shared_ptr<TxReceiptImpl>;
|
||||
nlohmann::json describe_receipt_v1(const TxReceiptImpl& receipt);
|
||||
ReceiptPtr describe_receipt_v2(const TxReceiptImpl& receipt);
|
||||
|
||||
// Manual JSON serializers are specified for these types as they are not
|
||||
// trivial POD structs
|
||||
|
||||
void to_json(nlohmann::json& j, const ProofReceipt::Components& step);
|
||||
void from_json(const nlohmann::json& j, ProofReceipt::Components& step);
|
||||
std::string schema_name(const ProofReceipt::Components*);
|
||||
void fill_json_schema(
|
||||
nlohmann::json& schema, const ProofReceipt::Components*);
|
||||
|
||||
void to_json(nlohmann::json& j, const ProofReceipt::ProofStep& step);
|
||||
void from_json(const nlohmann::json& j, ProofReceipt::ProofStep& step);
|
||||
std::string schema_name(const ProofReceipt::ProofStep*);
|
||||
void fill_json_schema(nlohmann::json& schema, const ProofReceipt::ProofStep*);
|
||||
|
||||
void to_json(nlohmann::json& j, const ReceiptPtr& receipt);
|
||||
void from_json(const nlohmann::json& j, ReceiptPtr& receipt);
|
||||
std::string schema_name(const ReceiptPtr*);
|
||||
void fill_json_schema(nlohmann::json& schema, const ReceiptPtr*);
|
||||
|
||||
template <typename T>
|
||||
void add_schema_components(
|
||||
T& helper, nlohmann::json& schema, const ProofReceipt::Components* comp)
|
||||
{
|
||||
helper.template add_schema_component<decltype(
|
||||
ProofReceipt::Components::write_set_digest)>();
|
||||
helper.template add_schema_component<decltype(
|
||||
ProofReceipt::Components::claims_digest)>();
|
||||
|
||||
fill_json_schema(schema, comp);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_schema_components(
|
||||
T& helper, nlohmann::json& schema, const ProofReceipt::ProofStep* ps)
|
||||
{
|
||||
helper
|
||||
.template add_schema_component<decltype(ProofReceipt::ProofStep::hash)>();
|
||||
|
||||
fill_json_schema(schema, ps);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_schema_components(
|
||||
T& helper, nlohmann::json& schema, const ReceiptPtr* r)
|
||||
{
|
||||
helper.template add_schema_component<decltype(Receipt::cert)>();
|
||||
helper.template add_schema_component<decltype(Receipt::node_id)>();
|
||||
helper
|
||||
.template add_schema_component<decltype(Receipt::service_endorsements)>();
|
||||
helper.template add_schema_component<decltype(Receipt::signature)>();
|
||||
|
||||
helper.template add_schema_component<decltype(ProofReceipt::proof)>();
|
||||
helper
|
||||
.template add_schema_component<decltype(ProofReceipt::leaf_components)>();
|
||||
helper
|
||||
.template add_schema_component<decltype(SignatureReceipt::signed_root)>();
|
||||
|
||||
fill_json_schema(schema, r);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -222,7 +222,7 @@ namespace loggingapp
|
|||
"This CCF sample app implements a simple logging application, securely "
|
||||
"recording messages at client-specified IDs. It demonstrates most of "
|
||||
"the features available to CCF apps.";
|
||||
openapi_info.document_version = "1.9.2";
|
||||
openapi_info.document_version = "1.9.3";
|
||||
|
||||
index_per_public_key = std::make_shared<RecordsIndexingStrategy>(
|
||||
PUBLIC_RECORDS, context, 10000, 20);
|
||||
|
@ -884,7 +884,7 @@ namespace loggingapp
|
|||
LoggingGetReceipt::Out out;
|
||||
out.msg = v.value();
|
||||
assert(historical_state->receipt);
|
||||
out.receipt = ccf::describe_receipt(historical_state->receipt);
|
||||
out.receipt = ccf::describe_receipt_v1(*historical_state->receipt);
|
||||
ccf::jsonhandler::set_response(std::move(out), ctx.rpc_ctx, pack);
|
||||
}
|
||||
else
|
||||
|
@ -938,10 +938,12 @@ namespace loggingapp
|
|||
out.msg = v.value();
|
||||
assert(historical_state->receipt);
|
||||
// SNIPPET_START: claims_digest_in_receipt
|
||||
out.receipt = ccf::describe_receipt(historical_state->receipt);
|
||||
// Claims are expanded as out.msg, so the claims digest is removed
|
||||
// from the receipt to force verification to re-compute it.
|
||||
out.receipt.leaf_components->claims_digest = std::nullopt;
|
||||
auto full_receipt =
|
||||
ccf::describe_receipt_v1(*historical_state->receipt);
|
||||
out.receipt = full_receipt;
|
||||
out.receipt["leaf_components"].erase("claims_digest");
|
||||
// SNIPPET_END: claims_digest_in_receipt
|
||||
ccf::jsonhandler::set_response(std::move(out), ctx.rpc_ctx, pack);
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace loggingapp
|
|||
struct Out
|
||||
{
|
||||
std::string msg;
|
||||
ccf::Receipt receipt;
|
||||
nlohmann::json receipt;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -270,7 +270,7 @@ namespace ccfapp
|
|||
ccf::endpoints::EndpointContext& endpoint_ctx,
|
||||
kv::ReadOnlyTx* historical_tx,
|
||||
const std::optional<ccf::TxID>& transaction_id,
|
||||
ccf::TxReceiptPtr receipt)
|
||||
ccf::TxReceiptImplPtr receipt)
|
||||
{
|
||||
js::Runtime rt;
|
||||
rt.add_ccf_classdefs();
|
||||
|
|
|
@ -17,16 +17,18 @@ namespace ccf::v8_tmpl
|
|||
|
||||
static ccf::Receipt* unwrap_receipt(v8::Local<v8::Object> obj)
|
||||
{
|
||||
return static_cast<ccf::Receipt*>(
|
||||
auto receipt_smart_ptr = static_cast<ccf::ReceiptPtr*>(
|
||||
get_internal_field(obj, InternalField::Receipt));
|
||||
return receipt_smart_ptr->get();
|
||||
}
|
||||
|
||||
static void get_signature(
|
||||
v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info)
|
||||
{
|
||||
ccf::Receipt* receipt = unwrap_receipt(info.Holder());
|
||||
const auto sig_b64 = crypto::b64_from_raw(receipt->signature);
|
||||
v8::Local<v8::String> value =
|
||||
v8_util::to_v8_str(info.GetIsolate(), receipt->signature.c_str());
|
||||
v8_util::to_v8_str(info.GetIsolate(), sig_b64);
|
||||
info.GetReturnValue().Set(value);
|
||||
}
|
||||
|
||||
|
@ -34,24 +36,18 @@ namespace ccf::v8_tmpl
|
|||
v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info)
|
||||
{
|
||||
ccf::Receipt* receipt = unwrap_receipt(info.Holder());
|
||||
v8::Local<v8::Value> value;
|
||||
if (receipt->cert.has_value())
|
||||
value = v8_util::to_v8_str(info.GetIsolate(), receipt->cert.value());
|
||||
else
|
||||
value = v8::Undefined(info.GetIsolate());
|
||||
v8::Local<v8::Value> value =
|
||||
v8_util::to_v8_str(info.GetIsolate(), receipt->cert.str());
|
||||
info.GetReturnValue().Set(value);
|
||||
}
|
||||
|
||||
static void get_leaf(
|
||||
v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info)
|
||||
{
|
||||
ccf::Receipt* receipt = unwrap_receipt(info.Holder());
|
||||
v8::Local<v8::Value> value;
|
||||
if (receipt->leaf.has_value())
|
||||
value = v8_util::to_v8_str(info.GetIsolate(), receipt->leaf.value());
|
||||
else
|
||||
value = v8::Undefined(info.GetIsolate());
|
||||
info.GetReturnValue().Set(value);
|
||||
v8::Isolate* isolate = info.GetIsolate();
|
||||
v8::Local<v8::String> what =
|
||||
v8_util::to_v8_str(isolate, "leaf is unimplemented in v8");
|
||||
isolate->ThrowException(what);
|
||||
}
|
||||
|
||||
static void get_node_id(
|
||||
|
@ -67,30 +63,9 @@ namespace ccf::v8_tmpl
|
|||
v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info)
|
||||
{
|
||||
v8::Isolate* isolate = info.GetIsolate();
|
||||
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
||||
ccf::Receipt* receipt = unwrap_receipt(info.Holder());
|
||||
|
||||
size_t size = receipt->proof.size();
|
||||
std::vector<v8::Local<v8::Value>> elements;
|
||||
elements.reserve(size);
|
||||
for (auto& element : receipt->proof)
|
||||
{
|
||||
auto is_left = element.left.has_value();
|
||||
v8::Local<v8::Object> obj = v8::Object::New(isolate);
|
||||
obj
|
||||
->Set(
|
||||
context,
|
||||
v8_util::to_v8_istr(isolate, is_left ? "left" : "right"),
|
||||
v8_util::to_v8_str(
|
||||
isolate, (is_left ? element.left : element.right).value()))
|
||||
.Check();
|
||||
elements.push_back(obj);
|
||||
}
|
||||
|
||||
v8::Local<v8::Array> array =
|
||||
v8::Array::New(info.GetIsolate(), elements.data(), size);
|
||||
|
||||
info.GetReturnValue().Set(array);
|
||||
v8::Local<v8::String> what =
|
||||
v8_util::to_v8_str(isolate, "proof is unimplemented in v8");
|
||||
isolate->ThrowException(what);
|
||||
}
|
||||
|
||||
v8::Local<v8::ObjectTemplate> Receipt::create_template(v8::Isolate* isolate)
|
||||
|
@ -114,12 +89,12 @@ namespace ccf::v8_tmpl
|
|||
}
|
||||
|
||||
v8::Local<v8::Object> Receipt::wrap(
|
||||
v8::Local<v8::Context> context, const ccf::TxReceipt& receipt)
|
||||
v8::Local<v8::Context> context, const ccf::TxReceiptImpl& receipt)
|
||||
{
|
||||
ccf::Receipt* receipt_out = new ccf::Receipt();
|
||||
ccf::ReceiptPtr* receipt_out = new ccf::ReceiptPtr();
|
||||
V8Context::from_context(context).register_finalizer(
|
||||
[](void* data) { delete static_cast<ccf::Receipt*>(data); }, receipt_out);
|
||||
*receipt_out = ccf::describe_receipt(receipt);
|
||||
*receipt_out = ccf::describe_receipt_v2(receipt);
|
||||
|
||||
v8::Isolate* isolate = context->GetIsolate();
|
||||
v8::EscapableHandleScope handle_scope(isolate);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "node/tx_receipt.h"
|
||||
#include "node/tx_receipt_impl.h"
|
||||
|
||||
#include <v8.h>
|
||||
|
||||
|
@ -15,7 +15,7 @@ namespace ccf::v8_tmpl
|
|||
static v8::Local<v8::ObjectTemplate> create_template(v8::Isolate* isolate);
|
||||
|
||||
static v8::Local<v8::Object> wrap(
|
||||
v8::Local<v8::Context> context, const ccf::TxReceipt& receipt);
|
||||
v8::Local<v8::Context> context, const ccf::TxReceiptImpl& receipt);
|
||||
};
|
||||
|
||||
} // namespace ccf::v8_tmpl
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace crypto
|
|||
return digest;
|
||||
}
|
||||
|
||||
Sha256Hash Sha256Hash::from_span(const std::span<uint8_t, SIZE>& sp)
|
||||
Sha256Hash Sha256Hash::from_span(const std::span<const uint8_t, SIZE>& sp)
|
||||
{
|
||||
Sha256Hash digest;
|
||||
std::copy(sp.begin(), sp.end(), digest.h.begin());
|
||||
|
@ -105,6 +105,22 @@ namespace crypto
|
|||
}
|
||||
}
|
||||
|
||||
std::string schema_name(const Sha256Hash*)
|
||||
{
|
||||
return "Sha256Digest";
|
||||
}
|
||||
|
||||
void fill_json_schema(nlohmann::json& schema, const Sha256Hash*)
|
||||
{
|
||||
schema["type"] = "string";
|
||||
|
||||
// According to the spec, "format is an open value, so you can use any
|
||||
// formats, even not those defined by the OpenAPI Specification"
|
||||
// https://swagger.io/docs/specification/data-models/data-types/#format
|
||||
schema["format"] = "hex";
|
||||
schema["pattern"] = fmt::format("^[a-f0-9]{{{}}}$", Sha256Hash::SIZE);
|
||||
}
|
||||
|
||||
bool operator==(const Sha256Hash& lhs, const Sha256Hash& rhs)
|
||||
{
|
||||
for (unsigned i = 0; i < crypto::Sha256Hash::SIZE; i++)
|
||||
|
|
|
@ -236,7 +236,7 @@ namespace ccf
|
|||
ccf::jsonhandler::get_json_params(ctx.rpc_ctx);
|
||||
|
||||
assert(historical_state->receipt);
|
||||
ccf::Receipt out = ccf::describe_receipt(historical_state->receipt);
|
||||
auto out = ccf::describe_receipt_v1(*historical_state->receipt);
|
||||
ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK);
|
||||
ccf::jsonhandler::set_response(out, ctx.rpc_ctx, pack);
|
||||
};
|
||||
|
@ -249,7 +249,7 @@ namespace ccf
|
|||
no_auth_required)
|
||||
.set_execute_outside_consensus(
|
||||
ccf::endpoints::ExecuteOutsideConsensus::Locally)
|
||||
.set_auto_schema<void, ccf::Receipt>()
|
||||
.set_auto_schema<void, nlohmann::json>()
|
||||
.add_query_parameter<ccf::TxID>(tx_id_param_key)
|
||||
.install();
|
||||
}
|
||||
|
|
|
@ -7,84 +7,93 @@ namespace ccf::js
|
|||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wc99-extensions"
|
||||
|
||||
static JSValue ccf_receipt_to_js(JSContext* ctx, TxReceiptPtr receipt)
|
||||
static JSValue ccf_receipt_to_js(JSContext* ctx, TxReceiptImplPtr receipt)
|
||||
{
|
||||
ccf::Receipt receipt_out = ccf::describe_receipt(receipt);
|
||||
ccf::ReceiptPtr receipt_out_p = ccf::describe_receipt_v2(*receipt);
|
||||
auto& receipt_out = *receipt_out_p;
|
||||
auto js_receipt = JS_NewObject(ctx);
|
||||
|
||||
const auto sig_b64 = crypto::b64_from_raw(receipt_out.signature);
|
||||
JS_SetPropertyStr(
|
||||
ctx, js_receipt, "signature", JS_NewString(ctx, sig_b64.c_str()));
|
||||
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
js_receipt,
|
||||
"signature",
|
||||
JS_NewString(ctx, receipt_out.signature.c_str()));
|
||||
if (receipt_out.cert.has_value())
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
js_receipt,
|
||||
"cert",
|
||||
JS_NewString(ctx, receipt_out.cert.value().c_str()));
|
||||
if (receipt_out.leaf.has_value())
|
||||
{
|
||||
JS_SetPropertyStr(
|
||||
ctx, js_receipt, "leaf", JS_NewString(ctx, receipt_out.leaf->c_str()));
|
||||
}
|
||||
else if (receipt_out.leaf_components.has_value())
|
||||
{
|
||||
auto leaf_components = JS_NewObject(ctx);
|
||||
if (receipt_out.leaf_components->write_set_digest.has_value())
|
||||
{
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
leaf_components,
|
||||
"write_set_digest",
|
||||
JS_NewString(
|
||||
ctx, receipt_out.leaf_components->write_set_digest->c_str()));
|
||||
}
|
||||
"cert",
|
||||
JS_NewString(ctx, receipt_out.cert.str().c_str()));
|
||||
|
||||
if (receipt_out.leaf_components->commit_evidence.has_value())
|
||||
{
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
leaf_components,
|
||||
"commit_evidence",
|
||||
JS_NewString(
|
||||
ctx, receipt_out.leaf_components->commit_evidence->c_str()));
|
||||
}
|
||||
|
||||
if (receipt_out.leaf_components->claims_digest.has_value())
|
||||
{
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
leaf_components,
|
||||
"claims_digest",
|
||||
JS_NewString(
|
||||
ctx, receipt_out.leaf_components->claims_digest->c_str()));
|
||||
}
|
||||
JS_SetPropertyStr(ctx, js_receipt, "leaf_components", leaf_components);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error("Receipt neither has leaf nor leaf_components");
|
||||
}
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
js_receipt,
|
||||
"node_id",
|
||||
JS_NewString(ctx, receipt_out.node_id.value().c_str()));
|
||||
auto proof = JS_NewArray(ctx);
|
||||
uint32_t i = 0;
|
||||
for (auto& element : receipt_out.proof)
|
||||
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
js_receipt,
|
||||
"is_signature_transaction",
|
||||
JS_NewBool(ctx, receipt_out.is_signature_transaction()));
|
||||
|
||||
if (!receipt_out_p->is_signature_transaction())
|
||||
{
|
||||
auto js_element = JS_NewObject(ctx);
|
||||
auto is_left = element.left.has_value();
|
||||
auto p_receipt =
|
||||
std::dynamic_pointer_cast<ccf::ProofReceipt>(receipt_out_p);
|
||||
auto leaf_components = JS_NewObject(ctx);
|
||||
const auto wsd_hex =
|
||||
ds::to_hex(p_receipt->leaf_components.write_set_digest.h);
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
js_element,
|
||||
is_left ? "left" : "right",
|
||||
JS_NewString(
|
||||
ctx, (is_left ? element.left : element.right).value().c_str()));
|
||||
JS_DefinePropertyValueUint32(ctx, proof, i++, js_element, JS_PROP_C_W_E);
|
||||
leaf_components,
|
||||
"write_set_digest",
|
||||
JS_NewString(ctx, wsd_hex.c_str()));
|
||||
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
leaf_components,
|
||||
"commit_evidence",
|
||||
JS_NewString(ctx, p_receipt->leaf_components.commit_evidence.c_str()));
|
||||
|
||||
if (!p_receipt->leaf_components.claims_digest.empty())
|
||||
{
|
||||
const auto cd_hex =
|
||||
ds::to_hex(p_receipt->leaf_components.claims_digest.value().h);
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
leaf_components,
|
||||
"claims_digest",
|
||||
JS_NewString(ctx, cd_hex.c_str()));
|
||||
}
|
||||
|
||||
JS_SetPropertyStr(ctx, js_receipt, "leaf_components", leaf_components);
|
||||
|
||||
auto proof = JS_NewArray(ctx);
|
||||
uint32_t i = 0;
|
||||
for (auto& element : p_receipt->proof)
|
||||
{
|
||||
auto js_element = JS_NewObject(ctx);
|
||||
auto is_left = element.direction == ccf::ProofReceipt::ProofStep::Left;
|
||||
const auto hash_hex = ds::to_hex(element.hash.h);
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
js_element,
|
||||
is_left ? "left" : "right",
|
||||
JS_NewString(ctx, hash_hex.c_str()));
|
||||
JS_DefinePropertyValueUint32(
|
||||
ctx, proof, i++, js_element, JS_PROP_C_W_E);
|
||||
}
|
||||
JS_SetPropertyStr(ctx, js_receipt, "proof", proof);
|
||||
}
|
||||
JS_SetPropertyStr(ctx, js_receipt, "proof", proof);
|
||||
else
|
||||
{
|
||||
auto sig_receipt =
|
||||
std::dynamic_pointer_cast<ccf::SignatureReceipt>(receipt_out_p);
|
||||
const auto signed_root = sig_receipt->signed_root;
|
||||
const auto root_hex = ds::to_hex(signed_root.h);
|
||||
JS_SetPropertyStr(
|
||||
ctx, js_receipt, "root_hex", JS_NewString(ctx, root_hex.c_str()));
|
||||
}
|
||||
|
||||
return js_receipt;
|
||||
}
|
||||
|
||||
|
|
|
@ -1511,7 +1511,7 @@ namespace ccf::js
|
|||
ReadOnlyTxContext* historical_txctx,
|
||||
ccf::RpcContext* rpc_ctx,
|
||||
const std::optional<ccf::TxID>& transaction_id,
|
||||
ccf::TxReceiptPtr receipt,
|
||||
ccf::TxReceiptImplPtr receipt,
|
||||
ccf::AbstractGovernanceEffects* gov_effects,
|
||||
ccf::AbstractHostProcesses* host_processes,
|
||||
ccf::NetworkState* network_state,
|
||||
|
@ -1804,7 +1804,7 @@ namespace ccf::js
|
|||
ReadOnlyTxContext* historical_txctx,
|
||||
ccf::RpcContext* rpc_ctx,
|
||||
const std::optional<ccf::TxID>& transaction_id,
|
||||
ccf::TxReceiptPtr receipt,
|
||||
ccf::TxReceiptImplPtr receipt,
|
||||
ccf::AbstractGovernanceEffects* gov_effects,
|
||||
ccf::AbstractHostProcesses* host_processes,
|
||||
ccf::NetworkState* network_state,
|
||||
|
@ -1837,7 +1837,7 @@ namespace ccf::js
|
|||
ReadOnlyTxContext* historical_txctx,
|
||||
ccf::RpcContext* rpc_ctx,
|
||||
const std::optional<ccf::TxID>& transaction_id,
|
||||
ccf::TxReceiptPtr receipt,
|
||||
ccf::TxReceiptImplPtr receipt,
|
||||
ccf::AbstractGovernanceEffects* gov_effects,
|
||||
ccf::AbstractHostProcesses* host_processes,
|
||||
ccf::NetworkState* network_state,
|
||||
|
|
|
@ -180,7 +180,7 @@ namespace ccf::js
|
|||
ReadOnlyTxContext* historical_txctx,
|
||||
ccf::RpcContext* rpc_ctx,
|
||||
const std::optional<ccf::TxID>& transaction_id,
|
||||
ccf::TxReceiptPtr receipt,
|
||||
ccf::TxReceiptImplPtr receipt,
|
||||
ccf::AbstractGovernanceEffects* gov_effects,
|
||||
ccf::AbstractHostProcesses* host_processes,
|
||||
ccf::NetworkState* network_state,
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "node/history.h"
|
||||
#include "node/ledger_secrets.h"
|
||||
#include "node/rpc/node_interface.h"
|
||||
#include "node/tx_receipt.h"
|
||||
#include "node/tx_receipt_impl.h"
|
||||
#include "service/tables/node_signature.h"
|
||||
|
||||
#include <list>
|
||||
|
@ -132,7 +132,7 @@ namespace ccf::historical
|
|||
ccf::ClaimsDigest claims_digest = {};
|
||||
kv::StorePtr store = nullptr;
|
||||
bool is_signature = false;
|
||||
TxReceiptPtr receipt = nullptr;
|
||||
TxReceiptImplPtr receipt = nullptr;
|
||||
ccf::TxID transaction_id;
|
||||
bool has_commit_evidence = false;
|
||||
|
||||
|
@ -332,7 +332,7 @@ namespace ccf::historical
|
|||
{
|
||||
auto proof = tree.get_proof(seqno);
|
||||
details->transaction_id = {sig->view, seqno};
|
||||
details->receipt = std::make_shared<TxReceipt>(
|
||||
details->receipt = std::make_shared<TxReceiptImpl>(
|
||||
sig->sig,
|
||||
proof.get_root(),
|
||||
proof.get_path(),
|
||||
|
@ -419,7 +419,7 @@ namespace ccf::historical
|
|||
{
|
||||
auto proof = tree.get_proof(new_seqno);
|
||||
new_details->transaction_id = {sig->view, new_seqno};
|
||||
new_details->receipt = std::make_shared<TxReceipt>(
|
||||
new_details->receipt = std::make_shared<TxReceiptImpl>(
|
||||
sig->sig,
|
||||
proof.get_root(),
|
||||
proof.get_path(),
|
||||
|
@ -466,7 +466,7 @@ namespace ccf::historical
|
|||
{
|
||||
auto proof = tree.get_proof(new_seqno);
|
||||
new_details->transaction_id = {sig->view, new_seqno};
|
||||
new_details->receipt = std::make_shared<TxReceipt>(
|
||||
new_details->receipt = std::make_shared<TxReceiptImpl>(
|
||||
sig->sig,
|
||||
proof.get_root(),
|
||||
proof.get_path(),
|
||||
|
@ -665,7 +665,7 @@ namespace ccf::historical
|
|||
const auto sig = get_signature(details->store);
|
||||
assert(sig.has_value());
|
||||
details->transaction_id = {sig->view, sig->seqno};
|
||||
details->receipt = std::make_shared<TxReceipt>(
|
||||
details->receipt = std::make_shared<TxReceiptImpl>(
|
||||
sig->sig, sig->root.h, nullptr, sig->node, sig->cert);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,79 +7,151 @@
|
|||
#include "ccf/service/tables/service.h"
|
||||
#include "kv/kv_types.h"
|
||||
#include "node/rpc/network_identity_subsystem.h"
|
||||
#include "node/tx_receipt.h"
|
||||
#include "node/tx_receipt_impl.h"
|
||||
|
||||
namespace ccf
|
||||
{
|
||||
static std::map<crypto::Pem, std::vector<crypto::Pem>>
|
||||
service_endorsement_cache;
|
||||
|
||||
ccf::Receipt describe_receipt(const TxReceipt& receipt, bool include_root)
|
||||
nlohmann::json describe_receipt_v1(const TxReceiptImpl& receipt)
|
||||
{
|
||||
ccf::Receipt out;
|
||||
out.signature = crypto::b64_from_raw(receipt.signature);
|
||||
if (include_root)
|
||||
{
|
||||
out.root = receipt.root.to_string();
|
||||
}
|
||||
// Legacy JSON format, retained for compatibility
|
||||
nlohmann::json out = nlohmann::json::object();
|
||||
|
||||
out["signature"] = crypto::b64_from_raw(receipt.signature);
|
||||
|
||||
auto proof = nlohmann::json::array();
|
||||
if (receipt.path != nullptr)
|
||||
{
|
||||
for (const auto& node : *receipt.path)
|
||||
{
|
||||
ccf::Receipt::Element n;
|
||||
auto n = nlohmann::json::object();
|
||||
if (node.direction == ccf::HistoryTree::Path::Direction::PATH_LEFT)
|
||||
{
|
||||
n.left = node.hash.to_string();
|
||||
n["left"] = node.hash.to_string();
|
||||
}
|
||||
else
|
||||
{
|
||||
n.right = node.hash.to_string();
|
||||
n["right"] = node.hash.to_string();
|
||||
}
|
||||
out.proof.emplace_back(std::move(n));
|
||||
proof.emplace_back(std::move(n));
|
||||
}
|
||||
}
|
||||
out.node_id = receipt.node_id;
|
||||
out["proof"] = proof;
|
||||
|
||||
out["node_id"] = receipt.node_id;
|
||||
|
||||
if (receipt.node_cert.has_value())
|
||||
{
|
||||
out.cert = receipt.node_cert->str();
|
||||
out["cert"] = receipt.node_cert->str();
|
||||
}
|
||||
|
||||
if (receipt.path == nullptr)
|
||||
{
|
||||
// Signature transaction
|
||||
out.leaf = receipt.root.to_string();
|
||||
out["leaf"] = receipt.root.to_string();
|
||||
}
|
||||
else if (!receipt.commit_evidence.has_value())
|
||||
{
|
||||
out.leaf = receipt.write_set_digest->hex_str();
|
||||
out["leaf"] = receipt.write_set_digest->hex_str();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::optional<std::string> write_set_digest_str = std::nullopt;
|
||||
auto leaf_components = nlohmann::json::object();
|
||||
if (receipt.write_set_digest.has_value())
|
||||
write_set_digest_str = receipt.write_set_digest->hex_str();
|
||||
std::optional<std::string> claims_digest_str = std::nullopt;
|
||||
{
|
||||
leaf_components["write_set_digest"] =
|
||||
receipt.write_set_digest->hex_str();
|
||||
}
|
||||
|
||||
if (receipt.commit_evidence.has_value())
|
||||
{
|
||||
leaf_components["commit_evidence"] = receipt.commit_evidence.value();
|
||||
}
|
||||
|
||||
if (!receipt.claims_digest.empty())
|
||||
claims_digest_str = receipt.claims_digest.value().hex_str();
|
||||
out.leaf_components = Receipt::LeafComponents{
|
||||
write_set_digest_str, receipt.commit_evidence, claims_digest_str};
|
||||
{
|
||||
leaf_components["claims_digest"] =
|
||||
receipt.claims_digest.value().hex_str();
|
||||
}
|
||||
out["leaf_components"] = leaf_components;
|
||||
}
|
||||
|
||||
out.service_endorsements = receipt.service_endorsements;
|
||||
if (receipt.service_endorsements.has_value())
|
||||
{
|
||||
out["service_endorsements"] = receipt.service_endorsements;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
ccf::Receipt describe_receipt(
|
||||
const TxReceiptPtr& receipt_ptr, bool include_root)
|
||||
ccf::ReceiptPtr describe_receipt_v2(const TxReceiptImpl& in)
|
||||
{
|
||||
if (receipt_ptr == nullptr)
|
||||
ccf::ReceiptPtr receipt = nullptr;
|
||||
|
||||
if (in.path != nullptr && in.commit_evidence.has_value())
|
||||
{
|
||||
throw std::runtime_error("Cannot describe nullptr receipt");
|
||||
auto proof_receipt = std::make_shared<ProofReceipt>();
|
||||
|
||||
proof_receipt->proof.reserve(in.path->size());
|
||||
for (const auto& node : *in.path)
|
||||
{
|
||||
const auto direction =
|
||||
node.direction == ccf::HistoryTree::Path::Direction::PATH_LEFT ?
|
||||
ccf::ProofReceipt::ProofStep::Left :
|
||||
ccf::ProofReceipt::ProofStep::Right;
|
||||
const auto hash = crypto::Sha256Hash::from_span(
|
||||
{node.hash.bytes, sizeof(node.hash.bytes)});
|
||||
proof_receipt->proof.push_back({direction, hash});
|
||||
}
|
||||
|
||||
if (in.write_set_digest.has_value())
|
||||
{
|
||||
proof_receipt->leaf_components.write_set_digest =
|
||||
in.write_set_digest.value();
|
||||
}
|
||||
|
||||
if (in.commit_evidence.has_value())
|
||||
{
|
||||
proof_receipt->leaf_components.commit_evidence =
|
||||
in.commit_evidence.value();
|
||||
}
|
||||
|
||||
if (!in.claims_digest.empty())
|
||||
{
|
||||
proof_receipt->leaf_components.claims_digest = in.claims_digest;
|
||||
}
|
||||
|
||||
receipt = proof_receipt;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Signature transaction
|
||||
auto sig_receipt = std::make_shared<SignatureReceipt>();
|
||||
sig_receipt->signed_root =
|
||||
crypto::Sha256Hash::from_span({in.root.bytes, sizeof(in.root.bytes)});
|
||||
|
||||
receipt = sig_receipt;
|
||||
}
|
||||
|
||||
return describe_receipt(*receipt_ptr, include_root);
|
||||
auto& out = *receipt;
|
||||
|
||||
out.signature = in.signature;
|
||||
|
||||
out.node_id = in.node_id;
|
||||
|
||||
if (in.node_cert.has_value())
|
||||
{
|
||||
out.cert = in.node_cert.value();
|
||||
}
|
||||
|
||||
if (in.service_endorsements.has_value())
|
||||
{
|
||||
out.service_endorsements = in.service_endorsements.value();
|
||||
}
|
||||
|
||||
return receipt;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,359 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "ccf/receipt.h"
|
||||
|
||||
#define FROM_JSON_TRY_PARSE(TYPE, FIELD) \
|
||||
try \
|
||||
{ \
|
||||
out.FIELD = it->get<decltype(TYPE::FIELD)>(); \
|
||||
} \
|
||||
catch (JsonParseError & jpe) \
|
||||
{ \
|
||||
jpe.pointer_elements.push_back(#FIELD); \
|
||||
throw; \
|
||||
}
|
||||
|
||||
#define FROM_JSON_GET_REQUIRED_FIELD(TYPE, FIELD) \
|
||||
{ \
|
||||
const auto it = j.find(#FIELD); \
|
||||
if (it == j.end()) \
|
||||
{ \
|
||||
throw JsonParseError(fmt::format( \
|
||||
"Missing required field '" #FIELD "' in object:", j.dump())); \
|
||||
} \
|
||||
FROM_JSON_TRY_PARSE(TYPE, FIELD) \
|
||||
}
|
||||
|
||||
#define FROM_JSON_GET_OPTIONAL_FIELD(TYPE, FIELD) \
|
||||
{ \
|
||||
const auto it = j.find(#FIELD); \
|
||||
if (it != j.end()) \
|
||||
{ \
|
||||
FROM_JSON_TRY_PARSE(TYPE, FIELD) \
|
||||
} \
|
||||
}
|
||||
|
||||
namespace ccf
|
||||
{
|
||||
void to_json(nlohmann::json& j, const ProofReceipt::Components& components)
|
||||
{
|
||||
j = nlohmann::json::object();
|
||||
|
||||
j["write_set_digest"] = components.write_set_digest;
|
||||
j["commit_evidence"] = components.commit_evidence;
|
||||
|
||||
if (!components.claims_digest.empty())
|
||||
{
|
||||
j["claims_digest"] = components.claims_digest;
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, ProofReceipt::Components& out)
|
||||
{
|
||||
if (!j.is_object())
|
||||
{
|
||||
throw JsonParseError(fmt::format(
|
||||
"Cannot parse Receipt LeafComponents: Expected object, got {}",
|
||||
j.dump()));
|
||||
}
|
||||
|
||||
FROM_JSON_GET_REQUIRED_FIELD(ProofReceipt::Components, write_set_digest);
|
||||
FROM_JSON_GET_REQUIRED_FIELD(ProofReceipt::Components, commit_evidence);
|
||||
|
||||
// claims_digest is always _emitted_ by current code, but may be
|
||||
// missing from old receipts. When parsing those from JSON, treat it as
|
||||
// optional
|
||||
FROM_JSON_GET_OPTIONAL_FIELD(ProofReceipt::Components, claims_digest);
|
||||
}
|
||||
|
||||
std::string schema_name(const ProofReceipt::Components*)
|
||||
{
|
||||
return "Receipt__LeafComponents";
|
||||
}
|
||||
|
||||
void fill_json_schema(nlohmann::json& schema, const ProofReceipt::Components*)
|
||||
{
|
||||
schema = nlohmann::json::object();
|
||||
schema["type"] = "object";
|
||||
|
||||
auto required = nlohmann::json::array();
|
||||
auto properties = nlohmann::json::object();
|
||||
|
||||
{
|
||||
required.push_back("claims_digest");
|
||||
properties["claims_digest"] = ds::openapi::components_ref_object(
|
||||
ds::json::schema_name<decltype(
|
||||
ProofReceipt::Components::claims_digest)>());
|
||||
|
||||
required.push_back("commit_evidence");
|
||||
properties["commit_evidence"] = ds::openapi::components_ref_object(
|
||||
ds::json::schema_name<decltype(
|
||||
ProofReceipt::Components::commit_evidence)>());
|
||||
|
||||
required.push_back("write_set_digest");
|
||||
properties["write_set_digest"] = ds::openapi::components_ref_object(
|
||||
ds::json::schema_name<decltype(
|
||||
ProofReceipt::Components::write_set_digest)>());
|
||||
}
|
||||
|
||||
schema["required"] = required;
|
||||
schema["properties"] = properties;
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const ProofReceipt::ProofStep& step)
|
||||
{
|
||||
j = nlohmann::json::object();
|
||||
const auto key =
|
||||
step.direction == ProofReceipt::ProofStep::Left ? "left" : "right";
|
||||
j[key] = step.hash;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, ProofReceipt::ProofStep& step)
|
||||
{
|
||||
if (!j.is_object())
|
||||
{
|
||||
throw JsonParseError(fmt::format(
|
||||
"Cannot parse Receipt Step: Expected object, got {}", j.dump()));
|
||||
}
|
||||
|
||||
const auto l_it = j.find("left");
|
||||
const auto r_it = j.find("right");
|
||||
if ((l_it == j.end()) == (r_it == j.end()))
|
||||
{
|
||||
throw JsonParseError(fmt::format(
|
||||
"Cannot parse Receipt Step: Expected either 'left' or 'right' field, "
|
||||
"got {}",
|
||||
j.dump()));
|
||||
}
|
||||
|
||||
if (l_it != j.end())
|
||||
{
|
||||
step.direction = ProofReceipt::ProofStep::Left;
|
||||
step.hash = l_it.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
step.direction = ProofReceipt::ProofStep::Right;
|
||||
step.hash = r_it.value();
|
||||
}
|
||||
}
|
||||
|
||||
std::string schema_name(const ProofReceipt::ProofStep*)
|
||||
{
|
||||
return "Receipt__Element";
|
||||
}
|
||||
|
||||
void fill_json_schema(nlohmann::json& schema, const ProofReceipt::ProofStep*)
|
||||
{
|
||||
schema = nlohmann::json::object();
|
||||
|
||||
auto possible_hash = [](const auto& name) {
|
||||
auto schema = nlohmann::json::object();
|
||||
schema["required"] = nlohmann::json::array();
|
||||
schema["required"].push_back(name);
|
||||
schema["properties"] = nlohmann::json::object();
|
||||
schema["properties"][name] = ds::openapi::components_ref_object(
|
||||
ds::json::schema_name<crypto::Sha256Hash>());
|
||||
return schema;
|
||||
};
|
||||
|
||||
schema["type"] = "object";
|
||||
schema["oneOf"] = nlohmann::json::array();
|
||||
schema["oneOf"].push_back(possible_hash("left"));
|
||||
schema["oneOf"].push_back(possible_hash("right"));
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const ReceiptPtr& receipt)
|
||||
{
|
||||
if (receipt == nullptr)
|
||||
{
|
||||
throw JsonParseError(
|
||||
fmt::format("Cannot serialise Receipt to JSON: Got nullptr"));
|
||||
}
|
||||
|
||||
j = nlohmann::json::object();
|
||||
|
||||
j["signature"] = receipt->signature;
|
||||
j["node_id"] = receipt->node_id;
|
||||
j["cert"] = receipt->cert;
|
||||
j["service_endorsements"] = receipt->service_endorsements;
|
||||
j["is_signature_transaction"] = receipt->is_signature_transaction();
|
||||
|
||||
if (receipt->is_signature_transaction())
|
||||
{
|
||||
throw std::logic_error(
|
||||
"Conversion of signature receipts to JSON is currently undefined");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto p_receipt = std::dynamic_pointer_cast<ProofReceipt>(receipt);
|
||||
if (p_receipt == nullptr)
|
||||
{
|
||||
throw std::logic_error("Unexpected receipt type");
|
||||
}
|
||||
|
||||
j["leaf_components"] = p_receipt->leaf_components;
|
||||
j["proof"] = p_receipt->proof;
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, ReceiptPtr& receipt)
|
||||
{
|
||||
if (!j.is_object())
|
||||
{
|
||||
throw JsonParseError(
|
||||
fmt::format("Cannot parse Receipt: Expected object, got {}", j.dump()));
|
||||
}
|
||||
|
||||
const auto is_sig_it = j.find("is_signature_transaction");
|
||||
if (is_sig_it != j.end())
|
||||
{
|
||||
const bool is_sig = is_sig_it->get<bool>();
|
||||
|
||||
if (!is_sig)
|
||||
{
|
||||
auto p_receipt = std::make_shared<ProofReceipt>();
|
||||
|
||||
auto& out = *p_receipt;
|
||||
FROM_JSON_GET_REQUIRED_FIELD(ProofReceipt, leaf_components);
|
||||
FROM_JSON_GET_REQUIRED_FIELD(ProofReceipt, proof);
|
||||
|
||||
receipt = p_receipt;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw JsonParseError(fmt::format(
|
||||
"Cannot parse Receipt: Expected 'leaf_components' and 'proof'"
|
||||
"fields, got {}",
|
||||
j.dump()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// An old receipt format! Look for leaf field or leaf_components, and
|
||||
// parse to new representation accordingly
|
||||
const auto leaf_it = j.find("leaf");
|
||||
const auto has_leaf = leaf_it != j.end();
|
||||
|
||||
const auto leaf_components_it = j.find("leaf_components");
|
||||
const auto has_leaf_components = leaf_components_it != j.end();
|
||||
|
||||
if (has_leaf && !has_leaf_components)
|
||||
{
|
||||
auto sig_receipt = std::make_shared<SignatureReceipt>();
|
||||
|
||||
try
|
||||
{
|
||||
sig_receipt->signed_root =
|
||||
leaf_it->get<decltype(SignatureReceipt::signed_root)>();
|
||||
}
|
||||
catch (JsonParseError& jpe)
|
||||
{
|
||||
jpe.pointer_elements.push_back("leaf");
|
||||
throw;
|
||||
}
|
||||
|
||||
receipt = sig_receipt;
|
||||
}
|
||||
else if (!has_leaf && has_leaf_components)
|
||||
{
|
||||
auto p_receipt = std::make_shared<ProofReceipt>();
|
||||
|
||||
auto& out = *p_receipt;
|
||||
FROM_JSON_GET_REQUIRED_FIELD(ProofReceipt, leaf_components);
|
||||
FROM_JSON_GET_REQUIRED_FIELD(ProofReceipt, proof);
|
||||
|
||||
receipt = p_receipt;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw JsonParseError(fmt::format(
|
||||
"Cannot parse v1 Receipt: Expected either 'leaf' or "
|
||||
"'leaf_components' "
|
||||
"field, got {}",
|
||||
j.dump()));
|
||||
}
|
||||
}
|
||||
|
||||
auto& out = *receipt;
|
||||
FROM_JSON_GET_REQUIRED_FIELD(Receipt, signature);
|
||||
FROM_JSON_GET_REQUIRED_FIELD(Receipt, node_id);
|
||||
FROM_JSON_GET_REQUIRED_FIELD(Receipt, cert);
|
||||
|
||||
// service_endorsements is always _emitted_ by current code, but may be
|
||||
// missing from old receipts. When parsing those from JSON, treat it as
|
||||
// optional
|
||||
FROM_JSON_GET_OPTIONAL_FIELD(Receipt, service_endorsements);
|
||||
}
|
||||
|
||||
std::string schema_name(const ReceiptPtr*)
|
||||
{
|
||||
return "Receipt";
|
||||
}
|
||||
|
||||
void fill_json_schema(nlohmann::json& schema, const ReceiptPtr*)
|
||||
{
|
||||
schema = nlohmann::json::object();
|
||||
schema["type"] = "object";
|
||||
|
||||
auto required = nlohmann::json::array();
|
||||
auto properties = nlohmann::json::object();
|
||||
|
||||
{
|
||||
required.push_back("cert");
|
||||
properties["cert"] = ds::openapi::components_ref_object(
|
||||
ds::json::schema_name<decltype(Receipt::cert)>());
|
||||
|
||||
required.push_back("node_id");
|
||||
properties["node_id"] = ds::openapi::components_ref_object(
|
||||
ds::json::schema_name<decltype(Receipt::node_id)>());
|
||||
|
||||
required.push_back("service_endorsements");
|
||||
properties["service_endorsements"] = ds::openapi::components_ref_object(
|
||||
ds::json::schema_name<decltype(Receipt::service_endorsements)>());
|
||||
|
||||
required.push_back("signature");
|
||||
properties["signature"] = ds::openapi::components_ref_object(
|
||||
ds::json::schema_name<decltype(Receipt::signature)>());
|
||||
|
||||
required.push_back("proof");
|
||||
properties["proof"] = ds::openapi::components_ref_object(
|
||||
ds::json::schema_name<decltype(ProofReceipt::proof)>());
|
||||
|
||||
properties["leaf_components"] = ds::openapi::components_ref_object(
|
||||
ds::json::schema_name<decltype(ProofReceipt::leaf_components)>());
|
||||
|
||||
properties["leaf"] = ds::openapi::components_ref_object(
|
||||
ds::json::schema_name<decltype(SignatureReceipt::signed_root)>());
|
||||
|
||||
// This says the required properties are all the properties we currently
|
||||
// have, AND one of either leaf OR leaf_components. It inserts the
|
||||
// following element into the schema, constructing a composite required
|
||||
// list:
|
||||
// "allOf":
|
||||
// [
|
||||
// {"required": ["cert", "signature"...]},
|
||||
// {
|
||||
// "oneOf": [
|
||||
// {"required": ["leaf"]},
|
||||
// {"required": ["leaf_components", "proof"]}
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
const auto oneOf = nlohmann::json::object(
|
||||
{{"oneOf",
|
||||
nlohmann::json::array(
|
||||
{nlohmann::json::object(
|
||||
{{"required", nlohmann::json::array({"leaf"})}}),
|
||||
nlohmann::json::object(
|
||||
{{"required",
|
||||
nlohmann::json::array({"leaf_components", "proof"})}})})}});
|
||||
|
||||
schema["allOf"] = nlohmann::json::array(
|
||||
{nlohmann::json::object({{"required", required}}), oneOf});
|
||||
}
|
||||
|
||||
schema["properties"] = properties;
|
||||
}
|
||||
}
|
|
@ -493,7 +493,7 @@ namespace ccf
|
|||
openapi_info.description =
|
||||
"This API is used to submit and query proposals which affect CCF's "
|
||||
"public governance tables.";
|
||||
openapi_info.document_version = "2.7.1";
|
||||
openapi_info.document_version = "2.7.2";
|
||||
}
|
||||
|
||||
static std::optional<MemberId> get_caller_member_id(
|
||||
|
|
|
@ -357,7 +357,7 @@ namespace ccf
|
|||
openapi_info.description =
|
||||
"This API provides public, uncredentialed access to service and node "
|
||||
"state.";
|
||||
openapi_info.document_version = "2.17.0";
|
||||
openapi_info.document_version = "2.17.1";
|
||||
}
|
||||
|
||||
void init_handlers() override
|
||||
|
|
|
@ -9,68 +9,12 @@
|
|||
#include "kv/kv_types.h"
|
||||
#include "kv/serialised_entry_format.h"
|
||||
#include "node/history.h"
|
||||
#include "node/tx_receipt.h"
|
||||
#include "node/tx_receipt_impl.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace ccf
|
||||
{
|
||||
/* Receipts included in snapshots always contain leaf components,
|
||||
including a claims digest and commit evidence, from 2.0.0-rc0 onwards.
|
||||
This verification code deliberately does not support snapshots
|
||||
produced by 2.0.0-dev* releases
|
||||
*/
|
||||
static crypto::Sha256Hash compute_root_from_snapshot_receipt(
|
||||
const Receipt& receipt)
|
||||
{
|
||||
crypto::Sha256Hash current;
|
||||
if (receipt.leaf_components.has_value())
|
||||
{
|
||||
auto components = receipt.leaf_components.value();
|
||||
if (
|
||||
components.write_set_digest.has_value() &&
|
||||
components.commit_evidence.has_value() &&
|
||||
components.claims_digest.has_value())
|
||||
{
|
||||
auto ws_dgst = crypto::Sha256Hash::from_hex_string(
|
||||
components.write_set_digest.value());
|
||||
crypto::Sha256Hash ce_dgst(components.commit_evidence.value());
|
||||
auto cl_dgst =
|
||||
crypto::Sha256Hash::from_hex_string(components.claims_digest.value());
|
||||
current = crypto::Sha256Hash(ws_dgst, ce_dgst, cl_dgst);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error(
|
||||
"Cannot compute leaf unless write_set_digest, commit_evidence and "
|
||||
"claims_digest "
|
||||
"are set");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error(
|
||||
"Cannot compute root if leaf_components are not set");
|
||||
}
|
||||
for (auto const& element : receipt.proof)
|
||||
{
|
||||
if (element.left.has_value())
|
||||
{
|
||||
assert(!element.right.has_value());
|
||||
auto left = crypto::Sha256Hash::from_hex_string(element.left.value());
|
||||
current = crypto::Sha256Hash(left, current);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(element.right.has_value());
|
||||
auto right = crypto::Sha256Hash::from_hex_string(element.right.value());
|
||||
current = crypto::Sha256Hash(current, right);
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
struct StartupSnapshotInfo
|
||||
{
|
||||
std::vector<uint8_t> raw;
|
||||
|
@ -135,20 +79,18 @@ namespace ccf
|
|||
auto receipt_size = size - store_snapshot_size;
|
||||
|
||||
auto j = nlohmann::json::parse(receipt_data, receipt_data + receipt_size);
|
||||
auto receipt = j.get<Receipt>();
|
||||
|
||||
if (
|
||||
!receipt.leaf_components.has_value() ||
|
||||
!receipt.leaf_components->claims_digest.has_value())
|
||||
auto receipt_p = j.get<ReceiptPtr>();
|
||||
auto receipt = std::dynamic_pointer_cast<ccf::ProofReceipt>(receipt_p);
|
||||
if (receipt == nullptr)
|
||||
{
|
||||
throw std::logic_error(
|
||||
"Snapshot receipt is missing snapshot digest claim");
|
||||
fmt::format("Unexpected receipt type: missing expanded claims"));
|
||||
}
|
||||
|
||||
auto snapshot_digest =
|
||||
crypto::Sha256Hash({snapshot.data(), store_snapshot_size});
|
||||
auto snapshot_digest_claim = crypto::Sha256Hash::from_hex_string(
|
||||
receipt.leaf_components->claims_digest.value());
|
||||
auto snapshot_digest_claim =
|
||||
receipt->leaf_components.claims_digest.value();
|
||||
if (snapshot_digest != snapshot_digest_claim)
|
||||
{
|
||||
throw std::logic_error(fmt::format(
|
||||
|
@ -157,17 +99,15 @@ namespace ccf
|
|||
snapshot_digest_claim));
|
||||
}
|
||||
|
||||
auto root = compute_root_from_snapshot_receipt(receipt);
|
||||
auto raw_sig = crypto::raw_from_b64(receipt.signature);
|
||||
auto root = receipt->calculate_root();
|
||||
auto raw_sig = receipt->signature;
|
||||
|
||||
if (!receipt.cert.has_value())
|
||||
{
|
||||
throw std::logic_error("Missing node certificate in snapshot receipt");
|
||||
}
|
||||
|
||||
auto v = crypto::make_unique_verifier(receipt.cert.value());
|
||||
auto v = crypto::make_unique_verifier(receipt->cert);
|
||||
if (!v->verify_hash(
|
||||
root.h.data(), root.h.size(), raw_sig.data(), raw_sig.size()))
|
||||
root.h.data(),
|
||||
root.h.size(),
|
||||
receipt->signature.data(),
|
||||
receipt->signature.size()))
|
||||
{
|
||||
throw std::logic_error(
|
||||
"Signature verification failed for snapshot receipt");
|
||||
|
@ -176,7 +116,10 @@ namespace ccf
|
|||
if (prev_service_identity)
|
||||
{
|
||||
crypto::Pem prev_pem(*prev_service_identity);
|
||||
if (!v->verify_certificate({&prev_pem}, {}, /* ignore_time */ true))
|
||||
if (!v->verify_certificate(
|
||||
{&prev_pem},
|
||||
{}, /* ignore_time */
|
||||
true))
|
||||
{
|
||||
throw std::logic_error(
|
||||
"Previous service identity does not endorse the node identity that "
|
||||
|
@ -239,7 +182,7 @@ namespace ccf
|
|||
auto proof = history.get_proof(seqno);
|
||||
ccf::ClaimsDigest cd;
|
||||
cd.set(std::move(claims_digest));
|
||||
auto tx_receipt = std::make_shared<ccf::TxReceipt>(
|
||||
ccf::TxReceiptImpl tx_receipt(
|
||||
sig,
|
||||
proof.get_root(),
|
||||
proof.get_path(),
|
||||
|
@ -249,8 +192,8 @@ namespace ccf
|
|||
commit_evidence,
|
||||
cd);
|
||||
|
||||
Receipt receipt = ccf::describe_receipt(tx_receipt);
|
||||
const auto receipt_str = nlohmann::json(receipt).dump();
|
||||
auto receipt = ccf::describe_receipt_v1(tx_receipt);
|
||||
const auto receipt_str = receipt.dump();
|
||||
return std::vector<uint8_t>(receipt_str.begin(), receipt_str.end());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "ccf/receipt.h"
|
||||
|
||||
#include "ccf/crypto/key_pair.h"
|
||||
#include "ccf/service/tables/nodes.h"
|
||||
#include "ds/x509_time_fmt.h"
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
|
||||
std::random_device rand_device;
|
||||
std::default_random_engine rand_engine(rand_device());
|
||||
|
||||
crypto::Sha256Hash rand_digest()
|
||||
{
|
||||
std::uniform_int_distribution<uint8_t> dist;
|
||||
|
||||
crypto::Sha256Hash ret;
|
||||
std::generate(
|
||||
ret.h.begin(), ret.h.end(), [&]() { return dist(rand_engine); });
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void populate_receipt(std::shared_ptr<ccf::ProofReceipt> receipt)
|
||||
{
|
||||
using namespace std::literals;
|
||||
const auto valid_from =
|
||||
ds::to_x509_time_string(std::chrono::system_clock::now() - 1h);
|
||||
const auto valid_to =
|
||||
ds::to_x509_time_string(std::chrono::system_clock::now() + 1h);
|
||||
|
||||
auto node_kp = crypto::make_key_pair();
|
||||
auto node_cert = node_kp->self_sign("CN=node", valid_from, valid_to);
|
||||
|
||||
receipt->cert = node_cert;
|
||||
receipt->node_id = ccf::compute_node_id_from_kp(node_kp);
|
||||
|
||||
auto current_digest = receipt->get_leaf_digest();
|
||||
|
||||
const auto num_proof_steps = rand() % 8;
|
||||
for (auto i = 0; i < num_proof_steps; ++i)
|
||||
{
|
||||
const auto dir = rand() % 2 == 0 ? ccf::ProofReceipt::ProofStep::Left :
|
||||
ccf::ProofReceipt::ProofStep::Right;
|
||||
const auto digest = rand_digest();
|
||||
|
||||
ccf::ProofReceipt::ProofStep step{dir, digest};
|
||||
receipt->proof.push_back(step);
|
||||
|
||||
if (dir == ccf::ProofReceipt::ProofStep::Left)
|
||||
{
|
||||
current_digest = crypto::Sha256Hash(digest, current_digest);
|
||||
}
|
||||
else
|
||||
{
|
||||
current_digest = crypto::Sha256Hash(current_digest, digest);
|
||||
}
|
||||
}
|
||||
|
||||
const auto root = receipt->calculate_root();
|
||||
receipt->signature = node_kp->sign_hash(root.h.data(), root.h.size());
|
||||
|
||||
const auto num_endorsements = rand() % 3;
|
||||
for (auto i = 0; i < num_endorsements; ++i)
|
||||
{
|
||||
auto service_kp = crypto::make_key_pair();
|
||||
auto service_cert =
|
||||
service_kp->self_sign("CN=service", valid_from, valid_to);
|
||||
const auto csr = node_kp->create_csr(fmt::format("CN=Test{}", i));
|
||||
const auto endorsement =
|
||||
service_kp->sign_csr(service_cert, csr, valid_from, valid_to);
|
||||
receipt->service_endorsements.push_back(endorsement);
|
||||
}
|
||||
}
|
||||
|
||||
void compare_receipts(ccf::ReceiptPtr l, ccf::ReceiptPtr r)
|
||||
{
|
||||
REQUIRE(l != nullptr);
|
||||
REQUIRE(r != nullptr);
|
||||
|
||||
REQUIRE(l->signature == r->signature);
|
||||
REQUIRE(l->node_id == r->node_id);
|
||||
REQUIRE(l->cert == r->cert);
|
||||
REQUIRE(l->service_endorsements == r->service_endorsements);
|
||||
REQUIRE(l->is_signature_transaction() == r->is_signature_transaction());
|
||||
|
||||
if (!l->is_signature_transaction())
|
||||
{
|
||||
auto p_l = std::dynamic_pointer_cast<ccf::ProofReceipt>(l);
|
||||
REQUIRE(p_l != nullptr);
|
||||
|
||||
auto p_r = std::dynamic_pointer_cast<ccf::ProofReceipt>(r);
|
||||
REQUIRE(p_r != nullptr);
|
||||
|
||||
REQUIRE(p_l->proof == p_r->proof);
|
||||
REQUIRE(
|
||||
p_l->leaf_components.write_set_digest ==
|
||||
p_r->leaf_components.write_set_digest);
|
||||
REQUIRE(
|
||||
p_l->leaf_components.commit_evidence ==
|
||||
p_r->leaf_components.commit_evidence);
|
||||
REQUIRE(
|
||||
p_l->leaf_components.claims_digest == p_r->leaf_components.claims_digest);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error("Unhandled receipt type");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON parsing" * doctest::test_suite("receipt"))
|
||||
{
|
||||
const auto sample_json_receipt =
|
||||
R"xxx({
|
||||
"cert": "-----BEGIN CERTIFICATE-----\nMIIBzjCCAVSgAwIBAgIQGR/ue9CFspRa/g6jSMHFYjAKBggqhkjOPQQDAzAWMRQw\nEgYDVQQDDAtDQ0YgTmV0d29yazAeFw0yMjAxMjgxNjAzNDZaFw0yMjAxMjkxNjAz\nNDVaMBMxETAPBgNVBAMMCENDRiBOb2RlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE\nwsdpHLNw7xso/g71XzlQjoITiTBOef8gCayOiPJh/W2YfzreOawzD6gVQPSI+iPg\nZPc6smFhtV5bP/WZ2KW0K9Pn+OIjm/jMU5+s3rSgts50cRjlA/k81bUI88dzQzx9\no2owaDAJBgNVHRMEAjAAMB0GA1UdDgQWBBQgtPwYar54AQ4UL0RImVsm6wQQpzAf\nBgNVHSMEGDAWgBS2ngksRlVPvwDcLhN57VV+j2WyBTAbBgNVHREEFDAShwR/AAAB\nhwR/ZEUlhwR/AAACMAoGCCqGSM49BAMDA2gAMGUCMQDq54yS4Bmfwfcikpy2yL2+\nGFemyqNKXheFExRVt2edxVgId+uvIBGjrJEqf6zS/dsCMHVnBCLYRgxpamFkX1BF\nBDkVitfTOdYfUDWGV3MIMNdbam9BDNxG4q6XtQr4eb3jqg==\n-----END CERTIFICATE-----\n",
|
||||
"leaf_components": {
|
||||
"commit_evidence": "ce:2.643:55dbbbf04b71c6dcc01dd9d1c0012a6a959aef907398f7e183cc8913c82468d8",
|
||||
"write_set_digest": "d0c521504ce2be6b4c22db8e99b14fc475b51bc91224181c75c64aa2cef72b83",
|
||||
"claims_digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"node_id": "7dfbb9a56ebe8b43c833b34cb227153ef61e4890187fe6164022255dec8f9646",
|
||||
"proof": [
|
||||
{
|
||||
"left": "00a771baf15468ed05d6ef8614b3669fcde6809314650061d64281b5d4faf9ec"
|
||||
},
|
||||
{
|
||||
"left": "a9c8a36d01aa9dfbfb74c6f6a2cef2efcbd92bd6dfd1f7440302ad5ac7be1577"
|
||||
},
|
||||
{
|
||||
"right": "8e238d95767e6ffe4b20e1a5e93dd7b926cbd86caa83698584a16ad2dd7d60b8"
|
||||
},
|
||||
{
|
||||
"left": "d4717996ae906cdce0ac47257a4a9445c58474c2f40811e575f804506e5fee9f"
|
||||
},
|
||||
{
|
||||
"left": "c1c206c4670bd2adee821013695d593f5983ca0994ae74630528da5fb6642205"
|
||||
}
|
||||
],
|
||||
"service_endorsements": [
|
||||
"-----BEGIN CERTIFICATE-----MIIBtTCCATugAwIBAgIRAN37fxGnWYNVLZn8nM8iBP8wCgYIKoZIzj0EAwMwFjEU\nMBIGA1UEAwwLQ0NGIE5ldHdvcmswHhcNMjIwMzIzMTMxMDA2WhcNMjIwMzI0MTMx\nMDA1WjAWMRQwEgYDVQQDDAtDQ0YgTmV0d29yazB2MBAGByqGSM49AgEGBSuBBAAi\nA2IABBErIfAEVg2Uw+iBPV9kEcpQw8NcoZWHmj4boHf7VVd6yCwRl+X/wOaOudca\nCqMMcwrt4Bb7n11RbsRwU04B7fG907MelICFHiPZjU/XMK5HEsSEZWowVtNwOLDo\nl5cN6aNNMEswCQYDVR0TBAIwADAdBgNVHQ4EFgQU4n5gHhHFnYZc3nwxKRggl8YB\nqdgwHwYDVR0jBBgwFoAUcAvR3F5YSUvPPGcAxrvh2Z5ump8wCgYIKoZIzj0EAwMD\naAAwZQIxAMeRoXo9FDzr51qkiD4Ws0Y+KZT06MFHcCg47TMDSGvnGrwL3DcIjGs7\nTTwJJQjbWAIwS9AqOJP24sN6jzXOTd6RokeF/MTGJbQAihzgTbZia7EKM8s/0yDB\n0QYtrfMjtPOx\n-----END CERTIFICATE-----\n"
|
||||
],
|
||||
"signature": "MGQCMHrnwS123oHqUKuQRPsQ+gk6WVutixeOvxcXX79InBgPOxJCoScCOlBnK4UYyLzangIwW9k7IZkMgG076qVv5zcx7OuKb7bKyii1yP1rcakeGVvVMwISeE+Fr3BnFfPD66Df",
|
||||
"is_signature_transaction": false
|
||||
})xxx";
|
||||
|
||||
nlohmann::json j = nlohmann::json::parse(sample_json_receipt);
|
||||
|
||||
auto receipt = j.get<ccf::ReceiptPtr>();
|
||||
|
||||
nlohmann::json j2 = receipt;
|
||||
REQUIRE(j == j2);
|
||||
|
||||
INFO("Check that old formats, with missing fields, can still be parsed");
|
||||
{
|
||||
j.erase("service_endorsements");
|
||||
auto unendorsed = j.get<ccf::ReceiptPtr>();
|
||||
receipt->service_endorsements.clear();
|
||||
compare_receipts(receipt, unendorsed);
|
||||
|
||||
j["leaf_components"].erase("claims_digest");
|
||||
REQUIRE_NOTHROW(j.get<ccf::ReceiptPtr>());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON roundtrip" * doctest::test_suite("receipt"))
|
||||
{
|
||||
{
|
||||
std::shared_ptr<ccf::Receipt> r = nullptr;
|
||||
nlohmann::json j;
|
||||
REQUIRE_THROWS(to_json(j, r));
|
||||
REQUIRE_THROWS(from_json(j, r));
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 20; ++i)
|
||||
{
|
||||
{
|
||||
INFO("ProofReceipt");
|
||||
auto p_receipt = std::make_shared<ccf::ProofReceipt>();
|
||||
p_receipt->leaf_components.write_set_digest = rand_digest();
|
||||
p_receipt->leaf_components.commit_evidence = "ce:2.4:abcd";
|
||||
p_receipt->leaf_components.claims_digest.set(rand_digest());
|
||||
|
||||
populate_receipt(p_receipt);
|
||||
|
||||
nlohmann::json j = p_receipt;
|
||||
|
||||
const auto parsed = j.get<ccf::ReceiptPtr>();
|
||||
compare_receipts(p_receipt, parsed);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,9 @@
|
|||
|
||||
namespace ccf
|
||||
{
|
||||
struct TxReceipt
|
||||
// Representation of receipt used by internal framework code. Mirrored in
|
||||
// public interface by ccf::Receipt
|
||||
struct TxReceiptImpl
|
||||
{
|
||||
std::vector<uint8_t> signature = {};
|
||||
HistoryTree::Hash root = {};
|
||||
|
@ -19,7 +21,7 @@ namespace ccf
|
|||
ccf::ClaimsDigest claims_digest = {};
|
||||
std::optional<std::vector<crypto::Pem>> service_endorsements = std::nullopt;
|
||||
|
||||
TxReceipt(
|
||||
TxReceiptImpl(
|
||||
const std::vector<uint8_t>& signature_,
|
||||
const HistoryTree::Hash& root_,
|
||||
std::shared_ptr<ccf::HistoryTree::Path> path_,
|
||||
|
@ -44,5 +46,5 @@ namespace ccf
|
|||
{}
|
||||
};
|
||||
|
||||
using TxReceiptPtr = std::shared_ptr<TxReceipt>;
|
||||
using TxReceiptImplPtr = std::shared_ptr<TxReceiptImpl>;
|
||||
}
|
|
@ -1385,6 +1385,6 @@ def network(
|
|||
raise
|
||||
finally:
|
||||
LOG.info("Stopping network")
|
||||
net.stop_all_nodes(skip_verification=True)
|
||||
net.stop_all_nodes(skip_verification=True, accept_ledger_diff=True)
|
||||
if init_partitioner:
|
||||
net.partitioner.cleanup()
|
||||
|
|
|
@ -80,6 +80,10 @@ def run(args):
|
|||
LOG.error(f"Writing to {alt_file} for comparison")
|
||||
with open(alt_file, "w", encoding="utf-8") as f2:
|
||||
f2.write(formatted_schema)
|
||||
try:
|
||||
old_schema.remove(alt_file)
|
||||
except KeyError:
|
||||
pass
|
||||
changed_files.append(openapi_target_file)
|
||||
else:
|
||||
LOG.debug("Schema matches in {}".format(openapi_target_file))
|
||||
|
|
Загрузка…
Ссылка в новой задаче