OpenCBDC Transaction Processor
Loading...
Searching...
No Matches
uhs/atomizer/sentinel/controller.cpp
Go to the documentation of this file.
1// Copyright (c) 2021 MIT Digital Currency Initiative,
2// Federal Reserve Bank of Boston
3// Distributed under the MIT software license, see the accompanying
4// file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
6#include "controller.hpp"
7
10
11#include <random>
12#include <utility>
13
14namespace cbdc::sentinel {
15 controller::controller(uint32_t sentinel_id,
16 config::options opts,
17 std::shared_ptr<logging::log> logger)
18 : m_sentinel_id(sentinel_id),
19 m_opts(std::move(opts)),
20 m_logger(std::move(logger)) {}
21
22 auto controller::init() -> bool {
23 auto skey = m_opts.m_sentinel_private_keys.find(m_sentinel_id);
24 if(skey == m_opts.m_sentinel_private_keys.end()) {
25 m_logger->error("No private key specified");
26 return false;
27 }
28 m_privkey = skey->second;
29
30 auto pubkey = pubkey_from_privkey(m_privkey, m_secp.get());
31 m_logger->info("Sentinel public key:", cbdc::to_string(pubkey));
32
33 m_shard_data.reserve(m_opts.m_shard_endpoints.size());
34 for(size_t i{0}; i < m_opts.m_shard_endpoints.size(); i++) {
35 const auto& shard = m_opts.m_shard_endpoints[i];
36 m_logger->info("Connecting to",
37 shard.first,
38 ":",
39 shard.second,
40 "...");
41 auto sock = std::make_unique<network::tcp_socket>();
42 if(!sock->connect(shard)) {
43 m_logger->warn("failed to connect");
44 }
45
46 const auto peer_id = m_shard_network.add(std::move(sock));
47 const auto& shard_range = m_opts.m_shard_ranges[i];
48 m_shard_data.push_back(shard_info{shard_range, peer_id});
49
50 m_logger->info("done");
51 }
52
53 m_shard_dist = decltype(m_shard_dist)(0, m_shard_data.size() - 1);
54
55 for(const auto& ep : m_opts.m_sentinel_endpoints) {
56 if(ep == m_opts.m_sentinel_endpoints[m_sentinel_id]) {
57 continue;
58 }
59 auto client = std::make_unique<sentinel::rpc::client>(
60 std::vector<network::endpoint_t>{ep},
61 m_logger);
62 if(!client->init()) {
63 m_logger->error("Failed to start sentinel client");
64 return false;
65 }
66 m_sentinel_clients.emplace_back(std::move(client));
67 }
68
69 m_dist = decltype(m_dist)(0, m_sentinel_clients.size() - 1);
70
71 auto rpc_server = std::make_unique<
73 m_opts.m_sentinel_endpoints[m_sentinel_id]);
74 if(!rpc_server->init()) {
75 m_logger->error("Failed to start sentinel RPC server");
76 return false;
77 }
78
79 m_rpc_server = std::make_unique<decltype(m_rpc_server)::element_type>(
80 this,
81 std::move(rpc_server));
82
83 return true;
84 }
85
87 -> std::optional<cbdc::sentinel::execute_response> {
88 const auto res = transaction::validation::check_tx(tx);
90 if(res.has_value()) {
92 }
93
94 auto tx_id = transaction::tx_id(tx);
95
96 if(!res.has_value()) {
97 m_logger->debug("Accepted tx:", cbdc::to_string(tx_id));
98 } else {
99 m_logger->debug("Rejected tx:", cbdc::to_string(tx_id));
100 }
101
102 // Only forward transactions that are valid
103 if(!res.has_value()) {
104 send_transaction(tx);
105 }
106
107 return execute_response{status, res};
108 }
109
110 void controller::send_transaction(const transaction::full_tx& tx) {
111 auto compact_tx = cbdc::transaction::compact_tx(tx);
112 auto attestation = compact_tx.sign(m_secp.get(), m_privkey);
113 compact_tx.m_attestations.insert(attestation);
114
115 gather_attestations(tx, compact_tx, {});
116 }
117
119 -> std::optional<validate_response> {
120 const auto res = transaction::validation::check_tx(tx);
121 if(res.has_value()) {
122 return std::nullopt;
123 }
124 auto compact_tx = cbdc::transaction::compact_tx(tx);
125 auto attestation = compact_tx.sign(m_secp.get(), m_privkey);
126 return attestation;
127 }
128
129 void
130 controller::validate_result_handler(async_interface::validate_result v_res,
131 const transaction::full_tx& tx,
133 std::unordered_set<size_t> requested) {
134 if(!v_res.has_value()) {
135 m_logger->error(cbdc::to_string(ctx.m_id),
136 "invalid according to remote sentinel");
137 return;
138 }
139 ctx.m_attestations.insert(std::move(v_res.value()));
140 gather_attestations(tx, ctx, std::move(requested));
141 }
142
143 void
144 controller::gather_attestations(const transaction::full_tx& tx,
145 const transaction::compact_tx& ctx,
146 std::unordered_set<size_t> requested) {
147 if(ctx.m_attestations.size() < m_opts.m_attestation_threshold) {
148 auto success = false;
149 while(!success) {
150 auto sentinel_id = [&]() {
151 std::unique_lock l(m_rand_mut);
152 return m_dist(m_rand);
153 }();
154 if(requested.find(sentinel_id) != requested.end()) {
155 continue;
156 }
157 success
158 = m_sentinel_clients[sentinel_id]->validate_transaction(
159 tx,
160 [=, this](async_interface::validate_result v_res) {
161 auto r = requested;
162 r.insert(sentinel_id);
163 validate_result_handler(v_res, tx, ctx, r);
164 });
165 }
166 return;
167 }
168
169 send_compact_tx(ctx);
170 }
171
172 void controller::send_compact_tx(const transaction::compact_tx& ctx) {
173 auto ctx_pkt = std::make_shared<cbdc::buffer>(cbdc::make_buffer(ctx));
174
175 auto offset = [&]() {
176 std::unique_lock l(m_rand_mut);
177 return m_shard_dist(m_rand);
178 }();
179 auto inputs_sent = std::unordered_set<size_t>();
180 for(size_t i = 0; i < m_shard_data.size(); i++) {
181 auto idx = (i + offset) % m_shard_data.size();
182 const auto& range = m_shard_data[idx].m_range;
183 const auto& pid = m_shard_data[idx].m_peer_id;
184 if(inputs_sent.size() == ctx.m_inputs.size()) {
185 break;
186 }
187 if(!m_shard_network.connected(pid)) {
188 continue;
189 }
190 auto should_send = false;
191 for(size_t j = 0; j < ctx.m_inputs.size(); j++) {
192 if(inputs_sent.find(j) != inputs_sent.end()) {
193 continue;
194 }
195 if(!config::hash_in_shard_range(range, ctx.m_inputs[i])) {
196 continue;
197 }
198 inputs_sent.insert(j);
199 should_send = true;
200 }
201 if(should_send) {
202 m_shard_network.send(ctx_pkt, pid);
203 }
204 }
205 }
206}
External client for sending new transactions to the system.
auto init() -> bool
Initializes the client.
auto connected(peer_id_t peer_id) -> bool
Determines whether the given peer ID is connected.
void send(const std::shared_ptr< buffer > &data, peer_id_t peer_id)
Sends the provided data to the specified peer.
Implements an RPC server over a TCP socket.
std::optional< cbdc::sentinel::validate_response > validate_result
Result of a validation operation.
auto validate_transaction(transaction::full_tx tx) -> std::optional< validate_response > override
Validate transaction and generate a sentinel attestation if the transaction is valid.
auto init() -> bool
Initializes the controller.
auto execute_transaction(transaction::full_tx tx) -> std::optional< cbdc::sentinel::execute_response > override
Validate transaction, forward it to shards for processing, and return the validation result to send b...
auto hash_in_shard_range(const shard_range_t &range, const hash_t &val) -> bool
Checks if a hash is in the given range handled.
Definition config.cpp:736
tx_status
Status of the transaction following sentinel processing.
@ static_invalid
Statically invalid. Must be fixed and resubmitted.
@ pending
Statically valid, and the sentinel has submitted the transaction to the network for processing.
auto check_tx(const cbdc::transaction::full_tx &tx) -> std::optional< tx_error >
Runs static validation checks on the given transaction.
auto tx_id(const full_tx &tx) noexcept -> hash_t
Calculates the unique hash of a full transaction.
auto to_string(const hash_t &val) -> std::string
Converts a hash to a hexadecimal string.
auto make_buffer(const T &obj) -> std::enable_if_t< std::is_same_v< B, nuraft::ptr< nuraft::buffer > >, nuraft::ptr< nuraft::buffer > >
Serialize object into nuraft::buffer using a cbdc::nuraft_serializer.
auto pubkey_from_privkey(const privkey_t &privkey, secp256k1_context *ctx) -> pubkey_t
Generates a public key from the specified private key.
Definition keys.cpp:12
Project-wide configuration options.
Definition config.hpp:132
size_t m_attestation_threshold
Number of sentinel attestations needed for a compact transaction.
Definition config.hpp:260
Sentinel-specific representation of shard network information.
A condensed, hash-only transaction representation.
std::unordered_map< pubkey_t, signature_t, hashing::null > m_attestations
Signatures from sentinels attesting the compact TX is valid.
hash_t m_id
The hash of the full transaction returned by tx_id.
A complete transaction.