Implement proposal 167: Authorities vote on network parameters.

This code adds a new field to vote on: "params".  It consists of a list of
sorted key=int pairs.  The output is computed as the median of all the
integers for any key on which anybody voted.

Improved with input from Roger.
This commit is contained in:
Nick Mathewson 2009-09-14 22:15:57 -04:00
parent 0edc39303d
commit 381766ce4b
8 changed files with 237 additions and 4 deletions

View File

@ -1,4 +1,9 @@
Changes in version 0.2.2.2-alpha - 2009-09-??
o Major features:
- Authorities can now vote on arbitary integer values as part of the
consensus process. This is designed to help set network parameters.
Implements proposal 167.
o Minor bugfixes:
- Fix an extremely rare infinite recursion bug that could occur if
we tried to log a message after shutting down the log subsystem.

View File

@ -1098,6 +1098,20 @@
enough votes were counted for the consensus for an authoritative
opinion to have been formed about their status.
"params" SP [Parameters] NL
[At most once]
Parameter ::= Keyword '=' Int32
Int32 ::= A decimal integer between -2147483648 and 2147483647.
Parameters ::= Parameter | Parameters SP Parameter
The parameters list, if present, contains a space-separated list of
key-value pairs, sorted in lexical order by their keyword. Each
parameter has its own meaning.
(Only included when the vote is generated with consensus-method 7 or
later.)
The authority section of a vote contains the following items, followed
in turn by the authority's current key certificate:
@ -1406,6 +1420,10 @@
Known-flags is the union of all flags known by any voter.
Entries are given on the "params" line for every keyword on which any
authority voted. The values given are the low-median of all votes on
that keyword.
"client-versions" and "server-versions" are sorted in ascending
order; A version is recommended in the consensus if it is recommended
by more than half of the voting authorities that included a
@ -1473,6 +1491,9 @@
a router, the authorities produce a consensus containing a
Bandwidth= keyword equal to the median of the Measured= votes.
* If consensus-method 7 or later is in use, the params line is
included in the output.
The signatures at the end of a consensus document are sorted in
ascending order by identity digest.

View File

@ -117,6 +117,9 @@ typedef unsigned int uint32_t;
#ifndef INT32_MAX
#define INT32_MAX 0x7fffffffu
#endif
#ifndef INT32_MIN
#define INT32_MIN (-2147483647-1)
#endif
#endif
#if (SIZEOF_LONG == 4)

View File

@ -24,7 +24,9 @@ static int dirvote_publish_consensus(void);
static char *make_consensus_method_list(int low, int high);
/** The highest consensus method that we currently support. */
#define MAX_SUPPORTED_CONSENSUS_METHOD 6
#define MAX_SUPPORTED_CONSENSUS_METHOD 7
#define MIN_METHOD_FOR_PARAMS 7
/* =====
* Voting
@ -97,6 +99,7 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key,
char fu[ISO_TIME_LEN+1];
char vu[ISO_TIME_LEN+1];
char *flags = smartlist_join_strings(v3_ns->known_flags, " ", 0, NULL);
char *params;
authority_cert_t *cert = v3_ns->cert;
char *methods =
make_consensus_method_list(1, MAX_SUPPORTED_CONSENSUS_METHOD);
@ -105,6 +108,11 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key,
format_iso_time(fu, v3_ns->fresh_until);
format_iso_time(vu, v3_ns->valid_until);
if (v3_ns->net_params)
params = smartlist_join_strings(v3_ns->net_params, " ", 0, NULL);
else
params = tor_strdup("");
tor_assert(cert);
tor_snprintf(status, len,
"network-status-version 3\n"
@ -117,6 +125,7 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key,
"voting-delay %d %d\n"
"%s" /* versions */
"known-flags %s\n"
"params %s\n"
"dir-source %s %s %s %s %d %d\n"
"contact %s\n",
v3_ns->type == NS_TYPE_VOTE ? "vote" : "opinion",
@ -125,9 +134,11 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key,
v3_ns->vote_seconds, v3_ns->dist_seconds,
version_lines,
flags,
params,
voter->nickname, fingerprint, voter->address,
ipaddr, voter->dir_port, voter->or_port, voter->contact);
tor_free(params);
tor_free(flags);
tor_free(methods);
outp = status + strlen(status);
@ -507,6 +518,89 @@ compute_consensus_versions_list(smartlist_t *lst, int n_versioning)
return result;
}
/** Helper: given a list of valid networkstatus_t, return a new string
* containing the contents of the consensus network parameter set.
*/
/* private */ char *
dirvote_compute_params(smartlist_t *votes)
{
int i;
int32_t *vals;
int cur_param_len;
const char *cur_param;
const char *eq;
char *result;
const int n_votes = smartlist_len(votes);
smartlist_t *output;
smartlist_t *param_list = smartlist_create();
/* We require that the parameter lists in the votes are well-formed: that
is, that their keywords are unique and sorted, and that their values are
between INT32_MIN and INT32_MAX inclusive. This should be guaranteed by
the parsing code. */
vals = tor_malloc(sizeof(int)*n_votes);
SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
if (!v->net_params)
continue;
smartlist_add_all(param_list, v->net_params);
} SMARTLIST_FOREACH_END(v);
if (smartlist_len(param_list) == 0) {
tor_free(vals);
smartlist_free(param_list);
return NULL;
}
smartlist_sort_strings(param_list);
i = 0;
cur_param = smartlist_get(param_list, 0);
eq = strchr(cur_param, '=');
tor_assert(eq);
cur_param_len = eq+1 - cur_param;
output = smartlist_create();
SMARTLIST_FOREACH_BEGIN(param_list, const char *, param) {
const char *next_param;
int ok=0;
eq = strchr(param, '=');
tor_assert(i<n_votes);
vals[i++] = (int32_t)
tor_parse_long(eq+1, 10, INT32_MIN, INT32_MAX, &ok, NULL);
tor_assert(ok);
if (param_sl_idx+1 == smartlist_len(param_list))
next_param = NULL;
else
next_param = smartlist_get(param_list, param_sl_idx+1);
if (!next_param || strncmp(next_param, param, cur_param_len)) {
/* We've reached the end of a series. */
int32_t median = median_int32(vals, i);
char *out_string = tor_malloc(64+cur_param_len);
memcpy(out_string, param, cur_param_len);
tor_snprintf(out_string+cur_param_len,64, "%ld", (long)median);
smartlist_add(output, out_string);
i = 0;
if (next_param) {
eq = strchr(next_param, '=');
cur_param_len = eq+1 - next_param;
}
}
} SMARTLIST_FOREACH_END(param);
result = smartlist_join_strings(output, " ", 0, NULL);
SMARTLIST_FOREACH(output, char *, cp, tor_free(cp));
smartlist_free(output);
smartlist_free(param_list);
tor_free(vals);
return result;
}
/** Given a list of vote networkstatus_t in <b>votes</b>, our public
* authority <b>identity_key</b>, our private authority <b>signing_key</b>,
* and the number of <b>total_authorities</b> that we believe exist in our
@ -659,6 +753,15 @@ networkstatus_compute_consensus(smartlist_t *votes,
tor_free(flaglist);
}
if (consensus_method >= MIN_METHOD_FOR_PARAMS) {
char *params = dirvote_compute_params(votes);
if (params) {
smartlist_add(chunks, tor_strdup("params "));
smartlist_add(chunks, params);
smartlist_add(chunks, tor_strdup("\n"));
}
}
/* Sort the votes. */
smartlist_sort(votes, _compare_votes_by_authority_id);
/* Add the authority sections. */

View File

@ -286,6 +286,10 @@ networkstatus_vote_free(networkstatus_t *ns)
SMARTLIST_FOREACH(ns->known_flags, char *, c, tor_free(c));
smartlist_free(ns->known_flags);
}
if (ns->net_params) {
SMARTLIST_FOREACH(ns->net_params, char *, c, tor_free(c));
smartlist_free(ns->net_params);
}
if (ns->supported_methods) {
SMARTLIST_FOREACH(ns->supported_methods, char *, c, tor_free(c));
smartlist_free(ns->supported_methods);

View File

@ -1672,6 +1672,10 @@ typedef struct networkstatus_t {
* not listed here, the voter has no opinion on what its value should be. */
smartlist_t *known_flags;
/** List of key=value strings for the parameters in this vote or
* consensus, sorted by key. */
smartlist_t *net_params;
/** List of networkstatus_voter_info_t. For a vote, only one element
* is included. For a consensus, one element is included for every voter
* whose vote contributed to the consensus. */
@ -3661,9 +3665,9 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_env_t *private_key,
authority_cert_t *cert);
#ifdef DIRVOTE_PRIVATE
char *
format_networkstatus_vote(crypto_pk_env_t *private_key,
networkstatus_t *v3_ns);
char *format_networkstatus_vote(crypto_pk_env_t *private_key,
networkstatus_t *v3_ns);
char *dirvote_compute_params(smartlist_t *votes);
#endif
/********************************* dns.c ***************************/

View File

@ -102,6 +102,7 @@ typedef enum {
K_VOTING_DELAY,
K_KNOWN_FLAGS,
K_PARAMS,
K_VOTE_DIGEST,
K_CONSENSUS_DIGEST,
K_CONSENSUS_METHODS,
@ -433,6 +434,7 @@ static token_rule_t networkstatus_token_table[] = {
T1("valid-until", K_VALID_UNTIL, CONCAT_ARGS, NO_OBJ ),
T1("voting-delay", K_VOTING_DELAY, GE(2), NO_OBJ ),
T1("known-flags", K_KNOWN_FLAGS, ARGS, NO_OBJ ),
T01("params", K_PARAMS, ARGS, NO_OBJ ),
T( "fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ),
CERTIFICATE_MEMBERS
@ -470,6 +472,7 @@ static token_rule_t networkstatus_consensus_token_table[] = {
T01("client-versions", K_CLIENT_VERSIONS, CONCAT_ARGS, NO_OBJ ),
T01("server-versions", K_SERVER_VERSIONS, CONCAT_ARGS, NO_OBJ ),
T01("consensus-method", K_CONSENSUS_METHOD, EQ(1), NO_OBJ),
T01("params", K_PARAMS, ARGS, NO_OBJ ),
END_OF_TABLE
};
@ -2408,6 +2411,34 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
goto err;
}
tok = find_opt_by_keyword(tokens, K_PARAMS);
if (tok) {
inorder = 1;
ns->net_params = smartlist_create();
for (i = 0; i < tok->n_args; ++i) {
int ok=0;
char *eq = strchr(tok->args[i], '=');
if (!eq) {
log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i]));
goto err;
}
tor_parse_long(eq+1, 10, INT32_MIN, INT32_MAX, &ok, NULL);
if (!ok) {
log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i]));
goto err;
}
if (i > 0 && strcmp(tok->args[i-1], tok->args[i]) >= 0) {
log_warn(LD_DIR, "%s >= %s", tok->args[i-1], tok->args[i]);
inorder = 0;
}
smartlist_add(ns->net_params, tor_strdup(tok->args[i]));
}
if (!inorder) {
log_warn(LD_DIR, "params not in order");
goto err;
}
}
ns->voters = smartlist_create();
SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, _tok) {

View File

@ -3355,6 +3355,54 @@ done:
return;
}
static void
test_dirutil_param_voting(void)
{
networkstatus_t vote1, vote2, vote3, vote4;
smartlist_t *votes = smartlist_create();
char *res = NULL;
/* dirvote_compute_params only looks at the net_params field of the votes,
so that's all we need to set.
*/
memset(&vote1, 0, sizeof(vote1));
memset(&vote2, 0, sizeof(vote2));
memset(&vote3, 0, sizeof(vote3));
memset(&vote4, 0, sizeof(vote4));
vote1.net_params = smartlist_create();
vote2.net_params = smartlist_create();
vote3.net_params = smartlist_create();
vote4.net_params = smartlist_create();
smartlist_split_string(vote1.net_params,
"ab=90 abcd=20 cw=50 x-yz=-99", NULL, 0, 0);
smartlist_split_string(vote2.net_params,
"ab=27 cw=5 x-yz=88", NULL, 0, 0);
smartlist_split_string(vote3.net_params,
"abcd=20 c=60 cw=500 x-yz=-9 zzzzz=101", NULL, 0, 0);
smartlist_split_string(vote4.net_params,
"ab=900 abcd=200 c=1 cw=51 x-yz=100", NULL, 0, 0);
smartlist_add(votes, &vote1);
smartlist_add(votes, &vote2);
smartlist_add(votes, &vote3);
smartlist_add(votes, &vote4);
res = dirvote_compute_params(votes);
test_streq(res,
"ab=90 abcd=20 c=1 cw=50 x-yz=-9 zzzzz=101");
done:
tor_free(res);
SMARTLIST_FOREACH(vote1.net_params, char *, cp, tor_free(cp));
SMARTLIST_FOREACH(vote2.net_params, char *, cp, tor_free(cp));
SMARTLIST_FOREACH(vote3.net_params, char *, cp, tor_free(cp));
SMARTLIST_FOREACH(vote4.net_params, char *, cp, tor_free(cp));
smartlist_free(vote1.net_params);
smartlist_free(vote2.net_params);
smartlist_free(vote3.net_params);
smartlist_free(vote4.net_params);
}
extern const char AUTHORITY_CERT_1[];
extern const char AUTHORITY_SIGNKEY_1[];
extern const char AUTHORITY_CERT_2[];
@ -3512,6 +3560,9 @@ test_v3_networkstatus(void)
crypto_pk_get_digest(cert1->identity_key, voter->identity_digest);
smartlist_add(vote->voters, voter);
vote->cert = authority_cert_dup(cert1);
vote->net_params = smartlist_create();
smartlist_split_string(vote->net_params, "circuitwindow=101 foo=990",
NULL, 0, 0);
vote->routerstatus_list = smartlist_create();
/* add the first routerstatus. */
vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
@ -3653,6 +3704,9 @@ test_v3_networkstatus(void)
vote->dist_seconds = 300;
authority_cert_free(vote->cert);
vote->cert = authority_cert_dup(cert2);
vote->net_params = smartlist_create();
smartlist_split_string(vote->net_params, "bar=2000000000 circuitwindow=20",
NULL, 0, 0);
tor_free(vote->client_versions);
tor_free(vote->server_versions);
voter = smartlist_get(vote->voters, 0);
@ -3691,6 +3745,9 @@ test_v3_networkstatus(void)
vote->dist_seconds = 250;
authority_cert_free(vote->cert);
vote->cert = authority_cert_dup(cert3);
vote->net_params = smartlist_create();
smartlist_split_string(vote->net_params, "circuitwindow=80 foo=660",
NULL, 0, 0);
smartlist_add(vote->supported_methods, tor_strdup("4"));
vote->client_versions = tor_strdup("0.1.2.14,0.1.2.17");
vote->server_versions = tor_strdup("0.1.2.10,0.1.2.15,0.1.2.16");
@ -3747,6 +3804,10 @@ test_v3_networkstatus(void)
test_streq(cp, "Authority:Exit:Fast:Guard:MadeOfCheese:MadeOfTin:"
"Running:Stable:V2Dir:Valid");
tor_free(cp);
cp = smartlist_join_strings(con->net_params, ":", 0, NULL);
test_streq(cp, "bar=2000000000:circuitwindow=80:foo=660");
tor_free(cp);
test_eq(4, smartlist_len(con->voters)); /*3 voters, 1 legacy key.*/
/* The voter id digests should be in this order. */
test_assert(memcmp(cert2->cache_info.identity_digest,
@ -4866,6 +4927,7 @@ static struct {
ENT(dir_format),
ENT(dirutil),
SUBENT(dirutil, measured_bw),
SUBENT(dirutil, param_voting),
ENT(v3_networkstatus),
ENT(policies),
ENT(rend_fns),