зеркало из https://github.com/microsoft/CCF.git
Report `TxID` at which current service was created (#3996)
This commit is contained in:
Родитель
4484e3b6d1
Коммит
7b2309047e
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче