You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
463 lines
16 KiB
463 lines
16 KiB
#include "etomiccurl.h"
|
|
#include <curl/curl.h>
|
|
|
|
pthread_mutex_t sendTxMutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
struct string {
|
|
char *ptr;
|
|
size_t len;
|
|
};
|
|
|
|
void init_eth_string(struct string *s) {
|
|
s->len = 0;
|
|
s->ptr = malloc(s->len+1);
|
|
if (s->ptr == NULL) {
|
|
fprintf(stderr, "malloc() failed\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
s->ptr[0] = '\0';
|
|
}
|
|
|
|
size_t writefunc(void *ptr, size_t size, size_t nmemb, struct string *s)
|
|
{
|
|
size_t new_len = s->len + size*nmemb;
|
|
s->ptr = realloc(s->ptr, new_len+1);
|
|
if (s->ptr == NULL) {
|
|
fprintf(stderr, "realloc() failed\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
memcpy(s->ptr+s->len, ptr, size*nmemb);
|
|
s->ptr[new_len] = '\0';
|
|
s->len = new_len;
|
|
|
|
return size*nmemb;
|
|
}
|
|
|
|
cJSON *parseEthRpcResponse(char *requestResult)
|
|
{
|
|
cJSON *json = cJSON_Parse(requestResult);
|
|
if (json == NULL) {
|
|
printf("ETH RPC response parse failed: %s!\n", requestResult);
|
|
return NULL;
|
|
}
|
|
cJSON *tmp = cJSON_GetObjectItem(json, "result");
|
|
cJSON *error = cJSON_GetObjectItem(json, "error");
|
|
cJSON *result = NULL;
|
|
if (tmp != NULL && !is_cJSON_Null(tmp)) {
|
|
result = cJSON_Duplicate(tmp, 1);
|
|
} else if (error != NULL && !is_cJSON_Null(error)) {
|
|
char *errorString = cJSON_PrintUnformatted(error);
|
|
printf("Got ETH rpc error: %s\n", errorString);
|
|
free(errorString);
|
|
}
|
|
cJSON_Delete(json);
|
|
return result;
|
|
}
|
|
|
|
char *send_post_json_request(char *request, char *url)
|
|
{
|
|
CURL *curl;
|
|
CURLcode res;
|
|
struct curl_slist *headers = NULL;
|
|
curl = curl_easy_init();
|
|
if (curl) {
|
|
struct string s;
|
|
init_eth_string(&s);
|
|
|
|
headers = curl_slist_append(headers, "Accept: application/json");
|
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
|
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request);
|
|
/* Perform the request, res will get the return code */
|
|
res = curl_easy_perform(curl);
|
|
/* Check for errors */
|
|
if (res != CURLE_OK) {
|
|
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
|
curl_easy_cleanup(curl);
|
|
return NULL;
|
|
}
|
|
|
|
/* always cleanup */
|
|
curl_easy_cleanup(curl);
|
|
return s.ptr;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
char *send_get_json_request(char *url)
|
|
{
|
|
CURL *curl;
|
|
CURLcode res;
|
|
struct curl_slist *headers = NULL;
|
|
curl = curl_easy_init();
|
|
if (curl) {
|
|
struct string s;
|
|
init_eth_string(&s);
|
|
|
|
headers = curl_slist_append(headers, "Accept: application/json");
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
|
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
/* Perform the request, res will get the return code */
|
|
res = curl_easy_perform(curl);
|
|
/* Check for errors */
|
|
if (res != CURLE_OK) {
|
|
printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
|
curl_easy_cleanup(curl);
|
|
return NULL;
|
|
}
|
|
|
|
/* always cleanup */
|
|
curl_easy_cleanup(curl);
|
|
return s.ptr;
|
|
} else {
|
|
printf("Couldn't init the curl\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
cJSON *sendRpcRequest(char *method, cJSON *params)
|
|
{
|
|
char* string;
|
|
cJSON *request = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(request, "jsonrpc", "2.0");
|
|
cJSON_AddStringToObject(request, "method", method);
|
|
if (params) {
|
|
cJSON_AddItemToObject(request, "params", cJSON_Duplicate(params, 1));
|
|
}
|
|
cJSON_AddNumberToObject(request, "id", 1);
|
|
string = cJSON_PrintUnformatted(request);
|
|
char* requestResult = send_post_json_request(string, ETOMIC_URL);
|
|
free(string);
|
|
cJSON_Delete(request);
|
|
cJSON *result = parseEthRpcResponse(requestResult);
|
|
free(requestResult);
|
|
return result;
|
|
}
|
|
|
|
char* sendRawTxWaitConfirm(char* rawTx)
|
|
{
|
|
cJSON *params = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(params, cJSON_CreateString(rawTx));
|
|
cJSON *resultJson = sendRpcRequest("eth_sendRawTransaction", params);
|
|
cJSON_Delete(params);
|
|
char *txId = NULL;
|
|
if (resultJson != NULL && is_cJSON_String(resultJson) && resultJson->valuestring != NULL) {
|
|
char* tmp = resultJson->valuestring;
|
|
txId = (char *) malloc(strlen(tmp) + 1);
|
|
strcpy(txId, tmp);
|
|
}
|
|
/*
|
|
if (resultJson != NULL && is_cJSON_String(resultJson) && resultJson->valuestring != NULL) {
|
|
char* tmp = resultJson->valuestring;
|
|
if (waitForConfirmation(tmp) > 0) {
|
|
txId = (char *) malloc(strlen(tmp) + 1);
|
|
strcpy(txId, tmp);
|
|
}
|
|
}
|
|
*/
|
|
cJSON_Delete(resultJson);
|
|
pthread_mutex_unlock(&sendTxMutex);
|
|
return txId;
|
|
}
|
|
|
|
char* sendRawTx(char* rawTx)
|
|
{
|
|
cJSON *params = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(params, cJSON_CreateString(rawTx));
|
|
cJSON *resultJson = sendRpcRequest("eth_sendRawTransaction", params);
|
|
cJSON_Delete(params);
|
|
char *txId = NULL;
|
|
if (resultJson != NULL && is_cJSON_String(resultJson) && resultJson->valuestring != NULL) {
|
|
char* tmp = resultJson->valuestring;
|
|
txId = (char *) malloc(strlen(tmp) + 1);
|
|
strcpy(txId, tmp);
|
|
}
|
|
cJSON_Delete(resultJson);
|
|
pthread_mutex_unlock(&sendTxMutex);
|
|
return txId;
|
|
}
|
|
|
|
int64_t getNonce(char* address)
|
|
{
|
|
// we should lock this mutex and unlock it only when transaction was already sent or failed.
|
|
// make sure that sendRawTx or unlock_send_tx_mutex is called after getting a nonce!
|
|
if (pthread_mutex_lock(&sendTxMutex) != 0) {
|
|
printf("Nonce mutex lock failed\n");
|
|
};
|
|
cJSON *params = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(params, cJSON_CreateString(address));
|
|
// cJSON_AddItemToArray(params, cJSON_CreateString("pending"));
|
|
int64_t nonce = -1;
|
|
cJSON *nonceJson = sendRpcRequest("parity_nextNonce", params);
|
|
cJSON_Delete(params);
|
|
if (nonceJson != NULL && is_cJSON_String(nonceJson) && nonceJson != NULL) {
|
|
nonce = (int64_t) strtol(nonceJson->valuestring, NULL, 0);
|
|
}
|
|
cJSON_Delete(nonceJson);
|
|
printf("Got ETH nonce %d\n", (int)nonce);
|
|
return nonce;
|
|
}
|
|
|
|
char* getEthBalanceRequest(char* address)
|
|
{
|
|
cJSON *params = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(params, cJSON_CreateString(address));
|
|
cJSON_AddItemToArray(params, cJSON_CreateString("latest"));
|
|
cJSON *balanceJson = sendRpcRequest("eth_getBalance", params);
|
|
cJSON_Delete(params);
|
|
char *balance = NULL;
|
|
if (balanceJson != NULL && is_cJSON_String(balanceJson) && balanceJson->valuestring != NULL) {
|
|
balance = (char *) malloc(strlen(balanceJson->valuestring) + 1);
|
|
strcpy(balance, balanceJson->valuestring);
|
|
}
|
|
cJSON_Delete(balanceJson);
|
|
return balance;
|
|
}
|
|
|
|
char *ethCall(char *to, const char *data)
|
|
{
|
|
cJSON *params = cJSON_CreateArray();
|
|
cJSON *txObject = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(txObject, "to", to);
|
|
cJSON_AddStringToObject(txObject, "data", data);
|
|
cJSON_AddItemToArray(params, txObject);
|
|
cJSON_AddItemToArray(params, cJSON_CreateString("latest"));
|
|
cJSON *resultJson = sendRpcRequest("eth_call", params);
|
|
cJSON_Delete(params);
|
|
char *result = NULL;
|
|
if (resultJson != NULL && is_cJSON_String(resultJson) && resultJson->valuestring != NULL) {
|
|
result = (char *) malloc(strlen(resultJson->valuestring) + 1);
|
|
strcpy(result, resultJson->valuestring);
|
|
}
|
|
cJSON_Delete(resultJson);
|
|
return result;
|
|
}
|
|
|
|
uint64_t estimateGas(char *from, char *to, const char *data)
|
|
{
|
|
cJSON *params = cJSON_CreateArray();
|
|
cJSON *txObject = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(txObject, "from", from);
|
|
cJSON_AddStringToObject(txObject, "to", to);
|
|
cJSON_AddStringToObject(txObject, "data", data);
|
|
cJSON_AddItemToArray(params, txObject);
|
|
cJSON_AddItemToArray(params, cJSON_CreateString("latest"));
|
|
cJSON *resultJson = sendRpcRequest("eth_estimateGas", params);
|
|
cJSON_Delete(params);
|
|
uint64_t result = 0;
|
|
if (resultJson != NULL && is_cJSON_String(resultJson) && resultJson->valuestring != NULL) {
|
|
result = (uint64_t)strtoul(resultJson->valuestring, NULL, 0);
|
|
result = (result / 100) * 120; // add 20% because real gas usage might differ from estimate
|
|
}
|
|
cJSON_Delete(resultJson);
|
|
return result;
|
|
}
|
|
|
|
EthTxReceipt getEthTxReceipt(char *txId)
|
|
{
|
|
EthTxReceipt result;
|
|
memset(&result, 0, sizeof(result));
|
|
cJSON *params = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(params, cJSON_CreateString(txId));
|
|
cJSON *receiptJson = sendRpcRequest("eth_getTransactionReceipt", params);
|
|
cJSON_Delete(params);
|
|
if (receiptJson == NULL || is_cJSON_Null(cJSON_GetObjectItem(receiptJson, "blockHash")) || is_cJSON_Null(cJSON_GetObjectItem(receiptJson, "blockNumber"))) {
|
|
printf("ETH tx %s is not confirmed yet or does not exist at all\n", txId);
|
|
strcpy(result.blockHash, "0x0000000000000000000000000000000000000000000000000000000000000000");
|
|
result.blockNumber = 0;
|
|
} else {
|
|
uint64_t currentBlockNumber = getEthBlockNumber();
|
|
strcpy(result.blockHash, cJSON_GetObjectItem(receiptJson, "blockHash")->valuestring);
|
|
strcpy(result.status, cJSON_GetObjectItem(receiptJson, "status")->valuestring);
|
|
result.blockNumber = (uint64_t) strtol(cJSON_GetObjectItem(receiptJson, "blockNumber")->valuestring, NULL, 0);
|
|
if (currentBlockNumber >= result.blockNumber) {
|
|
result.confirmations = currentBlockNumber - result.blockNumber + 1;
|
|
}
|
|
}
|
|
cJSON_Delete(receiptJson);
|
|
return result;
|
|
}
|
|
|
|
uint64_t getEthBlockNumber()
|
|
{
|
|
uint64_t result = 0;
|
|
cJSON *params = cJSON_CreateArray();
|
|
cJSON *blockNumberJson = sendRpcRequest("eth_blockNumber", params);
|
|
cJSON_Delete(params);
|
|
if (blockNumberJson != NULL && is_cJSON_String(blockNumberJson) && blockNumberJson->valuestring != NULL) {
|
|
result = (uint64_t) strtol(blockNumberJson->valuestring, NULL, 0);
|
|
}
|
|
cJSON_Delete(blockNumberJson);
|
|
return result;
|
|
}
|
|
|
|
EthTxData getEthTxData(char *txId)
|
|
{
|
|
EthTxData result;
|
|
memset(&result, 0, sizeof(result));
|
|
cJSON *params = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(params, cJSON_CreateString(txId));
|
|
cJSON *dataJson = sendRpcRequest("eth_getTransactionByHash", params);
|
|
cJSON_Delete(params);
|
|
if (dataJson == NULL) {
|
|
result.exists = 0;
|
|
printf("ETH tx %s get data error or txId does not exist\n", txId);
|
|
} else {
|
|
result.exists = 1;
|
|
strcpy(result.from, cJSON_GetObjectItem(dataJson, "from")->valuestring);
|
|
strcpy(result.to, cJSON_GetObjectItem(dataJson, "to")->valuestring);
|
|
strcpy(result.input, cJSON_GetObjectItem(dataJson, "input")->valuestring);
|
|
strcpy(result.valueHex, cJSON_GetObjectItem(dataJson, "value")->valuestring);
|
|
}
|
|
free(dataJson);
|
|
return result;
|
|
}
|
|
|
|
uint64_t getGasPriceFromStation(uint8_t defaultOnErr)
|
|
{
|
|
CURL *curl;
|
|
CURLcode res;
|
|
struct curl_slist *headers = NULL;
|
|
curl = curl_easy_init();
|
|
if (curl) {
|
|
struct string s;
|
|
init_eth_string(&s);
|
|
|
|
headers = curl_slist_append(headers, "Accept: application/json");
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
|
|
curl_easy_setopt(curl, CURLOPT_URL, "https://ethgasstation.info/json/ethgasAPI.json");
|
|
/* Perform the request, res will get the return code */
|
|
res = curl_easy_perform(curl);
|
|
uint64_t result;
|
|
if (defaultOnErr == 1) {
|
|
result = DEFAULT_GAS_PRICE;
|
|
} else {
|
|
result = 0;
|
|
}
|
|
/* Check for errors */
|
|
if (res != CURLE_OK) {
|
|
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
|
return DEFAULT_GAS_PRICE;
|
|
}
|
|
|
|
/* always cleanup */
|
|
curl_easy_cleanup(curl);
|
|
cJSON *resultJson = cJSON_Parse(s.ptr);
|
|
free(s.ptr);
|
|
if (resultJson == NULL) {
|
|
printf("Could not parse gas station response!\n");
|
|
return result;
|
|
}
|
|
|
|
if (is_cJSON_Number(cJSON_GetObjectItem(resultJson, "average"))) {
|
|
#ifdef ETOMIC_TESTNET
|
|
result = (uint64_t)(cJSON_GetObjectItem(resultJson, "average")->valuedouble / 10) + 10;
|
|
#else
|
|
result = (uint64_t)(cJSON_GetObjectItem(resultJson, "average")->valuedouble / 10) + 1;
|
|
#endif
|
|
}
|
|
cJSON_Delete(resultJson);
|
|
return result;
|
|
} else {
|
|
return DEFAULT_GAS_PRICE;
|
|
}
|
|
}
|
|
|
|
int32_t waitForConfirmation(char *txId)
|
|
{
|
|
EthTxReceipt receipt;
|
|
EthTxData txData;
|
|
uint8_t retries = 0;
|
|
do {
|
|
receipt = getEthTxReceipt(txId);
|
|
if (receipt.confirmations < 1) {
|
|
txData = getEthTxData(txId);
|
|
if (txData.exists == 0) {
|
|
retries++;
|
|
if (retries >= 30) {
|
|
printf("Have not found ETH tx %s after 30 checks, aborting\n", txId);
|
|
return (-1);
|
|
}
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
printf("waiting for ETH txId to be confirmed: %s\n", txId);
|
|
sleep(15);
|
|
} while (1);
|
|
|
|
if (strcmp(receipt.status, "0x1") != 0) {
|
|
printf("ETH txid %s receipt status failed\n", txId);
|
|
return(-1);
|
|
}
|
|
|
|
return((int32_t)receipt.confirmations);
|
|
}
|
|
|
|
void unlock_send_tx_mutex()
|
|
{
|
|
pthread_mutex_unlock(&sendTxMutex);
|
|
}
|
|
|
|
uint8_t get_etomic_from_faucet(char *etomic_addr)
|
|
{
|
|
char* string;
|
|
cJSON *request = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(request, "etomicAddress", etomic_addr);
|
|
string = cJSON_PrintUnformatted(request);
|
|
char* requestResult = send_post_json_request(string, FAUCET_URL);
|
|
free(string);
|
|
cJSON_Delete(request);
|
|
|
|
if (requestResult == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
cJSON *json = cJSON_Parse(requestResult);
|
|
if (json == NULL) {
|
|
printf("ETOMIC faucet response parse failed!\n");
|
|
return 0;
|
|
}
|
|
cJSON *error = cJSON_GetObjectItem(json, "error");
|
|
uint8_t result = 0;
|
|
if (error != NULL && !is_cJSON_Null(error)) {
|
|
char *errorString = cJSON_PrintUnformatted(error);
|
|
printf("Got ETOMIC faucet error: %s\n", errorString);
|
|
free(errorString);
|
|
} else {
|
|
result = 1;
|
|
}
|
|
cJSON_Delete(json);
|
|
return result;
|
|
}
|
|
|
|
char *eth_tx_history_etherscan(char *addr)
|
|
{
|
|
char resulting_url[4097];
|
|
sprintf(resulting_url, "%s?module=account&action=txlist&address=%s&startblock=0&endblock=99999999&sort=asc", ETHERSCAN_API, addr);
|
|
return send_get_json_request(resulting_url);
|
|
}
|
|
|
|
char *internal_eth_tx_history_etherscan(char *addr)
|
|
{
|
|
char resulting_url[4097];
|
|
sprintf(resulting_url, "%s?module=account&action=txlistinternal&address=%s&startblock=0&endblock=99999999&sort=asc", ETHERSCAN_API, addr);
|
|
return send_get_json_request(resulting_url);
|
|
}
|
|
|
|
char *erc20_tx_history_etherscan(char *addr, char *token_address)
|
|
{
|
|
char resulting_url[4097];
|
|
sprintf(resulting_url, "%s?module=account&action=tokentx&address=%s&contractaddress=%s&startblock=0&endblock=99999999&sort=asc", ETHERSCAN_API, addr, token_address);
|
|
return send_get_json_request(resulting_url);
|
|
}
|
|
|