Make implicit proposer vote explicit (#339)

* Proposer sends ballot

* Remove unnecessary error log line

* Add test of proposer initially voting against

* Throw errors, not strings

* Update memberclient documentation

* --standardize-long-options

* Remove final references to nodes.json

* Modified propose_params

* Python wrappers use --updated-option-names

* --member-cert

* '=' != ' '
This commit is contained in:
Eddy Ashton 2019-08-29 17:16:08 +01:00 коммит произвёл GitHub
Родитель 65bde18079
Коммит c9b0f7ef5f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 216 добавлений и 114 удалений

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

@ -20,7 +20,7 @@ For example, ``member1`` may submit a proposal to add a new member (``member4``)
.. code-block:: bash
$ memberclient add_member --ca=networkcert.pem --member_cert=member4_cert.pem --cert=member1_cert.pem --privk=member1_privk.pem --host=10.1.0.4 --port=25000
$ memberclient --server-address 127.83.203.69:55526 --cert member1_cert.pem --privk member1_privk.pem --ca networkcert.pem add_member --member-cert member4_cert.pem
{"commit":100,"global_commit":99,"id":0,"jsonrpc":"2.0","result":{"completed":false,"id":1},"term":2}
In this case, a new proposal with id ``1`` has successfully been created and the proposer member has automatically accepted it. Other members can then accept or reject the proposal:
@ -30,12 +30,12 @@ In this case, a new proposal with id ``1`` has successfully been created and the
// Proposal 1 is already created by member 1 (votes: 1/3)
// Member 2 rejects the proposal (votes: 1/3)
$ memberclient vote --reject --id=1 --cert=member2_cert.pem --privk=member2_privk.pem --host=10.1.0.4 --port=25000 --ca=networkcert.pem --sign
$ memberclient --server-address 127.83.203.69:55526 --cert member2_cert.pem --privk member2_privk.pem --ca networkcert.pem vote --reject --proposal-id 1
{"commit":104,"global_commit":103,"id":0,"jsonrpc":"2.0","result":false,"term":2}
// Member 3 accepts the proposal (votes: 2/3)
// As a quorum of members have accepted the proposal, member4 is added to the consortium
$ memberclient vote --accept --id=1 --cert=member3_cert.pem --privk=member3_privk.pem --host=10.1.0.4 --port=25000 --ca=networkcert.pem --sign
$ memberclient --server-address 127.83.203.69:55526 --cert member3_cert.pem --privk member3_privk.pem --ca networkcert.pem vote --accept --proposal-id 1
{"commit":106,"global_commit":105,"id":0,"jsonrpc":"2.0","result":true,"term":2}
As soon as ``member3`` accepts the proposal, a quorum (2 out of 3) of members has been reached and the proposal completes, successfully adding ``member4``.
@ -44,34 +44,50 @@ As soon as ``member3`` accepts the proposal, a quorum (2 out of 3) of members ha
.. code-block:: bash
$ ../build/memberclient ack --cert=member4_cert.pem --privk=member4_privk.pem --host=10.1.0.4 --port=25000 --ca=networkcert.pem --sign
$ memberclient --server-address 127.83.203.69:55526 --cert member4_cert.pem --privk member4_privk.pem --ca networkcert.pem ack
{"commit":108,"global_commit":107,"id":2,"jsonrpc":"2.0","result":true,"term":2}
Displaying proposals
--------------------
The details of pending proposals, including the proposer member ID, proposal script, parameters and votes, can be displayed with the ``proposal_display`` option of the ``memberclient`` utility. For example:
The details of pending proposals, including the proposer member ID, proposal script, parameters and votes, can be displayed with the ``proposal_display`` sub command of the ``memberclient`` utility. For example:
.. code-block:: bash
$ memberclient proposal_display --cert=member1_cert.pem --privk=member1_privk.pem --host=10.1.0.4 --port=25000 --ca=networkcert.pem
------ Proposal ------
-- Proposal id: 1
-- Proposer id: 0
-- Script: {"text":"\n tables, member_cert = ...\n return Calls:call(\"new_member\", member_cert)\n "}
-- Parameter: [<member_cert>]
-- Votes: [[1,{"text":"\n tables, changes = ...\n return false"}]]
----------------------
$ memberclient --server-address 127.83.203.69:55526 --cert member1_cert.pem --privk member1_privk.pem --ca networkcert.pem proposal_display
{
"1": {
"parameter": [...],
"proposer": 0,
"script": {
"text": "tables, member_cert = ...\n return Calls:call(\"new_member\", member_cert)"
},
"votes": [
[
0,
{
"text": "return true"
}
],
[
1,
{
"text": "return false"
}
]
]
}
}
In this case, there is one pending proposal (``id`` is 1), proposed by the first member (``member1``, ``id`` is 0) and which will call the ``new_member`` function with the new member's certificate as a parameter. Only one vote has been cast by ``member2`` (``id`` is 1) to reject the proposal while ``member1`` (proposer) has already implicitly accepted it.
In this case, there is one pending proposal (``id`` is 1), proposed by the first member (``member1``, ``id`` is 0) and which will call the ``accept_node`` function with the new member's certificate as a parameter. Two votes have been cast: ``member1`` (proposer) has voted for the proposal, while ``member2`` (``id`` is 1) has voted against it.
Removing proposals
------------------
At any stage during the voting process and before the proposal is completed, the proposer member may decide to remove a pending proposal:
At any stage during the voting process and before the proposal is completed, the proposing member may decide to remove a pending proposal:
.. code-block:: bash
$ memberclient removal --id=1 --cert=member1_cert.pem --privk=member1_privk.pem --host=10.1.0.4 --port=25000 --ca=networkcert.pem --sign
$ memberclient --server-address 127.83.203.69:55526 --cert member1_cert.pem --privk member1_privk.pem --ca networkcert.pem removal --proposal-id 0
{"commit":110,"global_commit":109,"id":0,"jsonrpc":"2.0","result":true,"term":4}

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

@ -22,11 +22,11 @@ To initiate the first phase of the recovery protocol, one or several nodes shoul
.. code-block:: bash
$ cchost --enclave-file=/path/to/enclave_library --enclave-type=debug --node-address=node_ip:node_port
--rpc-address=rpc_ip:rpc_port --public-rpc-address=public_rpc_ip:public_rpc_port
--ledger-file=/path/to/ledger/to/recover
--node-cert-file=/path/to/node_certificate --quote-file=/path/to/quote
recover --network-cert-file=/path/to/network_certificate
$ cchost --enclave-file /path/to/enclave_library --enclave-type debug --node-address node_ip:node_port
--rpc-address rpc_ip:rpc_port --public-rpc-address public_rpc_ip:public_rpc_port
--ledger-file /path/to/ledger/to/recover
--node-cert-file /path/to/node_certificate --quote-file /path/to/quote
recover --network-cert-file /path/to/network_certificate
Each node will then immediately restore the public entries of its ledger (``--ledger-file``). Because deserialising the public entries present in the ledger may take some time, operators can query the progress of the public recovery by running the ``getSignedIndex`` JSON-RPC which returns the version of the last signed recovered ledger entry. Once the public ledger is fully recovered, the recovered node automatically becomes part of the public network, allowing other nodes to join the network.
@ -67,17 +67,17 @@ Once the public crash-fault tolerant network is established, members are allowed
.. code-block:: bash
$ memberclient --cert=/path/to/member1/certificate --privk=/path/to/member1/private/key
--rpc-address=node2_rpc_ip:node2_rpc_port --ca=/path/to/new/network/certificate
accept_recovery --sealed-secrets=/path/to/sealed/secrets/file
$ memberclient --cert /path/to/member1/certificate --privk /path/to/member1/private/key
--rpc-address node2_rpc_ip:node2_rpc_port --ca /path/to/new/network/certificate
accept_recovery --sealed-secrets /path/to/sealed/secrets/file
If successful, this commands returns the proposal id that can be used by other members to submit their votes:
.. code-block:: bash
$ memberclient --cert=/path/to/member2/certificate --privk=/path/to/member2/private/key
--rpc-address=node2_rpc_ip:node2_rpc_port --ca=/path/to/new/network/certificate
vote --accept --proposal-id=proposal_id
$ memberclient --cert /path/to/member2/certificate --privk /path/to/member2/private/key
--rpc-address node2_rpc_ip:node2_rpc_port --ca /path/to/new/network/certificate
vote --accept --proposal-id proposal_id
Once a :term:`quorum` of members have agreed to recover the network, the network secrets are unsealed and each node begins recovery of the private ledger entries.

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

@ -1,6 +1,22 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"ballot": {
"properties": {
"bytecode": {
"items": {
"maximum": 255,
"minimum": 0,
"type": "number"
},
"type": "array"
},
"text": {
"type": "string"
}
},
"type": "object"
},
"parameter": {},
"script": {
"properties": {

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

@ -8,24 +8,24 @@ To start up a network, the first node of the network should be started with the
.. code-block:: bash
$ cchost --enclave-file=/path/to/enclave_library --enclave-type=debug
--node-address=node_ip:node_port --rpc-address=rpc_ip:rpc_port
--public-rpc-address=public_rpc_ip:public_rpc_port --ledger-file=/path/to/ledger
--node-cert-file=/path/to/node_certificate --quote-file=/path/to/quote
start --network-cert-file=/path/to/network_certificate --gov-script=/path/to/lua/governance_script
--member-certs=member_certificates_glob --user-certs=user_certificates_glob
$ cchost --enclave-file /path/to/enclave_library --enclave-type debug
--node-address node_ip:node_port --rpc-address rpc_ip:rpc_port
--public-rpc-address public_rpc_ip:public_rpc_port --ledger-file /path/to/ledger
--node-cert-file /path/to/node_certificate --quote-file /path/to/quote
start --network-cert-file /path/to/network_certificate --gov-script /path/to/lua/governance_script
--member-certs member_certificates_glob --user-certs user_certificates_glob
When starting up, the node generates its own key pair and outputs the certificate associated with its public key at the location specified by ``--node-cert-file``. A quote file, required for remote attestation, is also output at the location specified by ``--quote-file``. The certificate of the freshly-created CCF network is also output at the location specified by ``--network-cert-file``.
.. note:: The network certificate should be used by users and members as the certificate authority (CA) when establishing a TLS connection with any of the nodes part of the CCF network. For the ``client`` and ``memberclient`` utilities, ``--ca=/path/to/network_certificate`` should always be specified.
.. note:: The network certificate should be used by users and members as the certificate authority (CA) when establishing a TLS connection with any of the nodes part of the CCF network. For the ``client`` and ``memberclient`` utilities, ``--ca /path/to/network_certificate`` should always be specified.
The :ref:`governance` rules are defined as a Lua script passed via the ``--gov-script`` option. For example, a default set of `governance rules <https://github.com/microsoft/CCF/blob/master/src/runtime_config/gov.lua>`_ can be used to define a majority of members as the :term:`quorum` of the consortium.
The identities of members and users are specified as `glob patterns <https://en.wikipedia.org/wiki/Glob_(programming)>`_ via the ``--member-certs`` and ``--user-certs`` option, respectively. For example, if 2 members (``member1_cert.pem`` and ``member2_cert.pem``) and 3 users (``user1_cert.pem``, ``user2_cert.pem`` and ``user3_cert.pem``) should be added to CCF, operators should specify ``--member-certs=member*_cert.pem`` and ``--user-certs=user*_cert.pem``.
The identities of members and users are specified as `glob patterns <https://en.wikipedia.org/wiki/Glob_(programming)>`_ via the ``--member-certs`` and ``--user-certs`` option, respectively. For example, if 2 members (``member1_cert.pem`` and ``member2_cert.pem``) and 3 users (``user1_cert.pem``, ``user2_cert.pem`` and ``user3_cert.pem``) should be added to CCF, operators should specify ``--member-certs member*_cert.pem`` and ``--user-certs user*_cert.pem``.
.. note:: Once a CCF network is started, members can add other members and users via governance. See :ref:`Submitting a new proposal` for more information.
When CCF is used to run a custom Lua application, the starting node should also be started with the ``--app-script=/path/to/lua/application_script`` (see the `samples folder <https://github.com/microsoft/CCF/tree/master/samples/apps>`_ for example of Lua applications).
When CCF is used to run a custom Lua application, the starting node should also be started with the ``--app-script /path/to/lua/application_script`` (see the `samples folder <https://github.com/microsoft/CCF/tree/master/samples/apps>`_ for example of Lua applications).
Joining an existing network
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -34,11 +34,11 @@ To join an existing network, other nodes should be started with the ``join`` opt
.. code-block:: bash
$ cchost --enclave-file=/path/to/enclave_library --enclave-type=debug
--node-address=node_ip:node_port --rpc-address=rpc_ip:rpc_port
--public-rpc-address=public_rpc_ip:public_rpc_port --ledger-file=/path/to/ledger
--node-cert-file=/path/to/node_certificate --quote-file=/path/to/quote
join --network-cert-file=/path/to/existing/network_certificate --target-rpc-address=target_rpc_ip:target_rpc_port
$ cchost --enclave-file /path/to/enclave_library --enclave-type debug
--node-address node_ip:node_port --rpc-address rpc_ip:rpc_port
--public-rpc-address public_rpc_ip:public_rpc_port --ledger-file /path/to/ledger
--node-cert-file /path/to/node_certificate --quote-file /path/to/quote
join --network-cert-file /path/to/existing/network_certificate --target-rpc-address target_rpc_ip:target_rpc_port
The node takes the certificate of the existing network to join via ``--network-cert-file`` and initiates an enclave-to-enclave TLS connection to an existing node of the network as specified by ``--target-rpc-address``. Once the join protocol [#remote_attestation]_ completes, the joining node becomes part of the network as a backup (see :ref:`Ledger replication` for more details on consensus protocols).

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

@ -166,12 +166,6 @@ int main(int argc, char** argv)
app.require_subcommand(1, 1);
std::string nodes_file = "nodes.json";
size_t node_index = 0;
auto nodes_opt = app.add_option("--nodes", nodes_file, "Nodes file", true);
app.add_option(
"--host-node-index", node_index, "Index of host in nodes file", true);
bool pretty_print = false;
app.add_flag(
"--pretty-print",
@ -183,11 +177,10 @@ int main(int argc, char** argv)
cli::ParsedAddress server_address;
auto server_addr_opt = cli::add_address_option(
app,
server_address,
"--rpc-address",
"Remote node JSON-RPC server address")
->excludes(nodes_opt);
app,
server_address,
"--rpc-address",
"Remote node JSON-RPC server address");
app.add_option("--ca", ca_file, "Network CA", true);
auto member_rpc = app.add_subcommand("memberrpc", "Member RPC");
@ -222,34 +215,8 @@ int main(int argc, char** argv)
try
{
// If host connection has not been set explicitly by options then load from
// nodes file
if (!*server_addr_opt)
{
const auto j_nodes = files::slurp_json(nodes_file);
if (!j_nodes.is_array())
{
throw logic_error("Expected " + nodes_file + " to contain array");
}
if (node_index >= j_nodes.size())
{
throw logic_error(
"Expected node data at index " + to_string(node_index) + ", but " +
nodes_file + " defines only " + to_string(j_nodes.size()) + " files");
}
const auto& j_node = j_nodes[node_index];
host = j_node["pubhost"];
port = j_node["rpcport"];
}
else
{
host = server_address.hostname;
port = server_address.port;
}
host = server_address.hostname;
port = server_address.port;
nlohmann::json response;

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

@ -288,13 +288,13 @@ int main(int argc, char** argv)
auto add_member = app.add_subcommand("add_member", "Add a new member");
string member_cert_file;
add_member
->add_option("--member_cert", member_cert_file, "New member certificate")
->add_option("--member-cert", member_cert_file, "New member certificate")
->required(true)
->check(CLI::ExistingFile);
auto add_user = app.add_subcommand("add_user", "Add a new user");
string user_cert_file;
add_user->add_option("--user_cert", user_cert_file, "New user certificate")
add_user->add_option("--user-cert", user_cert_file, "New user certificate")
->required(true)
->check(CLI::ExistingFile);
@ -327,14 +327,14 @@ int main(int argc, char** argv)
auto add_node = app.add_subcommand("add_node", "Add a node");
add_node
->add_option(
"--nodes_to_add", nodes_file, "The file containing the nodes to be added")
"--nodes-to-add", nodes_file, "The file containing the nodes to be added")
->required(true);
std::string new_code_id;
auto add_code = app.add_subcommand("add_code", "Support executing new code");
add_code
->add_option(
"--new_code_id",
"--new-code-id",
new_code_id,
"The new code id (a 64 character string representing a 32 byte hash "
"value in hex format)")
@ -361,7 +361,8 @@ int main(int argc, char** argv)
->check(CLI::ExistingFile);
auto removal = app.add_subcommand("removal", "Remove a proposal");
removal->add_option("--id", proposal_id, "The proposal id")->required(true);
removal->add_option("--proposal-id", proposal_id, "The proposal id")
->required(true);
auto accept_recovery =
app.add_subcommand("accept_recovery", "Accept to recover network");

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

@ -66,12 +66,12 @@ public:
auto err = mbedtls_ctr_drbg_seed(
&ctr_drbg, mbedtls_entropy_func, &entropy, nullptr, 0);
if (err)
throw tls::error_string(err);
throw std::logic_error(tls::error_string(err));
err = mbedtls_net_connect(
&server_fd, host.c_str(), port.c_str(), MBEDTLS_NET_PROTO_TCP);
if (err)
throw tls::error_string(err);
throw std::logic_error(tls::error_string(err));
err = mbedtls_ssl_config_defaults(
&conf,
@ -79,7 +79,7 @@ public:
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT);
if (err)
throw tls::error_string(err);
throw std::logic_error(tls::error_string(err));
if (cert != nullptr)
cert->use(&ssl, &conf);
@ -92,11 +92,11 @@ public:
err = mbedtls_ssl_setup(&ssl, &conf);
if (err)
throw tls::error_string(err);
throw std::logic_error(tls::error_string(err));
err = mbedtls_ssl_set_hostname(&ssl, sni.c_str());
if (err)
throw tls::error_string(err);
throw std::logic_error(tls::error_string(err));
mbedtls_ssl_set_bio(
&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, nullptr);
@ -109,7 +109,7 @@ public:
if (
(err != MBEDTLS_ERR_SSL_WANT_READ) &&
(err != MBEDTLS_ERR_SSL_WANT_WRITE))
throw tls::error_string(err);
throw std::logic_error(tls::error_string(err));
}
}
@ -135,7 +135,7 @@ public:
if (ret > 0)
written += ret;
else
throw tls::error_string(ret);
throw std::logic_error(tls::error_string(ret));
}
}
@ -149,7 +149,7 @@ public:
else if (ret == 0)
throw std::logic_error("Underlying transport closed");
else
throw tls::error_string(ret);
throw std::logic_error(tls::error_string(ret));
}
}

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

@ -57,6 +57,8 @@ namespace ccf
Script script;
//! fixed parameter for the script
nlohmann::json parameter;
//! vote ballot of proposer
Script ballot = {"return true"};
};
//! results from propose RPC
@ -68,8 +70,9 @@ namespace ccf
bool completed;
};
};
DECLARE_JSON_TYPE(Proposal::In)
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(Proposal::In)
DECLARE_JSON_REQUIRED_FIELDS(Proposal::In, script, parameter)
DECLARE_JSON_OPTIONAL_FIELDS(Proposal::In, ballot)
DECLARE_JSON_TYPE(Proposal::Out)
DECLARE_JSON_REQUIRED_FIELDS(Proposal::Out, id, completed)

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

@ -128,23 +128,15 @@ namespace ccf
proposed_calls);
/* count the votes
* if the proposer hasn't explicitly voted and is still active,
* an implicit pro vote is assumed.
*/
bool explicit_proposer_vote = false;
uint64_t pro = 0, con = 0;
const uint64_t total = proposal->votes.size();
for (const auto& vote : proposal->votes)
{
// can the proposal still succeed? If we haven't seen the proposer's
// vote yet, assume it to be pro.
if (total - con + (explicit_proposer_vote ? 0 : 1) < quorum)
// can the proposal still succeed?
if (total - con < quorum)
return false;
// is this an explicit proposer vote?
if (vote.first == proposal->proposer)
explicit_proposer_vote = true;
// valid voter
if (!check_member_active(tx, vote.first))
continue;
@ -161,9 +153,6 @@ namespace ccf
else
con++;
}
if (
!explicit_proposer_vote && check_member_active(tx, proposal->proposer))
pro++;
if (pro < quorum)
return false;
@ -284,8 +273,10 @@ namespace ccf
const auto in = args.params.get<Proposal::In>();
const auto proposal_id = get_next_id(
args.tx.get_view(this->network.values), ValueIds::NEXT_PROPOSAL_ID);
const OpenProposal proposal(in.script, in.parameter, args.caller_id);
args.tx.get_view(this->network.proposals)->put(proposal_id, proposal);
OpenProposal proposal(in.script, in.parameter, args.caller_id);
auto proposals = args.tx.get_view(this->network.proposals);
proposal.votes[args.caller_id] = in.ballot;
proposals->put(proposal_id, proposal);
const bool completed = complete_proposal(args.tx, proposal_id);
return jsonrpc::success<Proposal::Out>({proposal_id, completed});
};

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

@ -283,6 +283,108 @@ TEST_CASE("Member query/read")
}
}
TEST_CASE("Proposer ballot")
{
NetworkTables network;
GenesisGenerator gen(network);
gen.init_values();
const auto proposer_cert = get_cert_data(0, kp);
const auto proposer_id = gen.add_member(proposer_cert, MemberStatus::ACTIVE);
const auto voter_cert = get_cert_data(1, kp);
const auto voter_id = gen.add_member(voter_cert, MemberStatus::ACTIVE);
set_whitelists(gen);
gen.set_gov_scripts(lua::Interpreter().invoke<json>(gov_script_file));
gen.finalize();
StubNodeState node;
MemberCallRpcFrontend frontend(network, node);
size_t proposal_id;
const ccf::Script vote_for("return true");
const ccf::Script vote_against("return false");
{
INFO("Propose, initially voting against");
const auto proposed_member = get_cert_data(2, kp);
Script proposal(R"xxx(
tables, member_cert = ...
return Calls:call("new_member", member_cert)
)xxx");
const auto proposej = create_json_req(
Proposal::In{proposal, proposed_member, vote_against}, "propose");
enclave::RPCContext rpc_ctx(proposer_id, proposer_cert);
Store::Tx tx;
ccf::SignedReq sr(proposej);
Response<Proposal::Out> r =
frontend.process_json(rpc_ctx, tx, proposer_id, proposej, sr).value();
// the proposal should be accepted, but not succeed immediately
CHECK(r.result.completed == false);
proposal_id = r.result.id;
}
{
INFO("Second member votes for proposal");
const auto votej =
create_json_req_signed(Vote{proposal_id, vote_for}, "vote", kp);
Store::Tx tx;
enclave::RPCContext rpc_ctx(voter_id, voter_cert);
ccf::SignedReq sr(votej);
Response<bool> r =
frontend.process_json(rpc_ctx, tx, voter_id, votej["req"], sr).value();
// The vote should not yet succeed
CHECK(r.result == false);
}
{
INFO("Read current votes");
const auto readj = create_json_req_signed(
read_params(proposal_id, Tables::PROPOSALS), "read", kp);
Store::Tx tx;
enclave::RPCContext rpc_ctx(proposer_id, proposer_cert);
const Response<OpenProposal> proposal =
get_proposal(rpc_ctx, frontend, proposal_id, proposer_id);
const auto& votes = proposal.result.votes;
CHECK(votes.size() == 2);
const auto proposer_vote = votes.find(proposer_id);
CHECK(proposer_vote != votes.end());
CHECK(proposer_vote->second == vote_against);
const auto voter_vote = votes.find(voter_id);
CHECK(voter_vote != votes.end());
CHECK(voter_vote->second == vote_for);
}
{
INFO("Proposer votes for");
const auto votej =
create_json_req_signed(Vote{proposal_id, vote_for}, "vote", kp);
Store::Tx tx;
enclave::RPCContext rpc_ctx(proposer_id, proposer_cert);
ccf::SignedReq sr(votej);
Response<bool> r =
frontend.process_json(rpc_ctx, tx, proposer_id, votej["req"], sr).value();
// The vote should now succeed
CHECK(r.result == true);
}
}
struct NewMember
{
MemberId id;

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

@ -35,6 +35,11 @@ namespace ccf
return bytecode == other.bytecode && text == other.text;
}
bool operator!=(const Script& other) const
{
return !operator==(other);
}
MSGPACK_DEFINE(bytecode, text);
};

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

@ -350,7 +350,7 @@ class Network:
def propose_add_member(self, member_id, remote_node, new_member_cert):
return self.propose(
member_id, remote_node, "add_member", f"--member_cert={new_member_cert}"
member_id, remote_node, "add_member", f"--member-cert={new_member_cert}"
)
def stop_all_nodes(self):
@ -693,7 +693,6 @@ class Node:
)
def management_client(self, **kwargs):
LOG.error(f"{self.local_node_id}.pem")
return infra.jsonrpc.client(
self.host,
self.rpc_port,

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

@ -83,7 +83,7 @@ def run(args):
result = network.vote(1, primary, proposal_id, True)
assert result[0] and not result[1]
# result is true with just 2 votes because proposer implicit pro vote is assumed
# result is true with 3 votes (proposer, member 1, and member 2)
result = network.vote(2, primary, proposal_id, True)
assert result[0] and result[1]
@ -94,7 +94,9 @@ def run(args):
assert not result[1]["completed"]
assert proposal_id == 2
j_result = network.member_client_rpc_as_json(4, primary, "removal", "--id=2")
j_result = network.member_client_rpc_as_json(
4, primary, "removal", "--proposal-id=2"
)
assert j_result["result"]
# member 4 proposes to inactivate member 1 and other members vote yes
@ -122,7 +124,7 @@ def run(args):
assert result[1]["code"] == infra.jsonrpc.ErrorCode.INSUFFICIENT_RIGHTS.value
# member 4 proposes to add member 3 as user
result = network.propose(4, primary, "add_user", "--user_cert=member3_cert.pem")
result = network.propose(4, primary, "add_user", "--user-cert=member3_cert.pem")
assert not result[1]["completed"]