/******************************************************************************* * 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 . *******************************************************************************/ // @PUBLIC_HEADER /** @file * Request Context. * This is used for each request holding request and response-pointers but also controls the execution process. * */ #ifndef CONTEXT_H #define CONTEXT_H #ifdef __cplusplus extern "C" { #endif #include "data.h" #include "scache.h" #include "stringbuilder.h" #include "utils.h" #include "client.h" #include #include /** * type of the request context, */ typedef enum ctx_type { RT_RPC = 0, /**< a json-rpc request, which needs to be send to a incubed node */ RT_SIGN = 1 /**< a sign request */ } req_type_t; /** * the weight of a certain node as linked list. * * This will be used when picking the nodes to send the request to. A linked list of these structs desribe the result. */ typedef struct weight { unsigned int index; /**< index of the node in the nodelist */ uint32_t s; /**< The starting value */ uint32_t w; /**< weight value */ char* url; /**< the url of the node */ address_t address; /**< address of the server */ struct weight* next; /**< next in the linked-list or NULL if this is the last element*/ } node_match_t; /** response-object. * * if the error has a length>0 the response will be rejected */ typedef struct in3_response { uint32_t time; /**< measured time (in ms) which will be used for ajusting the weights */ in3_ret_t state; /**< the state of the response */ sb_t data; /**< a stringbuilder to add the result */ } in3_response_t; /** * The Request config. * * This is generated for each request and represents the current state. it holds the state until the request is finished and must be freed afterwards. * */ typedef struct in3_req { uint_fast8_t signers_length; /**< number or addresses */ uint_fast16_t len; /**< the number of requests */ uint_fast16_t attempt; /**< the number of attempts */ uint32_t id; /**< JSON RPC id of request at index 0 */ req_type_t type; /**< the type of the request */ in3_ret_t verification_state; /**< state of the verification */ char* error; /**< in case of an error this will hold the message, if not it points to `NULL` */ json_ctx_t* request_context; /**< the result of the json-parser for the request.*/ json_ctx_t* response_context; /**< the result of the json-parser for the response.*/ d_token_t** requests; /**< references to the tokens representring the requests*/ d_token_t** responses; /**< references to the tokens representring the parsed responses*/ in3_response_t* raw_response; /**< the raw response-data, which should be verified. */ uint8_t* signers; /**< the addresses of servers requested to sign the blockhash */ node_match_t* nodes; /**< selected nodes to process the request, which are stored as linked list.*/ cache_entry_t* cache; /** CTX [label="req_new()"] CTX -> exec exec -> error [label="IN3_..."] exec -> response[label="IN3_OK"] exec -> waiting[label="IN3_WAITING"] waiting -> sign[label=RT_SIGN] waiting -> request[label=RT_RPC] sign -> exec [label="in3_ctx_add_response()"] request -> exec[label="in3_ctx_add_response()"] response -> free error->free { rank = same; exec, sign, request } } * ``` * * Here is a example how to use this function: * * ```c * in3_ret_t in3_send_req(in3_req_t* req) { in3_ret_t ret; // execute the context and store the return value. // if the return value is 0 == IN3_OK, it was successful and we return, // if not, we keep on executing while ((ret = in3_req_execute(ctx))) { // error we stop here, because this means we got an error if (ret != IN3_WAITING) return ret; // handle subcontexts first, if they have not been finished while (ctx->required && in3_req_state(ctx->required) != REQ_SUCCESS) { // exxecute them, and return the status if still waiting or error if ((ret = in3_send_req(ctx->required))) return ret; // recheck in order to prepare the request. // if it is not waiting, then it we cannot do much, becaus it will an error or successfull. if ((ret = in3_req_execute(ctx)) != IN3_WAITING) return ret; } // only if there is no response yet... if (!ctx->raw_response) { // what kind of request do we need to provide? switch (ctx->type) { // RPC-request to send to the nodes case RT_RPC: { // build the request in3_http_request_t* request = in3_create_request(ctx); // here we use the transport, but you can also try to fetch the data in any other way. ctx->client->transport(request); // clean up request_free(request); break; } // this is a request to sign a transaction case RT_SIGN: { // read the data to sign from the request d_token_t* params = d_get(ctx->requests[0], K_PARAMS); // the data to sign bytes_t data = d_to_bytes(d_get_at(params, 0)); // the account to sign with bytes_t from = d_to_bytes(d_get_at(params, 1)); // prepare the response ctx->raw_response = _malloc(sizeof(in3_response_t)); sb_init(&ctx->raw_response[0].error); sb_init(&ctx->raw_response[0].result); // data for the signature uint8_t sig[65]; // use the signer to create the signature ret = ctx->client->signer->sign(ctx, SIGN_EC_HASH, data, from, sig); // if it fails we report this as error if (ret < 0) return req_set_error(ctx, ctx->raw_response->error.data, ret); // otherwise we simply add the raw 65 bytes to the response. sb_add_range(&ctx->raw_response->result, (char*) sig, 0, 65); } } } } // done... return ret; } * ``` * * * * */ NONULL in3_ret_t in3_req_execute( in3_req_t* req /**< [in] the request context. */ ); /** * returns the current state of the context. */ NONULL in3_req_state_t in3_req_state( in3_req_t* req /**< [in] the request context. */ ); /** * returns the error of the context. */ char* req_get_error_data( in3_req_t* req /**< [in] the request context. */ ); /** * returns json response for that context */ char* req_get_response_data( in3_req_t* req /**< [in] the request context. */ ); /** * returns the result or NULL in case of an error for that context. The result must be freed! */ char* req_get_result_json(in3_req_t* ctx, int index); /** * returns the type of the request */ NONULL req_type_t req_get_type( in3_req_t* req /**< [in] the request context. */ ); /** * frees all resources allocated during the request. * * But this will not free the request string passed when creating the context! */ NONULL void req_free( in3_req_t* req /**< [in] the request context. */ ); /** * adds a new context as a requirment. * * Whenever a verifier needs more data and wants to send a request, we should create the request and add it as dependency and stop. * * If the function is called again, we need to search and see if the required status is now useable. * * Here is an example of how to use it: * * ```c in3_ret_t get_from_nodes(in3_req_t* parent, char* method, char* params, bytes_t* dst) { // check if the method is already existing in3_req_t* req = req_find_required(parent, method, NULL); if (ctx) { // found one - so we check if it is useable. switch (in3_req_state(ctx)) { // in case of an error, we report it back to the parent context case REQ_ERROR: return req_set_error(parent, ctx->error, IN3_EUNKNOWN); // if we are still waiting, we stop here and report it. case CTX_WAITING_FOR_REQUIRED_CTX: case REQ_WAITING_FOR_RESPONSE: return IN3_WAITING; // if it is useable, we can now handle the result. case REQ_SUCCESS: { d_token_t* r = d_get(ctx->responses[0], K_RESULT); if (r) { // we have a result, so write it back to the dst *dst = d_to_bytes(r); return IN3_OK; } else // or check the error and report it return req_check_response_error(parent, 0); } } } // no required context found yet, so we create one: // since this is a subrequest it will be freed when the parent is freed. // allocate memory for the request-string char* req = _malloc(strlen(method) + strlen(params) + 200); // create it sprintf(req, "{\"method\":\"%s\",\"jsonrpc\":\"2.0\",\"id\":1,\"params\":%s}", method, params); // and add the request context to the parent. return req_add_required(parent, req_new(parent->client, req)); } * ``` */ NONULL in3_ret_t req_add_required( in3_req_t* parent, /**< [in] the current request context. */ in3_req_t* req /**< [in] the new request context to add. */ ); /** * searches within the required request contextes for one with the given method. * * This method is used internaly to find a previously added context. */ NONULL_FOR((1, 2)) in3_req_t* req_find_required( const in3_req_t* parent, /**< [in] the current request context. */ const char* method, /**< [in] the method of the rpc-request. */ const char* param_query /**< [in] a optional string within thew params. */ ); /** * removes a required context after usage. * removing will also call free_ctx to free resources. */ NONULL in3_ret_t req_remove_required( in3_req_t* parent, /**< [in] the current request context. */ in3_req_t* req, /**< [in] the request context to remove. */ bool rec /**< [in] if true all sub contexts will aƶsp be removed*/ ); /** * check if the response contains a error-property and reports this as error in the context. */ NONULL in3_ret_t req_check_response_error( in3_req_t* c, /**< [in] the current request context. */ int i /**< [in] the index of the request to check (if this is a batch-request, otherwise 0). */ ); /** * determins the errorcode for the given request. */ NONULL in3_ret_t req_get_error( in3_req_t* req, /**< [in] the current request context. */ int id /**< [in] the index of the request to check (if this is a batch-request, otherwise 0). */ ); /** * sends a request and returns a context used to access the result or errors. * * This context *MUST* be freed with req_free(ctx) after usage to release the resources. */ NONULL in3_req_t* in3_client_rpc_ctx_raw( in3_t* c, /**< [in] the client config. */ const char* request /**< [in] rpc request. */ ); /** * sends a request and returns a context used to access the result or errors. * * This context *MUST* be freed with req_free(ctx) after usage to release the resources. */ NONULL in3_req_t* in3_client_rpc_ctx( in3_t* c, /**< [in] the clientt config. */ const char* method, /**< [in] rpc method. */ const char* params /**< [in] params as string. */ ); /** * determines the proof as set in the request. */ NONULL in3_proof_t in3_req_get_proof( in3_req_t* req, /**< [in] the current request. */ int i /**< [in] the index within the request. */ ); #define TRY_SUB_REQUEST(req, name, res, fmt, ...) \ { \ sb_t sb = {0}; \ sb_printx(&sb, fmt, __VA_ARGS__); \ in3_ret_t r = req_send_sub_request(req, name, sb.data, NULL, res, NULL); \ _free(sb.data); \ if (r) return r; \ } #define TRY_CATCH_SUB_REQUEST(req, name, res, _catch, fmt, ...) \ { \ sb_t sb = {0}; \ sb_printx(&sb, fmt, __VA_ARGS__); \ in3_ret_t r = req_send_sub_request(req, name, sb.data, NULL, res, NULL); \ _free(sb.data); \ if (r) { \ _catch; \ return r; \ } \ } #define TRY_FINAL_SUB_REQUEST(req, name, res, _catch, fmt, ...) \ { \ sb_t sb = {0}; \ sb_printx(&sb, fmt, __VA_ARGS__); \ in3_ret_t r = req_send_sub_request(req, name, sb.data, NULL, res, NULL); \ _free(sb.data); \ _catch; \ if (r) \ return r; \ } #ifdef __cplusplus } #endif #endif