/******************************************************************************* * This file is part of the Incubed project. * Sources: https://github.com/blockchainsllc/in3 * * Copyright (C) 2018-2020 slock.it GmbH, Blockchains LLC * * * COMMERCIAL LICENSE USAGE * * Licensees holding a valid commercial license may use this file in accordance * with the commercial license agreement provided with the Software or, alternatively, * in accordance with the terms contained in a written agreement between you and * slock.it GmbH/Blockchains LLC. For licensing terms and conditions or further * information please contact slock.it at in3@slock.it. * * Alternatively, this file may be used under the AGPL license as follows: * * AGPL LICENSE USAGE * * This program is free software: you can redistribute it and/or modify it under the * terms of the GNU Affero General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. * [Permissions of this strong copyleft license are conditioned on making available * complete source code of licensed works and modifications, which include larger * works using a licensed work, under the same license. Copyright and license notices * must be preserved. Contributors provide an express grant of patent rights.] * You should have received a copy of the GNU Affero General Public License along * with this program. If not, see . *******************************************************************************/ /** * handles nodelists. * * */ #include "client.h" #include "request.h" #include "log.h" #include "mem.h" #include #ifndef NODELIST_H #define NODELIST_H #ifdef THREADSAFE #if defined(_MSC_VER) || defined(__MINGW32__) #include typedef HANDLE in3_mutex_t; #define MUTEX_INIT(mutex) mutex = CreateMutex(NULL, FALSE, NULL); #define MUTEX_LOCK(mutex) WaitForSingleObject(mutex, INFINITE); #define MUTEX_UNLOCK(mutex) ReleaseMutex(mutex); #define MUTEX_FREE(mutex) CloseHandle(mutex); #else #include typedef pthread_mutex_t in3_mutex_t; #define MUTEX_INIT(mutex) \ { \ pthread_mutexattr_t attr; \ pthread_mutexattr_init(&attr); \ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \ pthread_mutex_init(&(mutex), &attr); \ } #define MUTEX_LOCK(mutex) pthread_mutex_lock(&(mutex)); #define MUTEX_UNLOCK(mutex) pthread_mutex_unlock(&(mutex)); #define MUTEX_FREE(mutex) pthread_mutex_destroy(&(mutex)); #endif #endif /** * a list of node attributes (mostly used internally) */ typedef enum { ATTR_WHITELISTED = 1, /**< indicates if node exists in whiteList */ ATTR_BOOT_NODE = 2, /**< used to avoid filtering manually added nodes before first nodeList update */ } in3_node_attr_type_t; /** incubed node-configuration. * * These information are read from the Registry contract and stored in this struct representing a server or node. */ typedef struct in3_node { address_t address; /**< address of the server */ bool blocked; /**< if true this node has been blocked for sending wrong responses */ uint_fast16_t index; /**< index within the nodelist, also used in the contract as key */ uint_fast16_t capacity; /**< the maximal capacity able to handle */ uint64_t deposit; /**< the deposit stored in the registry contract, which this would lose if it sends a wrong blockhash */ in3_node_props_t props; /**< used to identify the capabilities of the node. See in3_node_props_type_t in nodelist.h */ char* url; /**< the url of the node */ uint_fast8_t attrs; /**< bitmask of internal attributes */ } in3_node_t; /** * Weight or reputation of a node. * * Based on the past performance of the node a weight is calculated given faster nodes a higher weight * and chance when selecting the next node from the nodelist. * These weights will also be stored in the cache (if available) */ typedef struct in3_node_weight { uint32_t response_count; /**< counter for responses */ uint32_t total_response_time; /**< total of all response times */ uint64_t blacklisted_until; /**< if >0 this node is blacklisted until k. k is a unix timestamp */ } in3_node_weight_t; /** * defines a whitelist structure used for the nodelist. */ typedef struct in3_whitelist { bool needs_update; /**< if true the nodelist should be updated and will trigger a `in3_nodeList`-request before the next request is send. */ uint64_t last_block; /**< last blocknumber the whiteList was updated, which is used to detect changed in the whitelist */ address_t contract; /**< address of whiteList contract. If specified, whiteList is always auto-updated and manual whiteList is overridden */ bytes_t addresses; /**< serialized list of node addresses that constitute the whiteList */ } in3_whitelist_t; typedef enum { NODE_PROP_PROOF = 0x1, /**< filter out nodes which are providing no proof */ NODE_PROP_MULTICHAIN = 0x2, /**< filter out nodes other then which have capability of the same RPC endpoint may also accept requests for different chains */ NODE_PROP_ARCHIVE = 0x4, /**< filter out non-archive supporting nodes */ NODE_PROP_HTTP = 0x8, /**< filter out non-http nodes */ NODE_PROP_BINARY = 0x10, /**< filter out nodes that don't support binary encoding */ NODE_PROP_ONION = 0x20, /**< filter out non-onion nodes */ NODE_PROP_SIGNER = 0x40, /**< filter out non-signer nodes */ NODE_PROP_DATA = 0x80, /**< filter out non-data provider nodes */ NODE_PROP_STATS = 0x100, /**< filter out nodes that do not provide stats */ NODE_PROP_MIN_BLOCK_HEIGHT = 0x400, /**< filter out nodes that will sign blocks with lower min block height than specified */ } in3_node_props_type_t; typedef struct { in3_node_props_t props; d_token_t* nodes; node_match_t* exclusions; } in3_node_filter_t; typedef struct node_offline_ { in3_node_t* offline; address_t reporter; struct node_offline_* next; } node_offline_t; typedef struct in3_nodeselect_def { bool dirty; /**< indicates whether the nodelist has been modified after last read from cache */ uint16_t avg_block_time; /**< average block time (seconds) for this data (calculated internally) */ unsigned int nodelist_length; /**< number of nodes in the nodeList */ uint64_t last_block; /**< last blocknumber the nodeList was updated, which is used to detect changed in the nodelist*/ address_t contract; /**< the address of the registry contract */ bytes32_t registry_id; /**< the identifier of the registry */ in3_node_t* nodelist; /**< array of nodes */ in3_node_weight_t* weights; /**< stats and weights recorded for each node */ bytes_t** init_addresses; /**< array of addresses of nodes that should always part of the nodeList */ node_offline_t* offlines; /**< linked-list of offline nodes */ #ifdef NODESELECT_DEF_WL in3_whitelist_t* whitelist; /**< if set the whitelist of the addresses. */ #endif struct { uint64_t exp_last_block; /**< the last_block when the nodelist last changed reported by this node */ uint64_t timestamp; /**< approx. time when nodelist must be updated (i.e. when reported last_block will be considered final) */ address_t node; /**< node that reported the last_block which necessitated a nodeList update */ } * nodelist_upd8_params; chain_id_t chain_id; /**< the chain_id of the data */ struct in3_nodeselect_def* next; /**< the next in the linked list */ uint32_t ref_counter; /**< number of client using this nodelist */ bytes_t* pre_address_filter; /**< addresses of allowed list (usually because those nodes where paid for) */ #ifdef THREADSAFE in3_mutex_t mutex; /**< mutex to lock this nodelist */ #endif } in3_nodeselect_def_t; /** config for each client pointing to the global data*/ typedef struct in3_nodeselect_config { in3_nodeselect_def_t* data; /**< points to the global nodelist data*/ in3_node_props_t node_props; /**< used to identify the capabilities of the node. */ uint64_t min_deposit; /**< min stake of the server. Only nodes owning at least this amount will be chosen. */ uint16_t node_limit; /**< the limit of nodes to store in the client. */ uint8_t request_count; /**< the number of request send when getting a first answer */ } in3_nodeselect_config_t; /** returns the nodelistwrapper.*/ NONULL in3_nodeselect_config_t* in3_get_nodelist(in3_t* c); /** removes all nodes and their weights from the nodelist */ NONULL void in3_nodelist_clear(in3_nodeselect_def_t* data); #ifdef NODESELECT_DEF_WL /** removes all nodes and their weights from the nodelist */ NONULL void in3_whitelist_clear(in3_whitelist_t* data); /** updates all whitelisted flags in the nodelist */ NONULL void in3_client_run_chain_whitelisting(in3_nodeselect_def_t* data); #endif /** check if the nodelist is up to date. * * if not it will fetch a new version first (if the needs_update-flag is set). */ NONULL in3_ret_t in3_node_list_get(in3_req_t* req, in3_nodeselect_def_t* data, bool update, in3_node_t** nodelist, unsigned int* nodelist_length, in3_node_weight_t** weights); /** * filters and fills the weights on a returned linked list. */ NONULL_FOR((1, 2, 3, 4, 7, 8)) node_match_t* in3_node_list_fill_weight(in3_t* c, in3_nodeselect_config_t* w, in3_node_t* all_nodes, in3_node_weight_t* weights, unsigned int len, uint64_t now, uint32_t* total_weight, unsigned int* total_found, const in3_node_filter_t* filter, bytes_t* pre_filter); /** * calculates the weight for a node. */ NONULL uint32_t in3_node_calculate_weight(in3_node_weight_t* n, uint32_t capa, uint64_t now); /** * picks (based on the config) a random number of nodes and returns them as weightslist. */ NONULL_FOR((1, 2, 3)) in3_ret_t in3_node_list_pick_nodes(in3_req_t* req, in3_nodeselect_config_t* w, node_match_t** nodes, unsigned int request_count, const in3_node_filter_t* filter); /** * forces the client to update the nodelist */ in3_ret_t update_nodes(in3_t* c, in3_nodeselect_def_t* data); #define NODE_FILTER_INIT \ (in3_node_filter_t) { .props = 0, .nodes = NULL } /** * Initializer for in3_node_props_t */ #define in3_node_props_init(np) *(np) = 0 /** * setter method for interacting with in3_node_props_t. */ NONULL void in3_node_props_set(in3_node_props_t* node_props, /**< pointer to the properties to change */ in3_node_props_type_t type, /**< key or type of the property */ uint8_t value /**< value to set */ ); /** * returns the value of the specified property-type. * @return value as a number */ static inline uint32_t in3_node_props_get(in3_node_props_t np, /**< property to read from */ in3_node_props_type_t t) { /**< the value to extract */ return ((t == NODE_PROP_MIN_BLOCK_HEIGHT) ? ((np >> 32U) & 0xFFU) : !!(np & t)); } /** * checks if the given type is set in the properties * @return true if set */ static inline bool in3_node_props_matches(in3_node_props_t np, /**< property to read from */ in3_node_props_type_t t) { /**< the value to extract */ return !!(np & t); } NONULL static inline bool nodelist_first_upd8(const in3_nodeselect_def_t* data) { return (data->nodelist_upd8_params != NULL && data->nodelist_upd8_params->exp_last_block == 0); } NONULL static inline bool nodelist_not_first_upd8(const in3_nodeselect_def_t* data) { return (data->nodelist_upd8_params != NULL && data->nodelist_upd8_params->exp_last_block != 0); } NONULL static inline in3_node_t* get_node_idx(const in3_nodeselect_def_t* data, unsigned int index) { return index < data->nodelist_length ? data->nodelist + index : NULL; } NONULL static inline in3_node_weight_t* get_node_weight_idx(const in3_nodeselect_def_t* data, unsigned int index) { return index < data->nodelist_length ? data->weights + index : NULL; } static inline in3_node_t* get_node(const in3_nodeselect_def_t* data, const node_match_t* node) { return node ? get_node_idx(data, node->index) : NULL; } NONULL static inline in3_node_weight_t* get_node_weight(const in3_nodeselect_def_t* data, const node_match_t* node) { return node ? get_node_weight_idx(data, node->index) : NULL; } static inline bool is_blacklisted(const in3_node_t* node) { return node && node->blocked; } NONULL static in3_ret_t blacklist_node(in3_nodeselect_def_t* data, unsigned int index, uint64_t secs_from_now) { in3_node_t* node = get_node_idx(data, index); if (is_blacklisted(node)) return IN3_ERPC; // already handled if (node && !node->blocked) { in3_node_weight_t* w = get_node_weight_idx(data, index); if (!w) { in3_log_debug("failed to blacklist node: %s\n", node->url); return IN3_EFIND; } // blacklist the node uint64_t blacklisted_until_ = in3_time(NULL) + secs_from_now; if (w->blacklisted_until != blacklisted_until_) data->dirty = true; w->blacklisted_until = blacklisted_until_; node->blocked = true; in3_log_debug("Blacklisting node for unverifiable response: %s\n", node ? node->url : ""); } return IN3_OK; } NONULL static inline in3_ret_t blacklist_node_addr(in3_nodeselect_def_t* data, const address_t node_addr, uint64_t secs_from_now) { for (unsigned int i = 0; i < data->nodelist_length; ++i) if (!memcmp(data->nodelist[i].address, node_addr, 20)) return blacklist_node(data, data->nodelist[i].index, secs_from_now); return IN3_OK; } NONULL static inline in3_ret_t blacklist_node_url(in3_nodeselect_def_t* data, const char* node_url, uint64_t secs_from_now) { for (unsigned int i = 0; i < data->nodelist_length; ++i) if (!strcmp(data->nodelist[i].url, node_url)) return blacklist_node(data, data->nodelist[i].index, secs_from_now); return IN3_OK; } #endif