Report `TxID` at which current service was created (#3996)

This commit is contained in:
Julien Maffre 2022-06-30 09:00:19 +01:00 коммит произвёл GitHub
Родитель 4484e3b6d1
Коммит 7b2309047e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 72 добавлений и 23 удалений

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

@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `/node/version` now contains an `unsafe` flag reflecting the status of the build.
- New per-interface configuration entries (`network.rpc_interfaces.http_configuration`) are added to let operators cap the maximum size of body, header value size and number of headers in client HTTP requests. The client session is automatically closed if the HTTP request exceeds one of these limits (#3941).
- Added new `recovery_count` field to `GET /node/network` endpoint to track the number of disaster recovery procedures undergone by the service (#3982).
- Added new `current_service_create_txid` field to `GET /node/network` endpoint to indicate `TxID` at which current service was created (#3996).
### Changed

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

@ -249,6 +249,9 @@
},
"GetNetworkInfo__Out": {
"properties": {
"current_service_create_txid": {
"$ref": "#/components/schemas/TransactionId"
},
"current_view": {
"$ref": "#/components/schemas/uint64"
},
@ -270,7 +273,8 @@
"service_certificate",
"current_view",
"primary_id",
"recovery_count"
"recovery_count",
"current_service_create_txid"
],
"type": "object"
},
@ -822,7 +826,7 @@
"info": {
"description": "This API provides public, uncredentialed access to service and node state.",
"title": "CCF Public Node API",
"version": "2.22.0"
"version": "2.23.0"
},
"openapi": "3.0.0",
"paths": {

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

@ -5,6 +5,7 @@
#include "ccf/crypto/pem.h"
#include "ccf/ds/json.h"
#include "ccf/service/map.h"
#include "ccf/tx_id.h"
namespace ccf
{
@ -29,15 +30,20 @@ namespace ccf
crypto::Pem cert;
/// Status of the service
ServiceStatus status = ServiceStatus::OPENING;
/// Version of previous service identity (before the last recovery)
/// Version (seqno) of previous service identity (before the last recovery)
std::optional<kv::Version> previous_service_identity_version = std::nullopt;
/// Number of disaster recoveries performed on this service
std::optional<size_t> recovery_count = std::nullopt;
/// TxID at which current service was created
std::optional<ccf::TxID> current_service_create_txid = std::nullopt;
};
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(ServiceInfo);
DECLARE_JSON_REQUIRED_FIELDS(ServiceInfo, cert, status);
DECLARE_JSON_OPTIONAL_FIELDS(
ServiceInfo, previous_service_identity_version, recovery_count);
ServiceInfo,
previous_service_identity_version,
recovery_count,
current_service_create_txid);
// As there is only one service active at a given time, it is stored in single
// Value in the KV

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

@ -147,8 +147,12 @@ namespace ccf
struct NodeStateMsg
{
NodeStateMsg(NodeState& self_) : self(self_) {}
NodeStateMsg(NodeState& self_, View create_view_ = 0) :
self(self_),
create_view(create_view_)
{}
NodeState& self;
View create_view;
};
//
@ -399,7 +403,8 @@ namespace ccf
// Become the primary and force replication
consensus->force_become_primary();
if (!create_and_send_boot_request(true /* Create new consortium */))
if (!create_and_send_boot_request(
aft::starting_view_change, true /* Create new consortium */))
{
throw std::runtime_error(
"Genesis transaction could not be committed");
@ -1037,7 +1042,7 @@ namespace ccf
// When reaching the end of the public ledger, truncate to last signed
// index
const auto last_recovered_term = view_history.size();
auto new_term = last_recovered_term + 2;
auto new_term = last_recovered_term + aft::starting_view_change;
LOG_INFO_FMT("Setting term on public recovery store to {}", new_term);
// Note: KV term must be set before the first Tx is committed
@ -1107,6 +1112,7 @@ namespace ccf
auto msg = std::make_unique<threading::Tmsg<NodeStateMsg>>(
[](std::unique_ptr<threading::Tmsg<NodeStateMsg>> msg) {
if (!msg->data.self.create_and_send_boot_request(
msg->data.create_view,
false /* Restore consortium from ledger */))
{
throw std::runtime_error(
@ -1114,7 +1120,8 @@ namespace ccf
}
msg->data.self.advance_part_of_public_network();
},
*this);
*this,
new_term);
threading::ThreadMessaging::thread_messaging.add_task(
threading::get_current_thread_id(), std::move(msg));
}
@ -1918,7 +1925,8 @@ namespace ccf
}
}
std::vector<uint8_t> serialize_create_request(bool create_consortium = true)
std::vector<uint8_t> serialize_create_request(
View create_view, bool create_consortium = true)
{
CreateNetworkNodeToNode::In create_params;
@ -1951,6 +1959,7 @@ namespace ccf
create_params.code_digest = node_code_id;
create_params.node_info_network = config.network;
create_params.node_data = config.node_data;
create_params.create_txid = {create_view, last_recovered_signed_idx + 1};
const auto body = serdes::pack(create_params, serdes::Pack::Text);
@ -2033,9 +2042,11 @@ namespace ccf
return parse_create_response(response.value());
}
bool create_and_send_boot_request(bool create_consortium = true)
bool create_and_send_boot_request(
View create_view, bool create_consortium = true)
{
return send_create_request(serialize_create_request(create_consortium));
return send_create_request(
serialize_create_request(create_view, create_consortium));
}
void backup_initiate_private_recovery()

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

@ -59,6 +59,7 @@ namespace ccf
std::optional<ccf::View> current_view;
std::optional<NodeId> primary_id;
size_t recovery_count;
std::optional<ccf::TxID> current_service_create_txid;
};
};

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

@ -62,6 +62,7 @@ namespace ccf
CodeDigest code_digest;
NodeInfoNetwork node_info_network;
nlohmann::json node_data;
ccf::TxID create_txid;
// Only set on genesis transaction, but not on recovery
std::optional<StartupConfig::Start> genesis_info = std::nullopt;

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

@ -370,7 +370,7 @@ namespace ccf
openapi_info.description =
"This API provides public, uncredentialed access to service and node "
"state.";
openapi_info.document_version = "2.22.0";
openapi_info.document_version = "2.23.0";
}
void init_handlers() override
@ -809,6 +809,8 @@ namespace ccf
out.service_status = service_value.status;
out.service_certificate = service_value.cert;
out.recovery_count = service_value.recovery_count.value_or(0);
out.current_service_create_txid =
service_value.current_service_create_txid;
if (consensus != nullptr)
{
out.current_view = consensus->get_view();
@ -1346,7 +1348,7 @@ namespace ccf
"Service is already created.");
}
g.create_service(in.service_cert, recovering);
g.create_service(in.service_cert, in.create_txid, recovering);
// Retire all nodes, in case there are any (i.e. post recovery)
g.retire_active_nodes();

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

@ -72,7 +72,8 @@ namespace ccf
quote_info,
public_encryption_key,
code_digest,
node_info_network)
node_info_network,
create_txid)
DECLARE_JSON_OPTIONAL_FIELDS(
CreateNetworkNodeToNode::In, genesis_info, node_data)
@ -89,7 +90,8 @@ namespace ccf
service_certificate,
current_view,
primary_id,
recovery_count)
recovery_count,
current_service_create_txid)
DECLARE_JSON_TYPE(GetNode::NodeInfo)
DECLARE_JSON_REQUIRED_FIELDS(

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

@ -500,7 +500,7 @@ void prepare_callers(NetworkState& network)
init_network(network);
GenesisGenerator g(network, tx);
g.create_service(network.identity->cert);
g.create_service(network.identity->cert, ccf::TxID{});
user_id = g.add_user({user_caller});
member_id = g.add_member(member_cert);
invalid_member_id = g.add_member(invalid_caller);

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

@ -118,7 +118,7 @@ TEST_CASE("Add a node to an opening service")
check_error_message(response, "No service is available to accept new node");
}
gen.create_service(network.identity->cert);
gen.create_service(network.identity->cert, ccf::TxID{});
REQUIRE(gen_tx.commit() == kv::CommitResult::SUCCESS);
auto tx = network.tables->create_tx();
@ -219,7 +219,7 @@ TEST_CASE("Add a node to an open service")
network.ledger_secrets->set_secret(
up_to_ledger_secret_seqno, make_ledger_secret());
gen.create_service(network.identity->cert);
gen.create_service(network.identity->cert, ccf::TxID{});
gen.init_configuration({1});
gen.activate_member(gen.add_member(
{member_cert, crypto::make_rsa_key_pair()->public_key_pem()}));

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

@ -24,7 +24,7 @@ DOCTEST_TEST_CASE("Unique proposal ids")
init_network(network);
auto gen_tx = network.tables->create_tx();
GenesisGenerator gen(network, gen_tx);
gen.create_service(network.identity->cert);
gen.create_service(network.identity->cert, ccf::TxID{});
const auto proposer_cert = get_cert(0, kp);
const auto proposer_id = gen.add_member(proposer_cert);
@ -144,7 +144,7 @@ DOCTEST_TEST_CASE("Compaction conflict")
network.tables->set_consensus(consensus);
auto gen_tx = network.tables->create_tx();
GenesisGenerator gen(network, gen_tx);
gen.create_service(network.identity->cert);
gen.create_service(network.identity->cert, ccf::TxID{});
const auto proposer_cert = get_cert(0, kp);
const auto proposer_id = gen.add_member(proposer_cert);

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

@ -288,7 +288,9 @@ namespace ccf
// Service status should use a state machine, very much like NodeState.
void create_service(
const crypto::Pem& service_cert, bool recovering = false)
const crypto::Pem& service_cert,
ccf::TxID create_txid,
bool recovering = false)
{
auto service = tx.rw(tables.service);
@ -311,7 +313,8 @@ namespace ccf
{service_cert,
recovering ? ServiceStatus::RECOVERING : ServiceStatus::OPENING,
recovering ? service->get_version_of_previous_write() : std::nullopt,
recovery_count});
recovery_count,
create_txid});
}
bool is_service_created(const crypto::Pem& expected_service_cert)

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

@ -710,7 +710,6 @@ class Consortium:
r = c.get("/node/network").body.json()
current_status = r["service_status"]
current_cert = r["service_certificate"]
# Note: to change once this is backported to 2.x
if remote_node.version_after("ccf-2.0.4"):
current_recovery_count = r["recovery_count"]
else:

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

@ -51,6 +51,7 @@ def test_recover_service(network, args, from_snapshot=True):
with old_primary.client() as c:
r = c.get("/node/service/previous_identity")
assert r.status_code in (200, 404), r.status_code
prev_view = c.get("/node/network").body.json()["current_view"]
snapshots_dir = None
if from_snapshot:
@ -99,6 +100,16 @@ def test_recover_service(network, args, from_snapshot=True):
recovered_network.recover(args)
LOG.info("Check that new service view is as expected")
new_primary, _ = recovered_network.find_primary()
with new_primary.client() as c:
assert (
ccf.tx_id.TxID.from_str(
c.get("/node/network").body.json()["current_service_create_txid"]
).view
== prev_view + 2
)
return recovered_network
@ -553,6 +564,14 @@ def run(args):
txs=txs,
) as network:
network.start_and_open(args)
primary, _ = network.find_primary()
LOG.info("Check for well-known genesis service TxID")
with primary.client() as c:
r = c.get("/node/network").body.json()
assert ccf.tx_id.TxID.from_str(
r["current_service_create_txid"]
) == ccf.tx_id.TxID(2, 1)
if args.with_load:
# See https://github.com/microsoft/CCF/issues/3788 for justification