diff --git a/CMakeLists.txt b/CMakeLists.txt index e66d15cec..73062d3c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,7 @@ option(GCC_ANALYZER "GCC10 static code analyses" OFF) option(PAY_ETH "support for direct Eth-Payment" OFF) option(USE_SCRYPT "integrate scrypt into the build in order to allow decrypt_key for scrypt encoded keys." ON) option(USE_CURL "if true the curl transport will be built (with a dependency to libcurl)" ON) +option(USE_WINHTTP "if true the winhttp transport will be built (with a dependency to winhttp)" OFF) option(DEV_NO_INTRN_PTR "(*dev option*) if true the client will NOT include a void pointer (named internal) for use by devs)" ON) option(LEDGER_NANO "include support for nano ledger" OFF) option(ESP_IDF "include support for ESP-IDF microcontroller framework" OFF) @@ -117,6 +118,10 @@ elseif(ETH_NANO) set(IN3_VERIFIER eth_nano) endif() +if (ETH_NANO) + set(WASM_MODULES ${WASM_MODULES} eth) +endif() + if(IN3API) ADD_DEFINITIONS(-DETH_API) set(IN3_API ${IN3_API} eth_api) @@ -134,6 +139,8 @@ endif() if(IPFS) ADD_DEFINITIONS(-DIPFS) set(IN3_VERIFIER ${IN3_VERIFIER} ipfs) + set(WASM_MODULES ${WASM_MODULES} ipfs) + if(IN3API) set(IN3_API ${IN3_API} ipfs_api) endif() @@ -142,6 +149,7 @@ endif() if(BTC) ADD_DEFINITIONS(-DBTC) set(IN3_VERIFIER ${IN3_VERIFIER} btc) + set(WASM_MODULES ${WASM_MODULES} btc) if(IN3API) set(IN3_API ${IN3_API} btc_api) endif() @@ -211,6 +219,7 @@ IF (WASM) set(IN3_LIB false) set(CMD false) set(USE_CURL false) + set(USE_WINHTTP false) ADD_DEFINITIONS(-DWASM) add_subdirectory(wasm/src) ENDIF (WASM) diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt index e8c04520e..b408dff81 100644 --- a/c/CMakeLists.txt +++ b/c/CMakeLists.txt @@ -54,6 +54,9 @@ IF (TRANSPORTS) if (CURL_BLOCKING) ADD_DEFINITIONS(-DCURL_BLOCKING) endif (CURL_BLOCKING) + elseif(USE_WINHTTP) + ADD_DEFINITIONS(-DUSE_WINHTTP) + set(IN3_TRANSPORT ${IN3_TRANSPORT} transport_winhttp) else () set(IN3_TRANSPORT ${IN3_TRANSPORT} transport_http) endif (USE_CURL) diff --git a/c/ci.yml b/c/ci.yml index 0614842be..bed7c3ef3 100644 --- a/c/ci.yml +++ b/c/ci.yml @@ -54,7 +54,7 @@ win_mingw: - .conanbuild - .only_full variables: - CONAN_OPTS: "-DLIBCURL_LINKTYPE=static -DJAVA=false" + CONAN_OPTS: "-DUSE_CURL=false -DUSE_WINHTTP=true -DJAVA=false" BUILD: "win_build" win_jni: diff --git a/c/include/in3/client.h b/c/include/in3/client.h index d415ad01d..418b107d6 100644 --- a/c/include/in3/client.h +++ b/c/include/in3/client.h @@ -53,17 +53,17 @@ #define IN3_PROTO_VER "2.1.0" /**< the protocol version used when sending requests from the this client */ -#define CHAIN_ID_MULTICHAIN 0x0 /**< chain_id working with all known chains */ -#define CHAIN_ID_MAINNET 0x01 /**< chain_id for mainnet */ -#define CHAIN_ID_KOVAN 0x2a /**< chain_id for kovan */ -#define CHAIN_ID_TOBALABA 0x44d /**< chain_id for tobalaba */ -#define CHAIN_ID_GOERLI 0x5 /**< chain_id for goerlii */ -#define CHAIN_ID_EVAN 0x4b1 /**< chain_id for evan */ -#define CHAIN_ID_EWC 0xf6 /**< chain_id for ewc */ -#define CHAIN_ID_IPFS 0x7d0 /**< chain_id for ipfs */ -#define CHAIN_ID_BTC 0x99 /**< chain_id for btc */ -#define CHAIN_ID_LOCAL 0xFFFF /**< chain_id for local chain */ -#define DEF_REPL_LATEST_BLK 6 /**< default replace_latest_block */ +#define CHAIN_ID_MULTICHAIN 0x0 /**< chain_id working with all known chains */ +#define CHAIN_ID_MAINNET 0x01 /**< chain_id for mainnet */ +#define CHAIN_ID_KOVAN 0x2a /**< chain_id for kovan */ +#define CHAIN_ID_TOBALABA 0x44d /**< chain_id for tobalaba */ +#define CHAIN_ID_GOERLI 0x5 /**< chain_id for goerlii */ +#define CHAIN_ID_EVAN 0x4b1 /**< chain_id for evan */ +#define CHAIN_ID_EWC 0xf6 /**< chain_id for ewc */ +#define CHAIN_ID_IPFS 0x7d0 /**< chain_id for ipfs */ +#define CHAIN_ID_BTC 0x99 /**< chain_id for btc */ +#define CHAIN_ID_LOCAL 0x11 /**< chain_id for local chain */ +#define DEF_REPL_LATEST_BLK 6 /**< default replace_latest_block */ /** * type for a chain_id. diff --git a/c/include/in3/colors.h b/c/include/in3/colors.h index cd47a3661..c9c9e7016 100644 --- a/c/include/in3/colors.h +++ b/c/include/in3/colors.h @@ -33,7 +33,7 @@ *******************************************************************************/ /*Term colors*/ - +#pragma GCC diagnostic ignored "-Wformat-zero-length" #ifdef LOG_USE_COLOR #define COLORT_RESET "\033[0m" #define COLORT_BOLD "\033[1m" diff --git a/c/include/in3/context.h b/c/include/in3/context.h index b73f40687..25f8c13a0 100644 --- a/c/include/in3/context.h +++ b/c/include/in3/context.h @@ -86,7 +86,7 @@ typedef struct in3_response { * */ typedef struct in3_ctx { uint_fast8_t signers_length; /**< number or addresses */ - uint_fast16_t len; /**< the number of requests */ + uint16_t len; /**< the number of requests */ uint_fast16_t attempt; /**< the number of attempts */ ctx_type_t type; /**< the type of the request */ in3_ret_t verification_state; /**< state of the verification */ diff --git a/c/include/in3/in3_winhttp.h b/c/include/in3/in3_winhttp.h new file mode 100644 index 000000000..037996e71 --- /dev/null +++ b/c/include/in3/in3_winhttp.h @@ -0,0 +1,51 @@ +/******************************************************************************* + * This file is part of the Incubed project. + * Sources: https://github.com/slockit/in3-c + * + * 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 + * transport-handler using simple http. + */ + +#ifndef in3_winhttp_h__ +#define in3_winhttp_h__ + +#include "client.h" +in3_ret_t send_winhttp(void* plugin_data, in3_plugin_act_t action, void* plugin_ctx); + +/** + * registers http as a default transport. + */ +in3_ret_t in3_register_winhttp(in3_t* c); + +#endif // in3_http_h__ diff --git a/c/src/cmd/in3/main.c b/c/src/cmd/in3/main.c index 7f5a9b72b..3d71df5ae 100644 --- a/c/src/cmd/in3/main.c +++ b/c/src/cmd/in3/main.c @@ -48,6 +48,8 @@ #include "../../third-party/crypto/secp256k1.h" #ifdef USE_CURL #include "../../transport/curl/in3_curl.h" +#elif USE_WINHTTP +#include "../../transport/winhttp/in3_winhttp.h" #else #include "../../transport/http/in3_http.h" #endif @@ -617,6 +619,8 @@ static in3_ret_t debug_transport(void* plugin_data, in3_plugin_act_t action, voi } #ifdef USE_CURL in3_ret_t r = send_curl(NULL, action, plugin_ctx); +#elif USE_WINHTTP + in3_ret_t r = send_winhttp(NULL, action, plugin_ctx); #else in3_ret_t r = send_http(NULL, action, plugin_ctx); #endif @@ -639,6 +643,8 @@ static in3_ret_t test_transport(void* plugin_data, in3_plugin_act_t action, void in3_request_t* req = plugin_ctx; #ifdef USE_CURL in3_ret_t r = send_curl(NULL, action, plugin_ctx); +#elif USE_WINHTTP + in3_ret_t r = send_winhttp(NULL, action, plugin_ctx); #else in3_ret_t r = send_http(NULL, action, plugin_ctx); #endif @@ -722,11 +728,8 @@ int main(int argc, char* argv[]) { bool to_eth = false; plugin_register(c, PLGN_ACT_TRANSPORT, debug_transport, NULL, true); -#ifdef __MINGW32__ - c->flags |= FLAGS_HTTP; -#endif -#ifndef USE_CURL - c->flags |= FLAGS_HTTP; +#ifndef USE_WINHTTP + c->request_count = 1; #endif // handle clear cache opt before initializing cache for (i = 1; i < argc; i++) @@ -1044,6 +1047,8 @@ int main(int argc, char* argv[]) { r.payload = ""; #ifdef USE_CURL send_curl(NULL, PLGN_ACT_TRANSPORT_SEND, &r); +#elif USE_WINHTTP + send_winhttp(NULL, PLGN_ACT_TRANSPORT_SEND, &r); #else send_http(NULL, PLGN_ACT_TRANSPORT_SEND, &r); #endif diff --git a/c/src/core/client/context.h b/c/src/core/client/context.h index 2e3cf9083..ab6a8aa42 100644 --- a/c/src/core/client/context.h +++ b/c/src/core/client/context.h @@ -86,7 +86,7 @@ typedef struct in3_response { * */ typedef struct in3_ctx { uint_fast8_t signers_length; /**< number or addresses */ - uint_fast16_t len; /**< the number of requests */ + uint16_t len; /**< the number of requests */ uint_fast16_t attempt; /**< the number of attempts */ ctx_type_t type; /**< the type of the request */ in3_ret_t verification_state; /**< state of the verification */ diff --git a/c/src/core/client/execute.c b/c/src/core/client/execute.c index 17afd109d..25b462714 100644 --- a/c/src/core/client/execute.c +++ b/c/src/core/client/execute.c @@ -995,7 +995,7 @@ in3_ret_t in3_ctx_execute(in3_ctx_t* ctx) { if (ctx->error) return (ctx->verification_state && ctx->verification_state != IN3_WAITING) ? ctx->verification_state : IN3_EUNKNOWN; // is it a valid request? - if (!ctx->request_context || !d_get(ctx->requests[0], K_METHOD)) return ctx_set_error(ctx, "No Method defined", IN3_ECONFIG); + if (!ctx->request_context || d_type(d_get(ctx->requests[0], K_METHOD)) != T_STRING) return ctx_set_error(ctx, "No Method defined", IN3_ECONFIG); // if there is response we are done. if (ctx->response_context && ctx->verification_state == IN3_OK) return IN3_OK; diff --git a/c/src/core/util/colors.h b/c/src/core/util/colors.h index cd47a3661..c9c9e7016 100644 --- a/c/src/core/util/colors.h +++ b/c/src/core/util/colors.h @@ -33,7 +33,7 @@ *******************************************************************************/ /*Term colors*/ - +#pragma GCC diagnostic ignored "-Wformat-zero-length" #ifdef LOG_USE_COLOR #define COLORT_RESET "\033[0m" #define COLORT_BOLD "\033[1m" diff --git a/c/src/transport/CMakeLists.txt b/c/src/transport/CMakeLists.txt index 99164f5f1..95918428a 100644 --- a/c/src/transport/CMakeLists.txt +++ b/c/src/transport/CMakeLists.txt @@ -34,6 +34,8 @@ IF (USE_CURL) add_subdirectory(curl) +elseif(USE_WINHTTP) + add_subdirectory(winhttp) else () add_subdirectory(http) ENDIF () diff --git a/c/src/transport/winhttp/CMakeLists.txt b/c/src/transport/winhttp/CMakeLists.txt new file mode 100644 index 000000000..8ed15497b --- /dev/null +++ b/c/src/transport/winhttp/CMakeLists.txt @@ -0,0 +1,47 @@ +############################################################################### +# This file is part of the Incubed project. +# Sources: https://github.com/slockit/in3-c +# +# 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 . +############################################################################### + +# add lib +add_static_library( + NAME transport_winhttp + + SOURCES + in3_winhttp.c + + DEPENDS + core + winhttp +) + + diff --git a/c/src/transport/winhttp/in3_winhttp.c b/c/src/transport/winhttp/in3_winhttp.c new file mode 100644 index 000000000..9a932b1eb --- /dev/null +++ b/c/src/transport/winhttp/in3_winhttp.c @@ -0,0 +1,187 @@ +/******************************************************************************* + * This file is part of the Incubed project. + * Sources: https://github.com/slockit/in3-c + * + * 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 . + *******************************************************************************/ + +#include "in3_winhttp.h" +#include "../../core/client/client.h" +#include "../../core/client/plugin.h" +#include "../../core/client/version.h" +#include "../../core/util/mem.h" +#include "../../core/util/utils.h" +#include /* printf, sprintf */ +#include /* exit, atoi, malloc, free */ +#include /* memcpy, memset */ +#include +#include /* read, write, close */ +#include +#include + +static inline wchar_t* convert_wstr(const char* src, void* dst) { + MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, (int) MultiByteToWideChar(CP_UTF8, 0, src, -1, NULL, 0)); + return dst; +} +#define to_wstr(c) convert_wstr(c, alloca(MultiByteToWideChar(CP_UTF8, 0, c, -1, NULL, 0) * sizeof(wchar_t) + 1)) + +in3_ret_t send_winhttp(void* plugin_data, in3_plugin_act_t action, void* plugin_ctx) { + UNUSED_VAR(plugin_data); + in3_request_t* req = plugin_ctx; + if (action != PLGN_ACT_TRANSPORT_SEND) return IN3_ENOTSUP; + for (unsigned int n = 0; n < req->urls_len; n++) { + uint32_t start = current_ms(); + sb_t* sb = &req->ctx->raw_response[n].data; + HINTERNET hSession = WinHttpOpen(to_wstr("in3 winhttp " IN3_VERSION), + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, 0); + if (!hSession) { + in3_ctx_add_response(req->ctx, n, true, "could not create the session", -1, 0); + continue; + } + + URL_COMPONENTS url_components; + wchar_t hostname[128]; + wchar_t url_path[4096]; + wchar_t* url = to_wstr(req->urls[n]); + memset(&url_components, 0, sizeof(URL_COMPONENTS)); + url_components.dwStructSize = sizeof(URL_COMPONENTS); + url_components.lpszHostName = hostname; + url_components.dwHostNameLength = 128; + url_components.lpszUrlPath = url_path; + url_components.dwUrlPathLength = 1024; + + WinHttpCrackUrl(url, 0, 0, &url_components); + + HINTERNET connect = WinHttpConnect(hSession, url_components.lpszHostName, url_components.nPort, 0); + if (!connect) { + in3_ctx_add_response(req->ctx, n, true, "could not connect to ", -1, 0); + sb_add_chars(sb, req->urls[n]); + sb_add_chars(sb, "Error code : "); + sb_add_int(sb, GetLastError()); + WinHttpCloseHandle(hSession); + continue; + } + bool https = strstr(req->urls[n], "https") == req->urls[n]; + HINTERNET request = WinHttpOpenRequest(connect, to_wstr("POST"), + url_components.lpszUrlPath, NULL, WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, https ? WINHTTP_FLAG_SECURE : 0); + if (!request) { + in3_ctx_add_response(req->ctx, n, true, "could not open the request to ", -1, 0); + sb_add_chars(sb, req->urls[n]); + sb_add_chars(sb, "Error code : "); + sb_add_int(sb, GetLastError()); + WinHttpCloseHandle(connect); + WinHttpCloseHandle(hSession); + continue; + } + int plen = strlen(req->payload); + bool success = plen + ? WinHttpSendRequest( + request, + to_wstr("Accept: application/json\r\nContent-Type: application/json\r\ncharsets: utf-8\r\n"), + (DWORD) -1, + (LPVOID) req->payload, + (DWORD) plen, (DWORD) plen, 0) + : WinHttpSendRequest( + request, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); + + if (success) { + WinHttpReceiveResponse(request, NULL); + DWORD dwSize = 0; + DWORD dwDownloaded = 0; + LPSTR pszOutBuffer = NULL; + + do { + // Check for available data. + dwSize = 0; + if (!WinHttpQueryDataAvailable(request, &dwSize)) { + sb->len = 0; + in3_ctx_add_response(req->ctx, n, true, "could not read the data from ", -1, 0); + sb_add_chars(sb, req->urls[n]); + sb_add_chars(sb, "Error code : "); + sb_add_int(sb, GetLastError()); + break; + } + + // No more available data. + if (!dwSize) + break; + + // Allocate space for the buffer. + pszOutBuffer = _calloc(dwSize + 1, 1); + + if (!WinHttpReadData(request, (LPVOID) pszOutBuffer, + dwSize, &dwDownloaded)) { + sb->len = 0; + in3_ctx_add_response(req->ctx, n, true, "could not read the data from ", -1, 0); + sb_add_chars(sb, req->urls[n]); + sb_add_chars(sb, "Error code : "); + sb_add_int(sb, GetLastError()); + _free(pszOutBuffer); + break; + } + else + sb_add_range(sb, pszOutBuffer, 0, dwSize); + + // Free the memory allocated to the buffer. + _free(pszOutBuffer); + + // This condition should never be reached since WinHttpQueryDataAvailable + // reported that there are bits to read. + if (!dwDownloaded) + break; + + } while (dwSize > 0); + + if (req->ctx->raw_response[n].state == IN3_WAITING) { + req->ctx->raw_response[n].state = IN3_OK; + req->ctx->raw_response[n].time = current_ms() - start; + } + } + else { + in3_ctx_add_response(req->ctx, n, true, "could not open send the request to ", -1, 0); + sb_add_chars(sb, req->urls[n]); + sb_add_chars(sb, "Error code : "); + sb_add_int(sb, GetLastError()); + } + WinHttpCloseHandle(request); + WinHttpCloseHandle(connect); + WinHttpCloseHandle(hSession); + } + return IN3_OK; +} + +in3_ret_t in3_register_winhttp(in3_t* c) { + return plugin_register(c, PLGN_ACT_TRANSPORT, send_winhttp, NULL, true); +} diff --git a/c/src/transport/winhttp/in3_winhttp.h b/c/src/transport/winhttp/in3_winhttp.h new file mode 100644 index 000000000..ce5eb3133 --- /dev/null +++ b/c/src/transport/winhttp/in3_winhttp.h @@ -0,0 +1,51 @@ +/******************************************************************************* + * This file is part of the Incubed project. + * Sources: https://github.com/slockit/in3-c + * + * 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 + * transport-handler using simple http. + */ + +#ifndef in3_winhttp_h__ +#define in3_winhttp_h__ + +#include "../../core/client/client.h" +in3_ret_t send_winhttp(void* plugin_data, in3_plugin_act_t action, void* plugin_ctx); + +/** + * registers http as a default transport. + */ +in3_ret_t in3_register_winhttp(in3_t* c); + +#endif // in3_http_h__ diff --git a/c/src/verifier/eth1/basic/eth_getLog.c b/c/src/verifier/eth1/basic/eth_getLog.c index 28a2ebd0c..43e38d475 100644 --- a/c/src/verifier/eth1/basic/eth_getLog.c +++ b/c/src/verifier/eth1/basic/eth_getLog.c @@ -222,6 +222,10 @@ in3_ret_t eth_verify_eth_getLog(in3_vctx_t* vc, int l_logs) { for (d_iterator_t it = d_iter(d_get(vc->proof, K_LOG_PROOF)); it.left; d_iter_next(&it)) { sprintf(xtmp, "0x%" PRIx64, d_get_longk(it.token, K_NUMBER)); + if (strlen(xtmp) % 2) { + memmove(xtmp + 3, xtmp + 2, strlen(xtmp) - 1); + xtmp[2] = '0'; + } // verify that block number matches key if (key(xtmp) != it.token->key) return vc_err(vc, "block number mismatch"); diff --git a/c/src/verifier/in3_init.c b/c/src/verifier/in3_init.c index 54b5e6aeb..f7bcc60eb 100644 --- a/c/src/verifier/in3_init.c +++ b/c/src/verifier/in3_init.c @@ -3,8 +3,13 @@ #include "../core/client/plugin.h" #include "../pay/eth/pay_eth.h" #include "../pay/zksync/zksync.h" +#ifdef USE_CURL #include "../transport/curl/in3_curl.h" +#elif USE_WINHTTP +#include "../transport/winhttp/in3_winhttp.h" +#else #include "../transport/http/in3_http.h" +#endif #include "../verifier/btc/btc.h" #include "../verifier/eth1/basic/eth_basic.h" #include "../verifier/eth1/full/eth_full.h" @@ -43,6 +48,8 @@ static void init_transport() { #ifdef TRANSPORTS #ifdef USE_CURL in3_register_default(in3_register_curl); +#elif USE_WINHTTP + in3_register_default(in3_register_winhttp); #else in3_register_default(in3_register_http); #endif /* USE_CURL */ diff --git a/python/.gitignore b/python/.gitignore index 12a40a162..e9d56494c 100644 --- a/python/.gitignore +++ b/python/.gitignore @@ -10,3 +10,4 @@ MANIFEST .DS_Store .coverage report.xml +examples/smart_meter_write.py diff --git a/python/in3/eth/model.py b/python/in3/eth/model.py index 83481dfb6..97e6f4060 100644 --- a/python/in3/eth/model.py +++ b/python/in3/eth/model.py @@ -191,6 +191,15 @@ def __init__(self, blockHash: hex, blockNumber: int, cumulativeGasUsed: int, Fro self.to = to self.contractAddress = contractAddress + def to_dict(self, int_to_hex: bool = False) -> dict: + base_dict = super(TransactionReceipt, self).to_dict() + logs = [log.to_dict() for log in base_dict['logs']] + base_dict['logs'] = logs + return base_dict + + def __str__(self): + return str(self.to_dict()) + class Account(DataTransferObject): """ diff --git a/python/in3/transport.py b/python/in3/transport.py index 7396acb57..88adbc67d 100644 --- a/python/in3/transport.py +++ b/python/in3/transport.py @@ -32,10 +32,7 @@ def https_transport(in3_request: In3Request, in3_response: In3Response): if not response.status == 200: raise TransportException('Request failed with status: {}'.format(str(response.status))) msg = response.read() - if 'error' in str(msg, 'utf8'): - in3_response.failure(i, msg) - else: - in3_response.success(i, msg) + in3_response.success(i, msg) except Exception as err: in3_response.failure(i, str(err).encode('utf8')) return 0 diff --git a/python/pylama.ini b/python/pylama.ini index 27ceff578..b58ac4d5f 100644 --- a/python/pylama.ini +++ b/python/pylama.ini @@ -1,4 +1,4 @@ [pylama] format = pylint -skip = */.tox/*,*/.env/* +skip = */.tox/*,*/.env/*,examples/smart* ignore = E501,W0611,C901 \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh index 9c64f8caa..0795e4c04 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -46,6 +46,7 @@ if [ "$CONTAINER" = "--help" ]; then echo " - clang9" echo " - clang50" echo " - gcc10" + echo " - gcc8-x86" echo " - gcc8" echo " - gcc8-armv7" echo " - gcc8-armv7hf" @@ -77,7 +78,7 @@ elif [ "$CONTAINER" = "win" ]; then CONTAINER=docker.slock.it/build-images/cmake:gcc7-mingw echo $CONTAINER > build/container.txt docker run --rm -v $RD:$RD $CONTAINER \ - /bin/bash -c "cd $RD/build; cmake -DCMAKE_BUILD_TYPE=MINSIZEREL -DJAVA=false -DLEDGER_NANO=true -DUSE_CURL=true -DLIBCURL_LINKTYPE=static .. && make -j8" + /bin/bash -c "cd $RD/build; cmake -DCMAKE_BUILD_TYPE=MINSIZEREL -DJAVA=false -DLEDGER_NANO=false -DUSE_CURL=false -DUSE_WINHTTP=true .. && make -j8" elif [ "$CONTAINER" = "cortexm3" ]; then CONTAINER=docker.io/zephyrprojectrtos/zephyr-build:v0.12 echo $CONTAINER > build/container.txt diff --git a/wasm/docs/2_examples.md b/wasm/docs/2_examples.md index abb6469fd..1f377b71f 100644 --- a/wasm/docs/2_examples.md +++ b/wasm/docs/2_examples.md @@ -67,6 +67,64 @@ async function showLatestBlock() { showLatestBlock().catch(console.error) ``` +### register_pugin + +source : [in3-c/wasm/examples/register_pugin.ts](https://github.com/slockit/in3-c/blob/master/wasm/examples/register_pugin.ts) + +register a custom plugin + + +```js +/// register a custom plugin + +import { IN3, RPCRequest } from 'in3-wasm' +import * as crypto from 'crypto' + +class Sha256Plugin { + + // this function will register for handling rpc-methods + // only if we return something other then `undefined`, it will be taken as the result of the rpc. + // if we don't return, the request will be forwarded to the incubed nodes + handleRPC(c: IN3, request: RPCRequest): any { + if (request.method === 'sha256') { + // assert params + if (request.params.length != 1 || typeof (request.params[0]) != 'string') + throw new Error('Only one parameter with as string is expected!') + + // create hash + const hash = crypto.createHash('sha256').update(Buffer.from(request.params[0], 'utf8')).digest() + + // return the result + return '0x' + hash.toString('hex') + } + } + +} + + + +async function registerPlugin() { + // create new incubed instance + const client = new IN3({ + chainId: 'goerli' + }) + + // register the plugin + client.registerPlugin(new Sha256Plugin()) + + // exeucte a rpc-call + const result = await client.sendRPC("sha256", ["testdata"]) + + console.log(" sha256: ", result) + + // clean up + client.free() + +} + +registerPlugin().catch(console.error) +``` + ### use_web3 source : [in3-c/wasm/examples/use_web3.ts](https://github.com/slockit/in3-c/blob/master/wasm/examples/use_web3.ts) diff --git a/wasm/examples/README.md b/wasm/examples/README.md index 17801ae71..d71ea1d66 100644 --- a/wasm/examples/README.md +++ b/wasm/examples/README.md @@ -7,6 +7,9 @@ - [get_block_api](./get_block_api.ts) read block with API +- [register_pugin](./register_pugin.ts) + register a custom plugin + - [use_web3](./use_web3.ts) use incubed as Web3Provider in web3js diff --git a/wasm/examples/get_block_rpc.js b/wasm/examples/get_block_rpc.js index 59805adf6..40cc4464a 100644 --- a/wasm/examples/get_block_rpc.js +++ b/wasm/examples/get_block_rpc.js @@ -10,13 +10,10 @@ async function showLatestBlock() { chainId: 0x5 // use goerli }) - // send raw RPC-Request - const lastBlockResponse = await c.send({ method: 'eth_getBlockByNumber', params: ['latest', false] }) + // send raw RPC-Request (this would throw if the response contains an error) + const lastBlockResponse = await c.sendRPC('eth_getBlockByNumber', ['latest', false]) - if (lastBlockResponse.error) - console.error("Error getting the latest block : ", lastBlockResponse.error) - else - console.log("latest Block: ", JSON.stringify(lastBlockResponse.result, null, 2)) + console.log("latest Block: ", JSON.stringify(lastBlockResponse, null, 2)) // clean up c.free() diff --git a/wasm/examples/register_pugin.ts b/wasm/examples/register_pugin.ts new file mode 100644 index 000000000..4af0e9b30 --- /dev/null +++ b/wasm/examples/register_pugin.ts @@ -0,0 +1,46 @@ +/// register a custom plugin + +import { IN3, RPCRequest } from 'in3-wasm' +import * as crypto from 'crypto' + +class Sha256Plugin { + + // this function will register for handling rpc-methods + // only if we return something other then `undefined`, it will be taken as the result of the rpc. + // if we don't return, the request will be forwarded to the incubed nodes + handleRPC(c: IN3, request: RPCRequest): any { + if (request.method === 'sha256') { + // assert params + if (request.params.length != 1 || typeof (request.params[0]) != 'string') + throw new Error('Only one parameter with as string is expected!') + + // create hash + const hash = crypto.createHash('sha256').update(Buffer.from(request.params[0], 'utf8')).digest() + + // return the result + return '0x' + hash.toString('hex') + } + } + +} + + + +async function registerPlugin() { + // create new incubed instance + const client = new IN3() + + // register the plugin + client.registerPlugin(new Sha256Plugin()) + + // exeucte a rpc-call + const result = await client.sendRPC("sha256", ["testdata"]) + + console.log(" sha256: ", result) + + // clean up + client.free() + +} + +registerPlugin().catch(console.error) \ No newline at end of file diff --git a/wasm/src/CMakeLists.txt b/wasm/src/CMakeLists.txt index a54fa4ee1..9518f71ae 100644 --- a/wasm/src/CMakeLists.txt +++ b/wasm/src/CMakeLists.txt @@ -63,11 +63,12 @@ add_executable(in3w wasm.c) target_link_libraries(in3w init) set_target_properties(in3w PROPERTIES LINK_FLAGS "${EMC_PROPS}") + add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/bin/in3.js - DEPENDS in3w in3.js in3_eth_api.js in3_btc_api.js in3_ipfs_api.js in3_util.js in3.d.ts + DEPENDS in3w in3.js modules/eth.js modules/btc.js modules/ipfs.js in3_util.js in3.d.ts modules/btc.d.ts modules/ipfs.d.ts modules/eth.d.ts WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/bin/ - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_js.sh ${CMAKE_CURRENT_SOURCE_DIR} ${ASMJS} + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_js.sh ${CMAKE_CURRENT_SOURCE_DIR} ${ASMJS} ${WASM_MODULES} ) add_custom_target(in3_wasm ALL DEPENDS ${CMAKE_BINARY_DIR}/bin/in3.js) diff --git a/wasm/src/build_js.sh b/wasm/src/build_js.sh index 020d03c00..846f2e0c3 100755 --- a/wasm/src/build_js.sh +++ b/wasm/src/build_js.sh @@ -1,4 +1,8 @@ #!/bin/bash +function replace_var { + echo "REPLACE $1 with $2" + sed -i "s/$(echo $1 | sed -e 's/\([[\/.*]\|\]\)/\\&/g')/$(echo $2 | sed -e 's/[\/&]/\\&/g')/g" $3 +} TARGET_JS="in3.js" @@ -10,9 +14,27 @@ cat in3w.js | sed "s/uncaughtException/ue/g" >> $TARGET_JS # it should also overwrite the module.exports to use the wrapper-class. cat "$1/in3.js" >> $TARGET_JS cat "$1/in3_util.js" >> $TARGET_JS -cat "$1/in3_eth_api.js" >> $TARGET_JS -cat "$1/in3_ipfs_api.js" >> $TARGET_JS -cat "$1/in3_btc_api.js" >> $TARGET_JS + +__CONFIG__="" +typedefs="" +__API__="" +for m in $3 $4 $5 $6 $7 $8 $9 +do + cat "$1/modules/$m.js" >> $TARGET_JS + if test -f "$1/modules/$m.d.ts"; then + if grep -q "${m}_config" "$1/modules/$m.d.ts"; then + __CONFIG__="$__CONFIG__\n /** config for $m */\n $m?:${m}_config" + fi + conf=`grep API $1/modules/$m.d.ts | grep public | grep "$m:"` + [[ ! -z "$conf" ]] && __API__="$__API__\n /** $m API */\n $conf" + typedefs="$typedefs $1/modules/$m.d.ts" + fi +done + + +#cat "$1/in3_ipfs_api.js" >> $TARGET_JS +#cat "$1/in3_btc_api.js" >> $TARGET_JS + # we return the default export echo " return IN3; })();" >> $TARGET_JS #echo "//# sourceMappingURL=index.js.map" >> $TARGET_JS @@ -21,7 +43,13 @@ echo " return IN3; })();" >> $TARGET_JS mkdir -p ../module cp ../../LICENSE.AGPL "$1/package.json" $1/../README.md ../module/ cp in3.js ../module/index.js -cp "$1/in3.d.ts" ../module/index.d.ts + +cat "$1/in3.d.ts" | awk -v "r=$__CONFIG__" '{gsub(/__CONFIG__/,r)}1' | awk -v "r=$__API__" '{gsub(/__API__/,r)}1' > ../module/index.d.ts +for f in $typedefs; do + cat $f >> ../module/index.d.ts +done + + if [ -e in3w.wasm ] then cp in3w.wasm ../module/ fi diff --git a/wasm/src/in3.d.ts b/wasm/src/in3.d.ts index 7b6489202..eda9ae8b0 100644 --- a/wasm/src/in3.d.ts +++ b/wasm/src/in3.d.ts @@ -37,6 +37,13 @@ * All properties are optional and will be verified when sending the next request. */ export declare interface IN3Config { + /** + * sets the transport-function. + * + * @param fn the function to fetch the response for the given url + */ + transport?: (url: string, payload: string, timeout?: number) => Promise + /** * if true the nodelist will be automaticly updated if the lastBlock is newer. * @@ -239,6 +246,8 @@ export declare interface IN3Config { } } + + __CONFIG__ } /** * a configuration of a in3-server. @@ -378,6 +387,34 @@ export declare interface RPCResponse { result?: any } +/** + * a Incubed plugin. + * + * Depending on the methods this will register for those actions. + */ +interface IN3Plugin { + /** + * this is called when the client is cleaned up. + * @param client the client object + */ + term?(client: IN3Generic) + + /** + * returns address + * @param client + */ + getAccount?(client: IN3Generic) + + /** + * called for each request. + * If the plugin wants to handle the request, this function should return the value or a Promise for the value. + * If the plugin does not want to handle it, it should rreturn undefined. + * @param client the current client + * @param request the rpc-request + */ + handleRPC?(client: IN3Generic, request: RPCRequest): undefined | Promise +} + export default class IN3Generic { /** * IN3 config @@ -432,7 +469,7 @@ export default class IN3Generic { /** - * changes the transport-function. + * changes the default transport-function. * * @param fn the function to fetch the response for the given url */ @@ -466,21 +503,18 @@ export default class IN3Generic { */ public eth: EthAPI + __API__ /** - * ipfs API. + * collection of util-functions. */ - public ipfs: IpfsAPI + public util: Utils /** - * Bitcoin API. + * rregisters a plugin. The plugin may define methods which will be called by the client. + * @param plugin the plugin-object to register */ - public btc: BtcAPI - - /** - * collection of util-functions. - */ - public util: Utils + public registerPlugin(plugin: IN3Plugin): void /** * collection of util-functions. @@ -585,212 +619,7 @@ export type Transaction = { /** optional chain id */ chainId?: any } -export type TransactionReceipt = { - /** 32 Bytes - hash of the block where this transaction was in. */ - blockHash: Hash - /** block number where this transaction was in.*/ - blockNumber: BlockType - /** 20 Bytes - The contract address created, if the transaction was a contract creation, otherwise null.*/ - contractAddress: Address - /** The total amount of gas used when this transaction was executed in the block. */ - cumulativeGasUsed: Quantity - /** 20 Bytes - The address of the sender. */ - from: Address - /** 20 Bytes - The address of the receiver. null when it’s a contract creation transaction.*/ - to: Address - /** The amount of gas used by this specific transaction alone. */ - gasUsed: Quantity - /** Array of log objects, which this transaction generated. */ - logs: Log[] - /** 256 Bytes - A bloom filter of logs/events generated by contracts during transaction execution. Used to efficiently rule out transactions without expected logs.*/ - logsBloom: Data - /** 32 Bytes - Merkle root of the state trie after the transaction has been executed (optional after Byzantium hard fork EIP609)*/ - root: Hash - /** 0x0 indicates transaction failure , 0x1 indicates transaction success. Set for blocks mined after Byzantium hard fork EIP609, null before. */ - status: Quantity - /** 32 Bytes - hash of the transaction. */ - transactionHash: Hash - /** Integer of the transactions index position in the block. */ - transactionIndex: Quantity -} -export type TransactionDetail = { - /** 32 Bytes - hash of the transaction. */ - hash: Hash - /** the number of transactions made by the sender prior to this one.*/ - nonce: Quantity - /** 32 Bytes - hash of the block where this transaction was in. null when its pending.*/ - blockHash: Hash - /** block number where this transaction was in. null when its pending.*/ - blockNumber: BlockType - /** integer of the transactions index position in the block. null when its pending.*/ - transactionIndex: Quantity - /** 20 Bytes - address of the sender.*/ - from: Address - /** 20 Bytes - address of the receiver. null when its a contract creation transaction. */ - to: Address - /** value transferred in Wei.*/ - value: Quantity - /** gas price provided by the sender in Wei.*/ - gasPrice: Quantity - /** gas provided by the sender. */ - gas: Quantity - /** the data send along with the transaction. */ - input: Data - /** the standardised V field of the signature.*/ - v: Quantity - /** the standardised V field of the signature (0 or 1).*/ - standardV: Quantity - /** the R field of the signature.*/ - r: Quantity - /** raw transaction data */ - raw: Data - /** public key of the signer. */ - publicKey: Hash - /** the chain id of the transaction, if any. */ - chainId: Quantity - /** creates contract address */ - creates: Address - /** (optional) conditional submission, Block number in block or timestamp in time or null. (parity-feature) */ - condition: any - /** optional: the private key to use for signing */ - pk?: any -} - -export type Block = { - /** The block number. null when its pending block */ - number: Quantity - /** hash of the block. null when its pending block */ - hash: Hash - /** hash of the parent block */ - parentHash: Hash - /** 8 bytes hash of the generated proof-of-work. null when its pending block. Missing in case of PoA. */ - nonce: Data - /** SHA3 of the uncles data in the block */ - sha3Uncles: Data - /** 256 Bytes - the bloom filter for the logs of the block. null when its pending block */ - logsBloom: Data - /** 32 Bytes - the root of the transaction trie of the block */ - transactionsRoot: Data - /** 32 Bytes - the root of the final state trie of the block */ - stateRoot: Data - /** 32 Bytes - the root of the receipts trie of the block */ - receiptsRoot: Data - /** 20 Bytes - the address of the author of the block (the beneficiary to whom the mining rewards were given)*/ - author: Address - /** 20 Bytes - alias of ‘author’*/ - miner: Address - /** integer of the difficulty for this block */ - difficulty: Quantity - /** integer of the total difficulty of the chain until this block */ - totalDifficulty: Quantity - /** the ‘extra data’ field of this block */ - extraData: Data - /** integer the size of this block in bytes */ - size: Quantity - /** the maximum gas allowed in this block */ - gasLimit: Quantity - /** the total used gas by all transactions in this block */ - gasUsed: Quantity - /** the unix timestamp for when the block was collated */ - timestamp: Quantity - /** Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter */ - transactions: (Hash | Transaction)[] - /** Array of uncle hashes */ - uncles: Hash[] - /** PoA-Fields */ - sealFields: Data[] -} -export type Log = { - /** true when the log was removed, due to a chain reorganization. false if its a valid log. */ - removed: boolean - /** integer of the log index position in the block. null when its pending log. */ - logIndex: Quantity - /** integer of the transactions index position log was created from. null when its pending log. */ - transactionIndex: Quantity - /** Hash, 32 Bytes - hash of the transactions this log was created from. null when its pending log. */ - transactionHash: Hash - /** Hash, 32 Bytes - hash of the block where this log was in. null when its pending. null when its pending log. */ - blockHash: Hash, - /** the block number where this log was in. null when its pending. null when its pending log. */ - blockNumber: Quantity - /** 20 Bytes - address from which this log originated. */ - address: Address, - /** contains the non-indexed arguments of the log. */ - data: Data - /** - Array of 0 to 4 32 Bytes DATA of indexed log arguments. (In solidity: The first topic is the hash of the signature of the event (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.) */ - topics: Data[] -} - -export type LogFilter = { - /** Quantity or Tag - (optional) (default: latest) Integer block number, or 'latest' for the last mined block or 'pending', 'earliest' for not yet mined transactions. */ - fromBlock: BlockType - /** Quantity or Tag - (optional) (default: latest) Integer block number, or 'latest' for the last mined block or 'pending', 'earliest' for not yet mined transactions.*/ - toBlock: BlockType - /** (optional) 20 Bytes - Contract address or a list of addresses from which logs should originate.*/ - address: Address - /** (optional) Array of 32 Bytes Data topics. Topics are order-dependent. It’s possible to pass in null to match any topic, or a subarray of multiple topics of which one should be matching. */ - topics: (string | string[])[] - /** å(optional) The maximum number of entries to retrieve (latest first). */ - limit: Quantity -} - -export type TxRequest = { - /** contract */ - to?: Address - - /** address of the account to use */ - from?: Address - - /** the data to send */ - data?: Data - - /** the gas needed */ - gas?: number - - /** the gasPrice used */ - gasPrice?: number - - /** the nonce */ - nonce?: number - - /** the value in wei */ - value?: Quantity - - /** the ABI of the method to be used */ - method?: string - - /** the argument to pass to the method */ - args?: any[] - - /**raw private key in order to sign */ - pk?: Hash - - /** number of block to wait before confirming*/ - confirmations?: number - - /** number of seconds to wait for confirmations before giving up. Default: 10 */ - timeout?: number -} -export interface Web3Event { - returnValues: { - [name: string]: any - }, - event: string, - signature: string, - logIndex: number - transactionIndex: number, - transactionHash: Hash, - address: Address - blockNumber: number - blockHash: Hash, - raw: { - data: Hex - topicx: Hash[] - } - - -} export declare interface Signer { /** optiional method which allows to change the transaction-data before sending it. This can be used for redirecting it through a multisig. */ @@ -806,282 +635,6 @@ export declare interface Signer { sign: (data: Hex, account: Address, hashFirst?: boolean, ethV?: boolean) => Promise } -export interface EthAPI { - client: IN3Generic; - signer?: Signer; - constructor(client: IN3Generic); - /** - * Returns the number of most recent block. (as number) - */ - blockNumber(): Promise; - /** - * Returns the current price per gas in wei. (as number) - */ - gasPrice(): Promise; - /** - * Executes a new message call immediately without creating a transaction on the block chain. - */ - call(tx: Transaction, block?: BlockType): Promise; - /** - * Executes a function of a contract, by passing a [method-signature](https://github.com/ethereumjs/ethereumjs-abi/blob/master/README.md#simple-encoding-and-decoding) and the arguments, which will then be ABI-encoded and send as eth_call. - */ - callFn(to: Address, method: string, ...args: any[]): Promise; - /** - * Returns the EIP155 chain ID used for transaction signing at the current best block. Null is returned if not available. - */ - chainId(): Promise; - /** - * Makes a call or transaction, which won’t be added to the blockchain and returns the used gas, which can be used for estimating the used gas. - */ - estimateGas(tx: Transaction): Promise; - /** - * Returns the balance of the account of given address in wei (as hex). - */ - getBalance(address: Address, block?: BlockType): Promise; - /** - * Returns code at a given address. - */ - getCode(address: Address, block?: BlockType): Promise; - /** - * Returns the value from a storage position at a given address. - */ - getStorageAt(address: Address, pos: Quantity, block?: BlockType): Promise; - /** - * Returns information about a block by hash. - */ - getBlockByHash(hash: Hash, includeTransactions?: boolean): Promise; - /** - * Returns information about a block by block number. - */ - getBlockByNumber(block?: BlockType, includeTransactions?: boolean): Promise; - /** - * Returns the number of transactions in a block from a block matching the given block hash. - */ - getBlockTransactionCountByHash(block: Hash): Promise; - /** - * Returns the number of transactions in a block from a block matching the given block number. - */ - getBlockTransactionCountByNumber(block: Hash): Promise; - /** - * Polling method for a filter, which returns an array of logs which occurred since last poll. - */ - getFilterChanges(id: Quantity): Promise; - /** - * Returns an array of all logs matching filter with given id. - */ - getFilterLogs(id: Quantity): Promise; - /** - * Returns an array of all logs matching a given filter object. - */ - getLogs(filter: LogFilter): Promise; - /** - * Returns information about a transaction by block hash and transaction index position. - */ - getTransactionByBlockHashAndIndex(hash: Hash, pos: Quantity): Promise; - /** - * Returns information about a transaction by block number and transaction index position. - */ - getTransactionByBlockNumberAndIndex(block: BlockType, pos: Quantity): Promise; - /** - * Returns the information about a transaction requested by transaction hash. - */ - getTransactionByHash(hash: Hash): Promise; - /** - * Returns the number of transactions sent from an address. (as number) - */ - getTransactionCount(address: Address, block?: BlockType): Promise; - /** - * Returns the receipt of a transaction by transaction hash. - * Note That the receipt is available even for pending transactions. - */ - getTransactionReceipt(hash: Hash): Promise; - /** - * Returns information about a uncle of a block by hash and uncle index position. - * Note: An uncle doesn’t contain individual transactions. - */ - getUncleByBlockHashAndIndex(hash: Hash, pos: Quantity): Promise; - /** - * Returns information about a uncle of a block number and uncle index position. - * Note: An uncle doesn’t contain individual transactions. - */ - getUncleByBlockNumberAndIndex(block: BlockType, pos: Quantity): Promise; - /** - * Returns the number of uncles in a block from a block matching the given block hash. - */ - getUncleCountByBlockHash(hash: Hash): Promise; - /** - * Returns the number of uncles in a block from a block matching the given block hash. - */ - getUncleCountByBlockNumber(block: BlockType): Promise; - /** - * Creates a filter in the node, to notify when a new block arrives. To check if the state has changed, call eth_getFilterChanges. - */ - newBlockFilter(): Promise; - /** - * Creates a filter object, based on filter options, to notify when the state changes (logs). To check if the state has changed, call eth_getFilterChanges. - * - * A note on specifying topic filters: - * Topics are order-dependent. A transaction with a log with topics [A, B] will be matched by the following topic filters: - * - * [] “anything” - * [A] “A in first position (and anything after)” - * [null, B] “anything in first position AND B in second position (and anything after)” - * [A, B] “A in first position AND B in second position (and anything after)” - * [[A, B], [A, B]] “(A OR B) in first position AND (A OR B) in second position (and anything after)” - */ - newFilter(filter: LogFilter): Promise; - /** - * Creates a filter in the node, to notify when new pending transactions arrive. - * - * To check if the state has changed, call eth_getFilterChanges. - */ - newPendingTransactionFilter(): Promise; - /** - * Uninstalls a filter with given id. Should always be called when watch is no longer needed. Additonally Filters timeout when they aren’t requested with eth_getFilterChanges for a period of time. - */ - uninstallFilter(id: Quantity): Promise; - /** - * Returns the current ethereum protocol version. - */ - protocolVersion(): Promise; - /** - * Returns the current ethereum protocol version. - */ - syncing(): Promise; - - /** - * resolves a name as an ENS-Domain. - * @param name the domain name - * @param type the type (currently only addr is supported) - * @param registry optionally the address of the registry (default is the mainnet ens registry) - */ - resolveENS(name: string, type: Address, registry?: string): Promise
; - - /** - * Creates new message call transaction or a contract creation for signed transactions. - */ - sendRawTransaction(data: Data): Promise; - /** - * signs any kind of message using the `\x19Ethereum Signed Message:\n`-prefix - * @param account the address to sign the message with (if this is a 32-bytes hex-string it will be used as private key) - * @param data the data to sign (Buffer, hexstring or utf8-string) - */ - sign(account: Address, data: Data): Promise; - /** sends a Transaction */ - sendTransaction(args: TxRequest): Promise; - - - - web3ContractAt(abi: ABI[], address?: Address, options?: { - gasPrice?: string | number | bigint, - gas?: string | number | bigint, - from?: Address, - data?: Hex - }): { - options: { - address: Address, - jsonInterface: ABI[], - gasPrice?: string | number | bigint, - gas?: string | number | bigint, - from?: Address, - data?: Hex, - transactionConfirmationBlocks: number, - transactionPollingTimeout: number - }, - methods: { - [methodName: string]: (...args: any) => { - call: (options?: { - gasPrice?: string | number | bigint, - gas?: string | number | bigint, - from?: Address, - }) => Promise, - send: (options?: { - gasPrice?: string | number | bigint, - gas?: string | number | bigint, - from?: Address, - value?: number | string | bigint - }) => Promise, - estimateGas: (options?: { - value?: string | number | bigint, - gas?: string | number | bigint, - from?: Address, - }) => Promise, - encodeABI: () => Hex - } - }, - - once: (eventName: string, options: {}, handler: (error?: Error, evData?: Web3Event) => void) => void, - - events: { - [eventName: string]: (options?: { - fromBlock?: number, - topics?: any[], - filter?: { [indexedName: string]: any } - }) => { - on: (ev: 'data' | 'error', handler: (ev: Web3Event | Error) => void) => any - once: (ev: 'data', handler: (ev: Web3Event) => void) => any - off: (ev: string, handler: (ev: any) => void) => any - } - }, - - getPastEvents(evName: string, options?: { - fromBlock?: number, - topics?: any[], - filter?: { [indexedName: string]: any } - }): Promise - - } - - contractAt(abi: ABI[], address?: Address): { - [methodName: string]: any; - _address: Address; - _eventHashes: any; - events: { - [event: string]: { - getLogs: (options: { - limit?: number; - fromBlock?: BlockType; - toBlock?: BlockType; - topics?: any[]; - filter?: { - [key: string]: any; - }; - }) => Promise<{ - [key: string]: any; - event: string; - log: Log; - }[]>; - }; - all: { - getLogs: (options: { - limit?: number; - fromBlock?: BlockType; - toBlock?: BlockType; - topics?: any[]; - filter?: { - [key: string]: any; - }; - }) => Promise<{ - [key: string]: any; - event: string; - log: Log; - }[]>; - }; - decode: any; - }; - _abi: ABI[]; - _in3: IN3Generic; - }; - decodeEventData(log: Log, d: ABI): any; - hashMessage(data: Data): Hex; -} export declare class SimpleSigner implements Signer { accounts: { [ac: string]: BufferType; @@ -1200,193 +753,4 @@ export declare interface Utils { private2address(pk: Hex | BufferType): Address } -/** - * API for storing and retrieving IPFS-data. - */ -export declare interface IpfsAPI { - /** - * retrieves the content for a hash from IPFS. - * @param multihash the IPFS-hash to fetch - * - */ - get(multihash: string): Promise - /** - * stores the data on ipfs and returns the IPFS-Hash. - * @param content puts a IPFS content - */ - put(content: BufferType): Promise -} - -/** - * a Input of a Bitcoin Transaction - */ -export declare interface BtcTransactionInput { - /** the transaction id */ - txid: Hash - - /** the index of the transactionoutput */ - vout: number - - /** the script */ - scriptSig: { - /** the asm data */ - asm: Data - - /** the raw hex data */ - hex: Data - } - - /** The script sequence number */ - sequence: number - - /** hex-encoded witness data (if any) */ - txinwitness: Data[] -} -/** - * a Input of a Bitcoin Transaction - */ -export declare interface BtcTransactionOutput { - /** the value in BTC */ - value: number - - /** the index */ - n: number - - /** the index of the transactionoutput */ - vout: number - - /** the script */ - scriptPubKey: { - /** the asm data */ - asm: Data - - /** the raw hex data */ - hex: Data - - /** the required sigs */ - reqSigs: number - - /** The type, eg 'pubkeyhash' */ - type: string - - /** list of addresses */ - addresses: Address[] - } -} - -/** - * a BitCoin Transaction. - */ -export declare interface BtcTransaction { - /** true if this transaction is part of the longest chain */ - in_active_chain: boolean - - /** the hex representation of raw data*/ - hex: Data - - /** The requested transaction id. */ - txid: Hash - - /** The transaction hash (differs from txid for witness transactions) */ - hash: Hash - - /** The serialized transaction size */ - size: number - - /** The virtual transaction size (differs from size for witness transactions) */ - vsize: number - /** The transaction’s weight (between vsize4-3 and vsize4) */ - weight: number - - /** The version */ - version: number - - /** The locktime */ - locktime: number - - /** the block hash of the block containing this transaction. */ - blockhash: Hash - - /** The confirmations. */ - confirmations: number - - /** The transaction time in seconds since epoch (Jan 1 1970 GMT) */ - time: number - - /** The block time in seconds since epoch (Jan 1 1970 GMT) */ - blocktime: number - - /** the transaction inputs */ - vin: BtcTransactionInput[] - - /** the transaction outputs */ - vout: BtcTransactionOutput[] - -} -/** a Block header */ -export interface BTCBlockHeader { - /** the hash of the blockheader */ - hash: string, - /** number of confirmations or blocks mined on top of the containing block*/ - confirmations: number, - /** block number */ - height: number, - /** used version */ - version: number, - /** version as hex */ - versionHex: string, - /** merkle root of the trie of all transactions in the block */ - merkleroot: string, - /** unix timestamp in seconds since 1970 */ - time: string, - /** unix timestamp in seconds since 1970 */ - mediantime: string, - /** nonce-field of the block */ - nonce: number, - /** bits (target) for the block as hex*/ - bits: string, - /** difficulty of the block */ - difficulty: number, - /** total amount of work since genesis */ - chainwork: string, - /** number of transactions in the block */ - nTx: number, - /** hash of the parent blockheader */ - previousblockhash: string, - /** hash of the next blockheader */ - nextblockhash: string -} - -/** a full Block including the transactions */ -export interface BTCBlock extends BTCBlockHeader { - /** the transactions */ - tx: T[] -} - - -/** - * API for handling BitCoin data - */ -export declare interface BtcAPI { - /** retrieves the transaction and returns the data as json. */ - getTransaction(txid: Hash): Promise - - /** retrieves the serialized transaction (bytes) */ - getTransactionBytes(txid: Hash): Promise - - /** retrieves the blockheader and returns the data as json. */ - getBlockHeader(blockHash: Hash): Promise - - /** retrieves the serialized blockheader (bytes) */ - getBlockHeaderBytes(blockHash: Hash): Promise - - /** retrieves the block including all tx data as json. */ - getBlockWithTxData(blockHash: Hash): Promise> - - /** retrieves the block including all tx ids as json. */ - getBlockWithTxIds(blockHash: Hash): Promise> - - /** retrieves the serialized block (bytes) including all transactions */ - getBlockBytes(blockHash: Hash): Promise -} diff --git a/wasm/src/in3.js b/wasm/src/in3.js index db4395780..017f4aa4e 100644 --- a/wasm/src/in3.js +++ b/wasm/src/in3.js @@ -123,11 +123,15 @@ function getVersion() { } // keep track of all created client instances -const clients = {} +const clients = in3w.clients = {} +in3w.promises = {} +in3w.promiseCount = 0; +in3w.extensions = [] // create a flag indicating when the wasm was succesfully loaded. let _in3_listeners = [] in3w.onRuntimeInitialized = _ => { + in3w.ccall('wasm_init', 'void', [], []); const o = _in3_listeners _in3_listeners = undefined o.forEach(_ => _(true)) @@ -157,6 +161,7 @@ class IN3 { if (chainId === 'ewc') chainId = '0xf6' this.ptr = in3w.ccall('in3_create', 'number', ['number'], [parseInt(chainId) || 0]); clients['' + this.ptr] = this + this.plugins.forEach(_ => this.registerPlugin(_)) } // here we are creating the instance lazy, when the first function is called. @@ -165,9 +170,8 @@ class IN3 { this.config = config ? { ...def, ...config } : def this.needsSetConfig = !!config this.ptr = 0; - this.eth = new EthAPI(this) - this.ipfs = new IpfsAPI(this) - this.btc = new BtcAPI(this) + in3w.extensions.forEach(_ => _(this)) + this.plugins = [] } /** @@ -181,6 +185,10 @@ class IN3 { } this.needsSetConfig = !this.ptr if (this.ptr) { + if (this.config.transport) { + this.transport = this.config.transport + delete this.config.transport + } const r = in3w.ccall('in3_config', 'number', ['number', 'string'], [this.ptr, JSON.stringify(this.config)]); if (r) { const ex = new Error(UTF8ToString(r)) @@ -204,6 +212,25 @@ class IN3 { return p } + registerPlugin(plgn) { + let action = 0 + if (plgn.term) action |= 0x2 + if (plgn.getAccount) action |= 0x20 + if (plgn.handleRPC) action |= 0x100 + if (plgn.verifyRPC) action |= 0x200 + if (plgn.cacheGet) action |= 0x800 + if (plgn.cacheSet) action |= 0x400 + if (plgn.cacheClear) action |= 0x1000 + let index = this.plugins.indexOf(plgn) + if (index == -1) { + index = this.plugins.length + this.plugins.push(plgn) + } + + if (this.ptr) + in3w.ccall('wasm_register_plugin', 'number', ['number', 'number', 'number'], [this.ptr, action, index]); + } + /** * sends a request and returns the response. @@ -232,7 +259,14 @@ class IN3 { // main async loop // we repeat it until we have a result while (this.ptr && !this.delayFree) { - const state = JSON.parse(call_string('ctx_execute', r).replace(/\n/g, ' > ')) + const js = call_string('ctx_execute', r).replace(/\n/g, ' > ') + let state; + try { + state = JSON.parse(js) + } + catch (x) { + throw new Error("Invalid json:", js) + } switch (state.status) { case 'error': throw new Error(state.error || 'Unknown error') @@ -262,7 +296,10 @@ class IN3 { case 'rpc': if (req.wait) await new Promise(r => setTimeout(r, req.wait)) - await getNextResponse(responses, req) + if (req.urls[0].startsWith("promise://")) + await resolvePromises(req.ctx, req.urls[0]) + else + await getNextResponse(responses, req) } } @@ -283,7 +320,7 @@ class IN3 { } - async sendRPC(method, params) { + async sendRPC(method, params = []) { const res = await this.sendRequest({ method, params }) if (res.error) throw new Error(res.error.message || res.error) return res.result @@ -295,13 +332,25 @@ class IN3 { if (this.pending) this.delayFree = true else if (this.ptr) { - delete clients['' + this.ptr] in3w.ccall('in3_dispose', 'void', ['number'], [this.ptr]) + delete clients['' + this.ptr] this.ptr = 0 } } } +async function resolvePromises(ctx, url) { + const pid = url.substr(10) + const p = in3w.promises[pid] + if (!p) + setResponse(ctx, JSON.stringify({ error: { message: 'could not find the requested proomise' } }), 0, false) + else { + delete in3w.promises[pid] + return p + .then(r => setResponse(ctx, JSON.stringify({ result: r }), 0, false)) + .catch(e => setResponse(ctx, JSON.stringify({ error: { message: e.message || e } }), 0, false)) + } +} function cleanUpResponses(responses, ptr) { Object.keys(responses).forEach(ctx => responses[ctx].cleanUp(ptr)) @@ -316,7 +365,8 @@ function url_queue(req) { let counter = 0 const promises = [], responses = [] if (req.in3.config.debug) console.log("send req (" + req.ctx + ") to " + req.urls.join() + ' : ', JSON.stringify(req.payload, null, 2)) - req.urls.forEach((url, i) => in3w.transport(url, JSON.stringify(req.payload), req.timeout || 30000).then( + const transport = req.in3.transport || in3w.transport + req.urls.forEach((url, i) => transport(url, JSON.stringify(req.payload), req.timeout || 30000).then( response => { responses.push({ i, url, response }); trigger() }, error => { responses.push({ i, url, error }); trigger() } )) @@ -444,4 +494,5 @@ function setResponse(ctx, msg, i, isError) { function check_ready() { if (_in3_listeners) throw new Error('The Incubed wasm runtime is not initialized yet! Please use onInit() to execute it when ready.') -} \ No newline at end of file +} + diff --git a/wasm/src/in3_util.js b/wasm/src/in3_util.js index d0f3d33dd..df15bff91 100644 --- a/wasm/src/in3_util.js +++ b/wasm/src/in3_util.js @@ -408,20 +408,19 @@ function padEnd(val, minLength, fill = ' ') { } function soliditySha3(...args) { - - const abiCoder = new AbiCoder() - return toHex(keccak(abiCoder.encode(args.map(_ => { + return toHex(keccak('0x' + toHex(abiEncode('_(' + args.map(_ => { switch (typeof (_)) { case 'number': + case 'bigint': return _ < 0 ? 'int256' : 'uint256' case 'string': return _.substr(0, 2) === '0x' ? 'bytes' : 'string' case 'boolean': return 'bool' default: - return BN.isBN(_) ? 'uint256' : 'bytes' + return 'bytes' } - }), args.map(encodeEtheresBN)))) + }).join() + ')', args)).substr(10))) } function createSignatureHash(def) { diff --git a/wasm/src/modules/btc.d.ts b/wasm/src/modules/btc.d.ts new file mode 100644 index 000000000..3b4ff3265 --- /dev/null +++ b/wasm/src/modules/btc.d.ts @@ -0,0 +1,194 @@ + +/** + * a Input of a Bitcoin Transaction + */ +export declare interface BtcTransactionInput { + /** the transaction id */ + txid: Hash + + /** the index of the transactionoutput */ + vout: number + + /** the script */ + scriptSig: { + /** the asm data */ + asm: Data + + /** the raw hex data */ + hex: Data + } + + /** The script sequence number */ + sequence: number + + /** hex-encoded witness data (if any) */ + txinwitness: Data[] +} +/** + * a Input of a Bitcoin Transaction + */ +export declare interface BtcTransactionOutput { + /** the value in BTC */ + value: number + + /** the index */ + n: number + + /** the index of the transactionoutput */ + vout: number + + /** the script */ + scriptPubKey: { + /** the asm data */ + asm: Data + + /** the raw hex data */ + hex: Data + + /** the required sigs */ + reqSigs: number + + /** The type, eg 'pubkeyhash' */ + type: string + + /** list of addresses */ + addresses: Address[] + } +} + +/** + * a BitCoin Transaction. + */ +export declare interface BtcTransaction { + /** true if this transaction is part of the longest chain */ + in_active_chain: boolean + + /** the hex representation of raw data*/ + hex: Data + + /** The requested transaction id. */ + txid: Hash + + /** The transaction hash (differs from txid for witness transactions) */ + hash: Hash + + /** The serialized transaction size */ + size: number + + /** The virtual transaction size (differs from size for witness transactions) */ + vsize: number + + /** The transaction’s weight (between vsize4-3 and vsize4) */ + weight: number + + /** The version */ + version: number + + /** The locktime */ + locktime: number + + /** the block hash of the block containing this transaction. */ + blockhash: Hash + + /** The confirmations. */ + confirmations: number + + /** The transaction time in seconds since epoch (Jan 1 1970 GMT) */ + time: number + + /** The block time in seconds since epoch (Jan 1 1970 GMT) */ + blocktime: number + + /** the transaction inputs */ + vin: BtcTransactionInput[] + + /** the transaction outputs */ + vout: BtcTransactionOutput[] + +} +/** a Block header */ +export interface BTCBlockHeader { + /** the hash of the blockheader */ + hash: string, + /** number of confirmations or blocks mined on top of the containing block*/ + confirmations: number, + /** block number */ + height: number, + /** used version */ + version: number, + /** version as hex */ + versionHex: string, + /** merkle root of the trie of all transactions in the block */ + merkleroot: string, + /** unix timestamp in seconds since 1970 */ + time: string, + /** unix timestamp in seconds since 1970 */ + mediantime: string, + /** nonce-field of the block */ + nonce: number, + /** bits (target) for the block as hex*/ + bits: string, + /** difficulty of the block */ + difficulty: number, + /** total amount of work since genesis */ + chainwork: string, + /** number of transactions in the block */ + nTx: number, + /** hash of the parent blockheader */ + previousblockhash: string, + /** hash of the next blockheader */ + nextblockhash: string +} + +/** a full Block including the transactions */ +export interface BTCBlock extends BTCBlockHeader { + /** the transactions */ + tx: T[] +} + + +/** + * API for handling BitCoin data + */ +export declare interface BtcAPI { + /** retrieves the transaction and returns the data as json. */ + getTransaction(txid: Hash): Promise + + /** retrieves the serialized transaction (bytes) */ + getTransactionBytes(txid: Hash): Promise + + /** retrieves the blockheader and returns the data as json. */ + getBlockHeader(blockHash: Hash): Promise + + /** retrieves the serialized blockheader (bytes) */ + getBlockHeaderBytes(blockHash: Hash): Promise + + /** retrieves the block including all tx data as json. */ + getBlockWithTxData(blockHash: Hash): Promise> + + /** retrieves the block including all tx ids as json. */ + getBlockWithTxIds(blockHash: Hash): Promise> + + /** retrieves the serialized block (bytes) including all transactions */ + getBlockBytes(blockHash: Hash): Promise +} + +/** + * bitcoin configuration. + */ +export declare interface btc_config { + /** + * max number of DAPs (Difficulty Adjustment Periods) allowed when accepting new targets. + */ + maxDAP?: number + + /** + * max increase (in percent) of the difference between targets when accepting new targets. + */ + maxDiff?: number + +} + +/* +public btc:BtcAPI + */ \ No newline at end of file diff --git a/wasm/src/in3_btc_api.js b/wasm/src/modules/btc.js similarity index 97% rename from wasm/src/in3_btc_api.js rename to wasm/src/modules/btc.js index 3c330ad9c..d66ae0ec0 100644 --- a/wasm/src/in3_btc_api.js +++ b/wasm/src/modules/btc.js @@ -1,3 +1,5 @@ +in3w.extensions.push(c => c.btc = new BtcAPI(c)) + class BtcAPI { constructor(client) { this.client = client diff --git a/wasm/src/modules/eth.d.ts b/wasm/src/modules/eth.d.ts new file mode 100644 index 000000000..e63fdb48d --- /dev/null +++ b/wasm/src/modules/eth.d.ts @@ -0,0 +1,485 @@ +export type TransactionReceipt = { + /** 32 Bytes - hash of the block where this transaction was in. */ + blockHash: Hash + /** block number where this transaction was in.*/ + blockNumber: BlockType + /** 20 Bytes - The contract address created, if the transaction was a contract creation, otherwise null.*/ + contractAddress: Address + /** The total amount of gas used when this transaction was executed in the block. */ + cumulativeGasUsed: Quantity + /** 20 Bytes - The address of the sender. */ + from: Address + /** 20 Bytes - The address of the receiver. null when it’s a contract creation transaction.*/ + to: Address + /** The amount of gas used by this specific transaction alone. */ + gasUsed: Quantity + /** Array of log objects, which this transaction generated. */ + logs: Log[] + /** 256 Bytes - A bloom filter of logs/events generated by contracts during transaction execution. Used to efficiently rule out transactions without expected logs.*/ + logsBloom: Data + /** 32 Bytes - Merkle root of the state trie after the transaction has been executed (optional after Byzantium hard fork EIP609)*/ + root: Hash + /** 0x0 indicates transaction failure , 0x1 indicates transaction success. Set for blocks mined after Byzantium hard fork EIP609, null before. */ + status: Quantity + /** 32 Bytes - hash of the transaction. */ + transactionHash: Hash + /** Integer of the transactions index position in the block. */ + transactionIndex: Quantity +} +export type TransactionDetail = { + /** 32 Bytes - hash of the transaction. */ + hash: Hash + /** the number of transactions made by the sender prior to this one.*/ + nonce: Quantity + /** 32 Bytes - hash of the block where this transaction was in. null when its pending.*/ + blockHash: Hash + /** block number where this transaction was in. null when its pending.*/ + blockNumber: BlockType + /** integer of the transactions index position in the block. null when its pending.*/ + transactionIndex: Quantity + /** 20 Bytes - address of the sender.*/ + from: Address + /** 20 Bytes - address of the receiver. null when its a contract creation transaction. */ + to: Address + /** value transferred in Wei.*/ + value: Quantity + /** gas price provided by the sender in Wei.*/ + gasPrice: Quantity + /** gas provided by the sender. */ + gas: Quantity + /** the data send along with the transaction. */ + input: Data + /** the standardised V field of the signature.*/ + v: Quantity + /** the standardised V field of the signature (0 or 1).*/ + standardV: Quantity + /** the R field of the signature.*/ + r: Quantity + /** raw transaction data */ + raw: Data + /** public key of the signer. */ + publicKey: Hash + /** the chain id of the transaction, if any. */ + chainId: Quantity + /** creates contract address */ + creates: Address + /** (optional) conditional submission, Block number in block or timestamp in time or null. (parity-feature) */ + condition: any + /** optional: the private key to use for signing */ + pk?: any +} + +export type Block = { + /** The block number. null when its pending block */ + number: Quantity + /** hash of the block. null when its pending block */ + hash: Hash + /** hash of the parent block */ + parentHash: Hash + /** 8 bytes hash of the generated proof-of-work. null when its pending block. Missing in case of PoA. */ + nonce: Data + /** SHA3 of the uncles data in the block */ + sha3Uncles: Data + /** 256 Bytes - the bloom filter for the logs of the block. null when its pending block */ + logsBloom: Data + /** 32 Bytes - the root of the transaction trie of the block */ + transactionsRoot: Data + /** 32 Bytes - the root of the final state trie of the block */ + stateRoot: Data + /** 32 Bytes - the root of the receipts trie of the block */ + receiptsRoot: Data + /** 20 Bytes - the address of the author of the block (the beneficiary to whom the mining rewards were given)*/ + author: Address + /** 20 Bytes - alias of ‘author’*/ + miner: Address + /** integer of the difficulty for this block */ + difficulty: Quantity + /** integer of the total difficulty of the chain until this block */ + totalDifficulty: Quantity + /** the ‘extra data’ field of this block */ + extraData: Data + /** integer the size of this block in bytes */ + size: Quantity + /** the maximum gas allowed in this block */ + gasLimit: Quantity + /** the total used gas by all transactions in this block */ + gasUsed: Quantity + /** the unix timestamp for when the block was collated */ + timestamp: Quantity + /** Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter */ + transactions: (Hash | Transaction)[] + /** Array of uncle hashes */ + uncles: Hash[] + /** PoA-Fields */ + sealFields: Data[] +} +export type Log = { + /** true when the log was removed, due to a chain reorganization. false if its a valid log. */ + removed: boolean + /** integer of the log index position in the block. null when its pending log. */ + logIndex: Quantity + /** integer of the transactions index position log was created from. null when its pending log. */ + transactionIndex: Quantity + /** Hash, 32 Bytes - hash of the transactions this log was created from. null when its pending log. */ + transactionHash: Hash + /** Hash, 32 Bytes - hash of the block where this log was in. null when its pending. null when its pending log. */ + blockHash: Hash, + /** the block number where this log was in. null when its pending. null when its pending log. */ + blockNumber: Quantity + /** 20 Bytes - address from which this log originated. */ + address: Address, + /** contains the non-indexed arguments of the log. */ + data: Data + /** - Array of 0 to 4 32 Bytes DATA of indexed log arguments. (In solidity: The first topic is the hash of the signature of the event (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.) */ + topics: Data[] +} + +export type LogFilter = { + /** Quantity or Tag - (optional) (default: latest) Integer block number, or 'latest' for the last mined block or 'pending', 'earliest' for not yet mined transactions. */ + fromBlock: BlockType + /** Quantity or Tag - (optional) (default: latest) Integer block number, or 'latest' for the last mined block or 'pending', 'earliest' for not yet mined transactions.*/ + toBlock: BlockType + /** (optional) 20 Bytes - Contract address or a list of addresses from which logs should originate.*/ + address: Address + /** (optional) Array of 32 Bytes Data topics. Topics are order-dependent. It’s possible to pass in null to match any topic, or a subarray of multiple topics of which one should be matching. */ + topics: (string | string[])[] + /** å(optional) The maximum number of entries to retrieve (latest first). */ + limit: Quantity +} + +export type TxRequest = { + /** contract */ + to?: Address + + /** address of the account to use */ + from?: Address + + /** the data to send */ + data?: Data + + /** the gas needed */ + gas?: number + + /** the gasPrice used */ + gasPrice?: number + + /** the nonce */ + nonce?: number + + /** the value in wei */ + value?: Quantity + + /** the ABI of the method to be used */ + method?: string + + /** the argument to pass to the method */ + args?: any[] + + /**raw private key in order to sign */ + pk?: Hash + + /** number of block to wait before confirming*/ + confirmations?: number + + /** number of seconds to wait for confirmations before giving up. Default: 10 */ + timeout?: number +} + +export interface Web3Event { + returnValues: { + [name: string]: any + }, + event: string, + signature: string, + logIndex: number + transactionIndex: number, + transactionHash: Hash, + address: Address + blockNumber: number + blockHash: Hash, + raw: { + data: Hex + topicx: Hash[] + } + + +} + + + +export interface EthAPI { + client: IN3Generic; + signer?: Signer; + constructor(client: IN3Generic); + /** + * Returns the number of most recent block. (as number) + */ + blockNumber(): Promise; + /** + * Returns the current price per gas in wei. (as number) + */ + gasPrice(): Promise; + /** + * Executes a new message call immediately without creating a transaction on the block chain. + */ + call(tx: Transaction, block?: BlockType): Promise; + /** + * Executes a function of a contract, by passing a [method-signature](https://github.com/ethereumjs/ethereumjs-abi/blob/master/README.md#simple-encoding-and-decoding) and the arguments, which will then be ABI-encoded and send as eth_call. + */ + callFn(to: Address, method: string, ...args: any[]): Promise; + /** + * Returns the EIP155 chain ID used for transaction signing at the current best block. Null is returned if not available. + */ + chainId(): Promise; + /** + * Makes a call or transaction, which won’t be added to the blockchain and returns the used gas, which can be used for estimating the used gas. + */ + estimateGas(tx: Transaction): Promise; + /** + * Returns the balance of the account of given address in wei (as hex). + */ + getBalance(address: Address, block?: BlockType): Promise; + /** + * Returns code at a given address. + */ + getCode(address: Address, block?: BlockType): Promise; + /** + * Returns the value from a storage position at a given address. + */ + getStorageAt(address: Address, pos: Quantity, block?: BlockType): Promise; + /** + * Returns information about a block by hash. + */ + getBlockByHash(hash: Hash, includeTransactions?: boolean): Promise; + /** + * Returns information about a block by block number. + */ + getBlockByNumber(block?: BlockType, includeTransactions?: boolean): Promise; + /** + * Returns the number of transactions in a block from a block matching the given block hash. + */ + getBlockTransactionCountByHash(block: Hash): Promise; + /** + * Returns the number of transactions in a block from a block matching the given block number. + */ + getBlockTransactionCountByNumber(block: Hash): Promise; + /** + * Polling method for a filter, which returns an array of logs which occurred since last poll. + */ + getFilterChanges(id: Quantity): Promise; + /** + * Returns an array of all logs matching filter with given id. + */ + getFilterLogs(id: Quantity): Promise; + /** + * Returns an array of all logs matching a given filter object. + */ + getLogs(filter: LogFilter): Promise; + /** + * Returns information about a transaction by block hash and transaction index position. + */ + getTransactionByBlockHashAndIndex(hash: Hash, pos: Quantity): Promise; + /** + * Returns information about a transaction by block number and transaction index position. + */ + getTransactionByBlockNumberAndIndex(block: BlockType, pos: Quantity): Promise; + /** + * Returns the information about a transaction requested by transaction hash. + */ + getTransactionByHash(hash: Hash): Promise; + /** + * Returns the number of transactions sent from an address. (as number) + */ + getTransactionCount(address: Address, block?: BlockType): Promise; + /** + * Returns the receipt of a transaction by transaction hash. + * Note That the receipt is available even for pending transactions. + */ + getTransactionReceipt(hash: Hash): Promise; + /** + * Returns information about a uncle of a block by hash and uncle index position. + * Note: An uncle doesn’t contain individual transactions. + */ + getUncleByBlockHashAndIndex(hash: Hash, pos: Quantity): Promise; + /** + * Returns information about a uncle of a block number and uncle index position. + * Note: An uncle doesn’t contain individual transactions. + */ + getUncleByBlockNumberAndIndex(block: BlockType, pos: Quantity): Promise; + /** + * Returns the number of uncles in a block from a block matching the given block hash. + */ + getUncleCountByBlockHash(hash: Hash): Promise; + /** + * Returns the number of uncles in a block from a block matching the given block hash. + */ + getUncleCountByBlockNumber(block: BlockType): Promise; + /** + * Creates a filter in the node, to notify when a new block arrives. To check if the state has changed, call eth_getFilterChanges. + */ + newBlockFilter(): Promise; + /** + * Creates a filter object, based on filter options, to notify when the state changes (logs). To check if the state has changed, call eth_getFilterChanges. + * + * A note on specifying topic filters: + * Topics are order-dependent. A transaction with a log with topics [A, B] will be matched by the following topic filters: + * + * [] “anything” + * [A] “A in first position (and anything after)” + * [null, B] “anything in first position AND B in second position (and anything after)” + * [A, B] “A in first position AND B in second position (and anything after)” + * [[A, B], [A, B]] “(A OR B) in first position AND (A OR B) in second position (and anything after)” + */ + newFilter(filter: LogFilter): Promise; + /** + * Creates a filter in the node, to notify when new pending transactions arrive. + * + * To check if the state has changed, call eth_getFilterChanges. + */ + newPendingTransactionFilter(): Promise; + /** + * Uninstalls a filter with given id. Should always be called when watch is no longer needed. Additonally Filters timeout when they aren’t requested with eth_getFilterChanges for a period of time. + */ + uninstallFilter(id: Quantity): Promise; + /** + * Returns the current ethereum protocol version. + */ + protocolVersion(): Promise; + /** + * Returns the current ethereum protocol version. + */ + syncing(): Promise; + + /** + * resolves a name as an ENS-Domain. + * @param name the domain name + * @param type the type (currently only addr is supported) + * @param registry optionally the address of the registry (default is the mainnet ens registry) + */ + resolveENS(name: string, type: Address, registry?: string): Promise
; + + /** + * Creates new message call transaction or a contract creation for signed transactions. + */ + sendRawTransaction(data: Data): Promise; + /** + * signs any kind of message using the `\x19Ethereum Signed Message:\n`-prefix + * @param account the address to sign the message with (if this is a 32-bytes hex-string it will be used as private key) + * @param data the data to sign (Buffer, hexstring or utf8-string) + */ + sign(account: Address, data: Data): Promise; + /** sends a Transaction */ + sendTransaction(args: TxRequest): Promise; + + + + web3ContractAt(abi: ABI[], address?: Address, options?: { + gasPrice?: string | number | bigint, + gas?: string | number | bigint, + from?: Address, + data?: Hex + }): { + options: { + address: Address, + jsonInterface: ABI[], + gasPrice?: string | number | bigint, + gas?: string | number | bigint, + from?: Address, + data?: Hex, + transactionConfirmationBlocks: number, + transactionPollingTimeout: number + }, + methods: { + [methodName: string]: (...args: any) => { + call: (options?: { + gasPrice?: string | number | bigint, + gas?: string | number | bigint, + from?: Address, + }) => Promise, + send: (options?: { + gasPrice?: string | number | bigint, + gas?: string | number | bigint, + from?: Address, + value?: number | string | bigint + }) => Promise, + estimateGas: (options?: { + value?: string | number | bigint, + gas?: string | number | bigint, + from?: Address, + }) => Promise, + encodeABI: () => Hex + } + }, + + once: (eventName: string, options: {}, handler: (error?: Error, evData?: Web3Event) => void) => void, + + events: { + [eventName: string]: (options?: { + fromBlock?: number, + topics?: any[], + filter?: { [indexedName: string]: any } + }) => { + on: (ev: 'data' | 'error', handler: (ev: Web3Event | Error) => void) => any + once: (ev: 'data', handler: (ev: Web3Event) => void) => any + off: (ev: string, handler: (ev: any) => void) => any + } + }, + + getPastEvents(evName: string, options?: { + fromBlock?: number, + topics?: any[], + filter?: { [indexedName: string]: any } + }): Promise + + } + + contractAt(abi: ABI[], address?: Address): { + [methodName: string]: any; + _address: Address; + _eventHashes: any; + events: { + [event: string]: { + getLogs: (options: { + limit?: number; + fromBlock?: BlockType; + toBlock?: BlockType; + topics?: any[]; + filter?: { + [key: string]: any; + }; + }) => Promise<{ + [key: string]: any; + event: string; + log: Log; + }[]>; + }; + all: { + getLogs: (options: { + limit?: number; + fromBlock?: BlockType; + toBlock?: BlockType; + topics?: any[]; + filter?: { + [key: string]: any; + }; + }) => Promise<{ + [key: string]: any; + event: string; + log: Log; + }[]>; + }; + decode: any; + }; + _abi: ABI[]; + _in3: IN3Generic; + }; + decodeEventData(log: Log, d: ABI): any; + hashMessage(data: Data): Hex; +} \ No newline at end of file diff --git a/wasm/src/in3_eth_api.js b/wasm/src/modules/eth.js similarity index 99% rename from wasm/src/in3_eth_api.js rename to wasm/src/modules/eth.js index 5a07d2a93..19b0e42b9 100644 --- a/wasm/src/in3_eth_api.js +++ b/wasm/src/modules/eth.js @@ -1,3 +1,5 @@ +in3w.extensions.push(c => c.eth = new EthAPI(c)) + class EthAPI { constructor(client) { this.client = client } @@ -443,9 +445,6 @@ class EthAPI { } - - - contractAt(abi, address) { const api = this, ob = { _address: address, _eventHashes: {}, events: {}, _abi: abi, _in3: this.client } for (const def of abi.filter(_ => _.type == 'function')) { diff --git a/wasm/src/modules/ipfs.d.ts b/wasm/src/modules/ipfs.d.ts new file mode 100644 index 000000000..a1c0e1f38 --- /dev/null +++ b/wasm/src/modules/ipfs.d.ts @@ -0,0 +1,20 @@ + +/** + * API for storing and retrieving IPFS-data. + */ +export declare interface IpfsAPI { + /** + * retrieves the content for a hash from IPFS. + * @param multihash the IPFS-hash to fetch + * + */ + get(multihash: string): Promise + /** + * stores the data on ipfs and returns the IPFS-Hash. + * @param content puts a IPFS content + */ + put(content: BufferType): Promise +} +/* +public ipfs:IpfsAPI + */ \ No newline at end of file diff --git a/wasm/src/in3_ipfs_api.js b/wasm/src/modules/ipfs.js similarity index 94% rename from wasm/src/in3_ipfs_api.js rename to wasm/src/modules/ipfs.js index f9f807285..526d3a219 100644 --- a/wasm/src/in3_ipfs_api.js +++ b/wasm/src/modules/ipfs.js @@ -1,3 +1,5 @@ +in3w.extensions.push(c => c.ipfs = new IpfsAPI(c)) + class IpfsAPI { constructor(client) { this.client = client diff --git a/wasm/src/modules/zksync.d.ts b/wasm/src/modules/zksync.d.ts new file mode 100644 index 000000000..4e79ed2ed --- /dev/null +++ b/wasm/src/modules/zksync.d.ts @@ -0,0 +1,28 @@ + +/** + * API for zksync. + */ +export declare interface ZksyncAPI { +} + + +/** + * zksync configuration. + */ +export declare interface zksync_config { + /** + * max number of DAPs (Difficulty Adjustment Periods) allowed when accepting new targets. + */ + maxDAP?: number + + /** + * max increase (in percent) of the difference between targets when accepting new targets. + */ + maxDiff?: number + +} + + +/* +public zksync:ZksyncAPI + */ \ No newline at end of file diff --git a/wasm/src/modules/zksync.js b/wasm/src/modules/zksync.js new file mode 100644 index 000000000..8e5af02ce --- /dev/null +++ b/wasm/src/modules/zksync.js @@ -0,0 +1,8 @@ +in3w.extensions.push(c => c.zksync = new ZksyncAPI(c)) + +class ZksyncAPI { + constructor(client) { + this.client = client + } + +} \ No newline at end of file diff --git a/wasm/src/wasm.c b/wasm/src/wasm.c index 8e4e97947..e89923be8 100644 --- a/wasm/src/wasm.c +++ b/wasm/src/wasm.c @@ -32,10 +32,10 @@ * with this program. If not, see . *******************************************************************************/ #include "../../c/src/core/client/cache.h" -#include "../../c/src/core/client/client.h" #include "../../c/src/core/client/context_internal.h" #include "../../c/src/core/client/keys.h" #include "../../c/src/core/client/nodelist.h" +#include "../../c/src/core/client/plugin.h" #include "../../c/src/core/client/version.h" #include "../../c/src/core/util/mem.h" #include "../../c/src/third-party/crypto/ecdsa.h" @@ -95,6 +95,7 @@ EM_JS(void, in3_cache_set, (const char* key, char* val), { Module.in3_cache.set(UTF8ToString(key),UTF8ToString(val)); }) + char* EMSCRIPTEN_KEEPALIVE in3_version() { return IN3_VERSION; } @@ -114,6 +115,82 @@ void storage_set_item(void* cptr, const char* key, bytes_t* content) { in3_cache_set(key, buffer); } +EM_JS(int, plgn_exec_term, (in3_t * c, int index), { + var client = Module.clients[c]; + var plgn = client && client.plugins[index]; + if (!plgn) return -4; + return plgn.term(client) || 0; +}) + +EM_JS(int, plgn_exec_rpc_handle, (in3_t * c, in3_ctx_t* ctx, char* req, int index), { + var client = Module.clients[c]; + var plgn = client && client.plugins[index]; + if (!plgn) return -4; + try { + var json = JSON.parse(UTF8ToString(req)); + var val = plgn.handleRPC(client, json); + if (typeof(val) == "undefined") return -17; + if (!val.then) val = Promise.resolve(val); + var id = ++in3w.promiseCount; + in3w.promises["" + id] = val; + json.in3 = {rpc : "promise://" + id}; + in3w.ccall("wasm_set_request_ctx", "void", [ "number", "string" ], [ ctx, JSON.stringify(json) ]); + } catch (x) { + setResponse(ctx, JSON.stringify({error : {message : x.message || x}}), 0, false) + } + return 0; +}) + +/** + * the main plgn-function which is called for each js-plugin, + * delegating it depending on the action. + */ +in3_ret_t wasm_plgn(void* data, in3_plugin_act_t action, void* ctx) { + // we use the custom data pointer of the plugin as index within the clients plugin array + int index = (int) data; + + switch (action) { + case PLGN_ACT_INIT: return IN3_OK; + case PLGN_ACT_TERM: return plgn_exec_term(ctx, index); + case PLGN_ACT_RPC_HANDLE: { + // extract the request as string, so we can pass it to js + in3_rpc_handle_ctx_t* rc = ctx; + str_range_t sr = d_to_json(rc->request); + char* req = alloca(sr.len + 1); + memcpy(req, sr.data, sr.len); + req[sr.len] = 0; + return plgn_exec_rpc_handle(rc->ctx->client, rc->ctx, req, index); + } + default: break; + } + return IN3_ENOTSUP; +} + +void EMSCRIPTEN_KEEPALIVE wasm_register_plugin(in3_t* c, in3_plugin_act_t action, int index) { + // the index is used as the custom void* or data for the plugin. + // This way we can cast it backward in order doing the call to js to find the plugin + // if a js-plugin needs custom data, the it should do this in js withihn its own plugin object + in3_plugin_register(NULL, c, action, wasm_plgn, (void*) index, false); +} + +/** + * repareses the request for the context with a new input. + */ +void EMSCRIPTEN_KEEPALIVE wasm_set_request_ctx(in3_ctx_t* ctx, char* req) { + if (!ctx->request_context) return; + char* src = ctx->request_context->c; // we keep the old pointer since this may be an internal request where this needs to be freed. + json_free(ctx->request_context); // throw away the old pares context + char* r = _strdupn(req, -1); // need to copy, because req is on the stack and the pointers of the tokens need to point to valid memory + ctx->request_context = parse_json(r); // parse the new + ctx->requests[0] = ctx->request_context->result; //since we don't support bulks in custom rpc, requests must be allocated with len=1 + ctx->request_context->c = src; // set the old pointer, so the memory management will clean it correctly + in3_cache_add_ptr(&ctx->cache, r)->props = CACHE_PROP_MUST_FREE; // but add the copy to be cleaned when freeing ctx to avoid memory leaks. +} + +/** + * main execute function which generates a json representing the status and all required data to be handled in js. + * The resulting string needs to be freed by the caller! + */ char* EMSCRIPTEN_KEEPALIVE ctx_execute(in3_ctx_t* ctx) { in3_ctx_t* p = ctx; in3_request_t* req = NULL; @@ -179,6 +256,9 @@ char* EMSCRIPTEN_KEEPALIVE ctx_execute(in3_ctx_t* ctx) { void EMSCRIPTEN_KEEPALIVE ifree(void* ptr) { _free(ptr); } +void EMSCRIPTEN_KEEPALIVE wasm_init() { + in3_init(); +} void* EMSCRIPTEN_KEEPALIVE imalloc(size_t size) { return _malloc(size); } @@ -197,6 +277,7 @@ void EMSCRIPTEN_KEEPALIVE in3_blacklist(in3_t* in3, char* url) { } void EMSCRIPTEN_KEEPALIVE ctx_set_response(in3_ctx_t* ctx, int i, int is_error, char* msg) { + if (!ctx->raw_response) ctx->raw_response = _calloc(sizeof(in3_response_t), i + 1); ctx->raw_response[i].time = now() - ctx->raw_response[i].time; ctx->raw_response[i].state = is_error ? IN3_ERPC : IN3_OK; if (ctx->type == CT_SIGN && !is_error) { diff --git a/wasm/test/testEthApi.js b/wasm/test/testEthApi.js index af69210a9..81b30a9dc 100644 --- a/wasm/test/testEthApi.js +++ b/wasm/test/testEthApi.js @@ -89,6 +89,28 @@ describe('EthAPI-Tests', () => { }) + it('plugin._handlRPC', async () => { + const c = createClient(); + c.registerPlugin({ + handleRPC(client, req) { + if (req.method === 'rpc_test') + return "test" + if (req.method === 'rpc_error') + throw new Error('RPCERROR') + if (req.method === 'rpc_error2') + return Promise.reject(new Error('RPCERROR2')) + + } + }) + + assert.equal('test', await c.sendRPC('rpc_test')) + assert.equal(true, await c.sendRPC('test2').catch(() => true)) + assert.equal('RPCERROR', await c.sendRPC('rpc_error').catch(x => x.message)) + assert.equal('RPCERROR2', await c.sendRPC('rpc_error2').catch(x => x.message)) + + }) + + it('eth.sign()', async () => { const pk = '0x889dbed9450f7a4b68e0732ccb7cd016dab158e6946d16158f2736fda1143ca6' const msg = '0x9fa034abf05bd334e60d92da257eb3d66dd3767bba9a1d7a7575533eb0977465'