From 3cd9835382220bf1baeba62faeb637ef1d492aa7 Mon Sep 17 00:00:00 2001 From: "daniil.frolov" <daniil.frolov@demlabs.net> Date: Tue, 16 Jan 2024 04:56:32 +0000 Subject: [PATCH] hotfix-9667 --- modules/common/CMakeLists.txt | 2 +- modules/common/dap_chain_datum.c | 37 +- modules/common/dap_chain_datum_tx.c | 1 + modules/common/dap_chain_datum_tx_items.c | 45 + modules/common/dap_chain_datum_tx_voting.c | 223 +++ modules/common/dap_chain_datum_vote.c | 22 - modules/common/include/dap_chain_common.h | 4 + .../common/include/dap_chain_datum_tx_items.h | 7 + .../include/dap_chain_datum_tx_voting.h | 90 ++ modules/common/include/dap_chain_datum_vote.h | 22 - modules/net/dap_chain_ledger.c | 179 +++ modules/net/dap_chain_net.c | 2 + modules/net/dap_chain_net_voting.c | 1360 +++++++++++++++++ modules/net/include/dap_chain_ledger.h | 9 +- modules/net/include/dap_chain_net_voting.h | 39 + 15 files changed, 1994 insertions(+), 48 deletions(-) create mode 100644 modules/common/dap_chain_datum_tx_voting.c delete mode 100644 modules/common/dap_chain_datum_vote.c create mode 100644 modules/common/include/dap_chain_datum_tx_voting.h delete mode 100644 modules/common/include/dap_chain_datum_vote.h create mode 100644 modules/net/dap_chain_net_voting.c create mode 100644 modules/net/include/dap_chain_net_voting.h diff --git a/modules/common/CMakeLists.txt b/modules/common/CMakeLists.txt index 7b6258a44b..79ce7eb3e4 100644 --- a/modules/common/CMakeLists.txt +++ b/modules/common/CMakeLists.txt @@ -22,4 +22,4 @@ INSTALL(TARGETS ${PROJECT_NAME} ARCHIVE DESTINATION lib/modules/common/ PUBLIC_HEADER DESTINATION include/modules/common/ ) -endif() \ No newline at end of file +endif() diff --git a/modules/common/dap_chain_datum.c b/modules/common/dap_chain_datum.c index 4fec72ab3c..1548f1698f 100644 --- a/modules/common/dap_chain_datum.c +++ b/modules/common/dap_chain_datum.c @@ -31,6 +31,7 @@ #include "dap_chain_datum_tx_items.h" #include "dap_chain_datum_decree.h" #include "dap_chain_datum_anchor.h" +#include "dap_chain_datum_tx_voting.h" #include "dap_chain_datum_hashtree_roots.h" #include "dap_enc_base58.h" @@ -356,7 +357,7 @@ bool dap_chain_datum_dump_tx(dap_chain_datum_tx_t *a_datum, dap_chain_addr_t l_sender_addr; dap_chain_addr_fill_from_sign(&l_sender_addr, l_sign_tmp, a_net_id); const char *l_addr_str = dap_chain_addr_to_str(&l_sender_addr); - dap_string_append_printf(a_str_out, "\tSender addr: %s", l_addr_str); + dap_string_append_printf(a_str_out, "\tSender addr: %s\n", l_addr_str); DAP_DELETE(l_addr_str); } break; case TX_ITEM_TYPE_RECEIPT: { @@ -549,6 +550,40 @@ bool dap_chain_datum_dump_tx(dap_chain_datum_tx_t *a_datum, DAP_DELETE(l_value_str); DAP_DELETE(l_coins_str); } break; + case TX_ITEM_TYPE_VOTING:{ + int l_tsd_size = 0; + dap_chain_tx_tsd_t *l_item = (dap_chain_tx_tsd_t *)dap_chain_datum_tx_item_get(a_datum, 0, TX_ITEM_TYPE_TSD, &l_tsd_size); + if (!l_item || !l_tsd_size) + break; + dap_chain_datum_tx_voting_params_t *l_voting_params = dap_chain_voting_parse_tsd(a_datum); + dap_string_append_printf(a_str_out, "\t VOTING:\n\tVoting question: %s\n\t Answer options:\n", l_voting_params->voting_question); + dap_list_t *l_temp = l_voting_params->answers_list; + uint8_t l_index = 0; + while (l_temp){ + dap_string_append_printf(a_str_out, "\t\t %i) %s\n", l_index, (char*)l_temp->data); + l_index++; + l_temp = l_temp->next; + } + + if(l_voting_params->voting_expire) + dap_string_append_printf(a_str_out, "\t Voting expire: %s\n", dap_ctime_r(&l_voting_params->voting_expire, l_tmp_buf)); + if (l_voting_params->votes_max_count) + dap_string_append_printf(a_str_out, "\t Votes max count: %"DAP_UINT64_FORMAT_U"\n", l_voting_params->votes_max_count); + dap_string_append_printf(a_str_out, "\t Changing vote is %s available.\n", l_voting_params->vote_changing_allowed ? "" : "not"); + dap_string_append_printf(a_str_out, "\t A delegated key is%s required to participate in voting. \n", l_voting_params->delegate_key_required ? "" : " not"); + + dap_list_free_full(l_voting_params->answers_list, NULL); + DAP_DELETE(l_voting_params->voting_question); + DAP_DELETE(l_voting_params); + } break; + case TX_ITEM_TYPE_VOTE:{ + dap_chain_tx_vote_t *l_vote_item = (dap_chain_tx_vote_t *)item; + char *l_hash_str = dap_chain_hash_fast_to_str_new(&l_vote_item->voting_hash); + dap_string_append_printf(a_str_out, "\t VOTE: \n" + "\t Voting hash: %s\n" + "\t Vote answer idx: %"DAP_UINT64_FORMAT_U"\n", l_hash_str, l_vote_item->answer_idx); + DAP_DELETE(l_hash_str); + } break; default: dap_string_append_printf(a_str_out, " This transaction have unknown item type \n"); break; diff --git a/modules/common/dap_chain_datum_tx.c b/modules/common/dap_chain_datum_tx.c index 7da9de1563..32b2777732 100644 --- a/modules/common/dap_chain_datum_tx.c +++ b/modules/common/dap_chain_datum_tx.c @@ -28,6 +28,7 @@ #include "dap_sign.h" #include "dap_chain_datum_tx_items.h" #include "dap_chain_datum_tx.h" +#include "dap_chain_datum_tx_voting.h" #define LOG_TAG "dap_chain_datum_tx" diff --git a/modules/common/dap_chain_datum_tx_items.c b/modules/common/dap_chain_datum_tx_items.c index 2a0611b9c4..253cf0495d 100644 --- a/modules/common/dap_chain_datum_tx_items.c +++ b/modules/common/dap_chain_datum_tx_items.c @@ -32,6 +32,8 @@ #include "dap_hash.h" #include "dap_chain_datum_tx.h" #include "dap_chain_datum_tx_items.h" +#include "dap_chain_datum_tx_voting.h" + static size_t dap_chain_tx_in_get_size(const dap_chain_tx_in_t *a_item) { @@ -112,6 +114,19 @@ static size_t dap_chain_tx_tsd_get_size(const dap_chain_tx_tsd_t *a_item) return sizeof(dap_chain_tx_tsd_t) + a_item->header.size; } +static size_t dap_chain_tx_voting_get_size(const dap_chain_tx_voting_t *a_item) +{ + (void) a_item; + size_t size = sizeof(dap_chain_tx_voting_t); + return size; +} + +static size_t dap_chain_tx_vote_get_size(const dap_chain_tx_vote_t *a_item) +{ + (void) a_item; + size_t size = sizeof(dap_chain_tx_vote_t); + return size; +} /** * Get item type by item name * @@ -144,6 +159,10 @@ dap_chain_tx_item_type_t dap_chain_datum_tx_item_str_to_type(const char *a_datum return TX_ITEM_TYPE_RECEIPT; else if(!dap_strcmp(a_datum_name, "data")) return TX_ITEM_TYPE_TSD; + else if(!dap_strcmp(a_datum_name, "voting")) + return TX_ITEM_TYPE_VOTING; + else if(!dap_strcmp(a_datum_name, "vote")) + return TX_ITEM_TYPE_VOTE; return TX_ITEM_TYPE_UNKNOWN; } @@ -225,6 +244,12 @@ size_t dap_chain_datum_item_tx_get_size(const void *a_item) case TX_ITEM_TYPE_TSD: size = dap_chain_tx_tsd_get_size((const dap_chain_tx_tsd_t*)a_item); break; + case TX_ITEM_TYPE_VOTING: + size = dap_chain_tx_voting_get_size((const dap_chain_tx_voting_t*)a_item); + break; + case TX_ITEM_TYPE_VOTE: + size = dap_chain_tx_vote_get_size((const dap_chain_tx_vote_t*)a_item); + break; default: return 0; } @@ -653,3 +678,23 @@ dap_chain_tx_out_cond_t *dap_chain_datum_tx_out_cond_get(dap_chain_datum_tx_t *a } return l_res; } + +uint8_t *dap_chain_datum_tx_out_get_by_out_idx(dap_chain_datum_tx_t *a_tx, int a_out_num) +{ + uint8_t *l_ret = NULL; + dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get(a_tx, TX_ITEM_TYPE_OUT_ALL, NULL), *l_item; + if (!l_list_out_items) + return NULL; + + l_item = dap_list_nth(l_list_out_items, a_out_num); + + if(!l_item){ + dap_list_free(l_list_out_items); + return NULL; + } + + l_ret = l_item->data; + dap_list_free(l_list_out_items); + return l_ret; + +} diff --git a/modules/common/dap_chain_datum_tx_voting.c b/modules/common/dap_chain_datum_tx_voting.c new file mode 100644 index 0000000000..510499137c --- /dev/null +++ b/modules/common/dap_chain_datum_tx_voting.c @@ -0,0 +1,223 @@ +/* + * Authors: + * Daniil Frolov <daniil.frolov@demlabs.net> + * DeM Labs Inc. https://demlabs.net + * Copyright (c) 2020, All rights reserved. + + This file is part of CellFrame SDK the open source project + + CellFrame SDK is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + CellFrame SDK 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any CellFrame SDK based project. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dap_chain_datum_tx_voting.h" +#include "dap_chain_common.h" + + +#define LOG_TAG "datum_tx_voting" + + +dap_chain_datum_tx_voting_params_t* dap_chain_voting_parse_tsd(dap_chain_datum_tx_t* a_tx) +{ + if (!a_tx) + return NULL; + + dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx, TX_ITEM_TYPE_TSD, NULL); + dap_chain_datum_tx_voting_params_t *l_voting_parms = DAP_NEW_Z_SIZE(dap_chain_datum_tx_voting_params_t, + sizeof(dap_chain_datum_tx_voting_params_t)); + char *l_buf_string = NULL; + dap_list_t* l_temp = l_tsd_list; + while (l_temp){ + dap_tsd_t *l_tsd = (dap_tsd_t *)((dap_chain_tx_tsd_t*)l_temp->data)->tsd; + switch(l_tsd->type){ + case VOTING_TSD_TYPE_QUESTION: + l_buf_string = DAP_NEW_Z_SIZE(char, l_tsd->size + 1); + memcpy(l_buf_string, l_tsd->data, l_tsd->size); + l_buf_string[l_tsd->size] = '\0'; + l_voting_parms->voting_question = l_buf_string; + break; + case VOTING_TSD_TYPE_ANSWER: + l_buf_string = DAP_NEW_Z_SIZE(char, l_tsd->size + 1); + memcpy(l_buf_string, l_tsd->data, l_tsd->size); + l_buf_string[l_tsd->size] = '\0'; + l_voting_parms->answers_list = dap_list_append(l_voting_parms->answers_list, l_buf_string); + l_voting_parms->answers_count++; + break; + case VOTING_TSD_TYPE_EXPIRE: + l_voting_parms->voting_expire = *(dap_time_t*)l_tsd->data; + break; + case VOTING_TSD_TYPE_MAX_VOTES_COUNT: + l_voting_parms->votes_max_count = *(uint64_t*)l_tsd->data; + break; + case VOTING_TSD_TYPE_DELEGATED_KEY_REQUIRED: + l_voting_parms->delegate_key_required = *(bool*)l_tsd->data; + break; + case VOTING_TSD_TYPE_VOTE_CHANGING_ALLOWED: + l_voting_parms->vote_changing_allowed = *(bool*)l_tsd->data; + break; + default: + break; + } + + l_temp = l_temp->next; + } + dap_list_free(l_tsd_list); + + return l_voting_parms; +} + + +dap_chain_tx_tsd_t* dap_chain_datum_voting_question_tsd_create(const char* a_question_str, size_t str_len) +{ + if (!a_question_str || !str_len) + return NULL; + + dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create((void*)a_question_str, VOTING_TSD_TYPE_QUESTION, str_len); + + return l_tsd; +} + +dap_chain_tx_tsd_t* dap_chain_datum_voting_answer_tsd_create(const char* a_answer_str, size_t str_len) +{ + if (!a_answer_str || !str_len) + return NULL; + + dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create((void*)a_answer_str, VOTING_TSD_TYPE_ANSWER, str_len); + + return l_tsd; +} + +dap_chain_tx_tsd_t* dap_chain_datum_voting_expire_tsd_create(dap_time_t a_expire) +{ + if (!a_expire) + return NULL; + + dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create(&a_expire, VOTING_TSD_TYPE_EXPIRE, sizeof(dap_time_t)); + + return l_tsd; +} + +dap_chain_tx_tsd_t* dap_chain_datum_voting_max_votes_count_tsd_create(uint64_t a_max_count) +{ + if (!a_max_count) + return NULL; + + dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create(&a_max_count, VOTING_TSD_TYPE_MAX_VOTES_COUNT, sizeof(uint64_t)); + + return l_tsd; +} + +dap_chain_tx_tsd_t* dap_chain_datum_voting_delegated_key_required_tsd_create(bool a_delegate_key_required) +{ + dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create(&a_delegate_key_required, VOTING_TSD_TYPE_DELEGATED_KEY_REQUIRED, sizeof(bool)); + + return l_tsd; +} + +dap_chain_tx_tsd_t* dap_chain_datum_voting_vote_changing_allowed_tsd_create(bool a_vote_changing_allowed) +{ + dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create(&a_vote_changing_allowed, VOTING_TSD_TYPE_VOTE_CHANGING_ALLOWED, sizeof(bool)); + + return l_tsd; +} + +dap_chain_tx_tsd_t* dap_chain_datum_voting_vote_tx_cond_tsd_create(dap_chain_hash_fast_t a_tx_hash, int a_out_idx) +{ + dap_chain_tx_voting_tx_cond_t l_temp = { + .tx_hash = a_tx_hash, + .out_idx = a_out_idx + }; + + dap_chain_tx_tsd_t* l_tsd = dap_chain_datum_tx_item_tsd_create(&l_temp, VOTING_TSD_TYPE_VOTE_TX_COND, sizeof(dap_chain_tx_voting_tx_cond_t)); + + return l_tsd; +} + +dap_chain_tx_voting_t *dap_chain_datum_tx_item_voting_create(void) +{ + dap_chain_tx_voting_t * l_item = DAP_NEW_Z(dap_chain_tx_voting_t); + l_item->type = TX_ITEM_TYPE_VOTING; + return l_item; +} + +json_object *dap_chain_datum_tx_item_voting_tsd_to_json(dap_chain_datum_tx_t* a_tx) +{ + if (!a_tx) + return NULL; + + json_object *l_object = json_object_new_object(); + json_object *l_answer_array_object = json_object_new_array(); + json_object *l_json_obj = NULL; + dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx, TX_ITEM_TYPE_TSD, NULL); + dap_list_t* l_temp = l_tsd_list; + while (l_temp){ + dap_tsd_t* l_tsd = (dap_tsd_t *)((dap_chain_tx_tsd_t*)l_temp->data)->tsd; + switch(l_tsd->type){ + case VOTING_TSD_TYPE_QUESTION: + l_json_obj = json_object_new_string_len((char*)l_tsd->data, l_tsd->size); + json_object_object_add(l_object, "question", l_json_obj); + break; + case VOTING_TSD_TYPE_ANSWER: + l_json_obj = json_object_new_string_len((char*)l_tsd->data, l_tsd->size); + json_object_array_add(l_answer_array_object, l_json_obj); + break; + case VOTING_TSD_TYPE_EXPIRE: + l_json_obj = json_object_new_uint64(*(uint64_t*)l_tsd->data); + json_object_object_add(l_object, "exired", l_json_obj); + break; + case VOTING_TSD_TYPE_MAX_VOTES_COUNT: + l_json_obj = json_object_new_uint64(*(uint64_t*)l_tsd->data); + json_object_object_add(l_object, "maxVotes", l_json_obj); + break; + case VOTING_TSD_TYPE_DELEGATED_KEY_REQUIRED: + l_json_obj = json_object_new_boolean(*(bool*)l_tsd->data); + json_object_object_add(l_object, "delegateKeyRequired", l_json_obj); + break; + case VOTING_TSD_TYPE_VOTE_CHANGING_ALLOWED: + l_json_obj = json_object_new_boolean(*(bool*)l_tsd->data); + json_object_object_add(l_object, "voteChangingAllowed", l_json_obj); + break; + default: + break; + } + l_temp = l_temp->next; + } + dap_list_free_full(l_tsd_list, NULL); + + json_object_object_add(l_object, "answers", l_answer_array_object); + return l_object; +} + +dap_chain_tx_vote_t *dap_chain_datum_tx_item_vote_create(dap_chain_hash_fast_t *a_voting_hash, uint64_t *a_answer_idx) +{ + if (!a_voting_hash) + return NULL; + + dap_chain_tx_vote_t * l_item = DAP_NEW_Z(dap_chain_tx_vote_t); + l_item->type = TX_ITEM_TYPE_VOTE; + l_item->answer_idx = *a_answer_idx; + l_item->voting_hash = *a_voting_hash; + return l_item; +} + +json_object *dap_chain_datum_tx_item_vote_to_json(dap_chain_tx_vote_t *a_vote) +{ + json_object *l_object = json_object_new_object(); + char *l_voting_hash_str = dap_hash_fast_to_str_new(&a_vote->voting_hash); + json_object *l_voting_hash = json_object_new_string(l_voting_hash_str); + DAP_DELETE(l_voting_hash_str); + json_object *l_answer_idx = json_object_new_uint64(a_vote->answer_idx); + json_object_object_add(l_object, "votingHash", l_voting_hash); + json_object_object_add(l_object, "answer_idx", l_answer_idx); + return l_object; +} diff --git a/modules/common/dap_chain_datum_vote.c b/modules/common/dap_chain_datum_vote.c deleted file mode 100644 index 7d7eef1b52..0000000000 --- a/modules/common/dap_chain_datum_vote.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Authors: - * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net> - * DeM Labs Inc. https://demlabs.net - * Copyright (c) 2020, All rights reserved. - - This file is part of CellFrame SDK the open source project - - CellFrame SDK is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - CellFrame SDK 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with any CellFrame SDK based project. If not, see <http://www.gnu.org/licenses/>. -*/ - diff --git a/modules/common/include/dap_chain_common.h b/modules/common/include/dap_chain_common.h index fc2924bdfe..2e59ae9b82 100644 --- a/modules/common/include/dap_chain_common.h +++ b/modules/common/include/dap_chain_common.h @@ -200,6 +200,10 @@ enum dap_chain_tx_item_type { TX_ITEM_TYPE_RECEIPT = 0x70, TX_ITEM_TYPE_TSD = 0x80, + /// @brief Transaction: voting and vote + TX_ITEM_TYPE_VOTING = 0x90, + TX_ITEM_TYPE_VOTE = 0x91, + /// @brief Virtual types for items enumearting TX_ITEM_TYPE_IN_EMS_LOCK = 0xf1, TX_ITEM_TYPE_IN_ALL = 0xfd, diff --git a/modules/common/include/dap_chain_datum_tx_items.h b/modules/common/include/dap_chain_datum_tx_items.h index 7ab06e5c3b..dfb41acc1f 100644 --- a/modules/common/include/dap_chain_datum_tx_items.h +++ b/modules/common/include/dap_chain_datum_tx_items.h @@ -77,6 +77,10 @@ DAP_STATIC_INLINE const char * dap_chain_datum_tx_item_type_to_str(dap_chain_tx_ case TX_ITEM_TYPE_OUT_COND: return "TX_ITEM_TYPE_OUT_COND"; // 256 case TX_ITEM_TYPE_RECEIPT: return "TX_ITEM_TYPE_RECEIPT"; case TX_ITEM_TYPE_TSD: return "TX_ITEM_TYPE_TSD"; + case TX_ITEM_TYPE_OUT_ALL: return "TX_ITEM_TYPE_OUT_OLDALL"; + case TX_ITEM_TYPE_ANY: return "TX_ITEM_TYPE_ANY"; + case TX_ITEM_TYPE_VOTING: return "TX_ITEM_TYPE_VOTING"; + case TX_ITEM_TYPE_VOTE: return "TX_ITEM_TYPE_VOTE"; default: return "UNDEFINED"; } } @@ -239,3 +243,6 @@ uint8_t *dap_chain_datum_tx_item_get_nth(dap_chain_datum_tx_t *a_tx, dap_chain_t dap_list_t* dap_chain_datum_tx_items_get(dap_chain_datum_tx_t *a_tx, dap_chain_tx_item_type_t a_type, int *a_item_count); // Get conditional out item with it's idx dap_chain_tx_out_cond_t *dap_chain_datum_tx_out_cond_get(dap_chain_datum_tx_t *a_tx, dap_chain_tx_item_type_t a_cond_type, int *a_out_num); +// Get output by output index +uint8_t *dap_chain_datum_tx_out_get_by_out_idx(dap_chain_datum_tx_t *a_tx, int a_out_num); + diff --git a/modules/common/include/dap_chain_datum_tx_voting.h b/modules/common/include/dap_chain_datum_tx_voting.h new file mode 100644 index 0000000000..1eaf8b5b42 --- /dev/null +++ b/modules/common/include/dap_chain_datum_tx_voting.h @@ -0,0 +1,90 @@ +/* + * Authors: + * Daniil Frolov <daniil.frolov@demlabs.net> + * DeM Labs Inc. https://demlabs.net + * Copyright (c) 2020, All rights reserved. + + This file is part of CellFrame SDK the open source project + + CellFrame SDK is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + CellFrame SDK 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any CellFrame SDK based project. If not, see <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#include <stdint.h> +#include "dap_chain_common.h" +#include "dap_chain_datum_tx.h" +#include "dap_chain_datum_tx_items.h" +#include "dap_time.h" +#include "dap_list.h" +#include "dap_tsd.h" +#include "json.h" + +#define DAP_CHAIN_DATUM_TX_VOTING_QUESTION_MAX_LENGTH 200 +#define DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_LENGTH 100 +#define DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_COUNT 10 + + +typedef enum dap_chain_datum_voting_tsd_type { + VOTING_TSD_TYPE_QUESTION = 0x01, + VOTING_TSD_TYPE_ANSWER, + VOTING_TSD_TYPE_EXPIRE, + VOTING_TSD_TYPE_MAX_VOTES_COUNT, + VOTING_TSD_TYPE_DELEGATED_KEY_REQUIRED, + VOTING_TSD_TYPE_VOTE_CHANGING_ALLOWED, + VOTING_TSD_TYPE_VOTE_TX_COND +} dap_chain_datum_voting_tsd_type_t; + +typedef struct dap_chain_tx_voting { + dap_chain_tx_item_type_t type; +} DAP_ALIGN_PACKED dap_chain_tx_voting_t; + +typedef struct dap_chain_tx_voting_tx_cond { + dap_chain_hash_fast_t tx_hash; + int out_idx; +} DAP_ALIGN_PACKED dap_chain_tx_voting_tx_cond_t; + +typedef struct dap_chain_tx_vote { + dap_chain_tx_item_type_t type; + dap_chain_hash_fast_t voting_hash; + uint64_t answer_idx; +} DAP_ALIGN_PACKED dap_chain_tx_vote_t; + + +typedef struct dap_chain_datum_tx_voting_params { + char *voting_question; + dap_list_t *answers_list; + uint8_t answers_count; + dap_time_t voting_expire; + uint64_t votes_max_count; + bool delegate_key_required; + bool vote_changing_allowed; +} dap_chain_datum_tx_voting_params_t; + + +dap_chain_datum_tx_voting_params_t *dap_chain_voting_parse_tsd(dap_chain_datum_tx_t* a_tx); + +dap_chain_tx_tsd_t* dap_chain_datum_voting_question_tsd_create(const char* a_question_str, size_t str_len); +dap_chain_tx_tsd_t* dap_chain_datum_voting_answer_tsd_create(const char* a_answer_str, size_t str_len); +dap_chain_tx_tsd_t* dap_chain_datum_voting_expire_tsd_create(dap_time_t a_expire); +dap_chain_tx_tsd_t* dap_chain_datum_voting_max_votes_count_tsd_create(uint64_t a_max_count); +dap_chain_tx_tsd_t* dap_chain_datum_voting_delegated_key_required_tsd_create(bool a_delegate_key_required); +dap_chain_tx_tsd_t* dap_chain_datum_voting_vote_changing_allowed_tsd_create(bool a_vote_changing_allowed); +dap_chain_tx_tsd_t* dap_chain_datum_voting_vote_tx_cond_tsd_create(dap_chain_hash_fast_t a_tx_hash, int a_out_idx); + +dap_chain_tx_voting_t *dap_chain_datum_tx_item_voting_create(void); +json_object *dap_chain_datum_tx_item_voting_tsd_to_json(dap_chain_datum_tx_t* a_tx); + + +dap_chain_tx_vote_t *dap_chain_datum_tx_item_vote_create(dap_chain_hash_fast_t *a_voting_hash, uint64_t *a_answer_idx); +json_object *dap_chain_datum_tx_item_vote_to_json(dap_chain_tx_vote_t *a_vote); diff --git a/modules/common/include/dap_chain_datum_vote.h b/modules/common/include/dap_chain_datum_vote.h deleted file mode 100644 index d023c26b51..0000000000 --- a/modules/common/include/dap_chain_datum_vote.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Authors: - * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net> - * DeM Labs Inc. https://demlabs.net - * Copyright (c) 2020, All rights reserved. - - This file is part of CellFrame SDK the open source project - - CellFrame SDK is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - CellFrame SDK 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with any CellFrame SDK based project. If not, see <http://www.gnu.org/licenses/>. -*/ -#pragma once diff --git a/modules/net/dap_chain_ledger.c b/modules/net/dap_chain_ledger.c index f6d78a3b01..49244b1163 100644 --- a/modules/net/dap_chain_ledger.c +++ b/modules/net/dap_chain_ledger.c @@ -70,6 +70,8 @@ typedef struct dap_ledger_verificator { static dap_ledger_verificator_t *s_verificators; static pthread_rwlock_t s_verificators_rwlock; +static dap_chain_ledger_voting_callback_t s_voting_callback; + #define MAX_OUT_ITEMS 10 static const char * const s_ledger_tx_check_err_str[] = { @@ -4203,6 +4205,30 @@ int dap_ledger_tx_cache_check(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx } } + + if (dap_chain_datum_tx_items_get((dap_chain_datum_tx_t*) a_tx, TX_ITEM_TYPE_VOTING, NULL)){ + if (s_voting_callback){ + if (!s_voting_callback(a_ledger, TX_ITEM_TYPE_VOTING, a_tx, false)){ + debug_if(s_debug_more, L_WARNING, "Verificator check error for voting."); + l_err_num = DAP_LEDGER_TX_CHECK_VERIFICATOR_CHECK_FAILURE; + } + } else { + debug_if(s_debug_more, L_WARNING, "Verificator check error for voting item"); + l_err_num = DAP_LEDGER_TX_CHECK_NO_VERIFICATOR_SET; + } + }else if (dap_chain_datum_tx_items_get((dap_chain_datum_tx_t*) a_tx, TX_ITEM_TYPE_VOTE, NULL)){ + if (s_voting_callback){ + if (!s_voting_callback(a_ledger, TX_ITEM_TYPE_VOTE, a_tx, false)){ + debug_if(s_debug_more, L_WARNING, "Verificator check error for vote."); + l_err_num = DAP_LEDGER_TX_CHECK_VERIFICATOR_CHECK_FAILURE; + } + } else { + debug_if(s_debug_more, L_WARNING, "Verificator check error for vote item"); + l_err_num = DAP_LEDGER_TX_CHECK_NO_VERIFICATOR_SET; + } + } + + // 8. Check sovereign tax if (l_fee_check && SUBTRACT_256_256(l_taxed_value, l_fee_sum, &l_taxed_value)) { log_it(L_WARNING, "Fee is greater than sum of inputs"); @@ -4627,6 +4653,11 @@ int dap_ledger_tx_add(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_ha DAP_DELETE (l_addr_str); } + if (dap_chain_datum_tx_items_get((dap_chain_datum_tx_t*) a_tx, TX_ITEM_TYPE_VOTING, NULL) && s_voting_callback) + s_voting_callback(a_ledger, TX_ITEM_TYPE_VOTING, a_tx, true); + else if (dap_chain_datum_tx_items_get((dap_chain_datum_tx_t*) a_tx, TX_ITEM_TYPE_VOTE, NULL) && s_voting_callback) + s_voting_callback(a_ledger, TX_ITEM_TYPE_VOTE, a_tx, true); + // add transaction to the cache list dap_ledger_tx_item_t *l_tx_item = DAP_NEW_Z(dap_ledger_tx_item_t); if ( !l_tx_item ) { @@ -5436,6 +5467,81 @@ dap_list_t *dap_ledger_get_list_tx_outs_with_val(dap_ledger_t *a_ledger, const c return l_list_used_out; } +dap_list_t *dap_ledger_get_list_tx_outs(dap_ledger_t *a_ledger, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from, + uint256_t *a_value_transfer) +{ + dap_list_t *l_list_used_out = NULL; // list of transaction with 'out' items + dap_chain_hash_fast_t l_tx_cur_hash = { 0 }; + uint256_t l_value_transfer = {}; + dap_chain_datum_tx_t *l_tx = dap_ledger_tx_find_by_addr(a_ledger, a_token_ticker, a_addr_from, + &l_tx_cur_hash); + + while(l_tx) + { + // Get all item from transaction by type + dap_list_t *l_list_out_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT_ALL, NULL); + + uint32_t l_out_idx_tmp = 0; // current index of 'out' item + for (dap_list_t *it = l_list_out_items; it; it = dap_list_next(it), l_out_idx_tmp++) { + dap_chain_tx_item_type_t l_type = *(uint8_t *)it->data; + uint256_t l_value = {}; + switch (l_type) { + case TX_ITEM_TYPE_OUT_OLD: { + dap_chain_tx_out_old_t *l_out = (dap_chain_tx_out_old_t *)it->data; + if (!l_out->header.value || memcmp(a_addr_from, &l_out->addr, sizeof(dap_chain_addr_t))) + continue; + l_value = GET_256_FROM_64(l_out->header.value); + } break; + case TX_ITEM_TYPE_OUT: { + dap_chain_tx_out_t *l_out = (dap_chain_tx_out_t *)it->data; + if (memcmp(a_addr_from, &l_out->addr, sizeof(dap_chain_addr_t)) || + dap_strcmp(dap_ledger_tx_get_token_ticker_by_hash(a_ledger, &l_tx_cur_hash), a_token_ticker) || + IS_ZERO_256(l_out->header.value)) + continue; + l_value = l_out->header.value; + } break; + case TX_ITEM_TYPE_OUT_EXT: { + dap_chain_tx_out_ext_t *l_out_ext = (dap_chain_tx_out_ext_t *)it->data; + if (memcmp(a_addr_from, &l_out_ext->addr, sizeof(dap_chain_addr_t)) || + strcmp((char *)a_token_ticker, l_out_ext->token) || + IS_ZERO_256(l_out_ext->header.value) ) { + continue; + } + l_value = l_out_ext->header.value; + } break; + case TX_ITEM_TYPE_OUT_COND: + default: + continue; + } + // Check whether used 'out' items + if (!dap_ledger_tx_hash_is_used_out_item (a_ledger, &l_tx_cur_hash, l_out_idx_tmp, NULL)) { + dap_chain_tx_used_out_item_t *l_item = DAP_NEW_Z(dap_chain_tx_used_out_item_t); + if ( !l_item ) { + log_it(L_CRITICAL, "Out of memory"); + if (l_list_used_out) + dap_list_free_full(l_list_used_out, NULL); + dap_list_free(l_list_out_items); + return NULL; + } + l_item->tx_hash_fast = l_tx_cur_hash; + l_item->num_idx_out = l_out_idx_tmp; + l_item->value = l_value; + l_list_used_out = dap_list_append(l_list_used_out, l_item); + SUM_256_256(l_value_transfer, l_item->value, &l_value_transfer); + } + } + dap_list_free(l_list_out_items); + l_tx = dap_ledger_tx_find_by_addr(a_ledger, a_token_ticker, a_addr_from, + &l_tx_cur_hash); + } + + if (a_value_transfer) { + *a_value_transfer = l_value_transfer; + } + return l_list_used_out; +} + + // Add new verificator callback with associated subtype. Returns 1 if callback replaced, -1 error, overwise returns 0 int dap_ledger_verificator_add(dap_chain_tx_out_cond_subtype_t a_subtype, dap_ledger_verificator_callback_t a_callback, dap_ledger_updater_callback_t a_callback_added) { @@ -5462,6 +5568,20 @@ int dap_ledger_verificator_add(dap_chain_tx_out_cond_subtype_t a_subtype, dap_le return 0; } +int dap_chain_ledger_voting_verificator_add(dap_chain_ledger_voting_callback_t a_callback) +{ + if (!a_callback) + return -1; + + if (s_voting_callback){ + s_voting_callback = a_callback; + return 1; + } + + s_voting_callback = a_callback; + return 0; +} + dap_list_t *dap_ledger_get_txs(dap_ledger_t *a_ledger, size_t a_count, size_t a_page, bool a_reverse, bool a_unspent_only) { dap_ledger_private_t *l_ledger_pvt = PVT(a_ledger); @@ -5561,6 +5681,65 @@ dap_list_t *dap_ledger_get_list_tx_cond_outs_with_val(dap_ledger_t *a_ledger, co return l_list_used_out; } +dap_list_t *dap_ledger_get_list_tx_cond_outs(dap_ledger_t *a_ledger, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from, + dap_chain_tx_out_cond_subtype_t a_subtype, uint256_t *a_value_transfer) +{ + dap_list_t *l_list_used_out = NULL; // list of transaction with 'out' items + dap_chain_hash_fast_t l_tx_cur_hash = { 0 }; + uint256_t l_value_transfer = { }; + dap_chain_datum_tx_t *l_tx = dap_ledger_tx_find_by_addr(a_ledger, a_token_ticker, a_addr_from, &l_tx_cur_hash); + while(l_tx) + { + // Get all item from transaction by type + dap_list_t *l_list_out_cond_items = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_OUT_COND, NULL); + + uint32_t l_out_idx_tmp = 0; // current index of 'out' item + for(dap_list_t *it = l_list_out_cond_items; it; it = dap_list_next(it), l_out_idx_tmp++) { + dap_chain_tx_item_type_t l_type = *(uint8_t*) it->data; + uint256_t l_value = { }; + switch (l_type) { + case TX_ITEM_TYPE_OUT_COND: { + dap_chain_tx_out_cond_t *l_out_cond = (dap_chain_tx_out_cond_t*) it->data; + if(IS_ZERO_256(l_out_cond->header.value) || a_subtype != l_out_cond->header.subtype) { + continue; + } + l_value = l_out_cond->header.value; + } + break; + default: + continue; + } + if (!IS_ZERO_256(l_value)) { + dap_chain_tx_used_out_item_t *l_item = DAP_NEW_Z(dap_chain_tx_used_out_item_t); + if ( !l_item ) { + if (l_list_used_out) + dap_list_free_full(l_list_used_out, NULL); + dap_list_free(l_list_out_cond_items); + return NULL; + } + l_item->tx_hash_fast = l_tx_cur_hash; + l_item->num_idx_out = l_out_idx_tmp; + l_item->value = l_value; + l_list_used_out = dap_list_append(l_list_used_out, l_item); + SUM_256_256(l_value_transfer, l_item->value, &l_value_transfer); + } + } + dap_list_free(l_list_out_cond_items); + l_tx = dap_ledger_tx_find_by_addr(a_ledger, a_token_ticker, a_addr_from, &l_tx_cur_hash); + } + + // nothing to tranfer (not enough funds) + if(!l_list_used_out) { + dap_list_free_full(l_list_used_out, NULL); + return NULL; + } + + if (a_value_transfer) { + *a_value_transfer = l_value_transfer; + } + return l_list_used_out; +} + void dap_ledger_tx_add_notify(dap_ledger_t *a_ledger, dap_ledger_tx_add_notify_t a_callback, void *a_arg) { if (!a_ledger) { log_it(L_ERROR, "NULL ledger passed to dap_ledger_tx_add_notify()"); diff --git a/modules/net/dap_chain_net.c b/modules/net/dap_chain_net.c index 3544354dec..a94d103d59 100644 --- a/modules/net/dap_chain_net.c +++ b/modules/net/dap_chain_net.c @@ -118,6 +118,7 @@ #include "dap_chain_net_srv_xchange.h" #include "dap_chain_node_net_ban_list.h" #include "dap_chain_cs_esbocs.h" +#include "dap_chain_net_voting.h" #include <stdio.h> #include <sys/types.h> @@ -270,6 +271,7 @@ int dap_chain_net_init() dap_stream_ch_chain_init(); dap_stream_ch_chain_net_init(); dap_chain_node_client_init(); + dap_chain_net_voting_init(); dap_chain_node_net_ban_list_init(); dap_cli_server_cmd_add ("net", s_cli_net, "Network commands", "net list [chains -net <chain net name>]\n" diff --git a/modules/net/dap_chain_net_voting.c b/modules/net/dap_chain_net_voting.c new file mode 100644 index 0000000000..00855faa3f --- /dev/null +++ b/modules/net/dap_chain_net_voting.c @@ -0,0 +1,1360 @@ +/* + * Authors: + * Daniil Frolov <daniil.frolov@demlabs.net> + * DeM Labs Inc. https://demlabs.net + * DeM Labs Open source community https://github.com/demlabsinc + * Copyright (c) 2017-2019 + * All rights reserved. + + This file is part of DAP (Deus Applications Prototypes) the open source project + + DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. + */ +#include <time.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> + +#include "dap_chain_net_voting.h" +#include "dap_chain_net_srv_stake_pos_delegate.h" +#include "dap_chain_node_cli.h" +#include "dap_chain_mempool.h" +#include "uthash.h" +#include "utlist.h" + +#define LOG_TAG "chain_net_voting" + +typedef struct dap_chain_net_voting_params_offsets{ + dap_chain_datum_tx_t* voting_tx; + size_t voting_question_offset; + size_t voting_question_length; + dap_list_t* option_offsets_list; + size_t voting_expire_offset; + size_t votes_max_count_offset; + size_t delegate_key_required_offset; + size_t vote_changing_allowed_offset; +} dap_chain_net_voting_params_offsets_t; + +typedef struct dap_chain_net_vote_option { + size_t vote_option_offset; + size_t vote_option_length; +} dap_chain_net_vote_option_t; + +typedef struct dap_chain_net_voting_cond_outs { + dap_chain_hash_fast_t tx_hash; + int out_idx; + + UT_hash_handle hh; +} dap_chain_net_voting_cond_outs_t; + +typedef struct dap_chain_net_vote { + dap_chain_hash_fast_t vote_hash; + dap_chain_hash_fast_t pkey_hash; + uint64_t answer_idx; + uint256_t weight; +} dap_chain_net_vote_t; + +typedef struct dap_chain_net_votings { + dap_chain_hash_fast_t voting_hash; + dap_chain_net_voting_params_offsets_t voting_params; + dap_list_t *votes; + dap_chain_net_id_t net_id; + + pthread_rwlock_t s_tx_outs_rwlock; + dap_chain_net_voting_cond_outs_t *voting_spent_cond_outs; + + UT_hash_handle hh; +} dap_chain_net_votings_t; + +static dap_chain_net_votings_t *s_votings; +static pthread_rwlock_t s_votings_rwlock; + +static int s_datum_tx_voting_coin_check_cond_out(dap_chain_net_t *a_net, dap_hash_fast_t a_voting_hash, dap_hash_fast_t a_tx_cond_hash, int a_cond_out_idx); +/// -1 error, 0 - unspent, 1 - spent +static int s_datum_tx_voting_coin_check_spent(dap_chain_net_t *a_net, dap_hash_fast_t a_voting_hash, dap_hash_fast_t a_tx_prev_hash, int a_out_idx); +static bool s_datum_tx_voting_verification_callback(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t a_type, dap_chain_datum_tx_t *a_tx_in, bool a_apply); +static int s_cli_voting(int argc, char **argv, void **a_str_reply); + +int dap_chain_net_voting_init() +{ + pthread_rwlock_init(&s_votings_rwlock, NULL); + dap_chain_ledger_voting_verificator_add(s_datum_tx_voting_verification_callback); + dap_cli_server_cmd_add("voting", s_cli_voting, "Voting commands.", "" + "voting create -net <net_name> -question <\"Question_string\"> -options <\"Option1\", \"Option2\" ... \"OptionN\"> [-expire <voting_expire_time_in_RCF822>] [-max_votes_count <Votes_count>] [-delegated_key_required] [-vote_changing_allowed] -fee <value_datoshi> -w <fee_wallet_name>\n" + "voting vote -net <net_name> -hash <voting_hash> -option_idx <option_index> [-cert <delegate_cert_name>] -fee <value_datoshi> -w <fee_wallet_name>\n" + "voting list -net <net_name>\n" + "voting dump -net <net_name> -hash <voting_hash>\n"); + return 0; +} + +uint64_t* dap_chain_net_voting_get_result(dap_ledger_t* a_ledger, dap_chain_hash_fast_t* a_voting_hash) +{ + if(!a_voting_hash){ + return NULL; + } + + uint64_t* l_voting_results = NULL; + + dap_chain_net_votings_t * l_voting = NULL; + pthread_rwlock_rdlock(&s_votings_rwlock); + HASH_FIND(hh, s_votings, a_voting_hash, sizeof(dap_hash_fast_t), l_voting); + pthread_rwlock_unlock(&s_votings_rwlock); + if(!l_voting || l_voting->net_id.uint64 != a_ledger->net->pub.id.uint64){ + char* l_hash_str = dap_hash_fast_to_str_new(a_voting_hash); + log_it(L_ERROR, "Can't find voting with hash %s in net %s", l_hash_str, a_ledger->net->pub.name); + DAP_DEL_Z(l_hash_str); + return NULL; + } + + l_voting_results = DAP_NEW_Z_SIZE(uint64_t, sizeof(uint64_t)*dap_list_length(l_voting->voting_params.option_offsets_list)); + if (!l_voting_results){ + log_it(L_CRITICAL, "Memory allocation error"); + return NULL; + } + + dap_list_t* l_temp = l_voting->votes; + while(l_temp){ + dap_chain_net_vote_t* l_vote = l_temp->data; + if (l_vote->answer_idx >= dap_list_length(l_voting->voting_params.option_offsets_list)) + continue; + + l_voting_results[l_vote->answer_idx]++; + + l_temp = l_temp->next; + } + + + return l_voting_results; +} + + + +bool s_datum_tx_voting_verification_callback(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t a_type, dap_chain_datum_tx_t *a_tx_in, bool a_apply) +{ + + dap_hash_fast_t l_hash = {}; + dap_hash_fast(a_tx_in, dap_chain_datum_tx_get_size(a_tx_in), &l_hash); + + if (a_type == TX_ITEM_TYPE_VOTING){ + if (!a_apply){ + dap_chain_net_votings_t * l_voting = NULL; + pthread_rwlock_rdlock(&s_votings_rwlock); + HASH_FIND(hh, s_votings, &l_hash, sizeof(dap_hash_fast_t), l_voting); + pthread_rwlock_unlock(&s_votings_rwlock); + if(l_voting && l_voting->net_id.uint64 == a_ledger->net->pub.id.uint64){ + char* l_hash_str = dap_hash_fast_to_str_new(&l_hash); + log_it(L_ERROR, "Voting with hash %s is already presents in net %s", l_hash_str, a_ledger->net->pub.name); + DAP_DEL_Z(l_hash_str); + return false; + } + + dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_TSD, NULL); + dap_list_t* l_temp = l_tsd_list; + size_t l_question_len = 0; + size_t l_options_count = 0; + while (l_temp){ + dap_tsd_t* l_tsd = (dap_tsd_t*)((dap_chain_tx_tsd_t*)l_temp->data)->tsd; + dap_chain_net_vote_option_t *l_vote_option = NULL; + switch(l_tsd->type){ + case VOTING_TSD_TYPE_QUESTION: + l_question_len = l_tsd->size; + break; + case VOTING_TSD_TYPE_ANSWER: + l_options_count++; + break; + default: + break; + } + l_temp = l_temp->next; + } + dap_list_free(l_tsd_list); + + if (!l_question_len || !l_options_count) + return false; + + return true; + } + + dap_chain_net_votings_t *l_item = DAP_NEW_Z_SIZE(dap_chain_net_votings_t, sizeof(dap_chain_net_votings_t)); + l_item->voting_hash = l_hash; + l_item->voting_params.voting_tx = a_tx_in; + l_item->net_id = a_ledger->net->pub.id; + pthread_rwlock_init(&l_item->s_tx_outs_rwlock, NULL); + + dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_TSD, NULL); + dap_list_t* l_temp = l_tsd_list; + while (l_temp){ + dap_tsd_t* l_tsd = (dap_tsd_t*)((dap_chain_tx_tsd_t*)l_temp->data)->tsd; + dap_chain_net_vote_option_t *l_vote_option = NULL; + switch(l_tsd->type){ + case VOTING_TSD_TYPE_QUESTION: + l_item->voting_params.voting_question_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx); + l_item->voting_params.voting_question_length = l_tsd->size; + break; + case VOTING_TSD_TYPE_ANSWER: + l_vote_option = DAP_NEW_Z(dap_chain_net_vote_option_t); + l_vote_option->vote_option_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx); + l_vote_option->vote_option_length = l_tsd->size; + l_item->voting_params.option_offsets_list = dap_list_append(l_item->voting_params.option_offsets_list, l_vote_option); + break; + case VOTING_TSD_TYPE_EXPIRE: + l_item->voting_params.voting_expire_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx); + break; + case VOTING_TSD_TYPE_MAX_VOTES_COUNT: + l_item->voting_params.votes_max_count_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx); + break; + case VOTING_TSD_TYPE_DELEGATED_KEY_REQUIRED: + l_item->voting_params.delegate_key_required_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx); + break; + case VOTING_TSD_TYPE_VOTE_CHANGING_ALLOWED: + l_item->voting_params.vote_changing_allowed_offset = (size_t)(l_tsd->data - (byte_t*)l_item->voting_params.voting_tx); + break; + default: + break; + } + l_temp = l_temp->next; + } + dap_list_free(l_tsd_list); + + pthread_rwlock_wrlock(&s_votings_rwlock); + HASH_ADD(hh, s_votings, voting_hash, sizeof(dap_hash_fast_t), l_item); + pthread_rwlock_unlock(&s_votings_rwlock); + return true; + } else if (a_type == TX_ITEM_TYPE_VOTE){ + dap_chain_tx_vote_t *l_vote_tx_item = (dap_chain_tx_vote_t *)dap_chain_datum_tx_item_get(a_tx_in, 0, TX_ITEM_TYPE_VOTE, NULL); + if(!l_vote_tx_item){ + log_it(L_ERROR, "Can't find vote item"); + return false; + } + + dap_chain_net_votings_t * l_voting = NULL; + pthread_rwlock_wrlock(&s_votings_rwlock); + HASH_FIND(hh, s_votings, &l_vote_tx_item->voting_hash, sizeof(dap_hash_fast_t), l_voting); + pthread_rwlock_unlock(&s_votings_rwlock); + if(!l_voting || l_voting->net_id.uint64 != a_ledger->net->pub.id.uint64) { + char *l_hash_str = dap_chain_hash_fast_to_str_new(&l_hash); + log_it(L_ERROR, "Can't find voting with hash %s in net %s", l_hash_str, a_ledger->net->pub.name); + DAP_DELETE(l_hash_str); + return false; + } + + if (!a_apply){ + if (l_vote_tx_item->answer_idx > dap_list_length(l_voting->voting_params.option_offsets_list)){ + log_it(L_ERROR, "Invalid vote option index."); + return false; + } + + if(l_voting->voting_params.votes_max_count_offset){ + uint64_t l_votes_max_count = *(uint64_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.votes_max_count_offset); + if (l_votes_max_count && dap_list_length(l_voting->votes) >= l_votes_max_count){ + log_it(L_ERROR, "The required number of votes has been collected."); + return false; + } + } + + if(l_voting->voting_params.voting_expire_offset){ + dap_time_t l_expire = *(dap_time_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_expire_offset); + if( l_expire && l_expire <= a_tx_in->header.ts_created){ + log_it(L_ERROR, "The voting has been expired."); + return false; + } + } + + dap_hash_fast_t pkey_hash = {}; + dap_chain_tx_sig_t *l_vote_sig = NULL; + int l_item_cnt = 0; + dap_list_t* l_signs_list = NULL; + l_signs_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_SIG, &l_item_cnt); + + if(!l_signs_list){ + log_it(L_ERROR, "Can't get sign."); + return false; + } + l_vote_sig = (dap_chain_tx_sig_t *)(dap_list_last(l_signs_list)->data); + dap_sign_get_pkey_hash((dap_sign_t*)l_vote_sig->sig, &pkey_hash); + if (l_voting->voting_params.delegate_key_required_offset && + *(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.delegate_key_required_offset)){ + if (!dap_chain_net_srv_stake_check_pkey_hash(&pkey_hash)){ + log_it(L_ERROR, "The voting required a delegated key."); + return false; + } + } + + dap_list_t *l_temp = l_voting->votes; + while(l_temp){ + if (dap_hash_fast_compare(&((dap_chain_net_vote_t *)l_temp->data)->pkey_hash, &pkey_hash)){ + if(l_voting->voting_params.vote_changing_allowed_offset && + *(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.vote_changing_allowed_offset)){ + //delete conditional outputs + dap_chain_datum_tx_t *l_old_tx = dap_ledger_tx_find_by_hash(a_ledger, &((dap_chain_net_vote_t *)l_temp->data)->vote_hash); + + dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(l_old_tx, TX_ITEM_TYPE_TSD, NULL); + dap_list_t* l_tsd_temp = l_tsd_list; + while (l_tsd_temp){ + dap_tsd_t* l_tsd = (dap_tsd_t*)((dap_chain_tx_tsd_t*)l_tsd_temp->data)->tsd; + dap_hash_fast_t l_hash = ((dap_chain_tx_voting_tx_cond_t*)l_tsd->data)->tx_hash; + if(l_tsd->type == VOTING_TSD_TYPE_VOTE_TX_COND){ + dap_chain_net_voting_cond_outs_t *l_tx_outs = NULL; + pthread_rwlock_wrlock(&l_voting->s_tx_outs_rwlock); + HASH_FIND(hh, l_voting->voting_spent_cond_outs, &l_hash, sizeof(dap_hash_fast_t), l_tx_outs); + if(l_tx_outs) + HASH_DELETE(hh, l_voting->voting_spent_cond_outs, l_tx_outs); + pthread_rwlock_unlock(&l_voting->s_tx_outs_rwlock); + } + l_tsd_temp = l_tsd_temp->next; + } + dap_list_free(l_tsd_list); + + + //delete vote + l_voting->votes = dap_list_remove(l_voting->votes, l_temp->data); + break; + } else { + log_it(L_ERROR, "The voting don't allow change your vote."); + return false; + } + } + l_temp = l_temp->next; + } + } + + uint256_t l_weight = {}; + + // check out conds + dap_list_t* l_tsd_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_TSD, NULL); + dap_list_t* l_tsd_temp = l_tsd_list; + while (l_tsd_temp){ + dap_tsd_t* l_tsd = (dap_tsd_t*)((dap_chain_tx_tsd_t*)l_tsd_temp->data)->tsd; + dap_hash_fast_t l_hash = ((dap_chain_tx_voting_tx_cond_t*)l_tsd->data)->tx_hash; + int l_out_idx = ((dap_chain_tx_voting_tx_cond_t*)l_tsd->data)->out_idx; + if(l_tsd->type == VOTING_TSD_TYPE_VOTE_TX_COND){ + if (s_datum_tx_voting_coin_check_cond_out(a_ledger->net, l_vote_tx_item->voting_hash, + l_hash, l_out_idx) != 0){ + l_tsd_temp = l_tsd_temp->next; + continue; + } + dap_chain_datum_tx_t *l_tx_prev_temp = dap_ledger_tx_find_by_hash(a_ledger, &l_hash); + dap_chain_tx_out_cond_t *l_prev_out = (dap_chain_tx_out_cond_t*)dap_chain_datum_tx_item_get(l_tx_prev_temp, &l_out_idx, TX_ITEM_TYPE_OUT_COND, NULL); + if(!l_prev_out || l_prev_out->header.subtype != DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_LOCK){ + l_tsd_temp = l_tsd_temp->next; + continue; + } + SUM_256_256(l_weight, l_prev_out->header.value, &l_weight); + + dap_chain_net_voting_cond_outs_t *l_item = DAP_NEW_Z_SIZE(dap_chain_net_voting_cond_outs_t, sizeof(dap_chain_net_voting_cond_outs_t)); + l_item->tx_hash = l_hash; + l_item->out_idx = l_out_idx; + pthread_rwlock_wrlock(&l_voting->s_tx_outs_rwlock); + HASH_ADD(hh, l_voting->voting_spent_cond_outs, tx_hash, sizeof(dap_hash_fast_t), l_item); + pthread_rwlock_unlock(&l_voting->s_tx_outs_rwlock); + } + l_tsd_temp = l_tsd_temp->next; + } + dap_list_free(l_tsd_list); + // check inputs + dap_list_t *l_ins_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_IN, NULL); + if (!l_ins_list){ + log_it(L_ERROR, "Can't get inputs from tx"); + return -1; + } + dap_list_t *l_in_temp = l_ins_list; + while(l_in_temp){ + dap_chain_tx_in_t *l_tx_in = (dap_chain_tx_in_t *)l_in_temp->data; + if (s_datum_tx_voting_coin_check_spent(a_ledger->net, l_vote_tx_item->voting_hash, l_tx_in->header.tx_prev_hash, l_tx_in->header.tx_out_prev_idx) == 0){ + dap_chain_datum_tx_t *l_tx_prev_temp = dap_ledger_tx_find_by_hash(a_ledger, &l_tx_in->header.tx_prev_hash); + int l_out_prev_idx = (int)l_tx_in->header.tx_out_prev_idx; + dap_chain_tx_out_t *l_prev_out_union = (dap_chain_tx_out_t *)dap_chain_datum_tx_out_get_by_out_idx(l_tx_prev_temp, l_out_prev_idx); + if (!l_prev_out_union){ + l_in_temp = l_in_temp->next; + continue; + } + + switch (l_prev_out_union->header.type) { + case TX_ITEM_TYPE_OUT: + case TX_ITEM_TYPE_OUT_EXT: + SUM_256_256(l_weight, l_prev_out_union->header.value, &l_weight); + } + } + l_in_temp = l_in_temp->next; + } + dap_list_free(l_ins_list); + + if (IS_ZERO_256(l_weight)){ + log_it(L_ERROR, "No unspent coins"); + return false; + } + + + if (a_apply){ + dap_hash_fast_t pkey_hash = {}; + dap_chain_tx_sig_t *l_vote_sig = NULL; + int l_item_cnt = 0; + dap_list_t* l_signs_list = NULL; + l_signs_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_SIG, &l_item_cnt); + + if(!l_signs_list){ + log_it(L_ERROR, "Can't get sign."); + return false; + } + l_vote_sig = (dap_chain_tx_sig_t *)(dap_list_last(l_signs_list)->data); + dap_sign_get_pkey_hash((dap_sign_t*)l_vote_sig->sig, &pkey_hash); + + dap_chain_net_vote_t *l_vote_item = DAP_NEW_Z(dap_chain_net_vote_t); + if (!l_vote_item){ + log_it(L_CRITICAL, "Memory allocate_error!"); + return false; + } + l_vote_item->vote_hash = l_hash; + l_vote_item->pkey_hash = pkey_hash; + l_vote_item->answer_idx = l_vote_tx_item->answer_idx; + l_vote_item->weight = l_weight; + + dap_list_t *l_temp = l_voting->votes; + while(l_temp){ + if (dap_hash_fast_compare(&((dap_chain_net_vote_t *)l_temp->data)->pkey_hash, &pkey_hash)){ + if(l_voting->voting_params.vote_changing_allowed_offset && + *(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.vote_changing_allowed_offset)){ + + l_voting->votes = dap_list_append(l_voting->votes, l_vote_item); + + log_it(L_ERROR, "Vote is changed."); + return true; + } else { + log_it(L_ERROR, "The voting don't allow change your vote."); + DAP_DELETE(l_vote_item); + return false; + } + } + l_temp = l_temp->next; + } + log_it(L_INFO, "Vote is accepted."); + l_voting->votes = dap_list_append(l_voting->votes, l_vote_item); + } + return true; + } else { + log_it(L_ERROR, "Item is not supported in votings."); + } + + return false; +} + +static dap_list_t* s_get_options_list_from_str(const char* a_str) +{ + dap_list_t* l_ret = NULL; + char * l_options_str_dup = strdup(a_str); + if (!l_options_str_dup) { + log_it(L_ERROR, "Memory allocation error in %s, line %d", __PRETTY_FUNCTION__, __LINE__); + return 0; + } + + size_t l_opt_str_len = strlen(l_options_str_dup); + char* l_option_start_ptr = l_options_str_dup; + dap_string_t* l_option_str = dap_string_new(NULL); + for (size_t i = 0; i <= l_opt_str_len; i++){ + if(i == l_opt_str_len){ + l_option_str = dap_string_append_len(l_option_str, l_option_start_ptr, &l_options_str_dup[i] - l_option_start_ptr); + char* l_option = dap_string_free(l_option_str, false); + l_option = dap_strstrip(l_option);// removes leading and trailing spaces + l_ret = dap_list_append(l_ret, l_option); + break; + } + if (l_options_str_dup [i] == ','){ + if(i > 0 && l_options_str_dup [i-1] == '\\'){ + l_option_str = dap_string_append_len(l_option_str, l_option_start_ptr, i-1); + l_option_start_ptr = &l_options_str_dup [i]; + continue; + } + l_option_str = dap_string_append_len(l_option_str, l_option_start_ptr, &l_options_str_dup[i] - l_option_start_ptr); + l_option_start_ptr = &l_options_str_dup [i+1]; + char* l_option = dap_string_free(l_option_str, false); + l_option_str = dap_string_new(NULL); + l_option = dap_strstrip(l_option);// removes leading and trailing spaces + l_ret = dap_list_append(l_ret, l_option); + } + } + + free(l_options_str_dup); + + return l_ret; +} + +static int s_cli_voting(int a_argc, char **a_argv, void **a_str_reply) +{ + enum {CMD_NONE=0, CMD_CREATE, CMD_VOTE, CMD_LIST, CMD_DUMP}; + + const char* l_net_str = NULL; + int arg_index = 1; + dap_chain_net_t *l_net = NULL; + + const char *l_hash_out_type = NULL; + dap_cli_server_cmd_find_option_val(a_argv, 1, a_argc, "-H", &l_hash_out_type); + if(!l_hash_out_type) + l_hash_out_type = "hex"; + if(dap_strcmp(l_hash_out_type,"hex") && dap_strcmp(l_hash_out_type, "base58")) + return -1; + + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-net", &l_net_str); + // Select chain network + if(!l_net_str) { + dap_cli_server_cmd_set_reply_text(a_str_reply, "command requires parameter '-net'"); + return -2; + } else { + if((l_net = dap_chain_net_by_name(l_net_str)) == NULL) { // Can't find such network + dap_cli_server_cmd_set_reply_text(a_str_reply, + "command requires parameter '-net' to be valid chain network name"); + return -3; + } + } + + int l_cmd = CMD_NONE; + if (dap_cli_server_cmd_find_option_val(a_argv, 1, 2, "create", NULL)) + l_cmd = CMD_CREATE; + else if (dap_cli_server_cmd_find_option_val(a_argv, 1, 2, "vote", NULL)) + l_cmd = CMD_VOTE; + else if (dap_cli_server_cmd_find_option_val(a_argv, 1, 2, "list", NULL)) + l_cmd = CMD_LIST; + else if (dap_cli_server_cmd_find_option_val(a_argv, 1, 2, "dump", NULL)) + l_cmd = CMD_DUMP; + + + switch(l_cmd){ + case CMD_CREATE:{ + const char* l_question_str = NULL; + const char* l_options_list_str = NULL; + const char* l_voting_expire_str = NULL; + const char* l_max_votes_count_str = NULL; + const char* l_fee_str = NULL; + const char* l_wallet_str = NULL; + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-question", &l_question_str); + if (!l_question_str){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Voting requires a question parameter to be valid."); + return -100; + } + + if (strlen(l_question_str) > DAP_CHAIN_DATUM_TX_VOTING_QUESTION_MAX_LENGTH){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "The question must contain no more than %d characters", DAP_CHAIN_DATUM_TX_VOTING_QUESTION_MAX_LENGTH); + return -101; + } + + dap_list_t *l_options_list = NULL; + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-options", &l_options_list_str); + if (!l_options_list_str){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Voting requires a question parameter to be valid."); + return -101; + } + // Parse options list + l_options_list = s_get_options_list_from_str(l_options_list_str); + if(!l_options_list || dap_list_length(l_options_list) < 2){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Number of options must be 2 or greater."); + return -102; + } + + if(dap_list_length(l_options_list)>DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_COUNT){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "The voting can to contain no more than %d options", DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_COUNT); + return -102; + } + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-expire", &l_voting_expire_str); + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-max_votes_count", &l_max_votes_count_str); + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-fee", &l_fee_str); + if (!l_fee_str){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Voting requires paramete -fee to be valid."); + return -102; + } + uint256_t l_value_fee = dap_chain_balance_scan(l_fee_str); + if (IS_ZERO_256(l_value_fee)) { + dap_cli_server_cmd_set_reply_text(a_str_reply, + "command requires parameter '-fee' to be valid uint256"); + return -103; + } + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-w", &l_wallet_str); + if (!l_wallet_str){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Voting requires parameter -w to be valid."); + return -103; + } + + dap_enc_key_t *l_priv_key = NULL; + const char *c_wallets_path = dap_chain_wallet_get_path(g_config); + dap_chain_wallet_t *l_wallet_fee = dap_chain_wallet_open(l_wallet_str, c_wallets_path); + if (!l_wallet_fee) { + dap_cli_server_cmd_set_reply_text(a_str_reply, "Wallet %s does not exist", l_wallet_str); + return -112; + } + l_priv_key = dap_chain_wallet_get_key(l_wallet_fee, 0); + + const dap_chain_addr_t *l_addr_from = (const dap_chain_addr_t *) dap_chain_wallet_get_addr(l_wallet_fee, l_net->pub.id); + + if(!l_addr_from) { + dap_cli_server_cmd_set_reply_text(a_str_reply, "source address is invalid"); + return -113; + } + + const char *l_native_ticker = l_net->pub.native_ticker; + uint256_t l_net_fee = {}, l_total_fee = {}, l_value_transfer; + dap_chain_addr_t l_addr_fee = {}; + bool l_net_fee_used = dap_chain_net_tx_get_fee(l_net->pub.id, &l_net_fee, &l_addr_fee); + SUM_256_256(l_net_fee, l_value_fee, &l_total_fee); + + dap_ledger_t* l_ledger = dap_ledger_by_net_name(l_net->pub.name); + dap_list_t *l_list_used_out = dap_ledger_get_list_tx_outs_with_val(l_ledger, l_native_ticker, + l_addr_from, l_total_fee, &l_value_transfer); + if (!l_list_used_out) { + dap_cli_server_cmd_set_reply_text(a_str_reply, "Not enough funds to transfer"); + return -113; + } + // create empty transaction + dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + + // Add Voting item + dap_chain_tx_voting_t* l_voting_item = dap_chain_datum_tx_item_voting_create(); + + dap_chain_datum_tx_add_item(&l_tx, l_voting_item); + DAP_DELETE(l_voting_item); + + // Add question to tsd data + dap_chain_tx_tsd_t* l_question_tsd = dap_chain_datum_voting_question_tsd_create(l_question_str, strlen(l_question_str)); + dap_chain_datum_tx_add_item(&l_tx, l_question_tsd); + DAP_DELETE(l_question_tsd); + + // Add options to tsd + dap_list_t *l_temp = l_options_list; + while(l_temp){ + if(strlen((char*)l_temp->data) > DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_LENGTH){ + dap_chain_datum_tx_delete(l_tx); + dap_list_free_full(l_options_list, NULL); + dap_cli_server_cmd_set_reply_text(a_str_reply, "The option must contain no more than %d characters", DAP_CHAIN_DATUM_TX_VOTING_OPTION_MAX_LENGTH); + return -114; + } + dap_chain_tx_tsd_t* l_option = dap_chain_datum_voting_answer_tsd_create((char*)l_temp->data, strlen((char*)l_temp->data)); + if(!l_option){ + dap_chain_datum_tx_delete(l_tx); + dap_list_free_full(l_options_list, NULL); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create option tsd item."); + return -114; + } + dap_chain_datum_tx_add_item(&l_tx, l_option); + DAP_DEL_Z(l_option); + + l_temp = l_temp->next; + } + dap_list_free_full(l_options_list, NULL); + + // add voting expire time if needed + if(l_voting_expire_str){ + dap_time_t l_expired_time = dap_time_from_str_rfc822(l_voting_expire_str); + if(!l_expired_time){ + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't parse expire time"); + return -114; + } + if (l_expired_time < dap_time_now()){ + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create voting with expired time"); + return -114; + } + + dap_chain_tx_tsd_t* l_expired_item = dap_chain_datum_voting_expire_tsd_create(l_expired_time); + if(!l_expired_item){ + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create expired tsd item."); + return -114; + } + dap_chain_datum_tx_add_item(&l_tx, l_expired_item); + DAP_DEL_Z(l_expired_item); + } + + // Add vote max count if needed + if(l_max_votes_count_str){ + uint64_t l_max_votes_count = atoll(l_max_votes_count_str); + dap_chain_tx_tsd_t* l_max_votes_item = dap_chain_datum_voting_max_votes_count_tsd_create(l_max_votes_count); + if(!l_max_votes_item){ + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create expired tsd item."); + return -114; + } + dap_chain_datum_tx_add_item(&l_tx, l_max_votes_item); + DAP_DEL_Z(l_max_votes_item); + } + + if(dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-delegated_key_required", NULL)){ + dap_chain_tx_tsd_t* l_delegated_key_req_item = dap_chain_datum_voting_delegated_key_required_tsd_create(true); + if(!l_delegated_key_req_item){ + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create delegated key req tsd item."); + return -114; + } + dap_chain_datum_tx_add_item(&l_tx, l_delegated_key_req_item); + DAP_DEL_Z(l_delegated_key_req_item); + } + + if(dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-vote_changing_allowed", NULL)){ + dap_chain_tx_tsd_t* l_vote_changing_item = dap_chain_datum_voting_vote_changing_allowed_tsd_create(true); + if(!l_vote_changing_item){ + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create delegated key req tsd item."); + return -114; + } + dap_chain_datum_tx_add_item(&l_tx, l_vote_changing_item); + DAP_DEL_Z(l_vote_changing_item); + } + + // add 'in' items + uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); + assert(EQUAL_256(l_value_to_items, l_value_transfer)); + dap_list_free_full(l_list_used_out, NULL); + uint256_t l_value_pack = {}; + // Network fee + if (l_net_fee_used) { + if (dap_chain_datum_tx_add_out_item(&l_tx, &l_addr_fee, l_net_fee) == 1) + SUM_256_256(l_value_pack, l_net_fee, &l_value_pack); + else { + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add net fee out."); + return -114; + } + } + // Validator's fee + if (!IS_ZERO_256(l_value_fee)) { + if (dap_chain_datum_tx_add_fee_item(&l_tx, l_value_fee) == 1) + SUM_256_256(l_value_pack, l_value_fee, &l_value_pack); + else { + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add net fee out."); + return -115; + } + } + // coin back + uint256_t l_value_back; + SUBTRACT_256_256(l_value_transfer, l_value_pack, &l_value_back); + if(!IS_ZERO_256(l_value_back)) { + if(dap_chain_datum_tx_add_out_item(&l_tx, l_addr_from, l_value_back) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add out with value back"); + return -116; + } + } + + // add 'sign' items + if(dap_chain_datum_tx_add_sign_item(&l_tx, l_priv_key) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't sign tx"); + return -117; + } + + size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx); + dap_hash_fast_t l_tx_hash; + dap_hash_fast(l_tx, l_tx_size, &l_tx_hash); + dap_chain_datum_t *l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, l_tx, l_tx_size); + DAP_DELETE(l_tx); + dap_chain_t* l_chain = dap_chain_net_get_default_chain_by_chain_type(l_net, CHAIN_TYPE_TX); + + char *l_ret = dap_chain_mempool_datum_add(l_datum, l_chain, l_hash_out_type); + if (l_ret) + dap_cli_server_cmd_set_reply_text(a_str_reply, "Datum %s successfully added to mempool", l_ret); + else + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add datum to mempool"); + DAP_DELETE(l_datum); + }break; + case CMD_VOTE:{ + const char* l_cert_name = NULL; + const char* l_fee_str = NULL; + const char* l_wallet_str = NULL; + const char* l_hash_str = NULL; + const char* l_option_idx_str = NULL; + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-hash", &l_hash_str); + if(!l_hash_str){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'vote' require the parameter -hash"); + return -110; + } + + dap_hash_fast_t l_voting_hash = {}; + dap_chain_hash_fast_from_str(l_hash_str, &l_voting_hash); + + + dap_chain_hash_fast_t l_pkey_hash; + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-cert", &l_cert_name); + dap_cert_t * l_cert = dap_cert_find_by_name(l_cert_name); + if (l_cert_name){ + if (l_cert == NULL) { + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't find \"%s\" certificate", l_cert_name); + return -7; + } + if (l_cert->enc_key == NULL) { + dap_cli_server_cmd_set_reply_text(a_str_reply, "No key found in \"%s\" certificate", l_cert_name ); + return -8; + } + // Get publivc key hash + size_t l_pub_key_size = 0; + uint8_t *l_pub_key = dap_enc_key_serialize_pub_key(l_cert->enc_key, &l_pub_key_size);; + if (l_pub_key == NULL) { + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't serialize public key of certificate \"%s\"", l_cert_name); + return -9; + } + + dap_hash_fast(l_pub_key, l_pub_key_size, &l_pkey_hash); + } + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-fee", &l_fee_str); + if (!l_fee_str){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'vote' requires paramete -fee to be valid."); + return -102; + } + uint256_t l_value_fee = dap_chain_balance_scan(l_fee_str); + if (IS_ZERO_256(l_value_fee)) { + dap_cli_server_cmd_set_reply_text(a_str_reply, + "command requires parameter '-fee' to be valid uint256"); + return -103; + } + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-w", &l_wallet_str); + if (!l_wallet_str){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'vote' requires parameter -w to be valid."); + return -103; + } + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-option_idx", &l_option_idx_str); + if (!l_option_idx_str){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'vote' requires parameter -option_idx to be valid."); + return -103; + } + + dap_chain_net_votings_t *l_voting = NULL; + pthread_rwlock_rdlock(&s_votings_rwlock); + HASH_FIND(hh, s_votings, &l_voting_hash, sizeof(l_voting_hash),l_voting); + pthread_rwlock_unlock(&s_votings_rwlock); + if(!l_voting || l_voting->net_id.uint64 != l_net->pub.id.uint64){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't find voting with hash %s", l_hash_str); + return -111; + } + + if(l_voting->voting_params.votes_max_count_offset){ + uint64_t l_max_count = *(uint64_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.votes_max_count_offset); + if (l_max_count && dap_list_length(l_voting->votes) >= l_max_count){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "This voting already received the required number of votes."); + return -111; + } + } + + if(l_voting->voting_params.voting_expire_offset){ + dap_time_t l_expire = *(dap_time_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_expire_offset); + dap_time_t l_time_now = dap_time_now(); + if (l_expire && l_time_now > l_expire){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "This voting already expired."); + return -111; + } + } + + if(l_voting->voting_params.delegate_key_required_offset && + *(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.delegate_key_required_offset) ){ + if (!l_cert){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "This voting required a delegated key."); + return -111; + } else if(!dap_chain_net_srv_stake_check_pkey_hash(&l_pkey_hash)){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Your key is not delegated."); + return -111; + } + } + + if(l_cert){ + dap_list_t *l_temp = l_voting->votes; + while(l_temp){ + if (dap_hash_fast_compare(&((dap_chain_net_vote_t *)l_temp->data)->pkey_hash, &l_pkey_hash)){ + if(!l_voting->voting_params.vote_changing_allowed_offset || + !*(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.vote_changing_allowed_offset)){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "The voting doesn't allow change your vote."); + return -113; + } + } + l_temp = l_temp->next; + } + } + + dap_enc_key_t *l_priv_key = NULL; + const char *c_wallets_path = dap_chain_wallet_get_path(g_config); + dap_chain_wallet_t *l_wallet_fee = dap_chain_wallet_open(l_wallet_str, c_wallets_path); + if (!l_wallet_fee) { + dap_cli_server_cmd_set_reply_text(a_str_reply, "Wallet %s does not exist", l_wallet_str); + return -112; + } + l_priv_key = dap_chain_wallet_get_key(l_wallet_fee, 0); + + const dap_chain_addr_t *l_addr_from = (const dap_chain_addr_t *) dap_chain_wallet_get_addr(l_wallet_fee, l_net->pub.id); + + if(!l_addr_from) { + dap_cli_server_cmd_set_reply_text(a_str_reply, "source address is invalid"); + return -113; + } + + const char *l_native_ticker = l_net->pub.native_ticker; + uint256_t l_net_fee = {}, l_total_fee = {}, l_value_transfer; + dap_chain_addr_t l_addr_fee = {}; + bool l_net_fee_used = dap_chain_net_tx_get_fee(l_net->pub.id, &l_net_fee, &l_addr_fee); + SUM_256_256(l_net_fee, l_value_fee, &l_total_fee); + + dap_ledger_t* l_ledger = dap_ledger_by_net_name(l_net->pub.name); + dap_list_t *l_list_used_out = dap_ledger_get_list_tx_outs(l_ledger, l_native_ticker, l_addr_from, &l_value_transfer); + if (!l_list_used_out || compare256(l_value_transfer, l_total_fee) <= 0) { + dap_cli_server_cmd_set_reply_text(a_str_reply, "Not enough funds to transfer"); + return -113; + } + + + // check outputs UTXOs + dap_list_t *l_utxo_temp = l_list_used_out; + uint256_t l_value_transfer_new = {}; + while(l_utxo_temp){ + dap_chain_tx_used_out_item_t *l_out = (dap_chain_tx_used_out_item_t *)l_utxo_temp->data; + if (s_datum_tx_voting_coin_check_spent(l_net, l_voting_hash, l_out->tx_hash_fast, l_out->num_idx_out) != 0 && + (!l_voting->voting_params.vote_changing_allowed_offset || + !*(bool*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.vote_changing_allowed_offset))){ + dap_list_t *l_temp = l_utxo_temp; + l_utxo_temp = l_utxo_temp->next; + dap_list_delete_link(l_list_used_out, l_temp); + continue; + } + SUM_256_256(l_value_transfer_new, l_out->value, &l_value_transfer_new); + l_utxo_temp = l_utxo_temp->next; + } + + if (IS_ZERO_256(l_value_transfer_new) || compare256(l_value_transfer_new, l_total_fee) <= 0){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "You have not unspent UTXO for participation in this voting."); + return -113; + } + + l_value_transfer = l_value_transfer_new; + + // create empty transaction + dap_chain_datum_tx_t *l_tx = dap_chain_datum_tx_create(); + + // Add vote item + uint64_t l_option_idx_count = atoll(l_option_idx_str); + if (l_option_idx_count > dap_list_length(l_voting->voting_params.option_offsets_list)){ + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Invalid option index."); + return -114; + } + dap_chain_tx_vote_t* l_vote_item = dap_chain_datum_tx_item_vote_create(&l_voting_hash, &l_option_idx_count); + if(!l_vote_item){ + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create vote item."); + return -114; + } + dap_chain_datum_tx_add_item(&l_tx, l_vote_item); + DAP_DEL_Z(l_vote_item); + + // add stake out conds items + dap_list_t *l_outs = dap_ledger_get_list_tx_cond_outs(l_ledger, l_net->pub.native_ticker, l_addr_from, + DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_LOCK, NULL); + dap_list_t *l_temp = l_outs; + while(l_temp){ + dap_chain_tx_used_out_item_t *l_out_item = (dap_chain_tx_used_out_item_t *)l_temp->data; + if (dap_ledger_tx_hash_is_used_out_item(l_net->pub.ledger, &l_out_item->tx_hash_fast, l_out_item->num_idx_out, NULL) || + s_datum_tx_voting_coin_check_cond_out(l_net, l_voting_hash, l_out_item->tx_hash_fast, l_out_item->num_idx_out ) != 0){ + l_temp = l_temp->next; + continue; + } + dap_chain_tx_tsd_t *l_item = dap_chain_datum_voting_vote_tx_cond_tsd_create(l_out_item->tx_hash_fast, l_out_item->num_idx_out); + if(!l_item){ + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't create tsd tx cond item."); + dap_list_free_full(l_outs, NULL); + return -114; + } + dap_chain_datum_tx_add_item(&l_tx, l_item); + DAP_DEL_Z(l_item); + l_temp = l_temp->next; + } + dap_list_free_full(l_outs, NULL); + + // add 'in' items + uint256_t l_value_to_items = dap_chain_datum_tx_add_in_item_list(&l_tx, l_list_used_out); + assert(EQUAL_256(l_value_to_items, l_value_transfer)); + dap_list_free_full(l_list_used_out, NULL); + uint256_t l_value_pack = {}; + // Network fee + if (l_net_fee_used) { + if (dap_chain_datum_tx_add_out_item(&l_tx, &l_addr_fee, l_net_fee) == 1) + SUM_256_256(l_value_pack, l_net_fee, &l_value_pack); + else { + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add net fee out."); + return -114; + } + } + // Validator's fee + if (!IS_ZERO_256(l_value_fee)) { + if (dap_chain_datum_tx_add_fee_item(&l_tx, l_value_fee) == 1) + SUM_256_256(l_value_pack, l_value_fee, &l_value_pack); + else { + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add net fee out."); + return -115; + } + } + // coin back + uint256_t l_value_back; + SUBTRACT_256_256(l_value_transfer, l_value_pack, &l_value_back); + if(!IS_ZERO_256(l_value_back)) { + if(dap_chain_datum_tx_add_out_item(&l_tx, l_addr_from, l_value_back) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add out with value back"); + return -116; + } + } + + // add 'sign' items with wallet sign + if(dap_chain_datum_tx_add_sign_item(&l_tx, l_priv_key) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't sign tx"); + return -117; + } + + // add 'sign' items with delegated key if needed + if(l_cert){ + if(dap_chain_datum_tx_add_sign_item(&l_tx, l_cert->enc_key) != 1) { + dap_chain_datum_tx_delete(l_tx); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't sign tx"); + return -117; + } + } + + size_t l_tx_size = dap_chain_datum_tx_get_size(l_tx); + dap_hash_fast_t l_tx_hash; + dap_hash_fast(l_tx, l_tx_size, &l_tx_hash); + dap_chain_datum_t *l_datum = dap_chain_datum_create(DAP_CHAIN_DATUM_TX, l_tx, l_tx_size); + DAP_DELETE(l_tx); + dap_chain_t* l_chain = dap_chain_net_get_default_chain_by_chain_type(l_net, CHAIN_TYPE_TX); + + char *l_ret = dap_chain_mempool_datum_add(l_datum, l_chain, l_hash_out_type); + if (l_ret) + dap_cli_server_cmd_set_reply_text(a_str_reply, "Datum %s successfully added to mempool", l_ret); + else + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't add datum to mempool"); + DAP_DELETE(l_datum); + + + }break; + case CMD_LIST:{ + dap_string_t *l_str_out = dap_string_new(NULL); + dap_string_append_printf(l_str_out, "List of votings in net %s:\n\n", l_net->pub.name); + dap_chain_net_votings_t *l_voting = NULL, *l_tmp; + pthread_rwlock_rdlock(&s_votings_rwlock); + HASH_ITER(hh, s_votings, l_voting, l_tmp){ + if (l_voting->net_id.uint64 != l_net->pub.id.uint64) + continue; + + char *l_hash_str = dap_chain_hash_fast_to_str_new(&l_voting->voting_hash); + dap_string_append_printf(l_str_out, "Voting hash: %s\n", l_hash_str); + DAP_DELETE(l_hash_str); + dap_string_append(l_str_out, "Voting question:\n"); + char* l_voting_question = (char*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_question_offset); + dap_string_append_len(l_str_out, + l_voting_question, + l_voting->voting_params.voting_question_length > strlen(l_voting_question) ? strlen(l_voting_question) : l_voting->voting_params.voting_question_length); + dap_string_append(l_str_out, "\n\n"); + } + pthread_rwlock_unlock(&s_votings_rwlock); + + dap_cli_server_cmd_set_reply_text(a_str_reply, "%s", l_str_out->str); + dap_string_free(l_str_out, true); + }break; + case CMD_DUMP:{ + const char* l_hash_str = NULL; + + dap_cli_server_cmd_find_option_val(a_argv, arg_index, a_argc, "-hash", &l_hash_str); + if(!l_hash_str){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Command 'results' require the parameter -hash"); + return -110; + } + + dap_hash_fast_t l_voting_hash = {}; + dap_chain_hash_fast_from_str(l_hash_str, &l_voting_hash); + dap_chain_net_votings_t *l_voting = NULL; + pthread_rwlock_rdlock(&s_votings_rwlock); + HASH_FIND(hh, s_votings, &l_voting_hash, sizeof(l_voting_hash),l_voting); + pthread_rwlock_unlock(&s_votings_rwlock); + if(!l_voting){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't find voting with hash %s", l_hash_str); + return -111; + } + + uint64_t l_options_count = 0; + l_options_count = dap_list_length(l_voting->voting_params.option_offsets_list); + if(!l_options_count){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "No options. May be datum is crashed."); + return -111; + } + + struct voting_results {uint64_t num_of_votes; uint256_t weights;}; + + struct voting_results* l_results = DAP_NEW_Z_SIZE(struct voting_results, sizeof(struct voting_results)*l_options_count); + if(!l_results){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Memlory allocation error!"); + return -111; + } + dap_list_t* l_list_tmp = l_voting->votes; + uint256_t l_total_weight = {}; + while(l_list_tmp){ + dap_chain_net_vote_t *l_vote = l_list_tmp->data; + l_results[l_vote->answer_idx].num_of_votes++; + SUM_256_256(l_results[l_vote->answer_idx].weights, l_vote->weight, &l_results[l_vote->answer_idx].weights); + l_list_tmp = l_list_tmp->next; + SUM_256_256(l_total_weight, l_vote->weight, &l_total_weight); + } + + uint64_t l_votes_count = 0; + l_votes_count = dap_list_length(l_voting->votes); + dap_string_t *l_str_out = dap_string_new(NULL); + dap_string_append_printf(l_str_out, "Dump of voting %s:\n\n", l_hash_str); + dap_string_append_len(l_str_out, + (char*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_question_offset), + l_voting->voting_params.voting_question_length); + dap_string_append(l_str_out, "\n\n"); + + if(l_voting->voting_params.voting_expire_offset){ + char l_tmp_buf[70]; + dap_time_t l_expire = *(dap_time_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.voting_expire_offset); + dap_string_append_printf(l_str_out, "\t Voting expire: %s", dap_ctime_r(&l_expire, l_tmp_buf)); + dap_string_truncate(l_str_out, l_str_out->len - 1); + dap_string_append_printf(l_str_out, " (%s)\n", l_expire > dap_time_now() ? "active" : "expired"); + } + if (l_voting->voting_params.votes_max_count_offset){ + uint64_t l_max_count = *(uint64_t*)((byte_t*)l_voting->voting_params.voting_tx + l_voting->voting_params.votes_max_count_offset); + dap_string_append_printf(l_str_out, "\t Votes max count: %"DAP_UINT64_FORMAT_U" (%s)\n", l_max_count, l_max_count <= l_votes_count ? "closed" : "active"); + } + dap_string_append_printf(l_str_out, "\t Changing vote is %s available.\n", l_voting->voting_params.vote_changing_allowed_offset ? "" : "not"); + dap_string_append_printf(l_str_out, "\t A delegated key is%s required to participate in voting. \n", l_voting->voting_params.delegate_key_required_offset ? "" : " not"); + dap_string_append_printf(l_str_out, "\n\nResults:\n\n"); + for (uint64_t i = 0; i < dap_list_length(l_voting->voting_params.option_offsets_list); i++){ + dap_string_append_printf(l_str_out, "%"DAP_UINT64_FORMAT_U") ", i); + dap_list_t* l_option = dap_list_nth(l_voting->voting_params.option_offsets_list, (uint64_t)i); + dap_chain_net_vote_option_t* l_vote_option = (dap_chain_net_vote_option_t*)l_option->data; + dap_string_append_len(l_str_out, + (char*)((byte_t*)l_voting->voting_params.voting_tx + l_vote_option->vote_option_offset), + l_vote_option->vote_option_length); + float l_percentage = l_votes_count ? ((float)l_results[i].num_of_votes/l_votes_count)*100 : 0; + uint256_t l_weight_percentage = {}; + + DIV_256_COIN(l_results[i].weights, l_total_weight, &l_weight_percentage); + MULT_256_COIN(l_weight_percentage, dap_chain_coins_to_balance("100.0"), &l_weight_percentage); + char *l_weight_percentage_str = dap_uint256_decimal_to_round_char(l_weight_percentage, 2, true); + dap_string_append_printf(l_str_out, "\nVotes: %"DAP_UINT64_FORMAT_U" (%.2f%%)\nWeight: %s (%s) %s (%s%%)\n", l_results[i].num_of_votes, l_percentage, + dap_chain_balance_to_coins(l_results[i].weights), dap_chain_balance_print(l_results[i].weights), l_net->pub.native_ticker, + l_weight_percentage_str); + } + DAP_DELETE(l_results); + dap_string_append_printf(l_str_out, "\nTotal number of votes: %"DAP_UINT64_FORMAT_U, l_votes_count); + dap_string_append_printf(l_str_out, "\nTotal weight: %s (%s) %s\n\n", dap_chain_balance_to_coins(l_total_weight), dap_chain_balance_print(l_total_weight), l_net->pub.native_ticker); + dap_cli_server_cmd_set_reply_text(a_str_reply, "%s", l_str_out->str); + dap_string_free(l_str_out, true); + }break; + default:{ + + }break; + } + + return 0; +} + +static int s_datum_tx_voting_coin_check_spent(dap_chain_net_t *a_net, dap_hash_fast_t a_voting_hash, dap_hash_fast_t a_tx_prev_hash, int a_out_idx) +{ + int l_coin_is_spent = 0; + + + dap_ledger_t *l_ledger = a_net->pub.ledger; + if(!l_ledger){ + log_it(L_ERROR, "Can't find ledger"); + return -1; + } + + dap_chain_datum_tx_t *l_voting_tx = dap_ledger_tx_find_by_hash(l_ledger, &a_voting_hash); + const char *l_native_ticker = a_net->pub.native_ticker; + + dap_list_t *l_tx_list = NULL; // "stack" for saving txs on up level + dap_chain_datum_tx_t *l_tx = dap_ledger_tx_find_by_hash(l_ledger, &a_tx_prev_hash); + if (!l_tx){ + log_it(L_ERROR, "Can't find tx"); + return -1; + } + + if (l_tx->header.ts_created < l_voting_tx->header.ts_created){ + return 0; + } + + if (s_datum_tx_voting_coin_check_cond_out(a_net, a_voting_hash, a_tx_prev_hash, a_out_idx) != 0){ + return 1; + } + + dap_chain_tx_vote_t *l_vote =(dap_chain_tx_vote_t *) dap_chain_datum_tx_item_get(l_tx, NULL, TX_ITEM_TYPE_VOTE, NULL); + if(l_vote && dap_hash_fast_compare(&l_vote->voting_hash, &a_voting_hash)){ + dap_chain_net_votings_t *l_voting = NULL; + pthread_rwlock_wrlock(&s_votings_rwlock); + HASH_FIND(hh, s_votings, &a_voting_hash, sizeof(dap_hash_fast_t), l_voting); + pthread_rwlock_unlock(&s_votings_rwlock); + if (l_voting) + { + dap_list_t *l_temp = l_voting->votes; + while (l_temp){ + dap_chain_net_vote_t *l_vote = (dap_chain_net_vote_t *)l_temp->data; + if (dap_hash_fast_compare(&l_vote->vote_hash, &a_tx_prev_hash)){ + l_coin_is_spent = 1; + return 1; + } + l_temp = l_temp->next; + } + } + } + + + dap_list_t *l_ins_list_temp = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN, NULL); + dap_list_t *l_cond_ins_list = dap_chain_datum_tx_items_get(l_tx, TX_ITEM_TYPE_IN_COND, NULL); + if (!l_ins_list_temp && !l_cond_ins_list){ + log_it(L_ERROR, "Can't get inputs from tx"); + return -1; + } + + dap_list_t *l_ins_list = NULL; + l_ins_list = dap_list_concat(l_ins_list, l_ins_list_temp); + l_ins_list = dap_list_concat(l_ins_list, l_cond_ins_list); + + l_tx_list = dap_list_append(l_tx_list, l_ins_list); + dap_list_t* l_tx_temp = dap_list_last(l_tx_list); + + while(l_tx_temp && !l_coin_is_spent){ + if (l_tx_temp->data == NULL){ + l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp); + l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL; + continue; + } + dap_list_t *l_ins_list = (dap_list_t*)l_tx_temp->data; + dap_chain_tx_in_t* l_temp_in = (dap_chain_tx_in_t*)l_ins_list->data; + dap_chain_datum_tx_t *l_tx_prev_temp = dap_ledger_tx_find_by_hash(l_ledger, &l_temp_in->header.tx_prev_hash); + int l_out_prev_idx = (int)l_temp_in->header.tx_out_prev_idx; + + const char* l_tx_token = NULL; + dap_chain_tx_out_t *l_prev_out_union = (dap_chain_tx_out_t*)dap_chain_datum_tx_out_get_by_out_idx(l_tx_prev_temp, l_out_prev_idx); + if (!l_prev_out_union){ + l_tx_temp->data = dap_list_remove(l_tx_temp->data, l_temp_in); + if (l_tx_temp->data == NULL){ + l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp); + l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL; + } + continue; + } + + switch (l_prev_out_union->header.type) { + case TX_ITEM_TYPE_OUT:{ + l_tx_token = dap_ledger_tx_get_token_ticker_by_hash(l_ledger, &l_temp_in->header.tx_prev_hash); + }break; + case TX_ITEM_TYPE_OUT_EXT:{ + dap_chain_tx_out_ext_t *l_temp_out = (dap_chain_tx_out_ext_t *)l_prev_out_union; + l_tx_token = l_temp_out->token; + }break; + case TX_ITEM_TYPE_OUT_COND:{ + dap_chain_tx_out_cond_t *l_temp_out = (dap_chain_tx_out_cond_t*)l_prev_out_union; + if (l_temp_out->header.subtype == DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_LOCK || + s_datum_tx_voting_coin_check_cond_out(a_net, a_voting_hash, l_temp_in->header.tx_prev_hash, l_temp_in->header.tx_out_prev_idx) == 0) + break; + } + default: + l_tx_temp->data = dap_list_remove((dap_list_t*)l_tx_temp->data, l_temp_in); + if (l_tx_temp->data == NULL){ + l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp); + l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL; + } + continue; + } + + if (l_tx_prev_temp->header.ts_created < l_voting_tx->header.ts_created || + dap_strcmp(l_tx_token, l_native_ticker)){ + l_tx_temp->data = dap_list_remove((dap_list_t*)l_tx_temp->data, l_temp_in); + if (l_tx_temp->data == NULL){ + l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp); + l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL; + } + continue; + } + + + dap_chain_tx_vote_t *l_vote =(dap_chain_tx_vote_t *) dap_chain_datum_tx_item_get(l_tx_prev_temp, NULL, TX_ITEM_TYPE_VOTE, NULL); + if(l_vote && dap_hash_fast_compare(&l_vote->voting_hash, &a_voting_hash)){ + dap_chain_net_votings_t *l_voting = NULL; + pthread_rwlock_wrlock(&s_votings_rwlock); + HASH_FIND(hh, s_votings, &a_voting_hash, sizeof(dap_hash_fast_t), l_voting); + pthread_rwlock_unlock(&s_votings_rwlock); + dap_list_t *l_temp = NULL; + while (l_temp){ + dap_chain_net_vote_t *l_vote = (dap_chain_net_vote_t *)l_temp->data; + if (dap_hash_fast_compare(&l_vote->vote_hash, &l_temp_in->header.tx_prev_hash)){ + l_coin_is_spent = 1; + break; + } + l_temp = l_temp->next; + } + } + + + l_ins_list = dap_chain_datum_tx_items_get(l_tx_prev_temp, TX_ITEM_TYPE_IN, NULL); + l_tx_list = dap_list_append(l_tx_list, l_ins_list); + l_tx_temp->data = dap_list_remove((dap_list_t*)l_tx_temp->data, l_temp_in); + l_tx_temp = l_tx_list ? dap_list_last(l_tx_list) : NULL; + + } + + if(l_tx_list){ + l_tx_temp = l_tx_list; + while(l_tx_temp){ + if (l_tx_temp->data) + dap_list_free((dap_list_t*)l_tx_temp->data); + l_tx_list = dap_list_delete_link(l_tx_list, l_tx_temp); + l_tx_temp = dap_list_first(l_tx_list); + } + } + + return l_coin_is_spent; +} + +static int s_datum_tx_voting_coin_check_cond_out(dap_chain_net_t *a_net, dap_hash_fast_t a_voting_hash, dap_hash_fast_t a_tx_cond_hash, int a_cond_out_idx) +{ + + dap_chain_net_votings_t * l_voting = NULL; + pthread_rwlock_wrlock(&s_votings_rwlock); + HASH_FIND(hh, s_votings, &a_voting_hash, sizeof(dap_hash_fast_t), l_voting); + pthread_rwlock_unlock(&s_votings_rwlock); + if(!l_voting || l_voting->net_id.uint64 != a_net->pub.id.uint64) { + char *l_hash_str = dap_chain_hash_fast_to_str_new(&a_voting_hash); + log_it(L_ERROR, "Can't find voting with hash %s in net %s", l_hash_str, a_net->pub.name); + DAP_DELETE(l_hash_str); + return -1; + } + + dap_chain_net_voting_cond_outs_t *l_tx_outs = NULL; + pthread_rwlock_wrlock(&l_voting->s_tx_outs_rwlock); + HASH_FIND(hh, l_voting->voting_spent_cond_outs, &a_tx_cond_hash, sizeof(dap_hash_fast_t), l_tx_outs); + pthread_rwlock_unlock(&l_voting->s_tx_outs_rwlock); + + if (!l_tx_outs || l_tx_outs->out_idx != a_cond_out_idx){ + return 0; + } + + return 1; +} diff --git a/modules/net/include/dap_chain_ledger.h b/modules/net/include/dap_chain_ledger.h index 7fa2f5d3e9..129086c4b0 100644 --- a/modules/net/include/dap_chain_ledger.h +++ b/modules/net/include/dap_chain_ledger.h @@ -120,6 +120,7 @@ typedef void (* dap_ledger_tx_add_notify_t)(void *a_arg, dap_ledger_t *a_ledger, typedef void (* dap_ledger_bridged_tx_notify_t)(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_hash_fast_t *a_tx_hash, void *a_arg); typedef bool (*dap_ledger_cache_tx_check_callback_t)(dap_hash_fast_t *a_tx_hash); typedef struct dap_chain_net dap_chain_net_t; +typedef bool (*dap_chain_ledger_voting_callback_t)(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t a_type, dap_chain_datum_tx_t *a_tx, bool a_apply); //Change this UUID to automatically reload ledger cache on next node startup #define DAP_LEDGER_CACHE_RELOAD_ONCE_UUID "0c92b759-a565-448f-b8bd-99103dacf7fc" @@ -331,15 +332,19 @@ uint256_t dap_ledger_tx_cache_get_out_cond_value(dap_ledger_t *a_ledger, dap_cha // Put this summary value to a_value_transfer dap_list_t *dap_ledger_get_list_tx_outs_with_val(dap_ledger_t *a_ledger, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from, uint256_t a_value_need, uint256_t *a_value_transfer); - +dap_list_t *dap_ledger_get_list_tx_outs(dap_ledger_t *a_ledger, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from, + uint256_t *a_value_transfer); // Get the list of 'out_cond' items with summary value >= than a_value_need dap_list_t *dap_ledger_get_list_tx_cond_outs_with_val(dap_ledger_t *a_ledger, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from, dap_chain_tx_out_cond_subtype_t a_subtype, uint256_t a_value_need, uint256_t *a_value_transfer); +dap_list_t *dap_ledger_get_list_tx_cond_outs(dap_ledger_t *a_ledger, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from, + dap_chain_tx_out_cond_subtype_t a_subtype, uint256_t *a_value_transfer); // Add new verificator callback with associated subtype. Returns 1 if callback replaced, overwise returns 0 int dap_ledger_verificator_add(dap_chain_tx_out_cond_subtype_t a_subtype, dap_ledger_verificator_callback_t a_callback, dap_ledger_updater_callback_t a_callback_added); - +// Add new verificator callback for voting. Returns 1 if callback replaced, overwise returns 0 +int dap_chain_ledger_voting_verificator_add(dap_chain_ledger_voting_callback_t a_callback); // Getting a list of transactions from the ledger. dap_list_t * dap_ledger_get_txs(dap_ledger_t *a_ledger, size_t a_count, size_t a_page, bool a_reverse, bool a_unspent_only); diff --git a/modules/net/include/dap_chain_net_voting.h b/modules/net/include/dap_chain_net_voting.h new file mode 100644 index 0000000000..be180c5015 --- /dev/null +++ b/modules/net/include/dap_chain_net_voting.h @@ -0,0 +1,39 @@ +/* + * Authors: + * Daniil Frolov <daniil.frolov@demlabs.net> + * DeM Labs Inc. https://demlabs.net + * Cellframe Network https://cellframe.net + * Copyright (c) 2022 + * All rights reserved. + + This file is part of DAP (Deus Applications Prototypes) the open source project + + DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. +*/ +#pragma once +#include "dap_chain_datum_tx_voting.h" +#include "dap_chain_ledger.h" +#include "dap_chain_net.h" +#include "dap_chain_common.h" + + +typedef struct dap_chain_net_voting_result { + uint64_t answer_idx; + uint64_t votes_count; +} dap_chain_net_voting_result_t; + +int dap_chain_net_voting_init(); + + +uint64_t* dap_chain_net_voting_get_result(dap_ledger_t* a_ledger, dap_chain_hash_fast_t* a_voting_hash); -- GitLab