Restore public `ccf::Receipt` type (#3793)

This commit is contained in:
Eddy Ashton 2022-04-29 13:03:22 +01:00 коммит произвёл GitHub
Родитель de4a3c4586
Коммит 8e0b2c91cf
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
32 изменённых файлов: 1029 добавлений и 539 удалений

Просмотреть файл

@ -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;
}
}

359
src/node/receipt.cpp Normal file
Просмотреть файл

@ -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());
}
}

195
src/node/test/receipt.cpp Normal file
Просмотреть файл

@ -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))