Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report TxID at which current service was created #3996

Merged
merged 8 commits into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 6 additions & 2 deletions doc/schemas/node_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@
},
"GetNetworkInfo__Out": {
"properties": {
"current_service_create_txid": {
"$ref": "#/components/schemas/TransactionId"
},
"current_view": {
"$ref": "#/components/schemas/uint64"
},
Expand All @@ -270,7 +273,8 @@
"service_certificate",
"current_view",
"primary_id",
"recovery_count"
"recovery_count",
"current_service_create_txid"
],
"type": "object"
},
Expand Down Expand Up @@ -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": {
Expand Down
10 changes: 8 additions & 2 deletions include/ccf/service/tables/service.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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
Expand Down
25 changes: 18 additions & 7 deletions src/node/node_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

//
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1107,14 +1112,16 @@ 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(
"End of public recovery transaction could not be committed");
}
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));
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions src/node/rpc/call_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
};

Expand Down
1 change: 1 addition & 0 deletions src/node/rpc/node_call_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions src/node/rpc/node_frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
6 changes: 4 additions & 2 deletions src/node/rpc/serialization.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/node/rpc/test/frontend_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/node/rpc/test/node_frontend_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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()}));
Expand Down
4 changes: 2 additions & 2 deletions src/node/rpc/test/proposal_id_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions src/service/genesis_gen.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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)
Expand Down
1 change: 0 additions & 1 deletion tests/infra/consortium.py
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,6 @@ def check_for_service(self, remote_node, status, recovery_count=None):
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:
Expand Down
19 changes: 19 additions & 0 deletions tests/recovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down