work on create topic Hedera Call and Hedera Task and get TransactionReceipt some time after call of create topic

This commit is contained in:
Dario 2020-11-10 19:31:20 +01:00 committed by Ulf Gebhardt
parent 5bb34f26a2
commit bfe4f6ad01
No known key found for this signature in database
GPG Key ID: 81308EFE29ABFEBD
24 changed files with 412 additions and 92 deletions

View File

@ -9,6 +9,8 @@ CREATE TABLE `hedera_topics` (
`submit_key_id` int unsigned DEFAULT NULL,
`current_timeout` DATETIME NOT NULL DEFAULT '2000-01-01 00:00:00',
`sequence_number` bigint unsigned DEFAULT '0',
`running_hash` VARBINARY(64) DEFAULT NULL,
`running_hash_version` int unsigned DEFAULT 0,
`updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `topic_hedera_id` (`topic_hedera_id`)

View File

@ -124,7 +124,26 @@ void AdminTopicPage::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::
if(DataTypeConverter::strToInt(group_id_string, group_id) != DataTypeConverter::NUMBER_PARSE_OKAY) {
addError(new Error("Int convert error", "Error converting group_id to int"));
}
}
}
//const std::string& name, int autoRenewAccountId, int autoRenewPeriod, int groupId
auto hedera_topic = controller::HederaTopic::create(name, auto_renew_account, auto_renew_period, group_id);
if(!hedera_topic->getModel()->insertIntoDB(true)) {
addError(new Error("Topic", "error saving into db"));
} else {
auto payer = controller::HederaAccount::load(auto_renew_account);
if(payer.isNull()) {
addError(new Error("Payer", "payer account not found"));
} else {
auto hedera_task = hedera_topic->createTopic(payer, user);
if(hedera_task.isNull()) {
addError(new Error("Create Topic", "Failed"));
getErrors(hedera_topic);
}
}
}
}
@ -208,7 +227,7 @@ void AdminTopicPage::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::
responseStream << "\t}\n";
responseStream << "\t\n";
responseStream << "</style>\n";
#line 133 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 152 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( getErrorsHtml() );
responseStream << "\n";
responseStream << "<div class=\"content-container info-container\">\n";
@ -233,7 +252,7 @@ void AdminTopicPage::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::
responseStream << "\t\t\t\t<div class=\"cell header-cell c5\">Aktionen</div>\n";
responseStream << "\t\t\t</div>\n";
responseStream << "\t\t\t";
#line 155 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 174 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
for(auto it = hedera_topics.begin(); it != hedera_topics.end(); it++) {
auto hedera_topic_model = (*it)->getModel();
auto updateUrl = ServerConfig::g_serverPath + "/topic?action=getTopicInfos&topic_id=" + std::to_string(hedera_topic_model->getID());
@ -245,58 +264,69 @@ void AdminTopicPage::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::
} else if((hedera_topic_model->getCurrentTimeout() - Poco::DateTime()) < Poco::Timespan(2,0,0,0,0)) {
timeout_color = "orange-color";
}
std::string topic_hedera_id_string = "";
auto topic_hedera_id = (*it)->getTopicHederaId();
if(!topic_hedera_id.isNull()) {
topic_hedera_id_string = topic_hedera_id->getModel()->toString();
}
responseStream << "\n";
responseStream << "\t\t\t\t<div class=\"row\">\n";
responseStream << "\t\t\t\t\t<div class=\"cell c2\">";
#line 168 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( (*it)->getTopicHederaId()->getModel()->toString() );
#line 193 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( topic_hedera_id_string );
responseStream << "</div>\n";
responseStream << "\t\t\t\t\t<div class=\"cell c3\">";
#line 169 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 194 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( hedera_topic_model->getName() );
responseStream << "</div>\n";
responseStream << "\t\t\t\t\t<div class=\"cell c3\">";
#line 170 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 195 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( model::table::HederaAccount::hederaNetworkTypeToString(renew_account_model->getNetworkType()) );
responseStream << "</div>\n";
responseStream << "\t\t\t\t\t<div class=\"cell c4\">";
#line 171 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 196 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( renew_account_model->getBalanceString() );
responseStream << "</div>\n";
responseStream << "\t\t\t\t\t<div class=\"cell c4\">";
#line 172 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 197 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( hedera_topic_model->getAutoRenewPeriodString() );
responseStream << "</div>\n";
responseStream << "\t\t\t\t\t<div class=\"cell c3\">";
#line 173 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 198 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( hedera_topic_model->getGroupId() );
responseStream << "</div>\n";
responseStream << "\t\t\t\t\t<div class=\"cell c3 ";
#line 174 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 199 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( timeout_color );
responseStream << "\">";
#line 174 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 199 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( hedera_topic_model->getCurrentTimeoutString() );
responseStream << "</div>\n";
responseStream << "\t\t\t\t\t<div class=\"cell c2\">";
#line 175 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 200 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( hedera_topic_model->getSequenceNumber() );
responseStream << "</div>\n";
responseStream << "\t\t\t\t\t<div class=\"cell c3\">";
#line 176 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 201 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( hedera_topic_model->getUpdatedString() );
responseStream << "</div>\n";
responseStream << "\t\t\t\t\t<div class=\"cell c5\">\n";
responseStream << "\t\t\t\t\t<div class=\"cell c5\">";
#line 202 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
if(!topic_hedera_id.isNull()) { responseStream << "\n";
responseStream << "\t\t\t\t\t\t<button class=\"form-button\" title=\"Query on Hedera, cost some fees\" onclick=\"window.location.href='";
#line 178 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 203 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( updateUrl );
responseStream << "'\" >\n";
responseStream << "\t\t\t\t\t\t\tget topic infos\n";
responseStream << "\t\t\t\t\t\t</button>\n";
responseStream << "\t\t\t\t\t\t";
#line 206 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
} responseStream << "\n";
responseStream << "\t\t\t\t\t</div>\n";
responseStream << "\t\t\t\t</div>\n";
responseStream << "\t\t\t";
#line 183 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 209 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
} responseStream << "\n";
responseStream << "\t\t</div>\n";
responseStream << "\t</div>\n";
@ -305,72 +335,72 @@ void AdminTopicPage::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::
responseStream << "\t</div>\n";
responseStream << "\t<div class=\"center-form-form\">\n";
responseStream << "\t\t<form method=\"POST\" action=\"";
#line 190 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 216 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( ServerConfig::g_serverPath );
responseStream << "/topic\">\n";
responseStream << "\t\t\t<label class=\"form-label\" for=\"topic-name\">Name</label>\n";
responseStream << "\t\t\t<input type=\"text\" class=\"form-control\" id=\"topic-name\" name=\"topic-name\" value=\"";
#line 192 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 218 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( name );
responseStream << "\">\n";
responseStream << "\t\t\t<label class=\"form-label\" for=\"topic-auto-renew-account\">Auto Renew Hedera Account</label>\n";
responseStream << "\t\t\t<select name=\"topic-auto-renew-account\" id=\"topic-auto-renew-account\">\n";
responseStream << "\t\t\t\t";
#line 195 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 221 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
for(auto it = hedera_accounts.begin(); it != hedera_accounts.end(); it++) {
auto model = (*it)->getModel();
responseStream << "\n";
responseStream << "\t\t\t\t\t<option title=\"";
#line 198 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 224 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( model->toString() );
responseStream << "\" value=\"";
#line 198 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 224 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( model->getID() );
responseStream << "\" ";
#line 198 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 224 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
if(auto_renew_account == model->getID()) { responseStream << "selected=\"selected\"";
#line 198 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 224 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
} responseStream << ">";
#line 198 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 224 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( (*it)->toShortSelectOptionName() );
responseStream << "</option>\n";
responseStream << "\t\t\t\t";
#line 199 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 225 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
} responseStream << "\n";
responseStream << "\t\t\t</select>\n";
responseStream << "\t\t\t<label class=\"form-label\" for=\"topic-auto-renew-period\">Auto Renew Period in seconds</label>\n";
responseStream << "\t\t\t<div><input class=\"form-control input-40\" id=\"topic-auto-renew-period\" value=\"";
#line 202 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 228 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( auto_renew_period );
responseStream << "\" type=\"number\" name=\"topic-auto-renew-period\"/><span style=\"margin-left:8px\" id=\"readable-auto-renew-period\"></span><div>\n";
responseStream << "\t\t\t<label class=\"form-label\" for=\"topic-group\">Group</label>\n";
responseStream << "\t\t\t<select class=\"form-control\" name=\"topic-group\" id=\"topic-group\">\t\t\t\n";
responseStream << "\t\t\t\t<option value=\"-1\">Keine Gruppe</option>\n";
responseStream << "\t\t\t\t";
#line 206 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 232 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
for(auto it = groups.begin(); it != groups.end(); it++) {
auto group_model = (*it)->getModel(); responseStream << "\n";
responseStream << "\t\t\t\t\t<option title=\"";
#line 208 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 234 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( group_model->getDescription() );
responseStream << "\" value=\"";
#line 208 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 234 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( group_model->getID() );
responseStream << "\" ";
#line 208 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 234 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
if(group_id == group_model->getID()) { responseStream << "selected=\"selected\"";
#line 208 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 234 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
} responseStream << ">";
#line 208 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 234 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( group_model->getName() );
responseStream << "</option>\n";
responseStream << "\t\t\t\t";
#line 209 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 235 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
} responseStream << "\n";
responseStream << "\t\t\t</select>\n";
responseStream << "\t\t\t\n";
responseStream << "\t\t\t<input class=\"center-form-submit form-button\" type=\"submit\" name=\"submit\" value=\"";
#line 212 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 238 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( gettext("Add Topic") );
responseStream << "\">\n";
responseStream << "\t\t</form>\n";
@ -401,7 +431,7 @@ void AdminTopicPage::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::
// end include footer.cpsp
responseStream << "\n";
responseStream << "<script type=\"text/javascript\" src=\"";
#line 217 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
#line 243 "F:\\Gradido\\gradido_login_server\\src\\cpsp\\adminTopic.cpsp"
responseStream << ( ServerConfig::g_php_serverPath );
responseStream << "/js/time_calculations.js\"></script>\n";
responseStream << "<script type=\"text/javascript\">\n";

View File

@ -75,7 +75,7 @@ namespace controller {
return nullptr;
}
Poco::AutoPtr<HederaAccount> HederaAccount::pick(ServerConfig::HederaNetworkType networkType, bool encrypted/* = false*/)
Poco::AutoPtr<HederaAccount> HederaAccount::pick(ServerConfig::HederaNetworkType networkType, bool encrypted/* = false*/, int user_id/* = 0*/)
{
auto cm = ConnectionManager::getInstance();
auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
@ -91,10 +91,18 @@ namespace controller {
<< "JOIN hedera_ids as i ON(i.id = account_hedera_id) "
<< "JOIN crypto_keys as k ON(k.id = account.account_key_id) "
<< "WHERE account.network_type = ? "
<< "AND k.crypto_key_type_id = ? "
<< "ORDER BY RAND() LIMIT 1 "
<< "AND k.crypto_key_type_id = ? ";
if (user_id > 0) {
select << " AND account.user_id = ? ";
}
select << "ORDER BY RAND() LIMIT 1 "
, into(result_tuple), use(network_type_int) , use(crypto_key_type);
if (user_id > 0) {
select, use(user_id);
}
try {
select.executeAsync();
select.wait();
@ -261,6 +269,10 @@ namespace controller {
{
auto account_model = getModel();
auto hedera_node = NodeServer::pick(account_model->networkTypeToNodeServerType(account_model->getNetworkType()));
auto hedera_id = getHederaId();
if (hedera_id.isNull()) {
return nullptr;
}
return std::make_unique<model::hedera::TransactionBody>(mHederaID, hedera_node);
}

View File

@ -25,7 +25,7 @@ namespace controller {
static Poco::AutoPtr<HederaAccount> load(Poco::AutoPtr<controller::HederaId> hederaId);
static std::vector<Poco::AutoPtr<HederaAccount>> listAll();
//! \brief for picking a account for paying transaction, mostly consensusSendMessage
static Poco::AutoPtr<HederaAccount> pick(ServerConfig::HederaNetworkType networkType, bool encrypted = false);
static Poco::AutoPtr<HederaAccount> pick(ServerConfig::HederaNetworkType networkType, bool encrypted = false, int user_id = 0);
inline bool deleteFromDB() { return mDBModel->deleteFromDB(); }

View File

@ -61,6 +61,12 @@ HederaRequestReturn HederaRequest::request(model::hedera::Query* query, model::h
status = stub->getTopicInfo(&context, *proto_query, proto_response);
}
else if (proto_query->has_transactiongetreceipt()) {
auto stub = proto::CryptoService::NewStub(channel);
queryName = "crypto transaction get receipt";
status = stub->getTransactionReceipts(&context, *proto_query, proto_response);
}
if (status.ok())
{
auto response_code = response->getResponseCode();
@ -83,22 +89,6 @@ HederaRequestReturn HederaRequest::request(model::hedera::Query* query, model::h
return HEDERA_REQUEST_UNKNOWN_QUERY;
}
HederaRequestReturn HederaRequest::request(model::hedera::TransactionGetReceiptQuery* query, model::hedera::Response* response)
{
auto channel = grpc::CreateChannel(query->getConnectionString(), grpc::InsecureChannelCredentials());
grpc::ClientContext context;
std::chrono::system_clock::time_point deadline = std::chrono::system_clock::now() +
std::chrono::milliseconds(5000);
context.set_deadline(deadline);
auto proto_query = query->getProto();
auto proto_response = response->getResponsePtr();
grpc::Status status;
auto stub = proto::CryptoService::NewStub(channel);
status = stub->getTransactionReceipts(&context, *proto_query, proto_response);
}
HederaRequestReturn HederaRequest::request(model::hedera::Transaction* transaction, model::hedera::Response* response)
{
@ -126,13 +116,22 @@ HederaRequestReturn HederaRequest::request(model::hedera::Transaction* transacti
if (model::hedera::TRANSACTION_CONSENSUS_SUBMIT_MESSAGE == transaction_type ||
model::hedera::TRANSACTION_CONSENSUS_CREATE_TOPIC == transaction_type) {
auto stub = proto::ConsensusService::NewStub(channel);
auto status = stub->submitMessage(&context, *transaction->getTransaction(), task->getTransactionResponse()->getProtoResponse());
grpc::Status status;
std::string service_name;
if (model::hedera::TRANSACTION_CONSENSUS_SUBMIT_MESSAGE == transaction_type) {
status = stub->submitMessage(&context, *transaction->getTransaction(), task->getTransactionResponse()->getProtoResponse());
service_name = "submitMessage";
}
else if (model::hedera::TRANSACTION_CONSENSUS_CREATE_TOPIC == transaction_type) {
status = stub->createTopic(&context, *transaction->getTransaction(), task->getTransactionResponse()->getProtoResponse());
service_name = "createTopic";
}
if (status.ok()) {
return HEDERA_REQUEST_RETURN_OK;
}
else {
addError(new ParamError("Hedera Request", "consensus service submit message error message:", status.error_message()));
addError(new ParamError("Hedera Request", "consensus service error message:", status.error_message()));
addError(new ParamError("Hedera Request", "service name", service_name));
addError(new ParamError("Hedera Request", "details: ", status.error_details()));
return HEDERA_REQUEST_RETURN_ERROR;
}

View File

@ -37,7 +37,6 @@ public:
~HederaRequest();
HederaRequestReturn request(model::hedera::Query* query, model::hedera::Response* response, Poco::UInt64 fee = 0);
HederaRequestReturn request(model::hedera::TransactionGetReceiptQuery* query, model::hedera::Response* response);
HederaRequestReturn request(model::hedera::Transaction* transaction, model::hedera::Response* response);
//!
//! \param task goes into HederaTaskManager and will be run after transaction

View File

@ -6,6 +6,8 @@
#include "../model/hedera/ConsensusCreateTopic.h"
#include "../model/hedera/Transaction.h"
#include "../SingletonManager/PendingTasksManager.h"
namespace controller {
HederaTopic::HederaTopic(model::table::HederaTopic* dbModel)
{
@ -31,7 +33,6 @@ namespace controller {
// throw an unresolved external symbol error
topic_list = db->loadAllFromDB<model::table::HederaTopicTuple>();
std::vector<Poco::AutoPtr<HederaTopic>> resultVector;
resultVector.reserve(topic_list.size());
@ -39,6 +40,7 @@ namespace controller {
Poco::AutoPtr<HederaTopic> topic_ptr(new HederaTopic(new model::table::HederaTopic(*it)));
resultVector.push_back(topic_ptr);
}
return resultVector;
}
@ -72,7 +74,12 @@ namespace controller {
{
auto payer_account = getAutoRenewAccount();
auto node_server = NodeServer::pick(payer_account->getModel()->getNetworkType(), getModel()->getGroupId());
auto query = model::hedera::Query::getTopicInfo(getTopicHederaId(), payer_account->getHederaId(), node_server);
auto hedera_topic_id = getTopicHederaId();
if (hedera_topic_id.isNull()) {
addError(new Error("Hedera Topic", "no hedera topic id exist"));
return false;
}
auto query = model::hedera::Query::getTopicInfo(hedera_topic_id, payer_account->getHederaId(), node_server);
query->setResponseType(proto::COST_ANSWER);
model::hedera::Response response;
HederaRequest request;
@ -127,13 +134,21 @@ namespace controller {
Poco::AutoPtr<HederaTask> HederaTopic::createTopic(Poco::AutoPtr<controller::HederaAccount> operatorAccount, Poco::AutoPtr<controller::User> user)
{
static const char* function_name = "HederaTopic::createTopic";
printf("[HederaTopic::createTopic]\n");
auto model = getModel();
if (!model->getID()) {
addError(new Error(function_name, "no db entry for topic created, id is missing"));
return nullptr;
}
Poco::AutoPtr<controller::HederaId> autoRenewAccountId(nullptr);
if (model->getAutoRenewAccountId()) {
autoRenewAccountId = controller::HederaId::load(model->getAutoRenewAccountId());
//autoRenewAccountId = controller::HederaId::load(model->getAutoRenewAccountId());
}
model::hedera::ConsensusCreateTopic hederaCreateTopic(autoRenewAccountId, model->getAutoRenewPeriod());
auto hederaTransactionBody = operatorAccount->createTransactionBody();
if (model->getName() != "") {
hederaCreateTopic.setMemo(model->getName());
}
hederaTransactionBody->setCreateTopic(hederaCreateTopic);
model::hedera::Transaction hederaTransaction;
if (!hederaTransaction.sign(operatorAccount->getCryptoKey()->getKeyPair(user), std::move(hederaTransactionBody))) {
@ -142,11 +157,20 @@ namespace controller {
}
Poco::AutoPtr<HederaTask> receiptTask(new HederaTask(&hederaTransaction));
auto receipt_task_model = receiptTask->getModel();
receipt_task_model->setParentPendingTaskId(model->getID());
receipt_task_model->setUserId(user->getModel()->getID());
HederaRequest request;
printf("[HederaTopic::createTopic] before calling request\n");
auto result = request.request(&hederaTransaction, receiptTask.get());
if (HEDERA_REQUEST_RETURN_OK == result) {
if (proto::OK == receiptTask->getTransactionResponse()->getPrecheckCode()) {
auto pt = PendingTasksManager::getInstance();
printf("[HederaTopic::createTopic] before add task\n");
pt->addTask(receiptTask);
printf("[HederaTopic::createTopic] before start timer\n");
receiptTask->startTimer();
return receiptTask;
}
else {

View File

@ -3,7 +3,9 @@
#include "../tasks/GradidoGroupAddMemberTask.h"
#include "../model/gradido/Transaction.h"
#include "../tasks/HederaTask.h"
#include "../SingletonManager/PendingTasksManager.h"
#include "../SingletonManager/ErrorManager.h"
namespace controller {
@ -83,6 +85,7 @@ namespace controller {
bool PendingTask::deleteFromDB()
{
Poco::ScopedLock<Poco::Mutex> _lock(mWorkMutex);
auto result = mDBModel->deleteFromDB();
if (result) {
@ -105,4 +108,41 @@ namespace controller {
return mUser;
}
void PendingTask::startTimer()
{
Poco::ScopedLock<Poco::Mutex> _lock(mWorkMutex);
static const char* function_name = "PendingTask::startTimer";
auto em = ErrorManager::getInstance();
if (isTimeoutTask()) {
auto next_run_time = getNextRunTime();
if (next_run_time >= Poco::DateTime()) {
auto result = run();
if (result != 1 && result != -1) {
return;
}
next_run_time = getNextRunTime();
}
if (next_run_time >= Poco::DateTime()) {
em->addError(new Error(function_name, "get next runtime doesn't seem to work correctly"));
em->addError(new ParamError(function_name, "task type", getModel()->getTaskTypeString()));
em->sendErrorsAsEmail();
}
mTimer.setStartInterval(Poco::Timespan(getNextRunTime() - Poco::DateTime()).milliseconds());
Poco::TimerCallback<PendingTask> callback(*this, &PendingTask::calledFromTimer);
mTimer.start(callback);
}
}
void PendingTask::calledFromTimer(Poco::Timer& timer)
{
Poco::ScopedLock<Poco::Mutex> _lock(mWorkMutex);
auto result = run();
if (result != 1 && result != -1) {
return;
}
mTimer.setStartInterval(Poco::Timespan(getNextRunTime() - Poco::DateTime()).milliseconds());
Poco::TimerCallback<PendingTask> callback(*this, &PendingTask::calledFromTimer);
mTimer.start(callback);
}
}

View File

@ -4,6 +4,7 @@
#include "../model/table/PendingTask.h"
#include "Poco/SharedPtr.h"
#include "Poco/Timer.h"
#include "TableControllerBase.h"
#include "User.h"
@ -38,6 +39,9 @@ namespace controller {
//! \return -2 critical error, abort, remove
virtual int run() { return false; };
void startTimer();
void calledFromTimer(Poco::Timer& timer);
Poco::AutoPtr<controller::User> getUser();
protected:
@ -45,7 +49,7 @@ namespace controller {
PendingTask(model::table::PendingTask* dbModel);
Poco::AutoPtr<controller::User> mUser;
Poco::Timer mTimer;
};
}

View File

@ -40,7 +40,7 @@ namespace model {
bool ConsensusCreateTopic::validate()
{
if (mProtoCreateTopic->autorenewperiod().seconds() > 86400 && 0 != mProtoCreateTopic->autorenewaccount().accountnum()) {
if (mProtoCreateTopic->autorenewperiod().seconds() > 86400) {// && 0 != mProtoCreateTopic->autorenewaccount().accountnum()) {
return true;
}
return false;

View File

@ -1,4 +1,5 @@
#include "Query.h"
#include "QueryHeader.h"
#include "Poco/Timestamp.h"
#include "../../SingletonManager/MemoryManager.h"
@ -76,6 +77,23 @@ namespace model {
return query;
}
Query* Query::getTransactionGetReceiptQuery(
const proto::TransactionID& transactionId,
Poco::AutoPtr<controller::HederaAccount> payerAccount,
const controller::NodeServerConnection& connection
)
{
assert(!payerAccount.isNull());
auto query = new Query;
query->mQueryHeader = QueryHeader::createWithPaymentTransaction(payerAccount, connection, 0);
auto transaction_get_receipt_query = query->mQueryProto.mutable_transactiongetreceipt();
transaction_get_receipt_query->set_allocated_header(query->mQueryHeader->getProtoQueryHeader());
auto transaction_id = transaction_get_receipt_query->transactionid();
transaction_id = transactionId;
return query;
}
proto::QueryHeader* Query::getQueryHeader()
{
if (mQueryProto.has_cryptogetaccountbalance()) {
@ -84,6 +102,9 @@ namespace model {
else if (mQueryProto.has_consensusgettopicinfo()) {
return mQueryProto.mutable_consensusgettopicinfo()->mutable_header();
}
else {
return mQueryHeader->getProtoQueryHeader();
}
return nullptr;
}

View File

@ -14,6 +14,7 @@
#include "../../controller/NodeServer.h"
#include "../../Crypto/KeyPairHedera.h"
#include "TransactionBody.h"
#include "QueryHeader.h"
namespace model {
namespace hedera {
@ -23,6 +24,11 @@ namespace model {
~Query();
static Query* getBalance(Poco::AutoPtr<controller::HederaId> accountId, const controller::NodeServerConnection& connection);
static Query* getTopicInfo(Poco::AutoPtr<controller::HederaId> topicId, Poco::AutoPtr<controller::HederaId> payerAccountId, const controller::NodeServerConnection& connection);
static Query* getTransactionGetReceiptQuery(
const proto::TransactionID& transactionId,
Poco::AutoPtr<controller::HederaAccount> payerAccount,
const controller::NodeServerConnection& connection
);
bool sign(std::unique_ptr<KeyPairHedera> keyPairHedera);
void setResponseType(proto::ResponseType type);
@ -37,6 +43,7 @@ namespace model {
protected:
Query();
proto::Query mQueryProto;
Poco::AutoPtr<QueryHeader> mQueryHeader;
TransactionBody* mTransactionBody;
};
}

View File

@ -18,11 +18,11 @@ namespace model {
Poco::AutoPtr<QueryHeader> QueryHeader::createWithPaymentTransaction(
Poco::AutoPtr<controller::HederaAccount> operatorAccount,
const controller::NodeServerConnection& connection,
Poco::AutoPtr<controller::User> keyHolder,
Poco::UInt32 cost
) {
Poco::AutoPtr<QueryHeader> query_header(new QueryHeader);
auto proto_query_header = query_header->getProtoQueryHeader();
proto_query_header->set_responsetype(proto::ANSWER_ONLY);
auto payment_transaction = proto_query_header->payment();
query_header->mConnectionString = connection.getUriWithPort();
@ -33,7 +33,7 @@ namespace model {
transfer_transaction.addSender(operatorAccount->getHederaId(), cost);
transfer_transaction.addReceiver(connection.hederaId, cost);
body.setCryptoTransfer(transfer_transaction);
transactionObj.sign(operatorAccount->getCryptoKey()->getKeyPair(keyHolder), &body);
transactionObj.sign(operatorAccount->getCryptoKey()->getKeyPair(), &body);
transactionObj.resetPointer();
return query_header;

View File

@ -20,7 +20,6 @@ namespace model {
static Poco::AutoPtr<QueryHeader> createWithPaymentTransaction(
Poco::AutoPtr<controller::HederaAccount> operatorAccount,
const controller::NodeServerConnection& connection,
Poco::AutoPtr<controller::User> keyHolder,
Poco::UInt32 cost
);

View File

@ -28,6 +28,14 @@ namespace model {
return nullptr;
}
TransactionReceipt* Response::getTransactionReceipt()
{
if (mResponseProto.has_transactiongetreceipt()) {
return new TransactionReceipt(mResponseProto.transactiongetreceipt().receipt());
}
return nullptr;
}
Poco::UInt64 Response::getQueryCost()
{
proto::ResponseHeader* response_header = nullptr;

View File

@ -12,6 +12,7 @@
#include "../../proto/hedera/Response.pb.h"
#include "ConsensusTopicInfo.h"
#include "TransactionReceipt.h"
#include "Poco/Types.h"
namespace model {
@ -25,6 +26,7 @@ namespace model {
inline proto::Response* getResponsePtr() { return &mResponseProto; }
Poco::UInt64 getAccountBalance();
std::unique_ptr<ConsensusTopicInfo> getConsensusTopicInfo();
TransactionReceipt* getTransactionReceipt();
Poco::UInt64 getQueryCost();
proto::ResponseCodeEnum getResponseCode();

View File

@ -28,6 +28,7 @@ namespace model {
inline proto::Transaction* getTransaction() { return mTransaction; }
inline std::string getConnectionString() const { return mConnection.getUriWithPort(); }
const controller::NodeServerConnection& getConnection() const { return mConnection; }
void resetPointer() { mTransaction = nullptr; }
inline TransactionBodyType getType() const { return mType; }
inline proto::TransactionID getTransactionId() const { return mTransactionId; }

View File

@ -8,7 +8,7 @@ namespace model {
connection.hederaId->copyToProtoAccountId(mTransactionBody.mutable_nodeaccountid());
auto transaction_id = mTransactionBody.mutable_transactionid();
operatorAccountId->copyToProtoAccountId(transaction_id->mutable_accountid());
mTransactionBody.set_transactionfee(1000000);
mTransactionBody.set_transactionfee(10000000);
auto transaction_valid_duration = mTransactionBody.mutable_transactionvalidduration();
transaction_valid_duration->set_seconds(120);

View File

@ -3,7 +3,8 @@
namespace model {
namespace hedera {
TransactionReceipt::TransactionReceipt()
TransactionReceipt::TransactionReceipt(const proto::TransactionReceipt& protoReceipt)
: mProtoReceipt(protoReceipt)
{
}
@ -12,5 +13,13 @@ namespace model {
{
}
MemoryBin* TransactionReceipt::getRunningHash()
{
auto hash = mProtoReceipt.topicrunninghash();
auto hashBin = MemoryManager::getInstance()->getFreeMemory(hash.size());
memcpy(*hashBin, hash.data(), hash.size());
return hashBin;
}
}
}

View File

@ -2,15 +2,23 @@
#define __GRADIDO_LOGIN_SERVER_MODEL_HEDERA_TRANSACTION_RECEIPT_H
#include "../proto/hedera/TransactionReceipt.pb.h"
#include "../../SingletonManager/MemoryManager.h"
namespace model {
namespace hedera {
class TransactionReceipt
{
public:
TransactionReceipt();
TransactionReceipt(const proto::TransactionReceipt& protoReceipt);
~TransactionReceipt();
proto::TopicID getTopicId() { return mProtoReceipt.topicid(); }
google::protobuf::uint64 getSequenceNumber() { return mProtoReceipt.topicsequencenumber(); }
google::protobuf::uint64 getRunningHashVersion() { return mProtoReceipt.topicrunninghashversion(); }
//! caller must release memory after finish with it
MemoryBin* getRunningHash();
proto::ResponseCodeEnum getStatus() { return mProtoReceipt.status(); }
inline proto::TransactionReceipt* getProto() { return &mProtoReceipt; }
protected:

View File

@ -19,7 +19,7 @@ namespace model {
}
HederaTopic::HederaTopic(const std::string& name, int autoRenewAccountId, int autoRenewPeriod, int groupId)
: mTopicHederaId(0), mName(name), mAutoRenewAccountHederaId(autoRenewAccountId), mAutoRenewPeriod(autoRenewAccountId), mGroupId(groupId),
: mTopicHederaId(0), mName(name), mAutoRenewAccountHederaId(autoRenewAccountId), mAutoRenewPeriod(autoRenewPeriod), mGroupId(groupId),
mAdminKeyId(0), mSubmitKeyId(0), mSequenceNumber(0)
{
@ -98,9 +98,9 @@ namespace model {
lock();
insert << "INSERT INTO " << getTableName()
<< " (topic_hedera_id, name, auto_renew_account_hedera_id, auto_renew_period,"
<< " group_id, admin_key_id, submit_key_id, current_timeout, sequence_number) VALUES(?,?,?,?,?,?,?,?)"
<< " group_id, admin_key_id, submit_key_id, current_timeout, sequence_number) VALUES(?,?,?,?,?,?,?,?,?)"
, use(mTopicHederaId), use(mName), use(mAutoRenewAccountHederaId), use(mAutoRenewPeriod)
, use(mGroupId), use(mAdminKeyId), use(mSubmitKeyId), use(mCurrentTimeout), use(mSequenceNumber), use(mUpdated);
, use(mGroupId), use(mAdminKeyId), use(mSubmitKeyId), use(mCurrentTimeout), use(mSequenceNumber);
unlock();
return insert;
}

View File

@ -3,8 +3,15 @@
#include "../proto/hedera/TransactionGetReceipt.pb.h"
#include "../controller/NodeServer.h"
#include "../controller/HederaAccount.h"
#include "../controller/HederaRequest.h"
#include "../controller/HederaTopic.h"
#include "../SingletonManager/PendingTasksManager.h"
HederaTask::HederaTask(const model::gradido::Transaction* transaction)
: controller::PendingTask(new model::table::PendingTask), mTransactionReceipt(nullptr)
: controller::PendingTask(new model::table::PendingTask), mTransactionReceipt(nullptr), mTryCount(0)
{
auto hedera_task_model = getModel();
auto gradido_task_model = transaction->getModel();
@ -14,7 +21,7 @@ HederaTask::HederaTask(const model::gradido::Transaction* transaction)
}
HederaTask::HederaTask(const model::hedera::Transaction* transaction)
: controller::PendingTask(new model::table::PendingTask), mTransactionReceipt(nullptr)
: controller::PendingTask(new model::table::PendingTask), mTransactionReceipt(nullptr), mTryCount(0)
{
auto hedera_task_model = getModel();
//auto gradido_task_model = transaction->getModel();
@ -32,11 +39,11 @@ HederaTask::HederaTask(const model::hedera::Transaction* transaction)
task_type = model::table::TASK_TYPE_HEDERA_ACCOUNT_TRANSFER; break;
}
hedera_task_model->setTaskType(task_type);
mTransactionID = transaction->getTransactionId();
}
HederaTask::HederaTask(model::table::PendingTask* dbModel)
: controller::PendingTask(dbModel), mTransactionReceipt(nullptr)
: controller::PendingTask(dbModel), mTransactionReceipt(nullptr), mTryCount(0)
{
}
@ -58,13 +65,27 @@ Poco::AutoPtr<HederaTask> HederaTask::load(model::table::PendingTask* dbModel)
return new HederaTask(dbModel);
}
bool HederaTask::isTimeout()
Poco::DateTime HederaTask::getNextRunTime()
{
std::shared_lock<std::shared_mutex> _lock(mWorkingMutex);
auto valid_start = mTransactionID.transactionvalidstart();
auto poco_timestamp = DataTypeConverter::convertFromProtoTimestamp(valid_start);
auto poco_duration = DataTypeConverter::convertFromProtoDuration(mValidDuration);
return (poco_timestamp + poco_duration) > Poco::Timestamp();
return mLastCheck + 2000 * mTryCount * 2000;
}
int HederaTask::run()
{
auto result = tryQueryReceipt();
// keep also by -1 task in db for debugging
if (result != 1 && result != -1) {
deleteFromDB();
}
if (result == -1) {
return 0;
}
return result;
}
void HederaTask::setTransactionReceipt(model::hedera::TransactionReceipt* transactionReceipt)
@ -79,8 +100,105 @@ void HederaTask::setTransactionReceipt(model::hedera::TransactionReceipt* transa
mTransactionReceipt = transactionReceipt;
}
bool HederaTask::tryQueryReceipt()
//! \return 0 by success
//! \return 1 if hedera query failed
//! \return -1 if run after failed
//! \return -2 if not enough data for query
int HederaTask::tryQueryReceipt()
{
proto::TransactionGetReceiptQuery get_receipt_query;
printf("[HederaTask::tryQueryReceipt]\n");
static const char* function_name = "HederaTask::tryQueryReceipt";
std::unique_lock<std::shared_mutex> _lock(mWorkingMutex);
auto node_server_type = model::table::HederaAccount::networkTypeToNodeServerType(ServerConfig::g_HederaNetworkType);
auto connection = controller::NodeServer::pick(node_server_type);
if (!connection.isValid()) {
addError(new ParamError(function_name, "couldn't find node server for server type: ", model::table::NodeServer::nodeServerTypeToString(node_server_type)));
return -2;
}
auto operator_account = controller::HederaAccount::pick(ServerConfig::g_HederaNetworkType, false);
if (operator_account.isNull()) {
addError(new ParamError(function_name, "couldn't find unencrypted operator account for hedera network type: ", ServerConfig::g_HederaNetworkType));
return -2;
}
auto query = model::hedera::Query::getTransactionGetReceiptQuery(mTransactionID, operator_account, connection);
HederaRequest request;
model::hedera::Response response;
if (HEDERA_REQUEST_RETURN_OK == request.request(query, &response)) {
mTransactionReceipt = response.getTransactionReceipt();
if (mTransactionReceipt) {
if (runAfterGettingReceipt()) {
return 0;
}
else {
return -1;
}
}
}
else {
mLastCheck = Poco::Timestamp();
mTryCount++;
}
return 1;
}
bool HederaTask::runAfterGettingReceipt()
{
assert(getModel());
auto type = getModel()->getTaskType();
switch (type) {
case model::table::TASK_TYPE_HEDERA_TOPIC_CREATE:
return runForHederaTopic();
case model::table::TASK_TYPE_HEDERA_TOPIC_MESSAGE:
case model::table::TASK_TYPE_HEDERA_ACCOUNT_CREATE:
case model::table::TASK_TYPE_HEDERA_ACCOUNT_TRANSFER:
return false;
}
return true;
}
bool HederaTask::runForHederaTopic()
{
static const char* function_name = "HederaTask::runForHederaTopic";
// parent pending task is set to hedera_topic.id in db
auto model = getModel();
auto hedera_topic = controller::HederaTopic::load(model->getParentPendingTaskId());
if (!hedera_topic.isNull()) {
auto hedera_topic_model = hedera_topic->getModel();
auto topic_id = mTransactionReceipt->getTopicId();
auto hedera_id = controller::HederaId::create(topic_id.shardnum(), topic_id.realmnum(), topic_id.topicnum());
if (!hedera_id->getModel()->insertIntoDB(true)) {
addError(new Error(function_name, "error saving hedera_id"));
addError(new ParamError(function_name, "for hedera topic: ", hedera_topic_model->getID()));
addError(new ParamError(function_name, "shardnum: ", topic_id.shardnum()));
addError(new ParamError(function_name, "realmnum: ", topic_id.realmnum()));
addError(new ParamError(function_name, "topicnum", topic_id.topicnum()));
return false;
}
hedera_topic_model->setTopicHederaID(hedera_id->getModel()->getID());
hedera_topic_model->setSequeceNumber(mTransactionReceipt->getSequenceNumber());
std::string fieldNames[] = { "topic_hedera_id", "sequence_number" };
if (1 != hedera_topic_model->updateIntoDB(
fieldNames,
hedera_topic_model->getTopicHederaId(),
hedera_topic_model->getSequenceNumber()
)) {
addError(new Error(function_name, "error updating topic id"));
addError(new ParamError(function_name, "for hedera topic: ", hedera_topic_model->getID()));
addError(new ParamError(function_name, "shardnum: ", topic_id.shardnum()));
addError(new ParamError(function_name, "realmnum: ", topic_id.realmnum()));
addError(new ParamError(function_name, "topicnum", topic_id.topicnum()));
return false;
}
//! TODO think about saving also the last running hash
}
else {
addError(new ParamError(function_name, "hedera topic not found, id: ", model->getParentPendingTaskId()));
return false;
}
return true;
}

View File

@ -4,6 +4,7 @@
#include "../model/hedera/TransactionResponse.h"
#include "../model/hedera/TransactionReceipt.h"
#include "../model/hedera/Transaction.h"
#include "../model/hedera/Query.h"
#include "../proto/hedera/BasicTypes.pb.h"
#include "../proto/hedera/Duration.pb.h"
@ -31,6 +32,7 @@ public:
HederaTask(const model::gradido::Transaction* transaction);
HederaTask(const model::hedera::Transaction* transaction);
HederaTask(model::table::PendingTask* dbModel);
//HederaTask(model::hedera::Query)
~HederaTask();
static Poco::AutoPtr<HederaTask> load(model::table::PendingTask* dbModel);
@ -45,22 +47,33 @@ public:
inline const proto::Duration& getDuration() const { std::shared_lock<std::shared_mutex> _lock(mWorkingMutex); return mValidDuration; }
inline const model::hedera::TransactionReceipt* getTransactionReceipt() const{ std::shared_lock<std::shared_mutex> _lock(mWorkingMutex); return mTransactionReceipt; }
//! \ brief return true if transactionValidStart + validDuration > now
bool isTimeout();
bool isTimeoutTask() { return true; }
bool tryQueryReceipt();
bool isTimeoutTask() { return true; }
Poco::DateTime getNextRunTime();
int run();
//! \return 0 by success
//! \return 1 if hedera query failed
//! \return -1 if run after failed
//! \return -2 if not enough data for query
int tryQueryReceipt();
protected:
bool runAfterGettingReceipt();
bool runForHederaTopic();
model::hedera::TransactionResponse mTransactionResponse;
model::hedera::TransactionReceipt* mTransactionReceipt;
proto::TransactionID mTransactionID;
proto::Duration mValidDuration;
// last time checked if transaction receipt is available
Poco::Timestamp mLastCheck;
int mTryCount;
mutable std::shared_mutex mWorkingMutex;
};

View File

@ -104,6 +104,23 @@
addError(new Error("Int convert error", "Error converting group_id to int"));
}
}
//const std::string& name, int autoRenewAccountId, int autoRenewPeriod, int groupId
auto hedera_topic = controller::HederaTopic::create(name, auto_renew_account, auto_renew_period, group_id);
if(!hedera_topic->getModel()->insertIntoDB(true)) {
addError(new Error("Topic", "error saving into db"));
} else {
auto payer = controller::HederaAccount::load(auto_renew_account);
if(payer.isNull()) {
addError(new Error("Payer", "payer account not found"));
} else {
auto hedera_task = hedera_topic->createTopic(payer, user);
if(hedera_task.isNull()) {
addError(new Error("Create Topic", "Failed"));
getErrors(hedera_topic);
}
}
}
}
@ -165,9 +182,15 @@
} else if((hedera_topic_model->getCurrentTimeout() - Poco::DateTime()) < Poco::Timespan(2,0,0,0,0)) {
timeout_color = "orange-color";
}
std::string topic_hedera_id_string = "";
auto topic_hedera_id = (*it)->getTopicHederaId();
if(!topic_hedera_id.isNull()) {
topic_hedera_id_string = topic_hedera_id->getModel()->toString();
}
%>
<div class="row">
<div class="cell c2"><%= (*it)->getTopicHederaId()->getModel()->toString() %></div>
<div class="cell c2"><%= topic_hedera_id_string %></div>
<div class="cell c3"><%= hedera_topic_model->getName() %></div>
<div class="cell c3"><%= model::table::HederaAccount::hederaNetworkTypeToString(renew_account_model->getNetworkType()) %></div>
<div class="cell c4"><%= renew_account_model->getBalanceString() %></div>
@ -176,10 +199,11 @@
<div class="cell c3 <%= timeout_color %>"><%= hedera_topic_model->getCurrentTimeoutString() %></div>
<div class="cell c2"><%= hedera_topic_model->getSequenceNumber() %></div>
<div class="cell c3"><%= hedera_topic_model->getUpdatedString() %></div>
<div class="cell c5">
<div class="cell c5"><% if(!topic_hedera_id.isNull()) { %>
<button class="form-button" title="Query on Hedera, cost some fees" onclick="window.location.href='<%= updateUrl %>'" >
get topic infos
</button>
<% } %>
</div>
</div>
<% } %>