From 6054b035c8c1295c02a5433296e37d1ea74a71fa Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Thu, 17 May 2018 19:57:54 +0200 Subject: [PATCH 001/294] create tables --- plugins/sql_db_plugin/db/accounts_table.cpp | 21 ++++++++----- plugins/sql_db_plugin/db/accounts_table.h | 1 + plugins/sql_db_plugin/db/actions_table.cpp | 26 +++++++++++++++- plugins/sql_db_plugin/db/actions_table.h | 11 ++++++- plugins/sql_db_plugin/db/blocks_table.cpp | 27 ++++++++++++++++- plugins/sql_db_plugin/db/blocks_table.h | 3 ++ plugins/sql_db_plugin/db/database.cpp | 12 ++++++-- plugins/sql_db_plugin/db/database.h | 2 ++ .../sql_db_plugin/db/transactions_table.cpp | 30 ++++++++++++++++++- plugins/sql_db_plugin/db/transactions_table.h | 11 ++++++- .../irreversible_block_storage.cpp | 2 +- 11 files changed, 130 insertions(+), 16 deletions(-) diff --git a/plugins/sql_db_plugin/db/accounts_table.cpp b/plugins/sql_db_plugin/db/accounts_table.cpp index 4f9ceb5ef58..37327bd1381 100644 --- a/plugins/sql_db_plugin/db/accounts_table.cpp +++ b/plugins/sql_db_plugin/db/accounts_table.cpp @@ -22,14 +22,19 @@ void accounts_table::drop() void accounts_table::create() { - *m_session << "create table accounts(" - "name TEXT," - "eos_balance REAL," - "staked_balance REAL," - "unstaking_balance REAL," - "abi TEXT," - "created_at DATETIME," - "updated_at DATETIME)"; + *m_session << "CREATE TABLE accounts(" + "name TEXT PRIMARY KEY," + "eos_balance REAL," + "staked_balance REAL," + "unstaking_balance REAL," + "abi TEXT," + "created_at NUMERIC," + "updated_at NUMERIC)"; +} + +void accounts_table::insert(std::string name) +{ + *m_session << "INSERT INTO accounts VALUES (:name, 0, 0, 0, '', strftime('%s','now'), strftime('%s','now'))", soci::use(name); } } // namespace diff --git a/plugins/sql_db_plugin/db/accounts_table.h b/plugins/sql_db_plugin/db/accounts_table.h index a00b7ebfae4..08736e83fd8 100644 --- a/plugins/sql_db_plugin/db/accounts_table.h +++ b/plugins/sql_db_plugin/db/accounts_table.h @@ -13,6 +13,7 @@ class accounts_table void drop(); void create(); + void insert(std::string name); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index d69ed1e7f1e..2eef98a7a15 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -1,10 +1,34 @@ #include "actions_table.h" +#include + namespace eosio { -actions_table::actions_table() +actions_table::actions_table(std::shared_ptr session): + m_session(session) { } +void actions_table::drop() +{ + try { + *m_session << "drop table actions"; + } + catch(std::exception& e){ + wlog(e.what()); + } +} + +void actions_table::create() +{ + *m_session << "create table actions(" + "id TEXT," + "index NUMERIC," + "transaction_id TEXT," + "handler_account_name TEXT," + "name TEXT," + "data TEXT," + "created_at DATETIME)"; +} } // namespace diff --git a/plugins/sql_db_plugin/db/actions_table.h b/plugins/sql_db_plugin/db/actions_table.h index 440c0d27d5e..c0013776ddf 100644 --- a/plugins/sql_db_plugin/db/actions_table.h +++ b/plugins/sql_db_plugin/db/actions_table.h @@ -1,12 +1,21 @@ #ifndef ACTIONS_TABLE_H #define ACTIONS_TABLE_H +#include +#include + namespace eosio { class actions_table { public: - actions_table(); + actions_table(std::shared_ptr session); + + void drop(); + void create(); + +private: + std::shared_ptr m_session; }; } // namespace diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index 47441b9a4bd..dfa7464e5cf 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -1,11 +1,36 @@ #include "blocks_table.h" +#include + namespace eosio { blocks_table::blocks_table(std::shared_ptr session): - m_session(session) + m_session(session) { } +void blocks_table::drop() +{ + try { + *m_session << "drop table blocks"; + } + catch(std::exception& e){ + wlog(e.what()); + } +} + +void blocks_table::create() +{ + *m_session << "CREATE TABLE blocks(" + "id TEXT PRIMARY KEY," + "index NUMERIC," + "prev_block_id TEXT," + "timestamp NUMERIC," + "transaction_merkle_root TEXT," + "producer_account_id TEXT," + "pending NUMERIC," + "updated_at NUMERIC)"; +} + } // namespace diff --git a/plugins/sql_db_plugin/db/blocks_table.h b/plugins/sql_db_plugin/db/blocks_table.h index f569a1447c1..056801056d1 100644 --- a/plugins/sql_db_plugin/db/blocks_table.h +++ b/plugins/sql_db_plugin/db/blocks_table.h @@ -11,6 +11,9 @@ class blocks_table public: blocks_table(std::shared_ptr session); + void drop(); + void create(); + private: std::shared_ptr m_session; }; diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index b3cb3d1db77..6c09e472533 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -6,8 +6,8 @@ database::database(const std::string &uri) { m_session = std::make_shared(uri); m_accounts_table = std::make_unique(m_session); - m_transactions_table = std::make_unique(); - m_actions_table = std::make_unique(); + m_transactions_table = std::make_unique(m_session); + m_actions_table = std::make_unique(m_session); m_blocks_table = std::make_unique(m_session); } @@ -15,9 +15,17 @@ void database::wipe() { std::unique_lock lock(m_mux); + m_actions_table->drop(); + m_transactions_table->drop(); + m_blocks_table->drop(); m_accounts_table->drop(); m_accounts_table->create(); + m_blocks_table->create(); + m_transactions_table->create(); + m_actions_table->create(); + + m_accounts_table->insert(eosio::chain::name(chain::config::system_account_name).to_string()); } } // namespace diff --git a/plugins/sql_db_plugin/db/database.h b/plugins/sql_db_plugin/db/database.h index 50e1f50c99a..4fad3f3c4fc 100644 --- a/plugins/sql_db_plugin/db/database.h +++ b/plugins/sql_db_plugin/db/database.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "accounts_table.h" #include "transactions_table.h" diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index 3eb7491098c..526f34b1056 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -1,10 +1,38 @@ #include "transactions_table.h" +#include + namespace eosio { -transactions_table::transactions_table() +transactions_table::transactions_table(std::shared_ptr session): + m_session(session) { } +void transactions_table::drop() +{ + try { + *m_session << "drop table transactions"; + } + catch(std::exception& e){ + wlog(e.what()); + } +} + +void transactions_table::create() +{ + *m_session << "CREATE TABLE transactions(" + "id TEXT PRIMARY KEY," + "sequence_num NUMERIC," + "block_id TEXT," + "ref_block_prefix NUMERIC," + "status TEXT," + "expiration NUMERIC," + "pending NUMERIC," + "created_at NUMERIC," + "type TEXT," + "updated_at DATETIME)"; +} + } // namespace diff --git a/plugins/sql_db_plugin/db/transactions_table.h b/plugins/sql_db_plugin/db/transactions_table.h index 5dfedc8a9e0..9c345f84b5d 100644 --- a/plugins/sql_db_plugin/db/transactions_table.h +++ b/plugins/sql_db_plugin/db/transactions_table.h @@ -1,12 +1,21 @@ #ifndef TRANSACTIONS_TABLE_H #define TRANSACTIONS_TABLE_H +#include +#include + namespace eosio { class transactions_table { public: - transactions_table(); + transactions_table(std::shared_ptr session); + + void drop(); + void create(); + +private: + std::shared_ptr m_session; }; } // namespace diff --git a/plugins/sql_db_plugin/irreversible_block_storage.cpp b/plugins/sql_db_plugin/irreversible_block_storage.cpp index 7dcb4ac24a1..abea5ea7d8a 100644 --- a/plugins/sql_db_plugin/irreversible_block_storage.cpp +++ b/plugins/sql_db_plugin/irreversible_block_storage.cpp @@ -10,7 +10,7 @@ irreversible_block_storage::irreversible_block_storage(std::shared_ptr void irreversible_block_storage::consume(const std::vector& blocks) { - for (const auto& block : blocks) + for (const chain::block_state_ptr& block : blocks) { ilog(block->id.str()); From 843fca9a9da03a4ae3a2ee0e236d5aa7b6e30b31 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Thu, 17 May 2018 20:49:44 +0200 Subject: [PATCH 002/294] insert block --- plugins/sql_db_plugin/db/blocks_table.cpp | 13 +++++++++++++ plugins/sql_db_plugin/db/blocks_table.h | 2 ++ plugins/sql_db_plugin/db/database.cpp | 5 +++++ plugins/sql_db_plugin/db/database.h | 2 ++ .../sql_db_plugin/irreversible_block_storage.cpp | 4 +--- 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index dfa7464e5cf..abccd08af3d 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -33,4 +33,17 @@ void blocks_table::create() "updated_at NUMERIC)"; } +void blocks_table::add(eosio::chain::block_state_ptr block) +{ + *m_session << "INSERT INTO blocks(id, index, prev_block_id, timestamp, transaction_merkle_root, producer_account_id, pending, updated_at) VALUES (:id, :in, :pb, :ti, :tr, :pa, :pe, :ua)", + soci::use(block->header.id().str()), + soci::use(block->header.block_num()), + soci::use(block->header.previous.str()), + soci::use(block->header.timestamp.slot), + soci::use(block->header.transaction_mroot.str()), + soci::use(block->header.producer.to_string()), + soci::use(1), + soci::use(block->header.timestamp.slot); +} + } // namespace diff --git a/plugins/sql_db_plugin/db/blocks_table.h b/plugins/sql_db_plugin/db/blocks_table.h index 056801056d1..f179a584c43 100644 --- a/plugins/sql_db_plugin/db/blocks_table.h +++ b/plugins/sql_db_plugin/db/blocks_table.h @@ -3,6 +3,7 @@ #include #include +#include namespace eosio { @@ -13,6 +14,7 @@ class blocks_table void drop(); void create(); + void add(eosio::chain::block_state_ptr block); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index 6c09e472533..dde43353a3b 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -28,4 +28,9 @@ void database::wipe() m_accounts_table->insert(eosio::chain::name(chain::config::system_account_name).to_string()); } +void database::add(chain::block_state_ptr block) +{ + m_blocks_table->add(block); +} + } // namespace diff --git a/plugins/sql_db_plugin/db/database.h b/plugins/sql_db_plugin/db/database.h index 4fad3f3c4fc..278e1212afd 100644 --- a/plugins/sql_db_plugin/db/database.h +++ b/plugins/sql_db_plugin/db/database.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "accounts_table.h" #include "transactions_table.h" @@ -20,6 +21,7 @@ class database database(const std::string& uri); void wipe(); + void add(chain::block_state_ptr block); private: mutable std::mutex m_mux; diff --git a/plugins/sql_db_plugin/irreversible_block_storage.cpp b/plugins/sql_db_plugin/irreversible_block_storage.cpp index abea5ea7d8a..77ad259aae8 100644 --- a/plugins/sql_db_plugin/irreversible_block_storage.cpp +++ b/plugins/sql_db_plugin/irreversible_block_storage.cpp @@ -13,9 +13,7 @@ void irreversible_block_storage::consume(const std::vectorid.str()); - - // TODO parse the block and .. - // TODO m_db->act + m_db->add(block); } } From 783e57001649bdef3c3a4d73d0f41a910b392f34 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Fri, 18 May 2018 00:46:28 +0200 Subject: [PATCH 003/294] transactions --- plugins/sql_db_plugin/db/actions_table.cpp | 2 +- plugins/sql_db_plugin/db/blocks_table.cpp | 4 ++-- plugins/sql_db_plugin/db/database.cpp | 5 +++++ plugins/sql_db_plugin/db/database.h | 1 + plugins/sql_db_plugin/db/transactions_table.cpp | 5 +++++ plugins/sql_db_plugin/db/transactions_table.h | 2 ++ plugins/sql_db_plugin/irreversible_block_storage.cpp | 4 +++- 7 files changed, 19 insertions(+), 4 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 2eef98a7a15..dff2f4b48c4 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -24,7 +24,7 @@ void actions_table::create() { *m_session << "create table actions(" "id TEXT," - "index NUMERIC," + "seq NUMERIC," "transaction_id TEXT," "handler_account_name TEXT," "name TEXT," diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index abccd08af3d..61097f922b8 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -24,7 +24,7 @@ void blocks_table::create() { *m_session << "CREATE TABLE blocks(" "id TEXT PRIMARY KEY," - "index NUMERIC," + "block_number NUMERIC," "prev_block_id TEXT," "timestamp NUMERIC," "transaction_merkle_root TEXT," @@ -35,7 +35,7 @@ void blocks_table::create() void blocks_table::add(eosio::chain::block_state_ptr block) { - *m_session << "INSERT INTO blocks(id, index, prev_block_id, timestamp, transaction_merkle_root, producer_account_id, pending, updated_at) VALUES (:id, :in, :pb, :ti, :tr, :pa, :pe, :ua)", + *m_session << "INSERT INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root, producer_account_id, pending, updated_at) VALUES (:id, :in, :pb, :ti, :tr, :pa, :pe, :ua)", soci::use(block->header.id().str()), soci::use(block->header.block_num()), soci::use(block->header.previous.str()), diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index dde43353a3b..43e6559cc84 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -33,4 +33,9 @@ void database::add(chain::block_state_ptr block) m_blocks_table->add(block); } +void database::add(chain::transaction_metadata_ptr transaction) +{ + m_transactions_table->add(transaction); +} + } // namespace diff --git a/plugins/sql_db_plugin/db/database.h b/plugins/sql_db_plugin/db/database.h index 278e1212afd..b124150b9bd 100644 --- a/plugins/sql_db_plugin/db/database.h +++ b/plugins/sql_db_plugin/db/database.h @@ -22,6 +22,7 @@ class database void wipe(); void add(chain::block_state_ptr block); + void add(chain::transaction_metadata_ptr transaction); private: mutable std::mutex m_mux; diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index 526f34b1056..dd2cffbbb2d 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -35,4 +35,9 @@ void transactions_table::create() "updated_at DATETIME)"; } +void transactions_table::add(eosio::chain::transaction_metadata_ptr transaction) +{ + // TODO: add transaction +} + } // namespace diff --git a/plugins/sql_db_plugin/db/transactions_table.h b/plugins/sql_db_plugin/db/transactions_table.h index 9c345f84b5d..7c37f9ebba2 100644 --- a/plugins/sql_db_plugin/db/transactions_table.h +++ b/plugins/sql_db_plugin/db/transactions_table.h @@ -3,6 +3,7 @@ #include #include +#include namespace eosio { @@ -13,6 +14,7 @@ class transactions_table void drop(); void create(); + void add(eosio::chain::transaction_metadata_ptr transaction); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/irreversible_block_storage.cpp b/plugins/sql_db_plugin/irreversible_block_storage.cpp index 77ad259aae8..8bd91d8f58d 100644 --- a/plugins/sql_db_plugin/irreversible_block_storage.cpp +++ b/plugins/sql_db_plugin/irreversible_block_storage.cpp @@ -12,8 +12,10 @@ void irreversible_block_storage::consume(const std::vectorid.str()); m_db->add(block); + for (const chain::transaction_metadata_ptr& transaction : block->trxs) { + m_db->add(transaction); + } } } From e2deb8ba156e02506af45c7890f60d81125580c2 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Fri, 18 May 2018 13:20:26 +0200 Subject: [PATCH 004/294] storing actions --- plugins/sql_db_plugin/db/actions_table.cpp | 20 +++++++++++----- plugins/sql_db_plugin/db/actions_table.h | 2 ++ plugins/sql_db_plugin/db/blocks_table.cpp | 24 ++++++++++++------- plugins/sql_db_plugin/db/database.cpp | 5 ++++ plugins/sql_db_plugin/db/database.h | 1 + .../sql_db_plugin/db/transactions_table.cpp | 17 ++++++++++++- .../irreversible_block_storage.cpp | 3 +++ 7 files changed, 57 insertions(+), 15 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index dff2f4b48c4..24b53ef3df3 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -23,12 +23,20 @@ void actions_table::drop() void actions_table::create() { *m_session << "create table actions(" - "id TEXT," - "seq NUMERIC," - "transaction_id TEXT," - "handler_account_name TEXT," + "account TEXT," "name TEXT," - "data TEXT," - "created_at DATETIME)"; + "data TEXT)"; } + +void actions_table::add(eosio::chain::action action){ + + // TODO: we may do different stuff depending of the action and account (ex: sync balance, create account) + const auto data = std::string(action.data.begin(),action.data.end()); + + *m_session << "INSERT INTO actions(account, name, data) VALUES (:ac, :na, :da) ", + soci::use(action.account.to_string()), + soci::use(action.name.to_string()), + soci::use(data); +} + } // namespace diff --git a/plugins/sql_db_plugin/db/actions_table.h b/plugins/sql_db_plugin/db/actions_table.h index c0013776ddf..37f7c885ae3 100644 --- a/plugins/sql_db_plugin/db/actions_table.h +++ b/plugins/sql_db_plugin/db/actions_table.h @@ -3,6 +3,7 @@ #include #include +#include namespace eosio { @@ -13,6 +14,7 @@ class actions_table void drop(); void create(); + void add(eosio::chain::action action); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index 61097f922b8..d9a4af21dc2 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -35,15 +35,23 @@ void blocks_table::create() void blocks_table::add(eosio::chain::block_state_ptr block) { - *m_session << "INSERT INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root, producer_account_id, pending, updated_at) VALUES (:id, :in, :pb, :ti, :tr, :pa, :pe, :ua)", - soci::use(block->header.id().str()), - soci::use(block->header.block_num()), - soci::use(block->header.previous.str()), - soci::use(block->header.timestamp.slot), - soci::use(block->header.transaction_mroot.str()), + const auto block_id_str = block->block->id().str(); + const auto previous_block_id_str = block->block->previous.str(); + const auto transaction_mroot_str = block->block->transaction_mroot.str(); + const auto timestamp = std::chrono::milliseconds{ + std::chrono::seconds{block->block->timestamp.operator fc::time_point().sec_since_epoch()} + }.count(); + + *m_session << "INSERT INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root," + "producer_account_id, pending, updated_at) VALUES (:id, :in, :pb, :ti, :tr, :pa, :pe, :ua)", + soci::use(block_id_str), + soci::use(block->block->block_num()), + soci::use(previous_block_id_str), + soci::use(timestamp), + soci::use(transaction_mroot_str), soci::use(block->header.producer.to_string()), - soci::use(1), - soci::use(block->header.timestamp.slot); + soci::use(0), + soci::use(timestamp); } } // namespace diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index 43e6559cc84..c28cb5fbc40 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -38,4 +38,9 @@ void database::add(chain::transaction_metadata_ptr transaction) m_transactions_table->add(transaction); } +void database::add(chain::action action) +{ + m_actions_table->add(action); +} + } // namespace diff --git a/plugins/sql_db_plugin/db/database.h b/plugins/sql_db_plugin/db/database.h index b124150b9bd..80eeffcb116 100644 --- a/plugins/sql_db_plugin/db/database.h +++ b/plugins/sql_db_plugin/db/database.h @@ -23,6 +23,7 @@ class database void wipe(); void add(chain::block_state_ptr block); void add(chain::transaction_metadata_ptr transaction); + void add(chain::action action); private: mutable std::mutex m_mux; diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index dd2cffbbb2d..9286b72374f 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -37,7 +37,22 @@ void transactions_table::create() void transactions_table::add(eosio::chain::transaction_metadata_ptr transaction) { - // TODO: add transaction + const auto transaction_id_str = transaction->trx.id().str(); + const auto expiration = std::chrono::milliseconds{ + std::chrono::seconds{transaction->trx.expiration.sec_since_epoch()} + }.count(); + + *m_session << "INSERT INTO transactions(id, sequence_num, block_id, ref_block_prefix, status," + "expiration, pending, created_at, type, updated_at) VALUES (:id, :se, :bi, :rb, :st, :ex, :pe, :ca, :ty, :ua)", + soci::use(transaction_id_str), + soci::use(transaction->trx.ref_block_num), // TODO: proper fields + soci::use(transaction->trx.ref_block_prefix), + soci::use(transaction_id_str), + soci::use(expiration), + soci::use(1), + soci::use(expiration), + soci::use(transaction_id_str), + soci::use(expiration); } } // namespace diff --git a/plugins/sql_db_plugin/irreversible_block_storage.cpp b/plugins/sql_db_plugin/irreversible_block_storage.cpp index 8bd91d8f58d..9df97aa93cc 100644 --- a/plugins/sql_db_plugin/irreversible_block_storage.cpp +++ b/plugins/sql_db_plugin/irreversible_block_storage.cpp @@ -15,6 +15,9 @@ void irreversible_block_storage::consume(const std::vectoradd(block); for (const chain::transaction_metadata_ptr& transaction : block->trxs) { m_db->add(transaction); + for (const chain::action& action : transaction->trx.actions) { + m_db->add(action); + } } } } From b6a15dfe1022718275ccd7539d2be118d205b07b Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Fri, 18 May 2018 22:15:18 +0200 Subject: [PATCH 005/294] [db_sql_plugin] fix compilation in linux --- plugins/sql_db_plugin/db/blocks_table.cpp | 1 + plugins/sql_db_plugin/db/transactions_table.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index d9a4af21dc2..e2a659b456c 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -1,5 +1,6 @@ #include "blocks_table.h" +#include #include namespace eosio { diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index 9286b72374f..9133d75f857 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -1,5 +1,6 @@ #include "transactions_table.h" +#include #include namespace eosio { From 627fccd180008e807e1c7a842b8e1fb55ff8b861 Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Fri, 18 May 2018 22:15:47 +0200 Subject: [PATCH 006/294] [db_sql_plugin] database is a consumer_core --- plugins/sql_db_plugin/db/database.cpp | 5 +++++ plugins/sql_db_plugin/db/database.h | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index c28cb5fbc40..27e6039dec5 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -11,6 +11,11 @@ database::database(const std::string &uri) m_blocks_table = std::make_unique(m_session); } +void database::consume(const std::vector &blocks) +{ + +} + void database::wipe() { std::unique_lock lock(m_mux); diff --git a/plugins/sql_db_plugin/db/database.h b/plugins/sql_db_plugin/db/database.h index 80eeffcb116..6a7522ccbb4 100644 --- a/plugins/sql_db_plugin/db/database.h +++ b/plugins/sql_db_plugin/db/database.h @@ -1,6 +1,8 @@ #ifndef DATABASE_H #define DATABASE_H +#include "consumer_core.h" + #include #include #include @@ -15,11 +17,13 @@ namespace eosio { -class database +class database : public consumer_core { public: database(const std::string& uri); + void consume(const std::vector& blocks) override; + void wipe(); void add(chain::block_state_ptr block); void add(chain::transaction_metadata_ptr transaction); From 94bf720ddffad5ec1c492428a99d1b71f9423f5e Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Fri, 18 May 2018 22:26:19 +0200 Subject: [PATCH 007/294] [db_sql_plugin] refactoring --- plugins/sql_db_plugin/CMakeLists.txt | 2 -- plugins/sql_db_plugin/block_storage.cpp | 11 ------- plugins/sql_db_plugin/block_storage.h | 20 ------------- plugins/sql_db_plugin/db/database.cpp | 11 ++++++- .../eosio/sql_db_plugin/sql_db_plugin.hpp | 3 -- .../irreversible_block_storage.cpp | 25 ---------------- .../irreversible_block_storage.h | 29 ------------------- plugins/sql_db_plugin/sql_db_plugin.cpp | 20 ++----------- 8 files changed, 13 insertions(+), 108 deletions(-) delete mode 100644 plugins/sql_db_plugin/block_storage.cpp delete mode 100644 plugins/sql_db_plugin/block_storage.h delete mode 100644 plugins/sql_db_plugin/irreversible_block_storage.cpp delete mode 100644 plugins/sql_db_plugin/irreversible_block_storage.h diff --git a/plugins/sql_db_plugin/CMakeLists.txt b/plugins/sql_db_plugin/CMakeLists.txt index 2efdaebbda5..2418f7f3f92 100644 --- a/plugins/sql_db_plugin/CMakeLists.txt +++ b/plugins/sql_db_plugin/CMakeLists.txt @@ -15,8 +15,6 @@ add_library(sql_db_plugin db/blocks_table.cpp db/actions_table.cpp sql_db_plugin.cpp - irreversible_block_storage.cpp - block_storage.cpp ) target_link_libraries(sql_db_plugin diff --git a/plugins/sql_db_plugin/block_storage.cpp b/plugins/sql_db_plugin/block_storage.cpp deleted file mode 100644 index c63581473a4..00000000000 --- a/plugins/sql_db_plugin/block_storage.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "block_storage.h" - -namespace eosio { - -void block_storage::consume(const std::vector &blocks) -{ - for (const auto& block : blocks) - ilog(block->id.str()); -} - -} diff --git a/plugins/sql_db_plugin/block_storage.h b/plugins/sql_db_plugin/block_storage.h deleted file mode 100644 index ea8758cbd53..00000000000 --- a/plugins/sql_db_plugin/block_storage.h +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ - -#pragma once - -#include "consumer_core.h" - -#include - -namespace eosio { - -class block_storage : public consumer_core -{ -public: - void consume(const std::vector& blocks) override; -}; - -} // namespace diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index 27e6039dec5..05968ae3c03 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -13,7 +13,16 @@ database::database(const std::string &uri) void database::consume(const std::vector &blocks) { - + for (const chain::block_state_ptr& block : blocks) + { + add(block); + for (const chain::transaction_metadata_ptr& transaction : block->trxs) { + add(transaction); + for (const chain::action& action : transaction->trx.actions) { + add(action); + } + } + } } void database::wipe() diff --git a/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp b/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp index 38efcd4ab54..a843004f1ef 100644 --- a/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp +++ b/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp @@ -36,8 +36,6 @@ class sql_db_plugin final : public plugin { public: APPBASE_PLUGIN_REQUIRES((chain_plugin)) - sql_db_plugin(); - virtual void set_program_options(options_description& cli, options_description& cfg) override; void plugin_initialize(const variables_map& options); @@ -46,7 +44,6 @@ class sql_db_plugin final : public plugin { private: std::unique_ptr> m_irreversible_block_consumer; - consumer m_block_consumer; }; } diff --git a/plugins/sql_db_plugin/irreversible_block_storage.cpp b/plugins/sql_db_plugin/irreversible_block_storage.cpp deleted file mode 100644 index 9df97aa93cc..00000000000 --- a/plugins/sql_db_plugin/irreversible_block_storage.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "irreversible_block_storage.h" - -namespace eosio { - -irreversible_block_storage::irreversible_block_storage(std::shared_ptr db): - m_db(db) -{ - -} - -void irreversible_block_storage::consume(const std::vector& blocks) -{ - for (const chain::block_state_ptr& block : blocks) - { - m_db->add(block); - for (const chain::transaction_metadata_ptr& transaction : block->trxs) { - m_db->add(transaction); - for (const chain::action& action : transaction->trx.actions) { - m_db->add(action); - } - } - } -} - -} // namespace diff --git a/plugins/sql_db_plugin/irreversible_block_storage.h b/plugins/sql_db_plugin/irreversible_block_storage.h deleted file mode 100644 index 4b3aaa048cf..00000000000 --- a/plugins/sql_db_plugin/irreversible_block_storage.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ - -#pragma once - -#include "consumer_core.h" - -#include -#include - -#include "database.h" - -namespace eosio { - -class irreversible_block_storage : public consumer_core -{ -public: - irreversible_block_storage(std::shared_ptr db); - - void consume(const std::vector& blocks) override; - -private: - std::shared_ptr m_db; -}; - -} // namespace - diff --git a/plugins/sql_db_plugin/sql_db_plugin.cpp b/plugins/sql_db_plugin/sql_db_plugin.cpp index c48fc5b966d..2b4fd4b4350 100644 --- a/plugins/sql_db_plugin/sql_db_plugin.cpp +++ b/plugins/sql_db_plugin/sql_db_plugin.cpp @@ -7,10 +7,6 @@ #include "database.h" -#include "consumer_core.h" -#include "irreversible_block_storage.h" -#include "block_storage.h" - namespace { const char* BUFFER_SIZE_OPTION = "sql_db-queue-size"; const char* SQL_DB_URI_OPTION = "sql_db-uri"; @@ -24,12 +20,6 @@ namespace eosio { static appbase::abstract_plugin& _sql_db_plugin = app().register_plugin(); -sql_db_plugin::sql_db_plugin(): - m_block_consumer(std::make_unique()) -{ - -} - void sql_db_plugin::set_program_options(options_description& cli, options_description& cfg) { dlog("set_program_options"); @@ -54,9 +44,7 @@ void sql_db_plugin::plugin_initialize(const variables_map& options) return; } ilog("connecting to ${u}", ("u", uri_str)); - - auto db = std::make_shared(uri_str); - + auto db = std::make_unique(uri_str); if (options.at(RESYNC_OPTION).as() || options.at(REPLAY_OPTION).as()) @@ -65,13 +53,11 @@ void sql_db_plugin::plugin_initialize(const variables_map& options) db->wipe(); } + m_irreversible_block_consumer = std::make_unique>(std::move(db)); + chain_plugin* chain_plug = app().find_plugin(); FC_ASSERT(chain_plug); auto& chain = chain_plug->chain(); - - m_irreversible_block_consumer = std::make_unique>(std::make_unique(db)); - - // chain.accepted_block.connect([=](const chain::block_state_ptr& b) {m_block_consumer.push(b);}); chain.irreversible_block.connect([=](const chain::block_state_ptr& b) {m_irreversible_block_consumer->push(b);}); } From 15705e51b96f4fc8a82f290f5043fc1b07db8c01 Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Fri, 18 May 2018 22:27:01 +0200 Subject: [PATCH 008/294] [db_sql_plugin] add functions are private --- plugins/sql_db_plugin/db/database.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/sql_db_plugin/db/database.h b/plugins/sql_db_plugin/db/database.h index 6a7522ccbb4..4b66a1e876a 100644 --- a/plugins/sql_db_plugin/db/database.h +++ b/plugins/sql_db_plugin/db/database.h @@ -25,11 +25,12 @@ class database : public consumer_core void consume(const std::vector& blocks) override; void wipe(); + +private: void add(chain::block_state_ptr block); void add(chain::transaction_metadata_ptr transaction); void add(chain::action action); -private: mutable std::mutex m_mux; std::shared_ptr m_session; std::unique_ptr m_accounts_table; From a66cf46e1e188044011c95b31ebdd2a77c8bc36f Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Fri, 18 May 2018 22:29:41 +0200 Subject: [PATCH 009/294] [db_sql_plugin] removed add methods --- plugins/sql_db_plugin/db/database.cpp | 23 +++-------------------- plugins/sql_db_plugin/db/database.h | 5 ----- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index 05968ae3c03..ed97d385ad9 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -15,11 +15,11 @@ void database::consume(const std::vector &blocks) { for (const chain::block_state_ptr& block : blocks) { - add(block); + m_blocks_table->add(block); for (const chain::transaction_metadata_ptr& transaction : block->trxs) { - add(transaction); + m_transactions_table->add(transaction); for (const chain::action& action : transaction->trx.actions) { - add(action); + m_actions_table->add(action); } } } @@ -27,8 +27,6 @@ void database::consume(const std::vector &blocks) void database::wipe() { - std::unique_lock lock(m_mux); - m_actions_table->drop(); m_transactions_table->drop(); m_blocks_table->drop(); @@ -42,19 +40,4 @@ void database::wipe() m_accounts_table->insert(eosio::chain::name(chain::config::system_account_name).to_string()); } -void database::add(chain::block_state_ptr block) -{ - m_blocks_table->add(block); -} - -void database::add(chain::transaction_metadata_ptr transaction) -{ - m_transactions_table->add(transaction); -} - -void database::add(chain::action action) -{ - m_actions_table->add(action); -} - } // namespace diff --git a/plugins/sql_db_plugin/db/database.h b/plugins/sql_db_plugin/db/database.h index 4b66a1e876a..385ef13af5c 100644 --- a/plugins/sql_db_plugin/db/database.h +++ b/plugins/sql_db_plugin/db/database.h @@ -27,11 +27,6 @@ class database : public consumer_core void wipe(); private: - void add(chain::block_state_ptr block); - void add(chain::transaction_metadata_ptr transaction); - void add(chain::action action); - - mutable std::mutex m_mux; std::shared_ptr m_session; std::unique_ptr m_accounts_table; std::unique_ptr m_actions_table; From f7b111ba4a44dbe6b0ddc284fc285f8e933224cf Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Sat, 19 May 2018 21:36:24 +0200 Subject: [PATCH 010/294] adding actions + abi --- plugins/sql_db_plugin/CMakeLists.txt | 1 + plugins/sql_db_plugin/db/accounts_table.cpp | 13 ++++++-- plugins/sql_db_plugin/db/accounts_table.h | 5 +++- plugins/sql_db_plugin/db/actions_table.cpp | 30 ++++++++++++++++--- plugins/sql_db_plugin/db/actions_table.h | 10 ++++++- plugins/sql_db_plugin/db/blocks_table.cpp | 5 ++-- plugins/sql_db_plugin/db/blocks_table.h | 5 +++- plugins/sql_db_plugin/db/database.cpp | 15 +++++++--- plugins/sql_db_plugin/db/database.h | 6 +++- .../sql_db_plugin/db/transactions_table.cpp | 4 +-- plugins/sql_db_plugin/db/transactions_table.h | 2 +- plugins/sql_db_plugin/sql_db_plugin.cpp | 3 +- 12 files changed, 78 insertions(+), 21 deletions(-) diff --git a/plugins/sql_db_plugin/CMakeLists.txt b/plugins/sql_db_plugin/CMakeLists.txt index 2418f7f3f92..c43ca7bd24e 100644 --- a/plugins/sql_db_plugin/CMakeLists.txt +++ b/plugins/sql_db_plugin/CMakeLists.txt @@ -19,6 +19,7 @@ add_library(sql_db_plugin target_link_libraries(sql_db_plugin chain_plugin + eosio_chain ${SOCI_LIBRARY} ) diff --git a/plugins/sql_db_plugin/db/accounts_table.cpp b/plugins/sql_db_plugin/db/accounts_table.cpp index 37327bd1381..9c12cf8c773 100644 --- a/plugins/sql_db_plugin/db/accounts_table.cpp +++ b/plugins/sql_db_plugin/db/accounts_table.cpp @@ -32,9 +32,18 @@ void accounts_table::create() "updated_at NUMERIC)"; } -void accounts_table::insert(std::string name) +void accounts_table::add(string name) { - *m_session << "INSERT INTO accounts VALUES (:name, 0, 0, 0, '', strftime('%s','now'), strftime('%s','now'))", soci::use(name); + *m_session << "INSERT INTO accounts VALUES (:name, 0, 0, 0, '', strftime('%s','now'), strftime('%s','now'))", + soci::use(name); +} + +bool accounts_table::exist(string name) +{ + int amount; + *m_session << "SELECT COUNT(*) FROM accounts WHERE name = :name", soci::into(amount), soci::use(name); + + return amount > 0; } } // namespace diff --git a/plugins/sql_db_plugin/db/accounts_table.h b/plugins/sql_db_plugin/db/accounts_table.h index 08736e83fd8..7d3aab18c72 100644 --- a/plugins/sql_db_plugin/db/accounts_table.h +++ b/plugins/sql_db_plugin/db/accounts_table.h @@ -6,6 +6,8 @@ namespace eosio { +using std::string; + class accounts_table { public: @@ -13,7 +15,8 @@ class accounts_table void drop(); void create(); - void insert(std::string name); + void add(string name); + bool exist(string name); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 24b53ef3df3..ebf084b54ce 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -28,15 +28,37 @@ void actions_table::create() "data TEXT)"; } -void actions_table::add(eosio::chain::action action){ +void actions_table::add(chain::action action){ - // TODO: we may do different stuff depending of the action and account (ex: sync balance, create account) - const auto data = std::string(action.data.begin(),action.data.end()); + // TODO: move + if (action.name.to_string() == "setabi") { + auto setabi = action.data_as(); + auto abi = fc::json::to_string(setabi.abi); + auto name = setabi.account.to_string(); + *m_session << "UPDATE accounts SET abi = :abi WHERE name = :name", soci::use(abi), soci::use(name); + } + /* + chain::abi_def abi; + std::string abi_def_account; + chain::abi_serializer abis; + + *m_session << "SELECT abi FROM accounts WHERE name = :name", soci::into(abi_def_account), soci::use(action.account.to_string()); + abi = fc::json::from_string(abi_def_account).as(); + + if (action.account == chain::config::system_account_name) { + abi = chain::eosio_contract_abi(abi); + } + + abis.set_abi(abi); + + auto v = abis.binary_to_variant(abis.get_action_type(action.name), action.data); + auto json = fc::json::to_string(v); */ + std::string json = ""; *m_session << "INSERT INTO actions(account, name, data) VALUES (:ac, :na, :da) ", soci::use(action.account.to_string()), soci::use(action.name.to_string()), - soci::use(data); + soci::use(json); } } // namespace diff --git a/plugins/sql_db_plugin/db/actions_table.h b/plugins/sql_db_plugin/db/actions_table.h index 37f7c885ae3..1f35449ce52 100644 --- a/plugins/sql_db_plugin/db/actions_table.h +++ b/plugins/sql_db_plugin/db/actions_table.h @@ -2,8 +2,16 @@ #define ACTIONS_TABLE_H #include + #include + +#include +#include + #include +#include +#include +#include namespace eosio { @@ -14,7 +22,7 @@ class actions_table void drop(); void create(); - void add(eosio::chain::action action); + void add(chain::action action); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index e2a659b456c..1ebe677a46a 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -1,6 +1,5 @@ #include "blocks_table.h" -#include #include namespace eosio { @@ -34,7 +33,7 @@ void blocks_table::create() "updated_at NUMERIC)"; } -void blocks_table::add(eosio::chain::block_state_ptr block) +void blocks_table::add(chain::block_state_ptr block) { const auto block_id_str = block->block->id().str(); const auto previous_block_id_str = block->block->previous.str(); @@ -50,7 +49,7 @@ void blocks_table::add(eosio::chain::block_state_ptr block) soci::use(previous_block_id_str), soci::use(timestamp), soci::use(transaction_mroot_str), - soci::use(block->header.producer.to_string()), + soci::use(block->block->producer.to_string()), soci::use(0), soci::use(timestamp); } diff --git a/plugins/sql_db_plugin/db/blocks_table.h b/plugins/sql_db_plugin/db/blocks_table.h index f179a584c43..f93daaa4039 100644 --- a/plugins/sql_db_plugin/db/blocks_table.h +++ b/plugins/sql_db_plugin/db/blocks_table.h @@ -2,7 +2,10 @@ #define BLOCKS_TABLE_H #include +#include + #include + #include namespace eosio { @@ -14,7 +17,7 @@ class blocks_table void drop(); void create(); - void add(eosio::chain::block_state_ptr block); + void add(chain::block_state_ptr block); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index ed97d385ad9..9623151a083 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -9,16 +9,18 @@ database::database(const std::string &uri) m_transactions_table = std::make_unique(m_session); m_actions_table = std::make_unique(m_session); m_blocks_table = std::make_unique(m_session); + + system_account = chain::name(chain::config::system_account_name).to_string(); } void database::consume(const std::vector &blocks) { - for (const chain::block_state_ptr& block : blocks) + for (const auto& block : blocks) { m_blocks_table->add(block); - for (const chain::transaction_metadata_ptr& transaction : block->trxs) { + for (const auto& transaction : block->trxs) { m_transactions_table->add(transaction); - for (const chain::action& action : transaction->trx.actions) { + for (const auto& action : transaction->trx.actions) { m_actions_table->add(action); } } @@ -37,7 +39,12 @@ void database::wipe() m_transactions_table->create(); m_actions_table->create(); - m_accounts_table->insert(eosio::chain::name(chain::config::system_account_name).to_string()); + m_accounts_table->add(system_account); +} + +bool database::is_started() +{ + return m_accounts_table->exist(system_account); } } // namespace diff --git a/plugins/sql_db_plugin/db/database.h b/plugins/sql_db_plugin/db/database.h index 385ef13af5c..be6d38a5480 100644 --- a/plugins/sql_db_plugin/db/database.h +++ b/plugins/sql_db_plugin/db/database.h @@ -5,10 +5,12 @@ #include #include + #include + #include -#include #include +#include #include "accounts_table.h" #include "transactions_table.h" @@ -25,6 +27,7 @@ class database : public consumer_core void consume(const std::vector& blocks) override; void wipe(); + bool is_started(); private: std::shared_ptr m_session; @@ -32,6 +35,7 @@ class database : public consumer_core std::unique_ptr m_actions_table; std::unique_ptr m_blocks_table; std::unique_ptr m_transactions_table; + std::string system_account; }; } // namespace diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index 9133d75f857..7466923769e 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -6,7 +6,7 @@ namespace eosio { transactions_table::transactions_table(std::shared_ptr session): - m_session(session) + m_session(session) { } @@ -36,7 +36,7 @@ void transactions_table::create() "updated_at DATETIME)"; } -void transactions_table::add(eosio::chain::transaction_metadata_ptr transaction) +void transactions_table::add(chain::transaction_metadata_ptr transaction) { const auto transaction_id_str = transaction->trx.id().str(); const auto expiration = std::chrono::milliseconds{ diff --git a/plugins/sql_db_plugin/db/transactions_table.h b/plugins/sql_db_plugin/db/transactions_table.h index 7c37f9ebba2..3ea005ed8c2 100644 --- a/plugins/sql_db_plugin/db/transactions_table.h +++ b/plugins/sql_db_plugin/db/transactions_table.h @@ -14,7 +14,7 @@ class transactions_table void drop(); void create(); - void add(eosio::chain::transaction_metadata_ptr transaction); + void add(chain::transaction_metadata_ptr transaction); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/sql_db_plugin.cpp b/plugins/sql_db_plugin/sql_db_plugin.cpp index 2b4fd4b4350..5b23fd099be 100644 --- a/plugins/sql_db_plugin/sql_db_plugin.cpp +++ b/plugins/sql_db_plugin/sql_db_plugin.cpp @@ -47,7 +47,8 @@ void sql_db_plugin::plugin_initialize(const variables_map& options) auto db = std::make_unique(uri_str); if (options.at(RESYNC_OPTION).as() || - options.at(REPLAY_OPTION).as()) + options.at(REPLAY_OPTION).as() || + !db->is_started()) { ilog("Resync requested: wiping database"); db->wipe(); From 4d8f629876f2dff4ade7f187de35c98d325d5b9e Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Sun, 20 May 2018 09:55:40 +0200 Subject: [PATCH 011/294] wip abi storage --- plugins/sql_db_plugin/db/actions_table.cpp | 38 +++++++++++++++------- plugins/sql_db_plugin/db/actions_table.h | 2 ++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index ebf084b54ce..8e7c4663840 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -30,20 +30,14 @@ void actions_table::create() void actions_table::add(chain::action action){ - // TODO: move - if (action.name.to_string() == "setabi") { - auto setabi = action.data_as(); - auto abi = fc::json::to_string(setabi.abi); - auto name = setabi.account.to_string(); - *m_session << "UPDATE accounts SET abi = :abi WHERE name = :name", soci::use(abi), soci::use(name); - } - /* chain::abi_def abi; - std::string abi_def_account; + std::string abi_def_account = ""; chain::abi_serializer abis; *m_session << "SELECT abi FROM accounts WHERE name = :name", soci::into(abi_def_account), soci::use(action.account.to_string()); - abi = fc::json::from_string(abi_def_account).as(); + if (abi_def_account != "") { + abi = fc::json::from_string(abi_def_account).as(); + } if (action.account == chain::config::system_account_name) { abi = chain::eosio_contract_abi(abi); @@ -52,13 +46,33 @@ void actions_table::add(chain::action action){ abis.set_abi(abi); auto v = abis.binary_to_variant(abis.get_action_type(action.name), action.data); - auto json = fc::json::to_string(v); */ - std::string json = ""; + string json = fc::json::to_string(v); *m_session << "INSERT INTO actions(account, name, data) VALUES (:ac, :na, :da) ", soci::use(action.account.to_string()), soci::use(action.name.to_string()), soci::use(json); + + // TODO: move & catch eosio.token -> transfer + if (action.account != chain::name(chain::config::system_account_name)) { + return; + } + + if (action.name == chain::setabi::get_name()) { + auto action_data = action.data_as(); + chain::abi_serializer::to_abi(action_data.abi, abi); + string abi_string = fc::json::to_string(abi); + + *m_session << "UPDATE accounts SET abi = :abi WHERE name = :name", + soci::use(abi_string), + soci::use(action_data.account.to_string()); + + } else if (action.name == chain::newaccount::get_name()) { + auto action_data = action.data_as(); + *m_session << "INSERT INTO accounts VALUES (:name, 0, 0, 0, '', strftime('%s','now'), strftime('%s','now'))", + soci::use(action_data.name.to_string()); + + } } } // namespace diff --git a/plugins/sql_db_plugin/db/actions_table.h b/plugins/sql_db_plugin/db/actions_table.h index 1f35449ce52..da72806c53a 100644 --- a/plugins/sql_db_plugin/db/actions_table.h +++ b/plugins/sql_db_plugin/db/actions_table.h @@ -15,6 +15,8 @@ namespace eosio { +using std::string; + class actions_table { public: From e2cbecaca923a7503a0ea4ae513736c8e2d9ff05 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Sun, 20 May 2018 10:07:29 +0200 Subject: [PATCH 012/294] not need to default eosio if it has abi --- plugins/sql_db_plugin/db/actions_table.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 8e7c4663840..c5ecde767fc 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -31,15 +31,13 @@ void actions_table::create() void actions_table::add(chain::action action){ chain::abi_def abi; - std::string abi_def_account = ""; + std::string abi_def_account; chain::abi_serializer abis; *m_session << "SELECT abi FROM accounts WHERE name = :name", soci::into(abi_def_account), soci::use(action.account.to_string()); - if (abi_def_account != "") { + if (!abi_def_account.empty()) { abi = fc::json::from_string(abi_def_account).as(); - } - - if (action.account == chain::config::system_account_name) { + } else if (action.account == chain::config::system_account_name) { abi = chain::eosio_contract_abi(abi); } From 6fde77ce0209c82ea37da2a475dacdf6ef039714 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Sun, 20 May 2018 12:55:22 +0200 Subject: [PATCH 013/294] refactoring --- plugins/sql_db_plugin/db/actions_table.cpp | 3 ++- plugins/sql_db_plugin/db/blocks_table.cpp | 14 +++++++------- plugins/sql_db_plugin/db/blocks_table.h | 2 +- plugins/sql_db_plugin/db/database.cpp | 4 ++-- plugins/sql_db_plugin/db/transactions_table.cpp | 10 +++++----- plugins/sql_db_plugin/db/transactions_table.h | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index c5ecde767fc..8985e4c6a43 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -28,7 +28,8 @@ void actions_table::create() "data TEXT)"; } -void actions_table::add(chain::action action){ +void actions_table::add(chain::action action) +{ chain::abi_def abi; std::string abi_def_account; diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index 1ebe677a46a..a80c6963f5e 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -33,23 +33,23 @@ void blocks_table::create() "updated_at NUMERIC)"; } -void blocks_table::add(chain::block_state_ptr block) +void blocks_table::add(chain::signed_block_ptr block) { - const auto block_id_str = block->block->id().str(); - const auto previous_block_id_str = block->block->previous.str(); - const auto transaction_mroot_str = block->block->transaction_mroot.str(); + const auto block_id_str = block->id().str(); + const auto previous_block_id_str = block->previous.str(); + const auto transaction_mroot_str = block->transaction_mroot.str(); const auto timestamp = std::chrono::milliseconds{ - std::chrono::seconds{block->block->timestamp.operator fc::time_point().sec_since_epoch()} + std::chrono::seconds{block->timestamp.operator fc::time_point().sec_since_epoch()} }.count(); *m_session << "INSERT INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root," "producer_account_id, pending, updated_at) VALUES (:id, :in, :pb, :ti, :tr, :pa, :pe, :ua)", soci::use(block_id_str), - soci::use(block->block->block_num()), + soci::use(block->block_num()), soci::use(previous_block_id_str), soci::use(timestamp), soci::use(transaction_mroot_str), - soci::use(block->block->producer.to_string()), + soci::use(block->producer.to_string()), soci::use(0), soci::use(timestamp); } diff --git a/plugins/sql_db_plugin/db/blocks_table.h b/plugins/sql_db_plugin/db/blocks_table.h index f93daaa4039..25e5a065ac1 100644 --- a/plugins/sql_db_plugin/db/blocks_table.h +++ b/plugins/sql_db_plugin/db/blocks_table.h @@ -17,7 +17,7 @@ class blocks_table void drop(); void create(); - void add(chain::block_state_ptr block); + void add(chain::signed_block_ptr block); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index 9623151a083..5f617a66111 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -17,9 +17,9 @@ void database::consume(const std::vector &blocks) { for (const auto& block : blocks) { - m_blocks_table->add(block); + m_blocks_table->add(block->block); for (const auto& transaction : block->trxs) { - m_transactions_table->add(transaction); + m_transactions_table->add(transaction->trx); for (const auto& action : transaction->trx.actions) { m_actions_table->add(action); } diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index 7466923769e..b4f44f6b020 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -36,18 +36,18 @@ void transactions_table::create() "updated_at DATETIME)"; } -void transactions_table::add(chain::transaction_metadata_ptr transaction) +void transactions_table::add(chain::transaction transaction) { - const auto transaction_id_str = transaction->trx.id().str(); + const auto transaction_id_str = transaction.id().str(); const auto expiration = std::chrono::milliseconds{ - std::chrono::seconds{transaction->trx.expiration.sec_since_epoch()} + std::chrono::seconds{transaction.expiration.sec_since_epoch()} }.count(); *m_session << "INSERT INTO transactions(id, sequence_num, block_id, ref_block_prefix, status," "expiration, pending, created_at, type, updated_at) VALUES (:id, :se, :bi, :rb, :st, :ex, :pe, :ca, :ty, :ua)", soci::use(transaction_id_str), - soci::use(transaction->trx.ref_block_num), // TODO: proper fields - soci::use(transaction->trx.ref_block_prefix), + soci::use(transaction.ref_block_num), // TODO: proper fields + soci::use(transaction.ref_block_prefix), soci::use(transaction_id_str), soci::use(expiration), soci::use(1), diff --git a/plugins/sql_db_plugin/db/transactions_table.h b/plugins/sql_db_plugin/db/transactions_table.h index 3ea005ed8c2..09bf1a65b32 100644 --- a/plugins/sql_db_plugin/db/transactions_table.h +++ b/plugins/sql_db_plugin/db/transactions_table.h @@ -14,7 +14,7 @@ class transactions_table void drop(); void create(); - void add(chain::transaction_metadata_ptr transaction); + void add(chain::transaction transaction); private: std::shared_ptr m_session; From d5db25bc3bbbce490f00745b4ec99a725899d507 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Sun, 20 May 2018 21:00:27 +0200 Subject: [PATCH 014/294] tokens storage --- plugins/sql_db_plugin/db/accounts_table.cpp | 7 +- plugins/sql_db_plugin/db/actions_table.cpp | 79 +++++++++++++++++-- plugins/sql_db_plugin/db/actions_table.h | 1 + plugins/sql_db_plugin/db/blocks_table.cpp | 2 +- .../sql_db_plugin/db/transactions_table.cpp | 2 +- 5 files changed, 76 insertions(+), 15 deletions(-) diff --git a/plugins/sql_db_plugin/db/accounts_table.cpp b/plugins/sql_db_plugin/db/accounts_table.cpp index 9c12cf8c773..aca6ad2d928 100644 --- a/plugins/sql_db_plugin/db/accounts_table.cpp +++ b/plugins/sql_db_plugin/db/accounts_table.cpp @@ -13,7 +13,7 @@ accounts_table::accounts_table(std::shared_ptr session): void accounts_table::drop() { try { - *m_session << "drop table accounts"; + *m_session << "DROP TABLE accounts"; } catch(std::exception& e){ wlog(e.what()); @@ -24,9 +24,6 @@ void accounts_table::create() { *m_session << "CREATE TABLE accounts(" "name TEXT PRIMARY KEY," - "eos_balance REAL," - "staked_balance REAL," - "unstaking_balance REAL," "abi TEXT," "created_at NUMERIC," "updated_at NUMERIC)"; @@ -34,7 +31,7 @@ void accounts_table::create() void accounts_table::add(string name) { - *m_session << "INSERT INTO accounts VALUES (:name, 0, 0, 0, '', strftime('%s','now'), strftime('%s','now'))", + *m_session << "INSERT INTO accounts VALUES (:name,'', strftime('%s','now'), strftime('%s','now'))", soci::use(name); } diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 8985e4c6a43..ca5fdfd7d47 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -14,6 +14,7 @@ void actions_table::drop() { try { *m_session << "drop table actions"; + *m_session << "drop table tokens"; } catch(std::exception& e){ wlog(e.what()); @@ -26,6 +27,13 @@ void actions_table::create() "account TEXT," "name TEXT," "data TEXT)"; + + // TODO: move to own class + *m_session << "create table tokens(" + "account TEXT," + "symbol TEXT," + "amount REAL," + "staked REAL)"; } void actions_table::add(chain::action action) @@ -40,35 +48,90 @@ void actions_table::add(chain::action action) abi = fc::json::from_string(abi_def_account).as(); } else if (action.account == chain::config::system_account_name) { abi = chain::eosio_contract_abi(abi); + } else { + return; // no ABI no party. Should we still store it? } abis.set_abi(abi); - auto v = abis.binary_to_variant(abis.get_action_type(action.name), action.data); - string json = fc::json::to_string(v); + auto abi_data = abis.binary_to_variant(abis.get_action_type(action.name), action.data); + string json = fc::json::to_string(abi_data); *m_session << "INSERT INTO actions(account, name, data) VALUES (:ac, :na, :da) ", soci::use(action.account.to_string()), soci::use(action.name.to_string()), soci::use(json); - // TODO: move & catch eosio.token -> transfer + // TODO: move all + if (action.name == N(issue)) { + + auto to_name = abi_data["to"].as().to_string(); + auto asset_quantity = abi_data["quantity"].as(); + int exist; + + *m_session << "SELECT COUNT(*) FROM tokens WHERE account = :ac AND symbol = :sy", + soci::into(exist), + soci::use(to_name), + soci::use(asset_quantity.sym.name()); + if (exist > 0) { + *m_session << "UPDATE tokens SET amount = amount + :am WHERE account = :ac AND symbol :sy", + soci::use(asset_quantity.to_real()), + soci::use(to_name), + soci::use(asset_quantity.sym.name()); + } else { + *m_session << "INSERT INTO tokens(account, amount, staked, symbol) VALUES (:ac, :am, 0, :as) ", + soci::use(to_name), + soci::use(asset_quantity.to_real()), + soci::use(asset_quantity.sym.name()); + } + } + + if (action.name == N(transfer)) { + + auto from_name = abi_data["from"].as().to_string(); + auto to_name = abi_data["to"].as().to_string(); + auto asset_quantity = abi_data["quantity"].as(); + int exist; + + *m_session << "SELECT COUNT(*) FROM tokens WHERE account = :ac AND symbol = :sy", + soci::into(exist), + soci::use(to_name), + soci::use(asset_quantity.sym.name()); + if (exist > 0) { + *m_session << "UPDATE tokens SET amount = amount + :am WHERE account = :ac AND symbol :sy", + soci::use(asset_quantity.to_real()), + soci::use(to_name), + soci::use(asset_quantity.sym.name()); + } else { + *m_session << "INSERT INTO tokens(account, amount, staked, symbol) VALUES (:ac, :am, 0, :as) ", + soci::use(to_name), + soci::use(asset_quantity.to_real()), + soci::use(asset_quantity.sym.name()); + } + + *m_session << "UPDATE tokens SET amount = amount - :am WHERE account = :ac AND symbol :sy", + soci::use(asset_quantity.to_real()), + soci::use(from_name), + soci::use(asset_quantity.sym.name()); + } + if (action.account != chain::name(chain::config::system_account_name)) { return; } if (action.name == chain::setabi::get_name()) { - auto action_data = action.data_as(); - chain::abi_serializer::to_abi(action_data.abi, abi); - string abi_string = fc::json::to_string(abi); + chain::abi_def abi_setabi; + chain::setabi action_data = action.data_as(); + chain::abi_serializer::to_abi(action_data.abi, abi_setabi); + string abi_string = fc::json::to_string(abi_setabi); - *m_session << "UPDATE accounts SET abi = :abi WHERE name = :name", + *m_session << "UPDATE accounts SET abi = :abi, updated_at = strftime('%s','now') WHERE name = :name", soci::use(abi_string), soci::use(action_data.account.to_string()); } else if (action.name == chain::newaccount::get_name()) { auto action_data = action.data_as(); - *m_session << "INSERT INTO accounts VALUES (:name, 0, 0, 0, '', strftime('%s','now'), strftime('%s','now'))", + *m_session << "INSERT INTO accounts VALUES (:name,'', strftime('%s','now'), strftime('%s','now'))", soci::use(action_data.name.to_string()); } diff --git a/plugins/sql_db_plugin/db/actions_table.h b/plugins/sql_db_plugin/db/actions_table.h index da72806c53a..4ac2bc1dea8 100644 --- a/plugins/sql_db_plugin/db/actions_table.h +++ b/plugins/sql_db_plugin/db/actions_table.h @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace eosio { diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index a80c6963f5e..a00f947727a 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -13,7 +13,7 @@ blocks_table::blocks_table(std::shared_ptr session): void blocks_table::drop() { try { - *m_session << "drop table blocks"; + *m_session << "DROP TABLE blocks"; } catch(std::exception& e){ wlog(e.what()); diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index b4f44f6b020..38268a76ce6 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -14,7 +14,7 @@ transactions_table::transactions_table(std::shared_ptr session): void transactions_table::drop() { try { - *m_session << "drop table transactions"; + *m_session << "DROP TABLE transactions"; } catch(std::exception& e){ wlog(e.what()); From 6497411f05c13935905b1392c6eb830f140c849f Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Sun, 20 May 2018 21:24:42 +0200 Subject: [PATCH 015/294] improved sql --- plugins/sql_db_plugin/db/actions_table.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index ca5fdfd7d47..0d44a78af4b 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -98,7 +98,7 @@ void actions_table::add(chain::action action) soci::use(to_name), soci::use(asset_quantity.sym.name()); if (exist > 0) { - *m_session << "UPDATE tokens SET amount = amount + :am WHERE account = :ac AND symbol :sy", + *m_session << "UPDATE tokens SET amount = amount + :am WHERE account = :ac AND symbol = :sy", soci::use(asset_quantity.to_real()), soci::use(to_name), soci::use(asset_quantity.sym.name()); @@ -109,7 +109,7 @@ void actions_table::add(chain::action action) soci::use(asset_quantity.sym.name()); } - *m_session << "UPDATE tokens SET amount = amount - :am WHERE account = :ac AND symbol :sy", + *m_session << "UPDATE tokens SET amount = amount - :am WHERE account = :ac AND symbol = :sy", soci::use(asset_quantity.to_real()), soci::use(from_name), soci::use(asset_quantity.sym.name()); From 2f6c6a512f2058f7f3037021f3f9269ea5e13b33 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Sun, 20 May 2018 22:00:13 +0200 Subject: [PATCH 016/294] improving tables --- plugins/sql_db_plugin/db/actions_table.cpp | 9 ++++++--- plugins/sql_db_plugin/db/actions_table.h | 2 +- plugins/sql_db_plugin/db/blocks_table.cpp | 6 +++--- plugins/sql_db_plugin/db/database.cpp | 2 +- plugins/sql_db_plugin/db/transactions_table.cpp | 15 +++++---------- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 0d44a78af4b..8119f1b710e 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -25,6 +25,7 @@ void actions_table::create() { *m_session << "create table actions(" "account TEXT," + "transaction_id TEXT," "name TEXT," "data TEXT)"; @@ -36,12 +37,13 @@ void actions_table::create() "staked REAL)"; } -void actions_table::add(chain::action action) +void actions_table::add(chain::action action, chain::transaction_id_type transaction_id) { chain::abi_def abi; std::string abi_def_account; chain::abi_serializer abis; + const auto transaction_id_str = transaction_id.str(); *m_session << "SELECT abi FROM accounts WHERE name = :name", soci::into(abi_def_account), soci::use(action.account.to_string()); if (!abi_def_account.empty()) { @@ -57,10 +59,11 @@ void actions_table::add(chain::action action) auto abi_data = abis.binary_to_variant(abis.get_action_type(action.name), action.data); string json = fc::json::to_string(abi_data); - *m_session << "INSERT INTO actions(account, name, data) VALUES (:ac, :na, :da) ", + *m_session << "INSERT INTO actions(account, name, data, transaction_id) VALUES (:ac, :na, :da, :ti) ", soci::use(action.account.to_string()), soci::use(action.name.to_string()), - soci::use(json); + soci::use(json), + soci::use(transaction_id_str); // TODO: move all if (action.name == N(issue)) { diff --git a/plugins/sql_db_plugin/db/actions_table.h b/plugins/sql_db_plugin/db/actions_table.h index 4ac2bc1dea8..67ea96831cf 100644 --- a/plugins/sql_db_plugin/db/actions_table.h +++ b/plugins/sql_db_plugin/db/actions_table.h @@ -25,7 +25,7 @@ class actions_table void drop(); void create(); - void add(chain::action action); + void add(chain::action action, chain::transaction_id_type transaction_id); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index a00f947727a..dc1f4c19c90 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -29,7 +29,7 @@ void blocks_table::create() "timestamp NUMERIC," "transaction_merkle_root TEXT," "producer_account_id TEXT," - "pending NUMERIC," + "confirmed NUMERIC," "updated_at NUMERIC)"; } @@ -43,14 +43,14 @@ void blocks_table::add(chain::signed_block_ptr block) }.count(); *m_session << "INSERT INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root," - "producer_account_id, pending, updated_at) VALUES (:id, :in, :pb, :ti, :tr, :pa, :pe, :ua)", + "producer_account_id, confirmed, updated_at) VALUES (:id, :in, :pb, :ti, :tr, :pa, :pe, :ua)", soci::use(block_id_str), soci::use(block->block_num()), soci::use(previous_block_id_str), soci::use(timestamp), soci::use(transaction_mroot_str), soci::use(block->producer.to_string()), - soci::use(0), + soci::use(block->confirmed), soci::use(timestamp); } diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index 5f617a66111..dac1e44422e 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -21,7 +21,7 @@ void database::consume(const std::vector &blocks) for (const auto& transaction : block->trxs) { m_transactions_table->add(transaction->trx); for (const auto& action : transaction->trx.actions) { - m_actions_table->add(action); + m_actions_table->add(action, transaction->trx.id()); } } } diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index 38268a76ce6..e6f7f5f0801 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -25,15 +25,12 @@ void transactions_table::create() { *m_session << "CREATE TABLE transactions(" "id TEXT PRIMARY KEY," - "sequence_num NUMERIC," "block_id TEXT," "ref_block_prefix NUMERIC," - "status TEXT," "expiration NUMERIC," "pending NUMERIC," "created_at NUMERIC," - "type TEXT," - "updated_at DATETIME)"; + "updated_at NUMERIC)"; } void transactions_table::add(chain::transaction transaction) @@ -43,16 +40,14 @@ void transactions_table::add(chain::transaction transaction) std::chrono::seconds{transaction.expiration.sec_since_epoch()} }.count(); - *m_session << "INSERT INTO transactions(id, sequence_num, block_id, ref_block_prefix, status," - "expiration, pending, created_at, type, updated_at) VALUES (:id, :se, :bi, :rb, :st, :ex, :pe, :ca, :ty, :ua)", + *m_session << "INSERT INTO transactions(id, block_id, ref_block_prefix," + "expiration, pending, created_at, updated_at) VALUES (:id, :bi, :rb, :ex, :pe, :ca, :ua)", soci::use(transaction_id_str), - soci::use(transaction.ref_block_num), // TODO: proper fields + soci::use(transaction.ref_block_num), soci::use(transaction.ref_block_prefix), - soci::use(transaction_id_str), soci::use(expiration), - soci::use(1), + soci::use(0), soci::use(expiration), - soci::use(transaction_id_str), soci::use(expiration); } From 7ff9e3d58e46f027203576cd719f9c2c1c97b4c7 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Tue, 22 May 2018 10:48:48 +0200 Subject: [PATCH 017/294] try catch --- plugins/sql_db_plugin/db/accounts_table.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/sql_db_plugin/db/accounts_table.cpp b/plugins/sql_db_plugin/db/accounts_table.cpp index aca6ad2d928..a7b7fa5dbe4 100644 --- a/plugins/sql_db_plugin/db/accounts_table.cpp +++ b/plugins/sql_db_plugin/db/accounts_table.cpp @@ -38,8 +38,13 @@ void accounts_table::add(string name) bool accounts_table::exist(string name) { int amount; - *m_session << "SELECT COUNT(*) FROM accounts WHERE name = :name", soci::into(amount), soci::use(name); - + try { + *m_session << "SELECT COUNT(*) FROM accounts WHERE name = :name", soci::into(amount), soci::use(name); + } + catch (std::exception const & e) + { + amount = 0; + } return amount > 0; } From 1bc3c52f083e7a261e0228be00f26cae03f2f0df Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Tue, 22 May 2018 13:25:01 +0200 Subject: [PATCH 018/294] wip --- externals/binaryen | 2 +- libraries/appbase | 2 +- .../bios-boot-tutorial/bios-boot-tutorial.py | 2 ++ programs/bios-boot-tutorial/test.db | Bin 0 -> 860160 bytes 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 programs/bios-boot-tutorial/test.db diff --git a/externals/binaryen b/externals/binaryen index b4b5dc9dee6..579f3a099c2 160000 --- a/externals/binaryen +++ b/externals/binaryen @@ -1 +1 @@ -Subproject commit b4b5dc9dee60489c4206af99227524b13f2eb3aa +Subproject commit 579f3a099c286a45f58ea1ffc7bf671c415be0a6 diff --git a/libraries/appbase b/libraries/appbase index 70e23f7ebbd..50dc015b2f0 160000 --- a/libraries/appbase +++ b/libraries/appbase @@ -1 +1 @@ -Subproject commit 70e23f7ebbdccb64f9ac11ade9fa41ba78b31b5e +Subproject commit 50dc015b2f0e25c0cd01cf520245da23c0ed446b diff --git a/programs/bios-boot-tutorial/bios-boot-tutorial.py b/programs/bios-boot-tutorial/bios-boot-tutorial.py index f5f19fbfb66..7cb7c748d3a 100755 --- a/programs/bios-boot-tutorial/bios-boot-tutorial.py +++ b/programs/bios-boot-tutorial/bios-boot-tutorial.py @@ -93,6 +93,8 @@ def startProducers(b, e): ' --private-key \'["' + account['pub'] + '","' + account['pvt'] + '"]\'' ' --plugin eosio::http_plugin' ' --plugin eosio::chain_api_plugin' + ' --plugin eosio::sql_db_plugin' + ' --sql_db-uri=sqlite3://test.db' ' --plugin eosio::producer_plugin' + otherOpts) with open(dir + 'stderr', mode='w') as f: diff --git a/programs/bios-boot-tutorial/test.db b/programs/bios-boot-tutorial/test.db new file mode 100644 index 0000000000000000000000000000000000000000..7d426ec19e3d4d8684b38a6dcafb71f025c44fe4 GIT binary patch literal 860160 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 z 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

{$)q_mtF~(e^-#UW&W)k;d6vr{_gdcPe1Z# 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 c`ZLg Date: Wed, 23 May 2018 18:57:41 +0200 Subject: [PATCH 019/294] ported to mysql --- plugins/sql_db_plugin/db/accounts_table.cpp | 10 ++++---- plugins/sql_db_plugin/db/actions_table.cpp | 24 ++++++++++--------- plugins/sql_db_plugin/db/blocks_table.cpp | 22 ++++++++--------- .../sql_db_plugin/db/transactions_table.cpp | 18 +++++++------- 4 files changed, 36 insertions(+), 38 deletions(-) diff --git a/plugins/sql_db_plugin/db/accounts_table.cpp b/plugins/sql_db_plugin/db/accounts_table.cpp index a7b7fa5dbe4..2c47f335c0a 100644 --- a/plugins/sql_db_plugin/db/accounts_table.cpp +++ b/plugins/sql_db_plugin/db/accounts_table.cpp @@ -23,15 +23,15 @@ void accounts_table::drop() void accounts_table::create() { *m_session << "CREATE TABLE accounts(" - "name TEXT PRIMARY KEY," - "abi TEXT," - "created_at NUMERIC," - "updated_at NUMERIC)"; + "name VARCHAR(18) PRIMARY KEY," + "abi JSON DEFAULT NULL," + "created_at INT," + "updated_at INT)"; } void accounts_table::add(string name) { - *m_session << "INSERT INTO accounts VALUES (:name,'', strftime('%s','now'), strftime('%s','now'))", + *m_session << "INSERT INTO accounts VALUES (:name, NULL, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())", soci::use(name); } diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 8119f1b710e..2238c4c7012 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -24,17 +24,17 @@ void actions_table::drop() void actions_table::create() { *m_session << "create table actions(" - "account TEXT," - "transaction_id TEXT," - "name TEXT," - "data TEXT)"; + "account VARCHAR(18)," + "transaction_id VARCHAR(64)," + "name VARCHAR(18)," + "data JSON)"; // TODO: move to own class *m_session << "create table tokens(" - "account TEXT," - "symbol TEXT," - "amount REAL," - "staked REAL)"; + "account VARCHAR(18)," + "symbol VARCHAR(10)," + "amount FLOAT," + "staked FLOAT)"; // NOT WORKING VERY GOOD float issue } void actions_table::add(chain::action action, chain::transaction_id_type transaction_id) @@ -43,9 +43,11 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac chain::abi_def abi; std::string abi_def_account; chain::abi_serializer abis; + soci::indicator ind; const auto transaction_id_str = transaction_id.str(); - *m_session << "SELECT abi FROM accounts WHERE name = :name", soci::into(abi_def_account), soci::use(action.account.to_string()); + *m_session << "SELECT abi FROM accounts WHERE name = :name", soci::into(abi_def_account, ind), soci::use(action.account.to_string()); + if (!abi_def_account.empty()) { abi = fc::json::from_string(abi_def_account).as(); } else if (action.account == chain::config::system_account_name) { @@ -128,13 +130,13 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac chain::abi_serializer::to_abi(action_data.abi, abi_setabi); string abi_string = fc::json::to_string(abi_setabi); - *m_session << "UPDATE accounts SET abi = :abi, updated_at = strftime('%s','now') WHERE name = :name", + *m_session << "UPDATE accounts SET abi = :abi, updated_at = UNIX_TIMESTAMP() WHERE name = :name", soci::use(abi_string), soci::use(action_data.account.to_string()); } else if (action.name == chain::newaccount::get_name()) { auto action_data = action.data_as(); - *m_session << "INSERT INTO accounts VALUES (:name,'', strftime('%s','now'), strftime('%s','now'))", + *m_session << "INSERT INTO accounts VALUES (:name, NULL, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())", soci::use(action_data.name.to_string()); } diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index dc1f4c19c90..a8efd5b4963 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -23,14 +23,14 @@ void blocks_table::drop() void blocks_table::create() { *m_session << "CREATE TABLE blocks(" - "id TEXT PRIMARY KEY," - "block_number NUMERIC," - "prev_block_id TEXT," - "timestamp NUMERIC," - "transaction_merkle_root TEXT," - "producer_account_id TEXT," - "confirmed NUMERIC," - "updated_at NUMERIC)"; + "id VARCHAR(64) PRIMARY KEY," + "block_number INT," + "prev_block_id VARCHAR(64)," + "timestamp INT," + "transaction_merkle_root VARCHAR(64)," + "producer VARCHAR(18)," + "confirmed INT," + "updated_at INT)"; } void blocks_table::add(chain::signed_block_ptr block) @@ -38,12 +38,10 @@ void blocks_table::add(chain::signed_block_ptr block) const auto block_id_str = block->id().str(); const auto previous_block_id_str = block->previous.str(); const auto transaction_mroot_str = block->transaction_mroot.str(); - const auto timestamp = std::chrono::milliseconds{ - std::chrono::seconds{block->timestamp.operator fc::time_point().sec_since_epoch()} - }.count(); + const auto timestamp = std::chrono::seconds{block->timestamp.operator fc::time_point().sec_since_epoch()}.count(); *m_session << "INSERT INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root," - "producer_account_id, confirmed, updated_at) VALUES (:id, :in, :pb, :ti, :tr, :pa, :pe, :ua)", + "producer, confirmed, updated_at) VALUES (:id, :in, :pb, :ti, :tr, :pa, :pe, :ua)", soci::use(block_id_str), soci::use(block->block_num()), soci::use(previous_block_id_str), diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index e6f7f5f0801..757561496d0 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -24,21 +24,19 @@ void transactions_table::drop() void transactions_table::create() { *m_session << "CREATE TABLE transactions(" - "id TEXT PRIMARY KEY," - "block_id TEXT," - "ref_block_prefix NUMERIC," - "expiration NUMERIC," - "pending NUMERIC," - "created_at NUMERIC," - "updated_at NUMERIC)"; + "id VARCHAR(64) PRIMARY KEY," + "block_id VARCHAR(64)," + "ref_block_prefix INT," + "expiration INT," + "pending INT," + "created_at INT," + "updated_at INT)"; } void transactions_table::add(chain::transaction transaction) { const auto transaction_id_str = transaction.id().str(); - const auto expiration = std::chrono::milliseconds{ - std::chrono::seconds{transaction.expiration.sec_since_epoch()} - }.count(); + const auto expiration = std::chrono::seconds{transaction.expiration.sec_since_epoch()}.count(); *m_session << "INSERT INTO transactions(id, block_id, ref_block_prefix," "expiration, pending, created_at, updated_at) VALUES (:id, :bi, :rb, :ex, :pe, :ca, :ua)", From 62cec35e5654a835a5b38f820db5c3a6ed47296e Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Thu, 24 May 2018 09:35:42 +0200 Subject: [PATCH 020/294] add proper types sql --- plugins/sql_db_plugin/db/accounts_table.cpp | 8 +++--- plugins/sql_db_plugin/db/actions_table.cpp | 25 +++++++++++++------ plugins/sql_db_plugin/db/blocks_table.cpp | 10 +++----- .../sql_db_plugin/db/transactions_table.cpp | 8 +++--- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/plugins/sql_db_plugin/db/accounts_table.cpp b/plugins/sql_db_plugin/db/accounts_table.cpp index 2c47f335c0a..8f8f767d948 100644 --- a/plugins/sql_db_plugin/db/accounts_table.cpp +++ b/plugins/sql_db_plugin/db/accounts_table.cpp @@ -23,15 +23,15 @@ void accounts_table::drop() void accounts_table::create() { *m_session << "CREATE TABLE accounts(" - "name VARCHAR(18) PRIMARY KEY," + "name VARCHAR(13) PRIMARY KEY," "abi JSON DEFAULT NULL," - "created_at INT," - "updated_at INT)"; + "created_at DATETIME DEFAULT NOW()," + "updated_at DATETIME DEFAULT NOW())"; } void accounts_table::add(string name) { - *m_session << "INSERT INTO accounts VALUES (:name, NULL, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())", + *m_session << "INSERT INTO accounts (name) VALUES (:name)", soci::use(name); } diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 2238c4c7012..f1d105e0409 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -15,6 +15,7 @@ void actions_table::drop() try { *m_session << "drop table actions"; *m_session << "drop table tokens"; + *m_session << "drop table actions_accounts"; } catch(std::exception& e){ wlog(e.what()); @@ -23,15 +24,20 @@ void actions_table::drop() void actions_table::create() { - *m_session << "create table actions(" - "account VARCHAR(18)," + *m_session << "CREATE TABLE actions(" + "id INT NOT NULL AUTO_INCREMENT," + "account VARCHAR(13)," "transaction_id VARCHAR(64)," "name VARCHAR(18)," - "data JSON)"; + "data JSON, PRIMARY KEY (id))"; + + *m_session << "CREATE TABLE actions_accounts(" + "account VARCHAR(13)," + "action_id INT)"; // TODO: move to own class - *m_session << "create table tokens(" - "account VARCHAR(18)," + *m_session << "CREATE TABLE tokens(" + "account VARCHAR(13)," "symbol VARCHAR(10)," "amount FLOAT," "staked FLOAT)"; // NOT WORKING VERY GOOD float issue @@ -61,6 +67,8 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac auto abi_data = abis.binary_to_variant(abis.get_action_type(action.name), action.data); string json = fc::json::to_string(abi_data); + // TODO: insert actions_accounts + *m_session << "INSERT INTO actions(account, name, data, transaction_id) VALUES (:ac, :na, :da, :ti) ", soci::use(action.account.to_string()), soci::use(action.name.to_string()), @@ -130,16 +138,19 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac chain::abi_serializer::to_abi(action_data.abi, abi_setabi); string abi_string = fc::json::to_string(abi_setabi); - *m_session << "UPDATE accounts SET abi = :abi, updated_at = UNIX_TIMESTAMP() WHERE name = :name", + *m_session << "UPDATE accounts SET abi = :abi, updated_at = NOW() WHERE name = :name", soci::use(abi_string), soci::use(action_data.account.to_string()); } else if (action.name == chain::newaccount::get_name()) { + // TODO: store public keys auto action_data = action.data_as(); - *m_session << "INSERT INTO accounts VALUES (:name, NULL, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())", + *m_session << "INSERT INTO accounts (name) VALUES (:name)", soci::use(action_data.name.to_string()); } + + // TODO: catch issue (tokens) // public keys creation } } // namespace diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index a8efd5b4963..78312f3d2c3 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -26,11 +26,10 @@ void blocks_table::create() "id VARCHAR(64) PRIMARY KEY," "block_number INT," "prev_block_id VARCHAR(64)," - "timestamp INT," + "timestamp DATETIME DEFAULT NOW()," "transaction_merkle_root VARCHAR(64)," "producer VARCHAR(18)," - "confirmed INT," - "updated_at INT)"; + "confirmed INT)"; } void blocks_table::add(chain::signed_block_ptr block) @@ -41,15 +40,14 @@ void blocks_table::add(chain::signed_block_ptr block) const auto timestamp = std::chrono::seconds{block->timestamp.operator fc::time_point().sec_since_epoch()}.count(); *m_session << "INSERT INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root," - "producer, confirmed, updated_at) VALUES (:id, :in, :pb, :ti, :tr, :pa, :pe, :ua)", + "producer, confirmed) VALUES (:id, :in, :pb, FROM_UNIXTIME(:ti), :tr, :pa, :pe)", soci::use(block_id_str), soci::use(block->block_num()), soci::use(previous_block_id_str), soci::use(timestamp), soci::use(transaction_mroot_str), soci::use(block->producer.to_string()), - soci::use(block->confirmed), - soci::use(timestamp); + soci::use(block->confirmed); } } // namespace diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index 757561496d0..1f4abcbce67 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -27,10 +27,10 @@ void transactions_table::create() "id VARCHAR(64) PRIMARY KEY," "block_id VARCHAR(64)," "ref_block_prefix INT," - "expiration INT," + "expiration DATETIME DEFAULT NOW()," "pending INT," - "created_at INT," - "updated_at INT)"; + "created_at DATETIME DEFAULT NOW()," + "updated_at DATETIME DEFAULT NOW())"; } void transactions_table::add(chain::transaction transaction) @@ -39,7 +39,7 @@ void transactions_table::add(chain::transaction transaction) const auto expiration = std::chrono::seconds{transaction.expiration.sec_since_epoch()}.count(); *m_session << "INSERT INTO transactions(id, block_id, ref_block_prefix," - "expiration, pending, created_at, updated_at) VALUES (:id, :bi, :rb, :ex, :pe, :ca, :ua)", + "expiration, pending, created_at, updated_at) VALUES (:id, :bi, :rb, FROM_UNIXTIME(:ex), :pe, FROM_UNIXTIME(:ca), FROM_UNIXTIME(:ua))", soci::use(transaction_id_str), soci::use(transaction.ref_block_num), soci::use(transaction.ref_block_prefix), From 13b586b0f4e1a86069993a7fba1666c700192d4c Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Thu, 24 May 2018 10:49:34 +0200 Subject: [PATCH 021/294] bios not needed --- .../bios-boot-tutorial/bios-boot-tutorial.py | 2 -- programs/bios-boot-tutorial/test.db | Bin 860160 -> 0 bytes 2 files changed, 2 deletions(-) delete mode 100644 programs/bios-boot-tutorial/test.db diff --git a/programs/bios-boot-tutorial/bios-boot-tutorial.py b/programs/bios-boot-tutorial/bios-boot-tutorial.py index 7cb7c748d3a..f5f19fbfb66 100755 --- a/programs/bios-boot-tutorial/bios-boot-tutorial.py +++ b/programs/bios-boot-tutorial/bios-boot-tutorial.py @@ -93,8 +93,6 @@ def startProducers(b, e): ' --private-key \'["' + account['pub'] + '","' + account['pvt'] + '"]\'' ' --plugin eosio::http_plugin' ' --plugin eosio::chain_api_plugin' - ' --plugin eosio::sql_db_plugin' - ' --sql_db-uri=sqlite3://test.db' ' --plugin eosio::producer_plugin' + otherOpts) with open(dir + 'stderr', mode='w') as f: diff --git a/programs/bios-boot-tutorial/test.db b/programs/bios-boot-tutorial/test.db deleted file mode 100644 index 7d426ec19e3d4d8684b38a6dcafb71f025c44fe4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 860160 zcmeFa37jNHedk-#({s;s>i~o}M+4$8LR95Zl_g&YA#BWH4k5rG4Oy94(xAE2(<3Cy z+V$v`jgR$h@O|QQeSvLk<8yrjw%4D1w%@aNz5DU*`g)7sXZNl5d2PS%?;nw8S9SOF zj6gv5OjlP%Mn?SOAOHB@8S%!~zxvo}ad>)aW>1t}< zUU=??U3;#$`sy9`ztWAPe(X%6i8H%fJvQ&Wz_)(*b#Hj(Yp#F8TMxhL#zW{r}=v9?!&Kr(`#;g!z*vNrnvj`v6WOKtBOt+C-Y+`Z>z4el_Fg&@}n4e zg4p;$_WT%9gMtaarYIUd*wWgxX~OPxWBvZ-m&M3XFhYs zxf?vK8L2{S?(aei5oPHMDr#!Yd^$PiNNjTx_mYL(SKRQ-d0>`(9UYY4cEV)#iqY)i zcX9cz^mTRlAC~{$<&U`w6>R@(V_+Kt+Zfo!z%~Z9F|dt+Z47K<;0cF;$IdS7e$Ln4 zxVTuHI&Lo{ zb#+uKI90uJ>@HnJO6}%-w_I}sL+)(0>K}DanBg%kR%mvTo>3con;t8U=dEhfl~X5t z&)shBy9PLXW2ZK*bLZLg$|)_gSJzbHuXjc)+XBt4#< z%!)PWZVoFuyRuT8%X(7qvhp>&sEgsAFQtDJPGV(KDSm-X}5x1?~8C$d2AR_ zbVqTIEm}Qtq@0c;G^88q)^40Vc5;>KYQII&%|U7kFKQnpG@dv#GmUW{PtTq);3zIu zR!*(pzBqFxy{#;RYaK1_Ud1IQuN9-a<-WJ=yzz{FVZ`B~lIxu~bL_Upji*&Nj2*f= zJTSTcv-td~a}l2hU&zOSg#|u$IFtXa%YU`}8_OS9{@&%UTYg}9WjS4b-SX!yU%ULw z<%3KAu=H0;f4uaYOFzBzeM?`z^x)E&rODFkmtMN`lBH)Y9Xj;C5B>F_KRNVUhkoYJ z_aFL(Lk}HVJ(L}K!=dXB^$$J!(9*$wJoq;U|IdTJeeh=wzVF~S9(?%V*@OAPHy(W1 z!NI}92bT~0(}BM|@P8fnodX{{@B;_F>A)ig?mAE$xaq(R2Z94v9k^uwKkxs)_y6hs z-`)Rn`+so%H}8LR|GV~2_rGcX%lC)-kLRnD%l6#8XS(O6JulnS-}9_J2Y3H}yZ`I%f4BSBcK^ig@7Vp7 zyT4@jJ9ocj_bYZsyN~R?WY<6K`kP&Uvg{f?dh3=k0pZ zu7$;qE&kc!?=F6D@qLTmu=wEO%Hlf~U%U8Oi=VOh)WzL9|NGAWxbydRerV?p?|jeB zM|a+}GvE1!oiE*a?apWH+`sVm3;%iH4;OxA;l~!fZQaEdVkO<346caDG7SNSC*VOVGYH- zk9JC;-bXqmVeiA8lA!my?GoVmolZ&A`|VCi*!!(cNznVvs>Fd4_kN>O67_z)Qxf+6 zO{XO2{aU*Ocz(5067_ziQxf)mxlNznVLb_wwOWTzzR{Y0lE?EQGB zB ziF#ktDG7W3s#6m5zPcJ&tGbqd0>oq}MsT@b}* zIt9^6ry#tuQxKdk3vRp30dlHS5S{E4geN)$!SQxM6yMP)h~C*L2#<9Ng4?SCkB{3r z1<|xq5Eh++Aa55$an>n_CY^#X?Gyy>C<~@j2guP*L3C@UAiSkh5WKxz5XEon6hvRt zDG1-%DG1(D6?lBSxl<6`+$jj()F}vVY8OQD8#@Kj8#)Ey>pKO(>&k+naDaSaryzQ5 zryzVyryzKByC8~R)hUR+pi>aOvQrRzepTS{@p+ws=oOuU@WxI-@bY#+6yMM(h+fty z2(RxH1TQTM^4tOPxt)UOb2OPzx7P^Ta`*e;0T1D%3sf2Sba*C`10Rs|j(dpZT-?sh>G@9GqUi%UDN z+TlE+@(t`<-uWC1$G4O^hO_u0tT4s@cP{^(%*tP}`2XMS|KPqq-uw4^uh=u({ia>3 zi(k9*Cl-Ex$H#Uo_pa^r&fR+1!E=XpoIAAZ+@UM>?&{mmAWTz!qfr*6lSw~H(sY^y z{Xu^a4knXfmJ9}AJerRB<771K2SvX~r$I8z2jk0q1Hq)|hoeb29OOJfJ7F}=CTW(0 z{c#u!(kP3PB2NcJA5snTocRf&bU5zs$R7NScf4;f7)E(M2uJ4&qGkSo9Ta)kPY0uMGEU>sXcT5iGEQ0pyky1zw+!EOpW8t{7)%(92Ke@0%93%IO%gCL zofME$4uMS5An2!21l5d3SvKf1QTZf~gE;I@qET3kq9PH|F~!vY-~LPIZe5-+z%9df zi5noJ$mo5N^%+slWF_MXWZKV1<7|+pec(vKVKGXFK{1KPVH89eAc6@uK)f`gY#c=S*c#x{+5v7IzGXka zVv;4p>2R8*K@?1etfm+_Ibf|ugJL{PShG}O0(k`FY%xBO9KHz)Zq)6hUKvIn2kSNjeUuP+t*E3qQa?8bY*_ zFr2{8vVK|wNfEQaf*_j=qVbf0WdSTNjiL}4%)$HjC6&RPRJIAefYhVPIc zU^b2i!#J7d@h}}{NiduahOk*%B@^G5Qbc2@ZmHr(g7?cD`Kc*5C%aw8b@F>j`9hpQvA&|b?(*!GX}V2 z_zwC3var%|G8|^Za1>33@EbUPI!#8|G#{sInXbo`xi~(*Lz5{-M zX%xdaijcWUl4LXpfhisj`hA#tK4LDj7=Bt5qfsy@l5su&=phT%*5K>+vaUvqfNBl8bd1ANWlbGPo9F~BXuSK@w@r<1flLPn&0X)senA^bk==aVU`iglAt zCrKDWmE(LkjxvN@l23-lWL|SPiVFmHoFs7{=^6Ipe3-^8UrGjHm>2y#Lpc~Acc9l{ z7>)yRpCUw?42B4$)&O_U7~q!S+v8C-iE0c>hgRe}^-MVYW0JjX^ZVy^K2$2a{I7XF0$`4@)@o=0DhiIb+ z=mI@uFdU3g;-~#s`c{7k2kcLBk?1RvXpEX2Pmm&`=_DG!P>Vr4Ndx3d247n>l%;A0HNeFg1Kcuv-W=&i{XXm@==aBH+vA`=&66OGhocn9 zpJmeo`vJ`vJ!_i9IkGGoBSa?sFwy{Td>vnh35pIx7$CfZ{zQlwOW)4Xk3t0IAWx=I z&RE7tFh(McqP#y$Lv;LcSd-|DuRC|^&KU#TGJK1E2E!OF8TpL##jG(?C>lrM01Yb{ zjHbx4cnn#N<2XlW3#k?Mr&6lj052MeYJdwf2DoMTc6!hPu-K2{G>w8OGCxM$N4FGV zGN1v{DjkJUnC2|R{y0XjiV3?Zrw3sfLn%d!Gnfm&qsZhNYU>H z!88x1DWX2Ze8x^fCq;6k!Dt$ff)rssiPI4>C5I+8gBLbH4E+a&e0;}q`FPW1F8==k zzW3g@{DGyvT>9>%Tb8ak^!tat{LqaD|CyNmnS(Dr@Lvvm=Yh8!c=G;_?tg6m4g3Ca z-%szmbKi^h{>9#J-}~0Rm+$%To=5gvzxy9{zkm0s-7nnrpLTugt~c+xbn$lI=)t zMnyJEiZDomV2r&m>gzWAZ!jJ;TNzEU{U*uOG`OOVm74Vt5Ltvu8Ph*qW5e^*1m=N5 z0T}{OBmRfYR))|78t#BeALrwuKMqp79n%Co6&n-#Yn0Jsk&ci|*>v2GaumZfnU3OS zD=}`5VVDPlD2oGhR8v-8{}1VE<#3#vK1c8Y3iWJ)R8EP^aN!oCRUC6_nW^EvBf@7!(5#I!!|e9u+Xa z2#coEFa#->xzKmP>@kx<)nE`bTL}hO$|K`6ySJ@0I=E0AVpioq{e*`V$h<0BHbonr@+2tkhq_U2=58bDo~2A2rCyQEg$ro ztzZ_(O2`MJ0Ak6r{vb_@0S=g;81i(8PQc?B8Zcsrk}-vO1`R|nX|xhdlW~+!A*(1( zFiWL{Pm^Fe9OFBK>iV!ih%&`hG=}iT7~D+$V4Q{5Hd`ssnqu_iaRCx@%u~ib9**U0 zLtUXTD~7`eXj0TJneNk3I35Mb$O{ zU1AW9_7x;y6pv$EHKVvcz^{P?%sf4(*-D5F0(Fg&$#|L%ivs(Kr>B#=z|2B3lj%I^ z=i*xhYy&oj2WXrZgCosW0+=(#K1M&}+Yi!2E-U6Xnlc0iF@Ypu1SrI;6ByNG6hk^_ z9alA58PHi8CUKacrC@{u$%yfdFn9+^ivBXeod!P~1y~Hg0t=Yru&(TIvlUnlHe)}U z@KireqLA<|hza5M!%+bXi$@byCkiQi6N7rp%waBv{bx5?8KBmWV;&#Hz@H$zsDf03 z#PDCq1jW}iDsTaf@Ur&N*#r0k&Vl?{%~lFf6{h1U8ZuKZ>KuhQ_9x6!0sBvxCt!(- z0(%d+B?nm_Uv$6!%w{V&YPh+|^^IVmdCc z`=8Nl1#O*5xXecwFBoM>kcPuZJ~)^?ixezR@_qpsPH+On{EH&Y$|QPvvlV0nqGyEQ z1^qZTr7wq5wCErnFw7B*Du=uaFo73sI>PUZB^qbLr!`wilOcQv=FbQMe4hju0?Z1d z<}qvri*}$%BFunKJHzdnqMVE-!IjNc7z6ZzAC;M47RLyZ5s)EW@Z9Hpm@rUGhLqzg z$B}?*Qmm^8pW19CkuDM-%2OPxQ|tg3-MARx)WU6!iOs0+2nHbnViJP&j8ZTY4Xx{U?ibi~Im{7bc$m`z1~9T7O(~E0g^m$m@l%?u4D(3}K;MQf$R<;~Ly0qEKmculcG+WtnEM&RS%GLu8OU+ic?A1TiY-P)? z^MlP+w(Ori&}?PPPT>8`R<`VE-Pdeo%kIp*%~rPTGu+c`Wy_Aa-OX0E?ETu+Y-P(X zrp0C}TlVAZY__r`X9Hm_-}bzF&kJ||`tEFZ4@>{L#s6b*b@9@jKf3c(3;%WD zp@kzmesRa!d;hEV4Qg=fKj{nhojY{t;;y~Bb|nRYVcf99zlqAA$D_~1G0t-?GOjS2 zunA&x=pg}}6k9mKK1Qh+^s_0FWe_~?H-F$}`AvTNaPu>Le$~Yo#}XP{KgBc{OEX6# zC8+zOG)!?FqW7aSAdU-jxDoimGEZU@L?YZ#AIo`O7tgD@AnuJsouI6uWTNm5@D*e^ z<3b(8n?T?pK@X15@rOvb;e=Q*Rt}~UJ2pgw>*jWGb=3vx1ELzeYKZYeKcj4j{g33H zA_s9dV7?J{PiP+7dW^da2O+4%>Ln7N5iFcHisx2cq{M275DsGkaH#1)grG*@!zPc0 zME4P$plw1p2WP4r%V>&3Nr{6d8J>MK$9Y{mr|M#ae}u3xQ%A%kC&rI!eVkzV$5=(E zRd~j*a|y%~iI>JHeU|tmay=WM9*^dBair=(u8K)A!c{TG7{&%0pv&hdoMRdDcq53R z%3XnJiSf-{$U{+1tTtwAf?IeFMO{^Ofdw%wf&gP)0lA67U3l>DS`s%QK!U@WY>p`& z7j$J5blfLs`hkMv*$Cr1nA^qSs*4OK4mJb|JTZ9O0$3-b6cZNxjNlWYz$xUM;QGS0 zCG3X|MlwyDhFIOK9<;&1hd!`sVW-Zzcy`qVrW-mO{*5rj>_p`z5dp94D8gAO#S*te zLjDT2H<2HFIR&$Z`Gi555G7$Q=5_I`stc52mJq8MLJA?2g4HW(9}|;^0(5eWB((cc z2FVf8#-ojQCKnSRcLH5_ZWqt2y1+_}hjD>ZTni1_87ql{ATj=xD96D%Aq>fy!?}UQ zH9|Wa;9tP%BRoQYB%9mCGpa5i%OTDnQi^~dlLY&XOdf0zT#A?xsPn`>a8|JPQ*xxn zEFnIlK{CwXJ<ygx&tanmu{ zZ!sAvCt>a=o>p~1v=VMM#-9=4!z*&uL{6-WWr1bHiklV$@=)CS7;Tsa5zZ4b0y5k+ zW3sQHwRzKhWz|JGRPY_skLiOqBv>gRXo!skGEy97a%p0Vp^7D`+}`j*7AkQUvWD=; z&h6r7)-lfWv)1A)0Rw~H&P zF352sIyk}NAT%aMk`ZhPae@j7A%-}*4&D>u6vTa#1jaaxA_CmXzYb9{p;2+4>IbA%d z>H=p$h%X0UJPzgp*2fag@ZZXd!fMCnhAY8HKp&xgoCo-m;j&CM11CdZZWotVT`+q% zVPOloELI#m2!0Y$NG2c@iO+|a3K)T`h}>l|lnL&^)&jDt;QgdT%p1jJRTnIXFvZD> zN2<@Fl0l7V8p7>xyD=?FC1F-r@L1ib1DMIO(dEJ>EQpt8UKf{EU69IwtIjQ}@+)*r7UBF=x83Dc$<`(BQvyh_(Fb7DC z5u8`+0uK;8mrPAkK4qWap2E>5L!1=9*%bATCAwHPZBWq_GUiC+#8pH(4_pq~$AJie zDV+<|kNk>3s=&q={b7io3bkaKPDW$6RW^R{;je#1bIrN$?>JQTKv)-7Ql?Bx1O^h5 z94Sa>%)nR{crjtNC>vA4t+@09;tdkQsD#Nh>k~nm*Tcc82i(7Djz=OIMj4!BOne#l z2tIP$pfEzfr5{8G>4xB+?8!b#iJZm^gQ*|R>)}Av!vswgS|=tp3W+S>HDt97VAfJ; z(5&!Y6DdKvk>i*s59%{(k$CthQN(OAuZR6r54b>4=wfNyeVj29e7AT~hJ?c?7?Zv+cO;?R_bnOxd~X$aFsH|ry+q97mU zaR>yYzM+GX|A%rDpoq@vVNcZqT9HM^ViY^PR%AWmUYbgAWf>qA;PH%*WUcX-Fk?i- zWy&0oU6A3?CTur+;oqKlttj+Ew>LlEvAgO4C488WBSS(M>27d1Tw0o9)*2purBk!q zS#RW06Qqh`(xRbRbU?o3A^d;&Ywmro+Wz2|Hb397tLlM_EYOLDM;Htr8ioQ^N(|AH z>AaRy`1?k;@Q&oZ>no+@Cn( zl)y(a1IrJ8IWAH}9ufO5%7@P~)8vFR>N!2^ta>2hjTBpjr9uT^a4QmKKmkEP zBkc^35|2cu%qq)otwCDL{_vCtI-`{3`9UNvOoNQK7Z-}jFTf4=X|y?=4=#k>B~jt6$Xf9bmy-rM`1 zdmdR>-hFE6iUUvH@!5MX-*xHIUmpC~U2i_{w&kB&KD+NlyI;8Pr}sa$@TQ$74*v6@ zFW+-L8351U|ItG?9{A3KXBK~F>6TsJy7#U7Z=k=e{W!NrP!N6}PDjMfMpy_0q{#V1 zGDf(uaRQJ;mJ)R&d`Bj>V#64EIa&d}ZIoJ4My(Zu6;c94N(sqW9O#J(=S8R=@O0ve z@G63qq`smF5wj<*gKR;Qp=Au9)W!c5x*$x5r3ir$yMux-IbdJtdPIBR!vw0ah=Bkb zfOUdfL1qDY2^dyc-^Kq)c1c2{GkOKQ1_O{N9C=pAGs2zZT=5vzH9mga>F8iUf-Mv( zQawhoAvWgSL5L~hPjVm-0?WYSLiL8y7)g8 zWzXQNCz60^gQb#^1BU#8wprcqd;)y^F-kBI)Qm)8bQEYFTROrne^5QOdR=#+J+4Vw@1lk)-q%|dM}O_tZ*C} zsElA<3Ms*{5z11*`x^ZWE;+^CR>F^8jOnDsYNm!@@Gj z71Qf57_uM^jvV;-|0JOwF_bSkbu|Fmxb7MRa<5v8JBo1r}@?((0F8+_+ zMg9T!!-z*4gVYpniV4h5pbGp62n~${?2xrWBqbwuiDH68rnzM3;{Uv@AQ5fq1|Y0t=V?{G<<{SS$pHv4(ON|7X;raS9?~^oXb!!`sX}G^dC+V$jwI zqLWTG9Y9cYMM?#!sCddqvX=4W;{O-wbae55`nn*$qKp4usO!+h|1Z=#c(tvg3v~cK z*IKzyAD@f=U#J_;$Nw+TQ|IFU7wU|2@&60;x4HQLg}T&S{Qp9|W-k7Jp^h;Z|G!Y* zmy7>jsC&!B|1Z>o<>LPr>ZEe<{|og)x%mHux|&@4|3bY5p>1`t(PoNUhyKIkM|Lie(xt~YQ>tG zpe`!aPvM55eM6)CvEG%%v6c}wx6)%|QpGiw!X5&N0#Q#BbN13GP zWlHHZN}D^G3@hM+!SM=SAOiMB00=S6EXdcktQ_8QRLI%84}M z5hs8%&4#30g1LZ{HYvG8gmE7bAt6|VWr@u&#$e2o-u*P?Fhx904s>#WdDlJ1$F2D! zEe<=A_m4qT&bz!y{Dw++D;$FWMI0XrDPZ`7hQl{1{(tk59R=`vKK}nV%l~EhKQ902 z@*j}_@R8-;TK=`=4=w-9@=q-P@bdR9f5-BB*d6dImmgiefB8$6?^-^+{LbZk`PSvP zEWcs-)yuC~e(Cac%Mp79zF_&t@-vp7vV6(%{^iA`e_HzcrT=H?ZswNFMVL?$CiF@>3f#GZRwkqzGms&OAjx7>CzW3tuCEhx}E(6-@bJ7((9JK zVCm&cpR<%K1xwc~J#Xpo($kinw6x>^2K4QpZ47KgIQ#=s+QU)*!WxkFceckRvL>Nh`AjT-yMEMPwG9u-ea(gd==o8^c!sro>BjU_Nw&%A(5;6$^^Y5dF z(6d@rrV36}9DMj=fS4E;Jpc*6yg5#pV8C@kQZC+pzN==W6m;_8d0R+NDd7U>=J!V; zGf(mQ9Us`?hYHHacJ@CrL<1!Lrd``=Q9bbkAVhw6FN|!B85H6w@yV-NC{B!{ ziBW%-GznfM#TB@APOS#n=One@u-1+PY?6WF4Hc-11wxv2@Yibsde2gN9dj;fT4ek z14Dxd$AH-vw-ZH*AM&aNyhdxsmqSdfN^vB(8!mp__u@3X6d_K^Ry*Q3L=0Rj)NZUv z@lacxx=W;};+AEf+C!vT!B$)Sh~tWa+%Tp}z6nMm?=hhgBllv|=)c z`z1GbIOgR$Ukf7#J3$*!G~^FBzzFp(F5%G z4P&_K!8?sFTxh_Svm?CBzxRmkXn$&j#coX__Wg+tc6qHfV598RyH0YR!gco^S-pLw zICJ}{S^J|FMMP2MJHEQZ+q8ja?|wmiOJ2UZ+g{T*9WIwmb~`P z;vVSZhF8BfncVQ&@TMClx80o#Uw-B;9Y67|;@EAsvt|Fl?fvggoYRK!+)9seEP?ip zcc%i}1zTDJ1t|LHW3S~ie(Ys@{?cRGBmT!9yPD4*dh98De(=kfWAk6gj$&pskk^d}y^htHpU_?>*7dH8L7{`AAI=JT5$mJ$8y z53>_p@3$U)HlM%!u!7#d^RVLA|K_0;K7Z|@jL%4_wLTsR#D+dHVi)`MmS~<9z!}kl%KXCu&^7;Mu_xb$3`>*2jd+)!D&+oZkXY+mgxm7;D=Qo}E zJU+kfoSZjbdrq78f6ck4@%feK4)FOEU&6gJ57}&by|BH(s>n*Jw{L8%`<;(WZHU_pau#JIF zQ4DOy|7T?xY{&n*@vrUpf0@%k7>%IpcKkoHm|7X+t1^7Hc=Lgr ze>TS<8$UUZRE*VAcN8a|@VOWt&$&3X{Ik7<-dFW{U$x&h0sI83{eSozk;eO8w76)b zaqp2Tqt4~lTz4HAwJ#Z5+h-D$cjt%}3OK5(qdxD(XXdy|R}r~wH}AXV-Xq=Wr;C*n z$Ifti)yd61k!GvsQG0bIJ$c4Iax6DUJ@`PC-CksOoH=`f9r2IM0?sA&bRIf;?Bwcz z`egT(ZGLn}nG_{at5OrIv z>o$6F*d@oP0)vF6qdqNZeE^jCRCRy)?Bw{d?5N5bZB^~KWJ{m!)>P?TTS4~~goRc~sd43%akY87m zdpK~ujd%Bo!v;9tG|YM-q5@_{Z)Z>E>1rYRy;wcgWLS4xm#snS>2#$yxw@&U9!ih8 zdd;=SHZP7BpMU{1$?o{ElXqN9jJ8Q;-s&>6R8!rQm<`VE$TZ(&hd&#_w~fU znc`$#th8D-4I^lA6Kbs<1%kT!8S8g)_MVmWguAgmn>(G}Lq2qaK+M0gW<9PHSpjdI zSrcXT>mNJ2u=_b*d!ttvom=K{P#b4!T896r0klD>D|XpoIP>$%-PeYk2mzV z)WOwU8`k6o-G1DVP)n@!urNuFYe6?AX6>-Dvnz;w~+iCvS9XU3PK)7^LDRUa_k#ahY(2#DZTe~5a zMs>AM5u+-ZI$GSlTAY+| zszKax-`jTHct*eQzRx>Uay`9b(zx-o>V`Qy$~%`4{x9#mZRZXDUB8O|Kfjmt7K_C% zSbWLilQ^;O&vyRm&iC#7ik&Mv-@Nm)c3!n}_rhN<{NBR*7v8h*FBkHKS1w$;aQTkE z-|?qAer3n^?|Ao)J9pf?<1=?0-m$CqSG|w+eyaD)y?;S-&7Uh@xa(@VMX_AvL~(wi z5Qy&9@0Bm;-U_N)&#!OkA%3b`&+Fcbs#{lgZ`FN0w|lGZ>p7iUQQg;(?yb77tGc)9 zz7BV9)qOp?x;2?}#`Uc3t-7ygc5l^vJ)?U|eZ5P+S3bRS%f{tyJ*|67eYsm#c5kUK zck8L$TXkPo)VJKQghTh#Q@Xe6zMkB@RrmFz?yb77%R9Fk*8!0SrDHA;8aGg5my8?w?HykDH^E ze?1kgxcl_6l@1K6$4(SSPoF~IAC*h30rW~SJvuplD!b$8$+Hce+{_*CfOQ&2UGX$u zk2S0w)ouIbq_dqYR*#;Q=sxq=AD0An^$Rby+1em-+lcQte3)_R+zT>|LQ=}J)?b@SX9y(?&w%%&kXe2 z#nr?0S96|{X}`vG&>+K&%=)sbK!n;lvbz9oFqsY0Yea&+^6O)%NBg*_0<`>M$aS?r0(-9E?MaNGnt zr}I!%L**CJHv1@@PutofM2GHZ1I-j5rWGXhSU9d^&R95Vi9-a{pT;?BESw;_E}qzH ze@@$Wl9;uv=4Z7{cFWe=W^Np&w%Yo-Gt8-i=Qj`MML$f}hOIlwWN@M_=84UI*yOUL zR!)WHxNtTO(qYcp(1@*zRM+Z@k~u+gOK%^Z}xuP6Stzw@W zVC1f7nWNGDm1d0XTY-m$0EjbK`gCFIhQn$&l-v^T4}&k1>J=#-P+>BMSq*U>3^|M(|Dwn@^KK5U^+JvVZJ zs;8h#HhAMe1gk43In%(cVSk5F`7xF_C?Ro>c4;T5AmymZ244_v{5_Q$y*FUg2&!_O z!tP);TyT$3cW0f`8rgtV+gEYFraAYGEw*W`3!A@3eU1BZ;aM8l`fXP>yE|?PxI#k`7kBSqpkY>WpNmXEFzMNVY zGi-8vVVGIJ^^!-fgZFyg`x8!7I$w@e;>z)(fsToRE{w^7+VQ{%~NJ` zM?L8zXw4^eTpwlXv7)E-`6P0}L&rDH1ob?KQVo6v2hu*+wkc&LcQgvaRd?00i8W`v zfqS};4khUPjMD!4oQQqR06;}v0}k+|H<%EIc=jVs zfOD{z?5yZpa$>~BfhHlSXEV+DbE?c4ioVwdHBqM?4I2#Fnkbat z^WW)hT49ZHqvqC_JPiRQhx~aO5(I*jGUM+YWG*a~{M~(XGAry-3Oc3nE}r-GfJY!X zcr+`Ga)XBhoIJ(UeXxbN@HV4$<6W&*YMN=cF4{tGnAI&pvvDLLs0Byx}vSFQvEj@GnmoW0Q+D=v>zpECw^@Ug8pVM@KAhoYf! zrC&})WBqc?2C&+wRBM?Pq4S9coJ-02LdtOdZqOG6X_;}?{;Jf~=MN_rP-;(?I;LCy z(zV#RQ2_8?6var@x>1u>ubXq-)fMN$f<4#(pBk0Q{EAa6Qb?^84a_9( z*6Ce*tim`?*Z5}#O5-D5PDUKcU{ICZ0iH;G$)kD%^kcOsszyZ_8yXv(`)}4?>%O2c z{L`t2r*+@26Q0+%HP2h0%$?xTjPO^p$BC#m0<+9pV`ZMWUyjwyp`TqnubEQ(f9GP} z+x69p|G=N^pKT0mV_+Kt7Zn5Np0sz*bIu*Q;@lp$A+9AQUFgZRmVd@Jwx>wsvSx7- zSJ{!*9g!e%KiBHdVYWM=g5^eD=l&kOG8em=MaknnJ?cO6kL$8LdlPB4P zuwR3C-`AbkZRZob?*;?vLeIvXcRqpNlXz3d6NKw*k#E4W&D#5VN_UWU9p0pWv)i=j(`fbqJuAK8!we7iWj;`&w@gmwL+nndbcI_Ml zjnOmVJLk`{y?wp${~*N1omGq@9Lm{kmG0BARj0=6oGBlZ5ws4Lo-8O zss<9B^c!HLg?(-1j@PvHY`=O|Ta>GQ<5>=v;C!U2e$pQc2Rhcl9ok^yn$artgdf$_ zaT_)>Mst{ocEB@7B~+~f{_OeS81DMWj>dd!a-Kqtu420m&4Ggk{OGU`j@RH@qdmr$ zG_Q?9?C=GfPqi?k2I!p$9c`}}JI`}4Y!@T0ADdUB7ck{E(1)^svnqq;=C78#RhJ_- zj0(EPpV+4HwtYOaYx9DOW>YJL+E5H9|IQw*4ygbF2QR#ZSq4`vn6zdt6n@d1=UtoN zZX6`WN_gVI#ql7}nH`8?Joxdzl#a@)`T%F^;ojDTt>MRK*5I00b?`pp*6YL4rWxqi z_{FBE+bEg!JY~6X+=c2-KBMZoRcqL( zRb97gjXJfe>sBp!Y??h**R5LPPOa*?RV(e(s;*nLCY@TsGD2Q>(ge z)#41wR$J9|s}`9LtysGCbo_ww9 zx>c*9Cts_&Zq=&j$=9l`TeT{B^0lh#R;`Mje68xbRjZ;WU#q%q*1{d$n#cOORjZ;W z*H%znw`x`NF(J!L9#FoSp?NBW|qjpV30c?W6C`J+K|u8mfNVVC=& z8LzMO&92mfleR*Uq_2f5l76?qGG`uNk(I_BBlSn-*v|W@@&%Rp$)OOzp3nyq|m~_QBPNP(hajdlOOFRVV!G&^YT(*dV zIqT6JrZHDfgAG3aJZE1+sY_>8JdE|u3A8txkeg)2e$cWsJ>#cf=4 zNvkVycd96yC}t!MM5Mf0(ey1|=vE`xrcRlGA^)5(q^M?@SI3BK)-48UtJ0!apv+~< zS$Z7cN|u+DYNM*gLTh*-1f}FMgK+gcHPLP;E@IR)XRZ2}B~Xd}#{8~btEm4r|H6o7 z(OVIgMUi)@Zt#ZfbL@GH7&(`sLs75x0!Now;F5g*+ zDlv=;N7f-!r?0|4GwxP9!T7*p;g%~hwIApe2Il)^q=K1DlEUBZSCQ{BD@1khABsY( z?TVMH9ZJMEjki?2MFxeyk%YmUB%Cl3#l@lV8gA97IDj9S%J^lCRP}_}L6m?YQL*mo zulSYE3+BGo6ove+Z7DPp*G7L@tqnctYG{_+dXol2A-^iC=aKvdlZZy2wlcK}-5k5f zzm1V+Y;^%ofi$rhuy!=Vx}%m8dtNi?Mq-dQ;4Y=JUkl~Pc(NW3>$EW*#-zKx4Tl$H z8OYbysHZyM-e(#xM>>dhe!j1Z&o?Q!gy54eKXhyq;W+boj6ymzc&syeYN}#oVgE0a zG1VVH+fF79;e5s-MD3E`Hkaj!9(eBF_73Q2o$TsoiWM^%)6K_Ueh#F8a!RzQ)X|_m zBb5e8I*?v!cWbHVR^xgy&8<+{fzW8HxpFL5Tv;gdHCait`La!v8CSB6_2^}rbZi=J z$u>BXmu+CXCEMVuUJu1r%6f2FI!?A}7TbEL&!4sRruA9p)7DqQpj`<*KdZXduXMy& zvQ6YEy`a=UyHGNq$6K~dMYzLKTa)(6ue3LEm)5=$KvmP=DxU4ABC*6;{~ig>KJY%_ zT@pqn4kdlT>M=Rvu2m&w)SpHH8jBige`C-qml`y#4siti_h2fj4YtvS>77Jr4SElP~*&tmX_{#iZ2Z4=E`t3jwOlb_# z*VL=iPGk$|O7NGwPCe;qtLZo<&kBPYgR~9tQ1m9}PhdrtRMf>niARm2>Vvi`MT%{rQSrRm0pDvj2oxXit*Q~Nr;3JebZVI+)WJ0Di=Q|@+zz8;|f_; z$)s73)pMsTZ6`mJvTvs2THu54|x%4IRbU5t)EwPD+pRFMFy4R-sXku-4m=rDfCZ`Nl) zEW|hm6Pd%eHP2Q9C=o(@=7WLk7QQ!YBz={-8kn`T!FNVfwlQ&b=qJh#0p)qTdfBqA zK|#Tv!WtAyQE#-AJ}lr>TzmkzZgW5amIKEkY_>|?7F_~a3LRQ8djgNbt zFJWK#hqT(f4$r!_C$-9lEFW=6q@tG-rJUAE#`aZtEo%jrhs}6v#n;f^RQ~cHXZ>%hYJ;CedS#RVvvRj^U+!C!g{W>hgf5>5xR1)By^Vk_r^Sy)QdUnDbMM& zlp8cgEADL!`qUV#?P?y(qwjjEFsnXow1U<1Qc6t9lzxqp^3=byMbjG_-y9~SOR~1W zNA7Jm;9?{|99-Vv&aAKpKT0mV_+Kt+ZcE} zF>wDM9^JL~igSma{?PlLbFotVwITbx)poZ2orCh+q4PhWY>ZKY;fs95w;=xuTHvVQ zU;D}(`GwS`hUyml#S?)qLM6nP>VImnm6cRd8LJs~K{HdNpKRS;^#u6=i}#6akJvwGZDoqj;FbAXzl zO!)%jDcfEd&}8IGu_?=u-i^F%vLk&KKRKUd_>t?UtVa?kTq^@*+FJgpGVSMNJI{TS zfR^yJ_?DEdU$-K6r0sZXfL@v@#0l1$WjUrX?VZija3Y zN*V%ATU)bcD6(FnA`asz^@ekJLiMfz+P(t%>S}F2jG@NIysYz@hHRy@-c{;A!wP{q z$lZtj8?;*28bu*+8{}?{kV|zwV^LkTfGb}kq>X4y39B+9=b*YfruU40;(1!N&_k;p zbv0L3pLG?D!KBEKmz^_uu%d|R6{!uzC>NIoLjX8KPD76p^86j{$-2#;(?!M=Jwo!YMvZM-7yh zbl_^RyPn35AJ;{a0iCCL4EgO^)3tEyVsDHdPneIrdH)z11a*X)IvOY9Av#Blo|d?y z8Wi{v5226CjqyAimhCPO8&yc}@ZMq%yPls`9^(>XHLSK>98=Hw=pwbtIzVqeI?MZW z6wO`3pY8Z3icySspnHKlb_F9=Mx$Xp2hWzZQ@wjoK4tZU;dVWhpuezs5cQ%?H>Xen ziV^F}$~2!OC73tSR;94w=bg?qD$DMHwm>0QWv&+PV~0=h@8If}@L0XTBJmo9t?@F` z#{8o4`ML~`J9iz4azuZaz3zDs>$5)$YhX;iS#d4RoO+-J9%joA zQ4d<%6<%YvJ0YdOtdTWn%+pycTC#-1PYVtO7O_}tc4qN&*9#rT`2B}a!-A7^wq8cz_8C4E8L;ffytt#l2RnrUVYS?qK) zyJxbr%-RwUJJ0p$=UppHYpUdnf@3|tsKgqh84-!+i`I8%4AKE7gt{tM9p7%5=?6$@!ZnOJ;iY+P5lwvw{@9`*U&WRmZV+t z)(!J++}ugcGPB*}D07QhGOth5k(A}l99TNfRFPg$5!X+V-J zI1=4%Z7v^;mW}?LR<;2>;-V<^ZJUozS9-+kf86L%bn^tF67*ovo%qWjlQL^pkZp@@ zJDivLwuZ`VQDwZB8f}ZNsfYc48S6=T4lEw1QMTE37LE8dT1hLb2hwm^G~DyLK#5_? zAS}zURW&* zRgd_`y)01Ma6@M`*SfQpelv1ebf<1xSJt8%0}$F63?AKc8B2m5Ey?c`y2)G?)5Im6 zOyk26%S;A1pT*{KS+o`;dFywUBo$15^f^6(jQ3s7XD^Ehx&xRcN{F1OP7pA;?qrVT z@6xd#SNqL(JvEb(uJS;*U2eU^EVX1r3-sjc81FK>U+vU3HV=#Uw7fN<63a!h5wkxG zl_VXBU9{dkY2?k}?w742N?QNj6U|-Y9ntFD6KU)`Ut6lwV=ef1Pr$~SRRP`E6PCwR z;>TL==DOhq7oPl_)2_RwuBhH!2`hG`7Tj8@C|(l2eD?&@*}ACS29_6~;Q?@%Ev*;f z(Me60m1w*UW^eNd6_y%udvwa=YrZut>bG8@T(5WVr0E_0t!d%hy*16Z_FK~)GxErJ zJz1E@&&*EC z?A=j6`_dZ4N3@p3Vf19VKE-b=d)4V+S`p5hOGQ_oRjJ1`%SP2|gnq0RMa{A&{7%9C zJu*g6?{8t_sYX%aN^(PMTK`ohtX2}jF=e)??|9;WnJE2be!troq6xyEfXvrGsd+> zStT(_J|=&;X{+nJkB^4ekRRLcE;i){lvKT!FS_^yzq?o<`H=)J=LmVU_I)mU!xjyI ztO27X_j3)=Hf!?s7iWc?M>R$IOUNs9badHK=~H=5jB--hx_Ge z3T335yXI>$z1C5(d`*qFhD{44!b44uLmw%FzGuZiq(sUCUB*DXbX9tUtqI*PtwL6* zlMYOT^A=FjeW#c?qU*KqCS(6!ZrV6A*u1OLRBBrXRsc5G@>ZULSvtGcoayY9E}$!( z8F_2i?y8J_NY|2%AbGz=gV&37MVd_}Z0f`P`8vEfA39jTUeQj67uyi$u(y}j*U^<{ z;B<7pZAM2=$qC%n(VvFP9Vf;`7?aka*blrn_ITY9$oCdt(_W0SyI zz23svq>iq>OZ{1`@w-;nuJ!gxM`tHdSj(o?7cmseQ_*|f@`Jd&o`AF%W!ZQfhUHl~ z#n4?Fo@!ZH@)NjKqb2zLB+wIsarGX&@F6)s{vdscZXHK+C*`R-M%$(XQAaH!)VHr!Qmw%xUzyQx1)*C5wq~lHJcLGVGa>Ze6hWis+mLuTJ*BS? z=Nfnn)6jz(t6f=|4ntGcURHV%xZj?C=2K;!@|wIFg-^a!y9>2eqb#3qO3!(`T5Wq? zEj;VWV#^*=Hvd2^it;)UGf?A($7_!VcMLbwtY85|=P@at_T%V0=Vi-`=YWWIJ-&oJ z_%!(Hyq9gGD7`ohX)xCQ39?t`?P%g8msYCSpLm%t<>*blxgb_a`BtwQn@CPN=)~<) z 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

{$)q_mtF~(e^-#UW&W)k;d6vr{_gdcPe1Z# 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 c`ZLg Date: Thu, 24 May 2018 22:12:45 +0200 Subject: [PATCH 022/294] sql improvements --- plugins/sql_db_plugin/db/accounts_table.cpp | 8 ++- plugins/sql_db_plugin/db/actions_table.cpp | 56 ++++++++++++++----- plugins/sql_db_plugin/db/actions_table.h | 4 ++ plugins/sql_db_plugin/db/blocks_table.cpp | 9 +-- plugins/sql_db_plugin/db/database.cpp | 6 +- .../sql_db_plugin/db/transactions_table.cpp | 8 +-- plugins/sql_db_plugin/sql_db_plugin.cpp | 3 +- 7 files changed, 70 insertions(+), 24 deletions(-) diff --git a/plugins/sql_db_plugin/db/accounts_table.cpp b/plugins/sql_db_plugin/db/accounts_table.cpp index 8f8f767d948..72b97ce20e2 100644 --- a/plugins/sql_db_plugin/db/accounts_table.cpp +++ b/plugins/sql_db_plugin/db/accounts_table.cpp @@ -13,7 +13,8 @@ accounts_table::accounts_table(std::shared_ptr session): void accounts_table::drop() { try { - *m_session << "DROP TABLE accounts"; + *m_session << "DROP TABLE IF EXISTS accounts_keys"; + *m_session << "DROP TABLE IF EXISTS accounts"; } catch(std::exception& e){ wlog(e.what()); @@ -27,6 +28,11 @@ void accounts_table::create() "abi JSON DEFAULT NULL," "created_at DATETIME DEFAULT NOW()," "updated_at DATETIME DEFAULT NOW())"; + + *m_session << "CREATE TABLE accounts_keys(" + "account VARCHAR(13)," + "public_key VARCHAR(255)," + "permission VARCHAR(13), FOREIGN KEY (account) REFERENCES accounts(name))"; } void accounts_table::add(string name) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 54f565a3feb..a184b8032f6 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -13,9 +13,9 @@ actions_table::actions_table(std::shared_ptr session): void actions_table::drop() { try { - *m_session << "drop table actions"; - *m_session << "drop table tokens"; - *m_session << "drop table actions_accounts"; + *m_session << "drop table IF EXISTS actions_accounts"; + *m_session << "drop table IF EXISTS tokens"; + *m_session << "drop table IF EXISTS actions"; } catch(std::exception& e){ wlog(e.what()); @@ -25,22 +25,25 @@ void actions_table::drop() void actions_table::create() { *m_session << "CREATE TABLE actions(" - "id INT NOT NULL AUTO_INCREMENT," + "id VARCHAR(36) PRIMARY KEY," "account VARCHAR(13)," "transaction_id VARCHAR(64)," - "name VARCHAR(18)," - "data JSON, PRIMARY KEY (id))"; + "name VARCHAR(13)," + "data JSON, FOREIGN KEY (transaction_id) REFERENCES transactions(id)," + "FOREIGN KEY (account) REFERENCES accounts(name))"; *m_session << "CREATE TABLE actions_accounts(" - "account VARCHAR(13)," - "action_id INT)"; + "actor VARCHAR(13)," + "permission VARCHAR(13)," + "action_id VARCHAR(36), FOREIGN KEY (action_id) REFERENCES actions(id)," + "FOREIGN KEY (actor) REFERENCES accounts(name))"; // TODO: move to own class *m_session << "CREATE TABLE tokens(" "account VARCHAR(13)," "symbol VARCHAR(10)," "amount FLOAT," - "staked FLOAT)"; // NOT WORKING VERY GOOD float issue + "staked FLOAT, FOREIGN KEY (account) REFERENCES accounts(name))"; // NOT WORKING VERY GOOD float issue } void actions_table::add(chain::action action, chain::transaction_id_type transaction_id) @@ -67,14 +70,24 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac auto abi_data = abis.binary_to_variant(abis.get_action_type(action.name), action.data); string json = fc::json::to_string(abi_data); - // TODO: insert actions_accounts + boost::uuids::random_generator gen; + boost::uuids::uuid id = gen(); + std::string action_id = boost::uuids::to_string(id); - *m_session << "INSERT INTO actions(account, name, data, transaction_id) VALUES (:ac, :na, :da, :ti) ", + *m_session << "INSERT INTO actions(id, account, name, data, transaction_id) VALUES (:id, :ac, :na, :da, :ti) ", + soci::use(action_id), soci::use(action.account.to_string()), soci::use(action.name.to_string()), soci::use(json), soci::use(transaction_id_str); + for (const auto& auth : action.authorization) { + *m_session << "INSERT INTO actions_accounts(action_id, actor, permission) VALUES (:id, :ac, :pe) ", + soci::use(action_id), + soci::use(auth.actor.to_string()), + soci::use(auth.permission.to_string()); + } + // TODO: move all if (action.name == N(issue)) { @@ -143,14 +156,31 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac soci::use(action_data.account.to_string()); } else if (action.name == chain::newaccount::get_name()) { - // TODO: store public keys auto action_data = action.data_as(); *m_session << "INSERT INTO accounts (name) VALUES (:name)", soci::use(action_data.name.to_string()); + for (const auto& key_owner : action_data.owner.keys) { + string permission_owner = "owner"; + string public_key_owner = static_cast(key_owner.key); + *m_session << "INSERT INTO accounts_keys(account, public_key, permission) VALUES (:ac, :ke, :pe) ", + soci::use(action_data.name.to_string()), + soci::use(public_key_owner), + soci::use(permission_owner); + } + + for (const auto& key_active : action_data.active.keys) { + string permission_active = "active"; + string public_key_active = static_cast(key_active.key); + *m_session << "INSERT INTO accounts_keys(account, public_key, permission) VALUES (:ac, :ke, :pe) ", + soci::use(action_data.name.to_string()), + soci::use(public_key_active), + soci::use(permission_active); + } + } - // TODO: catch issue (tokens) // public keys creation + // TODO: catch issue (tokens) // public keys update // stake / voting } } // namespace diff --git a/plugins/sql_db_plugin/db/actions_table.h b/plugins/sql_db_plugin/db/actions_table.h index 67ea96831cf..1a6113c9d02 100644 --- a/plugins/sql_db_plugin/db/actions_table.h +++ b/plugins/sql_db_plugin/db/actions_table.h @@ -8,6 +8,10 @@ #include #include +#include +#include +#include + #include #include #include diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index 78312f3d2c3..ae5fba56243 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -13,7 +13,7 @@ blocks_table::blocks_table(std::shared_ptr session): void blocks_table::drop() { try { - *m_session << "DROP TABLE blocks"; + *m_session << "DROP TABLE IF EXISTS blocks"; } catch(std::exception& e){ wlog(e.what()); @@ -24,12 +24,13 @@ void blocks_table::create() { *m_session << "CREATE TABLE blocks(" "id VARCHAR(64) PRIMARY KEY," - "block_number INT," + "block_number INT NOT NULL AUTO_INCREMENT," "prev_block_id VARCHAR(64)," + "irreversible TINYINT(1) DEFAULT 0," "timestamp DATETIME DEFAULT NOW()," "transaction_merkle_root VARCHAR(64)," - "producer VARCHAR(18)," - "confirmed INT)"; + "producer VARCHAR(13)," + "confirmed INT, FOREIGN KEY (producer) REFERENCES accounts(name), UNIQUE KEY block_number (block_number))"; } void blocks_table::add(chain::signed_block_ptr block) diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index dac1e44422e..10ab77afff7 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -6,9 +6,9 @@ database::database(const std::string &uri) { m_session = std::make_shared(uri); m_accounts_table = std::make_unique(m_session); + m_blocks_table = std::make_unique(m_session); m_transactions_table = std::make_unique(m_session); m_actions_table = std::make_unique(m_session); - m_blocks_table = std::make_unique(m_session); system_account = chain::name(chain::config::system_account_name).to_string(); } @@ -29,11 +29,15 @@ void database::consume(const std::vector &blocks) void database::wipe() { + *m_session << "SET foreign_key_checks = 0;"; + m_actions_table->drop(); m_transactions_table->drop(); m_blocks_table->drop(); m_accounts_table->drop(); + *m_session << "SET foreign_key_checks = 1;"; + m_accounts_table->create(); m_blocks_table->create(); m_transactions_table->create(); diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index 1f4abcbce67..335d3765424 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -14,7 +14,7 @@ transactions_table::transactions_table(std::shared_ptr session): void transactions_table::drop() { try { - *m_session << "DROP TABLE transactions"; + *m_session << "DROP TABLE IF EXISTS transactions"; } catch(std::exception& e){ wlog(e.what()); @@ -25,12 +25,12 @@ void transactions_table::create() { *m_session << "CREATE TABLE transactions(" "id VARCHAR(64) PRIMARY KEY," - "block_id VARCHAR(64)," + "block_id INT NOT NULL," "ref_block_prefix INT," "expiration DATETIME DEFAULT NOW()," - "pending INT," + "pending TINYINT(1)," "created_at DATETIME DEFAULT NOW()," - "updated_at DATETIME DEFAULT NOW())"; + "updated_at DATETIME DEFAULT NOW(), FOREIGN KEY (block_id) REFERENCES blocks(block_number))"; } void transactions_table::add(chain::transaction transaction) diff --git a/plugins/sql_db_plugin/sql_db_plugin.cpp b/plugins/sql_db_plugin/sql_db_plugin.cpp index aee0f796062..91bdeda364b 100644 --- a/plugins/sql_db_plugin/sql_db_plugin.cpp +++ b/plugins/sql_db_plugin/sql_db_plugin.cpp @@ -59,7 +59,8 @@ void sql_db_plugin::plugin_initialize(const variables_map& options) chain_plugin* chain_plug = app().find_plugin(); FC_ASSERT(chain_plug); auto& chain = chain_plug->chain(); - chain.irreversible_block.connect([=](const chain::block_state_ptr& b) {m_irreversible_block_consumer->push(b);}); + // TODO: check irreversible to update info + chain.accepted_block.connect([=](const chain::block_state_ptr& b) {m_irreversible_block_consumer->push(b);}); //m_irreversible_block_consumer = std::make_unique>(std::make_unique(db)); //m_irreversible_block_connection.emplace(chain.irreversible_block.connect([=](const chain::block_state_ptr& b) {m_irreversible_block_consumer->push(b);})); From 5b05dc2b9cadd4a99ffcb0d204f920113e2e4805 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Thu, 24 May 2018 23:19:14 +0200 Subject: [PATCH 023/294] varchar name --- plugins/sql_db_plugin/db/accounts_table.cpp | 8 ++++---- plugins/sql_db_plugin/db/actions_table.cpp | 8 ++++---- plugins/sql_db_plugin/db/blocks_table.cpp | 2 +- .../include/eosio/sql_db_plugin/sql_db_plugin.hpp | 2 +- plugins/sql_db_plugin/sql_db_plugin.cpp | 7 ++----- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/plugins/sql_db_plugin/db/accounts_table.cpp b/plugins/sql_db_plugin/db/accounts_table.cpp index 72b97ce20e2..34f40c19e6f 100644 --- a/plugins/sql_db_plugin/db/accounts_table.cpp +++ b/plugins/sql_db_plugin/db/accounts_table.cpp @@ -24,15 +24,15 @@ void accounts_table::drop() void accounts_table::create() { *m_session << "CREATE TABLE accounts(" - "name VARCHAR(13) PRIMARY KEY," + "name VARCHAR(12) PRIMARY KEY," "abi JSON DEFAULT NULL," "created_at DATETIME DEFAULT NOW()," "updated_at DATETIME DEFAULT NOW())"; *m_session << "CREATE TABLE accounts_keys(" - "account VARCHAR(13)," - "public_key VARCHAR(255)," - "permission VARCHAR(13), FOREIGN KEY (account) REFERENCES accounts(name))"; + "account VARCHAR(12)," + "public_key VARCHAR(53)," + "permission VARCHAR(12), FOREIGN KEY (account) REFERENCES accounts(name))"; } void accounts_table::add(string name) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index a184b8032f6..6534a526cc0 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -26,15 +26,15 @@ void actions_table::create() { *m_session << "CREATE TABLE actions(" "id VARCHAR(36) PRIMARY KEY," - "account VARCHAR(13)," + "account VARCHAR(12)," "transaction_id VARCHAR(64)," - "name VARCHAR(13)," + "name VARCHAR(12)," "data JSON, FOREIGN KEY (transaction_id) REFERENCES transactions(id)," "FOREIGN KEY (account) REFERENCES accounts(name))"; *m_session << "CREATE TABLE actions_accounts(" - "actor VARCHAR(13)," - "permission VARCHAR(13)," + "actor VARCHAR(12)," + "permission VARCHAR(12)," "action_id VARCHAR(36), FOREIGN KEY (action_id) REFERENCES actions(id)," "FOREIGN KEY (actor) REFERENCES accounts(name))"; diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index ae5fba56243..9ab54a3b2ac 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -29,7 +29,7 @@ void blocks_table::create() "irreversible TINYINT(1) DEFAULT 0," "timestamp DATETIME DEFAULT NOW()," "transaction_merkle_root VARCHAR(64)," - "producer VARCHAR(13)," + "producer VARCHAR(12)," "confirmed INT, FOREIGN KEY (producer) REFERENCES accounts(name), UNIQUE KEY block_number (block_number))"; } diff --git a/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp b/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp index fedc2306567..4ddb968dfa5 100644 --- a/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp +++ b/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp @@ -45,7 +45,7 @@ class sql_db_plugin final : public plugin { private: std::unique_ptr> m_irreversible_block_consumer; - //fc::optional m_irreversible_block_connection; + fc::optional m_irreversible_block_connection; //consumer m_block_consumer; }; diff --git a/plugins/sql_db_plugin/sql_db_plugin.cpp b/plugins/sql_db_plugin/sql_db_plugin.cpp index 91bdeda364b..4c2d5103327 100644 --- a/plugins/sql_db_plugin/sql_db_plugin.cpp +++ b/plugins/sql_db_plugin/sql_db_plugin.cpp @@ -60,10 +60,7 @@ void sql_db_plugin::plugin_initialize(const variables_map& options) FC_ASSERT(chain_plug); auto& chain = chain_plug->chain(); // TODO: check irreversible to update info - chain.accepted_block.connect([=](const chain::block_state_ptr& b) {m_irreversible_block_consumer->push(b);}); - - //m_irreversible_block_consumer = std::make_unique>(std::make_unique(db)); - //m_irreversible_block_connection.emplace(chain.irreversible_block.connect([=](const chain::block_state_ptr& b) {m_irreversible_block_consumer->push(b);})); + m_irreversible_block_connection.emplace(chain.accepted_block.connect([=](const chain::block_state_ptr& b) {m_irreversible_block_consumer->push(b);})); } void sql_db_plugin::plugin_startup() @@ -74,7 +71,7 @@ void sql_db_plugin::plugin_startup() void sql_db_plugin::plugin_shutdown() { ilog("shutdown"); - // m_irreversible_block_connection.reset(); + m_irreversible_block_connection.reset(); } } // namespace eosio From 88e17f87761e07aacaacb09b3a752a9980c7cb1c Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Tue, 29 May 2018 21:00:51 +0200 Subject: [PATCH 024/294] db --- plugins/sql_db_plugin/db/blocks_table.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index 9ab54a3b2ac..12e20a9db8c 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -29,7 +29,9 @@ void blocks_table::create() "irreversible TINYINT(1) DEFAULT 0," "timestamp DATETIME DEFAULT NOW()," "transaction_merkle_root VARCHAR(64)," + "action_merkle_root VARCHAR(64)," "producer VARCHAR(12)," + "num_transactions INT," "confirmed INT, FOREIGN KEY (producer) REFERENCES accounts(name), UNIQUE KEY block_number (block_number))"; } @@ -38,17 +40,21 @@ void blocks_table::add(chain::signed_block_ptr block) const auto block_id_str = block->id().str(); const auto previous_block_id_str = block->previous.str(); const auto transaction_mroot_str = block->transaction_mroot.str(); + const auto action_mroot_str = block->action_mroot.str(); const auto timestamp = std::chrono::seconds{block->timestamp.operator fc::time_point().sec_since_epoch()}.count(); + const auto num_transactions = (int)block->transactions.size(); - *m_session << "INSERT INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root," - "producer, confirmed) VALUES (:id, :in, :pb, FROM_UNIXTIME(:ti), :tr, :pa, :pe)", + *m_session << "INSERT INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root, action_merkle_root," + "producer, confirmed, num_transactions) VALUES (:id, :in, :pb, FROM_UNIXTIME(:ti), :tr, :ar, :pa, :pe, :nt)", soci::use(block_id_str), soci::use(block->block_num()), soci::use(previous_block_id_str), soci::use(timestamp), soci::use(transaction_mroot_str), + soci::use(action_mroot_str), soci::use(block->producer.to_string()), - soci::use(block->confirmed); + soci::use(block->confirmed), + soci::use(num_transactions); } } // namespace From 6e62061a1b692a392d5ec2a40e86a859d48bd804 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Tue, 29 May 2018 21:18:38 +0200 Subject: [PATCH 025/294] db improvements --- plugins/sql_db_plugin/db/actions_table.cpp | 20 ++++++++++++++----- plugins/sql_db_plugin/db/blocks_table.cpp | 2 +- .../sql_db_plugin/db/transactions_table.cpp | 7 ++++--- .../eosio/sql_db_plugin/sql_db_plugin.hpp | 7 +++---- plugins/sql_db_plugin/sql_db_plugin.cpp | 6 +++--- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 6534a526cc0..7d44be757c6 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -91,8 +91,13 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac // TODO: move all if (action.name == N(issue)) { - auto to_name = abi_data["to"].as().to_string(); - auto asset_quantity = abi_data["quantity"].as(); + try { + auto to_name = abi_data["to"].as().to_string(); + auto asset_quantity = abi_data["quantity"].as(); + } catch (...) { + return; + } + int exist; *m_session << "SELECT COUNT(*) FROM tokens WHERE account = :ac AND symbol = :sy", @@ -114,9 +119,14 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac if (action.name == N(transfer)) { - auto from_name = abi_data["from"].as().to_string(); - auto to_name = abi_data["to"].as().to_string(); - auto asset_quantity = abi_data["quantity"].as(); + try { + auto from_name = abi_data["from"].as().to_string(); + auto to_name = abi_data["to"].as().to_string(); + auto asset_quantity = abi_data["quantity"].as(); + } catch (...) { + return; + } + int exist; *m_session << "SELECT COUNT(*) FROM tokens WHERE account = :ac AND symbol = :sy", diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index 12e20a9db8c..8a1ff275058 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -31,7 +31,7 @@ void blocks_table::create() "transaction_merkle_root VARCHAR(64)," "action_merkle_root VARCHAR(64)," "producer VARCHAR(12)," - "num_transactions INT," + "num_transactions INT DEFAULT 0," "confirmed INT, FOREIGN KEY (producer) REFERENCES accounts(name), UNIQUE KEY block_number (block_number))"; } diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index 335d3765424..8cbfb4ec440 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -30,6 +30,7 @@ void transactions_table::create() "expiration DATETIME DEFAULT NOW()," "pending TINYINT(1)," "created_at DATETIME DEFAULT NOW()," + "num_actions INT DEFAULT 0," "updated_at DATETIME DEFAULT NOW(), FOREIGN KEY (block_id) REFERENCES blocks(block_number))"; } @@ -37,16 +38,16 @@ void transactions_table::add(chain::transaction transaction) { const auto transaction_id_str = transaction.id().str(); const auto expiration = std::chrono::seconds{transaction.expiration.sec_since_epoch()}.count(); - *m_session << "INSERT INTO transactions(id, block_id, ref_block_prefix," - "expiration, pending, created_at, updated_at) VALUES (:id, :bi, :rb, FROM_UNIXTIME(:ex), :pe, FROM_UNIXTIME(:ca), FROM_UNIXTIME(:ua))", + "expiration, pending, created_at, updated_at, num_actions) VALUES (:id, :bi, :rb, FROM_UNIXTIME(:ex), :pe, FROM_UNIXTIME(:ca), FROM_UNIXTIME(:ua), :na)", soci::use(transaction_id_str), soci::use(transaction.ref_block_num), soci::use(transaction.ref_block_prefix), soci::use(expiration), soci::use(0), soci::use(expiration), - soci::use(expiration); + soci::use(expiration), + soci::use(transaction.total_actions()); } } // namespace diff --git a/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp b/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp index 4ddb968dfa5..c81f825d0c7 100644 --- a/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp +++ b/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp @@ -26,7 +26,7 @@ namespace eosio { * See data dictionary (DB Schema Definition - EOS API) for description of SQL DB schema. * * The goal ultimately is for all chainbase data to be mirrored in SQL DB via a delayed node processing - * irreversible blocks. Currently, only Blocks, Transactions, Messages, and Account balance it mirrored. + * blocks. Currently, only Blocks, Transactions, Messages, and Account balance it mirrored. * Chainbase is being rewritten to be multi-threaded. Once chainbase is stable, integration directly with * a mirror database approach can be followed removing the need for the direct processing of Blocks employed * with this implementation. @@ -44,9 +44,8 @@ class sql_db_plugin final : public plugin { void plugin_shutdown(); private: - std::unique_ptr> m_irreversible_block_consumer; - fc::optional m_irreversible_block_connection; - //consumer m_block_consumer; + std::unique_ptr> m_block_consumer; + fc::optional m_block_connection; }; } diff --git a/plugins/sql_db_plugin/sql_db_plugin.cpp b/plugins/sql_db_plugin/sql_db_plugin.cpp index 7a540339e49..e533067a883 100644 --- a/plugins/sql_db_plugin/sql_db_plugin.cpp +++ b/plugins/sql_db_plugin/sql_db_plugin.cpp @@ -56,13 +56,13 @@ void sql_db_plugin::plugin_initialize(const variables_map& options) db->wipe(); } - m_irreversible_block_consumer = std::make_unique>(std::move(db)); + m_block_consumer = std::make_unique>(std::move(db)); chain_plugin* chain_plug = app().find_plugin(); FC_ASSERT(chain_plug); auto& chain = chain_plug->chain(); // TODO: check irreversible to update info - m_irreversible_block_connection.emplace(chain.accepted_block.connect([=](const chain::block_state_ptr& b) {m_irreversible_block_consumer->push(b);})); + m_block_connection.emplace(chain.accepted_block.connect([=](const chain::block_state_ptr& b) {m_block_consumer->push(b);})); } void sql_db_plugin::plugin_startup() @@ -73,7 +73,7 @@ void sql_db_plugin::plugin_startup() void sql_db_plugin::plugin_shutdown() { ilog("shutdown"); - m_irreversible_block_connection.reset(); + m_block_connection.reset(); } } // namespace eosio From f5b9fe0495c45ae274727b296e9be2cc921a28f3 Mon Sep 17 00:00:00 2001 From: Christopher Allnutt Date: Wed, 30 May 2018 16:33:40 -0400 Subject: [PATCH 026/294] Initial commit of ricardian contract abi importer --- scripts/ricardeos/ricardeos.py | 92 ++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100755 scripts/ricardeos/ricardeos.py diff --git a/scripts/ricardeos/ricardeos.py b/scripts/ricardeos/ricardeos.py new file mode 100755 index 00000000000..6b6be9cd9c5 --- /dev/null +++ b/scripts/ricardeos/ricardeos.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 + +import json +import sys +import os.path + +def add_ricardian_contracts_to_actions(source_abi_directory, contract_name, abi_actions): + abi_actions_with_ricardian_contracts = [] + + for abi_action in abi_actions: + action_name = abi_action["name"] + contract_action_filename = f'{contract_name}-{action_name}-rc.md' + + # check for rc file + rcContractPath = os.path.join(source_abi_directory, contract_action_filename) + if os.path.exists(rcContractPath): + print('Importing Contract {contract_action_filename} for {contract_name}:{action_name}'.format( + contract_action_filename = contract_action_filename, + contract_name = contract_name, + action_name = action_name + )) + + with open(rcContractPath) as contract_file_handle: + contract_contents = contract_file_handle.read() + + abi_action["ricardian_contract"] = contract_contents + else: + print('Did not find recardian contract file {contract_action_filename} for {contract_name}:{action_name}, skipping inclusion'.format( + contract_action_filename = contract_action_filename, + contract_name = contract_name, + action_name = action_name + )) + + abi_actions_with_ricardian_contracts.append(abi_action) + + return abi_actions_with_ricardian_contracts + +def add_ricardian_contracts_to_abi(source_abi, output_abi): + source_abi_directory = os.path.dirname(source_abi) + contract_name = os.path.split(source_abi)[1].rpartition(".")[0] + + print('Creating {output_abi} with ricardian contracts included'.format(output_abi = output_abi)) + + source_abi_file = open(source_abi, 'r') + source_abi_json = json.load(source_abi_file) + + source_abi_json['actions'] = add_ricardian_contracts_to_actions(source_abi_directory, contract_name, source_abi_json['actions']) + + with open(output_abi, 'w') as output_abi_file: + json.dump(source_abi_json, output_abi_file, indent=2) + + +def import_ricardian_to_abi(source_abi, output_abi): + if not os.path.exists(source_abi): + print(f'Source ABI not found in {source_abi}') + sys.exit(0) + + if os.path.exists(output_abi): + overwrite_prompt_response = input('Output ABI {output_abi} already exists, do you want to proceed? (y|n): '.format(output_abi = output_abi)) + if overwrite_prompt_response == 'y': + print('Overwriting existing output abi') + add_ricardian_contracts_to_abi(source_abi, output_abi) + sys.exit(0) + else: + print('User aborted, not overwriting existing abi') + sys.exit(0) + else: + add_ricardian_contracts_to_abi(source_abi, output_abi) + +def main(): + if len(sys.argv) == 1: + print('Please specify an operation of export or import: ./ricardeos.py ') + sys.exit(1) + + if sys.argv[1] == 'import': + if len(sys.argv) != 4: + print('Please specify a source and destination abi:') + print('Usage: ./ricardeos.py import /eos/contracts/contract/mycontract.abi /eos/contracts/contract/withricardian-mycontract.abi') + + sys.exit(0) + else: + import_ricardian_to_abi(sys.argv[2], sys.argv[3]) + + sys.exit(0) + elif sys.argv[2] == 'export': + print('exporting') + else: + print('Operation not recognized only import and export operations are supported') + +if __name__ == '__main__': + main() + From 8f5efcbe4397788676a940a704774324a5062240 Mon Sep 17 00:00:00 2001 From: Christopher Allnutt Date: Wed, 30 May 2018 17:02:06 -0400 Subject: [PATCH 027/294] Import clauses to ricardian_clauses --- scripts/ricardeos/ricardeos.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/scripts/ricardeos/ricardeos.py b/scripts/ricardeos/ricardeos.py index 6b6be9cd9c5..7b89fe8cef3 100755 --- a/scripts/ricardeos/ricardeos.py +++ b/scripts/ricardeos/ricardeos.py @@ -3,6 +3,7 @@ import json import sys import os.path +import fnmatch def add_ricardian_contracts_to_actions(source_abi_directory, contract_name, abi_actions): abi_actions_with_ricardian_contracts = [] @@ -35,16 +36,43 @@ def add_ricardian_contracts_to_actions(source_abi_directory, contract_name, abi_ return abi_actions_with_ricardian_contracts +def create_ricardian_clauses_list(source_abi_directory, contract_name): + clause_file_pattern = '*-clause*-rc.md' + clause_files = fnmatch.filter(os.listdir(source_abi_directory), clause_file_pattern) + + clause_prefix = 'clause-' + clause_postfix = '-rc.md' + + abi_ricardian_clauses = [] + + for clause_file_name in clause_files: + rcContractPath = os.path.join(source_abi_directory, clause_file_name) + with open(rcContractPath) as contract_file_handle: + contract_contents = contract_file_handle.read() + + start_of_clause_id = clause_file_name.index( clause_prefix ) + len( clause_prefix ) + end_of_clause_id = clause_file_name.rindex(clause_postfix, start_of_clause_id) + + clause_id = clause_file_name[start_of_clause_id:end_of_clause_id] + + abi_ricardian_clauses.append({ + 'id': clause_id, + 'body': contract_contents + }) + + return abi_ricardian_clauses + def add_ricardian_contracts_to_abi(source_abi, output_abi): source_abi_directory = os.path.dirname(source_abi) contract_name = os.path.split(source_abi)[1].rpartition(".")[0] print('Creating {output_abi} with ricardian contracts included'.format(output_abi = output_abi)) - source_abi_file = open(source_abi, 'r') - source_abi_json = json.load(source_abi_file) + with open(source_abi, 'r') as source_abi_file: + source_abi_json = json.load(source_abi_file) source_abi_json['actions'] = add_ricardian_contracts_to_actions(source_abi_directory, contract_name, source_abi_json['actions']) + source_abi_json['ricardian_clauses'] = create_ricardian_clauses_list(source_abi_directory, contract_name) with open(output_abi, 'w') as output_abi_file: json.dump(source_abi_json, output_abi_file, indent=2) From 14198fd4c55903e77589a15a577d14668fe840c7 Mon Sep 17 00:00:00 2001 From: Christopher Allnutt Date: Wed, 30 May 2018 17:41:27 -0400 Subject: [PATCH 028/294] Added export functionality to create rc md files from existing abi's --- scripts/ricardeos/ricardeos.py | 56 ++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/scripts/ricardeos/ricardeos.py b/scripts/ricardeos/ricardeos.py index 7b89fe8cef3..846a2222390 100755 --- a/scripts/ricardeos/ricardeos.py +++ b/scripts/ricardeos/ricardeos.py @@ -24,7 +24,7 @@ def add_ricardian_contracts_to_actions(source_abi_directory, contract_name, abi_ with open(rcContractPath) as contract_file_handle: contract_contents = contract_file_handle.read() - abi_action["ricardian_contract"] = contract_contents + abi_action['ricardian_contract'] = contract_contents else: print('Did not find recardian contract file {contract_action_filename} for {contract_name}:{action_name}, skipping inclusion'.format( contract_action_filename = contract_action_filename, @@ -77,7 +77,6 @@ def add_ricardian_contracts_to_abi(source_abi, output_abi): with open(output_abi, 'w') as output_abi_file: json.dump(source_abi_json, output_abi_file, indent=2) - def import_ricardian_to_abi(source_abi, output_abi): if not os.path.exists(source_abi): print(f'Source ABI not found in {source_abi}') @@ -94,7 +93,44 @@ def import_ricardian_to_abi(source_abi, output_abi): sys.exit(0) else: add_ricardian_contracts_to_abi(source_abi, output_abi) - + +def write_rc_file(path, filename, content): + output_filename = os.path.join(path, filename) + write_file = True + + if os.path.exists(output_filename): + overwrite_prompt_response = input('Output rc {output_filename} already exists, do you want to proceed? (y|n): '.format(output_filename = output_filename)) + if overwrite_prompt_response == 'y': + print('Overwriting existing output rc') + elif overwrite_prompt_response == 'n': + print('Skipping overwrite of {output_filename}'.format(output_filename = output_filename)) + write_file = False + + if write_file: + with open(output_filename, 'w') as text_file: + print(content, file=text_file) + + print('Wrote {output_filename}'.format(output_filename = output_filename)) + +def export_ricardian_from_abi(source_abi): + source_abi_directory = os.path.dirname(source_abi) + contract_name = os.path.split(source_abi)[1].rpartition(".")[0] + + if not os.path.exists(source_abi): + print(f'Source ABI not found in {source_abi}') + sys.exit(0) + + with open(source_abi, 'r') as source_abi_file: + source_abi_json = json.load(source_abi_file) + + for abi_action in source_abi_json['actions']: + output_action_rc_file_name = '{contract_name}-{action_name}-rc.md'.format(contract_name = contract_name, action_name = abi_action['name']) + write_rc_file(source_abi_directory, output_action_rc_file_name, abi_action['ricardian_contract']) + + for abi_clause in source_abi_json['ricardian_clauses']: + output_clause_rc_file_name = '{contract_name}-clause-{clause_id}-rc.md'.format(contract_name = contract_name, clause_id = abi_clause['id']) + write_rc_file(source_abi_directory, output_clause_rc_file_name, abi_clause['body']) + def main(): if len(sys.argv) == 1: print('Please specify an operation of export or import: ./ricardeos.py ') @@ -110,11 +146,19 @@ def main(): import_ricardian_to_abi(sys.argv[2], sys.argv[3]) sys.exit(0) - elif sys.argv[2] == 'export': - print('exporting') + elif sys.argv[1] == 'export': + if len(sys.argv) != 3: + print('Please specify a source abi:') + print('Usage: ./ricardeos.py export /eos/contracts/contract/mycontract.abi') + + sys.exit(0) + else: + export_ricardian_from_abi(sys.argv[2]) + + sys.exit(0) + else: print('Operation not recognized only import and export operations are supported') if __name__ == '__main__': main() - From 8d94f9ae52256633fdcbc271e92682c064faec81 Mon Sep 17 00:00:00 2001 From: Christopher Allnutt Date: Wed, 30 May 2018 17:52:59 -0400 Subject: [PATCH 029/294] Added readme --- scripts/ricardeos/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 scripts/ricardeos/README.md diff --git a/scripts/ricardeos/README.md b/scripts/ricardeos/README.md new file mode 100644 index 00000000000..3a070c03eea --- /dev/null +++ b/scripts/ricardeos/README.md @@ -0,0 +1,20 @@ +# Purpose +The `recardeos.py` imports or exports recardian contracts to and from a contracts abi + +## Import Example +`$ python3 recardeos.py import /path/to/sorce-contract.abi /path/to/new-smart-contract-abi.abi` + +Running this will scan the directory of the abi for all rc.md files and add them to their respective actions. All files with a path format of *clause*-rc.md will be added to the ricardian_clauses section. You can provide the same name for the source and new smart contract abi, the script will prompt you before overwriting. + +The script will also notify the user of any actions that the script cannot find rc.md files for. + +## Export Example +`$ python3 recardeos.py export ../../contracts/currency/currency.abi` + +Running this will dump the contents of all ricardian contracts: + +Actions will be exported in the following format: `--rc.md` + +Clauses will be exported in the following format: `-clause--rc.md` + +If a file already exists the user will be asked if they wish to overwrite the file From b8b5e5aae2183e4829ec96a099e0f020ca170206 Mon Sep 17 00:00:00 2001 From: Christopher Allnutt Date: Wed, 30 May 2018 18:07:15 -0400 Subject: [PATCH 030/294] Updates from code review --- scripts/ricardeos/README.md | 6 +++--- scripts/ricardeos/ricardeos.py | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/ricardeos/README.md b/scripts/ricardeos/README.md index 3a070c03eea..267caa85ba7 100644 --- a/scripts/ricardeos/README.md +++ b/scripts/ricardeos/README.md @@ -1,15 +1,15 @@ # Purpose -The `recardeos.py` imports or exports recardian contracts to and from a contracts abi +The `ricardeos.py` imports or exports recardian contracts to and from a contracts abi ## Import Example -`$ python3 recardeos.py import /path/to/sorce-contract.abi /path/to/new-smart-contract-abi.abi` +`$ python3 ricardeos.py import /path/to/sorce-contract.abi /path/to/new-smart-contract-abi.abi` Running this will scan the directory of the abi for all rc.md files and add them to their respective actions. All files with a path format of *clause*-rc.md will be added to the ricardian_clauses section. You can provide the same name for the source and new smart contract abi, the script will prompt you before overwriting. The script will also notify the user of any actions that the script cannot find rc.md files for. ## Export Example -`$ python3 recardeos.py export ../../contracts/currency/currency.abi` +`$ python3 ricardeos.py export ../../contracts/currency/currency.abi` Running this will dump the contents of all ricardian contracts: diff --git a/scripts/ricardeos/ricardeos.py b/scripts/ricardeos/ricardeos.py index 846a2222390..b0d692e59ff 100755 --- a/scripts/ricardeos/ricardeos.py +++ b/scripts/ricardeos/ricardeos.py @@ -10,18 +10,18 @@ def add_ricardian_contracts_to_actions(source_abi_directory, contract_name, abi_ for abi_action in abi_actions: action_name = abi_action["name"] - contract_action_filename = f'{contract_name}-{action_name}-rc.md' - + contract_action_filename = '{contract_name}-{action_name}-rc.md'.format(contract_name = contract_name, action_name = action_name) + # check for rc file - rcContractPath = os.path.join(source_abi_directory, contract_action_filename) - if os.path.exists(rcContractPath): + rc_contract_path = os.path.join(source_abi_directory, contract_action_filename) + if os.path.exists(rc_contract_path): print('Importing Contract {contract_action_filename} for {contract_name}:{action_name}'.format( contract_action_filename = contract_action_filename, contract_name = contract_name, action_name = action_name )) - with open(rcContractPath) as contract_file_handle: + with open(rc_contract_path) as contract_file_handle: contract_contents = contract_file_handle.read() abi_action['ricardian_contract'] = contract_contents @@ -46,8 +46,8 @@ def create_ricardian_clauses_list(source_abi_directory, contract_name): abi_ricardian_clauses = [] for clause_file_name in clause_files: - rcContractPath = os.path.join(source_abi_directory, clause_file_name) - with open(rcContractPath) as contract_file_handle: + rc_contract_path = os.path.join(source_abi_directory, clause_file_name) + with open(rc_contract_path) as contract_file_handle: contract_contents = contract_file_handle.read() start_of_clause_id = clause_file_name.index( clause_prefix ) + len( clause_prefix ) From e968e915ff8d8aeaf8880d0da45f1f4bda666bd1 Mon Sep 17 00:00:00 2001 From: Christopher Allnutt Date: Wed, 30 May 2018 18:10:58 -0400 Subject: [PATCH 031/294] Fixed used of f' --- scripts/ricardeos/ricardeos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ricardeos/ricardeos.py b/scripts/ricardeos/ricardeos.py index b0d692e59ff..aac4cbe597f 100755 --- a/scripts/ricardeos/ricardeos.py +++ b/scripts/ricardeos/ricardeos.py @@ -79,7 +79,7 @@ def add_ricardian_contracts_to_abi(source_abi, output_abi): def import_ricardian_to_abi(source_abi, output_abi): if not os.path.exists(source_abi): - print(f'Source ABI not found in {source_abi}') + print('Source ABI not found in {source_abi}'.format(source_abi = source_abi)) sys.exit(0) if os.path.exists(output_abi): From d64c30bdde3e2ad0281ccb6f3655108570e24074 Mon Sep 17 00:00:00 2001 From: Christopher Allnutt Date: Wed, 30 May 2018 18:11:33 -0400 Subject: [PATCH 032/294] Last format update --- scripts/ricardeos/ricardeos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ricardeos/ricardeos.py b/scripts/ricardeos/ricardeos.py index aac4cbe597f..1110328a529 100755 --- a/scripts/ricardeos/ricardeos.py +++ b/scripts/ricardeos/ricardeos.py @@ -117,7 +117,7 @@ def export_ricardian_from_abi(source_abi): contract_name = os.path.split(source_abi)[1].rpartition(".")[0] if not os.path.exists(source_abi): - print(f'Source ABI not found in {source_abi}') + print('Source ABI not found in {source_abi}'.format(source_abi = source_abi)) sys.exit(0) with open(source_abi, 'r') as source_abi_file: From 83147c96394fee8bcd8a014de9b20430c27e1b3c Mon Sep 17 00:00:00 2001 From: Chris Allnutt Date: Wed, 30 May 2018 21:47:33 -0400 Subject: [PATCH 033/294] Update path of sample for export --- scripts/ricardeos/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ricardeos/README.md b/scripts/ricardeos/README.md index 267caa85ba7..cac5023d953 100644 --- a/scripts/ricardeos/README.md +++ b/scripts/ricardeos/README.md @@ -9,7 +9,7 @@ Running this will scan the directory of the abi for all rc.md files and add them The script will also notify the user of any actions that the script cannot find rc.md files for. ## Export Example -`$ python3 ricardeos.py export ../../contracts/currency/currency.abi` +`$ python3 ricardeos.py export /path/to/sorce-contract.abi` Running this will dump the contents of all ricardian contracts: From c37d38abb62653e2dd3ffd4a609271cdc0ab7b8a Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Fri, 1 Jun 2018 09:55:08 +0200 Subject: [PATCH 034/294] =?UTF-8?q?=F0=9F=9A=80ready=20for=20mainnet?= =?UTF-8?q?=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/CMakeLists.txt | 2 +- plugins/sql_db_plugin/db/actions_table.cpp | 24 ++++++------------- plugins/sql_db_plugin/db/database.cpp | 2 +- .../sql_db_plugin/db/transactions_table.cpp | 8 ++++--- plugins/sql_db_plugin/db/transactions_table.h | 2 +- 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 5325a3ae78b..7a20a89aed0 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -16,7 +16,7 @@ add_subdirectory(txn_test_gen_plugin) add_subdirectory(db_size_api_plugin) #add_subdirectory(faucet_testnet_plugin) #add_subdirectory(mongo_db_plugin) -#add_subdirectory(sql_db_plugin) +add_subdirectory(sql_db_plugin) # Forward variables to top level so packaging picks them up set(CPACK_DEBIAN_PACKAGE_DEPENDS ${CPACK_DEBIAN_PACKAGE_DEPENDS} PARENT_SCOPE) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 7d44be757c6..b09cb14d134 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -42,8 +42,8 @@ void actions_table::create() *m_session << "CREATE TABLE tokens(" "account VARCHAR(13)," "symbol VARCHAR(10)," - "amount FLOAT," - "staked FLOAT, FOREIGN KEY (account) REFERENCES accounts(name))"; // NOT WORKING VERY GOOD float issue + "amount REAL," + "staked REAL, FOREIGN KEY (account) REFERENCES accounts(name))"; // NOT WORKING VERY GOOD float issue } void actions_table::add(chain::action action, chain::transaction_id_type transaction_id) @@ -91,13 +91,8 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac // TODO: move all if (action.name == N(issue)) { - try { - auto to_name = abi_data["to"].as().to_string(); - auto asset_quantity = abi_data["quantity"].as(); - } catch (...) { - return; - } - + auto to_name = abi_data["to"].as().to_string(); + auto asset_quantity = abi_data["quantity"].as(); int exist; *m_session << "SELECT COUNT(*) FROM tokens WHERE account = :ac AND symbol = :sy", @@ -119,14 +114,9 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac if (action.name == N(transfer)) { - try { - auto from_name = abi_data["from"].as().to_string(); - auto to_name = abi_data["to"].as().to_string(); - auto asset_quantity = abi_data["quantity"].as(); - } catch (...) { - return; - } - + auto from_name = abi_data["from"].as().to_string(); + auto to_name = abi_data["to"].as().to_string(); + auto asset_quantity = abi_data["quantity"].as(); int exist; *m_session << "SELECT COUNT(*) FROM tokens WHERE account = :ac AND symbol = :sy", diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index 10ab77afff7..4f3863e35f8 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -19,7 +19,7 @@ void database::consume(const std::vector &blocks) { m_blocks_table->add(block->block); for (const auto& transaction : block->trxs) { - m_transactions_table->add(transaction->trx); + m_transactions_table->add(block->block_num, transaction->trx); for (const auto& action : transaction->trx.actions) { m_actions_table->add(action, transaction->trx.id()); } diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index 8cbfb4ec440..3d9bc3e8761 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -26,6 +26,7 @@ void transactions_table::create() *m_session << "CREATE TABLE transactions(" "id VARCHAR(64) PRIMARY KEY," "block_id INT NOT NULL," + "ref_block_num INT NOT NULL," "ref_block_prefix INT," "expiration DATETIME DEFAULT NOW()," "pending TINYINT(1)," @@ -34,13 +35,14 @@ void transactions_table::create() "updated_at DATETIME DEFAULT NOW(), FOREIGN KEY (block_id) REFERENCES blocks(block_number))"; } -void transactions_table::add(chain::transaction transaction) +void transactions_table::add(uint32_t block_id, chain::transaction transaction) { const auto transaction_id_str = transaction.id().str(); const auto expiration = std::chrono::seconds{transaction.expiration.sec_since_epoch()}.count(); - *m_session << "INSERT INTO transactions(id, block_id, ref_block_prefix," - "expiration, pending, created_at, updated_at, num_actions) VALUES (:id, :bi, :rb, FROM_UNIXTIME(:ex), :pe, FROM_UNIXTIME(:ca), FROM_UNIXTIME(:ua), :na)", + *m_session << "INSERT INTO transactions(id, block_id, ref_block_num, ref_block_prefix," + "expiration, pending, created_at, updated_at, num_actions) VALUES (:id, :bi, :rbi, :rb, FROM_UNIXTIME(:ex), :pe, FROM_UNIXTIME(:ca), FROM_UNIXTIME(:ua), :na)", soci::use(transaction_id_str), + soci::use(block_id), soci::use(transaction.ref_block_num), soci::use(transaction.ref_block_prefix), soci::use(expiration), diff --git a/plugins/sql_db_plugin/db/transactions_table.h b/plugins/sql_db_plugin/db/transactions_table.h index 09bf1a65b32..87ed57c3a03 100644 --- a/plugins/sql_db_plugin/db/transactions_table.h +++ b/plugins/sql_db_plugin/db/transactions_table.h @@ -14,7 +14,7 @@ class transactions_table void drop(); void create(); - void add(chain::transaction transaction); + void add(uint32_t block_id, chain::transaction transaction); private: std::shared_ptr m_session; From 2cb098dc77992226593aac787b96bc5ed1a12690 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Sun, 3 Jun 2018 17:00:35 +0200 Subject: [PATCH 035/294] improving sql --- plugins/sql_db_plugin/db/actions_table.cpp | 12 ++++++------ plugins/sql_db_plugin/db/database.cpp | 1 + plugins/sql_db_plugin/db/transactions_table.cpp | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index b09cb14d134..e7084a79b6c 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -29,21 +29,21 @@ void actions_table::create() "account VARCHAR(12)," "transaction_id VARCHAR(64)," "name VARCHAR(12)," - "data JSON, FOREIGN KEY (transaction_id) REFERENCES transactions(id)," + "data JSON, FOREIGN KEY (transaction_id) REFERENCES transactions(id) ON DELETE CASCADE," "FOREIGN KEY (account) REFERENCES accounts(name))"; *m_session << "CREATE TABLE actions_accounts(" "actor VARCHAR(12)," "permission VARCHAR(12)," - "action_id VARCHAR(36), FOREIGN KEY (action_id) REFERENCES actions(id)," + "action_id VARCHAR(36), FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE," "FOREIGN KEY (actor) REFERENCES accounts(name))"; // TODO: move to own class *m_session << "CREATE TABLE tokens(" "account VARCHAR(13)," "symbol VARCHAR(10)," - "amount REAL," - "staked REAL, FOREIGN KEY (account) REFERENCES accounts(name))"; // NOT WORKING VERY GOOD float issue + "amount REAL(14,4)," + "staked REAL(14,4), FOREIGN KEY (account) REFERENCES accounts(name))"; // NOT WORKING VERY GOOD float issue } void actions_table::add(chain::action action, chain::transaction_id_type transaction_id) @@ -88,7 +88,7 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac soci::use(auth.permission.to_string()); } - // TODO: move all + // TODO: move all + try / catch issue + transfer if (action.name == N(issue)) { auto to_name = abi_data["to"].as().to_string(); @@ -100,7 +100,7 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac soci::use(to_name), soci::use(asset_quantity.get_symbol().name()); if (exist > 0) { - *m_session << "UPDATE tokens SET amount = amount + :am WHERE account = :ac AND symbol :sy", + *m_session << "UPDATE tokens SET amount = amount + :am WHERE account = :ac AND symbol = :sy", soci::use(asset_quantity.to_real()), soci::use(to_name), soci::use(asset_quantity.get_symbol().name()); diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index 4f3863e35f8..68315b8a471 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -17,6 +17,7 @@ void database::consume(const std::vector &blocks) { for (const auto& block : blocks) { + // TODO: support forks m_blocks_table->add(block->block); for (const auto& transaction : block->trxs) { m_transactions_table->add(block->block_num, transaction->trx); diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index 3d9bc3e8761..af8af63ea8c 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -32,7 +32,7 @@ void transactions_table::create() "pending TINYINT(1)," "created_at DATETIME DEFAULT NOW()," "num_actions INT DEFAULT 0," - "updated_at DATETIME DEFAULT NOW(), FOREIGN KEY (block_id) REFERENCES blocks(block_number))"; + "updated_at DATETIME DEFAULT NOW(), FOREIGN KEY (block_id) REFERENCES blocks(block_number) ON DELETE CASCADE)"; } void transactions_table::add(uint32_t block_id, chain::transaction transaction) From 47532693f622c78e92dd492a969f546864a5f446 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Tue, 5 Jun 2018 13:08:52 +0200 Subject: [PATCH 036/294] minor refactorings --- plugins/sql_db_plugin/db/actions_table.cpp | 15 ++++++++++----- plugins/sql_db_plugin/db/actions_table.h | 3 +++ plugins/sql_db_plugin/db/blocks_table.cpp | 6 ++++++ plugins/sql_db_plugin/db/blocks_table.h | 1 + plugins/sql_db_plugin/db/database.cpp | 9 +++++++-- .../include/eosio/sql_db_plugin/sql_db_plugin.hpp | 3 +++ plugins/sql_db_plugin/sql_db_plugin.cpp | 5 ++++- 7 files changed, 34 insertions(+), 8 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index e7084a79b6c..ba7ace7a2d7 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -38,12 +38,11 @@ void actions_table::create() "action_id VARCHAR(36), FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE," "FOREIGN KEY (actor) REFERENCES accounts(name))"; - // TODO: move to own class *m_session << "CREATE TABLE tokens(" "account VARCHAR(13)," "symbol VARCHAR(10)," "amount REAL(14,4)," - "staked REAL(14,4), FOREIGN KEY (account) REFERENCES accounts(name))"; // NOT WORKING VERY GOOD float issue + "staked REAL(14,4), FOREIGN KEY (account) REFERENCES accounts(name))"; // TODO: other tokens could have diff format. } void actions_table::add(chain::action action, chain::transaction_id_type transaction_id) @@ -87,8 +86,16 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac soci::use(auth.actor.to_string()), soci::use(auth.permission.to_string()); } + try { + parse_actions(action, abi_data); + } catch(std::exception& e){ + wlog(e.what()); + } +} - // TODO: move all + try / catch issue + transfer +void actions_table::parse_actions(chain::action action, fc::variant abi_data) +{ + // TODO: move all + catch // public keys update // stake / voting if (action.name == N(issue)) { auto to_name = abi_data["to"].as().to_string(); @@ -179,8 +186,6 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac } } - - // TODO: catch issue (tokens) // public keys update // stake / voting } } // namespace diff --git a/plugins/sql_db_plugin/db/actions_table.h b/plugins/sql_db_plugin/db/actions_table.h index 1a6113c9d02..5bfb5787751 100644 --- a/plugins/sql_db_plugin/db/actions_table.h +++ b/plugins/sql_db_plugin/db/actions_table.h @@ -33,6 +33,9 @@ class actions_table private: std::shared_ptr m_session; + + void + parse_actions(chain::action action, fc::variant variant); }; } // namespace diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index 8a1ff275058..33a89e41aaf 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -57,4 +57,10 @@ void blocks_table::add(chain::signed_block_ptr block) soci::use(num_transactions); } +void blocks_table::remove(chain::signed_block_ptr block) +{ + const auto block_id_str = block->id().str(); + *m_session << "DELETE FROM blocks WHERE id = :id", soci::use(block_id_str); +} + } // namespace diff --git a/plugins/sql_db_plugin/db/blocks_table.h b/plugins/sql_db_plugin/db/blocks_table.h index 25e5a065ac1..17eb74ee970 100644 --- a/plugins/sql_db_plugin/db/blocks_table.h +++ b/plugins/sql_db_plugin/db/blocks_table.h @@ -18,6 +18,7 @@ class blocks_table void drop(); void create(); void add(chain::signed_block_ptr block); + void remove(chain::signed_block_ptr block); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index 68315b8a471..adde632b79a 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -17,8 +17,13 @@ void database::consume(const std::vector &blocks) { for (const auto& block : blocks) { - // TODO: support forks - m_blocks_table->add(block->block); + try { + m_blocks_table->add(block->block); + } catch(std::exception& e){ + wlog(e.what()); + m_blocks_table->remove(block->block); // fork? + m_blocks_table->add(block->block); + } for (const auto& transaction : block->trxs) { m_transactions_table->add(block->block_num, transaction->trx); for (const auto& action : transaction->trx.actions) { diff --git a/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp b/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp index c81f825d0c7..4ceecdca75f 100644 --- a/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp +++ b/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp @@ -46,6 +46,9 @@ class sql_db_plugin final : public plugin { private: std::unique_ptr> m_block_consumer; fc::optional m_block_connection; + + std::unique_ptr> m_irreversible_block_consumer; + fc::optional m_irreversible_block_connection; }; } diff --git a/plugins/sql_db_plugin/sql_db_plugin.cpp b/plugins/sql_db_plugin/sql_db_plugin.cpp index e533067a883..4e1bf677fea 100644 --- a/plugins/sql_db_plugin/sql_db_plugin.cpp +++ b/plugins/sql_db_plugin/sql_db_plugin.cpp @@ -57,11 +57,13 @@ void sql_db_plugin::plugin_initialize(const variables_map& options) } m_block_consumer = std::make_unique>(std::move(db)); + m_irreversible_block_consumer = std::make_unique>(std::move(db)); chain_plugin* chain_plug = app().find_plugin(); FC_ASSERT(chain_plug); auto& chain = chain_plug->chain(); - // TODO: check irreversible to update info + // TODO: irreversible to different queue to just find block & update flag + //m_irreversible_block_connection.emplace(chain.irreversible_block.connect([=](const chain::block_state_ptr& b) {m_irreversible_block_consumer->push(b);})); m_block_connection.emplace(chain.accepted_block.connect([=](const chain::block_state_ptr& b) {m_block_consumer->push(b);})); } @@ -74,6 +76,7 @@ void sql_db_plugin::plugin_shutdown() { ilog("shutdown"); m_block_connection.reset(); + m_irreversible_block_connection.reset(); } } // namespace eosio From 415dbfe0e5c43d0fe7b84b208f3c9a47adedf945 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Tue, 5 Jun 2018 15:52:23 +0200 Subject: [PATCH 037/294] =?UTF-8?q?=F0=9F=9A=80ready=20for=20mainnet<2728>?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/sql_db_plugin/db/blocks_table.cpp | 8 +------- plugins/sql_db_plugin/db/blocks_table.h | 1 - plugins/sql_db_plugin/db/database.cpp | 8 +------- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index 33a89e41aaf..79529c312ee 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -44,7 +44,7 @@ void blocks_table::add(chain::signed_block_ptr block) const auto timestamp = std::chrono::seconds{block->timestamp.operator fc::time_point().sec_since_epoch()}.count(); const auto num_transactions = (int)block->transactions.size(); - *m_session << "INSERT INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root, action_merkle_root," + *m_session << "REPLACE INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root, action_merkle_root," "producer, confirmed, num_transactions) VALUES (:id, :in, :pb, FROM_UNIXTIME(:ti), :tr, :ar, :pa, :pe, :nt)", soci::use(block_id_str), soci::use(block->block_num()), @@ -57,10 +57,4 @@ void blocks_table::add(chain::signed_block_ptr block) soci::use(num_transactions); } -void blocks_table::remove(chain::signed_block_ptr block) -{ - const auto block_id_str = block->id().str(); - *m_session << "DELETE FROM blocks WHERE id = :id", soci::use(block_id_str); -} - } // namespace diff --git a/plugins/sql_db_plugin/db/blocks_table.h b/plugins/sql_db_plugin/db/blocks_table.h index 17eb74ee970..25e5a065ac1 100644 --- a/plugins/sql_db_plugin/db/blocks_table.h +++ b/plugins/sql_db_plugin/db/blocks_table.h @@ -18,7 +18,6 @@ class blocks_table void drop(); void create(); void add(chain::signed_block_ptr block); - void remove(chain::signed_block_ptr block); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index adde632b79a..4f3863e35f8 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -17,13 +17,7 @@ void database::consume(const std::vector &blocks) { for (const auto& block : blocks) { - try { - m_blocks_table->add(block->block); - } catch(std::exception& e){ - wlog(e.what()); - m_blocks_table->remove(block->block); // fork? - m_blocks_table->add(block->block); - } + m_blocks_table->add(block->block); for (const auto& transaction : block->trxs) { m_transactions_table->add(block->block_num, transaction->trx); for (const auto& action : transaction->trx.actions) { From 286174a8be4b2240b9a2ef0837a977b7d47ad344 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Mon, 11 Jun 2018 19:25:11 +0200 Subject: [PATCH 038/294] storing votes --- plugins/sql_db_plugin/db/actions_table.cpp | 16 ++++++++++++++++ plugins/sql_db_plugin/db/database.cpp | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index ba7ace7a2d7..fac61a80213 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -14,6 +14,7 @@ void actions_table::drop() { try { *m_session << "drop table IF EXISTS actions_accounts"; + *m_session << "drop table IF EXISTS votes"; *m_session << "drop table IF EXISTS tokens"; *m_session << "drop table IF EXISTS actions"; } @@ -43,6 +44,12 @@ void actions_table::create() "symbol VARCHAR(10)," "amount REAL(14,4)," "staked REAL(14,4), FOREIGN KEY (account) REFERENCES accounts(name))"; // TODO: other tokens could have diff format. + + *m_session << "CREATE TABLE votes(" + "account VARCHAR(13) PRIMARY KEY," + "votes JSON" + ", FOREIGN KEY (account) REFERENCES accounts(name))"; + } void actions_table::add(chain::action action, chain::transaction_id_type transaction_id) @@ -152,6 +159,15 @@ void actions_table::parse_actions(chain::action action, fc::variant abi_data) return; } + if (action.name == N(voteproducer)) { + auto voter = abi_data["voter"].as().to_string(); + string votes = fc::json::to_string(abi_data["producers"]); + + *m_session << "REPLACE INTO votes(account, votes) VALUES (:ac, :vo) ", + soci::use(voter), + soci::use(votes); + } + if (action.name == chain::setabi::get_name()) { chain::abi_def abi_setabi; chain::setabi action_data = action.data_as(); diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index 4f3863e35f8..395a5802d97 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -21,7 +21,11 @@ void database::consume(const std::vector &blocks) for (const auto& transaction : block->trxs) { m_transactions_table->add(block->block_num, transaction->trx); for (const auto& action : transaction->trx.actions) { - m_actions_table->add(action, transaction->trx.id()); + try { + m_actions_table->add(action, transaction->trx.id()); + } catch (const fc::assert_exception&) { // malformed actions + continue; + } } } } From 36c0029d728bc2775b8d9b2bb1cabaf7f0567841 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Tue, 12 Jun 2018 11:10:25 +0200 Subject: [PATCH 039/294] stakes --- plugins/sql_db_plugin/db/actions_table.cpp | 23 ++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index fac61a80213..e4ef96739a2 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -14,6 +14,7 @@ void actions_table::drop() { try { *m_session << "drop table IF EXISTS actions_accounts"; + *m_session << "drop table IF EXISTS stakes"; *m_session << "drop table IF EXISTS votes"; *m_session << "drop table IF EXISTS tokens"; *m_session << "drop table IF EXISTS actions"; @@ -43,13 +44,19 @@ void actions_table::create() "account VARCHAR(13)," "symbol VARCHAR(10)," "amount REAL(14,4)," - "staked REAL(14,4), FOREIGN KEY (account) REFERENCES accounts(name))"; // TODO: other tokens could have diff format. + "FOREIGN KEY (account) REFERENCES accounts(name))"; // TODO: other tokens could have diff format. *m_session << "CREATE TABLE votes(" "account VARCHAR(13) PRIMARY KEY," "votes JSON" ", FOREIGN KEY (account) REFERENCES accounts(name))"; + *m_session << "CREATE TABLE stakes(" + "account VARCHAR(13)," + "cpu REAL(14,4)," + "net REAL(14,4)," + "FOREIGN KEY (account) REFERENCES accounts(name))"; + } void actions_table::add(chain::action action, chain::transaction_id_type transaction_id) @@ -143,7 +150,7 @@ void actions_table::parse_actions(chain::action action, fc::variant abi_data) soci::use(to_name), soci::use(asset_quantity.get_symbol().name()); } else { - *m_session << "INSERT INTO tokens(account, amount, staked, symbol) VALUES (:ac, :am, 0, :as) ", + *m_session << "INSERT INTO tokens(account, amount, symbol) VALUES (:ac, :am, :as) ", soci::use(to_name), soci::use(asset_quantity.to_real()), soci::use(asset_quantity.get_symbol().name()); @@ -168,6 +175,18 @@ void actions_table::parse_actions(chain::action action, fc::variant abi_data) soci::use(votes); } + + if (action.name == N(delegatebw)) { + auto account = abi_data["receiver"].as().to_string(); + auto cpu = abi_data["stake_cpu_quantity"].as(); + auto net = abi_data["stake_net_quantity"].as(); + + *m_session << "REPLACE INTO stakes(account, cpu, net) VALUES (:ac, :cp, :ne) ", + soci::use(account), + soci::use(cpu.to_real()), + soci::use(net.to_real()); + } + if (action.name == chain::setabi::get_name()) { chain::abi_def abi_setabi; chain::setabi action_data = action.data_as(); From c162bff39c7f60dab02247b3c5d4f88e46b765d9 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Tue, 12 Jun 2018 11:18:10 +0200 Subject: [PATCH 040/294] staked removed table --- plugins/sql_db_plugin/db/actions_table.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index e4ef96739a2..01f4de92e21 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -126,7 +126,7 @@ void actions_table::parse_actions(chain::action action, fc::variant abi_data) soci::use(to_name), soci::use(asset_quantity.get_symbol().name()); } else { - *m_session << "INSERT INTO tokens(account, amount, staked, symbol) VALUES (:ac, :am, 0, :as) ", + *m_session << "INSERT INTO tokens(account, amount, symbol) VALUES (:ac, :am, :as) ", soci::use(to_name), soci::use(asset_quantity.to_real()), soci::use(asset_quantity.get_symbol().name()); From c6d1a8e7efdec88c9278de59c4dbe85b1766100b Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Sat, 16 Jun 2018 23:10:07 +0200 Subject: [PATCH 041/294] block to start --- plugins/sql_db_plugin/db/database.cpp | 8 ++++++-- plugins/sql_db_plugin/db/database.h | 3 ++- plugins/sql_db_plugin/sql_db_plugin.cpp | 12 +++++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index 395a5802d97..09e9406e844 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -2,14 +2,14 @@ namespace eosio { -database::database(const std::string &uri) +database::database(const std::string &uri, uint32_t block_num_start) { m_session = std::make_shared(uri); m_accounts_table = std::make_unique(m_session); m_blocks_table = std::make_unique(m_session); m_transactions_table = std::make_unique(m_session); m_actions_table = std::make_unique(m_session); - + m_block_num_start = block_num_start; system_account = chain::name(chain::config::system_account_name).to_string(); } @@ -17,6 +17,10 @@ void database::consume(const std::vector &blocks) { for (const auto& block : blocks) { + if (m_block_num_start > 0 && block->block_num < m_block_num_start) { + continue; + } + m_blocks_table->add(block->block); for (const auto& transaction : block->trxs) { m_transactions_table->add(block->block_num, transaction->trx); diff --git a/plugins/sql_db_plugin/db/database.h b/plugins/sql_db_plugin/db/database.h index be6d38a5480..7b83e49ad24 100644 --- a/plugins/sql_db_plugin/db/database.h +++ b/plugins/sql_db_plugin/db/database.h @@ -22,7 +22,7 @@ namespace eosio { class database : public consumer_core { public: - database(const std::string& uri); + database(const std::string& uri, uint32_t block_num_start); void consume(const std::vector& blocks) override; @@ -36,6 +36,7 @@ class database : public consumer_core std::unique_ptr m_blocks_table; std::unique_ptr m_transactions_table; std::string system_account; + uint32_t m_block_num_start; }; } // namespace diff --git a/plugins/sql_db_plugin/sql_db_plugin.cpp b/plugins/sql_db_plugin/sql_db_plugin.cpp index 4e1bf677fea..5643daa90a7 100644 --- a/plugins/sql_db_plugin/sql_db_plugin.cpp +++ b/plugins/sql_db_plugin/sql_db_plugin.cpp @@ -8,6 +8,7 @@ #include "database.h" namespace { +const char* BLOCK_START_OPTION = "sql_db-block-start"; const char* BUFFER_SIZE_OPTION = "sql_db-queue-size"; const char* SQL_DB_URI_OPTION = "sql_db-uri"; const char* HARD_REPLAY_OPTION = "hard-replay-blockchain"; @@ -28,6 +29,8 @@ void sql_db_plugin::set_program_options(options_description& cli, options_descri cfg.add_options() (BUFFER_SIZE_OPTION, bpo::value()->default_value(256), "The queue size between nodeos and SQL DB plugin thread.") + (BLOCK_START_OPTION, bpo::value()->default_value(0), + "The block to start sync.") (SQL_DB_URI_OPTION, bpo::value(), "Sql DB URI connection string" " If not specified then plugin is disabled. Default database 'EOS' is used if not specified in URI.") @@ -45,15 +48,18 @@ void sql_db_plugin::plugin_initialize(const variables_map& options) return; } ilog("connecting to ${u}", ("u", uri_str)); - auto db = std::make_unique(uri_str); + uint32_t block_num_start = options.at(BLOCK_START_OPTION).as(); + auto db = std::make_unique(uri_str, block_num_start); if (options.at(HARD_REPLAY_OPTION).as() || options.at(REPLAY_OPTION).as() || options.at(RESYNC_OPTION).as() || !db->is_started()) { - ilog("Resync requested: wiping database"); - db->wipe(); + if (block_num_start == 0) { + ilog("Resync requested: wiping database"); + db->wipe(); + } } m_block_consumer = std::make_unique>(std::move(db)); From bb361c081e91ada943001c674335c10bd8b193af Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Tue, 19 Jun 2018 00:53:36 +0200 Subject: [PATCH 042/294] support codification --- plugins/sql_db_plugin/db/accounts_table.cpp | 4 ++-- plugins/sql_db_plugin/db/actions_table.cpp | 10 +++++----- plugins/sql_db_plugin/db/blocks_table.cpp | 2 +- plugins/sql_db_plugin/db/transactions_table.cpp | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/sql_db_plugin/db/accounts_table.cpp b/plugins/sql_db_plugin/db/accounts_table.cpp index 34f40c19e6f..48bc0e6511d 100644 --- a/plugins/sql_db_plugin/db/accounts_table.cpp +++ b/plugins/sql_db_plugin/db/accounts_table.cpp @@ -27,12 +27,12 @@ void accounts_table::create() "name VARCHAR(12) PRIMARY KEY," "abi JSON DEFAULT NULL," "created_at DATETIME DEFAULT NOW()," - "updated_at DATETIME DEFAULT NOW())"; + "updated_at DATETIME DEFAULT NOW()) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; *m_session << "CREATE TABLE accounts_keys(" "account VARCHAR(12)," "public_key VARCHAR(53)," - "permission VARCHAR(12), FOREIGN KEY (account) REFERENCES accounts(name))"; + "permission VARCHAR(12), FOREIGN KEY (account) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; } void accounts_table::add(string name) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 01f4de92e21..88d8f9c11a7 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -32,30 +32,30 @@ void actions_table::create() "transaction_id VARCHAR(64)," "name VARCHAR(12)," "data JSON, FOREIGN KEY (transaction_id) REFERENCES transactions(id) ON DELETE CASCADE," - "FOREIGN KEY (account) REFERENCES accounts(name))"; + "FOREIGN KEY (account) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; *m_session << "CREATE TABLE actions_accounts(" "actor VARCHAR(12)," "permission VARCHAR(12)," "action_id VARCHAR(36), FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE," - "FOREIGN KEY (actor) REFERENCES accounts(name))"; + "FOREIGN KEY (actor) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; *m_session << "CREATE TABLE tokens(" "account VARCHAR(13)," "symbol VARCHAR(10)," "amount REAL(14,4)," - "FOREIGN KEY (account) REFERENCES accounts(name))"; // TODO: other tokens could have diff format. + "FOREIGN KEY (account) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; // TODO: other tokens could have diff format. *m_session << "CREATE TABLE votes(" "account VARCHAR(13) PRIMARY KEY," "votes JSON" - ", FOREIGN KEY (account) REFERENCES accounts(name))"; + ", FOREIGN KEY (account) REFERENCES accounts(name), UNIQUE KEY account (account)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; *m_session << "CREATE TABLE stakes(" "account VARCHAR(13)," "cpu REAL(14,4)," "net REAL(14,4)," - "FOREIGN KEY (account) REFERENCES accounts(name))"; + "FOREIGN KEY (account) REFERENCES accounts(name), UNIQUE KEY account (account)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; } diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index 79529c312ee..eac33746cab 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -32,7 +32,7 @@ void blocks_table::create() "action_merkle_root VARCHAR(64)," "producer VARCHAR(12)," "num_transactions INT DEFAULT 0," - "confirmed INT, FOREIGN KEY (producer) REFERENCES accounts(name), UNIQUE KEY block_number (block_number))"; + "confirmed INT, FOREIGN KEY (producer) REFERENCES accounts(name), UNIQUE KEY block_number (block_number)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; } void blocks_table::add(chain::signed_block_ptr block) diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index af8af63ea8c..53871de1054 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -32,7 +32,7 @@ void transactions_table::create() "pending TINYINT(1)," "created_at DATETIME DEFAULT NOW()," "num_actions INT DEFAULT 0," - "updated_at DATETIME DEFAULT NOW(), FOREIGN KEY (block_id) REFERENCES blocks(block_number) ON DELETE CASCADE)"; + "updated_at DATETIME DEFAULT NOW(), FOREIGN KEY (block_id) REFERENCES blocks(block_number) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; } void transactions_table::add(uint32_t block_id, chain::transaction transaction) From 7b8acb7039e84be6fd1f2f02ef3c589a8562f02f Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Tue, 19 Jun 2018 14:51:03 +0200 Subject: [PATCH 043/294] store version + producers --- plugins/sql_db_plugin/db/blocks_table.cpp | 13 ++++++++++++- plugins/sql_db_plugin/db/blocks_table.h | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index eac33746cab..c35e94692e8 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -31,6 +31,8 @@ void blocks_table::create() "transaction_merkle_root VARCHAR(64)," "action_merkle_root VARCHAR(64)," "producer VARCHAR(12)," + "version INT NOT NULL DEFAULT 0," + "new_producers JSON DEFAULT NULL," "num_transactions INT DEFAULT 0," "confirmed INT, FOREIGN KEY (producer) REFERENCES accounts(name), UNIQUE KEY block_number (block_number)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; } @@ -44,8 +46,9 @@ void blocks_table::add(chain::signed_block_ptr block) const auto timestamp = std::chrono::seconds{block->timestamp.operator fc::time_point().sec_since_epoch()}.count(); const auto num_transactions = (int)block->transactions.size(); + *m_session << "REPLACE INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root, action_merkle_root," - "producer, confirmed, num_transactions) VALUES (:id, :in, :pb, FROM_UNIXTIME(:ti), :tr, :ar, :pa, :pe, :nt)", + "producer, version, confirmed, num_transactions) VALUES (:id, :in, :pb, FROM_UNIXTIME(:ti), :tr, :ar, :pa, :ve, :pe, :nt)", soci::use(block_id_str), soci::use(block->block_num()), soci::use(previous_block_id_str), @@ -53,8 +56,16 @@ void blocks_table::add(chain::signed_block_ptr block) soci::use(transaction_mroot_str), soci::use(action_mroot_str), soci::use(block->producer.to_string()), + soci::use(block->schedule_version), soci::use(block->confirmed), soci::use(num_transactions); + + if (block->new_producers) { + const auto new_producers = fc::json::to_string(block->new_producers->producers); + *m_session << "UPDATE blocks SET new_producers = :np WHERE id = :id", + soci::use(new_producers), + soci::use(block_id_str); + } } } // namespace diff --git a/plugins/sql_db_plugin/db/blocks_table.h b/plugins/sql_db_plugin/db/blocks_table.h index 25e5a065ac1..634c40914a5 100644 --- a/plugins/sql_db_plugin/db/blocks_table.h +++ b/plugins/sql_db_plugin/db/blocks_table.h @@ -6,6 +6,9 @@ #include +#include +#include + #include namespace eosio { From e09ed0415c1d7f35ab34b8c5c86ecb290d574b89 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Thu, 21 Jun 2018 17:40:26 +0200 Subject: [PATCH 044/294] actions improvement + avoid crashes --- plugins/sql_db_plugin/db/actions_table.cpp | 19 ++++----- plugins/sql_db_plugin/db/actions_table.h | 2 +- plugins/sql_db_plugin/db/database.cpp | 46 ++++++++++++++-------- scripts/eosio_build_ubuntu.sh | 2 +- 4 files changed, 41 insertions(+), 28 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 88d8f9c11a7..98ba86fcc9f 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -1,7 +1,5 @@ #include "actions_table.h" -#include - namespace eosio { actions_table::actions_table(std::shared_ptr session): @@ -27,17 +25,19 @@ void actions_table::drop() void actions_table::create() { *m_session << "CREATE TABLE actions(" - "id VARCHAR(36) PRIMARY KEY," + "id INT NOT NULL AUTO_INCREMENT KEY," "account VARCHAR(12)," "transaction_id VARCHAR(64)," + "seq SMALLINT," "name VARCHAR(12)," + "created_at DATETIME DEFAULT NOW()," "data JSON, FOREIGN KEY (transaction_id) REFERENCES transactions(id) ON DELETE CASCADE," "FOREIGN KEY (account) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; *m_session << "CREATE TABLE actions_accounts(" "actor VARCHAR(12)," "permission VARCHAR(12)," - "action_id VARCHAR(36), FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE," + "action_id INT NOT NULL, FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE," "FOREIGN KEY (actor) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; *m_session << "CREATE TABLE tokens(" @@ -59,7 +59,7 @@ void actions_table::create() } -void actions_table::add(chain::action action, chain::transaction_id_type transaction_id) +void actions_table::add(chain::action action, chain::transaction_id_type transaction_id, fc::time_point_sec transaction_time, uint8_t seq) { chain::abi_def abi; @@ -67,6 +67,7 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac chain::abi_serializer abis; soci::indicator ind; const auto transaction_id_str = transaction_id.str(); + const auto expiration = std::chrono::seconds{transaction_time.sec_since_epoch()}.count(); *m_session << "SELECT abi FROM accounts WHERE name = :name", soci::into(abi_def_account, ind), soci::use(action.account.to_string()); @@ -87,16 +88,16 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac boost::uuids::uuid id = gen(); std::string action_id = boost::uuids::to_string(id); - *m_session << "INSERT INTO actions(id, account, name, data, transaction_id) VALUES (:id, :ac, :na, :da, :ti) ", - soci::use(action_id), + *m_session << "INSERT INTO actions(account, seq, created_at, name, data, transaction_id) VALUES (:ac, :se, FROM_UNIXTIME(:ca), :na, :da, :ti) ", soci::use(action.account.to_string()), + soci::use(seq), + soci::use(expiration), soci::use(action.name.to_string()), soci::use(json), soci::use(transaction_id_str); for (const auto& auth : action.authorization) { - *m_session << "INSERT INTO actions_accounts(action_id, actor, permission) VALUES (:id, :ac, :pe) ", - soci::use(action_id), + *m_session << "INSERT INTO actions_accounts(action_id, actor, permission) VALUES (LAST_INSERT_ID(), :ac, :pe) ", soci::use(auth.actor.to_string()), soci::use(auth.permission.to_string()); } diff --git a/plugins/sql_db_plugin/db/actions_table.h b/plugins/sql_db_plugin/db/actions_table.h index 5bfb5787751..b84b1c6e150 100644 --- a/plugins/sql_db_plugin/db/actions_table.h +++ b/plugins/sql_db_plugin/db/actions_table.h @@ -29,7 +29,7 @@ class actions_table void drop(); void create(); - void add(chain::action action, chain::transaction_id_type transaction_id); + void add(chain::action action, chain::transaction_id_type transaction_id, fc::time_point_sec transaction_time, uint8_t seq); private: std::shared_ptr m_session; diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp index 09e9406e844..85cb21ff6d9 100644 --- a/plugins/sql_db_plugin/db/database.cpp +++ b/plugins/sql_db_plugin/db/database.cpp @@ -1,6 +1,7 @@ #include "database.h" -namespace eosio { +namespace eosio +{ database::database(const std::string &uri, uint32_t block_num_start) { @@ -13,29 +14,39 @@ database::database(const std::string &uri, uint32_t block_num_start) system_account = chain::name(chain::config::system_account_name).to_string(); } -void database::consume(const std::vector &blocks) +void +database::consume(const std::vector &blocks) { - for (const auto& block : blocks) - { - if (m_block_num_start > 0 && block->block_num < m_block_num_start) { - continue; - } + try { + for (const auto &block : blocks) { + if (m_block_num_start > 0 && block->block_num < m_block_num_start) { + continue; + } - m_blocks_table->add(block->block); - for (const auto& transaction : block->trxs) { - m_transactions_table->add(block->block_num, transaction->trx); - for (const auto& action : transaction->trx.actions) { - try { - m_actions_table->add(action, transaction->trx.id()); - } catch (const fc::assert_exception&) { // malformed actions - continue; + + m_blocks_table->add(block->block); + for (const auto &transaction : block->trxs) { + m_transactions_table->add(block->block_num, transaction->trx); + uint8_t seq = 0; + for (const auto &action : transaction->trx.actions) { + try { + m_actions_table->add(action, transaction->trx.id(), transaction->trx.expiration, seq); + seq++; + } catch (const fc::assert_exception &ex) { // malformed actions + wlog("${e}", ("e", ex.what())); + continue; + } } } + } + } catch (const std::exception &ex) { + elog("${e}", ("e", ex.what())); // prevent crash } } -void database::wipe() +void +database::wipe() { *m_session << "SET foreign_key_checks = 0;"; @@ -54,7 +65,8 @@ void database::wipe() m_accounts_table->add(system_account); } -bool database::is_started() +bool +database::is_started() { return m_accounts_table->exist(system_account); } diff --git a/scripts/eosio_build_ubuntu.sh b/scripts/eosio_build_ubuntu.sh index 1ee13067453..22303e720c5 100644 --- a/scripts/eosio_build_ubuntu.sh +++ b/scripts/eosio_build_ubuntu.sh @@ -55,7 +55,7 @@ DEP_ARRAY=(clang-4.0 lldb-4.0 libclang-4.0-dev cmake make automake libbz2-dev libssl-dev \ libgmp3-dev autotools-dev build-essential libicu-dev python2.7-dev python3-dev \ - autoconf libtool curl zlib1g-dev doxygen graphviz) + autoconf libtool curl zlib1g-dev doxygen graphviz libsoci-dev) COUNT=1 DISPLAY="" DEP="" From e64b72a1d06465e15551007069cdac73ff7bff62 Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Thu, 21 Jun 2018 20:26:36 +0200 Subject: [PATCH 045/294] db improvements --- plugins/sql_db_plugin/db/actions_table.cpp | 17 +++++++++++++---- plugins/sql_db_plugin/db/blocks_table.cpp | 6 ++++++ plugins/sql_db_plugin/db/transactions_table.cpp | 3 +++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 98ba86fcc9f..48f85039425 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -25,38 +25,47 @@ void actions_table::drop() void actions_table::create() { *m_session << "CREATE TABLE actions(" - "id INT NOT NULL AUTO_INCREMENT KEY," + "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY," "account VARCHAR(12)," "transaction_id VARCHAR(64)," "seq SMALLINT," + "parent INT DEFAULT NULL," "name VARCHAR(12)," "created_at DATETIME DEFAULT NOW()," "data JSON, FOREIGN KEY (transaction_id) REFERENCES transactions(id) ON DELETE CASCADE," "FOREIGN KEY (account) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; + *m_session << "CREATE INDEX idx_actions_account ON actions (account);"; + *m_session << "CREATE INDEX idx_actions_tx_id ON actions (transaction_id);"; + *m_session << "CREATE INDEX idx_actions_created ON actions (created_at);"; + *m_session << "CREATE TABLE actions_accounts(" "actor VARCHAR(12)," "permission VARCHAR(12)," "action_id INT NOT NULL, FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE," "FOREIGN KEY (actor) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; + *m_session << "CREATE INDEX idx_actions_actor ON actions_accounts (actor);"; + *m_session << "CREATE INDEX idx_actions_action_id ON actions_accounts (action_id);"; + *m_session << "CREATE TABLE tokens(" "account VARCHAR(13)," "symbol VARCHAR(10)," "amount REAL(14,4)," "FOREIGN KEY (account) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; // TODO: other tokens could have diff format. + *m_session << "CREATE INDEX idx_tokens_account ON tokens (account);"; + *m_session << "CREATE TABLE votes(" "account VARCHAR(13) PRIMARY KEY," "votes JSON" ", FOREIGN KEY (account) REFERENCES accounts(name), UNIQUE KEY account (account)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; *m_session << "CREATE TABLE stakes(" - "account VARCHAR(13)," + "account VARCHAR(13) PRIMARY KEY," "cpu REAL(14,4)," "net REAL(14,4)," - "FOREIGN KEY (account) REFERENCES accounts(name), UNIQUE KEY account (account)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; - + "FOREIGN KEY (account) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; } void actions_table::add(chain::action action, chain::transaction_id_type transaction_id, fc::time_point_sec transaction_time, uint8_t seq) diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp index c35e94692e8..92e36c18e8d 100644 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ b/plugins/sql_db_plugin/db/blocks_table.cpp @@ -35,8 +35,14 @@ void blocks_table::create() "new_producers JSON DEFAULT NULL," "num_transactions INT DEFAULT 0," "confirmed INT, FOREIGN KEY (producer) REFERENCES accounts(name), UNIQUE KEY block_number (block_number)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; + + *m_session << "CREATE INDEX idx_blocks_producer ON blocks (producer);"; + *m_session << "CREATE INDEX idx_blocks_number ON blocks (block_number);"; + } + + void blocks_table::add(chain::signed_block_ptr block) { const auto block_id_str = block->id().str(); diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp index 53871de1054..34d0f954c33 100644 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ b/plugins/sql_db_plugin/db/transactions_table.cpp @@ -33,6 +33,9 @@ void transactions_table::create() "created_at DATETIME DEFAULT NOW()," "num_actions INT DEFAULT 0," "updated_at DATETIME DEFAULT NOW(), FOREIGN KEY (block_id) REFERENCES blocks(block_number) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; + + *m_session << "CREATE INDEX transactions_block_id ON transactions (block_id);"; + } void transactions_table::add(uint32_t block_id, chain::transaction transaction) From df1309f42af1ceed6e9857269e38eaa096130e8e Mon Sep 17 00:00:00 2001 From: Cesar Rodriguez Date: Thu, 21 Jun 2018 21:32:33 +0200 Subject: [PATCH 046/294] db improvements --- plugins/sql_db_plugin/db/actions_table.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 48f85039425..35544f7779d 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -76,7 +76,7 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac chain::abi_serializer abis; soci::indicator ind; const auto transaction_id_str = transaction_id.str(); - const auto expiration = std::chrono::seconds{transaction_time.sec_since_epoch()}.count(); + const auto expiration = boost::chrono::seconds{transaction_time.sec_since_epoch()}.count(); *m_session << "SELECT abi FROM accounts WHERE name = :name", soci::into(abi_def_account, ind), soci::use(action.account.to_string()); From 15ab2f2e84e6259cf3b7d8259b6eb08e388dde4b Mon Sep 17 00:00:00 2001 From: "blockxyz@gmail.com" Date: Sat, 7 Jul 2018 17:52:00 +0800 Subject: [PATCH 047/294] [EOS] Fix token amount issue --- plugins/sql_db_plugin/db/actions_table.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index 35544f7779d..9378c067c70 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -51,7 +51,7 @@ void actions_table::create() *m_session << "CREATE TABLE tokens(" "account VARCHAR(13)," "symbol VARCHAR(10)," - "amount REAL(14,4)," + "amount DOUBLE(64,4)," "FOREIGN KEY (account) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; // TODO: other tokens could have diff format. *m_session << "CREATE INDEX idx_tokens_account ON tokens (account);"; From ac85d6e8b58c58316b31aeed2a0ec81aaaffa25e Mon Sep 17 00:00:00 2001 From: Spartucus Date: Tue, 10 Jul 2018 18:27:23 +0800 Subject: [PATCH 048/294] Fix confirmations check --- libraries/chain/fork_database.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index d1556abd00d..0609ca3dae1 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -291,7 +291,7 @@ namespace eosio { namespace chain { b->add_confirmation( c ); if( b->bft_irreversible_blocknum < b->block_num && - b->confirmations.size() > ((b->active_schedule.producers.size() * 2) / 3) ) { + b->confirmations.size() >= ((b->active_schedule.producers.size() * 2) / 3 + 1) ) { set_bft_irreversible( c.block_id ); } } From 28cb4cd71bfc470e991173495385c16dc6ad68f0 Mon Sep 17 00:00:00 2001 From: Paul Calabrese Date: Tue, 17 Jul 2018 16:23:38 -0500 Subject: [PATCH 049/294] Fix problems with launcher scripts --- programs/eosio-launcher/main.cpp | 6 +++--- scripts/eosio-tn_bounce.sh | 2 +- scripts/eosio-tn_down.sh | 2 +- scripts/eosio-tn_roll.sh | 2 +- scripts/eosio-tn_up.sh | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/programs/eosio-launcher/main.cpp b/programs/eosio-launcher/main.cpp index b6664608a53..0fe9652d49b 100644 --- a/programs/eosio-launcher/main.cpp +++ b/programs/eosio-launcher/main.cpp @@ -1652,8 +1652,8 @@ launcher_def::bounce (const string& node_numbers) { string node_num = node.name.substr( node.name.length() - 2 ); string cmd = "cd " + host.eosio_home + "; " + "export EOSIO_HOME=" + host.eosio_home + string("; ") - + "export EOSIO_TN_NODE=" + node_num + "; " - + "./scripts/eosio-tn_bounce.sh"; + + "export EOSIO_NODE=" + node_num + "; " + + "./scripts/eosio-tn_bounce.sh " + eosd_extra_args; cout << "Bouncing " << node.name << endl; if (!do_ssh(cmd, host.host_name)) { cerr << "Unable to bounce " << node.name << endl; @@ -1671,7 +1671,7 @@ launcher_def::down (const string& node_numbers) { string node_num = node.name.substr( node.name.length() - 2 ); string cmd = "cd " + host.eosio_home + "; " + "export EOSIO_HOME=" + host.eosio_home + "; " - + "export EOSIO_TN_NODE=" + node_num + "; " + + "export EOSIO_NODE=" + node_num + "; " + "export EOSIO_TN_RESTART_CONFIG_DIR=" + node.config_dir_name + "; " + "./scripts/eosio-tn_down.sh"; cout << "Taking down " << node.name << endl; diff --git a/scripts/eosio-tn_bounce.sh b/scripts/eosio-tn_bounce.sh index 202ac7b66d1..7062836c92c 100755 --- a/scripts/eosio-tn_bounce.sh +++ b/scripts/eosio-tn_bounce.sh @@ -3,7 +3,7 @@ # eosio-tn_bounce is used to restart a node that is acting badly or is down. # usage: eosio-tn_bounce.sh [arglist] # arglist will be passed to the node's command line. First with no modifiers -# then with --replay and then a third time with --resync +# then with --hard-replay-blockchain and then a third time with --delete-all-blocks # # the data directory and log file are set by this script. Do not pass them on # the command line. diff --git a/scripts/eosio-tn_down.sh b/scripts/eosio-tn_down.sh index ad8ca2106be..e13d1357b0a 100755 --- a/scripts/eosio-tn_down.sh +++ b/scripts/eosio-tn_down.sh @@ -20,7 +20,7 @@ running=`ps -e | grep $runtest | grep -cv grep ` if [ $running -ne 0 ]; then echo killing $prog - pkill -15 $prog + kill -15 $runtest for (( a = 1;11-$a; a = $(($a + 1)) )); do echo waiting for safe termination, pass $a diff --git a/scripts/eosio-tn_roll.sh b/scripts/eosio-tn_roll.sh index fe46d002b57..7c8f665c880 100755 --- a/scripts/eosio-tn_roll.sh +++ b/scripts/eosio-tn_roll.sh @@ -5,7 +5,7 @@ # all instances are restarted. # usage: eosio-tn_roll.sh [arglist] # arglist will be passed to the node's command line. First with no modifiers -# then with --replay and then a third time with --resync +# then with --hard-replay-blockchain and then a third time with --delete-all-blocks # # The data directory and log file are set by this script. Do not pass them on # the command line. diff --git a/scripts/eosio-tn_up.sh b/scripts/eosio-tn_up.sh index 451e6ffaf7f..9d1c0dd743b 100755 --- a/scripts/eosio-tn_up.sh +++ b/scripts/eosio-tn_up.sh @@ -66,7 +66,7 @@ fi if [ "$EOSIO_LEVEL" == replay ]; then echo starting with replay - relaunch $* --replay + relaunch $* --hard-replay-blockchain if [ "$connected" -eq 0 ]; then EOSIO_LEVEL=resync else @@ -74,6 +74,6 @@ if [ "$EOSIO_LEVEL" == replay ]; then fi fi if [ "$EOSIO_LEVEL" == resync ]; then - echo starting wih resync - relaunch $* --resync + echo starting wih delete-all-blocks + relaunch $* --delete-all-blocks fi From 8da78b4bfc4953089c4a878ec5ae5f1b49260043 Mon Sep 17 00:00:00 2001 From: Paul Calabrese Date: Wed, 18 Jul 2018 08:37:50 -0500 Subject: [PATCH 050/294] Fix spelling error --- scripts/eosio-tn_up.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/eosio-tn_up.sh b/scripts/eosio-tn_up.sh index 9d1c0dd743b..895322a5eee 100755 --- a/scripts/eosio-tn_up.sh +++ b/scripts/eosio-tn_up.sh @@ -74,6 +74,6 @@ if [ "$EOSIO_LEVEL" == replay ]; then fi fi if [ "$EOSIO_LEVEL" == resync ]; then - echo starting wih delete-all-blocks + echo starting with delete-all-blocks relaunch $* --delete-all-blocks fi From fe211680222a8dfe445e57114fcc484424746dcb Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 18 Jul 2018 11:21:05 -0400 Subject: [PATCH 051/294] Add abi cache to speed up serialization --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 256 ++++++++++++-------- 1 file changed, 160 insertions(+), 96 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 2033d84421d..0b5f0a13d3d 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -73,6 +73,13 @@ class mongo_db_plugin_impl { void process_irreversible_block(const chain::block_state_ptr&); void _process_irreversible_block(const chain::block_state_ptr&); + optional get_abi_serializer( account_name n ); + template fc::variant to_variant_with_abi( const T& obj ); + + void purge_abi_cache(); + + void update_account(const chain::action& act); + void init(); void wipe_database(); @@ -87,6 +94,7 @@ class mongo_db_plugin_impl { mongocxx::collection accounts; size_t queue_size = 0; + size_t abi_cache_size = 0; std::deque transaction_metadata_queue; std::deque transaction_metadata_process_queue; std::deque transaction_trace_queue; @@ -103,6 +111,24 @@ class mongo_db_plugin_impl { fc::optional chain_id; fc::microseconds abi_serializer_max_time; + struct by_account; + struct by_last_access; + + struct abi_cache { + account_name account; + fc::time_point last_accessed; + fc::optional serializer; + }; + + typedef boost::multi_index_container, member >, + ordered_non_unique< tag, member > + > + > abi_cache_index_t; + + abi_cache_index_t abi_cache_index; + static const account_name newaccount; static const account_name setabi; @@ -310,39 +336,6 @@ namespace { return blocks.find_one( make_document( kvp( "block_id", id ))); } - optional get_abi_serializer( account_name n, mongocxx::collection& accounts, const fc::microseconds& abi_serializer_max_time ) { - using bsoncxx::builder::basic::kvp; - using bsoncxx::builder::basic::make_document; - if( n.good()) { - try { - auto account = accounts.find_one( make_document( kvp("name", n.to_string())) ); - if(account) { - auto view = account->view(); - abi_def abi; - if( view.find( "abi" ) != view.end()) { - try { - abi = fc::json::from_string( bsoncxx::to_json( view["abi"].get_document())).as(); - } catch (...) { - ilog( "Unable to convert account abi to abi_def for ${n}", ( "n", n )); - return optional(); - } - return abi_serializer( abi, abi_serializer_max_time ); - } - } - } FC_CAPTURE_AND_LOG((n)) - } - return optional(); - } - - template - fc::variant to_variant_with_abi( const T& obj, mongocxx::collection& accounts, const fc::microseconds& abi_serializer_max_time ) { - fc::variant pretty_output; - abi_serializer::to_variant( obj, pretty_output, - [&]( account_name n ) { return get_abi_serializer( n, accounts, abi_serializer_max_time ); }, - abi_serializer_max_time ); - return pretty_output; - } - void handle_mongo_exception( const std::string& desc, int line_num ) { bool shutdown = true; try { @@ -385,66 +378,6 @@ void handle_mongo_exception( const std::string& desc, int line_num ) { } } - void update_account(mongocxx::collection& accounts, const chain::action& act) { - using bsoncxx::builder::basic::kvp; - using bsoncxx::builder::basic::make_document; - using namespace bsoncxx::types; - - if (act.account != chain::config::system_account_name) - return; - - try { - if( act.name == mongo_db_plugin_impl::newaccount ) { - auto now = std::chrono::duration_cast( - std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()} ); - auto newaccount = act.data_as(); - - // create new account - if( !accounts.insert_one( make_document( kvp( "name", newaccount.name.to_string()), - kvp( "createdAt", b_date{now} )))) { - elog( "Failed to insert account ${n}", ("n", newaccount.name)); - } - - } else if( act.name == mongo_db_plugin_impl::setabi ) { - auto now = std::chrono::duration_cast( - std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()} ); - auto setabi = act.data_as(); - auto from_account = find_account( accounts, setabi.account ); - if( !from_account ) { - if( !accounts.insert_one( make_document( kvp( "name", setabi.account.to_string()), - kvp( "createdAt", b_date{now} )))) { - elog( "Failed to insert account ${n}", ("n", setabi.account)); - } - from_account = find_account( accounts, setabi.account ); - } - if( from_account ) { - const abi_def& abi_def = fc::raw::unpack( setabi.abi ); - const string json_str = fc::json::to_string( abi_def ); - - try{ - auto update_from = make_document( - kvp( "$set", make_document( kvp( "abi", bsoncxx::from_json( json_str )), - kvp( "updatedAt", b_date{now} )))); - - try { - if( !accounts.update_one( make_document( kvp( "_id", from_account->view()["_id"].get_oid())), - update_from.view())) { - EOS_ASSERT( false, chain::mongo_db_update_fail, "Failed to udpdate account ${n}", ("n", setabi.account)); - } - } catch( ... ) { - handle_mongo_exception( "account update", __LINE__ ); - } - } catch( bsoncxx::exception& e ) { - elog( "Unable to convert abi JSON to MongoDB JSON: ${e}", ("e", e.what())); - elog( " JSON: ${j}", ("j", json_str)); - } - } - } - } catch( fc::exception& e ) { - // if unable to unpack native type, skip account creation - } - } - void add_data( bsoncxx::builder::basic::document& act_doc, mongocxx::collection& accounts, const chain::action& act, const fc::microseconds& abi_serializer_max_time ) { using bsoncxx::builder::basic::kvp; using bsoncxx::builder::basic::make_document; @@ -512,6 +445,67 @@ void add_data( bsoncxx::builder::basic::document& act_doc, mongocxx::collection& } // anonymous namespace +void mongo_db_plugin_impl::purge_abi_cache() { + if( abi_cache_index.size() < abi_cache_size ) return; + + // remove the oldest (smallest) last accessed + auto& idx = abi_cache_index.get(); + auto itr = idx.begin(); + if( itr != idx.end() ) { + idx.erase( itr ); + } +} + +optional mongo_db_plugin_impl::get_abi_serializer( account_name n ) { + using bsoncxx::builder::basic::kvp; + using bsoncxx::builder::basic::make_document; + if( n.good()) { + try { + + auto itr = abi_cache_index.find( n ); + if( itr != abi_cache_index.end() ) { + abi_cache_index.modify( itr, []( auto& entry ) { + entry.last_accessed = fc::time_point::now(); + }); + + return itr->serializer; + } + + auto account = accounts.find_one( make_document( kvp("name", n.to_string())) ); + if(account) { + auto view = account->view(); + abi_def abi; + if( view.find( "abi" ) != view.end()) { + try { + abi = fc::json::from_string( bsoncxx::to_json( view["abi"].get_document())).as(); + } catch (...) { + ilog( "Unable to convert account abi to abi_def for ${n}", ( "n", n )); + return optional(); + } + + purge_abi_cache(); // make room if necessary + abi_cache entry; + entry.account = n; + entry.last_accessed = fc::time_point::now(); + entry.serializer.emplace( abi, abi_serializer_max_time ); + abi_cache_index.insert( entry ); + return entry.serializer; + } + } + } FC_CAPTURE_AND_LOG((n)) + } + return optional(); +} + +template +fc::variant mongo_db_plugin_impl::to_variant_with_abi( const T& obj ) { + fc::variant pretty_output; + abi_serializer::to_variant( obj, pretty_output, + [&]( account_name n ) { return get_abi_serializer( n ); }, + abi_serializer_max_time ); + return pretty_output; +} + void mongo_db_plugin_impl::process_accepted_transaction( const chain::transaction_metadata_ptr& t ) { try { // always call since we need to capture setabi on accounts even if not storing transactions @@ -616,7 +610,7 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti } )); } try { - update_account( accounts, act ); + update_account( act ); } catch (...) { ilog( "Unable to update account for ${s}::${n}", ("s", act.account)( "n", act.name )); } @@ -776,7 +770,7 @@ void mongo_db_plugin_impl::_process_applied_transaction( const chain::transactio auto now = std::chrono::duration_cast( std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()}); - auto v = to_variant_with_abi( *t, accounts, abi_serializer_max_time ); + auto v = to_variant_with_abi( *t ); string json = fc::json::to_string( v ); try { const auto& value = bsoncxx::from_json( json ); @@ -861,7 +855,7 @@ void mongo_db_plugin_impl::_process_accepted_block( const chain::block_state_ptr kvp( "block_id", block_id_str ), kvp( "irreversible", b_bool{false} )); - auto v = to_variant_with_abi( *bs->block, accounts, abi_serializer_max_time ); + auto v = to_variant_with_abi( *bs->block ); json = fc::json::to_string( v ); try { const auto& value = bsoncxx::from_json( json ); @@ -966,6 +960,70 @@ void mongo_db_plugin_impl::_process_irreversible_block(const chain::block_state_ } } +void mongo_db_plugin_impl::update_account(const chain::action& act) +{ + using bsoncxx::builder::basic::kvp; + using bsoncxx::builder::basic::make_document; + using namespace bsoncxx::types; + + if (act.account != chain::config::system_account_name) + return; + + try { + if( act.name == mongo_db_plugin_impl::newaccount ) { + auto now = std::chrono::duration_cast( + std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()} ); + auto newaccount = act.data_as(); + + // create new account + if( !accounts.insert_one( make_document( kvp( "name", newaccount.name.to_string()), + kvp( "createdAt", b_date{now} )))) { + elog( "Failed to insert account ${n}", ("n", newaccount.name)); + } + + } else if( act.name == mongo_db_plugin_impl::setabi ) { + auto now = std::chrono::duration_cast( + std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()} ); + auto setabi = act.data_as(); + + abi_cache_index.erase( setabi.account ); + + auto from_account = find_account( accounts, setabi.account ); + if( !from_account ) { + if( !accounts.insert_one( make_document( kvp( "name", setabi.account.to_string()), + kvp( "createdAt", b_date{now} )))) { + elog( "Failed to insert account ${n}", ("n", setabi.account)); + } + from_account = find_account( accounts, setabi.account ); + } + if( from_account ) { + const abi_def& abi_def = fc::raw::unpack( setabi.abi ); + const string json_str = fc::json::to_string( abi_def ); + + try{ + auto update_from = make_document( + kvp( "$set", make_document( kvp( "abi", bsoncxx::from_json( json_str )), + kvp( "updatedAt", b_date{now} )))); + + try { + if( !accounts.update_one( make_document( kvp( "_id", from_account->view()["_id"].get_oid())), + update_from.view())) { + EOS_ASSERT( false, chain::mongo_db_update_fail, "Failed to udpdate account ${n}", ("n", setabi.account)); + } + } catch( ... ) { + handle_mongo_exception( "account update", __LINE__ ); + } + } catch( bsoncxx::exception& e ) { + elog( "Unable to convert abi JSON to MongoDB JSON: ${e}", ("e", e.what())); + elog( " JSON: ${j}", ("j", json_str)); + } + } + } + } catch( fc::exception& e ) { + // if unable to unpack native type, skip account creation + } +} + mongo_db_plugin_impl::mongo_db_plugin_impl() : mongo_inst{} , mongo_conn{} @@ -1077,6 +1135,8 @@ void mongo_db_plugin::set_program_options(options_description& cli, options_desc cfg.add_options() ("mongodb-queue-size,q", bpo::value()->default_value(256), "The target queue size between nodeos and MongoDB plugin thread.") + ("mongodb-abi-cache-size", bpo::value()->default_value(2048), + "The maximum size of the abi cache for serializing data.") ("mongodb-wipe", bpo::bool_switch()->default_value(false), "Required with --replay-blockchain, --hard-replay-blockchain, or --delete-all-blocks to wipe mongo db." "This option required to prevent accidental wipe of mongo db.") @@ -1114,6 +1174,10 @@ void mongo_db_plugin::plugin_initialize(const variables_map& options) if( options.count( "mongodb-queue-size" )) { my->queue_size = options.at( "mongodb-queue-size" ).as(); } + if( options.count( "mongodb-abi-cache-size" )) { + my->abi_cache_size = options.at( "mongodb-abi-cache-size" ).as(); + EOS_ASSERT( my->abi_cache_size > 0, chain::plugin_config_exception, "mongodb-abi-cache-size > 0 required" ); + } if( options.count( "mongodb-block-start" )) { my->start_block_num = options.at( "mongodb-block-start" ).as(); } From e333c9187976c87a10c5f877c36adf860a30a5e8 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 18 Jul 2018 13:34:33 -0400 Subject: [PATCH 052/294] Use cache when serializing action data --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 153 +++++++++----------- 1 file changed, 72 insertions(+), 81 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 0b5f0a13d3d..bd94094a783 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -78,6 +78,7 @@ class mongo_db_plugin_impl { void purge_abi_cache(); + void add_data(bsoncxx::builder::basic::document& act_doc, const chain::action& act); void update_account(const chain::action& act); void init(); @@ -318,23 +319,23 @@ void mongo_db_plugin_impl::consume_blocks() { namespace { - auto find_account(mongocxx::collection& accounts, const account_name& name) { - using bsoncxx::builder::basic::make_document; - using bsoncxx::builder::basic::kvp; - return accounts.find_one( make_document( kvp( "name", name.to_string()))); - } +auto find_account( mongocxx::collection& accounts, const account_name& name ) { + using bsoncxx::builder::basic::make_document; + using bsoncxx::builder::basic::kvp; + return accounts.find_one( make_document( kvp( "name", name.to_string()))); +} - auto find_transaction(mongocxx::collection& trans, const string& id) { - using bsoncxx::builder::basic::make_document; - using bsoncxx::builder::basic::kvp; - return trans.find_one( make_document( kvp( "trx_id", id ))); - } +auto find_transaction( mongocxx::collection& trans, const string& id ) { + using bsoncxx::builder::basic::make_document; + using bsoncxx::builder::basic::kvp; + return trans.find_one( make_document( kvp( "trx_id", id ))); +} - auto find_block(mongocxx::collection& blocks, const string& id) { - using bsoncxx::builder::basic::make_document; - using bsoncxx::builder::basic::kvp; - return blocks.find_one( make_document( kvp( "block_id", id ))); - } +auto find_block( mongocxx::collection& blocks, const string& id ) { + using bsoncxx::builder::basic::make_document; + using bsoncxx::builder::basic::kvp; + return blocks.find_one( make_document( kvp( "block_id", id ))); +} void handle_mongo_exception( const std::string& desc, int line_num ) { bool shutdown = true; @@ -378,71 +379,6 @@ void handle_mongo_exception( const std::string& desc, int line_num ) { } } -void add_data( bsoncxx::builder::basic::document& act_doc, mongocxx::collection& accounts, const chain::action& act, const fc::microseconds& abi_serializer_max_time ) { - using bsoncxx::builder::basic::kvp; - using bsoncxx::builder::basic::make_document; - try { - if( act.account == chain::config::system_account_name ) { - if( act.name == mongo_db_plugin_impl::setabi ) { - auto setabi = act.data_as(); - try { - const abi_def& abi_def = fc::raw::unpack( setabi.abi ); - const string json_str = fc::json::to_string( abi_def ); - - act_doc.append( - kvp( "data", make_document( kvp( "account", setabi.account.to_string()), - kvp( "abi_def", bsoncxx::from_json( json_str ))))); - return; - } catch( bsoncxx::exception& ) { - // better error handling below - } catch( fc::exception& e ) { - ilog( "Unable to convert action abi_def to json for ${n}", ("n", setabi.account.to_string())); - } - } - } - auto account = find_account( accounts, act.account ); - if( account ) { - auto from_account = *account; - abi_def abi; - if( from_account.view().find( "abi" ) != from_account.view().end()) { - try { - abi = fc::json::from_string( bsoncxx::to_json( from_account.view()["abi"].get_document())).as(); - } catch( ... ) { - ilog( "Unable to convert account abi to abi_def for ${s}::${n}", ("s", act.account)( "n", act.name )); - } - } - string json; - try { - abi_serializer abis; - abis.set_abi( abi, abi_serializer_max_time ); - auto v = abis.binary_to_variant( abis.get_action_type( act.name ), act.data, abi_serializer_max_time ); - json = fc::json::to_string( v ); - - const auto& value = bsoncxx::from_json( json ); - act_doc.append( kvp( "data", value )); - return; - } catch( bsoncxx::exception& e ) { - ilog( "Unable to convert EOS JSON to MongoDB JSON: ${e}", ("e", e.what())); - ilog( " EOS JSON: ${j}", ("j", json)); - ilog( " Storing data has hex." ); - } - } - } catch( std::exception& e ) { - ilog( "Unable to convert action.data to ABI: ${s}::${n}, std what: ${e}", - ("s", act.account)( "n", act.name )( "e", e.what())); - } catch (fc::exception& e) { - if (act.name != "onblock") { // eosio::onblock not in original eosio.system abi - ilog( "Unable to convert action.data to ABI: ${s}::${n}, fc exception: ${e}", - ("s", act.account)( "n", act.name )( "e", e.to_detail_string())); - } - } catch( ... ) { - ilog( "Unable to convert action.data to ABI: ${s}::${n}, unknown exception", - ("s", act.account)( "n", act.name )); - } - // if anything went wrong just store raw hex_data - act_doc.append( kvp( "hex_data", fc::variant( act.data ).as_string())); -} - } // anonymous namespace void mongo_db_plugin_impl::purge_abi_cache() { @@ -615,7 +551,7 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti ilog( "Unable to update account for ${s}::${n}", ("s", act.account)( "n", act.name )); } if( start_block_reached ) { - add_data( act_doc, accounts, act, abi_serializer_max_time ); + add_data( act_doc, act ); act_array.append( act_doc ); mongocxx::model::insert_one insert_op{act_doc.view()}; bulk_actions.append( insert_op ); @@ -960,6 +896,61 @@ void mongo_db_plugin_impl::_process_irreversible_block(const chain::block_state_ } } +void mongo_db_plugin_impl::add_data( bsoncxx::builder::basic::document& act_doc, const chain::action& act ) +{ + using bsoncxx::builder::basic::kvp; + using bsoncxx::builder::basic::make_document; + try { + if( act.account == chain::config::system_account_name ) { + if( act.name == mongo_db_plugin_impl::setabi ) { + auto setabi = act.data_as(); + try { + const abi_def& abi_def = fc::raw::unpack( setabi.abi ); + const string json_str = fc::json::to_string( abi_def ); + + act_doc.append( + kvp( "data", make_document( kvp( "account", setabi.account.to_string()), + kvp( "abi_def", bsoncxx::from_json( json_str ))))); + return; + } catch( bsoncxx::exception& ) { + // better error handling below + } catch( fc::exception& e ) { + ilog( "Unable to convert action abi_def to json for ${n}", ("n", setabi.account.to_string())); + } + } + } + auto serializer = get_abi_serializer( act.account ); + if( serializer.valid() ) { + string json; + try { + auto v = serializer->binary_to_variant( serializer->get_action_type( act.name ), act.data, abi_serializer_max_time ); + json = fc::json::to_string( v ); + + const auto& value = bsoncxx::from_json( json ); + act_doc.append( kvp( "data", value )); + return; + } catch( bsoncxx::exception& e ) { + ilog( "Unable to convert EOS JSON to MongoDB JSON: ${e}", ("e", e.what())); + ilog( " EOS JSON: ${j}", ("j", json)); + ilog( " Storing data has hex." ); + } + } + } catch( std::exception& e ) { + ilog( "Unable to convert action.data to ABI: ${s}::${n}, std what: ${e}", + ("s", act.account)( "n", act.name )( "e", e.what())); + } catch (fc::exception& e) { + if (act.name != "onblock") { // eosio::onblock not in original eosio.system abi + ilog( "Unable to convert action.data to ABI: ${s}::${n}, fc exception: ${e}", + ("s", act.account)( "n", act.name )( "e", e.to_detail_string())); + } + } catch( ... ) { + ilog( "Unable to convert action.data to ABI: ${s}::${n}, unknown exception", + ("s", act.account)( "n", act.name )); + } + // if anything went wrong just store raw hex_data + act_doc.append( kvp( "hex_data", fc::variant( act.data ).as_string())); +} + void mongo_db_plugin_impl::update_account(const chain::action& act) { using bsoncxx::builder::basic::kvp; From b0a4c74fede6868d2687c24499695a3715329d98 Mon Sep 17 00:00:00 2001 From: Scott Sallinen Date: Wed, 18 Jul 2018 15:33:12 -0700 Subject: [PATCH 053/294] Add filter-out history config option --- plugins/history_plugin/history_plugin.cpp | 69 ++++++++++++++++++----- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/plugins/history_plugin/history_plugin.cpp b/plugins/history_plugin/history_plugin.cpp index d8af87408ec..49de13d29c7 100644 --- a/plugins/history_plugin/history_plugin.cpp +++ b/plugins/history_plugin/history_plugin.cpp @@ -139,29 +139,56 @@ namespace eosio { public: bool bypass_filter = false; std::set filter_on; + std::set filter_out; chain_plugin* chain_plug = nullptr; fc::optional applied_transaction_connection; - bool filter( const action_trace& act ) { - if( bypass_filter ) - return true; - if( filter_on.find({ act.receipt.receiver, act.act.name, 0 }) != filter_on.end() ) - return true; - for( const auto& a : act.act.authorization ) - if( filter_on.find({ act.receipt.receiver, act.act.name, a.actor }) != filter_on.end() ) - return true; - return false; - } + bool filter(const action_trace& act) { + bool pass_on = false; + if (bypass_filter) { + pass_on = true; + } + if (filter_on.find({ act.receipt.receiver, act.act.name, 0 }) != filter_on.end()) { + pass_on = true; + } + for (const auto& a : act.act.authorization) { + if (filter_on.find({ act.receipt.receiver, act.act.name, a.actor }) != filter_on.end()) { + pass_on = true; + } + } + + if (!pass_on) { return false; } + + if (filter_out.find({ act.receipt.receiver, 0, 0 }) != filter_out.end()) { + return false; + } + if (filter_out.find({ act.receipt.receiver, act.act.name, 0 }) != filter_out.end()) { + return false; + } + for (const auto& a : act.act.authorization) { + if (filter_out.find({ act.receipt.receiver, act.act.name, a.actor }) != filter_out.end()) { + return false; + } + } + + return true; + } set account_set( const action_trace& act ) { set result; result.insert( act.receipt.receiver ); - for( const auto& a : act.act.authorization ) + for( const auto& a : act.act.authorization ) { if( bypass_filter || filter_on.find({ act.receipt.receiver, act.act.name, 0}) != filter_on.end() || - filter_on.find({ act.receipt.receiver, act.act.name, a.actor }) != filter_on.end() ) - result.insert( a.actor ); + filter_on.find({ act.receipt.receiver, act.act.name, a.actor }) != filter_on.end() ) { + if ((filter_out.find({ act.receipt.receiver, 0, 0 }) == filter_out.end()) && + (filter_out.find({ act.receipt.receiver, act.act.name, 0 }) == filter_out.end()) && + (filter_out.find({ act.receipt.receiver, act.act.name, a.actor }) == filter_out.end())) { + result.insert( a.actor ); + } + } + } return result; } @@ -263,6 +290,10 @@ namespace eosio { ("filter-on,f", bpo::value>()->composing(), "Track actions which match receiver:action:actor. Actor may be blank to include all. Receiver and Action may not be blank.") ; + cfg.add_options() + ("filter-out,f", bpo::value>()->composing(), + "Do not track actions which match receiver:action:actor. Action and Actor both blank excludes all from reciever. Actor blank excludes all from reciever:action. Receiver may not be blank.") + ; } void history_plugin::plugin_initialize(const variables_map& options) { @@ -284,6 +315,18 @@ namespace eosio { my->filter_on.insert( fe ); } } + if( options.count( "filter-out" )) { + auto fo = options.at( "filter-out" ).as>(); + for( auto& s : fo ) { + std::vector v; + boost::split( v, s, boost::is_any_of( ":" )); + EOS_ASSERT( v.size() == 3, fc::invalid_arg_exception, "Invalid value ${s} for --filter-out", ("s", s)); + filter_entry fe{v[0], v[1], v[2]}; + EOS_ASSERT( fe.receiver.value, fc::invalid_arg_exception, + "Invalid value ${s} for --filter-out", ("s", s)); + my->filter_out.insert( fe ); + } + } my->chain_plug = app().find_plugin(); auto& chain = my->chain_plug->chain(); From 418c7b52daa46e953e71d1baf872bf66bfda17f6 Mon Sep 17 00:00:00 2001 From: Andrianto Lie Date: Wed, 18 Jul 2018 17:16:20 +0800 Subject: [PATCH 054/294] modify wastPath and abiPath of cleos set contract to be relative to contract dir --- programs/cleos/main.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 7ec0f108489..aab5b25ec62 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2115,11 +2115,12 @@ int main( int argc, char** argv ) { if( cpath.filename().generic_string() == "." ) cpath = cpath.parent_path(); - if( wastPath.empty() ) - { + if( wastPath.empty() ) { wastPath = (cpath / (cpath.filename().generic_string()+".wasm")).generic_string(); if (!fc::exists(wastPath)) wastPath = (cpath / (cpath.filename().generic_string()+".wast")).generic_string(); + } else { + wastPath = (cpath / wastPath).generic_string(); } std::cerr << localized(("Reading WAST/WASM from " + wastPath + "...").c_str()) << std::endl; @@ -2147,9 +2148,10 @@ int main( int argc, char** argv ) { fc::path cpath(contractPath); if( cpath.filename().generic_string() == "." ) cpath = cpath.parent_path(); - if( abiPath.empty() ) - { + if( abiPath.empty() ) { abiPath = (cpath / (cpath.filename().generic_string()+".abi")).generic_string(); + } else { + abiPath = (cpath / abiPath).generic_string(); } EOS_ASSERT( fc::exists( abiPath ), abi_file_not_found, "no abi file found ${f}", ("f", abiPath) ); From 96a769702e83a2bc78356e096c985fbe0b292a69 Mon Sep 17 00:00:00 2001 From: Andrianto Lie Date: Thu, 19 Jul 2018 13:49:35 +0800 Subject: [PATCH 055/294] Change cleos set contract command used in the unit tests --- testnet.template | 10 +++++----- tests/Cluster.py | 12 ++++++------ tests/consensus-validation-malicious-producers.py | 4 ++-- tests/nodeos_run_test.py | 12 ++++++------ tests/p2p_network_test.py | 4 ++-- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/testnet.template b/testnet.template index f161b1335c7..12e49a799fc 100644 --- a/testnet.template +++ b/testnet.template @@ -75,7 +75,7 @@ wcmd create -n ignition # ------ DO NOT ALTER THE NEXT LINE ------- ###INSERT prodkeys -ecmd set contract eosio contracts/eosio.bios contracts/eosio.bios/eosio.bios.wast contracts/eosio.bios/eosio.bios.abi +ecmd set contract eosio contracts/eosio.bios eosio.bios.wast eosio.bios.abi # Create required system accounts ecmd create key @@ -94,9 +94,9 @@ ecmd create account eosio eosio.token $pubsyskey $pubsyskey ecmd create account eosio eosio.vpay $pubsyskey $pubsyskey ecmd create account eosio eosio.sudo $pubsyskey $pubsyskey -ecmd set contract eosio.token contracts/eosio.token contracts/eosio.token/eosio.token.wast contracts/eosio.token/eosio.token.abi -ecmd set contract eosio.msig contracts/eosio.msig contracts/eosio.msig/eosio.msig.wast contracts/eosio.msig/eosio.msig.abi -ecmd set contract eosio.sudo contracts/eosio.sudo contracts/eosio.sudo/eosio.sudo.wast contracts/eosio.sudo/eosio.sudo.abi +ecmd set contract eosio.token contracts/eosio.token eosio.token.wast eosio.token.abi +ecmd set contract eosio.msig contracts/eosio.msig eosio.msig.wast eosio.msig.abi +ecmd set contract eosio.sudo contracts/eosio.sudo eosio.sudo.wast eosio.sudo.abi echo ===== Start: $step ============ >> $logfile echo executing: cleos --wallet-url $wdurl --url http://$bioshost:$biosport push action eosio.token create '[ "eosio", "10000000000.0000 SYS" ]' -p eosio.token | tee -a $logfile @@ -107,7 +107,7 @@ programs/cleos/cleos --wallet-url $wdurl --url http://$bioshost:$biosport push a echo ==== End: $step ============== >> $logfile step=$(($step + 1)) -ecmd set contract eosio contracts/eosio.system contracts/eosio.system/eosio.system.wast contracts/eosio.system/eosio.system.abi +ecmd set contract eosio contracts/eosio.system eosio.system.wast eosio.system.abi # Manual deployers, add a series of lines below this block that looks like: # cacmd $PRODNAME[0] $OWNERKEY[0] $ACTIVEKEY[0] diff --git a/tests/Cluster.py b/tests/Cluster.py index b60ce6f4035..82e7c4b49a5 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -722,8 +722,8 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio contract="eosio.bios" contractDir="contracts/%s" % (contract) - wastFile="contracts/%s/%s.wast" % (contract, contract) - abiFile="contracts/%s/%s.abi" % (contract, contract) + wastFile="%s.wast" % (contract) + abiFile="%s.abi" % (contract) Utils.Print("Publish %s contract" % (contract)) trans=biosNode.publishContract(eosioAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) if trans is None: @@ -846,8 +846,8 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio contract="eosio.token" contractDir="contracts/%s" % (contract) - wastFile="contracts/%s/%s.wast" % (contract, contract) - abiFile="contracts/%s/%s.abi" % (contract, contract) + wastFile="%s.wast" % (contract) + abiFile="%s.abi" % (contract) Utils.Print("Publish %s contract" % (contract)) trans=biosNode.publishContract(eosioTokenAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) if trans is None: @@ -901,8 +901,8 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio contract="eosio.system" contractDir="contracts/%s" % (contract) - wastFile="contracts/%s/%s.wast" % (contract, contract) - abiFile="contracts/%s/%s.abi" % (contract, contract) + wastFile="%s.wast" % (contract) + abiFile="%s.abi" % (contract) Utils.Print("Publish %s contract" % (contract)) trans=biosNode.publishContract(eosioAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) if trans is None: diff --git a/tests/consensus-validation-malicious-producers.py b/tests/consensus-validation-malicious-producers.py index b31fd8ba455..875de5201fa 100755 --- a/tests/consensus-validation-malicious-producers.py +++ b/tests/consensus-validation-malicious-producers.py @@ -298,8 +298,8 @@ def myTest(transWillEnterBlock): error("Failed to create account %s" % (currencyAccount.name)) return False - wastFile="contracts/currency/currency.wast" - abiFile="contracts/currency/currency.abi" + wastFile="currency.wast" + abiFile="currency.abi" Print("Publish contract") trans=node.publishContract(currencyAccount.name, wastFile, abiFile, waitForTransBlock=True) if trans is None: diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index ed537bdcf32..bdacb5eb693 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -357,8 +357,8 @@ def cmdError(name, cmdCode=0, exitNow=False): errorExit("FAILURE - get code currency1111 failed", raw=True) contractDir="contracts/eosio.token" - wastFile="contracts/eosio.token/eosio.token.wast" - abiFile="contracts/eosio.token/eosio.token.abi" + wastFile="eosio.token.wast" + abiFile="eosio.token.abi" Print("Publish contract") trans=node.publishContract(currencyAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) if trans is None: @@ -644,8 +644,8 @@ def cmdError(name, cmdCode=0, exitNow=False): Print("upload exchange contract") contractDir="contracts/exchange" - wastFile="contracts/exchange/exchange.wast" - abiFile="contracts/exchange/exchange.abi" + wastFile="exchange.wast" + abiFile="exchange.abi" Print("Publish exchange contract") trans=node.publishContract(exchangeAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) if trans is None: @@ -653,8 +653,8 @@ def cmdError(name, cmdCode=0, exitNow=False): errorExit("Failed to publish contract.") contractDir="contracts/simpledb" - wastFile="contracts/simpledb/simpledb.wast" - abiFile="contracts/simpledb/simpledb.abi" + wastFile="simpledb.wast" + abiFile="simpledb.abi" Print("Setting simpledb contract without simpledb account was causing core dump in %s." % (ClientName)) Print("Verify %s generates an error, but does not core dump." % (ClientName)) retMap=node.publishContract("simpledb", contractDir, wastFile, abiFile, shouldFail=True) diff --git a/tests/p2p_network_test.py b/tests/p2p_network_test.py index e61dac24e6f..a61996576a2 100755 --- a/tests/p2p_network_test.py +++ b/tests/p2p_network_test.py @@ -152,8 +152,8 @@ Print("host %s: %s" % (hosts[i], trans)) -wastFile="contracts/eosio.system/eosio.system.wast" -abiFile="contracts/eosio.system/eosio.system.abi" +wastFile="eosio.system.wast" +abiFile="eosio.system.abi" Print("\nPush system contract %s %s" % (wastFile, abiFile)) trans=node0.publishContract(eosio.name, wastFile, abiFile, waitForTransBlock=True) if trans is None: From fad0546e8d255a01b272b83bf37f5811268e3a08 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 19 Jul 2018 16:02:16 -0400 Subject: [PATCH 056/294] Add pub_keys and account_controls collections. --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 199 ++++++++++++++++++-- 1 file changed, 185 insertions(+), 14 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index bd94094a783..f348198d18b 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -81,6 +81,14 @@ class mongo_db_plugin_impl { void add_data(bsoncxx::builder::basic::document& act_doc, const chain::action& act); void update_account(const chain::action& act); + void add_pub_keys( const vector& keys, const account_name& name, + const permission_name& permission, const std::chrono::milliseconds& now ); + void remove_pub_keys( const account_name& name, const permission_name& permission ); + void add_account_control( const vector& controlling_accounts, + const account_name& name, const permission_name& permission, + const std::chrono::milliseconds& now ); + void remove_account_control( const account_name& name, const permission_name& permission ); + void init(); void wipe_database(); @@ -130,8 +138,12 @@ class mongo_db_plugin_impl { abi_cache_index_t abi_cache_index; - static const account_name newaccount; - static const account_name setabi; + static const action_name newaccount; + static const action_name setabi; + static const action_name updateauth; + static const action_name deleteauth; + static const permission_name owner; + static const permission_name active; static const std::string block_states_col; static const std::string blocks_col; @@ -139,10 +151,16 @@ class mongo_db_plugin_impl { static const std::string trans_traces_col; static const std::string actions_col; static const std::string accounts_col; + static const std::string pub_keys_col; + static const std::string account_controls_col; }; -const account_name mongo_db_plugin_impl::newaccount = "newaccount"; -const account_name mongo_db_plugin_impl::setabi = "setabi"; +const action_name mongo_db_plugin_impl::newaccount = N(newaccount); +const action_name mongo_db_plugin_impl::setabi = N(setabi); +const action_name mongo_db_plugin_impl::updateauth = N(updateauth); +const action_name mongo_db_plugin_impl::deleteauth = N(deleteauth); +const permission_name mongo_db_plugin_impl::owner = N(owner); +const permission_name mongo_db_plugin_impl::active = N(active); const std::string mongo_db_plugin_impl::block_states_col = "block_states"; const std::string mongo_db_plugin_impl::blocks_col = "blocks"; @@ -150,6 +168,8 @@ const std::string mongo_db_plugin_impl::trans_col = "transactions"; const std::string mongo_db_plugin_impl::trans_traces_col = "transaction_traces"; const std::string mongo_db_plugin_impl::actions_col = "actions"; const std::string mongo_db_plugin_impl::accounts_col = "accounts"; +const std::string mongo_db_plugin_impl::pub_keys_col = "pub_keys"; +const std::string mongo_db_plugin_impl::account_controls_col = "account_controls"; namespace { @@ -826,8 +846,8 @@ void mongo_db_plugin_impl::_process_irreversible_block(const chain::block_state_ using bsoncxx::builder::basic::make_document; using bsoncxx::builder::basic::kvp; - auto blocks = mongo_conn[db_name][blocks_col]; // Blocks - auto trans = mongo_conn[db_name][trans_col]; // Transactions + auto blocks = mongo_conn[db_name][blocks_col]; + auto trans = mongo_conn[db_name][trans_col]; const auto block_id = bs->block->id(); const auto block_id_str = block_id.str(); @@ -951,6 +971,118 @@ void mongo_db_plugin_impl::add_data( bsoncxx::builder::basic::document& act_doc, act_doc.append( kvp( "hex_data", fc::variant( act.data ).as_string())); } + +void mongo_db_plugin_impl::add_pub_keys( const vector& keys, const account_name& name, + const permission_name& permission, const std::chrono::milliseconds& now ) +{ + using bsoncxx::builder::basic::kvp; + using namespace bsoncxx::types; + + if( keys.empty()) return; + + auto pub_keys = mongo_conn[db_name][pub_keys_col]; + + mongocxx::bulk_write bulk_pub_keys = pub_keys.create_bulk_write(); + + for( const auto& pub_key_weight : keys ) { + auto doc = bsoncxx::builder::basic::document(); + + doc.append( kvp( "account", name.to_string()), + kvp( "public_key", pub_key_weight.key.operator string()), + kvp( "permission", permission.to_string()), + kvp( "createdAt", b_date{now} )); + + mongocxx::model::insert_one insert_op{doc.view()}; + bulk_pub_keys.append( insert_op ); + } + + try { + if( !bulk_pub_keys.execute()) { + EOS_ASSERT( false, chain::mongo_db_insert_fail, + "Bulk pub_keys insert failed for account: ${a}, permission: ${p}", + ("a", name)( "p", permission )); + } + } catch (...) { + handle_mongo_exception( "pub_keys insert", __LINE__ ); + } +} + +void mongo_db_plugin_impl::remove_pub_keys( const account_name& name, const permission_name& permission ) +{ + using bsoncxx::builder::basic::kvp; + using bsoncxx::builder::basic::make_document; + + auto pub_keys = mongo_conn[db_name][pub_keys_col]; + + try { + auto result = pub_keys.delete_many( make_document( kvp( "account", name.to_string()), + kvp( "permission", permission.to_string()))); + if( !result ) { + EOS_ASSERT( false, chain::mongo_db_update_fail, + "pub_keys delete failed for account: ${a}, permission: ${p}", + ("a", name)( "p", permission )); + } + } catch (...) { + handle_mongo_exception( "pub_keys delete", __LINE__ ); + } +} + +void mongo_db_plugin_impl::add_account_control( const vector& controlling_accounts, + const account_name& name, const permission_name& permission, + const std::chrono::milliseconds& now ) +{ + using bsoncxx::builder::basic::kvp; + using namespace bsoncxx::types; + + if( controlling_accounts.empty()) return; + + auto account_controls = mongo_conn[db_name][account_controls_col]; + + mongocxx::bulk_write bulk = account_controls.create_bulk_write(); + + for( const auto& controlling_account : controlling_accounts ) { + auto doc = bsoncxx::builder::basic::document(); + + doc.append( kvp( "controlled_account", name.to_string()), + kvp( "controlled_permission", permission.to_string()), + kvp( "controlling_account", controlling_account.permission.actor.to_string()), + kvp( "createdAt", b_date{now} )); + + mongocxx::model::insert_one insert_op{doc.view()}; + bulk.append( insert_op ); + } + + try { + if( !bulk.execute()) { + EOS_ASSERT( false, chain::mongo_db_insert_fail, + "Bulk account_controls insert failed for account: ${a}, permission: ${p}", + ("a", name)( "p", permission )); + } + } catch (...) { + handle_mongo_exception( "account_controls insert", __LINE__ ); + } +} + +void mongo_db_plugin_impl::remove_account_control( const account_name& name, const permission_name& permission ) +{ + using bsoncxx::builder::basic::kvp; + using bsoncxx::builder::basic::make_document; + + auto account_controls = mongo_conn[db_name][account_controls_col]; + + try { + auto result = account_controls.delete_many( make_document( kvp( "controlled_account", name.to_string()), + kvp( "controlled_permission", permission.to_string()))); + if( !result ) { + EOS_ASSERT( false, chain::mongo_db_update_fail, + "account_controls delete failed for account: ${a}, permission: ${p}", + ("a", name)( "p", permission )); + } + } catch (...) { + handle_mongo_exception( "account_controls delete", __LINE__ ); + } +} + void mongo_db_plugin_impl::update_account(const chain::action& act) { using bsoncxx::builder::basic::kvp; @@ -961,18 +1093,38 @@ void mongo_db_plugin_impl::update_account(const chain::action& act) return; try { - if( act.name == mongo_db_plugin_impl::newaccount ) { - auto now = std::chrono::duration_cast( + + if( act.name == newaccount ) { + std::chrono::milliseconds now = std::chrono::duration_cast( std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()} ); - auto newaccount = act.data_as(); + auto newacc = act.data_as(); // create new account - if( !accounts.insert_one( make_document( kvp( "name", newaccount.name.to_string()), + if( !accounts.insert_one( make_document( kvp( "name", newacc.name.to_string()), kvp( "createdAt", b_date{now} )))) { - elog( "Failed to insert account ${n}", ("n", newaccount.name)); + elog( "Failed to insert account ${n}", ("n", newacc.name)); } - } else if( act.name == mongo_db_plugin_impl::setabi ) { + add_pub_keys( newacc.owner.keys, newacc.name, owner, now ); + add_account_control( newacc.owner.accounts, newacc.name, owner, now ); + add_pub_keys( newacc.active.keys, newacc.name, active, now ); + add_account_control( newacc.active.accounts, newacc.name, active, now ); + + } else if( act.name == updateauth ) { + auto now = std::chrono::duration_cast( + std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()} ); + const auto update = act.data_as(); + remove_pub_keys(update.account, update.permission); + remove_account_control(update.account, update.permission); + add_pub_keys(update.auth.keys, update.account, update.permission, now); + add_account_control(update.auth.accounts, update.account, update.permission, now); + + } else if( act.name == deleteauth ) { + const auto del = act.data_as(); + remove_pub_keys( del.account, del.permission ); + remove_account_control(del.account, del.permission); + + } else if( act.name == setabi ) { auto now = std::chrono::duration_cast( std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()} ); auto setabi = act.data_as(); @@ -1044,6 +1196,8 @@ void mongo_db_plugin_impl::wipe_database() { auto trans_traces = mongo_conn[db_name][trans_traces_col]; auto actions = mongo_conn[db_name][actions_col]; accounts = mongo_conn[db_name][accounts_col]; + auto pub_keys = mongo_conn[db_name][pub_keys_col]; + auto account_controls = mongo_conn[db_name][account_controls_col]; block_states.drop(); blocks.drop(); @@ -1051,6 +1205,8 @@ void mongo_db_plugin_impl::wipe_database() { trans_traces.drop(); actions.drop(); accounts.drop(); + pub_keys.drop(); + account_controls.drop(); } void mongo_db_plugin_impl::init() { @@ -1079,7 +1235,7 @@ void mongo_db_plugin_impl::init() { try { // blocks indexes - auto blocks = mongo_conn[db_name][blocks_col]; // Blocks + auto blocks = mongo_conn[db_name][blocks_col]; blocks.create_index( bsoncxx::from_json( R"xxx({ "block_num" : 1 })xxx" )); blocks.create_index( bsoncxx::from_json( R"xxx({ "block_id" : 1 })xxx" )); @@ -1091,11 +1247,26 @@ void mongo_db_plugin_impl::init() { accounts.create_index( bsoncxx::from_json( R"xxx({ "name" : 1 })xxx" )); // transactions indexes - auto trans = mongo_conn[db_name][trans_col]; // Transactions + auto trans = mongo_conn[db_name][trans_col]; trans.create_index( bsoncxx::from_json( R"xxx({ "trx_id" : 1 })xxx" )); + auto trans_trace = mongo_conn[db_name][trans_traces_col]; + trans_trace.create_index( bsoncxx::from_json( R"xxx({ "id" : 1 })xxx" )); + + // actions indexes auto actions = mongo_conn[db_name][actions_col]; actions.create_index( bsoncxx::from_json( R"xxx({ "trx_id" : 1 })xxx" )); + + // pub_keys indexes + auto pub_keys = mongo_conn[db_name][pub_keys_col]; + pub_keys.create_index( bsoncxx::from_json( R"xxx({ "account" : 1, "permission" : 1 })xxx" )); + pub_keys.create_index( bsoncxx::from_json( R"xxx({ "public_key" : 1 })xxx" )); + + // account_controls indexes + auto account_controls = mongo_conn[db_name][account_controls_col]; + account_controls.create_index( bsoncxx::from_json( R"xxx({ "controlled_account" : 1, "controlled_permission" : 1 })xxx" )); + account_controls.create_index( bsoncxx::from_json( R"xxx({ "controlling_account" : 1 })xxx" )); + } catch(...) { handle_mongo_exception("create indexes", __LINE__); } From bb5a149ca3d51258ec51b52f40fdb61f1fc11b04 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 20 Jul 2018 11:09:46 -0400 Subject: [PATCH 057/294] Update comment --- .../include/eosio/mongo_db_plugin/mongo_db_plugin.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mongo_db_plugin/include/eosio/mongo_db_plugin/mongo_db_plugin.hpp b/plugins/mongo_db_plugin/include/eosio/mongo_db_plugin/mongo_db_plugin.hpp index 6ee85f07bc8..b9456898b6d 100644 --- a/plugins/mongo_db_plugin/include/eosio/mongo_db_plugin/mongo_db_plugin.hpp +++ b/plugins/mongo_db_plugin/include/eosio/mongo_db_plugin/mongo_db_plugin.hpp @@ -20,6 +20,8 @@ using mongo_db_plugin_impl_ptr = std::shared_ptr; * blocks * transaction_traces * transactions + * pub_keys + * account_controls * * See data dictionary (DB Schema Definition - EOS API) for description of MongoDB schema. * From f00da2b863745b572a7020f21553442c3ccd3af6 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 20 Jul 2018 11:10:55 -0400 Subject: [PATCH 058/294] Upsert accounts, pub_keys, and account_controls --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 146 +++++++++++--------- 1 file changed, 83 insertions(+), 63 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index f348198d18b..941c0f7ed43 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -92,6 +92,8 @@ class mongo_db_plugin_impl { void init(); void wipe_database(); + template void queue(Queue& queue, const Entry& e); + bool configured{false}; bool wipe_database_on_startup{false}; uint32_t start_block_num = 0; @@ -102,7 +104,8 @@ class mongo_db_plugin_impl { mongocxx::client mongo_conn; mongocxx::collection accounts; - size_t queue_size = 0; + size_t max_queue_size = 0; + int queue_sleep_time = 0; size_t abi_cache_size = 0; std::deque transaction_metadata_queue; std::deque transaction_metadata_process_queue; @@ -155,12 +158,12 @@ class mongo_db_plugin_impl { static const std::string account_controls_col; }; -const action_name mongo_db_plugin_impl::newaccount = N(newaccount); -const action_name mongo_db_plugin_impl::setabi = N(setabi); -const action_name mongo_db_plugin_impl::updateauth = N(updateauth); -const action_name mongo_db_plugin_impl::deleteauth = N(deleteauth); -const permission_name mongo_db_plugin_impl::owner = N(owner); -const permission_name mongo_db_plugin_impl::active = N(active); +const action_name mongo_db_plugin_impl::newaccount = chain::newaccount::get_name(); +const action_name mongo_db_plugin_impl::setabi = chain::setabi::get_name(); +const action_name mongo_db_plugin_impl::updateauth = chain::updateauth::get_name(); +const action_name mongo_db_plugin_impl::deleteauth = chain::deleteauth::get_name(); +const permission_name mongo_db_plugin_impl::owner = chain::config::owner_name; +const permission_name mongo_db_plugin_impl::active = chain::config::active_name; const std::string mongo_db_plugin_impl::block_states_col = "block_states"; const std::string mongo_db_plugin_impl::blocks_col = "blocks"; @@ -171,36 +174,29 @@ const std::string mongo_db_plugin_impl::accounts_col = "accounts"; const std::string mongo_db_plugin_impl::pub_keys_col = "pub_keys"; const std::string mongo_db_plugin_impl::account_controls_col = "account_controls"; -namespace { - template -void queue(boost::mutex& mtx, boost::condition_variable& condition, Queue& queue, const Entry& e, size_t queue_size) { - int sleep_time = 100; - size_t last_queue_size = 0; - boost::mutex::scoped_lock lock(mtx); - if (queue.size() > queue_size) { +void mongo_db_plugin_impl::queue( Queue& queue, const Entry& e ) { + boost::mutex::scoped_lock lock( mtx ); + auto queue_size = queue.size(); + if( queue_size > max_queue_size ) { lock.unlock(); condition.notify_one(); - if (last_queue_size < queue.size()) { - sleep_time += 100; - } else { - sleep_time -= 100; - if (sleep_time < 0) sleep_time = 100; - } - last_queue_size = queue.size(); - boost::this_thread::sleep_for(boost::chrono::milliseconds(sleep_time)); + queue_sleep_time += 100; + wlog("queue size: ${q}", ("q", queue_size)); + boost::this_thread::sleep_for( boost::chrono::milliseconds( queue_sleep_time )); lock.lock(); + } else { + queue_sleep_time -= 100; + if( queue_sleep_time < 0 ) queue_sleep_time = 0; } - queue.emplace_back(e); + queue.emplace_back( e ); lock.unlock(); condition.notify_one(); } -} - void mongo_db_plugin_impl::accepted_transaction( const chain::transaction_metadata_ptr& t ) { try { - queue( mtx, condition, transaction_metadata_queue, t, queue_size ); + queue( transaction_metadata_queue, t ); } catch (fc::exception& e) { elog("FC Exception while accepted_transaction ${e}", ("e", e.to_string())); } catch (std::exception& e) { @@ -212,7 +208,7 @@ void mongo_db_plugin_impl::accepted_transaction( const chain::transaction_metada void mongo_db_plugin_impl::applied_transaction( const chain::transaction_trace_ptr& t ) { try { - queue( mtx, condition, transaction_trace_queue, t, queue_size ); + queue( transaction_trace_queue, t ); } catch (fc::exception& e) { elog("FC Exception while applied_transaction ${e}", ("e", e.to_string())); } catch (std::exception& e) { @@ -224,7 +220,7 @@ void mongo_db_plugin_impl::applied_transaction( const chain::transaction_trace_p void mongo_db_plugin_impl::applied_irreversible_block( const chain::block_state_ptr& bs ) { try { - queue( mtx, condition, irreversible_block_state_queue, bs, queue_size ); + queue( irreversible_block_state_queue, bs ); } catch (fc::exception& e) { elog("FC Exception while applied_irreversible_block ${e}", ("e", e.to_string())); } catch (std::exception& e) { @@ -236,7 +232,7 @@ void mongo_db_plugin_impl::applied_irreversible_block( const chain::block_state_ void mongo_db_plugin_impl::accepted_block( const chain::block_state_ptr& bs ) { try { - queue( mtx, condition, block_state_queue, bs, queue_size ); + queue( block_state_queue, bs ); } catch (fc::exception& e) { elog("FC Exception while accepted_block ${e}", ("e", e.to_string())); } catch (std::exception& e) { @@ -282,13 +278,7 @@ void mongo_db_plugin_impl::consume_blocks() { lock.unlock(); - // warn if queue size greater than 75% - if( transaction_metadata_size > (queue_size * 0.75) || - transaction_trace_size > (queue_size * 0.75) || - block_state_size > (queue_size * 0.75) || - irreversible_block_size > (queue_size * 0.75)) { - wlog("queue size: ${q}", ("q", transaction_metadata_size + transaction_trace_size + block_state_size + irreversible_block_size)); - } else if (done) { + if (done) { ilog("draining queue, size: ${q}", ("q", transaction_metadata_size + transaction_trace_size + block_state_size + irreversible_block_size)); } @@ -976,28 +966,35 @@ void mongo_db_plugin_impl::add_pub_keys( const vector& keys, const permission_name& permission, const std::chrono::milliseconds& now ) { using bsoncxx::builder::basic::kvp; + using bsoncxx::builder::basic::make_document; using namespace bsoncxx::types; if( keys.empty()) return; auto pub_keys = mongo_conn[db_name][pub_keys_col]; - mongocxx::bulk_write bulk_pub_keys = pub_keys.create_bulk_write(); + mongocxx::bulk_write bulk = pub_keys.create_bulk_write(); for( const auto& pub_key_weight : keys ) { - auto doc = bsoncxx::builder::basic::document(); + auto find_doc = bsoncxx::builder::basic::document(); + auto update_doc = bsoncxx::builder::basic::document(); + + find_doc.append( kvp( "account", name.to_string()), + kvp( "public_key", pub_key_weight.key.operator string()), + kvp( "permission", permission.to_string()) ); - doc.append( kvp( "account", name.to_string()), - kvp( "public_key", pub_key_weight.key.operator string()), - kvp( "permission", permission.to_string()), - kvp( "createdAt", b_date{now} )); + update_doc.append( bsoncxx::builder::concatenate_doc{find_doc.view()} ); + update_doc.append( kvp( "createdAt", b_date{now} )); - mongocxx::model::insert_one insert_op{doc.view()}; - bulk_pub_keys.append( insert_op ); + auto update = make_document( kvp( "$set", update_doc.view() ) ); + + mongocxx::model::update_one insert_op{find_doc.view(), update_doc.view()}; + insert_op.upsert(true); + bulk.append( insert_op ); } try { - if( !bulk_pub_keys.execute()) { + if( !bulk.execute()) { EOS_ASSERT( false, chain::mongo_db_insert_fail, "Bulk pub_keys insert failed for account: ${a}, permission: ${p}", ("a", name)( "p", permission )); @@ -1032,6 +1029,7 @@ void mongo_db_plugin_impl::add_account_control( const vector( std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()} ); auto newacc = act.data_as(); - // create new account - if( !accounts.insert_one( make_document( kvp( "name", newacc.name.to_string()), - kvp( "createdAt", b_date{now} )))) { - elog( "Failed to insert account ${n}", ("n", newacc.name)); - } + create_account( accounts, newacc.name, now ); add_pub_keys( newacc.owner.keys, newacc.name, owner, now ); add_account_control( newacc.owner.accounts, newacc.name, owner, now ); @@ -1133,10 +1156,7 @@ void mongo_db_plugin_impl::update_account(const chain::action& act) auto from_account = find_account( accounts, setabi.account ); if( !from_account ) { - if( !accounts.insert_one( make_document( kvp( "name", setabi.account.to_string()), - kvp( "createdAt", b_date{now} )))) { - elog( "Failed to insert account ${n}", ("n", setabi.account)); - } + create_account( accounts, setabi.account, now ); from_account = find_account( accounts, setabi.account ); } if( from_account ) { @@ -1295,7 +1315,7 @@ mongo_db_plugin::~mongo_db_plugin() void mongo_db_plugin::set_program_options(options_description& cli, options_description& cfg) { cfg.add_options() - ("mongodb-queue-size,q", bpo::value()->default_value(256), + ("mongodb-queue-size,q", bpo::value()->default_value(1024), "The target queue size between nodeos and MongoDB plugin thread.") ("mongodb-abi-cache-size", bpo::value()->default_value(2048), "The maximum size of the abi cache for serializing data.") @@ -1334,7 +1354,7 @@ void mongo_db_plugin::plugin_initialize(const variables_map& options) my->abi_serializer_max_time = app().get_plugin().get_abi_serializer_max_time(); if( options.count( "mongodb-queue-size" )) { - my->queue_size = options.at( "mongodb-queue-size" ).as(); + my->max_queue_size = options.at( "mongodb-queue-size" ).as(); } if( options.count( "mongodb-abi-cache-size" )) { my->abi_cache_size = options.at( "mongodb-abi-cache-size" ).as(); From bac2efc19bd490fa933b2085ebd9bca5b1147849 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 20 Jul 2018 11:32:54 -0400 Subject: [PATCH 059/294] Insert correct document view --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 941c0f7ed43..f5bb6447e77 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -558,7 +558,7 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti try { update_account( act ); } catch (...) { - ilog( "Unable to update account for ${s}::${n}", ("s", act.account)( "n", act.name )); + handle_mongo_exception( "update_account", __LINE__ ); } if( start_block_reached ) { add_data( act_doc, act ); @@ -977,16 +977,13 @@ void mongo_db_plugin_impl::add_pub_keys( const vector& keys, for( const auto& pub_key_weight : keys ) { auto find_doc = bsoncxx::builder::basic::document(); - auto update_doc = bsoncxx::builder::basic::document(); find_doc.append( kvp( "account", name.to_string()), kvp( "public_key", pub_key_weight.key.operator string()), kvp( "permission", permission.to_string()) ); - update_doc.append( bsoncxx::builder::concatenate_doc{find_doc.view()} ); - update_doc.append( kvp( "createdAt", b_date{now} )); - - auto update = make_document( kvp( "$set", update_doc.view() ) ); + auto update_doc = make_document( kvp( "$set", make_document( bsoncxx::builder::concatenate_doc{find_doc.view()}, + kvp( "createdAt", b_date{now} )))); mongocxx::model::update_one insert_op{find_doc.view(), update_doc.view()}; insert_op.upsert(true); @@ -1040,15 +1037,14 @@ void mongo_db_plugin_impl::add_account_control( const vector Date: Fri, 20 Jul 2018 15:49:43 -0400 Subject: [PATCH 060/294] Add logging with timing --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index f5bb6447e77..20ea1cc692c 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -245,6 +245,7 @@ void mongo_db_plugin_impl::accepted_block( const chain::block_state_ptr& bs ) { void mongo_db_plugin_impl::consume_blocks() { try { while (true) { + ilog( "consume_blocks" ); boost::mutex::scoped_lock lock(mtx); while ( transaction_metadata_queue.empty() && transaction_trace_queue.empty() && @@ -283,31 +284,51 @@ void mongo_db_plugin_impl::consume_blocks() { } // process transactions + auto start_time = fc::time_point::now(); + auto size = transaction_metadata_process_queue.size(); while (!transaction_metadata_process_queue.empty()) { const auto& t = transaction_metadata_process_queue.front(); process_accepted_transaction(t); transaction_metadata_process_queue.pop_front(); } + auto time = fc::time_point::now() - start_time; + auto per = size > 0 ? time.count()/size : 0; + ilog( "process_accepted_transaction, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); + start_time = fc::time_point::now(); + size = transaction_trace_process_queue.size(); while (!transaction_trace_process_queue.empty()) { const auto& t = transaction_trace_process_queue.front(); process_applied_transaction(t); transaction_trace_process_queue.pop_front(); } + time = fc::time_point::now() - start_time; + per = size > 0 ? time.count()/size : 0; + ilog( "process_applied_transaction, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); // process blocks + start_time = fc::time_point::now(); + size = block_state_process_queue.size(); while (!block_state_process_queue.empty()) { const auto& bs = block_state_process_queue.front(); process_accepted_block( bs ); block_state_process_queue.pop_front(); } + time = fc::time_point::now() - start_time; + per = size > 0 ? time.count()/size : 0; + ilog( "process_accepted_block, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); // process irreversible blocks + start_time = fc::time_point::now(); + size = irreversible_block_state_process_queue.size(); while (!irreversible_block_state_process_queue.empty()) { const auto& bs = irreversible_block_state_process_queue.front(); process_irreversible_block(bs); irreversible_block_state_process_queue.pop_front(); } + time = fc::time_point::now() - start_time; + per = size > 0 ? time.count()/size : 0; + ilog( "process_irreversible_block, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); if( transaction_metadata_size == 0 && transaction_trace_size == 0 && From 660bd45ee8398b94503d8c46b2728eaff3833ebb Mon Sep 17 00:00:00 2001 From: Lazaridis <35095758+lazaridiscom@users.noreply.github.com> Date: Sat, 21 Jul 2018 02:51:34 +0300 Subject: [PATCH 061/294] Update Resources * remove deprecated wiki * remove API (dead link, api reachable from dev-portal) * remove repeated "EOSIO" --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4344049a6be..5419001a051 100644 --- a/README.md +++ b/README.md @@ -35,17 +35,15 @@ EOSIO currently supports the following operating systems: 6. Ubuntu 18.04 7. MacOS Darwin 10.12 and higher (MacOS 10.13.x recommended) -# Resources -1. [eos.io website](https://eos.io) -2. [EOSIO Blog](https://medium.com/eosio) -3. [EOSIO Documentation Wiki](https://github.com/EOSIO/eos/wiki) -4. [EOSIO API Documentation](https://eosio.github.io/eos/) -5. [EOSIO Developer Portal](https://developers.eos.io) -6. [EOSIO StackExchange for Q&A](https://eosio.stackexchange.com/) +## Resources +1. [Website](https://eos.io) +2. [Blog](https://medium.com/eosio) +5. [Developer Portal](https://developers.eos.io) +6. [StackExchange for Q&A](https://eosio.stackexchange.com/) 7. [Community Telegram Group](https://t.me/EOSProject) 8. [Developer Telegram Group](https://t.me/joinchat/EaEnSUPktgfoI-XPfMYtcQ) 9. [White Paper](https://github.com/EOSIO/Documentation/blob/master/TechnicalWhitePaper.md) -10. [Roadmap](https://github.com/EOSIO/Documentation/blob/master/Roadmap.md) +10.[Roadmap](https://github.com/EOSIO/Documentation/blob/master/Roadmap.md) ## Getting Started From 87029c51209038d4450efa6ba1c35fd2827c1ddf Mon Sep 17 00:00:00 2001 From: Sylvain Cormier Date: Sun, 22 Jul 2018 17:05:36 -0400 Subject: [PATCH 062/294] Fix memory leak --- programs/cleos/main.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 7ec0f108489..61bde0e00fb 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -214,10 +214,8 @@ fc::variant call( const std::string& url, const std::string& path, const T& v ) { try { - eosio::client::http::connection_param *cp = new eosio::client::http::connection_param(context, parse_url(url) + path, - no_verify ? false : true, headers); - - return eosio::client::http::do_http_call( *cp, fc::variant(v), print_request, print_response ); + auto sp = std::make_unique(context, parse_url(url) + path, no_verify ? false : true, headers); + return eosio::client::http::do_http_call(*sp, fc::variant(v), print_request, print_response ); } catch(boost::system::system_error& e) { if(url == ::url) From bd7a2e26c4d94e1d8a0bcedde7795de72492c09b Mon Sep 17 00:00:00 2001 From: Andrianto Lie Date: Mon, 23 Jul 2018 11:32:47 +0800 Subject: [PATCH 063/294] Increase listproducers column width to support R1 key --- programs/cleos/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 7ec0f108489..84c555aea97 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -1052,9 +1052,9 @@ struct list_producers_subcommand { auto weight = result.total_producer_vote_weight; if ( !weight ) weight = 1; - printf("%-13s %-54s %-59s %s\n", "Producer", "Producer key", "Url", "Scaled votes"); + printf("%-13s %-57s %-59s %s\n", "Producer", "Producer key", "Url", "Scaled votes"); for ( auto& row : result.rows ) - printf("%-13.13s %-54.54s %-59.59s %1.4f\n", + printf("%-13.13s %-57.57s %-59.59s %1.4f\n", row["owner"].as_string().c_str(), row["producer_key"].as_string().c_str(), row["url"].as_string().c_str(), From 07b5c20a1f8e2554c87f7e4dbf2e97c63836d178 Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Mon, 23 Jul 2018 11:52:34 +0200 Subject: [PATCH 064/294] [db_sql_plugin] add serializer max time --- plugins/sql_db_plugin/db/actions_table.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp index f9e9961f849..aac5e9442f3 100644 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ b/plugins/sql_db_plugin/db/actions_table.cpp @@ -88,7 +88,7 @@ void actions_table::add(chain::action action, chain::transaction_id_type transac return; // no ABI no party. Should we still store it? } - static const fc::microseconds abi_serializer_max_time; + static const fc::microseconds abi_serializer_max_time(1000000); // 1 second abis.set_abi(abi, abi_serializer_max_time); auto abi_data = abis.binary_to_variant(abis.get_action_type(action.name), action.data, abi_serializer_max_time); From 734367045e6467746b0e035d74db5246c7badc50 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Mon, 23 Jul 2018 08:24:56 -0500 Subject: [PATCH 065/294] Adding contract for miscelaneous actions for integration tests. GH #4812 --- contracts/CMakeLists.txt | 1 + contracts/integration_test/CMakeLists.txt | 8 ++++ .../integration_test/integration_test.abi | 41 +++++++++++++++++++ .../integration_test/integration_test.cpp | 36 ++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 contracts/integration_test/CMakeLists.txt create mode 100644 contracts/integration_test/integration_test.abi create mode 100644 contracts/integration_test/integration_test.cpp diff --git a/contracts/CMakeLists.txt b/contracts/CMakeLists.txt index 4b1e235715a..fa3685a199a 100644 --- a/contracts/CMakeLists.txt +++ b/contracts/CMakeLists.txt @@ -34,6 +34,7 @@ add_subdirectory(noop) add_subdirectory(dice) add_subdirectory(tic_tac_toe) add_subdirectory(payloadless) +add_subdirectory(integration_test) file(GLOB SKELETONS RELATIVE ${CMAKE_SOURCE_DIR}/contracts "skeleton/*") diff --git a/contracts/integration_test/CMakeLists.txt b/contracts/integration_test/CMakeLists.txt new file mode 100644 index 00000000000..6439a566f36 --- /dev/null +++ b/contracts/integration_test/CMakeLists.txt @@ -0,0 +1,8 @@ +file(GLOB ABI_FILES "*.abi") +configure_file("${ABI_FILES}" "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY) + +add_wast_executable(TARGET integration_test + INCLUDE_FOLDERS "${STANDARD_INCLUDE_FOLDERS}" + LIBRARIES libc libc++ eosiolib + DESTINATION_FOLDER ${CMAKE_CURRENT_BINARY_DIR} +) diff --git a/contracts/integration_test/integration_test.abi b/contracts/integration_test/integration_test.abi new file mode 100644 index 00000000000..6f181b255ba --- /dev/null +++ b/contracts/integration_test/integration_test.abi @@ -0,0 +1,41 @@ +{ + "version": "eosio::abi/1.0", + "types": [{ + "new_type_name": "account_name", + "type": "name" + }], + "structs": [{ + "name": "store", + "base": "", + "fields": [ + {"name":"from", "type":"account_name"}, + {"name":"to", "type":"account_name"}, + {"name":"num", "type":"uint64"} + ] + },{ + "name": "payload", + "base": "", + "fields": [ + {"name":"key", "type":"uint64"}, + {"name":"data", "type":"uint64[]"} + ] + } + ], + "actions": [{ + "name": "store", + "type": "store", + "ricardian_contract": "" + } + + ], + "tables": [{ + "name": "payloads", + "type": "payload", + "index_type": "i64", + "key_names" : ["key"], + "key_types" : ["uint64"] + } + ], + "ricardian_clauses": [], + "abi_extensions": [] +} diff --git a/contracts/integration_test/integration_test.cpp b/contracts/integration_test/integration_test.cpp new file mode 100644 index 00000000000..87a0edeecc1 --- /dev/null +++ b/contracts/integration_test/integration_test.cpp @@ -0,0 +1,36 @@ +#include +using namespace eosio; + +struct integration_test : public eosio::contract { + using contract::contract; + + struct payload { + uint64_t key; + vector data; + + uint64_t primary_key()const { return key; } + }; + typedef eosio::multi_index payloads; + + /// @abi action + void store( account_name from, + account_name to, + uint64_t num ) { + require_auth( from ); + eosio_assert( is_account( to ), "to account does not exist"); + payloads data ( _self, from ); + uint64_t key = 0; + const uint64_t num_keys = 5; + while (data.find( key ) != data.end()) { + key += num_keys; + } + for (uint64_t i = 0; i < num_keys; ++i) { + data.emplace(from, [&]( auto& g ) { + g.key = key + i; + g.data = vector(num, 5); + }); + } + } +}; + +EOSIO_ABI( integration_test, (store) ) From fa9e8997bc3e662bb7039b875f137b4d99e8dedb Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Mon, 23 Jul 2018 08:30:01 -0500 Subject: [PATCH 066/294] Added relaunching node with new or changed command line parameters. GH #4812 --- .../chain/include/eosio/chain/config.hpp | 2 +- tests/Node.py | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/libraries/chain/include/eosio/chain/config.hpp b/libraries/chain/include/eosio/chain/config.hpp index 7de4d83ced8..9e1dcd0b073 100644 --- a/libraries/chain/include/eosio/chain/config.hpp +++ b/libraries/chain/include/eosio/chain/config.hpp @@ -20,7 +20,7 @@ const static auto default_reversible_guard_size = 2*1024*1024ll;/// 1MB * 340 bl const static auto default_state_dir_name = "state"; const static auto forkdb_filename = "forkdb.dat"; const static auto default_state_size = 1*1024*1024*1024ll; -const static auto default_state_guard_size = 128*1024*1024ll; +const static auto default_state_guard_size = 128*1024*1024ll; const static uint64_t system_account_name = N(eosio); diff --git a/tests/Node.py b/tests/Node.py index 2338c1a6293..4c10a867f96 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -1,3 +1,4 @@ +import copy import decimal import subprocess import time @@ -992,7 +993,7 @@ def myFunc(): # TBD: make nodeId an internal property # pylint: disable=too-many-locals - def relaunch(self, nodeId, chainArg, newChain=False, timeout=Utils.systemWaitTimeout): + def relaunch(self, nodeId, chainArg, newChain=False, timeout=Utils.systemWaitTimeout, addOrSwapFlags=None): assert(self.pid is None) assert(self.killed) @@ -1001,8 +1002,10 @@ def relaunch(self, nodeId, chainArg, newChain=False, timeout=Utils.systemWaitTim cmdArr=[] myCmd=self.cmd + toAddOrSwap=copy.deepcopy(addOrSwapFlags) if addOrSwapFlags is not None else {} if not newChain: skip=False + swapValue=None for i in self.cmd.split(): Utils.Print("\"%s\"" % (i)) if skip: @@ -1012,7 +1015,19 @@ def relaunch(self, nodeId, chainArg, newChain=False, timeout=Utils.systemWaitTim skip=True continue - cmdArr.append(i) + if swapValue is None: + cmdArr.append(i) + else: + cmdArr.append(swapValue) + swapValue=None + + if i in toAddOrSwap: + swapValue=toAddOrSwap[i] + del toAddOrSwap[i] + for k,v in toAddOrSwap.items(): + cmdArr.append(k) + cmdArr.append(v) + myCmd=" ".join(cmdArr) dataDir="var/lib/node_%02d" % (nodeId) @@ -1044,5 +1059,6 @@ def isNodeAlive(): self.pid=None return False + self.cmd=cmd self.killed=False return True From b472d552aeffcdae9aee9825adf30eed6324a57a Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Mon, 23 Jul 2018 08:32:38 -0500 Subject: [PATCH 067/294] Added passing in extra command args for cluster launch and passing extra flags for account creation. GH #4812 --- tests/Cluster.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index b60ce6f4035..4217f85ff93 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -97,7 +97,7 @@ def setWalletMgr(self, walletMgr): # pylint: disable=too-many-branches # pylint: disable=too-many-statements def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", p2pPlugin="net", delay=1, onlyBios=False, dontKill=False - , dontBootstrap=False, totalProducers=None): + , dontBootstrap=False, totalProducers=None, extraNodeosArgs=None): """Launch cluster. pnodes: producer nodes count totalNodes: producer + non-producer nodes count @@ -129,11 +129,14 @@ def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", p2pPlugin="ne if self.staging: cmdArr.append("--nogen") - nodeosArgs="--max-transaction-time 5000 --abi-serializer-max-time-ms 5000 --filter-on * --p2p-max-nodes-per-host %d" % (totalNodes) + nodeosArgs="--max-transaction-time 50000 --abi-serializer-max-time-ms 50000 --filter-on * --p2p-max-nodes-per-host %d" % (totalNodes) if not self.walletd: nodeosArgs += " --plugin eosio::wallet_api_plugin" if self.enableMongo: nodeosArgs += " --plugin eosio::mongo_db_plugin --mongodb-wipe --delete-all-blocks --mongodb-uri %s" % self.mongoUri + if extraNodeosArgs is not None: + assert(isinstance(extraNodeosArgs, str)) + nodeosArgs += extraNodeosArgs if Utils.Debug: nodeosArgs += " --contracts-console" @@ -564,11 +567,11 @@ def validateAccounts(self, accounts, testSysAccounts=True): node.validateAccounts(myAccounts) - def createAccountAndVerify(self, account, creator, stakedDeposit=1000): + def createAccountAndVerify(self, account, creator, stakedDeposit=1000, stakeNet=100, stakeCPU=100, buyRAM=100): """create account, verify account and return transaction id""" assert(len(self.nodes) > 0) node=self.nodes[0] - trans=node.createInitializeAccount(account, creator, stakedDeposit) + trans=node.createInitializeAccount(account, creator, stakedDeposit, stakeNet=stakeNet, stakeCPU=stakeCPU, buyRAM=buyRAM) assert(trans) assert(node.verifyAccount(account)) return trans @@ -586,10 +589,10 @@ def createAccountAndVerify(self, account, creator, stakedDeposit=1000): # return transId # return None - def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False): + def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False, stakeNet=100, stakeCPU=100, buyRAM=100): assert(len(self.nodes) > 0) node=self.nodes[0] - trans=node.createInitializeAccount(account, creatorAccount, stakedDeposit, waitForTransBlock) + trans=node.createInitializeAccount(account, creatorAccount, stakedDeposit, waitForTransBlock, stakeNet=stakeNet, stakeCPU=stakeCPU, buyRAM=buyRAM) return trans @staticmethod From 40533090f6d350a923f29069a8883981e50a976b Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Mon, 23 Jul 2018 08:33:42 -0500 Subject: [PATCH 068/294] Added method to check if the node's pid is still active. GH #4812 --- tests/Node.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Node.py b/tests/Node.py index 4c10a867f96..c923ceb947c 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -991,6 +991,23 @@ def myFunc(): self.killed=True return True + def verifyAlive(self): + if Utils.Debug: Utils.Print("Checking if node(pid=%s) is alive(killed=%s): %s" % (self.pid, self.killed, self.cmd)) + if self.killed or self.pid is None: + return False + + try: + os.kill(self.pid, 0) + except ProcessLookupError as ex: + # mark node as killed + self.pid=None + self.killed=True + return False + except PermissionError as ex: + return True + else: + return True + # TBD: make nodeId an internal property # pylint: disable=too-many-locals def relaunch(self, nodeId, chainArg, newChain=False, timeout=Utils.systemWaitTimeout, addOrSwapFlags=None): From a5487f73907c5d9c205e8a32504d9ee907369b49 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Mon, 23 Jul 2018 08:38:20 -0500 Subject: [PATCH 069/294] Added integration test to verify that nodeos will shutdown if minimum RAM setting hit and can restart with higher RAM. GH #4812 --- tests/nodeos_under_min_avail_ram.py | 348 ++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100755 tests/nodeos_under_min_avail_ram.py diff --git a/tests/nodeos_under_min_avail_ram.py b/tests/nodeos_under_min_avail_ram.py new file mode 100755 index 00000000000..efe64d3bab6 --- /dev/null +++ b/tests/nodeos_under_min_avail_ram.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 + +from core_symbol import CORE_SYMBOL +from Cluster import Cluster +from WalletMgr import WalletMgr +from Node import Node +from TestHelper import TestHelper +from testUtils import Utils +import testUtils +import time + +import decimal +import math +import re + +class NamedAccounts: + + def __init__(self, cluster, numAccounts): + Print("NamedAccounts %d" % (numAccounts)) + self.numAccounts=numAccounts + self.accounts=cluster.createAccountKeys(numAccounts) + if self.accounts is None: + errorExit("FAILURE - create keys") + accountNum = 0 + for account in self.accounts: + Print("NamedAccounts Name for %d" % (accountNum)) + account.name=self.setName(accountNum) + accountNum+=1 + + def setName(self, num): + retStr="test" + digits=[] + maxDigitVal=5 + maxDigits=8 + temp=num + while len(digits) < maxDigits: + digit=(num % maxDigitVal)+1 + num=int(num/maxDigitVal) + digits.append(digit) + + digits.reverse() + for digit in digits: + retStr=retStr+str(digit) + + Print("NamedAccounts Name for %d is %s" % (temp, retStr)) + return retStr + +############################################################### +# nodeos_voting_test +# --dump-error-details +# --keep-logs +############################################################### +Print=Utils.Print +errorExit=Utils.errorExit + +args = TestHelper.parse_args({"--dump-error-details","--keep-logs","-v","--leave-running","--clean-run"}) +Utils.Debug=args.v +totalNodes=4 +cluster=Cluster(walletd=True) +dumpErrorDetails=args.dump_error_details +keepLogs=args.keep_logs +dontKill=args.leave_running +killAll=args.clean_run + +walletMgr=WalletMgr(True) +testSuccessful=False +killEosInstances=not dontKill +killWallet=not dontKill + +WalletdName="keosd" +ClientName="cleos" + +try: + TestHelper.printSystemInfo("BEGIN") + + cluster.killall(allInstances=killAll) + cluster.cleanup() + Print("Stand up cluster") + minRAMFlag="--chain-state-db-guard-size-mb" + minRAMValue=1002 + maxRAMFlag="--chain-state-db-size-mb" + maxRAMValue=1010 + extraNodeosArgs=" %s %d %s %d " % (minRAMFlag, minRAMValue, maxRAMFlag, maxRAMValue) + if cluster.launch(onlyBios=False, dontKill=dontKill, pnodes=totalNodes, totalNodes=totalNodes, totalProducers=totalNodes*21, extraNodeosArgs=extraNodeosArgs) is False: + Utils.cmdError("launcher") + errorExit("Failed to stand up eos cluster.") + + Print("Validating system accounts after bootstrap") + cluster.validateAccounts(None) + + Print("creating accounts") + namedAccounts=NamedAccounts(cluster,10) + accounts=namedAccounts.accounts + + testWalletName="test" + + Print("Creating wallet \"%s\"." % (testWalletName)) + walletMgr.killall(allInstances=killAll) + walletMgr.cleanup() + if walletMgr.launch() is False: + Utils.cmdError("%s" % (WalletdName)) + errorExit("Failed to stand up eos walletd.") + + testWallet=walletMgr.create(testWalletName, [cluster.eosioAccount]) + if testWallet is None: + Utils.cmdError("eos wallet create") + errorExit("Failed to create wallet %s." % (testWalletName)) + + for _, account in cluster.defProducerAccounts.items(): + walletMgr.importKey(account, testWallet, ignoreDupKeyWarning=True) + + Print("Wallet \"%s\" password=%s." % (testWalletName, testWallet.password.encode("utf-8"))) + + nodes=[] + nodes.append(cluster.getNode(0)) + if nodes[0] is None: + errorExit("Cluster in bad state, received None node") + nodes.append(cluster.getNode(1)) + if nodes[1] is None: + errorExit("Cluster in bad state, received None node") + nodes.append(cluster.getNode(2)) + if nodes[2] is None: + errorExit("Cluster in bad state, received None node") + nodes.append(cluster.getNode(3)) + if nodes[3] is None: + errorExit("Cluster in bad state, received None node") + + + for account in accounts: + walletMgr.importKey(account, testWallet) + + # create accounts via eosio as otherwise a bid is needed + for account in accounts: + Print("Create new account %s via %s" % (account.name, cluster.eosioAccount.name)) + trans=nodes[0].createInitializeAccount(account, cluster.eosioAccount, stakedDeposit=500000, waitForTransBlock=False, stakeNet=50000, stakeCPU=50000, buyRAM=50000) + if trans is None: + Utils.cmdError("%s create account" % (account.name)) + errorExit("Failed to create account %s" % (account.name)) + transferAmount="70000000.0000 {0}".format(CORE_SYMBOL) + Print("Transfer funds %s from account %s to %s" % (transferAmount, cluster.eosioAccount.name, account.name)) + if nodes[0].transferFunds(cluster.eosioAccount, account, transferAmount, "test transfer") is None: + errorExit("Failed to transfer funds %d from account %s to %s" % ( + transferAmount, cluster.eosioAccount.name, account.name)) + trans=nodes[0].delegatebw(account, 1000000.0000, 68000000.0000) + if trans is None: + Utils.cmdError("delegate bandwidth for %s" % (account.name)) + errorExit("Failed to delegate bandwidth for %s" % (account.name)) + + contractAccount=cluster.createAccountKeys(1)[0] + contractAccount.name="contracttest" + walletMgr.importKey(contractAccount, testWallet) + Print("Create new account %s via %s" % (contractAccount.name, cluster.eosioAccount.name)) + trans=nodes[0].createInitializeAccount(contractAccount, cluster.eosioAccount, stakedDeposit=500000, waitForTransBlock=False, stakeNet=50000, stakeCPU=50000, buyRAM=50000) + if trans is None: + Utils.cmdError("%s create account" % (contractAccount.name)) + errorExit("Failed to create account %s" % (contractAccount.name)) + transferAmount="90000000.0000 {0}".format(CORE_SYMBOL) + Print("Transfer funds %s from account %s to %s" % (transferAmount, cluster.eosioAccount.name, contractAccount.name)) + if nodes[0].transferFunds(cluster.eosioAccount, contractAccount, transferAmount, "test transfer") is None: + errorExit("Failed to transfer funds %d from account %s to %s" % ( + transferAmount, cluster.eosioAccount.name, contractAccount.name)) + trans=nodes[0].delegatebw(contractAccount, 1000000.0000, 88000000.0000) + if trans is None: + Utils.cmdError("delegate bandwidth for %s" % (contractAccount.name)) + errorExit("Failed to delegate bandwidth for %s" % (contractAccount.name)) + + contractDir="contracts/integration_test" + wastFile="contracts/integration_test/integration_test.wast" + abiFile="contracts/integration_test/integration_test.abi" + Print("Publish contract") + trans=nodes[0].publishContract(contractAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) + if trans is None: + cmdError("%s set contract %s" % (ClientName, contractAccount.name)) + errorExit("Failed to publish contract.") + + contract=contractAccount.name + Print("push create action to %s contract" % (contract)) + action="store" + numAmount=5000 + keepProcessing=True + count=0 + while keepProcessing: + numAmount+=1 + for fromIndex in range(namedAccounts.numAccounts): + count+=1 + toIndex=fromIndex+1 + if toIndex==namedAccounts.numAccounts: + toIndex=0 + fromAccount=accounts[fromIndex] + toAccount=accounts[toIndex] + data="{\"from\":\"%s\",\"to\":\"%s\",\"num\":%d}" % (fromAccount.name, toAccount.name, numAmount) + opts="--permission %s@active --permission %s@active --expiration 90" % (contract, fromAccount.name) + try: + trans=nodes[0].pushMessage(contract, action, data, opts) + if trans is None or not trans[0]: + Print("Failed to push create action to eosio contract. sleep for 60 seconds") + time.sleep(60) + time.sleep(1) + except TypeError as ex: + keepProcessing=False + break + + #spread the actions to all accounts, to use each accounts tps bandwidth + fromIndexStart=fromIndex+1 if fromIndex+1 15: + strMsg="little" if count < 15 else "much" + Utils.cmdError("Was able to send %d store actions which was too %s" % (count, strMsg)) + errorExit("Incorrect number of store actions sent") + + # Make sure all the nodes are shutdown (may take a little while for this to happen, so making multiple passes) + allDone=False + count=0 + while not allDone: + allDone=True + for node in nodes: + if node.verifyAlive(): + allDone=False + if not allDone: + time.sleep(5) + if ++count>5: + Utils.cmdError("All Nodes should have died") + errorExit("Failure - All Nodes should have died") + + Print("relaunch nodes with new capacity") + addOrSwapFlags={} + numNodes=len(nodes) + maxRAMValue+=2 + currentMinimumMaxRAM=maxRAMValue + for i in range(numNodes): + addOrSwapFlags[maxRAMFlag]=str(maxRAMValue) + if i==numNodes-1: + addOrSwapFlags["--enable-stale-production"]="" # just enable stale production for the first node + nodeIndex=numNodes-i-1 + if not nodes[nodeIndex].relaunch(nodeIndex, "", newChain=False, addOrSwapFlags=addOrSwapFlags): + Utils.cmdError("Failed to restart node0 with new capacity %s" % (maxRAMValue)) + errorExit("Failure - Node should have restarted") + addOrSwapFlags={} + maxRAMValue=currentMinimumMaxRAM+30 + + time.sleep(10) + for i in range(numNodes): + if not nodes[i].verifyAlive(): + Utils.cmdError("Node %d should be alive" % (i)) + errorExit("Failure - All Nodes should be alive") + + Print("push more actions to %s contract" % (contract)) + action="store" + keepProcessing=True + count=0 + while keepProcessing and count < 40: + Print("Send %s" % (action)) + numAmount+=1 + for fromIndexOffset in range(namedAccounts.numAccounts): + count+=1 + fromIndex=fromIndexStart+fromIndexOffset + if fromIndex>=namedAccounts.numAccounts: + fromIndex-=namedAccounts.numAccounts + toIndex=fromIndex+1 + if toIndex==namedAccounts.numAccounts: + toIndex=0 + fromAccount=accounts[fromIndex] + toAccount=accounts[toIndex] + data="{\"from\":\"%s\",\"to\":\"%s\",\"num\":%d}" % (fromAccount.name, toAccount.name, numAmount) + opts="--permission %s@active --permission %s@active --expiration 90" % (contract, fromAccount.name) + try: + trans=nodes[0].pushMessage(contract, action, data, opts) + if trans is None or not trans[0]: + Print("Failed to push create action to eosio contract. sleep for 60 seconds") + time.sleep(60) + time.sleep(1) + except TypeError as ex: + Print("Failed to send %s" % (action)) + + if not nodes[len(nodes)-1].verifyAlive(): + keepProcessing=False + break + + if keepProcessing: + Utils.cmdError("node[%d] never shutdown" % (numNodes-1)) + errorExit("Failure - Node should be shutdown") + + for i in range(numNodes): + # only the last node should be dead + if not nodes[i].verifyAlive() and i=len(nodes): + break + fromIndex=fromIndexStart+fromIndexOffset + if fromIndex>=namedAccounts.numAccounts: + fromIndex-=namedAccounts.numAccounts + toIndex=fromIndex+1 + if toIndex==namedAccounts.numAccounts: + toIndex=0 + fromAccount=accounts[fromIndex] + toAccount=accounts[toIndex] + node=nodes[fromIndexOffset] + data="{\"from\":\"%s\",\"to\":\"%s\",\"num\":%d}" % (fromAccount.name, toAccount.name, numAmount) + opts="--permission %s@active --permission %s@active --expiration 90" % (contract, fromAccount.name) + try: + trans=nodes[0].pushMessage(contract, action, data, opts) + if trans is None or not trans[0]: + Print("Failed to push create action to eosio contract. sleep for 60 seconds") + time.sleep(60) + continue + time.sleep(1) + except TypeError as ex: + Utils.cmdError("Failed to send %s action to node %d" % (fromAccount, fromIndexOffset, action)) + errorExit("Failure - send %s action should have succeeded" % (action)) + + time.sleep(10) + Print("Check nodes are alive") + allDone=True + for node in nodes: + if not node.verifyAlive(): + Utils.cmdError("All Nodes should be alive") + errorExit("Failure - All Nodes should be alive") + + testSuccessful=True +finally: + TestHelper.shutdown(cluster, walletMgr, testSuccessful, killEosInstances, killWallet, keepLogs, killAll, dumpErrorDetails) + +exit(0) From e4905fa2f56ae8d9a5b9302adcefcf44257c0ab0 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Mon, 23 Jul 2018 08:59:22 -0500 Subject: [PATCH 070/294] Added min RAM test to long running tests. GH #4812 --- tests/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 762a55d2615..565a6679ee3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -36,6 +36,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/sample-cluster-map.json ${CMAKE_CURRE configure_file(${CMAKE_CURRENT_SOURCE_DIR}/restart-scenarios-test.py ${CMAKE_CURRENT_BINARY_DIR}/restart-scenarios-test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_run_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_run_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_run_remote_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_run_remote_test.py COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_under_min_avail_ram.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_under_min_avail_ram.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_voting_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_voting_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/consensus-validation-malicious-producers.py ${CMAKE_CURRENT_BINARY_DIR}/consensus-validation-malicious-producers.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/validate-dirty-db.py ${CMAKE_CURRENT_BINARY_DIR}/validate-dirty-db.py COPYONLY) @@ -68,6 +69,9 @@ set_property(TEST nodeos_sanity_lr_test PROPERTY LABELS long_running_tests) add_test(NAME nodeos_voting_lr_test COMMAND tests/nodeos_voting_test.py -v --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_voting_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME nodeos_under_min_avail_ram_lr_test COMMAND tests/nodeos_under_min_avail_ram.py -v --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_under_min_avail_ram_lr_test PROPERTY LABELS long_running_tests) + if(ENABLE_COVERAGE_TESTING) From b6648732e67673e6b9bf11b03366009756b55c66 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 23 Jul 2018 11:47:23 -0500 Subject: [PATCH 071/294] Optimize transaction update. Minimize logging. --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 44 ++++++++++----------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 20ea1cc692c..87cfcda5077 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -245,7 +245,6 @@ void mongo_db_plugin_impl::accepted_block( const chain::block_state_ptr& bs ) { void mongo_db_plugin_impl::consume_blocks() { try { while (true) { - ilog( "consume_blocks" ); boost::mutex::scoped_lock lock(mtx); while ( transaction_metadata_queue.empty() && transaction_trace_queue.empty() && @@ -293,7 +292,8 @@ void mongo_db_plugin_impl::consume_blocks() { } auto time = fc::time_point::now() - start_time; auto per = size > 0 ? time.count()/size : 0; - ilog( "process_accepted_transaction, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); + if( time > fc::microseconds(500000) ) // reduce logging, .5 secs + ilog( "process_accepted_transaction, time per: ${p}, size: ${s}, time: ${t}", ("s", size)( "t", time )( "p", per )); start_time = fc::time_point::now(); size = transaction_trace_process_queue.size(); @@ -304,7 +304,8 @@ void mongo_db_plugin_impl::consume_blocks() { } time = fc::time_point::now() - start_time; per = size > 0 ? time.count()/size : 0; - ilog( "process_applied_transaction, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); + if( time > fc::microseconds(500000) ) // reduce logging, .5 secs + ilog( "process_applied_transaction, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); // process blocks start_time = fc::time_point::now(); @@ -316,7 +317,8 @@ void mongo_db_plugin_impl::consume_blocks() { } time = fc::time_point::now() - start_time; per = size > 0 ? time.count()/size : 0; - ilog( "process_accepted_block, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); + if( time > fc::microseconds(500000) ) // reduce logging, .5 secs + ilog( "process_accepted_block, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); // process irreversible blocks start_time = fc::time_point::now(); @@ -328,7 +330,8 @@ void mongo_db_plugin_impl::consume_blocks() { } time = fc::time_point::now() - start_time; per = size > 0 ? time.count()/size : 0; - ilog( "process_irreversible_block, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); + if( time > fc::microseconds(500000) ) // reduce logging, .5 secs + ilog( "process_irreversible_block, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); if( transaction_metadata_size == 0 && transaction_trace_size == 0 && @@ -356,12 +359,6 @@ auto find_account( mongocxx::collection& accounts, const account_name& name ) { return accounts.find_one( make_document( kvp( "name", name.to_string()))); } -auto find_transaction( mongocxx::collection& trans, const string& id ) { - using bsoncxx::builder::basic::make_document; - using bsoncxx::builder::basic::kvp; - return trans.find_one( make_document( kvp( "trx_id", id ))); -} - auto find_block( mongocxx::collection& blocks, const string& id ) { using bsoncxx::builder::basic::make_document; using bsoncxx::builder::basic::kvp; @@ -774,6 +771,8 @@ void mongo_db_plugin_impl::_process_accepted_block( const chain::block_state_ptr update_opts.upsert( true ); auto block_num = bs->block_num; + if( block_num % 1000 == 0 ) + ilog( "block_num: ${b}", ("b", block_num) ); const auto block_id = bs->id; const auto block_id_str = block_id.str(); const auto prev_block_id_str = bs->block->previous.str(); @@ -865,7 +864,7 @@ void mongo_db_plugin_impl::_process_irreversible_block(const chain::block_state_ const auto block_num = bs->block->block_num(); // genesis block 1 is not signaled to accepted_block - if (block_num < 2) return; +// if (block_num < 2) return; auto now = std::chrono::duration_cast( std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()}); @@ -893,7 +892,7 @@ void mongo_db_plugin_impl::_process_irreversible_block(const chain::block_state_ string trx_id_str; if( receipt.trx.contains()) { const auto& pt = receipt.trx.get(); - // get id via get_raw_transaction() as packed_transaction.id() mutates inernal transaction state + // get id via get_raw_transaction() as packed_transaction.id() mutates internal transaction state const auto& raw = pt.get_raw_transaction(); const auto& id = fc::raw::unpack(raw).id(); trx_id_str = id.str(); @@ -902,18 +901,15 @@ void mongo_db_plugin_impl::_process_irreversible_block(const chain::block_state_ trx_id_str = id.str(); } - auto ir_trans = find_transaction(trans, trx_id_str); - - if (ir_trans) { - auto update_doc = make_document( kvp( "$set", make_document( kvp( "irreversible", b_bool{true} ), - kvp( "block_id", block_id_str), - kvp( "block_num", b_int32{static_cast(block_num)} ), - kvp( "updatedAt", b_date{now})))); + auto update_doc = make_document( kvp( "$set", make_document( kvp( "irreversible", b_bool{true} ), + kvp( "block_id", block_id_str ), + kvp( "block_num", b_int32{static_cast(block_num)} ), + kvp( "updatedAt", b_date{now} )))); - mongocxx::model::update_one update_op{ make_document(kvp("_id", ir_trans->view()["_id"].get_oid())), update_doc.view()}; - bulk.append(update_op); - transactions_in_block = true; - } + mongocxx::model::update_one update_op{make_document( kvp( "trx_id", trx_id_str )), update_doc.view()}; + update_op.upsert( true ); + bulk.append( update_op ); + transactions_in_block = true; } if( transactions_in_block ) { From b9a746e003ffb95ef54c8197cf38af1af02363c6 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 23 Jul 2018 11:52:11 -0500 Subject: [PATCH 072/294] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5419001a051..1729927f857 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,13 @@ EOSIO currently supports the following operating systems: ## Resources 1. [Website](https://eos.io) -2. [Blog](https://medium.com/eosio) -5. [Developer Portal](https://developers.eos.io) -6. [StackExchange for Q&A](https://eosio.stackexchange.com/) -7. [Community Telegram Group](https://t.me/EOSProject) -8. [Developer Telegram Group](https://t.me/joinchat/EaEnSUPktgfoI-XPfMYtcQ) -9. [White Paper](https://github.com/EOSIO/Documentation/blob/master/TechnicalWhitePaper.md) -10.[Roadmap](https://github.com/EOSIO/Documentation/blob/master/Roadmap.md) +1. [Blog](https://medium.com/eosio) +1. [Developer Portal](https://developers.eos.io) +1. [StackExchange for Q&A](https://eosio.stackexchange.com/) +1. [Community Telegram Group](https://t.me/EOSProject) +1. [Developer Telegram Group](https://t.me/joinchat/EaEnSUPktgfoI-XPfMYtcQ) +1. [White Paper](https://github.com/EOSIO/Documentation/blob/master/TechnicalWhitePaper.md) +1. [Roadmap](https://github.com/EOSIO/Documentation/blob/master/Roadmap.md) ## Getting Started From 7a9b2d9b24e598d8bafc80d90e2fed497d023a0f Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Mon, 23 Jul 2018 14:19:25 -0500 Subject: [PATCH 073/294] Added option to write password returned by "wallet create" to a file. GH #4554 --- programs/cleos/main.cpp | 19 +++++++++++++++++-- tests/WalletMgr.py | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 7ec0f108489..c54978516a0 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2251,14 +2251,29 @@ int main( int argc, char** argv ) { wallet->require_subcommand(); // create wallet string wallet_name = "default"; + string password_file; + bool print_console = false; auto createWallet = wallet->add_subcommand("create", localized("Create a new wallet locally"), false); createWallet->add_option("-n,--name", wallet_name, localized("The name of the new wallet"), true); - createWallet->set_callback([&wallet_name] { + createWallet->add_option("-f,--file", password_file, localized("Name of file to write wallet password output to. (Must be set, unless \"--console\" is passed")); + createWallet->add_flag( "--to-console", print_console, localized("Print password to console.")); + createWallet->set_callback([&wallet_name, &password_file, &print_console] { + if (password_file.empty() && !print_console) { + std::cerr << "ERROR: Either indicate a file using \"--file\" or pass \"--console\"" << std::endl; + return; + } + const auto& v = call(wallet_url, wallet_create, wallet_name); std::cout << localized("Creating wallet: ${wallet_name}", ("wallet_name", wallet_name)) << std::endl; std::cout << localized("Save password to use in the future to unlock this wallet.") << std::endl; std::cout << localized("Without password imported keys will not be retrievable.") << std::endl; - std::cout << fc::json::to_pretty_string(v) << std::endl; + if (print_console) { + std::cout << fc::json::to_pretty_string(v) << std::endl; + } else { + std::cerr << localized("saving password to ${filename}", ("filename", password_file)) << std::endl; + std::ofstream out( password_file.c_str() ); + out << fc::json::to_pretty_string(v); + } }); // open wallet diff --git a/tests/WalletMgr.py b/tests/WalletMgr.py index 6a4201ad783..4b1c49bfcfd 100644 --- a/tests/WalletMgr.py +++ b/tests/WalletMgr.py @@ -53,7 +53,7 @@ def create(self, name, accounts=None): if Utils.Debug: Utils.Print("Wallet \"%s\" already exists. Returning same." % name) return wallet p = re.compile(r'\n\"(\w+)\"\n', re.MULTILINE) - cmd="%s %s wallet create --name %s" % (Utils.EosClientPath, self.endpointArgs, name) + cmd="%s %s wallet create --name %s --to-console" % (Utils.EosClientPath, self.endpointArgs, name) if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) retStr=Utils.checkOutput(cmd.split()) #Utils.Print("create: %s" % (retStr)) From 6967ee19162ad30c0c59a0db64a2d876f1f8bd12 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Mon, 23 Jul 2018 15:46:42 -0500 Subject: [PATCH 074/294] Added option to write keys returned by "create key" to a file. GH #4554 --- programs/cleos/main.cpp | 33 +++++++++++-------- tests/Cluster.py | 4 +-- .../bios-boot-tutorial/bios-boot-tutorial.py | 2 +- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index c54978516a0..917fc09bb27 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -1676,23 +1676,31 @@ int main( int argc, char** argv ) { create->require_subcommand(); bool r1 = false; + string key_file; + bool print_console = false; // create key - auto create_key = create->add_subcommand("key", localized("Create a new keypair and print the public and private keys"))->set_callback( [&r1](){ - if( r1 ) { - auto pk = private_key_type::generate_r1(); - auto privs = string(pk); - auto pubs = string(pk.get_public_key()); + auto create_key = create->add_subcommand("key", localized("Create a new keypair and print the public and private keys"))->set_callback( [&r1, &key_file, &print_console](){ + if (key_file.empty() && !print_console) { + std::cerr << "ERROR: Either indicate a file using \"--file\" or pass \"--to-console\"" << std::endl; + return; + } + + auto pk = r1 ? private_key_type::generate_r1() : private_key_type::generate(); + auto privs = string(pk); + auto pubs = string(pk.get_public_key()); + if (print_console) { std::cout << localized("Private key: ${key}", ("key", privs) ) << std::endl; std::cout << localized("Public key: ${key}", ("key", pubs ) ) << std::endl; } else { - auto pk = private_key_type::generate(); - auto privs = string(pk); - auto pubs = string(pk.get_public_key()); - std::cout << localized("Private key: ${key}", ("key", privs) ) << std::endl; - std::cout << localized("Public key: ${key}", ("key", pubs ) ) << std::endl; + std::cerr << localized("saving keys to ${filename}", ("filename", key_file)) << std::endl; + std::ofstream out( key_file.c_str() ); + out << localized("Private key: ${key}", ("key", privs) ) << std::endl; + out << localized("Public key: ${key}", ("key", pubs ) ) << std::endl; } }); create_key->add_flag( "--r1", r1, "Generate a key using the R1 curve (iPhone), instead of the K1 curve (Bitcoin)" ); + create_key->add_option("-f,--file", key_file, localized("Name of file to write wallet password output to. (Must be set, unless \"--to-console\" is passed")); + create_key->add_flag( "--to-console", print_console, localized("Print password to console.")); // create account auto createAccount = create_account_subcommand( create, true /*simple*/ ); @@ -2252,14 +2260,13 @@ int main( int argc, char** argv ) { // create wallet string wallet_name = "default"; string password_file; - bool print_console = false; auto createWallet = wallet->add_subcommand("create", localized("Create a new wallet locally"), false); createWallet->add_option("-n,--name", wallet_name, localized("The name of the new wallet"), true); - createWallet->add_option("-f,--file", password_file, localized("Name of file to write wallet password output to. (Must be set, unless \"--console\" is passed")); + createWallet->add_option("-f,--file", password_file, localized("Name of file to write wallet password output to. (Must be set, unless \"--to-console\" is passed")); createWallet->add_flag( "--to-console", print_console, localized("Print password to console.")); createWallet->set_callback([&wallet_name, &password_file, &print_console] { if (password_file.empty() && !print_console) { - std::cerr << "ERROR: Either indicate a file using \"--file\" or pass \"--console\"" << std::endl; + std::cerr << "ERROR: Either indicate a file using \"--file\" or pass \"--to-console\"" << std::endl; return; } diff --git a/tests/Cluster.py b/tests/Cluster.py index b60ce6f4035..8a02dbf4926 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -351,7 +351,7 @@ def createAccountKeys(count): p = re.compile('Private key: (.+)\nPublic key: (.+)\n', re.MULTILINE) for _ in range(0, count): try: - cmd="%s create key" % (Utils.EosClientPath) + cmd="%s create key --to-console" % (Utils.EosClientPath) if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) keyStr=Utils.checkOutput(cmd.split()) m=p.search(keyStr) @@ -362,7 +362,7 @@ def createAccountKeys(count): ownerPrivate=m.group(1) ownerPublic=m.group(2) - cmd="%s create key" % (Utils.EosClientPath) + cmd="%s create key --to-console" % (Utils.EosClientPath) if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) keyStr=Utils.checkOutput(cmd.split()) m=p.match(keyStr) diff --git a/tutorials/bios-boot-tutorial/bios-boot-tutorial.py b/tutorials/bios-boot-tutorial/bios-boot-tutorial.py index f4a03603b11..11aee4e34c1 100755 --- a/tutorials/bios-boot-tutorial/bios-boot-tutorial.py +++ b/tutorials/bios-boot-tutorial/bios-boot-tutorial.py @@ -266,7 +266,7 @@ def msigReplaceSystem(): def produceNewAccounts(): with open('newusers', 'w') as f: for i in range(120_000, 200_000): - x = getOutput(args.cleos + 'create key') + x = getOutput(args.cleos + 'create key --to-console') r = re.match('Private key: *([^ \n]*)\nPublic key: *([^ \n]*)', x, re.DOTALL | re.MULTILINE) name = 'user' for j in range(7, -1, -1): From 728fcd3e89d958fdd08ee5e033bee91d3511b9da Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Mon, 23 Jul 2018 20:39:39 -0500 Subject: [PATCH 075/294] Fixed option/flag descriptions. GH #4554 --- programs/cleos/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 917fc09bb27..bbd422bf570 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -1699,8 +1699,8 @@ int main( int argc, char** argv ) { } }); create_key->add_flag( "--r1", r1, "Generate a key using the R1 curve (iPhone), instead of the K1 curve (Bitcoin)" ); - create_key->add_option("-f,--file", key_file, localized("Name of file to write wallet password output to. (Must be set, unless \"--to-console\" is passed")); - create_key->add_flag( "--to-console", print_console, localized("Print password to console.")); + create_key->add_option("-f,--file", key_file, localized("Name of file to write private/public key output to. (Must be set, unless \"--to-console\" is passed")); + create_key->add_flag( "--to-console", print_console, localized("Print private/public keys to console.")); // create account auto createAccount = create_account_subcommand( create, true /*simple*/ ); From 446d31bfdb46d2085fc762ad6f62756d1ad92fa6 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 24 Jul 2018 11:37:15 -0500 Subject: [PATCH 076/294] Optimized block find. Minimized logging of queue size. --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 87cfcda5077..dcc9a7705bf 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -181,12 +181,13 @@ void mongo_db_plugin_impl::queue( Queue& queue, const Entry& e ) { if( queue_size > max_queue_size ) { lock.unlock(); condition.notify_one(); - queue_sleep_time += 100; - wlog("queue size: ${q}", ("q", queue_size)); + queue_sleep_time += 10; + if( queue_sleep_time > 1000 ) + wlog("queue size: ${q}", ("q", queue_size)); boost::this_thread::sleep_for( boost::chrono::milliseconds( queue_sleep_time )); lock.lock(); } else { - queue_sleep_time -= 100; + queue_sleep_time -= 10; if( queue_sleep_time < 0 ) queue_sleep_time = 0; } queue.emplace_back( e ); @@ -362,7 +363,10 @@ auto find_account( mongocxx::collection& accounts, const account_name& name ) { auto find_block( mongocxx::collection& blocks, const string& id ) { using bsoncxx::builder::basic::make_document; using bsoncxx::builder::basic::kvp; - return blocks.find_one( make_document( kvp( "block_id", id ))); + + mongocxx::options::find options; + options.projection( make_document( kvp( "_id", 1 )) ); // only return _id + return blocks.find_one( make_document( kvp( "block_id", id )), options); } void handle_mongo_exception( const std::string& desc, int line_num ) { @@ -863,9 +867,6 @@ void mongo_db_plugin_impl::_process_irreversible_block(const chain::block_state_ const auto block_id_str = block_id.str(); const auto block_num = bs->block->block_num(); - // genesis block 1 is not signaled to accepted_block -// if (block_num < 2) return; - auto now = std::chrono::duration_cast( std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()}); From 4d48c20fc07c91faa045eebf5a5fb8f5df81e41a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 24 Jul 2018 16:36:56 -0500 Subject: [PATCH 077/294] Fix compiler warning --- contracts/integration_test/integration_test.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/integration_test/integration_test.cpp b/contracts/integration_test/integration_test.cpp index 87a0edeecc1..6b6997903b7 100644 --- a/contracts/integration_test/integration_test.cpp +++ b/contracts/integration_test/integration_test.cpp @@ -18,16 +18,17 @@ struct integration_test : public eosio::contract { uint64_t num ) { require_auth( from ); eosio_assert( is_account( to ), "to account does not exist"); + eosio_assert( num < std::numeric_limits::max(), "num to large"); payloads data ( _self, from ); uint64_t key = 0; const uint64_t num_keys = 5; while (data.find( key ) != data.end()) { key += num_keys; } - for (uint64_t i = 0; i < num_keys; ++i) { + for (size_t i = 0; i < num_keys; ++i) { data.emplace(from, [&]( auto& g ) { g.key = key + i; - g.data = vector(num, 5); + g.data = vector( static_cast(num), 5); }); } } From 12231814f397668367a6c5b23b56bc8c88634ee0 Mon Sep 17 00:00:00 2001 From: Spartucus Date: Wed, 25 Jul 2018 11:16:22 +0800 Subject: [PATCH 078/294] Remove useless code --- libraries/chain/include/eosio/chain/wasm_eosio_injection.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/chain/include/eosio/chain/wasm_eosio_injection.hpp b/libraries/chain/include/eosio/chain/wasm_eosio_injection.hpp index d9f09b61719..f5ebf01c1f7 100644 --- a/libraries/chain/include/eosio/chain/wasm_eosio_injection.hpp +++ b/libraries/chain/include/eosio/chain/wasm_eosio_injection.hpp @@ -230,7 +230,6 @@ namespace eosio { namespace chain { namespace wasm_injections { static constexpr bool post = false; static void init() { fcnt = 0; } static void accept( wasm_ops::instr* inst, wasm_ops::visitor_arg& arg ) { - size_t inst_idx = checktime_block_type::block_stack.top(); fcnt = instruction_counter::tcnt - instruction_counter::bcnt; } static size_t fcnt; From 4bc9271b823568a4277dea84b0a5ca5218942474 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 11 Jul 2018 09:14:03 -0500 Subject: [PATCH 079/294] Improvements for create account methods. --- tests/Cluster.py | 5 ++--- tests/Node.py | 45 +++++++++++++------------------------ tests/nodeos_run_test.py | 15 +++---------- tests/nodeos_voting_test.py | 5 +---- tests/p2p_stress.py | 4 ++-- 5 files changed, 24 insertions(+), 50 deletions(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index 9711f5c585e..1d8bd5f687e 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -571,8 +571,7 @@ def createAccountAndVerify(self, account, creator, stakedDeposit=1000, stakeNet= """create account, verify account and return transaction id""" assert(len(self.nodes) > 0) node=self.nodes[0] - trans=node.createInitializeAccount(account, creator, stakedDeposit, stakeNet=stakeNet, stakeCPU=stakeCPU, buyRAM=buyRAM) - assert(trans) + trans=node.createInitializeAccount(account, creator, stakedDeposit, stakeNet=stakeNet, stakeCPU=stakeCPU, buyRAM=buyRAM, exitOnError=True) assert(node.verifyAccount(account)) return trans @@ -589,7 +588,7 @@ def createAccountAndVerify(self, account, creator, stakedDeposit=1000, stakeNet= # return transId # return None - def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False, stakeNet=100, stakeCPU=100, buyRAM=100): + def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False, stakeNet=100, stakeCPU=100, buyRAM=100, exitOnError=False): assert(len(self.nodes) > 0) node=self.nodes[0] trans=node.createInitializeAccount(account, creatorAccount, stakedDeposit, waitForTransBlock, stakeNet=stakeNet, stakeCPU=stakeCPU, buyRAM=buyRAM) diff --git a/tests/Node.py b/tests/Node.py index c923ceb947c..ab330201211 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -385,22 +385,16 @@ def isTransFinalized(self, transId): assert(isinstance(blockId, int)) return self.isBlockFinalized(blockId) - # Create & initialize account and return creation transactions. Return transaction json object - def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False, stakeNet=100, stakeCPU=100, buyRAM=100): - cmd='%s %s system newaccount -j %s %s %s %s --stake-net "%s %s" --stake-cpu "%s %s" --buy-ram "%s %s"' % ( - Utils.EosClientPath, self.endpointArgs, creatorAccount.name, account.name, - account.ownerPublicKey, account.activePublicKey, - stakeNet, CORE_SYMBOL, stakeCPU, CORE_SYMBOL, buyRAM, CORE_SYMBOL) - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - trans=None - try: - trans=Utils.runCmdReturnJson(cmd) - transId=Node.getTransId(trans) - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during account creation. %s" % (msg)) - return None + # Create & initialize account and return creation transactions. Return transaction json object + def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False, stakeNet=100, stakeCPU=100, buyRAM=100, exitOnError=False): + cmdDesc="system newaccount" + cmd='%s -j %s %s %s %s --stake-net "%s %s" --stake-cpu "%s %s" --buy-ram "%s %s"' % ( + cmdDesc, creatorAccount.name, account.name, account.ownerPublicKey, + account.activePublicKey, stakeNet, CORE_SYMBOL, stakeCPU, CORE_SYMBOL, buyRAM, CORE_SYMBOL) + msg="(creator account=%s, account=%s)" % (creatorAccount.name, account.name); + trans=self.processCmd(cmd, cmdDesc, silentErrors=False, exitOnError=exitOnError, exitMsg=msg) + transId=Node.getTransId(trans) if stakedDeposit > 0: self.waitForTransInBlock(transId) # seems like account creation needs to be finalized before transfer can happen @@ -412,22 +406,15 @@ def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, w return trans - def createAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False): + def createAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False, exitOnError=False): """Create account and return creation transactions. Return transaction json object. waitForTransBlock: wait on creation transaction id to appear in a block.""" - cmd="%s %s create account -j %s %s %s %s" % ( - Utils.EosClientPath, self.endpointArgs, creatorAccount.name, account.name, - account.ownerPublicKey, account.activePublicKey) - - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - trans=None - try: - trans=Utils.runCmdReturnJson(cmd) - transId=Node.getTransId(trans) - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during account creation. %s" % (msg)) - return None + cmdDesc="create account" + cmd="%s -j %s %s %s %s" % ( + cmdDesc, creatorAccount.name, account.name, account.ownerPublicKey, account.activePublicKey) + msg="(creator account=%s, account=%s)" % (creatorAccount.name, account.name); + trans=self.processCmd(cmd, cmdDesc, silentErrors=False, exitOnError=exitOnError, exitMsg=msg) + transId=Node.getTransId(trans) if stakedDeposit > 0: self.waitForTransInBlock(transId) # seems like account creation needs to be finlized before transfer can happen diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index ed537bdcf32..3f650fcf3c1 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -217,22 +217,13 @@ def cmdError(name, cmdCode=0, exitNow=False): # create accounts via eosio as otherwise a bid is needed Print("Create new account %s via %s" % (testeraAccount.name, cluster.eosioAccount.name)) - transId=node.createInitializeAccount(testeraAccount, cluster.eosioAccount, stakedDeposit=0, waitForTransBlock=False) - if transId is None: - cmdError("%s create account" % (testeraAccount.name)) - errorExit("Failed to create account %s" % (testeraAccount.name)) + transId=node.createInitializeAccount(testeraAccount, cluster.eosioAccount, stakedDeposit=0, waitForTransBlock=False, exitOnError=True) Print("Create new account %s via %s" % (currencyAccount.name, cluster.eosioAccount.name)) - transId=node.createInitializeAccount(currencyAccount, cluster.eosioAccount, stakedDeposit=5000) - if transId is None: - cmdError("%s create account" % (ClientName)) - errorExit("Failed to create account %s" % (currencyAccount.name)) + transId=node.createInitializeAccount(currencyAccount, cluster.eosioAccount, stakedDeposit=5000, exitOnError=True) Print("Create new account %s via %s" % (exchangeAccount.name, cluster.eosioAccount.name)) - transId=node.createInitializeAccount(exchangeAccount, cluster.eosioAccount, waitForTransBlock=True) - if transId is None: - cmdError("%s create account" % (ClientName)) - errorExit("Failed to create account %s" % (exchangeAccount.name)) + transId=node.createInitializeAccount(exchangeAccount, cluster.eosioAccount, waitForTransBlock=True, exitOnError=True) Print("Validating accounts after user accounts creation") accounts=[testeraAccount, currencyAccount, exchangeAccount] diff --git a/tests/nodeos_voting_test.py b/tests/nodeos_voting_test.py index 3453be74707..87057d9da4d 100755 --- a/tests/nodeos_voting_test.py +++ b/tests/nodeos_voting_test.py @@ -275,10 +275,7 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): # create accounts via eosio as otherwise a bid is needed for account in accounts: Print("Create new account %s via %s" % (account.name, cluster.eosioAccount.name)) - trans=node.createInitializeAccount(account, cluster.eosioAccount, stakedDeposit=0, waitForTransBlock=False, stakeNet=1000, stakeCPU=1000, buyRAM=1000) - if trans is None: - Utils.cmdError("%s create account" % (account.name)) - errorExit("Failed to create account %s" % (account.name)) + trans=node.createInitializeAccount(account, cluster.eosioAccount, stakedDeposit=0, waitForTransBlock=False, stakeNet=1000, stakeCPU=1000, buyRAM=1000, exitOnError=True) transferAmount="100000000.0000 {0}".format(CORE_SYMBOL) Print("Transfer funds %s from account %s to %s" % (transferAmount, cluster.eosioAccount.name, account.name)) if node.transferFunds(cluster.eosioAccount, account, transferAmount, "test transfer") is None: diff --git a/tests/p2p_stress.py b/tests/p2p_stress.py index ef6080d7a07..8b775ba64b6 100644 --- a/tests/p2p_stress.py +++ b/tests/p2p_stress.py @@ -34,7 +34,7 @@ def execute(self, cmdInd, node, ta, eosio): ta.name = self.randAcctName() acc1 = copy.copy(ta) print("creating new account %s" % (ta.name)) - tr = node.createAccount(ta, eosio, stakedDeposit=0, waitForTransBlock=True) + tr = node.createAccount(ta, eosio, stakedDeposit=0, waitForTransBlock=True, exitOnError=True) trid = node.getTransId(tr) if trid is None: return ([], "", 0.0, "failed to create account") @@ -43,7 +43,7 @@ def execute(self, cmdInd, node, ta, eosio): ta.name = self.randAcctName() acc2 = copy.copy(ta) print("creating new account %s" % (ta.name)) - tr = node.createAccount(ta, eosio, stakedDeposit=0, waitForTransBlock=True) + tr = node.createAccount(ta, eosio, stakedDeposit=0, waitForTransBlock=True, exitOnError=True) trid = node.getTransId(tr) if trid is None: return ([], "", 0.0, "failed to create account") From 1a61d22bba1ce940745294575b757253f338cc8c Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 11 Jul 2018 09:15:16 -0500 Subject: [PATCH 080/294] Command error cleanup. --- tests/nodeos_run_test.py | 9 +-------- tests/testUtils.py | 7 ++----- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index 3f650fcf3c1..4330cafd890 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -17,16 +17,9 @@ Print=Utils.Print errorExit=Utils.errorExit - +cmdError=Utils.cmdError from core_symbol import CORE_SYMBOL -def cmdError(name, cmdCode=0, exitNow=False): - msg="FAILURE - %s%s" % (name, ("" if cmdCode == 0 else (" returned error code %d" % cmdCode))) - if exitNow: - errorExit(msg, True) - else: - Print(msg) - args = TestHelper.parse_args({"--host","--port","--prod-count","--defproducera_prvt_key","--defproducerb_prvt_key","--mongodb" ,"--dump-error-details","--dont-launch","--keep-logs","-v","--leave-running","--only-bios","--clean-run" ,"--sanity-test"}) diff --git a/tests/testUtils.py b/tests/testUtils.py index b2576218b78..0713e70f213 100755 --- a/tests/testUtils.py +++ b/tests/testUtils.py @@ -84,12 +84,9 @@ def errorExit(msg="", raw=False, errorCode=1): exit(errorCode) @staticmethod - def cmdError(name, cmdCode=0, exitNow=False): + def cmdError(name, cmdCode=0): msg="FAILURE - %s%s" % (name, ("" if cmdCode == 0 else (" returned error code %d" % cmdCode))) - if exitNow: - Utils.errorExit(msg, True) - else: - Utils.Print(msg) + Utils.Print(msg) @staticmethod def waitForObj(lam, timeout=None): From 1a8f971947da4843b5bb42d70a3ba14fda286513 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 11 Jul 2018 09:27:41 -0500 Subject: [PATCH 081/294] getTransaction method cleanup. --- tests/Node.py | 40 +++++++++---------- ...onsensus-validation-malicious-producers.py | 2 +- tests/nodeos_run_test.py | 6 +-- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index ab330201211..82652dd2743 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -229,27 +229,18 @@ def isBlockFinalized(self, blockNum): return finalized # pylint: disable=too-many-branches - def getTransaction(self, transId, silentErrors=False): + def getTransaction(self, transId, silentErrors=False, exitOnError=False): if not self.enableMongo: - cmd="%s %s get transaction %s" % (Utils.EosClientPath, self.endpointArgs, transId) - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - try: - trans=Utils.runCmdReturnJson(cmd) - return trans - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - if "Failed to connect" in msg: - Utils.Print("ERROR: Node is unreachable. %s" % (msg)) - raise - if not silentErrors: - Utils.Print("ERROR: Exception during transaction retrieval. %s" % (msg)) - return None + cmdDesc="get transaction" + cmd="%s %s" % (cmdDesc, transId) + msg="(transaction id=%s)" % (transId); + return self.processCmd(cmd, cmdDesc, silentErrors=silentErrors, exitOnError=exitOnError, exitMsg=msg) else: - return self.getTransactionMdb(transId, silentErrors) + return self.getTransactionMdb(transId, silentErrors=silentErrors, exitOnError=exitOnError) return None - def getTransactionMdb(self, transId, silentErrors=False): + def getTransactionMdb(self, transId, silentErrors=False, exitOnError=False): """Get transaction from MongoDB. Since DB only contains finalized blocks, transactions can take a while to appear in DB.""" cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) #subcommand='db.Transactions.findOne( { $and : [ { "trx_id": "%s" }, {"irreversible":true} ] } )' % (transId) @@ -257,11 +248,18 @@ def getTransactionMdb(self, transId, silentErrors=False): if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand) + if trans is None and exitOnError: + Utils.cmdError("could not retrieve transaction in mongodb for transaction id=%s" % (transId)) + errorExit("Failed to retrieve transaction in mongodb for transaction id=%s" % (transId)) return trans except subprocess.CalledProcessError as ex: - if not silentErrors: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during get db node get trans. %s" % (msg)) + msg=ex.output.decode("utf-8") + errorMsg="Exception during get db node get trans in mongodb with transaction id=%s. %s" % (transId,msg) + if exitOnError: + Utils.cmdError("" % (errorMsg)) + errorExit("Failed to retrieve transaction in mongodb for transaction id=%s" % (transId)) + elif not silentErrors: + Utils.Print("ERROR: %s" % (errorMsg)) return None def isTransInBlock(self, transId, blockId): @@ -302,9 +300,7 @@ def getBlockIdByTransId(self, transId): """Given a transaction Id (string), will return block id (int) containing the transaction""" assert(transId) assert(isinstance(transId, str)) - trans=self.getTransaction(transId) - if trans is None: - return None + trans=self.getTransaction(transId, exitOnError=True) refBlockNum=None key="" diff --git a/tests/consensus-validation-malicious-producers.py b/tests/consensus-validation-malicious-producers.py index b31fd8ba455..3395f0e0f22 100755 --- a/tests/consensus-validation-malicious-producers.py +++ b/tests/consensus-validation-malicious-producers.py @@ -338,7 +338,7 @@ def myTest(transWillEnterBlock): return False Print("Get details for transaction %s" % (transId)) - transaction=node2.getTransaction(transId) + transaction=node2.getTransaction(transId, exitOnError=True) signature=transaction["transaction"]["signatures"][0] blockNum=int(transaction["transaction"]["ref_block_num"]) diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index 4330cafd890..111b6fca4b1 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -300,14 +300,10 @@ node.waitForTransInBlock(transId) - transaction=node.getTransaction(transId) - if transaction is None: - cmdError("%s get transaction trans_id" % (ClientName)) - errorExit("Failed to retrieve transaction details %s" % (transId)) + transaction=node.getTransaction(transId, exitOnError=True) typeVal=None amountVal=None - assert(transaction) key="" try: if not enableMongo: From 57b6032fc9eb2647f13e0fef4bfa999ad044bea1 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 11 Jul 2018 09:31:38 -0500 Subject: [PATCH 082/294] processCmd method cleanup. --- tests/Node.py | 64 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index 82652dd2743..e256502949a 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -859,37 +859,64 @@ def delegatebw(self, fromAccount, netQuantity, cpuQuantity, toAccount=None, tran if toAccount is None: toAccount=fromAccount - specificCmd="system delegatebw" + cmdDesc="system delegatebw" transferStr="--transfer" if transferTo else "" - cmd="%s %s %s -j %s %s \"%s %s\" \"%s %s\" %s" % ( - Utils.EosClientPath, self.endpointArgs, specificCmd, fromAccount.name, toAccount.name, netQuantity, CORE_SYMBOL, cpuQuantity, CORE_SYMBOL, transferStr) - return self.processCmd(cmd, specificCmd, waitForTransBlock) + cmd="%s -j %s %s \"%s %s\" \"%s %s\" %s" % ( + cmdDesc, fromAccount.name, toAccount.name, netQuantity, CORE_SYMBOL, cpuQuantity, CORE_SYMBOL, transferStr) + trans=self.processCmd(cmd, cmdDesc, waitForTransBlock) + + transId=Node.getTransId(trans) + if waitForTransBlock and not self.waitForTransInBlock(transId): + return None + return trans def regproducer(self, producer, url, location, waitForTransBlock=False): - specificCmd="system regproducer" - cmd="%s %s %s -j %s %s %s %s" % ( - Utils.EosClientPath, self.endpointArgs, specificCmd, producer.name, producer.activePublicKey, url, location) - return self.processCmd(cmd, specificCmd, waitForTransBlock) + cmdDesc="system regproducer" + cmd="%s -j %s %s %s %s" % ( + cmdDesc, producer.name, producer.activePublicKey, url, location) + trans=self.processCmd(cmd, cmdDesc, waitForTransBlock) + + transId=Node.getTransId(trans) + if waitForTransBlock and not self.waitForTransInBlock(transId): + return None + return trans def vote(self, account, producers, waitForTransBlock=False): - specificCmd = "system voteproducer prods" - cmd="%s %s %s -j %s %s" % ( - Utils.EosClientPath, self.endpointArgs, specificCmd, account.name, " ".join(producers)) - return self.processCmd(cmd, specificCmd, waitForTransBlock) + cmdDesc = "system voteproducer prods" + cmd="%s -j %s %s" % ( + cmdDesc, account.name, " ".join(producers)) + trans=self.processCmd(cmd, cmdDesc, waitForTransBlock) - def processCmd(self, cmd, cmdDesc, waitForTransBlock): + transId=Node.getTransId(trans) + if waitForTransBlock and not self.waitForTransInBlock(transId): + return None + return trans + + def processCmd(self, cmd, cmdDesc, silentErrors=True, exitOnError=False, exitMsg=None): + cmd="%s %s %s" % (Utils.EosClientPath, self.endpointArgs, cmd) if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) trans=None try: trans=Utils.runCmdReturnJson(cmd) except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during %s. %s" % (cmdDesc, msg)) + if not silentErrors: + msg=ex.output.decode("utf-8") + errorMsg="Exception during %s. %s" % (cmdDesc, msg) + if errorOnExit: + Utils.cmdError(errorMsg) + Utils.errorExit(errorMsg) + else: + Utils.Print("ERROR: %s" % (errorMsg)) return None - transId=Node.getTransId(trans) - if waitForTransBlock and not self.waitForTransInBlock(transId): - return None + if exitMsg is not None: + exitMsg=": " + exitMsg + else: + exitMsg="" + if exitOnError and trans is None: + Utils.cmdError("could not %s%s" % (cmdDesc,exitMsg)) + errorExit("Failed to %s" % (cmdDesc)) + return trans def getInfo(self, silentErrors=False): @@ -1037,7 +1064,6 @@ def relaunch(self, nodeId, chainArg, newChain=False, timeout=Utils.systemWaitTim stdoutFile="%s/stdout.%s.txt" % (dataDir, dateStr) stderrFile="%s/stderr.%s.txt" % (dataDir, dateStr) with open(stdoutFile, 'w') as sout, open(stderrFile, 'w') as serr: - #cmd=self.cmd + ("" if chainArg is None else (" " + chainArg)) cmd=myCmd + ("" if chainArg is None else (" " + chainArg)) Utils.Print("cmd: %s" % (cmd)) popen=subprocess.Popen(cmd.split(), stdout=sout, stderr=serr) From 2e1a4a1ee40a6fba7b88c0a5719d0ff96cc5db6e Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 11 Jul 2018 09:35:51 -0500 Subject: [PATCH 083/294] getBlock method cleanup. --- tests/Node.py | 20 +++++++------------ ...onsensus-validation-malicious-producers.py | 2 +- tests/nodeos_run_test.py | 9 +++------ tests/nodeos_voting_test.py | 5 +---- 4 files changed, 12 insertions(+), 24 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index e256502949a..54b0e485666 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -142,20 +142,14 @@ def validateAccounts(self, accounts): raise # pylint: disable=too-many-branches - def getBlock(self, blockNum, silentErrors=False): + def getBlock(self, blockNum, silentErrors=False, exitOnError=False): """Given a blockId will return block details.""" assert(isinstance(blockNum, int)) if not self.enableMongo: - cmd="%s %s get block %d" % (Utils.EosClientPath, self.endpointArgs, blockNum) - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - try: - block=Utils.runCmdReturnJson(cmd) - return block - except subprocess.CalledProcessError as ex: - if not silentErrors: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during get block. %s" % (msg)) - return None + cmdDesc="get block" + cmd="%s %d" % (cmdDesc, blockNum) + msg="(block number=%s)" % (blockNum); + return self.processCmd(cmd, cmdDesc, silentErrors=silentErrors, exitOnError=exitOnError, exitMsg=msg) else: cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand='db.blocks.findOne( { "block_num": %d } )' % (blockNum) @@ -269,8 +263,8 @@ def isTransInBlock(self, transId, blockId): assert(blockId) assert(isinstance(blockId, int)) - block=self.getBlock(blockId) - assert(block) + block=self.getBlock(blockId, exitOnError=True) + transactions=None key="" try: diff --git a/tests/consensus-validation-malicious-producers.py b/tests/consensus-validation-malicious-producers.py index 3395f0e0f22..e6cd0613b05 100755 --- a/tests/consensus-validation-malicious-producers.py +++ b/tests/consensus-validation-malicious-producers.py @@ -345,7 +345,7 @@ def myTest(transWillEnterBlock): blockNum += 1 Print("Our transaction is in block %d" % (blockNum)) - block=node2.getBlock(blockNum) + block=node2.getBlock(blockNum, exitOnError=True) cycles=block["cycles"] if len(cycles) > 0: blockTransSignature=cycles[0][0]["user_input"][0]["signatures"][0] diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index 111b6fca4b1..fc87b346895 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -488,8 +488,8 @@ Print("Test for block decoded packed transaction (issue 2932)") blockId=node.getBlockIdByTransId(transId) assert(blockId) - block=node.getBlock(blockId) - assert(block) + block=node.getBlock(blockId, exitOnError=True) + transactions=None try: if not enableMongo: @@ -691,10 +691,7 @@ if enableMongo: start=2 # block 1 (genesis block) is not signaled to the plugins, so not available in DB for blockNum in range(start, currentBlockNum+1): - block=node.getBlock(blockNum, silentErrors=False) - if block is None: - cmdError("%s get block" % (ClientName)) - errorExit("get block by num %d" % blockNum) + block=node.getBlock(blockNum, silentErrors=False, exitOnError=True) if enableMongo: blockId=block["block_id"] diff --git a/tests/nodeos_voting_test.py b/tests/nodeos_voting_test.py index 87057d9da4d..e8a46c26989 100755 --- a/tests/nodeos_voting_test.py +++ b/tests/nodeos_voting_test.py @@ -35,10 +35,7 @@ def vote(node, account, producers): def getBlockProducer(node, blockNum): node.waitForBlock(blockNum) - block=node.getBlock(blockNum) - if block is None: - Utils.cmdError("could not get block number %s" % (blockNum)) - errorExit("Failed to get block") + block=node.getBlock(blockNum, exitOnError=True) blockProducer=block["producer"] if blockProducer is None: Utils.cmdError("could not get producer for block number %s" % (blockNum)) From 584dc44f271d83bfe769cfd484f62b0f5e618fe4 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 11 Jul 2018 09:37:15 -0500 Subject: [PATCH 084/294] getEosAccount method cleanup. --- tests/Node.py | 22 ++++++++-------------- tests/nodeos_run_test.py | 5 +---- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index 54b0e485666..ce616051b03 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -130,9 +130,8 @@ def validateAccounts(self, accounts): assert(account) assert(isinstance(account, Account)) if Utils.Debug: Utils.Print("Validating account %s" % (account.name)) - accountInfo=self.getEosAccount(account.name) + accountInfo=self.getEosAccount(account.name, exitOnError=True) try: - assert(accountInfo) if not self.enableMongo: assert(accountInfo["account_name"] == account.name) else: @@ -416,20 +415,15 @@ def createAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTran return trans - def getEosAccount(self, name): + def getEosAccount(self, name, exitOnError=False): assert(isinstance(name, str)) if not self.enableMongo: - cmd="%s %s get account -j %s" % (Utils.EosClientPath, self.endpointArgs, name) - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - try: - trans=Utils.runCmdReturnJson(cmd) - return trans - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during get account. %s" % (msg)) - return None + cmdDesc="get account" + cmd="%s -j %s" % (cmdDesc, name) + msg="( getEosAccount(name=%s) )" % (name); + return self.processCmd(cmd, cmdDesc, silentErrors=False, exitOnError=exitOnError, exitMsg=msg) else: - return self.getEosAccountFromDb(name) + return self.getEosAccountFromDb(name, exitOnError=exitOnError) def getEosAccountFromDb(self, name): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) @@ -822,7 +816,7 @@ def pushMessage(self, account, action, data, opts, silentErrors=False): if opts is not None: cmdArr += opts.split() s=" ".join(cmdArr) - if Utils.Debug: Utils.Print("cmd: %s" % (s)) + if Utils.Debug: Utils.Print("cmd: %s" % (cmdArr)) try: trans=Utils.runCmdArrReturnJson(cmdArr) return (True, trans) diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index fc87b346895..b4a490845f4 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -672,10 +672,7 @@ errorExit("Failed to unlock wallet %s" % (defproduceraWallet.name)) Print("Get account defproducera") - account=node.getEosAccount(defproduceraAccount.name) - if account is None: - cmdError("%s get account" % (ClientName)) - errorExit("Failed to get account %s" % (defproduceraAccount.name)) + account=node.getEosAccount(defproduceraAccount.name, exitOnError=True) Print("Unlocking wallet \"%s\"." % (defproduceraWallet.name)) if not walletMgr.unlockWallet(testWallet): From d6362c6c7a2c89eb3255533e2897b92ce7e3747e Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 11 Jul 2018 10:00:50 -0500 Subject: [PATCH 085/294] Added exitOnError processing to getPermission and changed to use processCmd. --- tests/Node.py | 15 ++++----------- tests/nodeos_run_test.py | 10 ++-------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index ce616051b03..d7992e1fdc6 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -826,17 +826,10 @@ def pushMessage(self, account, action, data, opts, silentErrors=False): Utils.Print("ERROR: Exception during push message. %s" % (msg)) return (False, msg) - def setPermission(self, account, code, pType, requirement, waitForTransBlock=False): - cmd="%s %s set action permission -j %s %s %s %s" % ( - Utils.EosClientPath, self.endpointArgs, account, code, pType, requirement) - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - trans=None - try: - trans=Utils.runCmdReturnJson(cmd) - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during set permission. %s" % (msg)) - return None + def setPermission(self, account, code, pType, requirement, waitForTransBlock=False, exitOnError=False): + cmdDesc="set action permission" + cmd="%s -j %s %s %s %s" % (cmdDesc, account, code, pType, requirement) + trans=self.processCmd(cmd, cmdDesc, silentErrors=False, exitOnError=exitOnError) transId=Node.getTransId(trans) if waitForTransBlock and not self.waitForTransInBlock(transId): diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index b4a490845f4..75678189d68 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -649,17 +649,11 @@ code="currency1111" pType="transfer" requirement="active" - trans=node.setPermission(testeraAccount.name, code, pType, requirement, waitForTransBlock=True) - if trans is None: - cmdError("%s set action permission set" % (ClientName)) - errorExit("Failed to set permission") + trans=node.setPermission(testeraAccount.name, code, pType, requirement, waitForTransBlock=True, exitOnError=True) Print("remove permission") requirement="null" - trans=node.setPermission(testeraAccount.name, code, pType, requirement, waitForTransBlock=True) - if trans is None: - cmdError("%s set action permission set" % (ClientName)) - errorExit("Failed to remove permission") + trans=node.setPermission(testeraAccount.name, code, pType, requirement, waitForTransBlock=True, exitOnError=True) Print("Locking all wallets.") if not walletMgr.lockAllWallets(): From c7177ec0c666358707721908abca6e53738a361a Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 11 Jul 2018 10:18:44 -0500 Subject: [PATCH 086/294] Added exitOnError processing to delegatebw, regproducer, and vote. --- tests/Node.py | 15 +++++++++------ tests/nodeos_voting_test.py | 15 +++------------ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index d7992e1fdc6..0cc0cfa5e63 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -836,7 +836,7 @@ def setPermission(self, account, code, pType, requirement, waitForTransBlock=Fal return None return trans - def delegatebw(self, fromAccount, netQuantity, cpuQuantity, toAccount=None, transferTo=False, waitForTransBlock=False): + def delegatebw(self, fromAccount, netQuantity, cpuQuantity, toAccount=None, transferTo=False, waitForTransBlock=False, exitOnError=False): if toAccount is None: toAccount=fromAccount @@ -844,29 +844,32 @@ def delegatebw(self, fromAccount, netQuantity, cpuQuantity, toAccount=None, tran transferStr="--transfer" if transferTo else "" cmd="%s -j %s %s \"%s %s\" \"%s %s\" %s" % ( cmdDesc, fromAccount.name, toAccount.name, netQuantity, CORE_SYMBOL, cpuQuantity, CORE_SYMBOL, transferStr) - trans=self.processCmd(cmd, cmdDesc, waitForTransBlock) + msg="fromAccount=%s, toAccount=%s" % (fromAccount.name, toAccount.name); + trans=self.processCmd(cmd, cmdDesc, waitForTransBlock, exitOnError=exitOnError, exitMsg=msg) transId=Node.getTransId(trans) if waitForTransBlock and not self.waitForTransInBlock(transId): return None return trans - def regproducer(self, producer, url, location, waitForTransBlock=False): + def regproducer(self, producer, url, location, waitForTransBlock=False, exitOnError=False): cmdDesc="system regproducer" cmd="%s -j %s %s %s %s" % ( cmdDesc, producer.name, producer.activePublicKey, url, location) - trans=self.processCmd(cmd, cmdDesc, waitForTransBlock) + msg="producer=%s" % (producer.name); + trans=self.processCmd(cmd, cmdDesc, waitForTransBlock, exitOnError=exitOnError, exitMsg=msg) transId=Node.getTransId(trans) if waitForTransBlock and not self.waitForTransInBlock(transId): return None return trans - def vote(self, account, producers, waitForTransBlock=False): + def vote(self, account, producers, waitForTransBlock=False, exitOnError=False): cmdDesc = "system voteproducer prods" cmd="%s -j %s %s" % ( cmdDesc, account.name, " ".join(producers)) - trans=self.processCmd(cmd, cmdDesc, waitForTransBlock) + msg="account=%s, producers=[ %s ]" % (account.name, ", ".join(producers)); + trans=self.processCmd(cmd, cmdDesc, waitForTransBlock, exitOnError=exitOnError, exitMsg=msg) transId=Node.getTransId(trans) if waitForTransBlock and not self.waitForTransInBlock(transId): diff --git a/tests/nodeos_voting_test.py b/tests/nodeos_voting_test.py index e8a46c26989..e8c7612ca7f 100755 --- a/tests/nodeos_voting_test.py +++ b/tests/nodeos_voting_test.py @@ -27,10 +27,7 @@ def populate(node, num): def vote(node, account, producers): Print("Votes for %s" % (account.name)) - trans=node.vote(account, producers, waitForTransBlock=False) - if trans is None: - Utils.cmdError("voting with %s" % (account.name)) - errorExit("Failed to vote with account %s" % (account.name)) + trans=node.vote(account, producers, waitForTransBlock=False, exitOnError=True) return trans def getBlockProducer(node, blockNum): @@ -250,10 +247,7 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): node=cluster.getNode(i) node.producers=Cluster.parseProducers(i) for prod in node.producers: - trans=node.regproducer(cluster.defProducerAccounts[prod], "http::/mysite.com", 0, waitForTransBlock=False) - if trans is None: - Utils.cmdError("registering producer %s" % (prod.name)) - errorExit("Failed registering producer %s" % (prod.name)) + trans=node.regproducer(cluster.defProducerAccounts[prod], "http::/mysite.com", 0, waitForTransBlock=False, exitOnError=True) node0=cluster.getNode(0) if node0 is None: @@ -278,10 +272,7 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): if node.transferFunds(cluster.eosioAccount, account, transferAmount, "test transfer") is None: errorExit("Failed to transfer funds %d from account %s to %s" % ( transferAmount, cluster.eosioAccount.name, account.name)) - trans=node.delegatebw(account, 20000000.0000, 20000000.0000) - if trans is None: - Utils.cmdError("delegate bandwidth for %s" % (account.name)) - errorExit("Failed to delegate bandwidth for %s" % (account.name)) + trans=node.delegatebw(account, 20000000.0000, 20000000.0000, exitOnError=True) # containers for tracking producers prodsActive={} From a39187a1f196112f9a2cf523ec2353a3e70679b9 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 11 Jul 2018 12:24:35 -0500 Subject: [PATCH 087/294] Added exitOnError processing to getInfo and checkPulse. --- tests/Cluster.py | 6 +++--- tests/Node.py | 28 +++++++++------------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index 1d8bd5f687e..f1d14505d2e 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -244,7 +244,7 @@ def initializeNodes(self, defproduceraPrvtKey=None, defproducerbPrvtKey=None, on node.setWalletEndpointArgs(self.walletEndpointArgs) if Utils.Debug: Utils.Print("Node: %s", str(node)) - node.checkPulse() + node.checkPulse(exitOnError=True) self.nodes=[node] if defproduceraPrvtKey is not None: @@ -284,7 +284,7 @@ def initializeNodesFromJson(self, nodesJsonStr): node.setWalletEndpointArgs(self.walletEndpointArgs) if Utils.Debug: Utils.Print("Node:", node) - node.checkPulse() + node.checkPulse(exitOnError=True) nodes.append(node) self.nodes=nodes @@ -806,7 +806,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio return False # wait for block production handover (essentially a block produced by anyone but eosio). - lam = lambda: biosNode.getInfo()["head_block_producer"] != "eosio" + lam = lambda: biosNode.getInfo(exitOnError=True)["head_block_producer"] != "eosio" ret=Utils.waitForBool(lam) if not ret: Utils.Print("ERROR: Block production handover failed.") diff --git a/tests/Node.py b/tests/Node.py index 0cc0cfa5e63..99906268407 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -186,8 +186,7 @@ def isBlockPresent(self, blockNum): assert isinstance(blockNum, int) assert (blockNum > 0) - info=self.getInfo(silentErrors=True) - assert(info) + info=self.getInfo(silentErrors=True, exitOnError=True) node_block_num=0 try: node_block_num=int(info["head_block_num"]) @@ -203,8 +202,7 @@ def isBlockFinalized(self, blockNum): assert isinstance(blockNum, int) assert (blockNum > 0) - info=self.getInfo(silentErrors=True) - assert(info) + info=self.getInfo(silentErrors=True, exitOnError=True) node_block_num=0 try: node_block_num=int(info["last_irreversible_block_num"]) @@ -903,17 +901,9 @@ def processCmd(self, cmd, cmdDesc, silentErrors=True, exitOnError=False, exitMsg return trans - def getInfo(self, silentErrors=False): - cmd="%s %s get info" % (Utils.EosClientPath, self.endpointArgs) - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - try: - trans=Utils.runCmdReturnJson(cmd, silentErrors=silentErrors) - return trans - except subprocess.CalledProcessError as ex: - if not silentErrors: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during get info. %s" % (msg)) - return None + def getInfo(self, silentErrors=False, exitOnError=False): + cmdDesc = "get info" + return self.processCmd(cmdDesc, cmdDesc, exitOnError=exitOnError) def getBlockFromDb(self, idx): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) @@ -927,14 +917,14 @@ def getBlockFromDb(self, idx): Utils.Print("ERROR: Exception during get db block. %s" % (msg)) return None - def checkPulse(self): - info=self.getInfo(True) + def checkPulse(self, exitOnError=False): + info=self.getInfo(True, exitOnError=exitOnError) return False if info is None else True def getHeadBlockNum(self): """returns head block number(string) as returned by cleos get info.""" if not self.enableMongo: - info=self.getInfo() + info=self.getInfo(exitOnError=True) if info is not None: headBlockNumTag="head_block_num" return info[headBlockNumTag] @@ -948,7 +938,7 @@ def getHeadBlockNum(self): def getIrreversibleBlockNum(self): if not self.enableMongo: - info=self.getInfo() + info=self.getInfo(exitOnError=True) if info is not None: return info["last_irreversible_block_num"] else: From 6369365cc5f16a8711ce01d100d84310e40fbc5d Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 11 Jul 2018 12:25:03 -0500 Subject: [PATCH 088/294] Added Mdb extension to indicate method for MongoDB. --- tests/Node.py | 2 +- tests/nodeos_run_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index 99906268407..f6117e5f323 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -165,7 +165,7 @@ def getBlock(self, blockNum, silentErrors=False, exitOnError=False): return None - def getBlockById(self, blockId, silentErrors=False): + def getBlockByIdMdb(self, blockId, silentErrors=False): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand='db.blocks.findOne( { "block_id": "%s" } )' % (blockId) if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index 75678189d68..fa3d430242e 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -686,7 +686,7 @@ if enableMongo: blockId=block["block_id"] - block2=node.getBlockById(blockId) + block2=node.getBlockByIdMdb(blockId) if block2 is None: errorExit("mongo get block by id %s" % blockId) From 047c9578b0f19e3513014585bc859622aa67a8c0 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 11 Jul 2018 12:35:52 -0500 Subject: [PATCH 089/294] Added exitOnError processing to getTable. --- tests/Node.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index f6117e5f323..4c1687d042f 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -435,23 +435,17 @@ def getEosAccountFromDb(self, name): Utils.Print("ERROR: Exception during get account from db. %s" % (msg)) return None - def getTable(self, contract, scope, table): - cmd="%s %s get table %s %s %s" % (Utils.EosClientPath, self.endpointArgs, contract, scope, table) - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - try: - trans=Utils.runCmdReturnJson(cmd) - return trans - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during table retrieval. %s" % (msg)) - return None + def getTable(self, contract, scope, table, exitOnError=False): + cmdDesc = "get table" + cmd="%s %s %s %s" % (cmdDesc, contract, scope, table) + msg="contract=%s, scope=%s, table=%s" % (contract, scope, table); + return self.processCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) def getTableAccountBalance(self, contract, scope): assert(isinstance(contract, str)) assert(isinstance(scope, str)) table="accounts" - trans = self.getTable(contract, scope, table) - assert(trans) + trans = self.getTable(contract, scope, table, exitOnError=True) try: return trans["rows"][0]["balance"] except (TypeError, KeyError) as _: From 6917c9ec21cf8589a13bceed0fa51077831b6eea Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 11 Jul 2018 12:37:46 -0500 Subject: [PATCH 090/294] Fixed general errors and cleaned up. --- tests/Node.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index 4c1687d042f..4415af4b22a 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -416,10 +416,10 @@ def createAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTran def getEosAccount(self, name, exitOnError=False): assert(isinstance(name, str)) if not self.enableMongo: - cmdDesc="get account" - cmd="%s -j %s" % (cmdDesc, name) - msg="( getEosAccount(name=%s) )" % (name); - return self.processCmd(cmd, cmdDesc, silentErrors=False, exitOnError=exitOnError, exitMsg=msg) + cmdDesc="get account" + cmd="%s -j %s" % (cmdDesc, name) + msg="( getEosAccount(name=%s) )" % (name); + return self.processCmd(cmd, cmdDesc, silentErrors=False, exitOnError=exitOnError, exitMsg=msg) else: return self.getEosAccountFromDb(name, exitOnError=exitOnError) @@ -837,7 +837,7 @@ def delegatebw(self, fromAccount, netQuantity, cpuQuantity, toAccount=None, tran cmd="%s -j %s %s \"%s %s\" \"%s %s\" %s" % ( cmdDesc, fromAccount.name, toAccount.name, netQuantity, CORE_SYMBOL, cpuQuantity, CORE_SYMBOL, transferStr) msg="fromAccount=%s, toAccount=%s" % (fromAccount.name, toAccount.name); - trans=self.processCmd(cmd, cmdDesc, waitForTransBlock, exitOnError=exitOnError, exitMsg=msg) + trans=self.processCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) transId=Node.getTransId(trans) if waitForTransBlock and not self.waitForTransInBlock(transId): @@ -849,7 +849,7 @@ def regproducer(self, producer, url, location, waitForTransBlock=False, exitOnEr cmd="%s -j %s %s %s %s" % ( cmdDesc, producer.name, producer.activePublicKey, url, location) msg="producer=%s" % (producer.name); - trans=self.processCmd(cmd, cmdDesc, waitForTransBlock, exitOnError=exitOnError, exitMsg=msg) + trans=self.processCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) transId=Node.getTransId(trans) if waitForTransBlock and not self.waitForTransInBlock(transId): @@ -861,7 +861,7 @@ def vote(self, account, producers, waitForTransBlock=False, exitOnError=False): cmd="%s -j %s %s" % ( cmdDesc, account.name, " ".join(producers)) msg="account=%s, producers=[ %s ]" % (account.name, ", ".join(producers)); - trans=self.processCmd(cmd, cmdDesc, waitForTransBlock, exitOnError=exitOnError, exitMsg=msg) + trans=self.processCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) transId=Node.getTransId(trans) if waitForTransBlock and not self.waitForTransInBlock(transId): @@ -878,7 +878,7 @@ def processCmd(self, cmd, cmdDesc, silentErrors=True, exitOnError=False, exitMsg if not silentErrors: msg=ex.output.decode("utf-8") errorMsg="Exception during %s. %s" % (cmdDesc, msg) - if errorOnExit: + if exitOnError: Utils.cmdError(errorMsg) Utils.errorExit(errorMsg) else: @@ -890,7 +890,7 @@ def processCmd(self, cmd, cmdDesc, silentErrors=True, exitOnError=False, exitMsg else: exitMsg="" if exitOnError and trans is None: - Utils.cmdError("could not %s%s" % (cmdDesc,exitMsg)) + Utils.cmdError("could not %s - %s" % (cmdDesc,exitMsg)) errorExit("Failed to %s" % (cmdDesc)) return trans From 57ad773edaa67dce696748a0dc249b668799b195 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 13 Jul 2018 12:18:38 -0500 Subject: [PATCH 091/294] Changed processCmd to be able to return Json or raw string. --- tests/Node.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index 4415af4b22a..5f18c425f6c 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -7,10 +7,15 @@ import datetime import json +from enum import Enum from core_symbol import CORE_SYMBOL from testUtils import Utils from testUtils import Account +class ReturnType(Enum): + raw = 1 + json = 2 + # pylint: disable=too-many-public-methods class Node(object): @@ -868,12 +873,16 @@ def vote(self, account, producers, waitForTransBlock=False, exitOnError=False): return None return trans - def processCmd(self, cmd, cmdDesc, silentErrors=True, exitOnError=False, exitMsg=None): + def processCmd(self, cmd, cmdDesc, silentErrors=True, exitOnError=False, exitMsg=None, returnType=ReturnType.json): + assert(isinstance(returnType, ReturnType)) cmd="%s %s %s" % (Utils.EosClientPath, self.endpointArgs, cmd) if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) trans=None try: - trans=Utils.runCmdReturnJson(cmd) + if returnType==ReturnType.json: + trans=Utils.runCmdReturnJson(cmd, silentErrors=silentErrors) + elif returnType==ReturnType.raw: + trans=Utils.runCmdReturnStr(cmd) except subprocess.CalledProcessError as ex: if not silentErrors: msg=ex.output.decode("utf-8") From eaeb10cc0fb190aeeadb9ef410b788c5ec039d1c Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 13 Jul 2018 12:21:11 -0500 Subject: [PATCH 092/294] Changed errorExit to always print traceback for errors and changed all scripts to use testUtils and not their own version. --- tests/consensus-validation-malicious-producers.py | 6 +----- tests/distributed-transactions-remote-test.py | 5 +---- tests/distributed-transactions-test.py | 5 +---- tests/nodeos_run_remote_test.py | 5 +---- tests/p2p_network_test.py | 2 +- tests/restart-scenarios-test.py | 7 +------ tests/testUtils.py | 2 ++ tests/validate-dirty-db.py | 5 +---- 8 files changed, 9 insertions(+), 28 deletions(-) diff --git a/tests/consensus-validation-malicious-producers.py b/tests/consensus-validation-malicious-producers.py index e6cd0613b05..5a487a95b6a 100755 --- a/tests/consensus-validation-malicious-producers.py +++ b/tests/consensus-validation-malicious-producers.py @@ -21,6 +21,7 @@ Print=testUtils.Utils.Print +errorExit=Utils.errorExit StagedNodeInfo=namedtuple("StagedNodeInfo", "config logging") @@ -192,11 +193,6 @@ def stageScenario(stagedNodeInfos): def cleanStaging(): os.path.exists(stagingDir) and shutil.rmtree(stagingDir) - -def errorExit(msg="", errorCode=1): - Print("ERROR:", msg) - exit(errorCode) - def error(msg="", errorCode=1): Print("ERROR:", msg) diff --git a/tests/distributed-transactions-remote-test.py b/tests/distributed-transactions-remote-test.py index 29848297a10..f94d609fb46 100755 --- a/tests/distributed-transactions-remote-test.py +++ b/tests/distributed-transactions-remote-test.py @@ -15,10 +15,7 @@ ############################################################### Print=Utils.Print - -def errorExit(msg="", errorCode=1): - Print("ERROR:", msg) - exit(errorCode) +errorExit=Utils.errorExit args = TestHelper.parse_args({"-p","--dump-error-details","-v","--leave-running","--clean-run"}) pnodes=args.p diff --git a/tests/distributed-transactions-test.py b/tests/distributed-transactions-test.py index 7c5104ce364..0ed76f6a956 100755 --- a/tests/distributed-transactions-test.py +++ b/tests/distributed-transactions-test.py @@ -8,10 +8,7 @@ import random Print=Utils.Print - -def errorExit(msg="", errorCode=1): - Print("ERROR:", msg) - exit(errorCode) +errorExit=Utils.errorExit args=TestHelper.parse_args({"-p","-n","-d","-s","--nodes-file","--seed" ,"--dump-error-details","-v","--leave-running","--clean-run"}) diff --git a/tests/nodeos_run_remote_test.py b/tests/nodeos_run_remote_test.py index e72fb928f76..5b3459e780c 100755 --- a/tests/nodeos_run_remote_test.py +++ b/tests/nodeos_run_remote_test.py @@ -13,10 +13,7 @@ ############################################################### Print=Utils.Print - -def errorExit(msg="", errorCode=1): - Print("ERROR:", msg) - exit(errorCode) +errorExit=Utils.errorExit args = TestHelper.parse_args({"--dump-error-details","-v","--leave-running","--only-bios","--clean-run"}) debug=args.v diff --git a/tests/p2p_network_test.py b/tests/p2p_network_test.py index e61dac24e6f..10666d8eb69 100755 --- a/tests/p2p_network_test.py +++ b/tests/p2p_network_test.py @@ -23,7 +23,7 @@ parser = argparse.ArgumentParser(add_help=False) Print=testUtils.Utils.Print -errorExit=testUtils.Utils.errorExit +errorExit=Utils.errorExit # Override default help argument so that only --help (and not -h) can call help parser.add_argument('-?', action='help', default=argparse.SUPPRESS, diff --git a/tests/restart-scenarios-test.py b/tests/restart-scenarios-test.py index e2c22e61204..20eb0208522 100755 --- a/tests/restart-scenarios-test.py +++ b/tests/restart-scenarios-test.py @@ -6,7 +6,6 @@ from TestHelper import TestHelper import random -import traceback ############################################################### # Test for different nodes restart scenarios. @@ -25,11 +24,7 @@ Print=Utils.Print - -def errorExit(msg="", errorCode=1): - Print("ERROR:", msg) - traceback.print_stack(limit=-1) - exit(errorCode) +errorExit=Utils.errorExit args=TestHelper.parse_args({"-p","-d","-s","-c","--kill-sig","--kill-count","--keep-logs","--p2p-plugin" ,"--dump-error-details","-v","--leave-running","--clean-run"}) diff --git a/tests/testUtils.py b/tests/testUtils.py index 0713e70f213..44f85e0d702 100755 --- a/tests/testUtils.py +++ b/tests/testUtils.py @@ -6,6 +6,7 @@ import json import shlex from sys import stdout +import traceback ########################################################################################### class Utils: @@ -81,6 +82,7 @@ def checkOutput(cmd): @staticmethod def errorExit(msg="", raw=False, errorCode=1): Utils.Print("ERROR:" if not raw else "", msg) + traceback.print_stack(limit=-1) exit(errorCode) @staticmethod diff --git a/tests/validate-dirty-db.py b/tests/validate-dirty-db.py index 98a52ee66cd..88a29fa1f62 100755 --- a/tests/validate-dirty-db.py +++ b/tests/validate-dirty-db.py @@ -14,10 +14,7 @@ Print=Utils.Print - -def errorExit(msg="", errorCode=1): - Print("ERROR:", msg) - exit(errorCode) +errorExit=Utils.errorExit args = TestHelper.parse_args({"--keep-logs","--dump-error-details","-v","--leave-running","--clean-run"}) debug=args.v From e514a97dba1a6a108da187109a8839386125ab04 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 13 Jul 2018 12:31:49 -0500 Subject: [PATCH 093/294] Added exitOnError flag to mongodb method. --- tests/Node.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index 5f18c425f6c..daf521cd51a 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -80,7 +80,7 @@ def normalizeJsonObject(extJStr): return tmpStr @staticmethod - def runMongoCmdReturnJson(cmd, subcommand, trace=False): + def runMongoCmdReturnJson(cmd, subcommand, trace=False, exitOnError=False): """Run mongodb subcommand and return response.""" assert(cmd) assert(isinstance(cmd, list)) @@ -88,7 +88,12 @@ def runMongoCmdReturnJson(cmd, subcommand, trace=False): assert(isinstance(subcommand, str)) retId,outs,errs=Node.stdinAndCheckOutput(cmd, subcommand) if retId is not 0: - Utils.Print("ERROR: mongodb call failed. %s" % (errs)) + errorMsg="mongodb call failed. cmd=[ %s ] subcommand=\"%s\" - %s" % (", ".join(cmd), subcommand, errs) + if exitOnError: + Utils.cmdError(errorMsg) + Utils.errorExit(errorMsg) + + Utils.Print("ERROR: %s" % (errMsg)) return None outStr=Node.byteArrToStr(outs) if not outStr: @@ -159,13 +164,18 @@ def getBlock(self, blockNum, silentErrors=False, exitOnError=False): subcommand='db.blocks.findOne( { "block_num": %d } )' % (blockNum) if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: - block=Node.runMongoCmdReturnJson(cmd.split(), subcommand) + block=Node.runMongoCmdReturnJson(cmd.split(), subcommand, exitOnError=exitOnError) if block is not None: return block except subprocess.CalledProcessError as ex: if not silentErrors: msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during get db node get block. %s" % (msg)) + errorMsg="Exception during get db node get block. %s" % (msg) + if exitOnError: + Utils.cmdError(errorMsg) + Utils.errorExit(errorMsg) + else: + Utils.Print("ERROR: %s" % (errorMsg)) return None return None @@ -243,11 +253,9 @@ def getTransactionMdb(self, transId, silentErrors=False, exitOnError=False): subcommand='db.transactions.findOne( { "trx_id": "%s" } )' % (transId) if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: - trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand) - if trans is None and exitOnError: - Utils.cmdError("could not retrieve transaction in mongodb for transaction id=%s" % (transId)) - errorExit("Failed to retrieve transaction in mongodb for transaction id=%s" % (transId)) - return trans + trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand, exitOnError=exitOnError) + if trans is not None: + return trans except subprocess.CalledProcessError as ex: msg=ex.output.decode("utf-8") errorMsg="Exception during get db node get trans in mongodb with transaction id=%s. %s" % (transId,msg) @@ -682,12 +690,17 @@ def getActionsMdb(self, account, pos=-1, offset=-1): subcommand='db.actions.find({$or: [{"data.from":"%s"},{"data.to":"%s"}]}).sort({"_id":%d}).limit(%d)' % (account.name, account.name, pos, abs(offset)) if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: - actions=Node.runMongoCmdReturnJson(cmd.split(), subcommand) + actions=Node.runMongoCmdReturnJson(cmd.split(), subcommand, exitOnError=exitOnError) if actions is not None: return actions except subprocess.CalledProcessError as ex: msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during get db actions. %s" % (msg)) + errorMsg="Exception during get db actions. %s" % (msg) + if exitOnError: + Utils.cmdError(errorMsg) + Utils.errorExit(errorMsg) + else: + Utils.Print("ERROR: %s" % (errorMsg)) return None # Gets accounts mapped to key. Returns array From 8914819b6305aaafeff9ee502550a4837ae9deed Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 13 Jul 2018 12:34:12 -0500 Subject: [PATCH 094/294] Changed getCurrency methods to use exitOnError flag. --- tests/Cluster.py | 1 - tests/Node.py | 30 ++++++++++-------------------- tests/nodeos_run_test.py | 3 +-- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index f1d14505d2e..6c256232d95 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -313,7 +313,6 @@ def doNodesHaveBlockNum(nodes, targetBlockNum): for node in nodes: try: if (not node.killed) and (not node.isBlockPresent(targetBlockNum)): - #if (not node.killed) and (not node.isBlockFinalized(targetBlockNum)): return False except (TypeError) as _: # This can happen if client connects before server is listening diff --git a/tests/Node.py b/tests/Node.py index daf521cd51a..d860cbfd4c7 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -465,7 +465,7 @@ def getTableAccountBalance(self, contract, scope): print("transaction[rows][0][balance] not found. Transaction: %s" % (trans)) raise - def getCurrencyBalance(self, contract, account, symbol=CORE_SYMBOL): + def getCurrencyBalance(self, contract, account, symbol=CORE_SYMBOL, exitOnError=False): """returns raw output from get currency balance e.g. '99999.9950 CUR'""" assert(contract) assert(isinstance(contract, str)) @@ -473,31 +473,21 @@ def getCurrencyBalance(self, contract, account, symbol=CORE_SYMBOL): assert(isinstance(account, str)) assert(symbol) assert(isinstance(symbol, str)) - cmd="%s %s get currency balance %s %s %s" % (Utils.EosClientPath, self.endpointArgs, contract, account, symbol) - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - try: - trans=Utils.runCmdReturnStr(cmd) - return trans - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during get currency stats. %s" % (msg)) - return None + cmdDesc = "get currency balance" + cmd="%s %s %s %s" % (cmdDesc, contract, account, symbol) + msg="contract=%s, account=%s, symbol=%s" % (contract, account, symbol); + return self.processCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg, returnType=ReturnType.raw) - def getCurrencyStats(self, contract, symbol=CORE_SYMBOL): + def getCurrencyStats(self, contract, symbol=CORE_SYMBOL, exitOnError=False): """returns Json output from get currency stats.""" assert(contract) assert(isinstance(contract, str)) assert(symbol) assert(isinstance(symbol, str)) - cmd="%s %s get currency stats %s %s" % (Utils.EosClientPath, self.endpointArgs, contract, symbol) - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - try: - trans=Utils.runCmdReturnJson(cmd) - return trans - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during get currency stats. %s" % (msg)) - return None + cmdDesc = "get currency stats" + cmd="%s %s %s" % (cmdDesc, contract, symbol) + msg="contract=%s, symbol=%s" % (contract, symbol); + return self.processCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) # Verifies account. Returns "get account" json return object def verifyAccount(self, account): diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index fa3d430242e..91c24589ad5 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -408,9 +408,8 @@ errorExit("FAILURE - currency1111 balance check failed. Expected: %s, Recieved %s" % (expected, actual), raw=True) Print("Verify currency1111 contract has proper total supply of CUR (via get currency1111 stats)") - res=node.getCurrencyStats(contract, "CUR") + res=node.getCurrencyStats(contract, "CUR", exitOnError=True) try: - assert(res) assert(res["CUR"]["supply"] == "100000.0000 CUR") except (AssertionError, KeyError) as _: Print("ERROR: Failed get currecy stats assertion. %s" % (res)) From e9afc6d3c1d4ce7054a0d1b0a9d023d83a5a086b Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 13 Jul 2018 12:42:39 -0500 Subject: [PATCH 095/294] Changed getAction methods to use exitOnError flag. --- tests/Node.py | 34 ++++++++++++---------------------- tests/nodeos_run_test.py | 3 +-- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index d860cbfd4c7..b72b207cd7f 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -641,37 +641,27 @@ def getEosBalances(self, accounts): return balances # Gets accounts mapped to key. Returns json object - def getAccountsByKey(self, key): - cmd="%s %s get accounts %s" % (Utils.EosClientPath, self.endpointArgs, key) - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - try: - trans=Utils.runCmdReturnJson(cmd) - return trans - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during accounts by key retrieval. %s" % (msg)) - return None + def getAccountsByKey(self, key, exitOnError=False): + cmdDesc = "get accounts" + cmd="%s %s" % (cmdDesc, key) + msg="key=%s" % (key); + return self.processCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) # Get actions mapped to an account (cleos get actions) - def getActions(self, account, pos=-1, offset=-1): + def getActions(self, account, pos=-1, offset=-1, exitOnError=False): assert(isinstance(account, Account)) assert(isinstance(pos, int)) assert(isinstance(offset, int)) if not self.enableMongo: - cmd="%s %s get actions -j %s %d %d" % (Utils.EosClientPath, self.endpointArgs, account.name, pos, offset) - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - try: - actions=Utils.runCmdReturnJson(cmd) - return actions - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during actions by account retrieval. %s" % (msg)) - return None + cmdDesc = "get actions" + cmd="%s -j %s %d %d" % (cmdDesc, account.name, pos, offset) + msg="account=%s, pos=%d, offset=%d" % (account.name, pos, offset); + return self.processCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) else: - return self.getActionsMdb(account, pos, offset) + return self.getActionsMdb(account, pos, offset, exitOnError=exitOnError) - def getActionsMdb(self, account, pos=-1, offset=-1): + def getActionsMdb(self, account, pos=-1, offset=-1, exitOnError=False): assert(isinstance(account, Account)) assert(isinstance(pos, int)) assert(isinstance(offset, int)) diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index 91c24589ad5..dfa716e638d 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -287,8 +287,7 @@ errorExit("Transfer verification failed. Excepted %s, actual: %s" % (expectedAmount, actualAmount)) Print("Validate last action for account %s" % (testeraAccount.name)) - actions=node.getActions(testeraAccount, -1, -1) - assert(actions) + actions=node.getActions(testeraAccount, -1, -1, exitOnError=True) try: if not enableMongo: assert(actions["actions"][0]["action_trace"]["act"]["name"] == "transfer") From cec8e18ff64f5a8594fb4d69a27ef4e42caca448 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 13 Jul 2018 12:43:38 -0500 Subject: [PATCH 096/294] Fixed error in getInfo. --- tests/Node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Node.py b/tests/Node.py index b72b207cd7f..ba06495af77 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -899,7 +899,7 @@ def processCmd(self, cmd, cmdDesc, silentErrors=True, exitOnError=False, exitMsg def getInfo(self, silentErrors=False, exitOnError=False): cmdDesc = "get info" - return self.processCmd(cmdDesc, cmdDesc, exitOnError=exitOnError) + return self.processCmd(cmdDesc, cmdDesc, silentErrors=silentErrors, exitOnError=exitOnError) def getBlockFromDb(self, idx): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) From e330f747ce4a0f0cff431f38f1373418cfdbe8bc Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 13 Jul 2018 12:50:28 -0500 Subject: [PATCH 097/294] Changed getServants methods to use exitOnError flag. --- tests/Node.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index ba06495af77..5df6f1fe35e 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -691,19 +691,14 @@ def getAccountsArrByKey(self, key): accounts=trans["account_names"] return accounts - def getServants(self, name): - cmd="%s %s get servants %s" % (Utils.EosClientPath, self.endpointArgs, name) - if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) - try: - trans=Utils.runCmdReturnJson(cmd) - return trans - except subprocess.CalledProcessError as ex: - msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during servants retrieval. %s" % (msg)) - return None + def getServants(self, name, exitOnError=False): + cmdDesc = "get servants" + cmd="%s %s" % (cmdDesc, name) + msg="name=%s" % (name); + return self.processCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) def getServantsArr(self, name): - trans=self.getServants(name) + trans=self.getServants(name, exitOnError=True) servants=trans["controlled_accounts"] return servants From 04000730189a0392c680e185e4622fca8f06ca36 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 25 Jul 2018 08:12:33 -0500 Subject: [PATCH 098/294] Added exitOnError flag to Cluster.getNode. --- tests/Cluster.py | 8 +++++++- tests/consensus-validation-malicious-producers.py | 3 --- tests/nodeos_run_test.py | 2 -- tests/nodeos_under_min_avail_ram.py | 8 -------- tests/nodeos_voting_test.py | 8 -------- tests/p2p_network_test.py | 4 +--- tests/validate-dirty-db.py | 2 -- 7 files changed, 8 insertions(+), 27 deletions(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index 6c256232d95..cac925fbae5 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -429,7 +429,13 @@ def populateWallet(self, accountsCount, wallet): self.accounts=accounts return True - def getNode(self, nodeId=0): + def getNode(self, nodeId=0, exitOnError=True): + if exitOnError and nodeId >= len(self.nodes): + Utils.cmdError("cluster never created node %d" % (nodeId)) + errorExit("Failed to retrieve node %d" % (nodeId)) + if exitOnError and self.nodes[nodeId] is None: + Utils.cmdError("cluster has None value for node %d" % (nodeId)) + errorExit("Failed to retrieve node %d" % (nodeId)) return self.nodes[nodeId] def getNodes(self): diff --git a/tests/consensus-validation-malicious-producers.py b/tests/consensus-validation-malicious-producers.py index 5a487a95b6a..0e86212d029 100755 --- a/tests/consensus-validation-malicious-producers.py +++ b/tests/consensus-validation-malicious-producers.py @@ -277,9 +277,6 @@ def myTest(transWillEnterBlock): node=cluster.getNode(0) node2=cluster.getNode(1) - if node is None or node2 is None: - error("Cluster in bad state, received None node") - return False defproduceraAccount=testUtils.Cluster.defproduceraAccount diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index dfa716e638d..530fcf97e33 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -202,8 +202,6 @@ errorExit("FAILURE - wallet keys did not include %s" % (noMatch), raw=True) node=cluster.getNode(0) - if node is None: - errorExit("Cluster in bad state, received None node") Print("Validating accounts before user accounts creation") cluster.validateAccounts(None) diff --git a/tests/nodeos_under_min_avail_ram.py b/tests/nodeos_under_min_avail_ram.py index efe64d3bab6..44fd85d66e9 100755 --- a/tests/nodeos_under_min_avail_ram.py +++ b/tests/nodeos_under_min_avail_ram.py @@ -113,17 +113,9 @@ def setName(self, num): nodes=[] nodes.append(cluster.getNode(0)) - if nodes[0] is None: - errorExit("Cluster in bad state, received None node") nodes.append(cluster.getNode(1)) - if nodes[1] is None: - errorExit("Cluster in bad state, received None node") nodes.append(cluster.getNode(2)) - if nodes[2] is None: - errorExit("Cluster in bad state, received None node") nodes.append(cluster.getNode(3)) - if nodes[3] is None: - errorExit("Cluster in bad state, received None node") for account in accounts: diff --git a/tests/nodeos_voting_test.py b/tests/nodeos_voting_test.py index e8c7612ca7f..2012d24b92e 100755 --- a/tests/nodeos_voting_test.py +++ b/tests/nodeos_voting_test.py @@ -250,17 +250,9 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): trans=node.regproducer(cluster.defProducerAccounts[prod], "http::/mysite.com", 0, waitForTransBlock=False, exitOnError=True) node0=cluster.getNode(0) - if node0 is None: - errorExit("Cluster in bad state, received None node") node1=cluster.getNode(1) - if node1 is None: - errorExit("Cluster in bad state, received None node") node2=cluster.getNode(2) - if node2 is None: - errorExit("Cluster in bad state, received None node") node3=cluster.getNode(3) - if node3 is None: - errorExit("Cluster in bad state, received None node") node=node0 # create accounts via eosio as otherwise a bid is needed diff --git a/tests/p2p_network_test.py b/tests/p2p_network_test.py index 10666d8eb69..34a6e893000 100755 --- a/tests/p2p_network_test.py +++ b/tests/p2p_network_test.py @@ -137,8 +137,6 @@ errorExit("Failed to import key for account %s" % (defproduceraAccount.name)) node0=cluster.getNode(0) -if node0 is None: - errorExit("cluster in bad state, received None node") # eosio should have the same key as defproducera eosio = copy.copy(defproduceraAccount) @@ -146,7 +144,7 @@ Print("Info of each node:") for i in range(len(hosts)): - node = cluster.getNode(0) + node = node0 cmd="%s %s get info" % (testUtils.Utils.EosClientPath, node.endpointArgs) trans = node.runCmdReturnJson(cmd) Print("host %s: %s" % (hosts[i], trans)) diff --git a/tests/validate-dirty-db.py b/tests/validate-dirty-db.py index 88a29fa1f62..8101369bb7b 100755 --- a/tests/validate-dirty-db.py +++ b/tests/validate-dirty-db.py @@ -78,8 +78,6 @@ def runNodeosAndGetOutput(myTimeout=3): errorExit("Failed to stand up eos cluster.") node=cluster.getNode(0) - if node is None: - errorExit("Cluster in bad state, received None node") Print("Kill cluster nodes.") cluster.killall(allInstances=killAll) From 3c373de63c074726d66d46f5d228a6453723ffa6 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 25 Jul 2018 08:15:16 -0500 Subject: [PATCH 099/294] Added exitOnError flag to WalletMgr.create. --- tests/Cluster.py | 3 --- tests/WalletMgr.py | 6 +++++- tests/consensus-validation-malicious-producers.py | 3 --- tests/nodeos_run_test.py | 6 ------ tests/nodeos_under_min_avail_ram.py | 3 --- tests/nodeos_voting_test.py | 3 --- tests/p2p_network_test.py | 6 ------ tests/restart-scenarios-test.py | 2 -- 8 files changed, 5 insertions(+), 27 deletions(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index cac925fbae5..85f0dba9359 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -711,9 +711,6 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio try: ignWallet=walletMgr.create("ignition") - if ignWallet is None: - Utils.Print("ERROR: Failed to create ignition wallet.") - return False eosioName="eosio" eosioKeys=producerKeys[eosioName] diff --git a/tests/WalletMgr.py b/tests/WalletMgr.py index 4b1c49bfcfd..934e0258638 100644 --- a/tests/WalletMgr.py +++ b/tests/WalletMgr.py @@ -47,7 +47,7 @@ def launch(self): time.sleep(1) return True - def create(self, name, accounts=None): + def create(self, name, accounts=None, exitOnError=True): wallet=self.wallets.get(name) if wallet is not None: if Utils.Debug: Utils.Print("Wallet \"%s\" already exists. Returning same." % name) @@ -59,6 +59,10 @@ def create(self, name, accounts=None): #Utils.Print("create: %s" % (retStr)) m=p.search(retStr) if m is None: + if exitOnError: + Utils.cmdError("could not create wallet %s" % (name)) + errorExit("Failed to create wallet %s" % (name)) + Utils.Print("ERROR: wallet password parser failure") return None p=m.group(1) diff --git a/tests/consensus-validation-malicious-producers.py b/tests/consensus-validation-malicious-producers.py index 0e86212d029..3aa385088fd 100755 --- a/tests/consensus-validation-malicious-producers.py +++ b/tests/consensus-validation-malicious-producers.py @@ -265,9 +265,6 @@ def myTest(transWillEnterBlock): testWalletName="test" Print("Creating wallet \"%s\"." % (testWalletName)) testWallet=walletMgr.create(testWalletName) - if testWallet is None: - error("Failed to create wallet %s." % (testWalletName)) - return False for account in accounts: Print("Importing keys for account %s into wallet %s." % (account.name, testWallet.name)) diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index 530fcf97e33..d894424ef30 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -114,9 +114,6 @@ testWalletName="test" Print("Creating wallet \"%s\"." % (testWalletName)) testWallet=walletMgr.create(testWalletName, [cluster.eosioAccount,cluster.defproduceraAccount,cluster.defproducerbAccount]) - if testWallet is None: - cmdError("eos wallet create") - errorExit("Failed to create wallet %s." % (testWalletName)) Print("Wallet \"%s\" password=%s." % (testWalletName, testWallet.password.encode("utf-8"))) @@ -129,9 +126,6 @@ defproduceraWalletName="defproducera" Print("Creating wallet \"%s\"." % (defproduceraWalletName)) defproduceraWallet=walletMgr.create(defproduceraWalletName) - if defproduceraWallet is None: - cmdError("eos wallet create") - errorExit("Failed to create wallet %s." % (defproduceraWalletName)) Print("Wallet \"%s\" password=%s." % (defproduceraWalletName, defproduceraWallet.password.encode("utf-8"))) diff --git a/tests/nodeos_under_min_avail_ram.py b/tests/nodeos_under_min_avail_ram.py index 44fd85d66e9..99d7c6d30a6 100755 --- a/tests/nodeos_under_min_avail_ram.py +++ b/tests/nodeos_under_min_avail_ram.py @@ -102,9 +102,6 @@ def setName(self, num): errorExit("Failed to stand up eos walletd.") testWallet=walletMgr.create(testWalletName, [cluster.eosioAccount]) - if testWallet is None: - Utils.cmdError("eos wallet create") - errorExit("Failed to create wallet %s." % (testWalletName)) for _, account in cluster.defProducerAccounts.items(): walletMgr.importKey(account, testWallet, ignoreDupKeyWarning=True) diff --git a/tests/nodeos_voting_test.py b/tests/nodeos_voting_test.py index 2012d24b92e..398d758abc6 100755 --- a/tests/nodeos_voting_test.py +++ b/tests/nodeos_voting_test.py @@ -234,9 +234,6 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): errorExit("Failed to stand up eos walletd.") testWallet=walletMgr.create(testWalletName, [cluster.eosioAccount,accounts[0],accounts[1],accounts[2],accounts[3],accounts[4]]) - if testWallet is None: - Utils.cmdError("eos wallet create") - errorExit("Failed to create wallet %s." % (testWalletName)) for _, account in cluster.defProducerAccounts.items(): walletMgr.importKey(account, testWallet, ignoreDupKeyWarning=True) diff --git a/tests/p2p_network_test.py b/tests/p2p_network_test.py index 34a6e893000..34de2162547 100755 --- a/tests/p2p_network_test.py +++ b/tests/p2p_network_test.py @@ -111,9 +111,6 @@ testWalletName="test" Print("Creating wallet \"%s\"." % (testWalletName)) testWallet=walletMgr.create(testWalletName) -if testWallet is None: - cmdError("eos wallet create") - errorExit("Failed to create wallet %s." % (testWalletName)) for account in accounts: Print("Importing keys for account %s into wallet %s." % (account.name, testWallet.name)) @@ -124,9 +121,6 @@ defproduceraWalletName="defproducera" Print("Creating wallet \"%s\"." % (defproduceraWalletName)) defproduceraWallet=walletMgr.create(defproduceraWalletName) -if defproduceraWallet is None: - cmdError("eos wallet create") - errorExit("Failed to create wallet %s." % (defproduceraWalletName)) defproduceraAccount=testUtils.Cluster.defproduceraAccount # defproducerbAccount=testUtils.Cluster.defproducerbAccount diff --git a/tests/restart-scenarios-test.py b/tests/restart-scenarios-test.py index 20eb0208522..d3329c8b65f 100755 --- a/tests/restart-scenarios-test.py +++ b/tests/restart-scenarios-test.py @@ -83,8 +83,6 @@ walletName="MyWallet" Print("Creating wallet %s if one doesn't already exist." % walletName) wallet=walletMgr.create(walletName, [cluster.eosioAccount,cluster.defproduceraAccount,cluster.defproducerbAccount]) - if wallet is None: - errorExit("Failed to create wallet %s" % (walletName)) Print ("Populate wallet with %d accounts." % (accountsCount)) if not cluster.populateWallet(accountsCount, wallet): From 91ba62b4763d09273d1921ab57f54f220d73774c Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 25 Jul 2018 08:17:17 -0500 Subject: [PATCH 100/294] Changed new test to use exitOnError flags. --- tests/nodeos_under_min_avail_ram.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/tests/nodeos_under_min_avail_ram.py b/tests/nodeos_under_min_avail_ram.py index 99d7c6d30a6..e50abf7e6af 100755 --- a/tests/nodeos_under_min_avail_ram.py +++ b/tests/nodeos_under_min_avail_ram.py @@ -121,37 +121,25 @@ def setName(self, num): # create accounts via eosio as otherwise a bid is needed for account in accounts: Print("Create new account %s via %s" % (account.name, cluster.eosioAccount.name)) - trans=nodes[0].createInitializeAccount(account, cluster.eosioAccount, stakedDeposit=500000, waitForTransBlock=False, stakeNet=50000, stakeCPU=50000, buyRAM=50000) - if trans is None: - Utils.cmdError("%s create account" % (account.name)) - errorExit("Failed to create account %s" % (account.name)) + trans=nodes[0].createInitializeAccount(account, cluster.eosioAccount, stakedDeposit=500000, waitForTransBlock=False, stakeNet=50000, stakeCPU=50000, buyRAM=50000, exitOnError=True) transferAmount="70000000.0000 {0}".format(CORE_SYMBOL) Print("Transfer funds %s from account %s to %s" % (transferAmount, cluster.eosioAccount.name, account.name)) if nodes[0].transferFunds(cluster.eosioAccount, account, transferAmount, "test transfer") is None: errorExit("Failed to transfer funds %d from account %s to %s" % ( transferAmount, cluster.eosioAccount.name, account.name)) - trans=nodes[0].delegatebw(account, 1000000.0000, 68000000.0000) - if trans is None: - Utils.cmdError("delegate bandwidth for %s" % (account.name)) - errorExit("Failed to delegate bandwidth for %s" % (account.name)) + trans=nodes[0].delegatebw(account, 1000000.0000, 68000000.0000, exitOnError=True) contractAccount=cluster.createAccountKeys(1)[0] contractAccount.name="contracttest" walletMgr.importKey(contractAccount, testWallet) Print("Create new account %s via %s" % (contractAccount.name, cluster.eosioAccount.name)) - trans=nodes[0].createInitializeAccount(contractAccount, cluster.eosioAccount, stakedDeposit=500000, waitForTransBlock=False, stakeNet=50000, stakeCPU=50000, buyRAM=50000) - if trans is None: - Utils.cmdError("%s create account" % (contractAccount.name)) - errorExit("Failed to create account %s" % (contractAccount.name)) + trans=nodes[0].createInitializeAccount(contractAccount, cluster.eosioAccount, stakedDeposit=500000, waitForTransBlock=False, stakeNet=50000, stakeCPU=50000, buyRAM=50000, exitOnError=True) transferAmount="90000000.0000 {0}".format(CORE_SYMBOL) Print("Transfer funds %s from account %s to %s" % (transferAmount, cluster.eosioAccount.name, contractAccount.name)) if nodes[0].transferFunds(cluster.eosioAccount, contractAccount, transferAmount, "test transfer") is None: errorExit("Failed to transfer funds %d from account %s to %s" % ( transferAmount, cluster.eosioAccount.name, contractAccount.name)) - trans=nodes[0].delegatebw(contractAccount, 1000000.0000, 88000000.0000) - if trans is None: - Utils.cmdError("delegate bandwidth for %s" % (contractAccount.name)) - errorExit("Failed to delegate bandwidth for %s" % (contractAccount.name)) + trans=nodes[0].delegatebw(contractAccount, 1000000.0000, 88000000.0000, exitOnError=True) contractDir="contracts/integration_test" wastFile="contracts/integration_test/integration_test.wast" From 14827605c23b137655747202f2d85d95a8de3956 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 25 Jul 2018 08:18:15 -0500 Subject: [PATCH 101/294] Added exitOnError flag to Node.transferFunds. --- tests/Cluster.py | 1 - tests/Node.py | 12 ++++++++++-- tests/nodeos_run_test.py | 14 ++------------ tests/nodeos_under_min_avail_ram.py | 8 ++------ tests/nodeos_voting_test.py | 4 +--- 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index 85f0dba9359..97051d35eac 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -460,7 +460,6 @@ def spreadFunds(self, source, accounts, amount=1): Utils.Print("Transfer %s units from account %s to %s on eos server port %d" % ( transferAmountStr, fromm.name, to.name, node.port)) trans=node.transferFunds(fromm, to, transferAmountStr) - assert(trans) transId=Node.getTransId(trans) if transId is None: return False diff --git a/tests/Node.py b/tests/Node.py index 5df6f1fe35e..5ae4e3bd2fb 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -546,7 +546,7 @@ def waitForIrreversibleBlock(self, blockNum, timeout=None): return ret # Trasfer funds. Returns "transfer" json return object - def transferFunds(self, source, destination, amountStr, memo="memo", force=False, waitForTransBlock=False): + def transferFunds(self, source, destination, amountStr, memo="memo", force=False, waitForTransBlock=False, exitOnError=True): assert isinstance(amountStr, str) assert(source) assert(isinstance(source, Account)) @@ -568,11 +568,19 @@ def transferFunds(self, source, destination, amountStr, memo="memo", force=False except subprocess.CalledProcessError as ex: msg=ex.output.decode("utf-8") Utils.Print("ERROR: Exception during funds transfer. %s" % (msg)) + if exitOnError: + Utils.cmdError("could not transfer \"%s\" from %s to %s" % (amountStr, source, destination)) + errorExit("Failed to transfer \"%s\" from %s to %s" % (amountStr, source, destination)) return None - assert(trans) + if trans is None: + Utils.cmdError("could not transfer \"%s\" from %s to %s" % (amountStr, source, destination)) + errorExit("Failed to transfer \"%s\" from %s to %s" % (amountStr, source, destination)) transId=Node.getTransId(trans) if waitForTransBlock and not self.waitForTransInBlock(transId): + if exitOnError: + Utils.cmdError("could not find transfer with transId=%s in block" % (transId)) + errorExit("Failed to find transfer with transId=%s in block" % (transId)) return None return trans diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index d894424ef30..e8b56525fd6 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -220,10 +220,7 @@ transferAmount="97.5321 {0}".format(CORE_SYMBOL) Print("Transfer funds %s from account %s to %s" % (transferAmount, defproduceraAccount.name, testeraAccount.name)) - if node.transferFunds(defproduceraAccount, testeraAccount, transferAmount, "test transfer") is None: - cmdError("%s transfer" % (ClientName)) - errorExit("Failed to transfer funds %d from account %s to %s" % ( - transferAmount, defproduceraAccount.name, testeraAccount.name)) + node.transferFunds(defproduceraAccount, testeraAccount, transferAmount, "test transfer") expectedAmount=transferAmount Print("Verify transfer, Expected: %s" % (expectedAmount)) @@ -235,10 +232,7 @@ transferAmount="0.0100 {0}".format(CORE_SYMBOL) Print("Force transfer funds %s from account %s to %s" % ( transferAmount, defproduceraAccount.name, testeraAccount.name)) - if node.transferFunds(defproduceraAccount, testeraAccount, transferAmount, "test transfer", force=True) is None: - cmdError("%s transfer" % (ClientName)) - errorExit("Failed to force transfer funds %d from account %s to %s" % ( - transferAmount, defproduceraAccount.name, testeraAccount.name)) + node.transferFunds(defproduceraAccount, testeraAccount, transferAmount, "test transfer", force=True) expectedAmount="97.5421 {0}".format(CORE_SYMBOL) Print("Verify transfer, Expected: %s" % (expectedAmount)) @@ -265,10 +259,6 @@ Print("Transfer funds %s from account %s to %s" % ( transferAmount, testeraAccount.name, currencyAccount.name)) trans=node.transferFunds(testeraAccount, currencyAccount, transferAmount, "test transfer a->b") - if trans is None: - cmdError("%s transfer" % (ClientName)) - errorExit("Failed to transfer funds %d from account %s to %s" % ( - transferAmount, testeraAccount.name, currencyAccount.name)) transId=Node.getTransId(trans) expectedAmount="98.0311 {0}".format(CORE_SYMBOL) # 5000 initial deposit diff --git a/tests/nodeos_under_min_avail_ram.py b/tests/nodeos_under_min_avail_ram.py index e50abf7e6af..55b8c7bcdc7 100755 --- a/tests/nodeos_under_min_avail_ram.py +++ b/tests/nodeos_under_min_avail_ram.py @@ -124,9 +124,7 @@ def setName(self, num): trans=nodes[0].createInitializeAccount(account, cluster.eosioAccount, stakedDeposit=500000, waitForTransBlock=False, stakeNet=50000, stakeCPU=50000, buyRAM=50000, exitOnError=True) transferAmount="70000000.0000 {0}".format(CORE_SYMBOL) Print("Transfer funds %s from account %s to %s" % (transferAmount, cluster.eosioAccount.name, account.name)) - if nodes[0].transferFunds(cluster.eosioAccount, account, transferAmount, "test transfer") is None: - errorExit("Failed to transfer funds %d from account %s to %s" % ( - transferAmount, cluster.eosioAccount.name, account.name)) + nodes[0].transferFunds(cluster.eosioAccount, account, transferAmount, "test transfer") trans=nodes[0].delegatebw(account, 1000000.0000, 68000000.0000, exitOnError=True) contractAccount=cluster.createAccountKeys(1)[0] @@ -136,9 +134,7 @@ def setName(self, num): trans=nodes[0].createInitializeAccount(contractAccount, cluster.eosioAccount, stakedDeposit=500000, waitForTransBlock=False, stakeNet=50000, stakeCPU=50000, buyRAM=50000, exitOnError=True) transferAmount="90000000.0000 {0}".format(CORE_SYMBOL) Print("Transfer funds %s from account %s to %s" % (transferAmount, cluster.eosioAccount.name, contractAccount.name)) - if nodes[0].transferFunds(cluster.eosioAccount, contractAccount, transferAmount, "test transfer") is None: - errorExit("Failed to transfer funds %d from account %s to %s" % ( - transferAmount, cluster.eosioAccount.name, contractAccount.name)) + nodes[0].transferFunds(cluster.eosioAccount, contractAccount, transferAmount, "test transfer") trans=nodes[0].delegatebw(contractAccount, 1000000.0000, 88000000.0000, exitOnError=True) contractDir="contracts/integration_test" diff --git a/tests/nodeos_voting_test.py b/tests/nodeos_voting_test.py index 398d758abc6..160642c30d1 100755 --- a/tests/nodeos_voting_test.py +++ b/tests/nodeos_voting_test.py @@ -258,9 +258,7 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): trans=node.createInitializeAccount(account, cluster.eosioAccount, stakedDeposit=0, waitForTransBlock=False, stakeNet=1000, stakeCPU=1000, buyRAM=1000, exitOnError=True) transferAmount="100000000.0000 {0}".format(CORE_SYMBOL) Print("Transfer funds %s from account %s to %s" % (transferAmount, cluster.eosioAccount.name, account.name)) - if node.transferFunds(cluster.eosioAccount, account, transferAmount, "test transfer") is None: - errorExit("Failed to transfer funds %d from account %s to %s" % ( - transferAmount, cluster.eosioAccount.name, account.name)) + node.transferFunds(cluster.eosioAccount, account, transferAmount, "test transfer") trans=node.delegatebw(account, 20000000.0000, 20000000.0000, exitOnError=True) # containers for tracking producers From fb5aafc8393e2d8a5e29f57fa3bf429530a616a2 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 25 Jul 2018 11:55:27 -0500 Subject: [PATCH 102/294] Replaced Enum since not supported on centos. GH #4864 --- tests/Node.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index 5ae4e3bd2fb..ca6d9c496f6 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -7,14 +7,20 @@ import datetime import json -from enum import Enum from core_symbol import CORE_SYMBOL from testUtils import Utils from testUtils import Account -class ReturnType(Enum): - raw = 1 - json = 2 +class ReturnType: + + def __init__(self, type): + self.type=type + + def __str__(self): + return self.type + +setattr(ReturnType, "raw", ReturnType("raw")) +setattr(ReturnType, "json", ReturnType("json")) # pylint: disable=too-many-public-methods class Node(object): From 48caac897a4b483224eae0057b8d9c5cb9089440 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 25 Jul 2018 11:56:17 -0500 Subject: [PATCH 103/294] Fixed use of exitOnError flag. GH #4864 --- tests/Node.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index ca6d9c496f6..ebc926a00b9 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -442,16 +442,20 @@ def getEosAccount(self, name, exitOnError=False): else: return self.getEosAccountFromDb(name, exitOnError=exitOnError) - def getEosAccountFromDb(self, name): + def getEosAccountFromDb(self, name, exitOnError=False): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) subcommand='db.accounts.findOne({"name" : "%s"})' % (name) if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: - trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand) + trans=Node.runMongoCmdReturnJson(cmd.split(), subcommand, exitOnError=exitOnError) return trans except subprocess.CalledProcessError as ex: msg=ex.output.decode("utf-8") - Utils.Print("ERROR: Exception during get account from db. %s" % (msg)) + if exitOnError: + Utils.cmdError("Exception during get account from db for %s. %s" % (name, msg)) + errorExit("Failed during get account from db for %s. %s" % (name, msg)) + + Utils.Print("ERROR: Exception during get account from db for %s. %s" % (name, msg)) return None def getTable(self, contract, scope, table, exitOnError=False): From 82f45296a9fcff8310c3832c2cd53cc25212cceb Mon Sep 17 00:00:00 2001 From: Paul Calabrese Date: Wed, 25 Jul 2018 15:33:53 -0500 Subject: [PATCH 104/294] Add test support for bnet --- tests/CMakeLists.txt | 8 +++++++- tests/nodeos_run_test.py | 5 +++-- tests/nodeos_voting_test.py | 5 +++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 565a6679ee3..86c6e97cb36 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -46,6 +46,7 @@ add_test(NAME plugin_test COMMAND plugin_test --report_level=detailed --color_ou add_test(NAME nodeos_sanity_test COMMAND tests/nodeos_run_test.py -v --sanity-test --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) add_test(NAME nodeos_run_test COMMAND tests/nodeos_run_test.py -v --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME bnet_nodeos_run_test COMMAND tests/nodeos_run_test.py -v --clean-run --p2p-plugin bnet --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) add_test(NAME p2p_dawn515_test COMMAND tests/p2p_tests/dawn_515/test.sh WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) if(BUILD_MONGO_DB_PLUGIN) @@ -62,6 +63,8 @@ add_test(NAME validate_dirty_db_test COMMAND tests/validate-dirty-db.py -v --cle # Long running tests add_test(NAME nodeos_sanity_lr_test COMMAND tests/nodeos_run_test.py -v --sanity-test --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_sanity_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME bnet_nodeos_sanity_lr_test COMMAND tests/nodeos_run_test.py -v --sanity-test --p2p-plugin bnet --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST bnet_nodeos_sanity_lr_test PROPERTY LABELS long_running_tests) #add_test(NAME distributed_transactions_lr_test COMMAND tests/distributed-transactions-test.py -d 2 -p 21 -n 21 -v --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) #set_property(TEST distributed_transactions_lr_test PROPERTY LABELS long_running_tests) @@ -69,6 +72,9 @@ set_property(TEST nodeos_sanity_lr_test PROPERTY LABELS long_running_tests) add_test(NAME nodeos_voting_lr_test COMMAND tests/nodeos_voting_test.py -v --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_voting_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME bnet_nodeos_voting_lr_test COMMAND tests/nodeos_voting_test.py -v --p2p-plugin bnet --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST bnet_nodeos_voting_lr_test PROPERTY LABELS long_running_tests) + add_test(NAME nodeos_under_min_avail_ram_lr_test COMMAND tests/nodeos_under_min_avail_ram.py -v --clean-run --dump-error-detail WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_under_min_avail_ram_lr_test PROPERTY LABELS long_running_tests) @@ -90,7 +96,7 @@ if(ENABLE_COVERAGE_TESTING) endif() # NOT GENHTML_PATH # no spaces allowed within tests list - set(ctest_tests 'plugin_test|p2p_dawn515_test|nodeos_run_test|distributed-transactions-test|restart-scenarios-test_resync') + set(ctest_tests 'plugin_test|p2p_dawn515_test|nodeos_run_test|bnet_nodeos_run_test|distributed-transactions-test|restart-scenarios-test_resync') set(ctest_exclude_tests 'nodeos_run_remote_test|nodeos_run_test-mongodb|distributed-transactions-remote-test|restart-scenarios-test_replay') # Setup target diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index e8b56525fd6..aed7c38e379 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -22,7 +22,7 @@ args = TestHelper.parse_args({"--host","--port","--prod-count","--defproducera_prvt_key","--defproducerb_prvt_key","--mongodb" ,"--dump-error-details","--dont-launch","--keep-logs","-v","--leave-running","--only-bios","--clean-run" - ,"--sanity-test"}) + ,"--sanity-test","--p2p-plugin"}) server=args.host port=args.port debug=args.v @@ -37,6 +37,7 @@ onlyBios=args.only_bios killAll=args.clean_run sanityTest=args.sanity_test +p2pPlugin=args.p2p_plugin Utils.Debug=debug localTest=True if server == TestHelper.LOCAL_HOST else False @@ -67,7 +68,7 @@ cluster.killall(allInstances=killAll) cluster.cleanup() Print("Stand up cluster") - if cluster.launch(prodCount=prodCount, onlyBios=onlyBios, dontKill=dontKill, dontBootstrap=dontBootstrap) is False: + if cluster.launch(prodCount=prodCount, onlyBios=onlyBios, dontKill=dontKill, dontBootstrap=dontBootstrap, p2pPlugin=p2pPlugin) is False: cmdError("launcher") errorExit("Failed to stand up eos cluster.") else: diff --git a/tests/nodeos_voting_test.py b/tests/nodeos_voting_test.py index 160642c30d1..df9bbb689f4 100755 --- a/tests/nodeos_voting_test.py +++ b/tests/nodeos_voting_test.py @@ -184,7 +184,7 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): from core_symbol import CORE_SYMBOL -args = TestHelper.parse_args({"--prod-count","--dump-error-details","--keep-logs","-v","--leave-running","--clean-run"}) +args = TestHelper.parse_args({"--prod-count","--dump-error-details","--keep-logs","-v","--leave-running","--clean-run","--p2p-plugin"}) Utils.Debug=args.v totalNodes=4 cluster=Cluster(walletd=True) @@ -193,6 +193,7 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): dontKill=args.leave_running prodCount=args.prod_count killAll=args.clean_run +p2pPlugin=args.p2p_plugin walletMgr=WalletMgr(True) testSuccessful=False @@ -208,7 +209,7 @@ def verifyProductionRounds(trans, node, prodsActive, rounds): cluster.killall(allInstances=killAll) cluster.cleanup() Print("Stand up cluster") - if cluster.launch(prodCount=prodCount, onlyBios=False, dontKill=dontKill, pnodes=totalNodes, totalNodes=totalNodes, totalProducers=totalNodes*21) is False: + if cluster.launch(prodCount=prodCount, onlyBios=False, dontKill=dontKill, pnodes=totalNodes, totalNodes=totalNodes, totalProducers=totalNodes*21, p2pPlugin=p2pPlugin) is False: Utils.cmdError("launcher") errorExit("Failed to stand up eos cluster.") From b9e630babae8d8c36f7b7c533bbe8b4968d62b5a Mon Sep 17 00:00:00 2001 From: Baegjae Sung Date: Thu, 26 Jul 2018 10:23:48 +0900 Subject: [PATCH 105/294] Enable multi-threading when building boost libraries --- scripts/eosio_build_amazon.sh | 4 ++-- scripts/eosio_build_centos.sh | 2 +- scripts/eosio_build_fedora.sh | 4 ++-- scripts/eosio_build_ubuntu.sh | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/eosio_build_amazon.sh b/scripts/eosio_build_amazon.sh index e33e2714cb2..04b89cd9a6d 100644 --- a/scripts/eosio_build_amazon.sh +++ b/scripts/eosio_build_amazon.sh @@ -277,7 +277,7 @@ printf "\\n\\tExiting now.\\n" exit 1 fi - if ! "${TEMP_DIR}"/boost_1_67_0/b2 install + if ! "${TEMP_DIR}"/boost_1_67_0/b2 -j"${CPU_CORE}" install then printf "\\n\\tInstallation of boost libraries failed. 1\\n" printf "\\n\\tExiting now.\\n" @@ -679,4 +679,4 @@ fi printf '\texport PATH=${HOME}/opt/mongodb/bin:$PATH \n' printf "\\tcd %s; make test\\n\\n" "${BUILD_DIR}" return 0 - } \ No newline at end of file + } diff --git a/scripts/eosio_build_centos.sh b/scripts/eosio_build_centos.sh index 8e9003e14cc..68d3226d662 100644 --- a/scripts/eosio_build_centos.sh +++ b/scripts/eosio_build_centos.sh @@ -363,7 +363,7 @@ printf "\\n\\tExiting now.\\n\\n" exit 1; fi - if ! "${TEMP_DIR}"/boost_1_67_0/b2 -j2 install + if ! "${TEMP_DIR}"/boost_1_67_0/b2 -j"${CPU_CORE}" install then printf "\\n\\tInstallation of boost libraries failed with the above error. 1\\n" printf "\\n\\tExiting now.\\n\\n" diff --git a/scripts/eosio_build_fedora.sh b/scripts/eosio_build_fedora.sh index 36a704a9217..899aca3102e 100644 --- a/scripts/eosio_build_fedora.sh +++ b/scripts/eosio_build_fedora.sh @@ -220,7 +220,7 @@ printf "\\tExiting now.\\n\\n" exit 1; fi - if ! "${TEMP_DIR}"/boost_1_67_0/b2 install + if ! "${TEMP_DIR}"/boost_1_67_0/b2 -j"${CPU_CORE}" install then printf "\\n\\tInstallation of boost libraries failed. 1\\n" printf "\\tExiting now.\\n\\n" @@ -542,4 +542,4 @@ printf "\\n\\t%s -f %s &\\n" "$( command -v mongod )" "${MONGOD_CONF}" printf "\\tcd %s; make test\\n\\n" "${BUILD_DIR}" return 0; - } \ No newline at end of file + } diff --git a/scripts/eosio_build_ubuntu.sh b/scripts/eosio_build_ubuntu.sh index a1151c51c7c..115dbc194d2 100644 --- a/scripts/eosio_build_ubuntu.sh +++ b/scripts/eosio_build_ubuntu.sh @@ -178,7 +178,7 @@ printf "\\n\\tExiting now.\\n\\n" exit 1 fi - if ! ./b2 install + if ! ./b2 -j"${CPU_CORE}" install then printf "\\n\\tInstallation of boost libraries failed. 1\\n" printf "\\n\\tExiting now.\\n\\n" From bffb66e1cf52233d8ebe3bd7559f8db9bcb2b360 Mon Sep 17 00:00:00 2001 From: Andrianto Lie Date: Wed, 25 Jul 2018 10:22:30 +0800 Subject: [PATCH 106/294] Add Get/ Set Whitelist Blacklist RPC API --- libraries/chain/controller.cpp | 43 +++++++++++++++++++ .../chain/include/eosio/chain/controller.hpp | 14 ++++++ plugins/chain_api_plugin/chain_api_plugin.cpp | 2 + plugins/chain_plugin/chain_plugin.cpp | 21 +++++++++ .../eosio/chain_plugin/chain_plugin.hpp | 28 ++++++++++++ 5 files changed, 108 insertions(+) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 408f694f71f..37bc633fc1d 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -1309,6 +1310,48 @@ transaction_trace_ptr controller::push_scheduled_transaction( const transaction_ return my->push_scheduled_transaction( trxid, deadline, billed_cpu_time_us ); } +const flat_set& controller::get_actor_whitelist() const { + return my->conf.actor_whitelist; +} +const flat_set& controller::get_actor_blacklist() const { + return my->conf.actor_blacklist; +} +const flat_set& controller::get_contract_whitelist() const { + return my->conf.contract_whitelist; +} +const flat_set& controller::get_contract_blacklist() const { + return my->conf.contract_blacklist; +} +const flat_set< pair >& controller::get_action_blacklist() const { + return my->conf.action_blacklist; +} +const flat_set& controller::get_key_blacklist() const { + return my->conf.key_blacklist; +} + +void controller::set_actor_whitelist( const flat_set& new_actor_whitelist ) { + my->conf.actor_whitelist = new_actor_whitelist; +} +void controller::set_actor_blacklist( const flat_set& new_actor_blacklist ) { + my->conf.actor_blacklist = new_actor_blacklist; +} +void controller::set_contract_whitelist( const flat_set& new_contract_whitelist ) { + my->conf.contract_whitelist = new_contract_whitelist; +} +void controller::set_contract_blacklist( const flat_set& new_contract_blacklist ) { + my->conf.contract_blacklist = new_contract_blacklist; +} +void controller::set_action_blacklist( const flat_set< pair >& new_action_blacklist ) { + for (auto& act: new_action_blacklist) { + EOS_ASSERT(act.first != account_name(), name_type_exception, "Action blacklist - contract name should not be empty"); + EOS_ASSERT(act.second != action_name(), action_type_exception, "Action blacklist - action name should not be empty"); + } + my->conf.action_blacklist = new_action_blacklist; +} +void controller::set_key_blacklist( const flat_set& new_key_blacklist ) { + my->conf.key_blacklist = new_key_blacklist; +} + uint32_t controller::head_block_num()const { return my->head->block_num; } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index c0fac7056e8..5c6a9c6f644 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -147,6 +147,20 @@ namespace eosio { namespace chain { const authorization_manager& get_authorization_manager()const; authorization_manager& get_mutable_authorization_manager(); + const flat_set& get_actor_whitelist() const; + const flat_set& get_actor_blacklist() const; + const flat_set& get_contract_whitelist() const; + const flat_set& get_contract_blacklist() const; + const flat_set< pair >& get_action_blacklist() const; + const flat_set& get_key_blacklist() const; + + void set_actor_whitelist( const flat_set& ); + void set_actor_blacklist( const flat_set& ); + void set_contract_whitelist( const flat_set& ); + void set_contract_blacklist( const flat_set& ); + void set_action_blacklist( const flat_set< pair >& ); + void set_key_blacklist( const flat_set& ); + uint32_t head_block_num()const; time_point head_block_time()const; block_id_type head_block_id()const; diff --git a/plugins/chain_api_plugin/chain_api_plugin.cpp b/plugins/chain_api_plugin/chain_api_plugin.cpp index d6ec6613dbc..2bb8e1db6e0 100644 --- a/plugins/chain_api_plugin/chain_api_plugin.cpp +++ b/plugins/chain_api_plugin/chain_api_plugin.cpp @@ -94,6 +94,8 @@ void chain_api_plugin::plugin_startup() { CHAIN_RO_CALL(abi_json_to_bin, 200), CHAIN_RO_CALL(abi_bin_to_json, 200), CHAIN_RO_CALL(get_required_keys, 200), + CHAIN_RO_CALL(get_whitelist_blacklist, 200), + CHAIN_RW_CALL(set_whitelist_blacklist, 200), CHAIN_RW_CALL_ASYNC(push_block, chain_apis::read_write::push_block_results, 202), CHAIN_RW_CALL_ASYNC(push_transaction, chain_apis::read_write::push_transaction_results, 202), CHAIN_RW_CALL_ASYNC(push_transactions, chain_apis::read_write::push_transactions_results, 202) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 894ee3986f0..d0dcb8e1b60 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1309,6 +1309,27 @@ fc::variant read_only::get_block_header_state(const get_block_header_state_param return vo; } +read_only::get_whitelist_blacklist_results read_only::get_whitelist_blacklist(const read_only::get_whitelist_blacklist_params& params) const { + return { + db.get_actor_whitelist(), + db.get_actor_blacklist(), + db.get_contract_whitelist(), + db.get_contract_blacklist(), + db.get_action_blacklist(), + db.get_key_blacklist() + }; +} + +read_write::set_whitelist_blacklist_results read_write::set_whitelist_blacklist(const read_write::set_whitelist_blacklist_params& params) const { + if(params.actor_whitelist.valid()) db.set_actor_whitelist(*params.actor_whitelist); + if(params.actor_blacklist.valid()) db.set_actor_blacklist(*params.actor_blacklist); + if(params.contract_whitelist.valid()) db.set_contract_whitelist(*params.contract_whitelist); + if(params.contract_blacklist.valid()) db.set_contract_blacklist(*params.contract_blacklist); + if(params.action_blacklist.valid()) db.set_action_blacklist(*params.action_blacklist); + if(params.key_blacklist.valid()) db.set_key_blacklist(*params.key_blacklist); + return set_whitelist_blacklist_results{}; +} + void read_write::push_block(const read_write::push_block_params& params, next_function next) { try { app().get_method()(std::make_shared(params)); diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index c4e140c0e8c..272cfafb180 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -24,6 +24,7 @@ namespace fc { class variant; } namespace eosio { using chain::controller; using std::unique_ptr; + using std::pair; using namespace appbase; using chain::name; using chain::uint128_t; @@ -33,6 +34,7 @@ namespace eosio { using chain::asset; using chain::authority; using chain::account_name; + using chain::action_name; using chain::abi_def; using chain::abi_serializer; @@ -453,6 +455,18 @@ class read_only { return result; } + using get_whitelist_blacklist_params = empty; + + struct get_whitelist_blacklist_results { + flat_set actor_whitelist; + flat_set actor_blacklist; + flat_set contract_whitelist; + flat_set contract_blacklist; + flat_set< pair > action_blacklist; + flat_set key_blacklist; + }; + get_whitelist_blacklist_results get_whitelist_blacklist(const get_whitelist_blacklist_params&) const; + friend struct resolver_factory; }; @@ -479,6 +493,17 @@ class read_write { using push_transactions_results = vector; void push_transactions(const push_transactions_params& params, chain::plugin_interface::next_function next); + struct set_whitelist_blacklist_params { + optional< flat_set > actor_whitelist; + optional< flat_set > actor_blacklist; + optional< flat_set > contract_whitelist; + optional< flat_set > contract_blacklist; + optional< flat_set< pair > > action_blacklist; + optional< flat_set > key_blacklist; + }; + using set_whitelist_blacklist_results = empty; + set_whitelist_blacklist_results set_whitelist_blacklist(const set_whitelist_blacklist_params& params) const; + friend resolver_factory; }; } // namespace chain_apis @@ -581,3 +606,6 @@ FC_REFLECT( eosio::chain_apis::read_only::abi_bin_to_json_params, (code)(action) FC_REFLECT( eosio::chain_apis::read_only::abi_bin_to_json_result, (args) ) FC_REFLECT( eosio::chain_apis::read_only::get_required_keys_params, (transaction)(available_keys) ) FC_REFLECT( eosio::chain_apis::read_only::get_required_keys_result, (required_keys) ) + +FC_REFLECT( eosio::chain_apis::read_only::get_whitelist_blacklist_results, (actor_whitelist)(actor_blacklist)(contract_whitelist)(contract_blacklist)(action_blacklist)(key_blacklist) ) +FC_REFLECT( eosio::chain_apis::read_write::set_whitelist_blacklist_params, (actor_whitelist)(actor_blacklist)(contract_whitelist)(contract_blacklist)(action_blacklist)(key_blacklist) ) From f698d86a74922f834d34c5f9b0e0e765b084d4d2 Mon Sep 17 00:00:00 2001 From: Andrianto Lie Date: Thu, 26 Jul 2018 11:34:24 +0800 Subject: [PATCH 107/294] Move whitelist_blacklist to producer_plugin --- plugins/chain_api_plugin/chain_api_plugin.cpp | 2 -- plugins/chain_plugin/chain_plugin.cpp | 21 --------------- .../eosio/chain_plugin/chain_plugin.hpp | 26 ------------------- .../producer_api_plugin.cpp | 4 +++ .../eosio/producer_plugin/producer_plugin.hpp | 15 +++++++++++ plugins/producer_plugin/producer_plugin.cpp | 23 ++++++++++++++++ 6 files changed, 42 insertions(+), 49 deletions(-) diff --git a/plugins/chain_api_plugin/chain_api_plugin.cpp b/plugins/chain_api_plugin/chain_api_plugin.cpp index 2bb8e1db6e0..d6ec6613dbc 100644 --- a/plugins/chain_api_plugin/chain_api_plugin.cpp +++ b/plugins/chain_api_plugin/chain_api_plugin.cpp @@ -94,8 +94,6 @@ void chain_api_plugin::plugin_startup() { CHAIN_RO_CALL(abi_json_to_bin, 200), CHAIN_RO_CALL(abi_bin_to_json, 200), CHAIN_RO_CALL(get_required_keys, 200), - CHAIN_RO_CALL(get_whitelist_blacklist, 200), - CHAIN_RW_CALL(set_whitelist_blacklist, 200), CHAIN_RW_CALL_ASYNC(push_block, chain_apis::read_write::push_block_results, 202), CHAIN_RW_CALL_ASYNC(push_transaction, chain_apis::read_write::push_transaction_results, 202), CHAIN_RW_CALL_ASYNC(push_transactions, chain_apis::read_write::push_transactions_results, 202) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index d0dcb8e1b60..894ee3986f0 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1309,27 +1309,6 @@ fc::variant read_only::get_block_header_state(const get_block_header_state_param return vo; } -read_only::get_whitelist_blacklist_results read_only::get_whitelist_blacklist(const read_only::get_whitelist_blacklist_params& params) const { - return { - db.get_actor_whitelist(), - db.get_actor_blacklist(), - db.get_contract_whitelist(), - db.get_contract_blacklist(), - db.get_action_blacklist(), - db.get_key_blacklist() - }; -} - -read_write::set_whitelist_blacklist_results read_write::set_whitelist_blacklist(const read_write::set_whitelist_blacklist_params& params) const { - if(params.actor_whitelist.valid()) db.set_actor_whitelist(*params.actor_whitelist); - if(params.actor_blacklist.valid()) db.set_actor_blacklist(*params.actor_blacklist); - if(params.contract_whitelist.valid()) db.set_contract_whitelist(*params.contract_whitelist); - if(params.contract_blacklist.valid()) db.set_contract_blacklist(*params.contract_blacklist); - if(params.action_blacklist.valid()) db.set_action_blacklist(*params.action_blacklist); - if(params.key_blacklist.valid()) db.set_key_blacklist(*params.key_blacklist); - return set_whitelist_blacklist_results{}; -} - void read_write::push_block(const read_write::push_block_params& params, next_function next) { try { app().get_method()(std::make_shared(params)); diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index 272cfafb180..2b18852974f 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -455,18 +455,6 @@ class read_only { return result; } - using get_whitelist_blacklist_params = empty; - - struct get_whitelist_blacklist_results { - flat_set actor_whitelist; - flat_set actor_blacklist; - flat_set contract_whitelist; - flat_set contract_blacklist; - flat_set< pair > action_blacklist; - flat_set key_blacklist; - }; - get_whitelist_blacklist_results get_whitelist_blacklist(const get_whitelist_blacklist_params&) const; - friend struct resolver_factory; }; @@ -493,17 +481,6 @@ class read_write { using push_transactions_results = vector; void push_transactions(const push_transactions_params& params, chain::plugin_interface::next_function next); - struct set_whitelist_blacklist_params { - optional< flat_set > actor_whitelist; - optional< flat_set > actor_blacklist; - optional< flat_set > contract_whitelist; - optional< flat_set > contract_blacklist; - optional< flat_set< pair > > action_blacklist; - optional< flat_set > key_blacklist; - }; - using set_whitelist_blacklist_results = empty; - set_whitelist_blacklist_results set_whitelist_blacklist(const set_whitelist_blacklist_params& params) const; - friend resolver_factory; }; } // namespace chain_apis @@ -606,6 +583,3 @@ FC_REFLECT( eosio::chain_apis::read_only::abi_bin_to_json_params, (code)(action) FC_REFLECT( eosio::chain_apis::read_only::abi_bin_to_json_result, (args) ) FC_REFLECT( eosio::chain_apis::read_only::get_required_keys_params, (transaction)(available_keys) ) FC_REFLECT( eosio::chain_apis::read_only::get_required_keys_result, (required_keys) ) - -FC_REFLECT( eosio::chain_apis::read_only::get_whitelist_blacklist_results, (actor_whitelist)(actor_blacklist)(contract_whitelist)(contract_blacklist)(action_blacklist)(key_blacklist) ) -FC_REFLECT( eosio::chain_apis::read_write::set_whitelist_blacklist_params, (actor_whitelist)(actor_blacklist)(contract_whitelist)(contract_blacklist)(action_blacklist)(key_blacklist) ) diff --git a/plugins/producer_api_plugin/producer_api_plugin.cpp b/plugins/producer_api_plugin/producer_api_plugin.cpp index 99e9dc40c65..f414d2c3924 100644 --- a/plugins/producer_api_plugin/producer_api_plugin.cpp +++ b/plugins/producer_api_plugin/producer_api_plugin.cpp @@ -82,6 +82,10 @@ void producer_api_plugin::plugin_startup() { INVOKE_V_R(producer, remove_greylist_accounts, producer_plugin::greylist_params), 201), CALL(producer, producer, get_greylist, INVOKE_R_V(producer, get_greylist), 201), + CALL(producer, producer, get_whitelist_blacklist, + INVOKE_R_V(producer, get_whitelist_blacklist), 201), + CALL(producer, producer, set_whitelist_blacklist, + INVOKE_V_R(producer, set_whitelist_blacklist, producer_plugin::whitelist_blacklist), 201), }); } diff --git a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp index 4b64b7bb73d..48b3331c376 100644 --- a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp +++ b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp @@ -26,6 +26,15 @@ class producer_plugin : public appbase::plugin { fc::optional subjective_cpu_leeway_us; }; + struct whitelist_blacklist { + fc::optional< flat_set > actor_whitelist; + fc::optional< flat_set > actor_blacklist; + fc::optional< flat_set > contract_whitelist; + fc::optional< flat_set > contract_blacklist; + fc::optional< flat_set< std::pair > > action_blacklist; + fc::optional< flat_set > key_blacklist; + }; + struct greylist_params { std::vector accounts; }; @@ -55,6 +64,10 @@ class producer_plugin : public appbase::plugin { void remove_greylist_accounts(const greylist_params& params); greylist_params get_greylist() const; + whitelist_blacklist get_whitelist_blacklist() const; + void set_whitelist_blacklist(const whitelist_blacklist& params); + + signal confirmed_block; private: std::shared_ptr my; @@ -64,4 +77,6 @@ class producer_plugin : public appbase::plugin { FC_REFLECT(eosio::producer_plugin::runtime_options, (max_transaction_time)(max_irreversible_block_age)(produce_time_offset_us)(last_block_time_offset_us)(subjective_cpu_leeway_us)); FC_REFLECT(eosio::producer_plugin::greylist_params, (accounts)); +FC_REFLECT(eosio::producer_plugin::whitelist_blacklist, (actor_whitelist)(actor_blacklist)(contract_whitelist)(contract_blacklist)(action_blacklist)(key_blacklist) ) + diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index d4be760a06c..ddde38e305b 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -767,6 +767,29 @@ producer_plugin::greylist_params producer_plugin::get_greylist() const { return result; } +producer_plugin::whitelist_blacklist producer_plugin::get_whitelist_blacklist() const { + chain::controller& chain = app().get_plugin().chain(); + return { + chain.get_actor_whitelist(), + chain.get_actor_blacklist(), + chain.get_contract_whitelist(), + chain.get_contract_blacklist(), + chain.get_action_blacklist(), + chain.get_key_blacklist() + }; +} + +void producer_plugin::set_whitelist_blacklist(const producer_plugin::whitelist_blacklist& params) { + chain::controller& chain = app().get_plugin().chain(); + if(params.actor_whitelist.valid()) chain.set_actor_whitelist(*params.actor_whitelist); + if(params.actor_blacklist.valid()) chain.set_actor_blacklist(*params.actor_blacklist); + if(params.contract_whitelist.valid()) chain.set_contract_whitelist(*params.contract_whitelist); + if(params.contract_blacklist.valid()) chain.set_contract_blacklist(*params.contract_blacklist); + if(params.action_blacklist.valid()) chain.set_action_blacklist(*params.action_blacklist); + if(params.key_blacklist.valid()) chain.set_key_blacklist(*params.key_blacklist); +} + + optional producer_plugin_impl::calculate_next_block_time(const account_name& producer_name) const { chain::controller& chain = app().get_plugin().chain(); const auto& hbs = chain.head_block_state(); From ceb02ef4ecb44603536bd8c00ec5704bc0359d9e Mon Sep 17 00:00:00 2001 From: Andrianto Lie Date: Fri, 27 Jul 2018 11:32:43 +0800 Subject: [PATCH 108/294] Change http status code for transaction_exception and eof_exception so its error message is not swallowed by cleos --- plugins/http_plugin/http_plugin.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/http_plugin/http_plugin.cpp b/plugins/http_plugin/http_plugin.cpp index f7fb5fdce59..c230755d209 100644 --- a/plugins/http_plugin/http_plugin.cpp +++ b/plugins/http_plugin/http_plugin.cpp @@ -460,12 +460,9 @@ namespace eosio { } catch (chain::tx_duplicate& e) { error_results results{409, "Conflict", error_results::error_info(e, verbose_http_errors)}; cb( 409, fc::json::to_string( results )); - } catch (chain::transaction_exception& e) { - error_results results{400, "Bad Request", error_results::error_info(e, verbose_http_errors)}; - cb( 400, fc::json::to_string( results )); } catch (fc::eof_exception& e) { - error_results results{400, "Bad Request", error_results::error_info(e, verbose_http_errors)}; - cb( 400, fc::json::to_string( results )); + error_results results{422, "Unprocessable Entity", error_results::error_info(e, verbose_http_errors)}; + cb( 422, fc::json::to_string( results )); elog( "Unable to parse arguments to ${api}.${call}", ("api", api_name)( "call", call_name )); dlog("Bad arguments: ${args}", ("args", body)); } catch (fc::exception& e) { From d818503e131d33ec079a66c9a9bd0bb8b3445ef9 Mon Sep 17 00:00:00 2001 From: venediktov Date: Fri, 27 Jul 2018 09:17:18 -0700 Subject: [PATCH 109/294] add --key-type sha256 to table command --- plugins/chain_plugin/chain_plugin.cpp | 9 +++++++++ programs/cleos/main.cpp | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 894ee3986f0..41125e3570b 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -28,6 +28,7 @@ #include #include +#include #include namespace eosio { @@ -1029,6 +1030,14 @@ read_only::get_table_rows_result read_only::get_table_rows( const read_only::get return f128; }); } + else if (p.key_type == "sha256") { + return get_table_rows_by_seckey(p, abi, [&p](const checksum256_type& v)->key256_t { + key256_t k; + k[0] = ((uint128_t *)&v._hash)[0]; + k[1] = ((uint128_t *)&v._hash)[1]; + return k; + }); + } EOS_ASSERT(false, chain::contract_table_query_exception, "Unsupported secondary index type: ${t}", ("t", p.key_type)); } } diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 7ec0f108489..f924e7546bb 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -1831,7 +1831,7 @@ int main( int argc, char** argv ) { localized("Index number, 1 - primary (first), 2 - secondary index (in order defined by multi_index), 3 - third index, etc.\n" "\t\t\t\tNumber or name of index can be specified, e.g. 'secondary' or '2'.")); getTable->add_option( "--key-type", key_type, - localized("The key type of --index, primary only supports (i64), all others support (i64, i128, i256, float64, float128).\n" + localized("The key type of --index, primary only supports (i64), all others support (i64, i128, i256, float64, float128, sha256).\n" "\t\t\t\tSpecial type 'name' indicates an account name.")); getTable->set_callback([&] { From 292bbda1ae521cc2ab8a7319ac32df01c4af8957 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 27 Jul 2018 11:59:36 -0500 Subject: [PATCH 110/294] Add implicit and scheduled flags --- .../chain/include/eosio/chain/transaction_metadata.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/chain/include/eosio/chain/transaction_metadata.hpp b/libraries/chain/include/eosio/chain/transaction_metadata.hpp index d89d09a6288..44d5d008a79 100644 --- a/libraries/chain/include/eosio/chain/transaction_metadata.hpp +++ b/libraries/chain/include/eosio/chain/transaction_metadata.hpp @@ -21,15 +21,17 @@ class transaction_metadata { packed_transaction packed_trx; optional>> signing_keys; bool accepted = false; + bool implicit = false; + bool scheduled = false; - transaction_metadata( const signed_transaction& t, packed_transaction::compression_type c = packed_transaction::none ) + explicit transaction_metadata( const signed_transaction& t, packed_transaction::compression_type c = packed_transaction::none ) :trx(t),packed_trx(t, c) { id = trx.id(); //raw_packed = fc::raw::pack( static_cast(trx) ); signed_id = digest_type::hash(packed_trx); } - transaction_metadata( const packed_transaction& ptrx ) + explicit transaction_metadata( const packed_transaction& ptrx ) :trx( ptrx.get_signed_transaction() ), packed_trx(ptrx) { id = trx.id(); //raw_packed = fc::raw::pack( static_cast(trx) ); From 04f1cab533db96d936c436bea815e0d6fbf1368a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 27 Jul 2018 12:00:36 -0500 Subject: [PATCH 111/294] Check new implicit and scheduled flags instead of signatures for determining implicit --- plugins/bnet_plugin/bnet_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/bnet_plugin/bnet_plugin.cpp b/plugins/bnet_plugin/bnet_plugin.cpp index 17d12c421d2..a93d385af66 100644 --- a/plugins/bnet_plugin/bnet_plugin.cpp +++ b/plugins/bnet_plugin/bnet_plugin.cpp @@ -1203,7 +1203,7 @@ namespace eosio { } void on_accepted_transaction( transaction_metadata_ptr trx ) { - if( trx->trx.signatures.size() == 0 ) return; + if( trx->implicit || trx->scheduled ) return; for_each_session( [trx]( auto ses ){ ses->on_accepted_transaction( trx ); } ); } From 43fad60891274a13f483afccb1a37ccb7771237f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 27 Jul 2018 12:02:19 -0500 Subject: [PATCH 112/294] - emit accepted_transaction signal for implicit transaction onerror. - emit accepted_transaction signal for all scheduled transactions. - emit applied_transaction signal after onerror processing of scheduled transactions. - emit applied_transaction signal for expired scheduled transactions. --- libraries/chain/controller.cpp | 57 +++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 408f694f71f..80879d90c6c 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -221,6 +221,8 @@ struct controller_impl { std::cerr << std::setw(10) << next->block_num() << " of " << end->block_num() <<"\r"; } } + std::cerr<< "\n"; + ilog( "${n} blocks replayed", ("n", head->block_num) ); int rev = 0; while( auto obj = reversible_blocks.find(head->block_num+1) ) { @@ -228,13 +230,12 @@ struct controller_impl { self.push_block( obj->get_block(), controller::block_status::validated ); } - std::cerr<< "\n"; ilog( "${n} reversible blocks replayed", ("n",rev) ); auto end = fc::time_point::now(); - ilog( "replayed ${n} blocks in seconds, ${mspb} ms/block", + ilog( "replayed ${n} blocks in ${s} seconds, ${mspb} ms/block", + ("s", (end-start).count()/1000/1000 ) ("n", head->block_num)("duration", (end-start).count()/1000000) ("mspb", ((end-start).count()/1000.0)/head->block_num) ); - std::cerr<< "\n"; replaying = false; } else if( !end ) { @@ -491,6 +492,11 @@ struct controller_impl { trx_context.billed_cpu_time_us, trace->net_usage ); fc::move_append( pending->_actions, move(trx_context.executed) ); + auto onetrx = std::make_shared( etrx ); + onetrx->accepted = true; + onetrx->implicit = true; + emit( self.accepted_transaction, onetrx ); + emit( self.applied_transaction, trace ); trx_context.squash(); @@ -554,19 +560,31 @@ struct controller_impl { EOS_ASSERT( gtrx.delay_until <= self.pending_block_time(), transaction_exception, "this transaction isn't ready", ("gtrx.delay_until",gtrx.delay_until)("pbt",self.pending_block_time()) ); + signed_transaction dtrx; + fc::raw::unpack(ds,static_cast(dtrx) ); + transaction_metadata_ptr trx = std::make_shared( dtrx ); + + transaction_trace_ptr trace; + auto emit_on_exit = fc::make_scoped_exit( [&trace, &trx, this]() { + if( trx ) { + trx->accepted = true; + trx->scheduled = true; + emit( self.accepted_transaction, trx ); + } + if( trace ) { + emit( self.applied_transaction, trace ); + } + } ); if( gtrx.expiration < self.pending_block_time() ) { - auto trace = std::make_shared(); + trace = std::make_shared(); trace->id = gtrx.trx_id; - trace->scheduled = false; + trace->scheduled = true; trace->receipt = push_receipt( gtrx.trx_id, transaction_receipt::expired, billed_cpu_time_us, 0 ); // expire the transaction undo_session.squash(); return trace; } - signed_transaction dtrx; - fc::raw::unpack(ds,static_cast(dtrx) ); - auto reset_in_trx_requiring_checks = fc::make_scoped_exit([old_value=in_trx_requiring_checks,this](){ in_trx_requiring_checks = old_value; }); @@ -575,7 +593,7 @@ struct controller_impl { transaction_context trx_context( self, dtrx, gtrx.trx_id ); trx_context.deadline = deadline; trx_context.billed_cpu_time_us = billed_cpu_time_us; - transaction_trace_ptr trace = trx_context.trace; + trace = trx_context.trace; flat_set bill_to_accounts; try { trx_context.init_for_deferred_trx( gtrx.published ); @@ -592,8 +610,6 @@ struct controller_impl { fc::move_append( pending->_actions, move(trx_context.executed) ); - emit( self.applied_transaction, trace ); - trx_context.squash(); undo_session.squash(); restore.cancel(); @@ -628,7 +644,6 @@ struct controller_impl { if (!failure_is_subjective(*trace->except)) { trace->receipt = push_receipt(gtrx.trx_id, transaction_receipt::hard_fail, trx_context.billed_cpu_time_us, 0); - emit( self.applied_transaction, trace ); undo_session.squash(); } @@ -659,7 +674,6 @@ struct controller_impl { */ transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline, - bool implicit, uint32_t billed_cpu_time_us) { EOS_ASSERT(deadline != fc::time_point(), transaction_exception, "deadline cannot be uninitialized"); @@ -674,7 +688,7 @@ struct controller_impl { trx_context.billed_cpu_time_us = billed_cpu_time_us; trace = trx_context.trace; try { - if( implicit ) { + if( trx->implicit ) { trx_context.init_for_implicit_trx(); trx_context.can_subjectively_fail = false; } else { @@ -690,7 +704,7 @@ struct controller_impl { trx_context.delay = fc::seconds(trx->trx.delay_sec); - if( !self.skip_auth_check() && !implicit ) { + if( !self.skip_auth_check() && !trx->implicit ) { authorization.check_authorization( trx->trx.actions, trx->recover_keys( chain_id ), @@ -707,7 +721,7 @@ struct controller_impl { auto restore = make_block_restore_point(); - if (!implicit) { + if (!trx->implicit) { transaction_receipt::status_enum s = (trx_context.delay == fc::seconds(0)) ? transaction_receipt::executed : transaction_receipt::delayed; @@ -725,8 +739,8 @@ struct controller_impl { // call the accept signal but only once for this transaction if (!trx->accepted) { - emit( self.accepted_transaction, trx); trx->accepted = true; + emit( self.accepted_transaction, trx); } emit(self.applied_transaction, trace); @@ -740,7 +754,7 @@ struct controller_impl { trx_context.squash(); } - if (!implicit) { + if (!trx->implicit) { unapplied_transactions.erase( trx->signed_id ); } return trace; @@ -805,11 +819,12 @@ struct controller_impl { try { auto onbtrx = std::make_shared( get_on_block_transaction() ); + onbtrx->implicit = true; auto reset_in_trx_requiring_checks = fc::make_scoped_exit([old_value=in_trx_requiring_checks,this](){ in_trx_requiring_checks = old_value; }); in_trx_requiring_checks = true; - push_transaction( onbtrx, fc::time_point::maximum(), true, self.get_global_properties().configuration.min_transaction_cpu_usage ); + push_transaction( onbtrx, fc::time_point::maximum(), self.get_global_properties().configuration.min_transaction_cpu_usage ); } catch( const boost::interprocess::bad_alloc& e ) { elog( "on block transaction failed due to a bad allocation" ); throw; @@ -848,7 +863,7 @@ struct controller_impl { if( receipt.trx.contains() ) { auto& pt = receipt.trx.get(); auto mtrx = std::make_shared(pt); - trace = push_transaction( mtrx, fc::time_point::maximum(), false, receipt.cpu_usage_us); + trace = push_transaction( mtrx, fc::time_point::maximum(), receipt.cpu_usage_us); } else if( receipt.trx.contains() ) { trace = push_scheduled_transaction( receipt.trx.get(), fc::time_point::maximum(), receipt.cpu_usage_us ); } else { @@ -1300,7 +1315,7 @@ void controller::push_confirmation( const header_confirmation& c ) { transaction_trace_ptr controller::push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline, uint32_t billed_cpu_time_us ) { validate_db_available_size(); - return my->push_transaction(trx, deadline, false, billed_cpu_time_us); + return my->push_transaction(trx, deadline, billed_cpu_time_us); } transaction_trace_ptr controller::push_scheduled_transaction( const transaction_id_type& trxid, fc::time_point deadline, uint32_t billed_cpu_time_us ) From 524a001b184de068058a76f2dd28e4afbed9eafd Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 27 Jul 2018 14:52:11 -0500 Subject: [PATCH 113/294] Storing off biosNode for cluster to report in status. GH #4903 --- tests/Cluster.py | 55 ++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index 97051d35eac..8bfba8fd32f 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -180,7 +180,8 @@ def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", p2pPlugin="ne return True Utils.Print("Bootstrap cluster.") - if not Cluster.bootstrap(totalNodes, prodCount, Cluster.__BiosHost, Cluster.__BiosPort, dontKill, onlyBios): + self.biosNode=Cluster.bootstrap(totalNodes, prodCount, Cluster.__BiosHost, Cluster.__BiosPort, dontKill, onlyBios) + if self.biosNode is None: Utils.Print("ERROR: Bootstrap failed.") return False @@ -691,13 +692,13 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio biosNode=Node(biosHost, biosPort) if not biosNode.checkPulse(): Utils.Print("ERROR: Bios node doesn't appear to be running...") - return False + return None producerKeys=Cluster.parseClusterKeys(totalNodes) # should have totalNodes node plus bios node if producerKeys is None or len(producerKeys) < (totalNodes+1): Utils.Print("ERROR: Failed to parse private keys from cluster config files.") - return False + return None walletMgr=WalletMgr(True) walletMgr.killall() @@ -705,7 +706,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio if not walletMgr.launch(): Utils.Print("ERROR: Failed to launch bootstrap wallet.") - return False + return None biosNode.setWalletEndpointArgs(walletMgr.walletEndpointArgs) try: @@ -721,7 +722,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio if not walletMgr.importKey(eosioAccount, ignWallet): Utils.Print("ERROR: Failed to import %s account keys into ignition wallet." % (eosioName)) - return False + return None contract="eosio.bios" contractDir="contracts/%s" % (contract) @@ -731,7 +732,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio trans=biosNode.publishContract(eosioAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) if trans is None: Utils.Print("ERROR: Failed to publish contract %s." % (contract)) - return False + return None Node.validateTransaction(trans) @@ -748,14 +749,14 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio trans=biosNode.createAccount(initx, eosioAccount, 0) if trans is None: Utils.Print("ERROR: Failed to create account %s" % (name)) - return False + return None Node.validateTransaction(trans) accounts.append(initx) transId=Node.getTransId(trans) if not biosNode.waitForTransInBlock(transId): Utils.Print("ERROR: Failed to validate transaction %s got rolled into a block on server port %d." % (transId, biosNode.port)) - return False + return None Utils.Print("Validating system accounts within bootstrap") biosNode.validateAccounts(accounts) @@ -772,7 +773,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio myTrans=biosNode.pushMessage("eosio", "setprods", setProdsStr, opts) if myTrans is None or not myTrans[0]: Utils.Print("ERROR: Failed to set producers.") - return False + return None else: counts=dict.fromkeys(range(totalNodes), 0) #initialize node prods count to 0 setProdsStr='{"schedule": [' @@ -798,54 +799,54 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio trans=biosNode.pushMessage("eosio", "setprods", setProdsStr, opts) if trans is None or not trans[0]: Utils.Print("ERROR: Failed to set producer %s." % (keys["name"])) - return False + return None trans=trans[1] transId=Node.getTransId(trans) if not biosNode.waitForTransInBlock(transId): Utils.Print("ERROR: Failed to validate transaction %s got rolled into a block on server port %d." % (transId, biosNode.port)) - return False + return None # wait for block production handover (essentially a block produced by anyone but eosio). lam = lambda: biosNode.getInfo(exitOnError=True)["head_block_producer"] != "eosio" ret=Utils.waitForBool(lam) if not ret: Utils.Print("ERROR: Block production handover failed.") - return False + return None eosioTokenAccount=copy.deepcopy(eosioAccount) eosioTokenAccount.name="eosio.token" trans=biosNode.createAccount(eosioTokenAccount, eosioAccount, 0) if trans is None: Utils.Print("ERROR: Failed to create account %s" % (eosioTokenAccount.name)) - return False + return None eosioRamAccount=copy.deepcopy(eosioAccount) eosioRamAccount.name="eosio.ram" trans=biosNode.createAccount(eosioRamAccount, eosioAccount, 0) if trans is None: Utils.Print("ERROR: Failed to create account %s" % (eosioRamAccount.name)) - return False + return None eosioRamfeeAccount=copy.deepcopy(eosioAccount) eosioRamfeeAccount.name="eosio.ramfee" trans=biosNode.createAccount(eosioRamfeeAccount, eosioAccount, 0) if trans is None: Utils.Print("ERROR: Failed to create account %s" % (eosioRamfeeAccount.name)) - return False + return None eosioStakeAccount=copy.deepcopy(eosioAccount) eosioStakeAccount.name="eosio.stake" trans=biosNode.createAccount(eosioStakeAccount, eosioAccount, 0) if trans is None: Utils.Print("ERROR: Failed to create account %s" % (eosioStakeAccount.name)) - return False + return None Node.validateTransaction(trans) transId=Node.getTransId(trans) if not biosNode.waitForTransInBlock(transId): Utils.Print("ERROR: Failed to validate transaction %s got rolled into a block on server port %d." % (transId, biosNode.port)) - return False + return None contract="eosio.token" contractDir="contracts/%s" % (contract) @@ -855,7 +856,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio trans=biosNode.publishContract(eosioTokenAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) if trans is None: Utils.Print("ERROR: Failed to publish contract %s." % (contract)) - return False + return None # Create currency0000, followed by issue currency0000 contract=eosioTokenAccount.name @@ -866,13 +867,13 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio trans=biosNode.pushMessage(contract, action, data, opts) if trans is None or not trans[0]: Utils.Print("ERROR: Failed to push create action to eosio contract.") - return False + return None Node.validateTransaction(trans[1]) transId=Node.getTransId(trans[1]) if not biosNode.waitForTransInBlock(transId): Utils.Print("ERROR: Failed to validate transaction %s got rolled into a block on server port %d." % (transId, biosNode.port)) - return False + return None contract=eosioTokenAccount.name Utils.Print("push issue action to %s contract" % (contract)) @@ -882,7 +883,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio trans=biosNode.pushMessage(contract, action, data, opts) if trans is None or not trans[0]: Utils.Print("ERROR: Failed to push issue action to eosio contract.") - return False + return None Node.validateTransaction(trans[1]) Utils.Print("Wait for issue action transaction to become finalized.") @@ -892,7 +893,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio timeout = .5 * 12 * 2 * len(producerKeys) + 60 if not biosNode.waitForTransFinalization(transId, timeout=timeout): Utils.Print("ERROR: Failed to validate transaction %s got rolled into a finalized block on server port %d." % (transId, biosNode.port)) - return False + return None expectedAmount="1000000000.0000 {0}".format(CORE_SYMBOL) Utils.Print("Verify eosio issue, Expected: %s" % (expectedAmount)) @@ -900,7 +901,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio if expectedAmount != actualAmount: Utils.Print("ERROR: Issue verification failed. Excepted %s, actual: %s" % (expectedAmount, actualAmount)) - return False + return None contract="eosio.system" contractDir="contracts/%s" % (contract) @@ -910,7 +911,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio trans=biosNode.publishContract(eosioAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) if trans is None: Utils.Print("ERROR: Failed to publish contract %s." % (contract)) - return False + return None Node.validateTransaction(trans) @@ -925,7 +926,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio trans=biosNode.pushMessage(contract, action, data, opts) if trans is None or not trans[0]: Utils.Print("ERROR: Failed to transfer funds from %s to %s." % (eosioTokenAccount.name, name)) - return False + return None Node.validateTransaction(trans[1]) @@ -933,7 +934,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio transId=Node.getTransId(trans[1]) if not biosNode.waitForTransInBlock(transId): Utils.Print("ERROR: Failed to validate transaction %s got rolled into a block on server port %d." % (transId, biosNode.port)) - return False + return None Utils.Print("Cluster bootstrap done.") finally: @@ -941,7 +942,7 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio walletMgr.killall() walletMgr.cleanup() - return True + return biosNode # Populates list of EosInstanceInfo objects, matched to actual running instances From 051e036f1b67cf9e5275925354682b377b4d136a Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 27 Jul 2018 15:05:28 -0500 Subject: [PATCH 114/294] Added method to waitForTransBlock if it is needed. GH #4903 --- tests/Node.py | 55 +++++++++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index ebc926a00b9..944a880ad92 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -407,10 +407,7 @@ def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, w trans = self.transferFunds(creatorAccount, account, Node.currencyIntToStr(stakedDeposit, CORE_SYMBOL), "init") transId=Node.getTransId(trans) - if waitForTransBlock and not self.waitForTransInBlock(transId): - return None - - return trans + return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) def createAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False, exitOnError=False): """Create account and return creation transactions. Return transaction json object. @@ -427,10 +424,7 @@ def createAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTran trans = self.transferFunds(creatorAccount, account, "%0.04f %s" % (stakedDeposit/10000, CORE_SYMBOL), "init") transId=Node.getTransId(trans) - if waitForTransBlock and not self.waitForTransInBlock(transId): - return None - - return trans + return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) def getEosAccount(self, name, exitOnError=False): assert(isinstance(name, str)) @@ -586,14 +580,8 @@ def transferFunds(self, source, destination, amountStr, memo="memo", force=False if trans is None: Utils.cmdError("could not transfer \"%s\" from %s to %s" % (amountStr, source, destination)) errorExit("Failed to transfer \"%s\" from %s to %s" % (amountStr, source, destination)) - transId=Node.getTransId(trans) - if waitForTransBlock and not self.waitForTransInBlock(transId): - if exitOnError: - Utils.cmdError("could not find transfer with transId=%s in block" % (transId)) - errorExit("Failed to find transfer with transId=%s in block" % (transId)) - return None - return trans + return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) @staticmethod def currencyStrToInt(balanceStr): @@ -782,10 +770,7 @@ def publishContract(self, account, contractDir, wastFile, abiFile, waitForTransB return None Node.validateTransaction(trans) - transId=Node.getTransId(trans) - if waitForTransBlock and not self.waitForTransInBlock(transId): - return None - return trans + return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=False) def getTableRows(self, contract, scope, table): jsonData=self.getTable(contract, scope, table) @@ -834,10 +819,7 @@ def setPermission(self, account, code, pType, requirement, waitForTransBlock=Fal cmd="%s -j %s %s %s %s" % (cmdDesc, account, code, pType, requirement) trans=self.processCmd(cmd, cmdDesc, silentErrors=False, exitOnError=exitOnError) - transId=Node.getTransId(trans) - if waitForTransBlock and not self.waitForTransInBlock(transId): - return None - return trans + return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) def delegatebw(self, fromAccount, netQuantity, cpuQuantity, toAccount=None, transferTo=False, waitForTransBlock=False, exitOnError=False): if toAccount is None: @@ -850,10 +832,7 @@ def delegatebw(self, fromAccount, netQuantity, cpuQuantity, toAccount=None, tran msg="fromAccount=%s, toAccount=%s" % (fromAccount.name, toAccount.name); trans=self.processCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) - transId=Node.getTransId(trans) - if waitForTransBlock and not self.waitForTransInBlock(transId): - return None - return trans + return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) def regproducer(self, producer, url, location, waitForTransBlock=False, exitOnError=False): cmdDesc="system regproducer" @@ -862,10 +841,7 @@ def regproducer(self, producer, url, location, waitForTransBlock=False, exitOnEr msg="producer=%s" % (producer.name); trans=self.processCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) - transId=Node.getTransId(trans) - if waitForTransBlock and not self.waitForTransInBlock(transId): - return None - return trans + return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) def vote(self, account, producers, waitForTransBlock=False, exitOnError=False): cmdDesc = "system voteproducer prods" @@ -874,10 +850,7 @@ def vote(self, account, producers, waitForTransBlock=False, exitOnError=False): msg="account=%s, producers=[ %s ]" % (account.name, ", ".join(producers)); trans=self.processCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) - transId=Node.getTransId(trans) - if waitForTransBlock and not self.waitForTransInBlock(transId): - return None - return trans + return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) def processCmd(self, cmd, cmdDesc, silentErrors=True, exitOnError=False, exitMsg=None, returnType=ReturnType.json): assert(isinstance(returnType, ReturnType)) @@ -910,6 +883,18 @@ def processCmd(self, cmd, cmdDesc, silentErrors=True, exitOnError=False, exitMsg return trans + def waitForTransBlockIfNeeded(self, trans, waitForTransBlock, exitOnError=False): + if not waitForTransBlock: + return trans + + transId=Node.getTransId(trans) + if not self.waitForTransInBlock(transId): + if exitOnError: + Utils.cmdError("transaction with id %s never made it to a block" % (transId)) + errorExit("Failed to find transaction with id %s in a block before timeout" % (transId)) + return None + return trans + def getInfo(self, silentErrors=False, exitOnError=False): cmdDesc = "get info" return self.processCmd(cmdDesc, cmdDesc, silentErrors=silentErrors, exitOnError=exitOnError) From 0a68052b491cc3fe0ff8355888f8cdce273ebae0 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 27 Jul 2018 15:06:52 -0500 Subject: [PATCH 115/294] Added ShuttingDown flag to prevent errorExit method exiting during shutdown. GH #4903 --- tests/TestHelper.py | 2 ++ tests/testUtils.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/tests/TestHelper.py b/tests/TestHelper.py index 6ecc88fe866..e11a5588747 100644 --- a/tests/TestHelper.py +++ b/tests/TestHelper.py @@ -110,6 +110,8 @@ def shutdown(cluster, walletMgr, testSuccessful=True, killEosInstances=True, kil assert(isinstance(cleanRun, bool)) assert(isinstance(dumpErrorDetails, bool)) + Utils.ShuttingDown=True + if testSuccessful: Utils.Print("Test succeeded.") else: diff --git a/tests/testUtils.py b/tests/testUtils.py index 44f85e0d702..ad77cd20c4c 100755 --- a/tests/testUtils.py +++ b/tests/testUtils.py @@ -23,6 +23,7 @@ class Utils: EosLauncherPath="programs/eosio-launcher/eosio-launcher" MongoPath="mongo" + ShuttingDown=False @staticmethod def Print(*args, **kwargs): @@ -81,6 +82,9 @@ def checkOutput(cmd): @staticmethod def errorExit(msg="", raw=False, errorCode=1): + if Utils.ShuttingDown: + Utils.Print("ERROR:" if not raw else "", " errorExit called during shutdown, ignoring. msg=", msg) + return Utils.Print("ERROR:" if not raw else "", msg) traceback.print_stack(limit=-1) exit(errorCode) From 39359c1f1609497e9c09ad0c1747101e7620a58e Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 27 Jul 2018 15:11:31 -0500 Subject: [PATCH 116/294] Added tracking for head block number and last irreversible block number in node and reporting node status in shutdown. GH #4903 --- tests/Cluster.py | 6 ++++++ tests/Node.py | 28 +++++++++++++++++++++++++--- tests/TestHelper.py | 1 + 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index 8bfba8fd32f..c56a1b05ae1 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -1116,3 +1116,9 @@ def createAccounts(self, creator, waitForTransBlock=True, stakedDeposit=1000): return True + def reportStatus(self): + if hasattr(self, "biosNode") and self.biosNode is not None: + self.biosNode.reportStatus() + if hasattr(self, "nodes"): + for node in self.nodes: + node.reportStatus() diff --git a/tests/Node.py b/tests/Node.py index 944a880ad92..b905b9e62c8 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -39,6 +39,9 @@ def __init__(self, host, port, pid=None, cmd=None, enableMongo=False, mongoHost= self.mongoDb=mongoDb self.endpointArgs="--url http://%s:%d" % (self.host, self.port) self.mongoEndpointArgs="" + self.infoValid=None + self.lastRetrievedHeadBlockNum=None + self.lastRetrievedLIB=None if self.enableMongo: self.mongoEndpointArgs += "--host %s --port %d %s" % (mongoHost, mongoPort, mongoDb) @@ -897,7 +900,14 @@ def waitForTransBlockIfNeeded(self, trans, waitForTransBlock, exitOnError=False) def getInfo(self, silentErrors=False, exitOnError=False): cmdDesc = "get info" - return self.processCmd(cmdDesc, cmdDesc, silentErrors=silentErrors, exitOnError=exitOnError) + info=self.processCmd(cmdDesc, cmdDesc, silentErrors=silentErrors, exitOnError=exitOnError) + if info is None: + self.infoValid=False + else: + self.infoValid=True + self.lastRetrievedHeadBlockNum=int(info["head_block_num"]) + self.lastRetrievedLIB=int(info["last_irreversible_block_num"]) + return info def getBlockFromDb(self, idx): cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) @@ -969,8 +979,8 @@ def myFunc(): self.killed=True return True - def verifyAlive(self): - if Utils.Debug: Utils.Print("Checking if node(pid=%s) is alive(killed=%s): %s" % (self.pid, self.killed, self.cmd)) + def verifyAlive(self, silent=False): + if not silent and Utils.Debug: Utils.Print("Checking if node(pid=%s) is alive(killed=%s): %s" % (self.pid, self.killed, self.cmd)) if self.killed or self.pid is None: return False @@ -1056,3 +1066,15 @@ def isNodeAlive(): self.cmd=cmd self.killed=False return True + + def reportStatus(self): + Utils.Print("Node State:") + Utils.Print(" cmd : %s" % (self.cmd)) + self.verifyAlive(silent=True) + Utils.Print(" killed: %s" % (self.killed)) + Utils.Print(" host : %s" % (self.host)) + Utils.Print(" port : %s" % (self.port)) + Utils.Print(" pid : %s" % (self.pid)) + status="last getInfo returned None" if not self.infoValid else "at last call to getInfo" + Utils.Print(" hbn : %s (%s)" % (self.lastRetrievedHeadBlockNum, status)) + Utils.Print(" lib : %s (%s)" % (self.lastRetrievedLIB, status)) diff --git a/tests/TestHelper.py b/tests/TestHelper.py index e11a5588747..9270c1a75a1 100644 --- a/tests/TestHelper.py +++ b/tests/TestHelper.py @@ -117,6 +117,7 @@ def shutdown(cluster, walletMgr, testSuccessful=True, killEosInstances=True, kil else: Utils.Print("Test failed.") if not testSuccessful and dumpErrorDetails: + cluster.reportStatus() cluster.dumpErrorDetails() if walletMgr: walletMgr.dumpErrorDetails() From c9032cbc0464b68ee27d37805e97e942dd80a44c Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 27 Jul 2018 15:13:10 -0500 Subject: [PATCH 117/294] Added delayedRetry flag to retry getTransaction if first attempt fails. GH #4903 --- tests/Node.py | 22 +++++++++++++++++----- tests/nodeos_run_test.py | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index b905b9e62c8..8f0b0deba60 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -244,16 +244,28 @@ def isBlockFinalized(self, blockNum): return finalized # pylint: disable=too-many-branches - def getTransaction(self, transId, silentErrors=False, exitOnError=False): + def getTransaction(self, transId, silentErrors=False, exitOnError=False, delayedRetry=True): + firstExitOnError=not delayedRetry and exitOnError if not self.enableMongo: cmdDesc="get transaction" cmd="%s %s" % (cmdDesc, transId) msg="(transaction id=%s)" % (transId); + trans=self.processCmd(cmd, cmdDesc, silentErrors=silentErrors, exitOnError=firstExitOnError, exitMsg=msg) + if trans is not None or not delayedRetry: + return trans + + # delay long enough to ensure + if Utils.Debug: Utils.Print("Could not find transaction with id %s, delay and retry" % (transId)) + time.sleep(1) return self.processCmd(cmd, cmdDesc, silentErrors=silentErrors, exitOnError=exitOnError, exitMsg=msg) else: - return self.getTransactionMdb(transId, silentErrors=silentErrors, exitOnError=exitOnError) + trans=self.getTransactionMdb(transId, silentErrors=silentErrors, exitOnError=firstExitOnError) + if trans is not None or not delayedRetry: + return trans - return None + if Utils.Debug: Utils.Print("Could not find transaction with id %s in mongodb, delay and retry" % (transId)) + time.sleep(3) + return self.getTransactionMdb(transId, silentErrors=silentErrors, exitOnError=firstExitOnError) def getTransactionMdb(self, transId, silentErrors=False, exitOnError=False): """Get transaction from MongoDB. Since DB only contains finalized blocks, transactions can take a while to appear in DB.""" @@ -309,11 +321,11 @@ def isTransInBlock(self, transId, blockId): return False - def getBlockIdByTransId(self, transId): + def getBlockIdByTransId(self, transId, delayedRetry=True): """Given a transaction Id (string), will return block id (int) containing the transaction""" assert(transId) assert(isinstance(transId, str)) - trans=self.getTransaction(transId, exitOnError=True) + trans=self.getTransaction(transId, exitOnError=True, delayedRetry=delayedRetry) refBlockNum=None key="" diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index aed7c38e379..5353fba7ae7 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -282,7 +282,7 @@ node.waitForTransInBlock(transId) - transaction=node.getTransaction(transId, exitOnError=True) + transaction=node.getTransaction(transId, exitOnError=True, delayedRetry=False) typeVal=None amountVal=None From 2b19c74d3cbe833820722e40193185331d492451 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 27 Jul 2018 16:33:02 -0500 Subject: [PATCH 118/294] Fix deferred transaction tests --- unittests/api_tests.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 94dbf91c1f8..bc51ac7abec 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -1016,10 +1016,10 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try { auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t->scheduled) { trace = t; } } ); CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {} ); BOOST_CHECK(!trace); - produce_block( fc::seconds(2) ); + produce_block( fc::seconds(3) ); //check that it gets executed afterwards - BOOST_CHECK(trace); + BOOST_REQUIRE(trace); //confirm printed message BOOST_TEST(!trace->action_traces.empty()); @@ -1045,7 +1045,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try { control->push_scheduled_transaction(trx, fc::time_point::maximum()); } BOOST_CHECK_EQUAL(1, count); - BOOST_CHECK(trace); + BOOST_REQUIRE(trace); BOOST_CHECK_EQUAL( 1, trace->action_traces.size() ); c.disconnect(); } @@ -1084,7 +1084,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try { auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t && t->scheduled) { trace = t; } } ); CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {}); CALL_TEST_FUNCTION(*this, "test_transaction", "cancel_deferred_transaction_success", {}); - produce_block( fc::seconds(2) ); + produce_block( fc::seconds(3) ); BOOST_CHECK(!trace); c.disconnect(); } @@ -1098,7 +1098,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try { produce_blocks(10); -{ + { // Trigger a tx which in turn sends a deferred tx with payer != receiver // Payer is alice in this case, this tx should fail since we don't have the authorization of alice dtt_action dtt_act1; From b4e8a95247d8f6f71ba25f21132c7f42d24d8fba Mon Sep 17 00:00:00 2001 From: Vladimir Venediktov Date: Fri, 27 Jul 2018 17:29:24 -0700 Subject: [PATCH 119/294] remove unused --- plugins/chain_plugin/chain_plugin.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 41125e3570b..cc296a6751a 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -28,7 +28,6 @@ #include #include -#include #include namespace eosio { @@ -1031,7 +1030,7 @@ read_only::get_table_rows_result read_only::get_table_rows( const read_only::get }); } else if (p.key_type == "sha256") { - return get_table_rows_by_seckey(p, abi, [&p](const checksum256_type& v)->key256_t { + return get_table_rows_by_seckey(p, abi, [](const checksum256_type& v)->key256_t { key256_t k; k[0] = ((uint128_t *)&v._hash)[0]; k[1] = ((uint128_t *)&v._hash)[1]; From b6bcb08f6440b9890cb5c879e8c5b0229ae95ccb Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Sat, 28 Jul 2018 11:05:48 -0500 Subject: [PATCH 120/294] Use explicit emit instead of scoped_exit. Attempting to fix unittests. --- libraries/chain/controller.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 80879d90c6c..d8af6a0c950 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -563,25 +563,18 @@ struct controller_impl { signed_transaction dtrx; fc::raw::unpack(ds,static_cast(dtrx) ); transaction_metadata_ptr trx = std::make_shared( dtrx ); + trx->accepted = true; + trx->scheduled = true; transaction_trace_ptr trace; - auto emit_on_exit = fc::make_scoped_exit( [&trace, &trx, this]() { - if( trx ) { - trx->accepted = true; - trx->scheduled = true; - emit( self.accepted_transaction, trx ); - } - if( trace ) { - emit( self.applied_transaction, trace ); - } - } ); - if( gtrx.expiration < self.pending_block_time() ) { trace = std::make_shared(); trace->id = gtrx.trx_id; trace->scheduled = true; trace->receipt = push_receipt( gtrx.trx_id, transaction_receipt::expired, billed_cpu_time_us, 0 ); // expire the transaction undo_session.squash(); + emit( self.accepted_transaction, trx ); + emit( self.applied_transaction, trace ); return trace; } @@ -612,7 +605,11 @@ struct controller_impl { trx_context.squash(); undo_session.squash(); + restore.cancel(); + + emit( self.accepted_transaction, trx ); + emit( self.applied_transaction, trace ); return trace; } catch( const fc::exception& e ) { trace->except = e; @@ -631,6 +628,8 @@ struct controller_impl { trace = error_trace; if( !trace->except_ptr ) { undo_session.squash(); + emit( self.accepted_transaction, trx ); + emit( self.applied_transaction, trace ); return trace; } } @@ -647,6 +646,8 @@ struct controller_impl { undo_session.squash(); } + emit( self.accepted_transaction, trx ); + emit( self.applied_transaction, trace ); return trace; } FC_CAPTURE_AND_RETHROW() } /// push_scheduled_transaction From 28cc7b5e13ceaf2939b93c366a11d17c2b81768e Mon Sep 17 00:00:00 2001 From: Scott Sallinen Date: Sat, 28 Jul 2018 11:23:00 -0700 Subject: [PATCH 121/294] More filtering options --- plugins/history_plugin/history_plugin.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/plugins/history_plugin/history_plugin.cpp b/plugins/history_plugin/history_plugin.cpp index 49de13d29c7..673a6ec4580 100644 --- a/plugins/history_plugin/history_plugin.cpp +++ b/plugins/history_plugin/history_plugin.cpp @@ -148,10 +148,16 @@ namespace eosio { if (bypass_filter) { pass_on = true; } + if (filter_on.find({ act.receipt.receiver, 0, 0 }) != filter_on.end()) { + pass_on = true; + } if (filter_on.find({ act.receipt.receiver, act.act.name, 0 }) != filter_on.end()) { pass_on = true; } for (const auto& a : act.act.authorization) { + if (filter_on.find({ act.receipt.receiver, 0, a.actor }) != filter_on.end()) { + pass_on = true; + } if (filter_on.find({ act.receipt.receiver, act.act.name, a.actor }) != filter_on.end()) { pass_on = true; } @@ -166,6 +172,9 @@ namespace eosio { return false; } for (const auto& a : act.act.authorization) { + if (filter_out.find({ act.receipt.receiver, 0, a.actor }) != filter_out.end()) { + return false; + } if (filter_out.find({ act.receipt.receiver, act.act.name, a.actor }) != filter_out.end()) { return false; } @@ -180,9 +189,12 @@ namespace eosio { result.insert( act.receipt.receiver ); for( const auto& a : act.act.authorization ) { if( bypass_filter || + filter_on.find({ act.receipt.receiver, 0, 0}) != filter_on.end() || + filter_on.find({ act.receipt.receiver, 0, a.actor}) != filter_on.end() || filter_on.find({ act.receipt.receiver, act.act.name, 0}) != filter_on.end() || filter_on.find({ act.receipt.receiver, act.act.name, a.actor }) != filter_on.end() ) { if ((filter_out.find({ act.receipt.receiver, 0, 0 }) == filter_out.end()) && + (filter_out.find({ act.receipt.receiver, 0, a.actor }) == filter_out.end()) && (filter_out.find({ act.receipt.receiver, act.act.name, 0 }) == filter_out.end()) && (filter_out.find({ act.receipt.receiver, act.act.name, a.actor }) == filter_out.end())) { result.insert( a.actor ); @@ -288,11 +300,11 @@ namespace eosio { void history_plugin::set_program_options(options_description& cli, options_description& cfg) { cfg.add_options() ("filter-on,f", bpo::value>()->composing(), - "Track actions which match receiver:action:actor. Actor may be blank to include all. Receiver and Action may not be blank.") + "Track actions which match receiver:action:actor. Actor may be blank to include all. Action and Actor both blank allows all from Recieiver. Receiver may not be blank.") ; cfg.add_options() ("filter-out,f", bpo::value>()->composing(), - "Do not track actions which match receiver:action:actor. Action and Actor both blank excludes all from reciever. Actor blank excludes all from reciever:action. Receiver may not be blank.") + "Do not track actions which match receiver:action:actor. Action and Actor both blank excludes all from Reciever. Actor blank excludes all from reciever:action. Receiver may not be blank.") ; } @@ -310,7 +322,7 @@ namespace eosio { boost::split( v, s, boost::is_any_of( ":" )); EOS_ASSERT( v.size() == 3, fc::invalid_arg_exception, "Invalid value ${s} for --filter-on", ("s", s)); filter_entry fe{v[0], v[1], v[2]}; - EOS_ASSERT( fe.receiver.value && fe.action.value, fc::invalid_arg_exception, + EOS_ASSERT( fe.receiver.value, fc::invalid_arg_exception, "Invalid value ${s} for --filter-on", ("s", s)); my->filter_on.insert( fe ); } From 874205c6b0de5b37b750751dfe79c52666beb6b9 Mon Sep 17 00:00:00 2001 From: wlbf Date: Mon, 30 Jul 2018 01:27:54 +0800 Subject: [PATCH 122/294] improve `is_valid_movement` function in tic.tac.toe example --- contracts/tic_tac_toe/tic_tac_toe.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/tic_tac_toe/tic_tac_toe.cpp b/contracts/tic_tac_toe/tic_tac_toe.cpp index 0cff793da45..cdbdc3d098d 100644 --- a/contracts/tic_tac_toe/tic_tac_toe.cpp +++ b/contracts/tic_tac_toe/tic_tac_toe.cpp @@ -24,8 +24,10 @@ bool is_empty_cell(const uint8_t& cell) { * @return true if movement is valid */ bool is_valid_movement(const uint16_t& row, const uint16_t& column, const vector& board) { - uint32_t movement_location = row * tic_tac_toe::game::board_width + column; - bool is_valid = movement_location < board.size() && is_empty_cell(board[movement_location]); + uint16_t board_width = tic_tac_toe::game::board_width; + uint16_t board_height = tic_tac_toe::game::board_height; + uint32_t movement_location = row * board_width + column; + bool is_valid = column < board_width && row < board_height && is_empty_cell(board[movement_location]); return is_valid; } From b6b4dde21b56a3dbcbbb396dadc04bc85ca67a2b Mon Sep 17 00:00:00 2001 From: Andrianto Lie Date: Fri, 27 Jul 2018 11:41:07 +0800 Subject: [PATCH 123/294] Add cleos command to get transaction id given transaction --- programs/cleos/main.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 42bd2b112a4..bb0826ddbb9 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -1095,6 +1095,23 @@ struct get_schedule_subcommand { } }; +struct get_transaction_id_subcommand { + string trx_to_check; + + get_transaction_id_subcommand(CLI::App* actionRoot) { + auto get_transaction_id = actionRoot->add_subcommand("transaction_id", localized("Get transaction id given transaction object")); + get_transaction_id->add_option("transaction", trx_to_check, localized("The JSON string or filename defining the transaction which transaction id we want to retrieve"))->required(); + + get_transaction_id->set_callback([&] { + try { + auto trx_var = json_from_file_or_string(trx_to_check); + auto trx = trx_var.as(); + std::cout << string(trx.id()) << std::endl; + } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse transaction JSON '${data}'", ("data",trx_to_check)) + }); + } +}; + struct delegate_bandwidth_subcommand { string from_str; string receiver_str; @@ -2030,7 +2047,8 @@ int main( int argc, char** argv ) { }); auto getSchedule = get_schedule_subcommand{get}; - + auto getTransactionId = get_transaction_id_subcommand{get}; + /* auto getTransactions = get->add_subcommand("transactions", localized("Retrieve all transactions with specific account name referenced in their scope"), false); getTransactions->add_option("account_name", account_name, localized("name of account to query on"))->required(); From bbb7b0f74ac3feb595c053c936cb1bb1f817e84b Mon Sep 17 00:00:00 2001 From: Andrianto Lie Date: Fri, 27 Jul 2018 14:08:19 +0800 Subject: [PATCH 124/294] Add API to get transaction id --- plugins/chain_api_plugin/chain_api_plugin.cpp | 1 + plugins/chain_plugin/chain_plugin.cpp | 4 ++++ .../include/eosio/chain_plugin/chain_plugin.hpp | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/plugins/chain_api_plugin/chain_api_plugin.cpp b/plugins/chain_api_plugin/chain_api_plugin.cpp index d6ec6613dbc..10537b2ba40 100644 --- a/plugins/chain_api_plugin/chain_api_plugin.cpp +++ b/plugins/chain_api_plugin/chain_api_plugin.cpp @@ -94,6 +94,7 @@ void chain_api_plugin::plugin_startup() { CHAIN_RO_CALL(abi_json_to_bin, 200), CHAIN_RO_CALL(abi_bin_to_json, 200), CHAIN_RO_CALL(get_required_keys, 200), + CHAIN_RO_CALL(get_transaction_id, 200), CHAIN_RW_CALL_ASYNC(push_block, chain_apis::read_write::push_block_results, 202), CHAIN_RW_CALL_ASYNC(push_transaction, chain_apis::read_write::push_transaction_results, 202), CHAIN_RW_CALL_ASYNC(push_transactions, chain_apis::read_write::push_transactions_results, 202) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 894ee3986f0..b6d225c4e47 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1600,6 +1600,10 @@ read_only::get_required_keys_result read_only::get_required_keys( const get_requ return result; } +read_only::get_transaction_id_result read_only::get_transaction_id( const read_only::get_transaction_id_params& params)const { + return params.id(); +} + } // namespace chain_apis } // namespace eosio diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index 2b18852974f..29b8aa8e63b 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -29,6 +29,8 @@ namespace eosio { using chain::name; using chain::uint128_t; using chain::public_key_type; + using chain::transaction; + using chain::transaction_id_type; using fc::optional; using boost::container::flat_set; using chain::asset; @@ -205,6 +207,10 @@ class read_only { get_required_keys_result get_required_keys( const get_required_keys_params& params)const; + using get_transaction_id_params = transaction; + using get_transaction_id_result = transaction_id_type; + + get_transaction_id_result get_transaction_id( const get_transaction_id_params& params)const; struct get_block_params { string block_num_or_id; From acb3bde3f3b970f83827d816d297dc3c9207b089 Mon Sep 17 00:00:00 2001 From: Andrianto Lie Date: Mon, 30 Jul 2018 18:00:17 +0800 Subject: [PATCH 125/294] Fix cleos transfer with contract beside eosio.token --- programs/cleos/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 42bd2b112a4..efc3ffb520a 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2216,7 +2216,7 @@ int main( int argc, char** argv ) { tx_force_unique = false; } - send_actions({create_transfer(con,sender, recipient, to_asset(amount), memo)}); + send_actions({create_transfer(con, sender, recipient, to_asset(con, amount), memo)}); }); // Net subcommand From 9449dfd2cbcce180891cb7c980b0a1403cdcc945 Mon Sep 17 00:00:00 2001 From: Denis Samokhvalov Date: Tue, 31 Jul 2018 16:26:55 +0300 Subject: [PATCH 126/294] Fixed required dependencies display on mac --- scripts/eosio_build_darwin.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/eosio_build_darwin.sh b/scripts/eosio_build_darwin.sh index 166671cf526..b851fb8e384 100644 --- a/scripts/eosio_build_darwin.sh +++ b/scripts/eosio_build_darwin.sh @@ -136,7 +136,7 @@ if [ $COUNT -gt 1 ]; then printf "\\n\\tThe following dependencies are required to install EOSIO.\\n" - printf "\\n\\t%s\\n\\n" "${DISPLAY}" + printf "\\n\\t${DISPLAY}\\n\\n" echo "Do you wish to install these packages?" select yn in "Yes" "No"; do case $yn in From 9bd48378ea9f445222ea518a68af9a6dd60b7afa Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Tue, 31 Jul 2018 17:24:29 -0500 Subject: [PATCH 127/294] No longer link wallet into nodeos. --- programs/nodeos/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 1ab5850ef9b..16f459844c3 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -50,7 +50,6 @@ target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} bnet_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} history_api_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} chain_api_plugin -Wl,${no_whole_archive_flag} - PRIVATE -Wl,${whole_archive_flag} wallet_api_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} net_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} net_api_plugin -Wl,${no_whole_archive_flag} # PRIVATE -Wl,${whole_archive_flag} faucet_testnet_plugin -Wl,${no_whole_archive_flag} @@ -96,4 +95,4 @@ install(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/eosio WORLD_EXECUTE ) -mas_sign(nodeos) \ No newline at end of file +mas_sign(nodeos) From 090ce829aebfe5efe32599130913a956b7e24d8e Mon Sep 17 00:00:00 2001 From: Andrianto Lie Date: Wed, 1 Aug 2018 15:44:00 +0800 Subject: [PATCH 128/294] Free allocated memory in unpack_action_data --- contracts/eosiolib/action.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/eosiolib/action.hpp b/contracts/eosiolib/action.hpp index e7bde04ccd4..ec1d83fb447 100644 --- a/contracts/eosiolib/action.hpp +++ b/contracts/eosiolib/action.hpp @@ -50,7 +50,11 @@ namespace eosio { size_t size = action_data_size(); char* buffer = (char*)( max_stack_buffer_size < size ? malloc(size) : alloca(size) ); read_action_data( buffer, size ); - return unpack( buffer, size ); + auto res = unpack( buffer, size ); + if ( max_stack_buffer_size < size ) { + free(buffer); + } + return res; } using ::require_auth; From 48b014e99a056961dcabb5065db008d9e256694b Mon Sep 17 00:00:00 2001 From: Andrianto Lie Date: Thu, 2 Aug 2018 09:51:28 +0800 Subject: [PATCH 129/294] Improves clarity in the code --- contracts/eosiolib/action.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/eosiolib/action.hpp b/contracts/eosiolib/action.hpp index ec1d83fb447..11028f09e93 100644 --- a/contracts/eosiolib/action.hpp +++ b/contracts/eosiolib/action.hpp @@ -48,10 +48,12 @@ namespace eosio { T unpack_action_data() { constexpr size_t max_stack_buffer_size = 512; size_t size = action_data_size(); - char* buffer = (char*)( max_stack_buffer_size < size ? malloc(size) : alloca(size) ); + const bool heap_allocation = max_stack_buffer_size < size; + char* buffer = (char*)( heap_allocation ? malloc(size) : alloca(size) ); read_action_data( buffer, size ); auto res = unpack( buffer, size ); - if ( max_stack_buffer_size < size ) { + // Free allocated memory + if ( heap_allocation ) { free(buffer); } return res; From 861f704681b6e6909b5f98ff621debd05fb252ed Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Thu, 2 Aug 2018 18:28:51 -0500 Subject: [PATCH 130/294] More flag in get table wrong when limit equals rows Advance itr when limit hit so 'more' flag can be calculated correctly. --- plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index 2b18852974f..dcd6ca1c1d4 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -445,6 +445,7 @@ class read_only { } if (++count == p.limit || fc::time_point::now() > end) { + ++itr; break; } } From 4427fb9e67878fdc1306c886e1dcc68f8dc66595 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Thu, 2 Aug 2018 22:33:06 -0400 Subject: [PATCH 131/294] (appbase sync) Provide a version string based off "git describe" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Appbase will now provide a version string that is based off "git describe --tags --dirty". This provides a readable version number like v1.1.0 as well as information on intermediate git revisions and/or dirtiness. Since we don’t provide eosio as a .tar.gz package due to Github’s lack of submodules on tarballing, generally all users should be building in a git clone. Be aware this change will run git describe on EVERY build. The command is quite fast (around 100ms even on a slower 2017 MacBook, but sub 30ms on a quality Linux dev box) and will hopefully provide enough benefit to be worth it. --- externals/binaryen | 2 +- libraries/appbase | 2 +- programs/nodeos/main.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/externals/binaryen b/externals/binaryen index 82a2cccb99b..16d641f62ab 160000 --- a/externals/binaryen +++ b/externals/binaryen @@ -1 +1 @@ -Subproject commit 82a2cccb99b6f6b159a066707e5f58c4311914b9 +Subproject commit 16d641f62ab14df845c87a63efe4d991b508d19a diff --git a/libraries/appbase b/libraries/appbase index 44d7d88e561..fa0e7fd9aa8 160000 --- a/libraries/appbase +++ b/libraries/appbase @@ -1 +1 @@ -Subproject commit 44d7d88e561cc9708bcac9453686c8d0f0ffa41e +Subproject commit fa0e7fd9aa8be6ddc0c2f620cae63e58fefafab2 diff --git a/programs/nodeos/main.cpp b/programs/nodeos/main.cpp index 9fc2d620e63..02c55ae9ff5 100644 --- a/programs/nodeos/main.cpp +++ b/programs/nodeos/main.cpp @@ -102,7 +102,7 @@ int main(int argc, char** argv) if(!app().initialize(argc, argv)) return INITIALIZE_FAIL; initialize_logging(); - ilog("nodeos version ${ver}", ("ver", eosio::utilities::common::itoh(static_cast(app().version())))); + ilog("nodeos version ${ver}", ("ver", app().version_string())); ilog("eosio root is ${root}", ("root", root.string())); app().startup(); app().exec(); From 5e57797f86b7c00e7ac252694511a21f5960534a Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Fri, 3 Aug 2018 12:50:30 +0200 Subject: [PATCH 132/294] removed sql_plugin --- CMakeModules/FindSoci.cmake | 97 ------- plugins/CMakeLists.txt | 1 - plugins/sql_db_plugin/CMakeLists.txt | 27 -- plugins/sql_db_plugin/consumer.h | 74 ------ plugins/sql_db_plugin/consumer_core.h | 21 -- plugins/sql_db_plugin/db/accounts_table.cpp | 57 ----- plugins/sql_db_plugin/db/accounts_table.h | 27 -- plugins/sql_db_plugin/db/actions_table.cpp | 237 ------------------ plugins/sql_db_plugin/db/actions_table.h | 43 ---- plugins/sql_db_plugin/db/blocks_table.cpp | 77 ------ plugins/sql_db_plugin/db/blocks_table.h | 31 --- plugins/sql_db_plugin/db/database.cpp | 74 ------ plugins/sql_db_plugin/db/database.h | 44 ---- .../sql_db_plugin/db/transactions_table.cpp | 58 ----- plugins/sql_db_plugin/db/transactions_table.h | 25 -- plugins/sql_db_plugin/fifo.h | 74 ------ .../eosio/sql_db_plugin/sql_db_plugin.hpp | 55 ---- plugins/sql_db_plugin/sql_db_plugin.cpp | 93 ------- plugins/sql_db_plugin/test/CMakeLists.txt | 12 - plugins/sql_db_plugin/test/consumer_test.cpp | 27 -- plugins/sql_db_plugin/test/fifo_test.cpp | 39 --- plugins/sql_db_plugin/test/test.cpp | 4 - programs/nodeos/CMakeLists.txt | 6 +- 23 files changed, 1 insertion(+), 1202 deletions(-) delete mode 100644 CMakeModules/FindSoci.cmake delete mode 100644 plugins/sql_db_plugin/CMakeLists.txt delete mode 100644 plugins/sql_db_plugin/consumer.h delete mode 100644 plugins/sql_db_plugin/consumer_core.h delete mode 100644 plugins/sql_db_plugin/db/accounts_table.cpp delete mode 100644 plugins/sql_db_plugin/db/accounts_table.h delete mode 100644 plugins/sql_db_plugin/db/actions_table.cpp delete mode 100644 plugins/sql_db_plugin/db/actions_table.h delete mode 100644 plugins/sql_db_plugin/db/blocks_table.cpp delete mode 100644 plugins/sql_db_plugin/db/blocks_table.h delete mode 100644 plugins/sql_db_plugin/db/database.cpp delete mode 100644 plugins/sql_db_plugin/db/database.h delete mode 100644 plugins/sql_db_plugin/db/transactions_table.cpp delete mode 100644 plugins/sql_db_plugin/db/transactions_table.h delete mode 100644 plugins/sql_db_plugin/fifo.h delete mode 100644 plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp delete mode 100644 plugins/sql_db_plugin/sql_db_plugin.cpp delete mode 100644 plugins/sql_db_plugin/test/CMakeLists.txt delete mode 100644 plugins/sql_db_plugin/test/consumer_test.cpp delete mode 100644 plugins/sql_db_plugin/test/fifo_test.cpp delete mode 100644 plugins/sql_db_plugin/test/test.cpp diff --git a/CMakeModules/FindSoci.cmake b/CMakeModules/FindSoci.cmake deleted file mode 100644 index 9a8c87a087c..00000000000 --- a/CMakeModules/FindSoci.cmake +++ /dev/null @@ -1,97 +0,0 @@ -############################################################################### -# CMake module to search for SOCI library -# -# WARNING: This module is experimental work in progress. -# -# This module defines: -# SOCI_INCLUDE_DIRS = include dirs to be used when using the soci library -# SOCI_LIBRARY = full path to the soci library -# SOCI_VERSION = the soci version found (not yet. soci does not provide that info.) -# SOCI_FOUND = true if soci was found -# -# This module respects: -# LIB_SUFFIX = (64|32|"") Specifies the suffix for the lib directory -# -# For each component you specify in find_package(), the following variables are set. -# -# SOCI_${COMPONENT}_PLUGIN = full path to the soci plugin -# SOCI_${COMPONENT}_FOUND -# -# Copyright (c) 2011 Michael Jansen -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. -# -############################################################################### -# -### Global Configuration Section -# -SET(_SOCI_ALL_PLUGINS mysql odbc postgresql sqlite3) -SET(_SOCI_REQUIRED_VARS SOCI_INCLUDE_DIR SOCI_LIBRARY) - -# -### FIRST STEP: Find the soci headers. -# -FIND_PATH( - SOCI_INCLUDE_DIR soci.h - PATH "/usr/local" - PATH_SUFFIXES "" "soci" - DOC "Soci (http://soci.sourceforge.net) include directory") -MARK_AS_ADVANCED(SOCI_INCLUDE_DIR) - -SET(SOCI_INCLUDE_DIRS ${SOCI_INCLUDE_DIR}) - -# -### SECOND STEP: Find the soci core library. Respect LIB_SUFFIX -# -FIND_LIBRARY( - SOCI_LIBRARY - NAMES soci_core - HINTS ${SOCI_INCLUDE_DIR}/.. - PATH_SUFFIXES lib${LIB_SUFFIX}) -MARK_AS_ADVANCED(SOCI_LIBRARY) - -GET_FILENAME_COMPONENT(SOCI_LIBRARY_DIR ${SOCI_LIBRARY} PATH) -MARK_AS_ADVANCED(SOCI_LIBRARY_DIR) - -# -### THIRD STEP: Find all installed plugins if the library was found -# -IF(SOCI_INCLUDE_DIR AND SOCI_LIBRARY) - - MESSAGE(STATUS "Soci found: Looking for plugins") - FOREACH(plugin IN LISTS _SOCI_ALL_PLUGINS) - - FIND_LIBRARY( - SOCI_${plugin}_PLUGIN - NAMES soci_${plugin} - HINTS ${SOCI_INCLUDE_DIR}/.. - PATH_SUFFIXES lib${LIB_SUFFIX}) - MARK_AS_ADVANCED(SOCI_${plugin}_PLUGIN) - - IF(SOCI_${plugin}_PLUGIN) - MESSAGE(STATUS " * Plugin ${plugin} found ${SOCI_${plugin}_PLUGIN}.") - SET(SOCI_${plugin}_FOUND True) - ELSE() - MESSAGE(STATUS " * Plugin ${plugin} not found.") - SET(SOCI_${plugin}_FOUND False) - ENDIF() - - ENDFOREACH() - - # - ### FOURTH CHECK: Check if the required components were all found - # - FOREACH(component ${Soci_FIND_COMPONENTS}) - IF(NOT SOCI_${component}_FOUND) - MESSAGE(SEND_ERROR "Required component ${component} not found.") - ENDIF() - ENDFOREACH() - -ENDIF() - -# -### ADHERE TO STANDARDS -# -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Soci DEFAULT_MSG ${_SOCI_REQUIRED_VARS}) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index be5bcaa644d..c9b65904499 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -17,7 +17,6 @@ add_subdirectory(txn_test_gen_plugin) add_subdirectory(db_size_api_plugin) #add_subdirectory(faucet_testnet_plugin) add_subdirectory(mongo_db_plugin) -add_subdirectory(sql_db_plugin) add_subdirectory(login_plugin) # Forward variables to top level so packaging picks them up diff --git a/plugins/sql_db_plugin/CMakeLists.txt b/plugins/sql_db_plugin/CMakeLists.txt deleted file mode 100644 index c43ca7bd24e..00000000000 --- a/plugins/sql_db_plugin/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -find_package(Soci) -if(NOT SOCI_FOUND) - message(STATUS "Database SQL plugin: disabled") - return() -endif() - -message(STATUS "Database SQL plugin: enabled") - -include_directories(${CMAKE_CURRENT_SOURCE_DIR} include db) - -add_library(sql_db_plugin - db/database.cpp - db/accounts_table.cpp - db/transactions_table.cpp - db/blocks_table.cpp - db/actions_table.cpp - sql_db_plugin.cpp - ) - -target_link_libraries(sql_db_plugin - chain_plugin - eosio_chain - ${SOCI_LIBRARY} - ) - -add_subdirectory(test) - diff --git a/plugins/sql_db_plugin/consumer.h b/plugins/sql_db_plugin/consumer.h deleted file mode 100644 index b23fc48f775..00000000000 --- a/plugins/sql_db_plugin/consumer.h +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "consumer_core.h" -#include "fifo.h" - -namespace eosio { - -template -class consumer final : public boost::noncopyable -{ -public: - consumer(std::unique_ptr> core); - ~consumer(); - - void push(const T& element); - -private: - void run(); - - fifo m_fifo; - std::unique_ptr> m_core; - std::atomic m_exit; - std::unique_ptr m_thread; -}; - -template -consumer::consumer(std::unique_ptr > core): - m_fifo(fifo::behavior::blocking), - m_core(std::move(core)), - m_exit(false), - m_thread(std::make_unique([&]{this->run();})) -{ - -} - -template -consumer::~consumer() -{ - m_fifo.set_behavior(fifo::behavior::not_blocking); - m_exit = true; - m_thread->join(); -} - -template -void consumer::push(const T& element) -{ - m_fifo.push(element); -} - -template -void consumer::run() -{ - dlog("Consumer thread Start"); - while (!m_exit) - { - auto elements = m_fifo.pop_all(); - m_core->consume(elements); - } - dlog("Consumer thread End"); -} - -} // namespace - diff --git a/plugins/sql_db_plugin/consumer_core.h b/plugins/sql_db_plugin/consumer_core.h deleted file mode 100644 index 7ec164c1084..00000000000 --- a/plugins/sql_db_plugin/consumer_core.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ - -#pragma once - -#include - -namespace eosio { - -template -class consumer_core { -public: - virtual ~consumer_core() {} - virtual void consume(const std::vector& elements) = 0; -}; - -} // namespace - - diff --git a/plugins/sql_db_plugin/db/accounts_table.cpp b/plugins/sql_db_plugin/db/accounts_table.cpp deleted file mode 100644 index 48bc0e6511d..00000000000 --- a/plugins/sql_db_plugin/db/accounts_table.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "accounts_table.h" - -#include - -namespace eosio { - -accounts_table::accounts_table(std::shared_ptr session): - m_session(session) -{ - -} - -void accounts_table::drop() -{ - try { - *m_session << "DROP TABLE IF EXISTS accounts_keys"; - *m_session << "DROP TABLE IF EXISTS accounts"; - } - catch(std::exception& e){ - wlog(e.what()); - } -} - -void accounts_table::create() -{ - *m_session << "CREATE TABLE accounts(" - "name VARCHAR(12) PRIMARY KEY," - "abi JSON DEFAULT NULL," - "created_at DATETIME DEFAULT NOW()," - "updated_at DATETIME DEFAULT NOW()) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; - - *m_session << "CREATE TABLE accounts_keys(" - "account VARCHAR(12)," - "public_key VARCHAR(53)," - "permission VARCHAR(12), FOREIGN KEY (account) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; -} - -void accounts_table::add(string name) -{ - *m_session << "INSERT INTO accounts (name) VALUES (:name)", - soci::use(name); -} - -bool accounts_table::exist(string name) -{ - int amount; - try { - *m_session << "SELECT COUNT(*) FROM accounts WHERE name = :name", soci::into(amount), soci::use(name); - } - catch (std::exception const & e) - { - amount = 0; - } - return amount > 0; -} - -} // namespace diff --git a/plugins/sql_db_plugin/db/accounts_table.h b/plugins/sql_db_plugin/db/accounts_table.h deleted file mode 100644 index 7d3aab18c72..00000000000 --- a/plugins/sql_db_plugin/db/accounts_table.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef ACCOUNTS_TABLE_H -#define ACCOUNTS_TABLE_H - -#include -#include - -namespace eosio { - -using std::string; - -class accounts_table -{ -public: - accounts_table(std::shared_ptr session); - - void drop(); - void create(); - void add(string name); - bool exist(string name); - -private: - std::shared_ptr m_session; -}; - -} // namespace - -#endif // ACCOUNTS_TABLE_H diff --git a/plugins/sql_db_plugin/db/actions_table.cpp b/plugins/sql_db_plugin/db/actions_table.cpp deleted file mode 100644 index aac5e9442f3..00000000000 --- a/plugins/sql_db_plugin/db/actions_table.cpp +++ /dev/null @@ -1,237 +0,0 @@ -#include "actions_table.h" - -namespace eosio { - -actions_table::actions_table(std::shared_ptr session): - m_session(session) -{ - -} - -void actions_table::drop() -{ - try { - *m_session << "drop table IF EXISTS actions_accounts"; - *m_session << "drop table IF EXISTS stakes"; - *m_session << "drop table IF EXISTS votes"; - *m_session << "drop table IF EXISTS tokens"; - *m_session << "drop table IF EXISTS actions"; - } - catch(std::exception& e){ - wlog(e.what()); - } -} - -void actions_table::create() -{ - *m_session << "CREATE TABLE actions(" - "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY," - "account VARCHAR(12)," - "transaction_id VARCHAR(64)," - "seq SMALLINT," - "parent INT DEFAULT NULL," - "name VARCHAR(12)," - "created_at DATETIME DEFAULT NOW()," - "data JSON, FOREIGN KEY (transaction_id) REFERENCES transactions(id) ON DELETE CASCADE," - "FOREIGN KEY (account) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; - - *m_session << "CREATE INDEX idx_actions_account ON actions (account);"; - *m_session << "CREATE INDEX idx_actions_tx_id ON actions (transaction_id);"; - *m_session << "CREATE INDEX idx_actions_created ON actions (created_at);"; - - *m_session << "CREATE TABLE actions_accounts(" - "actor VARCHAR(12)," - "permission VARCHAR(12)," - "action_id INT NOT NULL, FOREIGN KEY (action_id) REFERENCES actions(id) ON DELETE CASCADE," - "FOREIGN KEY (actor) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; - - *m_session << "CREATE INDEX idx_actions_actor ON actions_accounts (actor);"; - *m_session << "CREATE INDEX idx_actions_action_id ON actions_accounts (action_id);"; - - *m_session << "CREATE TABLE tokens(" - "account VARCHAR(13)," - "symbol VARCHAR(10)," - "amount DOUBLE(64,4)," - "FOREIGN KEY (account) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; // TODO: other tokens could have diff format. - - *m_session << "CREATE INDEX idx_tokens_account ON tokens (account);"; - - *m_session << "CREATE TABLE votes(" - "account VARCHAR(13) PRIMARY KEY," - "votes JSON" - ", FOREIGN KEY (account) REFERENCES accounts(name), UNIQUE KEY account (account)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; - - *m_session << "CREATE TABLE stakes(" - "account VARCHAR(13) PRIMARY KEY," - "cpu REAL(14,4)," - "net REAL(14,4)," - "FOREIGN KEY (account) REFERENCES accounts(name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; -} - -void actions_table::add(chain::action action, chain::transaction_id_type transaction_id, fc::time_point_sec transaction_time, uint8_t seq) -{ - - chain::abi_def abi; - std::string abi_def_account; - chain::abi_serializer abis; - soci::indicator ind; - const auto transaction_id_str = transaction_id.str(); - const auto expiration = boost::chrono::seconds{transaction_time.sec_since_epoch()}.count(); - - *m_session << "SELECT abi FROM accounts WHERE name = :name", soci::into(abi_def_account, ind), soci::use(action.account.to_string()); - - if (!abi_def_account.empty()) { - abi = fc::json::from_string(abi_def_account).as(); - } else if (action.account == chain::config::system_account_name) { - abi = chain::eosio_contract_abi(abi); - } else { - return; // no ABI no party. Should we still store it? - } - - static const fc::microseconds abi_serializer_max_time(1000000); // 1 second - abis.set_abi(abi, abi_serializer_max_time); - - auto abi_data = abis.binary_to_variant(abis.get_action_type(action.name), action.data, abi_serializer_max_time); - string json = fc::json::to_string(abi_data); - - boost::uuids::random_generator gen; - boost::uuids::uuid id = gen(); - std::string action_id = boost::uuids::to_string(id); - - *m_session << "INSERT INTO actions(account, seq, created_at, name, data, transaction_id) VALUES (:ac, :se, FROM_UNIXTIME(:ca), :na, :da, :ti) ", - soci::use(action.account.to_string()), - soci::use(seq), - soci::use(expiration), - soci::use(action.name.to_string()), - soci::use(json), - soci::use(transaction_id_str); - - for (const auto& auth : action.authorization) { - *m_session << "INSERT INTO actions_accounts(action_id, actor, permission) VALUES (LAST_INSERT_ID(), :ac, :pe) ", - soci::use(auth.actor.to_string()), - soci::use(auth.permission.to_string()); - } - try { - parse_actions(action, abi_data); - } catch(std::exception& e){ - wlog(e.what()); - } -} - -void actions_table::parse_actions(chain::action action, fc::variant abi_data) -{ - // TODO: move all + catch // public keys update // stake / voting - if (action.name == N(issue)) { - - auto to_name = abi_data["to"].as().to_string(); - auto asset_quantity = abi_data["quantity"].as(); - int exist; - - *m_session << "SELECT COUNT(*) FROM tokens WHERE account = :ac AND symbol = :sy", - soci::into(exist), - soci::use(to_name), - soci::use(asset_quantity.get_symbol().name()); - if (exist > 0) { - *m_session << "UPDATE tokens SET amount = amount + :am WHERE account = :ac AND symbol = :sy", - soci::use(asset_quantity.to_real()), - soci::use(to_name), - soci::use(asset_quantity.get_symbol().name()); - } else { - *m_session << "INSERT INTO tokens(account, amount, symbol) VALUES (:ac, :am, :as) ", - soci::use(to_name), - soci::use(asset_quantity.to_real()), - soci::use(asset_quantity.get_symbol().name()); - } - } - - if (action.name == N(transfer)) { - - auto from_name = abi_data["from"].as().to_string(); - auto to_name = abi_data["to"].as().to_string(); - auto asset_quantity = abi_data["quantity"].as(); - int exist; - - *m_session << "SELECT COUNT(*) FROM tokens WHERE account = :ac AND symbol = :sy", - soci::into(exist), - soci::use(to_name), - soci::use(asset_quantity.get_symbol().name()); - if (exist > 0) { - *m_session << "UPDATE tokens SET amount = amount + :am WHERE account = :ac AND symbol = :sy", - soci::use(asset_quantity.to_real()), - soci::use(to_name), - soci::use(asset_quantity.get_symbol().name()); - } else { - *m_session << "INSERT INTO tokens(account, amount, symbol) VALUES (:ac, :am, :as) ", - soci::use(to_name), - soci::use(asset_quantity.to_real()), - soci::use(asset_quantity.get_symbol().name()); - } - - *m_session << "UPDATE tokens SET amount = amount - :am WHERE account = :ac AND symbol = :sy", - soci::use(asset_quantity.to_real()), - soci::use(from_name), - soci::use(asset_quantity.get_symbol().name()); - } - - if (action.account != chain::name(chain::config::system_account_name)) { - return; - } - - if (action.name == N(voteproducer)) { - auto voter = abi_data["voter"].as().to_string(); - string votes = fc::json::to_string(abi_data["producers"]); - - *m_session << "REPLACE INTO votes(account, votes) VALUES (:ac, :vo) ", - soci::use(voter), - soci::use(votes); - } - - - if (action.name == N(delegatebw)) { - auto account = abi_data["receiver"].as().to_string(); - auto cpu = abi_data["stake_cpu_quantity"].as(); - auto net = abi_data["stake_net_quantity"].as(); - - *m_session << "REPLACE INTO stakes(account, cpu, net) VALUES (:ac, :cp, :ne) ", - soci::use(account), - soci::use(cpu.to_real()), - soci::use(net.to_real()); - } - - if (action.name == chain::setabi::get_name()) { - chain::abi_def abi_setabi; - chain::setabi action_data = action.data_as(); - chain::abi_serializer::to_abi(action_data.abi, abi_setabi); - string abi_string = fc::json::to_string(abi_setabi); - - *m_session << "UPDATE accounts SET abi = :abi, updated_at = NOW() WHERE name = :name", - soci::use(abi_string), - soci::use(action_data.account.to_string()); - - } else if (action.name == chain::newaccount::get_name()) { - auto action_data = action.data_as(); - *m_session << "INSERT INTO accounts (name) VALUES (:name)", - soci::use(action_data.name.to_string()); - - for (const auto& key_owner : action_data.owner.keys) { - string permission_owner = "owner"; - string public_key_owner = static_cast(key_owner.key); - *m_session << "INSERT INTO accounts_keys(account, public_key, permission) VALUES (:ac, :ke, :pe) ", - soci::use(action_data.name.to_string()), - soci::use(public_key_owner), - soci::use(permission_owner); - } - - for (const auto& key_active : action_data.active.keys) { - string permission_active = "active"; - string public_key_active = static_cast(key_active.key); - *m_session << "INSERT INTO accounts_keys(account, public_key, permission) VALUES (:ac, :ke, :pe) ", - soci::use(action_data.name.to_string()), - soci::use(public_key_active), - soci::use(permission_active); - } - - } -} - -} // namespace diff --git a/plugins/sql_db_plugin/db/actions_table.h b/plugins/sql_db_plugin/db/actions_table.h deleted file mode 100644 index b84b1c6e150..00000000000 --- a/plugins/sql_db_plugin/db/actions_table.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef ACTIONS_TABLE_H -#define ACTIONS_TABLE_H - -#include - -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace eosio { - -using std::string; - -class actions_table -{ -public: - actions_table(std::shared_ptr session); - - void drop(); - void create(); - void add(chain::action action, chain::transaction_id_type transaction_id, fc::time_point_sec transaction_time, uint8_t seq); - -private: - std::shared_ptr m_session; - - void - parse_actions(chain::action action, fc::variant variant); -}; - -} // namespace - -#endif // ACTIONS_TABLE_H diff --git a/plugins/sql_db_plugin/db/blocks_table.cpp b/plugins/sql_db_plugin/db/blocks_table.cpp deleted file mode 100644 index 92e36c18e8d..00000000000 --- a/plugins/sql_db_plugin/db/blocks_table.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "blocks_table.h" - -#include - -namespace eosio { - -blocks_table::blocks_table(std::shared_ptr session): - m_session(session) -{ - -} - -void blocks_table::drop() -{ - try { - *m_session << "DROP TABLE IF EXISTS blocks"; - } - catch(std::exception& e){ - wlog(e.what()); - } -} - -void blocks_table::create() -{ - *m_session << "CREATE TABLE blocks(" - "id VARCHAR(64) PRIMARY KEY," - "block_number INT NOT NULL AUTO_INCREMENT," - "prev_block_id VARCHAR(64)," - "irreversible TINYINT(1) DEFAULT 0," - "timestamp DATETIME DEFAULT NOW()," - "transaction_merkle_root VARCHAR(64)," - "action_merkle_root VARCHAR(64)," - "producer VARCHAR(12)," - "version INT NOT NULL DEFAULT 0," - "new_producers JSON DEFAULT NULL," - "num_transactions INT DEFAULT 0," - "confirmed INT, FOREIGN KEY (producer) REFERENCES accounts(name), UNIQUE KEY block_number (block_number)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; - - *m_session << "CREATE INDEX idx_blocks_producer ON blocks (producer);"; - *m_session << "CREATE INDEX idx_blocks_number ON blocks (block_number);"; - -} - - - -void blocks_table::add(chain::signed_block_ptr block) -{ - const auto block_id_str = block->id().str(); - const auto previous_block_id_str = block->previous.str(); - const auto transaction_mroot_str = block->transaction_mroot.str(); - const auto action_mroot_str = block->action_mroot.str(); - const auto timestamp = std::chrono::seconds{block->timestamp.operator fc::time_point().sec_since_epoch()}.count(); - const auto num_transactions = (int)block->transactions.size(); - - - *m_session << "REPLACE INTO blocks(id, block_number, prev_block_id, timestamp, transaction_merkle_root, action_merkle_root," - "producer, version, confirmed, num_transactions) VALUES (:id, :in, :pb, FROM_UNIXTIME(:ti), :tr, :ar, :pa, :ve, :pe, :nt)", - soci::use(block_id_str), - soci::use(block->block_num()), - soci::use(previous_block_id_str), - soci::use(timestamp), - soci::use(transaction_mroot_str), - soci::use(action_mroot_str), - soci::use(block->producer.to_string()), - soci::use(block->schedule_version), - soci::use(block->confirmed), - soci::use(num_transactions); - - if (block->new_producers) { - const auto new_producers = fc::json::to_string(block->new_producers->producers); - *m_session << "UPDATE blocks SET new_producers = :np WHERE id = :id", - soci::use(new_producers), - soci::use(block_id_str); - } -} - -} // namespace diff --git a/plugins/sql_db_plugin/db/blocks_table.h b/plugins/sql_db_plugin/db/blocks_table.h deleted file mode 100644 index 634c40914a5..00000000000 --- a/plugins/sql_db_plugin/db/blocks_table.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef BLOCKS_TABLE_H -#define BLOCKS_TABLE_H - -#include -#include - -#include - -#include -#include - -#include - -namespace eosio { - -class blocks_table -{ -public: - blocks_table(std::shared_ptr session); - - void drop(); - void create(); - void add(chain::signed_block_ptr block); - -private: - std::shared_ptr m_session; -}; - -} // namespace - -#endif // BLOCKS_TABLE_H diff --git a/plugins/sql_db_plugin/db/database.cpp b/plugins/sql_db_plugin/db/database.cpp deleted file mode 100644 index 85cb21ff6d9..00000000000 --- a/plugins/sql_db_plugin/db/database.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "database.h" - -namespace eosio -{ - -database::database(const std::string &uri, uint32_t block_num_start) -{ - m_session = std::make_shared(uri); - m_accounts_table = std::make_unique(m_session); - m_blocks_table = std::make_unique(m_session); - m_transactions_table = std::make_unique(m_session); - m_actions_table = std::make_unique(m_session); - m_block_num_start = block_num_start; - system_account = chain::name(chain::config::system_account_name).to_string(); -} - -void -database::consume(const std::vector &blocks) -{ - try { - for (const auto &block : blocks) { - if (m_block_num_start > 0 && block->block_num < m_block_num_start) { - continue; - } - - - m_blocks_table->add(block->block); - for (const auto &transaction : block->trxs) { - m_transactions_table->add(block->block_num, transaction->trx); - uint8_t seq = 0; - for (const auto &action : transaction->trx.actions) { - try { - m_actions_table->add(action, transaction->trx.id(), transaction->trx.expiration, seq); - seq++; - } catch (const fc::assert_exception &ex) { // malformed actions - wlog("${e}", ("e", ex.what())); - continue; - } - } - } - - } - } catch (const std::exception &ex) { - elog("${e}", ("e", ex.what())); // prevent crash - } -} - -void -database::wipe() -{ - *m_session << "SET foreign_key_checks = 0;"; - - m_actions_table->drop(); - m_transactions_table->drop(); - m_blocks_table->drop(); - m_accounts_table->drop(); - - *m_session << "SET foreign_key_checks = 1;"; - - m_accounts_table->create(); - m_blocks_table->create(); - m_transactions_table->create(); - m_actions_table->create(); - - m_accounts_table->add(system_account); -} - -bool -database::is_started() -{ - return m_accounts_table->exist(system_account); -} - -} // namespace diff --git a/plugins/sql_db_plugin/db/database.h b/plugins/sql_db_plugin/db/database.h deleted file mode 100644 index 7b83e49ad24..00000000000 --- a/plugins/sql_db_plugin/db/database.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef DATABASE_H -#define DATABASE_H - -#include "consumer_core.h" - -#include -#include - -#include - -#include -#include -#include - -#include "accounts_table.h" -#include "transactions_table.h" -#include "blocks_table.h" -#include "actions_table.h" - -namespace eosio { - -class database : public consumer_core -{ -public: - database(const std::string& uri, uint32_t block_num_start); - - void consume(const std::vector& blocks) override; - - void wipe(); - bool is_started(); - -private: - std::shared_ptr m_session; - std::unique_ptr m_accounts_table; - std::unique_ptr m_actions_table; - std::unique_ptr m_blocks_table; - std::unique_ptr m_transactions_table; - std::string system_account; - uint32_t m_block_num_start; -}; - -} // namespace - -#endif // DATABASE_H diff --git a/plugins/sql_db_plugin/db/transactions_table.cpp b/plugins/sql_db_plugin/db/transactions_table.cpp deleted file mode 100644 index 34d0f954c33..00000000000 --- a/plugins/sql_db_plugin/db/transactions_table.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "transactions_table.h" - -#include -#include - -namespace eosio { - -transactions_table::transactions_table(std::shared_ptr session): - m_session(session) -{ - -} - -void transactions_table::drop() -{ - try { - *m_session << "DROP TABLE IF EXISTS transactions"; - } - catch(std::exception& e){ - wlog(e.what()); - } -} - -void transactions_table::create() -{ - *m_session << "CREATE TABLE transactions(" - "id VARCHAR(64) PRIMARY KEY," - "block_id INT NOT NULL," - "ref_block_num INT NOT NULL," - "ref_block_prefix INT," - "expiration DATETIME DEFAULT NOW()," - "pending TINYINT(1)," - "created_at DATETIME DEFAULT NOW()," - "num_actions INT DEFAULT 0," - "updated_at DATETIME DEFAULT NOW(), FOREIGN KEY (block_id) REFERENCES blocks(block_number) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;"; - - *m_session << "CREATE INDEX transactions_block_id ON transactions (block_id);"; - -} - -void transactions_table::add(uint32_t block_id, chain::transaction transaction) -{ - const auto transaction_id_str = transaction.id().str(); - const auto expiration = std::chrono::seconds{transaction.expiration.sec_since_epoch()}.count(); - *m_session << "INSERT INTO transactions(id, block_id, ref_block_num, ref_block_prefix," - "expiration, pending, created_at, updated_at, num_actions) VALUES (:id, :bi, :rbi, :rb, FROM_UNIXTIME(:ex), :pe, FROM_UNIXTIME(:ca), FROM_UNIXTIME(:ua), :na)", - soci::use(transaction_id_str), - soci::use(block_id), - soci::use(transaction.ref_block_num), - soci::use(transaction.ref_block_prefix), - soci::use(expiration), - soci::use(0), - soci::use(expiration), - soci::use(expiration), - soci::use(transaction.total_actions()); -} - -} // namespace diff --git a/plugins/sql_db_plugin/db/transactions_table.h b/plugins/sql_db_plugin/db/transactions_table.h deleted file mode 100644 index 87ed57c3a03..00000000000 --- a/plugins/sql_db_plugin/db/transactions_table.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef TRANSACTIONS_TABLE_H -#define TRANSACTIONS_TABLE_H - -#include -#include -#include - -namespace eosio { - -class transactions_table -{ -public: - transactions_table(std::shared_ptr session); - - void drop(); - void create(); - void add(uint32_t block_id, chain::transaction transaction); - -private: - std::shared_ptr m_session; -}; - -} // namespace - -#endif // TRANSACTIONS_TABLE_H diff --git a/plugins/sql_db_plugin/fifo.h b/plugins/sql_db_plugin/fifo.h deleted file mode 100644 index e9347ba1d3d..00000000000 --- a/plugins/sql_db_plugin/fifo.h +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace eosio { - -template -class fifo : public boost::noncopyable -{ -public: - enum class behavior {blocking, not_blocking}; - - fifo(behavior value); - - void push(const T& element); - std::vector pop_all(); - void set_behavior(behavior value); - -private: - std::mutex m_mux; - std::condition_variable m_cond; - std::atomic m_behavior; - std::deque m_deque; -}; - -template -fifo::fifo(behavior value) -{ - m_behavior = value; -} - -template -void fifo::push(const T& element) -{ - std::lock_guard lock(m_mux); - m_deque.push_back(element); - m_cond.notify_one(); -} - -template -std::vector fifo::pop_all() -{ - std::unique_lock lock(m_mux); - m_cond.wait(lock, [&]{return m_behavior == behavior::not_blocking || !m_deque.empty();}); - - std::vector result; - while(!m_deque.empty()) - { - result.push_back(std::move(m_deque.front())); - m_deque.pop_front(); - } - return result; -} - -template -void fifo::set_behavior(behavior value) -{ - m_behavior = value; - m_cond.notify_all(); -} - -} // namespace - - diff --git a/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp b/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp deleted file mode 100644 index 4ceecdca75f..00000000000 --- a/plugins/sql_db_plugin/include/eosio/sql_db_plugin/sql_db_plugin.hpp +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ - -#pragma once - -#include -#include -#include -#include - -#include "consumer.h" - -namespace eosio { - -/** - * @author Alessandro Siniscalchi - * - * Provides persistence to SQL DB for: - * Blocks - * Transactions - * Actions - * Accounts - * - * See data dictionary (DB Schema Definition - EOS API) for description of SQL DB schema. - * - * The goal ultimately is for all chainbase data to be mirrored in SQL DB via a delayed node processing - * blocks. Currently, only Blocks, Transactions, Messages, and Account balance it mirrored. - * Chainbase is being rewritten to be multi-threaded. Once chainbase is stable, integration directly with - * a mirror database approach can be followed removing the need for the direct processing of Blocks employed - * with this implementation. - * - * If SQL DB env not available (#ifndef SQL DB) this plugin is a no-op. - */ -class sql_db_plugin final : public plugin { -public: - APPBASE_PLUGIN_REQUIRES((chain_plugin)) - - virtual void set_program_options(options_description& cli, options_description& cfg) override; - - void plugin_initialize(const variables_map& options); - void plugin_startup(); - void plugin_shutdown(); - -private: - std::unique_ptr> m_block_consumer; - fc::optional m_block_connection; - - std::unique_ptr> m_irreversible_block_consumer; - fc::optional m_irreversible_block_connection; -}; - -} - diff --git a/plugins/sql_db_plugin/sql_db_plugin.cpp b/plugins/sql_db_plugin/sql_db_plugin.cpp deleted file mode 100644 index f78a9d7d63e..00000000000 --- a/plugins/sql_db_plugin/sql_db_plugin.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - * @author Alessandro Siniscalchi - */ -#include - -#include "database.h" - -namespace { -const char* BLOCK_START_OPTION = "sql_db-block-start"; -const char* BUFFER_SIZE_OPTION = "sql_db-queue-size"; -const char* SQL_DB_URI_OPTION = "sql_db-uri"; -const char* HARD_REPLAY_OPTION = "hard-replay-blockchain"; -const char* RESYNC_OPTION = "delete-all-blocks"; -const char* REPLAY_OPTION = "replay-blockchain"; -} - -namespace fc { class variant; } - -namespace eosio { - -static appbase::abstract_plugin& _sql_db_plugin = app().register_plugin(); - -void sql_db_plugin::set_program_options(options_description& cli, options_description& cfg) -{ - dlog("set_program_options"); - - cfg.add_options() - (BUFFER_SIZE_OPTION, bpo::value()->default_value(256), - "The queue size between nodeos and SQL DB plugin thread.") - (BLOCK_START_OPTION, bpo::value()->default_value(0), - "The block to start sync.") - (SQL_DB_URI_OPTION, bpo::value(), - "Sql DB URI connection string" - " If not specified then plugin is disabled. Default database 'EOS' is used if not specified in URI.") - ; -} - -void sql_db_plugin::plugin_initialize(const variables_map& options) -{ - ilog("initialize"); - try { - std::string uri_str = options.at(SQL_DB_URI_OPTION).as(); - if (uri_str.empty()) - { - wlog("db URI not specified => eosio::sql_db_plugin disabled."); - return; - } - ilog("connecting to ${u}", ("u", uri_str)); - uint32_t block_num_start = options.at(BLOCK_START_OPTION).as(); - auto db = std::make_unique(uri_str, block_num_start); - - if (options.at(HARD_REPLAY_OPTION).as() || - options.at(REPLAY_OPTION).as() || - options.at(RESYNC_OPTION).as() || - !db->is_started()) - { - if (block_num_start == 0) { - ilog("Resync requested: wiping database"); - if( options.at( RESYNC_OPTION ).as() || - options.at( REPLAY_OPTION ).as()) { - ilog( "Resync requested: wiping database" ); - db->wipe(); - } - } - } - - m_block_consumer = std::make_unique>(std::move(db)); - m_irreversible_block_consumer = std::make_unique>(std::move(db)); - - chain_plugin* chain_plug = app().find_plugin(); - FC_ASSERT(chain_plug); - auto& chain = chain_plug->chain(); - // TODO: irreversible to different queue to just find block & update flag - //m_irreversible_block_connection.emplace(chain.irreversible_block.connect([=](const chain::block_state_ptr& b) {m_irreversible_block_consumer->push(b);})); - m_block_connection.emplace(chain.accepted_block.connect([=](const chain::block_state_ptr& b) {m_block_consumer->push(b);})); - } FC_LOG_AND_RETHROW() -} - -void sql_db_plugin::plugin_startup() -{ - ilog("startup"); -} - -void sql_db_plugin::plugin_shutdown() -{ - ilog("shutdown"); - m_block_connection.reset(); - m_irreversible_block_connection.reset(); -} - -} // namespace eosio diff --git a/plugins/sql_db_plugin/test/CMakeLists.txt b/plugins/sql_db_plugin/test/CMakeLists.txt deleted file mode 100644 index c2a5f5c9ea2..00000000000 --- a/plugins/sql_db_plugin/test/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -add_executable(sql_db_plugin_test - test.cpp - fifo_test.cpp - consumer_test.cpp - ) - -target_link_libraries(sql_db_plugin_test - sql_db_plugin - ${Boost_LIBRARIES} - ) - -#add_test(sql_db_plugin_test sql_db_plugin_test) diff --git a/plugins/sql_db_plugin/test/consumer_test.cpp b/plugins/sql_db_plugin/test/consumer_test.cpp deleted file mode 100644 index 8755ae7f8c4..00000000000 --- a/plugins/sql_db_plugin/test/consumer_test.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -#include "consumer.h" - -using namespace eosio; - -BOOST_AUTO_TEST_SUITE(consumer_test) - -BOOST_AUTO_TEST_CASE(instantiate) -{ - struct foo : public consumer_core - { - public: - void consume(const std::vector &blocks) override - { - for (int i : blocks) - std::cout << i << std::endl; - } - }; - - consumer c(std::make_unique()); - c.push(1); - c.push(10); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/plugins/sql_db_plugin/test/fifo_test.cpp b/plugins/sql_db_plugin/test/fifo_test.cpp deleted file mode 100644 index 9eb2bf67f1c..00000000000 --- a/plugins/sql_db_plugin/test/fifo_test.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include - -#include "fifo.h" - -using namespace eosio; - -BOOST_AUTO_TEST_SUITE(fifo_test) - -BOOST_AUTO_TEST_CASE(pop_empty_fifo_not_blocking) -{ - fifo f(fifo::behavior::not_blocking); - auto v = f.pop_all(); - BOOST_TEST(v.size() == 0); -} - -BOOST_AUTO_TEST_CASE(change_to_not_blocking) -{ - fifo f(fifo::behavior::blocking); - f.push(1); - f.push(2); - f.push(3); - auto v = f.pop_all(); - BOOST_TEST(v.size() == 3); - f.set_behavior(fifo::behavior::not_blocking); - v = f.pop_all(); - BOOST_TEST(v.size() == 0); -} - -BOOST_AUTO_TEST_CASE(pushing_2_int_pop_2_int) -{ - fifo f(fifo::behavior::not_blocking); - f.push(1); - f.push(2); - auto v = f.pop_all(); - BOOST_TEST(1 == v.at(0)); - BOOST_TEST(2 == v.at(1)); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/plugins/sql_db_plugin/test/test.cpp b/plugins/sql_db_plugin/test/test.cpp deleted file mode 100644 index 22b0985501a..00000000000 --- a/plugins/sql_db_plugin/test/test.cpp +++ /dev/null @@ -1,4 +0,0 @@ -#define BOOST_TEST_MODULE "sql_db_plugin" - -#include - diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 1ab5850ef9b..8e4d61fe349 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -60,10 +60,6 @@ target_link_libraries( nodeos PRIVATE chain_plugin http_plugin producer_plugin http_client_plugin PRIVATE eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) -if(TARGET sql_db_plugin) - target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} sql_db_plugin -Wl,${no_whole_archive_flag} ) -endif() - if(BUILD_MONGO_DB_PLUGIN) target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} mongo_db_plugin -Wl,${no_whole_archive_flag} ) endif() @@ -96,4 +92,4 @@ install(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/eosio WORLD_EXECUTE ) -mas_sign(nodeos) \ No newline at end of file +mas_sign(nodeos) From b1d833ff78a74d05867ecece2be6dad739b7f7ad Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Fri, 3 Aug 2018 13:22:53 +0200 Subject: [PATCH 133/294] usage of additional plugins --- programs/nodeos/CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 8e4d61fe349..b6a39563f0e 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -1,3 +1,8 @@ +foreach(ADDITIONAL_PLUGIN_SOURCE_DIR ${EOSIO_ADDITIONAL_PLUGINS}) + message(STATUS "Additional plugin source dir: ${ADDITIONAL_PLUGIN_SOURCE_DIR}") + add_subdirectory(${ADDITIONAL_PLUGIN_SOURCE_DIR} ${CMAKE_BINARY_DIR}/additional_plugins/${ADDITIONAL_PLUGIN_SOURCE_DIR}) +endforeach() + add_executable( nodeos main.cpp ) if( UNIX AND NOT APPLE ) set(rt_library rt ) @@ -60,6 +65,11 @@ target_link_libraries( nodeos PRIVATE chain_plugin http_plugin producer_plugin http_client_plugin PRIVATE eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) +foreach(ADDITIONAL_PLUGIN_TARGET ${ADDITIONAL_PLUGINS_TARGET}) + message(STATUS "Additional plugin target: ${ADDITIONAL_PLUGIN_TARGET}") + target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} ${ADDITIONAL_PLUGIN_TARGET} -Wl,${no_whole_archive_flag} ) +endforeach() + if(BUILD_MONGO_DB_PLUGIN) target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} mongo_db_plugin -Wl,${no_whole_archive_flag} ) endif() From 8c8f9bc6584b6d058414ee6e5483c75d6fd2d765 Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Fri, 3 Aug 2018 14:52:00 +0200 Subject: [PATCH 134/294] additionalPlugin cmake module --- CMakeModules/additionalPlugin.cmake | 5 +++++ programs/nodeos/CMakeLists.txt | 13 +++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 CMakeModules/additionalPlugin.cmake diff --git a/CMakeModules/additionalPlugin.cmake b/CMakeModules/additionalPlugin.cmake new file mode 100644 index 00000000000..1861604d273 --- /dev/null +++ b/CMakeModules/additionalPlugin.cmake @@ -0,0 +1,5 @@ +macro(eosio_additional_plugin) + set(ADDITIONAL_PLUGINS_TARGET "${ADDITIONAL_PLUGINS_TARGET};${ARGN}" PARENT_SCOPE) +endmacro() + + diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index b6a39563f0e..0c0b5e2a5ea 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -1,5 +1,7 @@ +include(additionalPlugin) + foreach(ADDITIONAL_PLUGIN_SOURCE_DIR ${EOSIO_ADDITIONAL_PLUGINS}) - message(STATUS "Additional plugin source dir: ${ADDITIONAL_PLUGIN_SOURCE_DIR}") + message(STATUS "[Additional Plugin] source dir: ${ADDITIONAL_PLUGIN_SOURCE_DIR}") add_subdirectory(${ADDITIONAL_PLUGIN_SOURCE_DIR} ${CMAKE_BINARY_DIR}/additional_plugins/${ADDITIONAL_PLUGIN_SOURCE_DIR}) endforeach() @@ -65,15 +67,14 @@ target_link_libraries( nodeos PRIVATE chain_plugin http_plugin producer_plugin http_client_plugin PRIVATE eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) -foreach(ADDITIONAL_PLUGIN_TARGET ${ADDITIONAL_PLUGINS_TARGET}) - message(STATUS "Additional plugin target: ${ADDITIONAL_PLUGIN_TARGET}") - target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} ${ADDITIONAL_PLUGIN_TARGET} -Wl,${no_whole_archive_flag} ) -endforeach() - if(BUILD_MONGO_DB_PLUGIN) target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} mongo_db_plugin -Wl,${no_whole_archive_flag} ) endif() +foreach(ADDITIONAL_PLUGIN_TARGET ${ADDITIONAL_PLUGINS_TARGET}) + target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} ${ADDITIONAL_PLUGIN_TARGET} -Wl,${no_whole_archive_flag} ) +endforeach() + install( TARGETS nodeos From 20d82c396658d0b03b607b44a2bebd6662c2f3f9 Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Fri, 3 Aug 2018 18:14:58 +0200 Subject: [PATCH 135/294] using UUID as subdirectory of additional plugins --- programs/nodeos/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 0c0b5e2a5ea..0ba6cf9a0a0 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -1,8 +1,9 @@ include(additionalPlugin) foreach(ADDITIONAL_PLUGIN_SOURCE_DIR ${EOSIO_ADDITIONAL_PLUGINS}) - message(STATUS "[Additional Plugin] source dir: ${ADDITIONAL_PLUGIN_SOURCE_DIR}") - add_subdirectory(${ADDITIONAL_PLUGIN_SOURCE_DIR} ${CMAKE_BINARY_DIR}/additional_plugins/${ADDITIONAL_PLUGIN_SOURCE_DIR}) + string(UUID PLUGIN_PATH NAMESPACE "00000000-0000-0000-0000-000000000000" NAME ${ADDITIONAL_PLUGIN_SOURCE_DIR} TYPE MD5) + message(STATUS "[Additional Plugin] source dir: ${ADDITIONAL_PLUGIN_SOURCE_DIR} => ${CMAKE_BINARY_DIR}/additional_plugins/${PLUGIN_PATH}") + add_subdirectory(${ADDITIONAL_PLUGIN_SOURCE_DIR} ${CMAKE_BINARY_DIR}/additional_plugins/${PLUGIN_PATH}) endforeach() add_executable( nodeos main.cpp ) From f24200d5e1990ec8c8a330dd96c921156df353c2 Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Fri, 3 Aug 2018 18:17:47 +0200 Subject: [PATCH 136/294] name refactoring --- programs/nodeos/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 0ba6cf9a0a0..161f1efd22c 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -1,9 +1,9 @@ include(additionalPlugin) foreach(ADDITIONAL_PLUGIN_SOURCE_DIR ${EOSIO_ADDITIONAL_PLUGINS}) - string(UUID PLUGIN_PATH NAMESPACE "00000000-0000-0000-0000-000000000000" NAME ${ADDITIONAL_PLUGIN_SOURCE_DIR} TYPE MD5) - message(STATUS "[Additional Plugin] source dir: ${ADDITIONAL_PLUGIN_SOURCE_DIR} => ${CMAKE_BINARY_DIR}/additional_plugins/${PLUGIN_PATH}") - add_subdirectory(${ADDITIONAL_PLUGIN_SOURCE_DIR} ${CMAKE_BINARY_DIR}/additional_plugins/${PLUGIN_PATH}) + string(UUID ADDITIONAL_PLUGIN_SOURCE_DIR_MD5 NAMESPACE "00000000-0000-0000-0000-000000000000" NAME ${ADDITIONAL_PLUGIN_SOURCE_DIR} TYPE MD5) + message(STATUS "[Additional Plugin] source dir: ${ADDITIONAL_PLUGIN_SOURCE_DIR} => ${CMAKE_BINARY_DIR}/additional_plugins/${ADDITIONAL_PLUGIN_SOURCE_DIR_MD5}") + add_subdirectory(${ADDITIONAL_PLUGIN_SOURCE_DIR} ${CMAKE_BINARY_DIR}/additional_plugins/${ADDITIONAL_PLUGIN_SOURCE_DIR_MD5}) endforeach() add_executable( nodeos main.cpp ) From 29f6964603dd14f63933f7041c8a7bd90b42dfc6 Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Fri, 3 Aug 2018 20:19:58 +0200 Subject: [PATCH 137/294] put all the additional_plugin code into cmake module --- CMakeModules/additionalPlugin.cmake | 8 ++++++++ programs/nodeos/CMakeLists.txt | 10 +--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CMakeModules/additionalPlugin.cmake b/CMakeModules/additionalPlugin.cmake index 1861604d273..ab14fbdbc6b 100644 --- a/CMakeModules/additionalPlugin.cmake +++ b/CMakeModules/additionalPlugin.cmake @@ -2,4 +2,12 @@ macro(eosio_additional_plugin) set(ADDITIONAL_PLUGINS_TARGET "${ADDITIONAL_PLUGINS_TARGET};${ARGN}" PARENT_SCOPE) endmacro() +foreach(ADDITIONAL_PLUGIN_SOURCE_DIR ${EOSIO_ADDITIONAL_PLUGINS}) + string(UUID ADDITIONAL_PLUGIN_SOURCE_DIR_MD5 NAMESPACE "00000000-0000-0000-0000-000000000000" NAME ${ADDITIONAL_PLUGIN_SOURCE_DIR} TYPE MD5) + message(STATUS "[Additional Plugin] source dir: ${ADDITIONAL_PLUGIN_SOURCE_DIR} => ${CMAKE_BINARY_DIR}/additional_plugins/${ADDITIONAL_PLUGIN_SOURCE_DIR_MD5}") + add_subdirectory(${ADDITIONAL_PLUGIN_SOURCE_DIR} ${CMAKE_BINARY_DIR}/additional_plugins/${ADDITIONAL_PLUGIN_SOURCE_DIR_MD5}) +endforeach() +foreach(ADDITIONAL_PLUGIN_TARGET ${ADDITIONAL_PLUGINS_TARGET}) + target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} ${ADDITIONAL_PLUGIN_TARGET} -Wl,${no_whole_archive_flag} ) +endforeach() diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 161f1efd22c..59bf0c97842 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -1,10 +1,4 @@ -include(additionalPlugin) -foreach(ADDITIONAL_PLUGIN_SOURCE_DIR ${EOSIO_ADDITIONAL_PLUGINS}) - string(UUID ADDITIONAL_PLUGIN_SOURCE_DIR_MD5 NAMESPACE "00000000-0000-0000-0000-000000000000" NAME ${ADDITIONAL_PLUGIN_SOURCE_DIR} TYPE MD5) - message(STATUS "[Additional Plugin] source dir: ${ADDITIONAL_PLUGIN_SOURCE_DIR} => ${CMAKE_BINARY_DIR}/additional_plugins/${ADDITIONAL_PLUGIN_SOURCE_DIR_MD5}") - add_subdirectory(${ADDITIONAL_PLUGIN_SOURCE_DIR} ${CMAKE_BINARY_DIR}/additional_plugins/${ADDITIONAL_PLUGIN_SOURCE_DIR_MD5}) -endforeach() add_executable( nodeos main.cpp ) if( UNIX AND NOT APPLE ) @@ -72,9 +66,7 @@ if(BUILD_MONGO_DB_PLUGIN) target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} mongo_db_plugin -Wl,${no_whole_archive_flag} ) endif() -foreach(ADDITIONAL_PLUGIN_TARGET ${ADDITIONAL_PLUGINS_TARGET}) - target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} ${ADDITIONAL_PLUGIN_TARGET} -Wl,${no_whole_archive_flag} ) -endforeach() +include(additionalPlugin) install( TARGETS nodeos From 7d1917fb5a5d0e5eb7c059ee1a333687242f8a60 Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Sun, 5 Aug 2018 08:31:11 +0200 Subject: [PATCH 138/294] refactoring --- .../{additionalPlugin.cmake => additionalPlugins.cmake} | 2 +- programs/nodeos/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename CMakeModules/{additionalPlugin.cmake => additionalPlugins.cmake} (80%) diff --git a/CMakeModules/additionalPlugin.cmake b/CMakeModules/additionalPlugins.cmake similarity index 80% rename from CMakeModules/additionalPlugin.cmake rename to CMakeModules/additionalPlugins.cmake index ab14fbdbc6b..e82de6ef6a9 100644 --- a/CMakeModules/additionalPlugin.cmake +++ b/CMakeModules/additionalPlugins.cmake @@ -4,7 +4,7 @@ endmacro() foreach(ADDITIONAL_PLUGIN_SOURCE_DIR ${EOSIO_ADDITIONAL_PLUGINS}) string(UUID ADDITIONAL_PLUGIN_SOURCE_DIR_MD5 NAMESPACE "00000000-0000-0000-0000-000000000000" NAME ${ADDITIONAL_PLUGIN_SOURCE_DIR} TYPE MD5) - message(STATUS "[Additional Plugin] source dir: ${ADDITIONAL_PLUGIN_SOURCE_DIR} => ${CMAKE_BINARY_DIR}/additional_plugins/${ADDITIONAL_PLUGIN_SOURCE_DIR_MD5}") + message(STATUS "[Additional Plugin] ${ADDITIONAL_PLUGIN_SOURCE_DIR} => ${CMAKE_BINARY_DIR}/additional_plugins/${ADDITIONAL_PLUGIN_SOURCE_DIR_MD5}") add_subdirectory(${ADDITIONAL_PLUGIN_SOURCE_DIR} ${CMAKE_BINARY_DIR}/additional_plugins/${ADDITIONAL_PLUGIN_SOURCE_DIR_MD5}) endforeach() diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 59bf0c97842..973e172f99a 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -66,7 +66,7 @@ if(BUILD_MONGO_DB_PLUGIN) target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} mongo_db_plugin -Wl,${no_whole_archive_flag} ) endif() -include(additionalPlugin) +include(additionalPlugins) install( TARGETS nodeos From fd45c87b020582d5b45c171942243c0835eb601f Mon Sep 17 00:00:00 2001 From: venediktov Date: Sun, 5 Aug 2018 17:20:58 -0700 Subject: [PATCH 139/294] add ripemd160 and --encode-type --- plugins/chain_plugin/chain_plugin.cpp | 37 ++++++----- .../eosio/chain_plugin/chain_plugin.hpp | 63 ++++++++++++++++++- programs/cleos/main.cpp | 8 ++- 3 files changed, 87 insertions(+), 21 deletions(-) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index cc296a6751a..f9c45470798 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -997,31 +996,31 @@ read_only::get_table_rows_result read_only::get_table_rows( const read_only::get } else { EOS_ASSERT( !p.key_type.empty(), chain::contract_table_query_exception, "key type required for non-primary index" ); - if (p.key_type == "i64" || p.key_type == "name") { + if (p.key_type == chain_apis::i64 || p.key_type == "name") { return get_table_rows_by_seckey(p, abi, [](uint64_t v)->uint64_t { return v; }); } - else if (p.key_type == "i128") { + else if (p.key_type == chain_apis::i128) { return get_table_rows_by_seckey(p, abi, [](uint128_t v)->uint128_t { return v; }); } - else if (p.key_type == "i256") { - return get_table_rows_by_seckey(p, abi, [](uint256_t v)->key256_t { - key256_t k; - k[0] = ((uint128_t *)&v)[0]; - k[1] = ((uint128_t *)&v)[1]; - return k; - }); + else if (p.key_type == chain_apis::i256) { + if ( p.encode_type == chain_apis::hex) { + using conv = keytype_converter; + return get_table_rows_by_seckey(p, abi, conv::function()); + } + using conv = keytype_converter; + return get_table_rows_by_seckey(p, abi, conv::function()); } - else if (p.key_type == "float64") { + else if (p.key_type == chain_apis::float64) { return get_table_rows_by_seckey(p, abi, [](double v)->float64_t { float64_t f = *(float64_t *)&v; return f; }); } - else if (p.key_type == "float128") { + else if (p.key_type == chain_apis::float128) { return get_table_rows_by_seckey(p, abi, [](double v)->float128_t{ float64_t f = *(float64_t *)&v; float128_t f128; @@ -1029,13 +1028,13 @@ read_only::get_table_rows_result read_only::get_table_rows( const read_only::get return f128; }); } - else if (p.key_type == "sha256") { - return get_table_rows_by_seckey(p, abi, [](const checksum256_type& v)->key256_t { - key256_t k; - k[0] = ((uint128_t *)&v._hash)[0]; - k[1] = ((uint128_t *)&v._hash)[1]; - return k; - }); + else if (p.key_type == chain_apis::sha256) { + using conv = keytype_converter; + return get_table_rows_by_seckey(p, abi, conv::function()); + } + else if(p.key_type == chain_apis::ripemd160) { + using conv = keytype_converter; + return get_table_rows_by_seckey(p, abi, conv::function()); } EOS_ASSERT(false, chain::contract_table_query_exception, "Unsupported secondary index type: ${t}", ("t", p.key_type)); } diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index c4e140c0e8c..cfde423786b 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -14,8 +14,10 @@ #include #include #include +#include #include +#include #include @@ -227,6 +229,7 @@ class read_only { uint32_t limit = 10; string key_type; // type of key specified by index_position string index_position; // 1 - primary (first), 2 - secondary index (in order defined by multi_index), 3 - third index, etc + string encode_type{"dec"}; //dec, hex , default=dec }; struct get_table_rows_result { @@ -481,6 +484,64 @@ class read_write { friend resolver_factory; }; + + //support for --key_types [sha256,ripemd160] and --encoding [dec/hex] + constexpr const char i64[] = "i64"; + constexpr const char i128[] = "i128"; + constexpr const char i256[] = "i256"; + constexpr const char float64[] = "float64"; + constexpr const char float128[] = "float128"; + constexpr const char sha256[] = "sha256"; + constexpr const char ripemd160[] = "ripemd160"; + constexpr const char dec[] = "dec"; + constexpr const char hex[] = "hex"; + + + template + struct keytype_converter ; + + template<> + struct keytype_converter { + using input_type = chain::checksum256_type; + using index_type = chain::index256_index; + static auto function() { + return [](const input_type& v) { + chain::key256_t k; + k[0] = ((uint128_t *)&v._hash)[0]; //0-127 + k[1] = ((uint128_t *)&v._hash)[1]; //127-256 + return k; + }; + } + }; + + template<> + struct keytype_converter { + using input_type = chain::checksum160_type; + using index_type = chain::index256_index; + static auto function() { + return [](const input_type& v) { + chain::key256_t k; + k[0] = ((uint32_t *)&v._hash)[0]; //0-31 + k[1] = ((uint128_t *)&v._hash)[1]; //32-160 + return k; + }; + } + }; + + template<> + struct keytype_converter { + using input_type = boost::multiprecision::uint256_t; + using index_type = chain::index256_index; + static auto function() { + return [](const input_type v) { + chain::key256_t k; + k[0] = ((uint128_t *)&v)[0]; //0-127 + k[1] = ((uint128_t *)&v)[1]; //127-256 + return k; + }; + } + }; + } // namespace chain_apis class chain_plugin : public plugin { @@ -547,7 +608,7 @@ FC_REFLECT(eosio::chain_apis::read_only::get_block_header_state_params, (block_n FC_REFLECT( eosio::chain_apis::read_write::push_transaction_results, (transaction_id)(processed) ) -FC_REFLECT( eosio::chain_apis::read_only::get_table_rows_params, (json)(code)(scope)(table)(table_key)(lower_bound)(upper_bound)(limit)(key_type)(index_position) ) +FC_REFLECT( eosio::chain_apis::read_only::get_table_rows_params, (json)(code)(scope)(table)(table_key)(lower_bound)(upper_bound)(limit)(key_type)(index_position)(encode_type) ) FC_REFLECT( eosio::chain_apis::read_only::get_table_rows_result, (rows)(more) ); FC_REFLECT( eosio::chain_apis::read_only::get_currency_balance_params, (code)(account)(symbol)); diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index f924e7546bb..f727163f6ee 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -1815,6 +1815,7 @@ int main( int argc, char** argv ) { string upper; string table_key; string key_type; + string encode_type{"dec"}; bool binary = false; uint32_t limit = 10; string index_position; @@ -1831,8 +1832,12 @@ int main( int argc, char** argv ) { localized("Index number, 1 - primary (first), 2 - secondary index (in order defined by multi_index), 3 - third index, etc.\n" "\t\t\t\tNumber or name of index can be specified, e.g. 'secondary' or '2'.")); getTable->add_option( "--key-type", key_type, - localized("The key type of --index, primary only supports (i64), all others support (i64, i128, i256, float64, float128, sha256).\n" + localized("The key type of --index, primary only supports (i64), all others support (i64, i128, i256, float64, float128, ripemd160, sha256).\n" "\t\t\t\tSpecial type 'name' indicates an account name.")); + getTable->add_option( "--encode-type", encode_type, + localized("The encoding type of key_type (i64 , i128 , float64, float128) only support decimal encoding e.g. 'dec'" + "i256 - supports both 'dec' and 'hex', ripemd160 and sha256 is 'hex' only\n")); + getTable->set_callback([&] { auto result = call(get_table_func, fc::mutable_variant_object("json", !binary) @@ -1845,6 +1850,7 @@ int main( int argc, char** argv ) { ("limit",limit) ("key_type",key_type) ("index_position", index_position) + ("encode_type", encode_type) ); std::cout << fc::json::to_pretty_string(result) From fc43e9c81638c5499071890dab8423f288657bab Mon Sep 17 00:00:00 2001 From: venediktov Date: Mon, 6 Aug 2018 01:28:27 -0700 Subject: [PATCH 140/294] add support for 160-bit keys --- contracts/eosiolib/datastream.hpp | 30 ++++ contracts/eosiolib/db.h | 149 +++++++++++++++++- contracts/eosiolib/fixed_key.hpp | 53 ++++--- contracts/eosiolib/multi_index.hpp | 10 +- libraries/chain/controller.cpp | 1 + .../include/eosio/chain/apply_context.hpp | 2 + .../eosio/chain/contract_table_objects.hpp | 11 ++ libraries/chain/include/eosio/chain/types.hpp | 2 + libraries/chain/wasm_interface.cpp | 2 + .../eosio/chain_plugin/chain_plugin.hpp | 11 +- 10 files changed, 238 insertions(+), 33 deletions(-) diff --git a/contracts/eosiolib/datastream.hpp b/contracts/eosiolib/datastream.hpp index 65666e78cc4..324e8222633 100644 --- a/contracts/eosiolib/datastream.hpp +++ b/contracts/eosiolib/datastream.hpp @@ -291,6 +291,36 @@ inline datastream& operator>>(datastream& ds, public_key& pubkey return ds; } +/** + * Serialize a key160 into a stream + * + * @brief Serialize a key160 + * @param ds - The stream to write + * @param d - The value to serialize + * @tparam Stream - Type of datastream buffer + * @return datastream& - Reference to the datastream + */ +template +inline datastream& operator<<(datastream& ds, const key160& d) { + ds.write( (const char*)d.data(), d.size() ); + return ds; +} + +/** + * Deserialize a key160 from a stream + * + * @brief Deserialize a key160 + * @param ds - The stream to read + * @param d - The destination for deserialized value + * @tparam Stream - Type of datastream buffer + * @return datastream& - Reference to the datastream + */ +template +inline datastream& operator>>(datastream& ds, key160& d) { + ds.read((char*)d.data(), d.size() ); + return ds; +} + /** * Serialize a key256 into a stream * diff --git a/contracts/eosiolib/db.h b/contracts/eosiolib/db.h index d2c01b6b15d..8c734825dbd 100644 --- a/contracts/eosiolib/db.h +++ b/contracts/eosiolib/db.h @@ -507,6 +507,153 @@ int32_t db_idx128_upperbound(account_name code, account_name scope, table_name t */ int32_t db_idx128_end(account_name code, account_name scope, table_name table); +/** + * + * Store an association of a 160-bit secondary key to a primary key in a secondary 160-bit index table + * + * @brief Store an association of a 160-bit secondary key to a primary key in a secondary 160-bit index table + * @param scope - The scope where the table resides (implied to be within the code of the current receiver) + * @param table - The table name + * @param payer - The account that pays for the storage costs + * @param id - The primary key to which to associate the secondary key + * @param data - Pointer to the secondary key data stored as an array of 5 `uint32_t` integers + * @param data_len - Must be set to 5 + * @return iterator to the newly created table row + * @post new secondary key association between primary key `id` and the specified secondary key is created in the secondary 256-bit index table + */ +int32_t db_idx160_store(account_name scope, table_name table, account_name payer, uint64_t id, const uint32_t* data, uint32_t data_len ); + +/** + * + * Update an association for a 160-bit secondary key to a primary key in a secondary 160-bit index table + * + * @brief Update an association for a 160-bit secondary key to a primary key in a secondary 160-bit index table + * @param iterator - The iterator to the table row containing the secondary key association to update + * @param payer - The account that pays for the storage costs (use 0 to continue using current payer) + * @param data - Pointer to the **new** secondary key data (which is stored as an array of 5 `uint32_t` integers) that will replace the existing one of the association + * @param data_len - Must be set to 5 + * @pre `iterator` points to an existing table row in the table + * @post the secondary key of the table row pointed to by `iterator` is replaced by the specified secondary key + */ +void db_idx160_update(int32_t iterator, account_name payer, const uint32_t* data, uint32_t data_len); + +/** + * + * Remove a table row from a secondary 160-bit index table + * + * @brief Remove a table row from a secondary 160-bit index table + * @param iterator - Iterator to the table row to remove + * @pre `iterator` points to an existing table row in the table + * @post the table row pointed to by `iterator` is removed and the associated storage costs are refunded to the payer + */ +void db_idx160_remove(int32_t iterator); + +/** + * + * Find the table row following the referenced table row in a secondary 160-bit index table + * + * @brief Find the table row following the referenced table row in a secondary 160-bit index table + * @param iterator - The iterator to the referenced table row + * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the next table row + * @return iterator to the table row following the referenced table row (or the end iterator of the table if the referenced table row is the last one in the table) + * @pre `iterator` points to an existing table row in the table + * @post `*primary` will be replaced with the primary key of the table row following the referenced table row if it exists, otherwise `*primary` will be left untouched + */ +int32_t db_idx160_next(int32_t iterator, uint64_t* primary); + +/** + * + * Find the table row preceding the referenced table row in a secondary 160-bit index table + * + * @brief Find the table row preceding the referenced table row in a secondary 160-bit index table + * @param iterator - The iterator to the referenced table row + * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the previous table row + * @return iterator to the table row preceding the referenced table row assuming one exists (it will return -1 if the referenced table row is the first one in the table) + * @pre `iterator` points to an existing table row in the table or it is the end iterator of the table + * @post `*primary` will be replaced with the primary key of the table row preceding the referenced table row if it exists, otherwise `*primary` will be left untouched + */ +int32_t db_idx160_previous(int32_t iterator, uint64_t* primary); + +/** + * + * Find a table row in a secondary 256-bit index table by primary key + * + * @brief Find a table row in a secondary 160-bit integer index table by primary key + * @param code - The name of the owner of the table + * @param scope - The scope where the table resides + * @param table - The table name + * @param data - Pointer to the an array of 5 `uint32_t` integers which will act as the buffer to hold the retrieved secondary key of the found table row + * @param data_len - Must be set to 5 + * @param primary - The primary key of the table row to look up + * @post If and only if the table row is found, the buffer pointed to by `data` will be filled with the secondary key of the found table row + * @return iterator to the table row with a primary key equal to `id` or the end iterator of the table if the table row could not be found + */ +int32_t db_idx160_find_primary(account_name code, account_name scope, table_name table, uint32_t* data, uint32_t data_len, uint64_t primary); + +/** + * + * Find a table row in a secondary 160-bit index table by secondary key + * + * @brief Find a table row in a secondary 160-bit index table by secondary key + * @param code - The name of the owner of the table + * @param scope - The scope where the table resides + * @param table - The table name + * @param data - Pointer to the secondary key data (which is stored as an array of 5 `uint32_t` integers) used to lookup the table row + * @param data_len - Must be set to 5 + * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the found table row + * @post If and only if the table row is found, `*primary` will be replaced with the primary key of the found table row + * @return iterator to the first table row with a secondary key equal to the specified secondary key or the end iterator of the table if the table row could not be found + */ +int32_t db_idx160_find_secondary(account_name code, account_name scope, table_name table, const uint32_t* data, uint32_t data_len, uint64_t* primary); + +/** + * + * Find the table row in a secondary 160-bit index table that matches the lowerbound condition for a given secondary key + * The table row that matches the lowerbound condition is the first table row in the table with the lowest secondary key that is >= the given key (uses lexicographical ordering on the 256-bit keys) + * + * @brief Find the table row in a secondary 160-bit index table that matches the lowerbound condition for a given secondary key + * @param code - The name of the owner of the table + * @param scope - The scope where the table resides + * @param table - The table name + * @param data - Pointer to the secondary key data (which is stored as an array of 5 `uint32_t` integers) first used to determine the lowerbound and which is then replaced with the secondary key of the found table row + * @param data_len - Must be set to 5 + * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the found table row + * @post If and only if the table row is found, the buffer pointed to by `data` will be filled with the secondary key of the found table row + * @post If and only if the table row is found, `*primary` will be replaced with the primary key of the found table row + * @return iterator to the found table row or the end iterator of the table if the table row could not be found + */ +int32_t db_idx160_lowerbound(account_name code, account_name scope, table_name table, uint32_t* data, uint32_t data_len, uint64_t* primary); + +/** + * + * Find the table row in a secondary 160-bit index table that matches the upperbound condition for a given secondary key + * The table row that matches the upperbound condition is the first table row in the table with the lowest secondary key that is > the given key (uses lexicographical ordering on the 256-bit keys) + * + * @brief Find the table row in a secondary 160-bit index table that matches the upperbound condition for a given secondary key + * @param code - The name of the owner of the table + * @param scope - The scope where the table resides + * @param table - The table name + * @param data - Pointer to the secondary key data (which is stored as an array of 5 `uint32_t` integers) first used to determine the upperbound and which is then replaced with the secondary key of the found table row + * @param data_len - Must be set to 2 + * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the found table row + * @post If and only if the table row is found, the buffer pointed to by `data` will be filled with the secondary key of the found table row + * @post If and only if the table row is found, `*primary` will be replaced with the primary key of the found table row + * @return iterator to the found table row or the end iterator of the table if the table row could not be found + */ +int32_t db_idx160_upperbound(account_name code, account_name scope, table_name table, uint32_t* data, uint32_t data_len, uint64_t* primary); + +/** + * + * Get an end iterator representing just-past-the-end of the last table row of a secondary 160-bit index table + * + * @brief Get an end iterator representing just-past-the-end of the last table row of a secondary 160-bit index table + * @param code - The name of the owner of the table + * @param scope - The scope where the table resides + * @param table - The table name + * @return end iterator of the table + */ +int32_t db_idx160_end(account_name code, account_name scope, table_name table); + /** * * Store an association of a 256-bit secondary key to a primary key in a secondary 256-bit index table @@ -578,7 +725,7 @@ int32_t db_idx256_previous(int32_t iterator, uint64_t* primary); * * Find a table row in a secondary 256-bit index table by primary key * - * @brief Find a table row in a secondary 128-bit integer index table by primary key + * @brief Find a table row in a secondary 256-bit integer index table by primary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name diff --git a/contracts/eosiolib/fixed_key.hpp b/contracts/eosiolib/fixed_key.hpp index eb04a45b46c..c928dd922ad 100644 --- a/contracts/eosiolib/fixed_key.hpp +++ b/contracts/eosiolib/fixed_key.hpp @@ -12,20 +12,20 @@ namespace eosio { - template + template class fixed_key; - template - bool operator==(const fixed_key &c1, const fixed_key &c2); + template + bool operator==(const fixed_key &c1, const fixed_key &c2); - template - bool operator!=(const fixed_key &c1, const fixed_key &c2); + template + bool operator!=(const fixed_key &c1, const fixed_key &c2); - template - bool operator>(const fixed_key &c1, const fixed_key &c2); + template + bool operator>(const fixed_key &c1, const fixed_key &c2); - template - bool operator<(const fixed_key &c1, const fixed_key &c2); + template + bool operator<(const fixed_key &c1, const fixed_key &c2); /** * @defgroup fixed_key Fixed Size Key @@ -41,7 +41,7 @@ namespace eosio { * @tparam Size - Size of the fixed_key object * @ingroup types */ - template + template class fixed_key { private: @@ -50,7 +50,7 @@ namespace eosio { using all_true = std::is_same< bool_pack, bool_pack >; template - static void set_from_word_sequence(const std::array& arr, fixed_key& key) + static void set_from_word_sequence(const std::array& arr, fixed_key& key) { auto itr = key._data.begin(); word_t temp_word = 0; @@ -82,7 +82,7 @@ namespace eosio { public: - typedef uint128_t word_t; + typedef T word_t; /** * Get number of words contained in this fixed_key object. A word is defined to be 16 bytes in size @@ -148,7 +148,7 @@ namespace eosio { */ template static - fixed_key + fixed_key make_from_word_sequence(typename std::enable_if::value && !std::is_same::value && sizeof(FirstWord) <= sizeof(word_t) && @@ -160,7 +160,7 @@ namespace eosio { "size of the backing word size is not divisible by the size of the words supplied as arguments" ); static_assert( sizeof(FirstWord) * (1 + sizeof...(Rest)) <= Size, "too many words supplied to make_from_word_sequence" ); - fixed_key key; + fixed_key key; set_from_word_sequence(std::array{{ first_word, rest... }}, key); return key; } @@ -221,13 +221,13 @@ namespace eosio { } // Comparison operators - friend bool operator== <>(const fixed_key &c1, const fixed_key &c2); + friend bool operator== <>(const fixed_key &c1, const fixed_key &c2); - friend bool operator!= <>(const fixed_key &c1, const fixed_key &c2); + friend bool operator!= <>(const fixed_key &c1, const fixed_key &c2); - friend bool operator> <>(const fixed_key &c1, const fixed_key &c2); + friend bool operator> <>(const fixed_key &c1, const fixed_key &c2); - friend bool operator< <>(const fixed_key &c1, const fixed_key &c2); + friend bool operator< <>(const fixed_key &c1, const fixed_key &c2); private: @@ -242,8 +242,8 @@ namespace eosio { * @param c2 - Second fixed_key object to compare * @return if c1 == c2, return true, otherwise false */ - template - bool operator==(const fixed_key &c1, const fixed_key &c2) { + template + bool operator==(const fixed_key &c1, const fixed_key &c2) { return c1._data == c2._data; } @@ -255,8 +255,8 @@ namespace eosio { * @param c2 - Second fixed_key object to compare * @return if c1 != c2, return true, otherwise false */ - template - bool operator!=(const fixed_key &c1, const fixed_key &c2) { + template + bool operator!=(const fixed_key &c1, const fixed_key &c2) { return c1._data != c2._data; } @@ -268,8 +268,8 @@ namespace eosio { * @param c2 - Second fixed_key object to compare * @return if c1 > c2, return true, otherwise false */ - template - bool operator>(const fixed_key& c1, const fixed_key& c2) { + template + bool operator>(const fixed_key& c1, const fixed_key& c2) { return c1._data > c2._data; } @@ -281,11 +281,12 @@ namespace eosio { * @param c2 - Second fixed_key object to compare * @return if c1 < c2, return true, otherwise false */ - template - bool operator<(const fixed_key &c1, const fixed_key &c2) { + template + bool operator<(const fixed_key &c1, const fixed_key &c2) { return c1._data < c2._data; } /// @} fixed_key + typedef fixed_key<20,uint32_t> key160; typedef fixed_key<32> key256; } diff --git a/contracts/eosiolib/multi_index.hpp b/contracts/eosiolib/multi_index.hpp index d7082559059..70126c40ac7 100644 --- a/contracts/eosiolib/multi_index.hpp +++ b/contracts/eosiolib/multi_index.hpp @@ -110,6 +110,12 @@ namespace _multi_index_detail { WRAP_SECONDARY_SIMPLE_TYPE(idx_long_double, long double) MAKE_TRAITS_FOR_ARITHMETIC_SECONDARY_KEY(long double) + WRAP_SECONDARY_ARRAY_TYPE(idx160, key160) + template<> + struct secondary_key_traits { + static constexpr key160 lowest() { return key160(); } + }; + WRAP_SECONDARY_ARRAY_TYPE(idx256, key256) template<> struct secondary_key_traits { @@ -1404,7 +1410,7 @@ class multi_index * * @tparam IndexName - the ID of the desired secondary index * - * @return An index of the appropriate type: Primitive 64-bit unsigned integer key (idx64), Primitive 128-bit unsigned integer key (idx128), 128-bit fixed-size lexicographical key (idx128), 256-bit fixed-size lexicographical key (idx256), Floating point key, Double precision floating point key, Long Double (quadruple) precision floating point key + * @return An index of the appropriate type: Primitive 64-bit unsigned integer key (idx64), Primitive 128-bit unsigned integer key (idx128), 128-bit fixed-size lexicographical key (idx128), 160-bit fixed-size lexicographical key (idx160), 256-bit fixed-size lexicographical key (idx256), Floating point key, Double precision floating point key, Long Double (quadruple) precision floating point key * * Example: * @@ -1468,7 +1474,7 @@ class multi_index * * @tparam IndexName - the ID of the desired secondary index * - * @return An index of the appropriate type: Primitive 64-bit unsigned integer key (idx64), Primitive 128-bit unsigned integer key (idx128), 128-bit fixed-size lexicographical key (idx128), 256-bit fixed-size lexicographical key (idx256), Floating point key, Double precision floating point key, Long Double (quadruple) precision floating point key + * @return An index of the appropriate type: Primitive 64-bit unsigned integer key (idx64), Primitive 128-bit unsigned integer key (idx128), 128-bit fixed-size lexicographical key (idx128), 160-bit fixed-size lexicographical key (idx160), 256-bit fixed-size lexicographical key (idx256), Floating point key, Double precision floating point key, Long Double (quadruple) precision floating point key * * Example: * diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 408f694f71f..9214c4da1e8 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -286,6 +286,7 @@ struct controller_impl { db.add_index(); db.add_index(); db.add_index(); + db.add_index(); db.add_index(); db.add_index(); db.add_index(); diff --git a/libraries/chain/include/eosio/chain/apply_context.hpp b/libraries/chain/include/eosio/chain/apply_context.hpp index ef44ca7e0df..fbe2b9cc5dd 100644 --- a/libraries/chain/include/eosio/chain/apply_context.hpp +++ b/libraries/chain/include/eosio/chain/apply_context.hpp @@ -461,6 +461,7 @@ class apply_context { ,recurse_depth(depth) ,idx64(*this) ,idx128(*this) + ,idx160(*this) ,idx256(*this) ,idx_double(*this) ,idx_long_double(*this) @@ -594,6 +595,7 @@ class apply_context { generic_index idx64; generic_index idx128; + generic_index idx160; generic_index idx256; generic_index idx_double; generic_index idx_long_double; diff --git a/libraries/chain/include/eosio/chain/contract_table_objects.hpp b/libraries/chain/include/eosio/chain/contract_table_objects.hpp index bc2fff140c4..e3dc2117da8 100644 --- a/libraries/chain/include/eosio/chain/contract_table_objects.hpp +++ b/libraries/chain/include/eosio/chain/contract_table_objects.hpp @@ -128,6 +128,10 @@ namespace eosio { namespace chain { typedef secondary_index::index_object index128_object; typedef secondary_index::index_index index128_index; + typedef std::array key160_t; + typedef secondary_index::index_object index160_object; + typedef secondary_index::index_index index160_index; + typedef std::array key256_t; typedef secondary_index::index_object index256_object; typedef secondary_index::index_index index256_index; @@ -185,6 +189,12 @@ namespace config { static const uint64_t value = 24 + 16 + overhead; ///< 24 bytes for fixed fields + 16 bytes key + overhead }; + template<> + struct billable_size { + static const uint64_t overhead = overhead_per_row_per_index_ram_bytes * 3; ///< overhead for potentially single-row table, 3x indices internal-key, primary key and primary+secondary key + static const uint64_t value = 24 + 20 + overhead; ///< 24 bytes for fixed fields + 20 bytes key + overhead + }; + template<> struct billable_size { static const uint64_t overhead = overhead_per_row_per_index_ram_bytes * 3; ///< overhead for potentially single-row table, 3x indices internal-key, primary key and primary+secondary key @@ -212,6 +222,7 @@ CHAINBASE_SET_INDEX_TYPE(eosio::chain::key_value_object, eosio::chain::key_value CHAINBASE_SET_INDEX_TYPE(eosio::chain::index64_object, eosio::chain::index64_index) CHAINBASE_SET_INDEX_TYPE(eosio::chain::index128_object, eosio::chain::index128_index) +CHAINBASE_SET_INDEX_TYPE(eosio::chain::index160_object, eosio::chain::index160_index) CHAINBASE_SET_INDEX_TYPE(eosio::chain::index256_object, eosio::chain::index256_index) CHAINBASE_SET_INDEX_TYPE(eosio::chain::index_double_object, eosio::chain::index_double_index) CHAINBASE_SET_INDEX_TYPE(eosio::chain::index_long_double_object, eosio::chain::index_long_double_index) diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index d031488e64b..21c9c8c6483 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -122,6 +122,7 @@ namespace eosio { namespace chain { key_value_object_type, index64_object_type, index128_object_type, + index160_object_type, index256_object_type, index_double_object_type, index_long_double_object_type, @@ -191,6 +192,7 @@ FC_REFLECT_ENUM(eosio::chain::object_type, (key_value_object_type) (index64_object_type) (index128_object_type) + (index160_object_type) (index256_object_type) (index_double_object_type) (index_long_double_object_type) diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index abe5a745766..5d0eec25fcc 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -1266,6 +1266,7 @@ class database_api : public context_aware_api { DB_API_METHOD_WRAPPERS_SIMPLE_SECONDARY(idx64, uint64_t) DB_API_METHOD_WRAPPERS_SIMPLE_SECONDARY(idx128, uint128_t) + DB_API_METHOD_WRAPPERS_ARRAY_SECONDARY(idx160, 5, uint32_t) DB_API_METHOD_WRAPPERS_ARRAY_SECONDARY(idx256, 2, uint128_t) DB_API_METHOD_WRAPPERS_FLOAT_SECONDARY(idx_double, float64_t) DB_API_METHOD_WRAPPERS_FLOAT_SECONDARY(idx_long_double, float128_t) @@ -1750,6 +1751,7 @@ REGISTER_INTRINSICS( database_api, DB_SECONDARY_INDEX_METHODS_SIMPLE(idx64) DB_SECONDARY_INDEX_METHODS_SIMPLE(idx128) + DB_SECONDARY_INDEX_METHODS_ARRAY(idx160) DB_SECONDARY_INDEX_METHODS_ARRAY(idx256) DB_SECONDARY_INDEX_METHODS_SIMPLE(idx_double) DB_SECONDARY_INDEX_METHODS_SIMPLE(idx_long_double) diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index cfde423786b..2584a59e0fd 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -517,12 +517,15 @@ class read_write { template<> struct keytype_converter { using input_type = chain::checksum160_type; - using index_type = chain::index256_index; + using index_type = chain::index160_index; static auto function() { return [](const input_type& v) { - chain::key256_t k; - k[0] = ((uint32_t *)&v._hash)[0]; //0-31 - k[1] = ((uint128_t *)&v._hash)[1]; //32-160 + chain::key160_t k; + k[0] = ((uint32_t *)&v._hash)[0]; + k[1] = ((uint32_t *)&v._hash)[1]; + k[2] = ((uint32_t *)&v._hash)[2]; + k[3] = ((uint32_t *)&v._hash)[3]; + k[4] = ((uint32_t *)&v._hash)[4]; return k; }; } From d4af45093b2863afb535d898b303accc69fead6c Mon Sep 17 00:00:00 2001 From: venediktov Date: Mon, 6 Aug 2018 15:36:47 -0700 Subject: [PATCH 141/294] Revert "add support for 160-bit keys" This reverts commit fc43e9c81638c5499071890dab8423f288657bab. --- contracts/eosiolib/datastream.hpp | 30 ---- contracts/eosiolib/db.h | 149 +----------------- contracts/eosiolib/fixed_key.hpp | 53 +++---- contracts/eosiolib/multi_index.hpp | 10 +- libraries/chain/controller.cpp | 1 - .../include/eosio/chain/apply_context.hpp | 2 - .../eosio/chain/contract_table_objects.hpp | 11 -- libraries/chain/include/eosio/chain/types.hpp | 2 - libraries/chain/wasm_interface.cpp | 2 - .../eosio/chain_plugin/chain_plugin.hpp | 11 +- 10 files changed, 33 insertions(+), 238 deletions(-) diff --git a/contracts/eosiolib/datastream.hpp b/contracts/eosiolib/datastream.hpp index 324e8222633..65666e78cc4 100644 --- a/contracts/eosiolib/datastream.hpp +++ b/contracts/eosiolib/datastream.hpp @@ -291,36 +291,6 @@ inline datastream& operator>>(datastream& ds, public_key& pubkey return ds; } -/** - * Serialize a key160 into a stream - * - * @brief Serialize a key160 - * @param ds - The stream to write - * @param d - The value to serialize - * @tparam Stream - Type of datastream buffer - * @return datastream& - Reference to the datastream - */ -template -inline datastream& operator<<(datastream& ds, const key160& d) { - ds.write( (const char*)d.data(), d.size() ); - return ds; -} - -/** - * Deserialize a key160 from a stream - * - * @brief Deserialize a key160 - * @param ds - The stream to read - * @param d - The destination for deserialized value - * @tparam Stream - Type of datastream buffer - * @return datastream& - Reference to the datastream - */ -template -inline datastream& operator>>(datastream& ds, key160& d) { - ds.read((char*)d.data(), d.size() ); - return ds; -} - /** * Serialize a key256 into a stream * diff --git a/contracts/eosiolib/db.h b/contracts/eosiolib/db.h index 8c734825dbd..d2c01b6b15d 100644 --- a/contracts/eosiolib/db.h +++ b/contracts/eosiolib/db.h @@ -507,153 +507,6 @@ int32_t db_idx128_upperbound(account_name code, account_name scope, table_name t */ int32_t db_idx128_end(account_name code, account_name scope, table_name table); -/** - * - * Store an association of a 160-bit secondary key to a primary key in a secondary 160-bit index table - * - * @brief Store an association of a 160-bit secondary key to a primary key in a secondary 160-bit index table - * @param scope - The scope where the table resides (implied to be within the code of the current receiver) - * @param table - The table name - * @param payer - The account that pays for the storage costs - * @param id - The primary key to which to associate the secondary key - * @param data - Pointer to the secondary key data stored as an array of 5 `uint32_t` integers - * @param data_len - Must be set to 5 - * @return iterator to the newly created table row - * @post new secondary key association between primary key `id` and the specified secondary key is created in the secondary 256-bit index table - */ -int32_t db_idx160_store(account_name scope, table_name table, account_name payer, uint64_t id, const uint32_t* data, uint32_t data_len ); - -/** - * - * Update an association for a 160-bit secondary key to a primary key in a secondary 160-bit index table - * - * @brief Update an association for a 160-bit secondary key to a primary key in a secondary 160-bit index table - * @param iterator - The iterator to the table row containing the secondary key association to update - * @param payer - The account that pays for the storage costs (use 0 to continue using current payer) - * @param data - Pointer to the **new** secondary key data (which is stored as an array of 5 `uint32_t` integers) that will replace the existing one of the association - * @param data_len - Must be set to 5 - * @pre `iterator` points to an existing table row in the table - * @post the secondary key of the table row pointed to by `iterator` is replaced by the specified secondary key - */ -void db_idx160_update(int32_t iterator, account_name payer, const uint32_t* data, uint32_t data_len); - -/** - * - * Remove a table row from a secondary 160-bit index table - * - * @brief Remove a table row from a secondary 160-bit index table - * @param iterator - Iterator to the table row to remove - * @pre `iterator` points to an existing table row in the table - * @post the table row pointed to by `iterator` is removed and the associated storage costs are refunded to the payer - */ -void db_idx160_remove(int32_t iterator); - -/** - * - * Find the table row following the referenced table row in a secondary 160-bit index table - * - * @brief Find the table row following the referenced table row in a secondary 160-bit index table - * @param iterator - The iterator to the referenced table row - * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the next table row - * @return iterator to the table row following the referenced table row (or the end iterator of the table if the referenced table row is the last one in the table) - * @pre `iterator` points to an existing table row in the table - * @post `*primary` will be replaced with the primary key of the table row following the referenced table row if it exists, otherwise `*primary` will be left untouched - */ -int32_t db_idx160_next(int32_t iterator, uint64_t* primary); - -/** - * - * Find the table row preceding the referenced table row in a secondary 160-bit index table - * - * @brief Find the table row preceding the referenced table row in a secondary 160-bit index table - * @param iterator - The iterator to the referenced table row - * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the previous table row - * @return iterator to the table row preceding the referenced table row assuming one exists (it will return -1 if the referenced table row is the first one in the table) - * @pre `iterator` points to an existing table row in the table or it is the end iterator of the table - * @post `*primary` will be replaced with the primary key of the table row preceding the referenced table row if it exists, otherwise `*primary` will be left untouched - */ -int32_t db_idx160_previous(int32_t iterator, uint64_t* primary); - -/** - * - * Find a table row in a secondary 256-bit index table by primary key - * - * @brief Find a table row in a secondary 160-bit integer index table by primary key - * @param code - The name of the owner of the table - * @param scope - The scope where the table resides - * @param table - The table name - * @param data - Pointer to the an array of 5 `uint32_t` integers which will act as the buffer to hold the retrieved secondary key of the found table row - * @param data_len - Must be set to 5 - * @param primary - The primary key of the table row to look up - * @post If and only if the table row is found, the buffer pointed to by `data` will be filled with the secondary key of the found table row - * @return iterator to the table row with a primary key equal to `id` or the end iterator of the table if the table row could not be found - */ -int32_t db_idx160_find_primary(account_name code, account_name scope, table_name table, uint32_t* data, uint32_t data_len, uint64_t primary); - -/** - * - * Find a table row in a secondary 160-bit index table by secondary key - * - * @brief Find a table row in a secondary 160-bit index table by secondary key - * @param code - The name of the owner of the table - * @param scope - The scope where the table resides - * @param table - The table name - * @param data - Pointer to the secondary key data (which is stored as an array of 5 `uint32_t` integers) used to lookup the table row - * @param data_len - Must be set to 5 - * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the found table row - * @post If and only if the table row is found, `*primary` will be replaced with the primary key of the found table row - * @return iterator to the first table row with a secondary key equal to the specified secondary key or the end iterator of the table if the table row could not be found - */ -int32_t db_idx160_find_secondary(account_name code, account_name scope, table_name table, const uint32_t* data, uint32_t data_len, uint64_t* primary); - -/** - * - * Find the table row in a secondary 160-bit index table that matches the lowerbound condition for a given secondary key - * The table row that matches the lowerbound condition is the first table row in the table with the lowest secondary key that is >= the given key (uses lexicographical ordering on the 256-bit keys) - * - * @brief Find the table row in a secondary 160-bit index table that matches the lowerbound condition for a given secondary key - * @param code - The name of the owner of the table - * @param scope - The scope where the table resides - * @param table - The table name - * @param data - Pointer to the secondary key data (which is stored as an array of 5 `uint32_t` integers) first used to determine the lowerbound and which is then replaced with the secondary key of the found table row - * @param data_len - Must be set to 5 - * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the found table row - * @post If and only if the table row is found, the buffer pointed to by `data` will be filled with the secondary key of the found table row - * @post If and only if the table row is found, `*primary` will be replaced with the primary key of the found table row - * @return iterator to the found table row or the end iterator of the table if the table row could not be found - */ -int32_t db_idx160_lowerbound(account_name code, account_name scope, table_name table, uint32_t* data, uint32_t data_len, uint64_t* primary); - -/** - * - * Find the table row in a secondary 160-bit index table that matches the upperbound condition for a given secondary key - * The table row that matches the upperbound condition is the first table row in the table with the lowest secondary key that is > the given key (uses lexicographical ordering on the 256-bit keys) - * - * @brief Find the table row in a secondary 160-bit index table that matches the upperbound condition for a given secondary key - * @param code - The name of the owner of the table - * @param scope - The scope where the table resides - * @param table - The table name - * @param data - Pointer to the secondary key data (which is stored as an array of 5 `uint32_t` integers) first used to determine the upperbound and which is then replaced with the secondary key of the found table row - * @param data_len - Must be set to 2 - * @param primary - Pointer to a `uint64_t` variable which will have its value set to the primary key of the found table row - * @post If and only if the table row is found, the buffer pointed to by `data` will be filled with the secondary key of the found table row - * @post If and only if the table row is found, `*primary` will be replaced with the primary key of the found table row - * @return iterator to the found table row or the end iterator of the table if the table row could not be found - */ -int32_t db_idx160_upperbound(account_name code, account_name scope, table_name table, uint32_t* data, uint32_t data_len, uint64_t* primary); - -/** - * - * Get an end iterator representing just-past-the-end of the last table row of a secondary 160-bit index table - * - * @brief Get an end iterator representing just-past-the-end of the last table row of a secondary 160-bit index table - * @param code - The name of the owner of the table - * @param scope - The scope where the table resides - * @param table - The table name - * @return end iterator of the table - */ -int32_t db_idx160_end(account_name code, account_name scope, table_name table); - /** * * Store an association of a 256-bit secondary key to a primary key in a secondary 256-bit index table @@ -725,7 +578,7 @@ int32_t db_idx256_previous(int32_t iterator, uint64_t* primary); * * Find a table row in a secondary 256-bit index table by primary key * - * @brief Find a table row in a secondary 256-bit integer index table by primary key + * @brief Find a table row in a secondary 128-bit integer index table by primary key * @param code - The name of the owner of the table * @param scope - The scope where the table resides * @param table - The table name diff --git a/contracts/eosiolib/fixed_key.hpp b/contracts/eosiolib/fixed_key.hpp index c928dd922ad..eb04a45b46c 100644 --- a/contracts/eosiolib/fixed_key.hpp +++ b/contracts/eosiolib/fixed_key.hpp @@ -12,20 +12,20 @@ namespace eosio { - template + template class fixed_key; - template - bool operator==(const fixed_key &c1, const fixed_key &c2); + template + bool operator==(const fixed_key &c1, const fixed_key &c2); - template - bool operator!=(const fixed_key &c1, const fixed_key &c2); + template + bool operator!=(const fixed_key &c1, const fixed_key &c2); - template - bool operator>(const fixed_key &c1, const fixed_key &c2); + template + bool operator>(const fixed_key &c1, const fixed_key &c2); - template - bool operator<(const fixed_key &c1, const fixed_key &c2); + template + bool operator<(const fixed_key &c1, const fixed_key &c2); /** * @defgroup fixed_key Fixed Size Key @@ -41,7 +41,7 @@ namespace eosio { * @tparam Size - Size of the fixed_key object * @ingroup types */ - template + template class fixed_key { private: @@ -50,7 +50,7 @@ namespace eosio { using all_true = std::is_same< bool_pack, bool_pack >; template - static void set_from_word_sequence(const std::array& arr, fixed_key& key) + static void set_from_word_sequence(const std::array& arr, fixed_key& key) { auto itr = key._data.begin(); word_t temp_word = 0; @@ -82,7 +82,7 @@ namespace eosio { public: - typedef T word_t; + typedef uint128_t word_t; /** * Get number of words contained in this fixed_key object. A word is defined to be 16 bytes in size @@ -148,7 +148,7 @@ namespace eosio { */ template static - fixed_key + fixed_key make_from_word_sequence(typename std::enable_if::value && !std::is_same::value && sizeof(FirstWord) <= sizeof(word_t) && @@ -160,7 +160,7 @@ namespace eosio { "size of the backing word size is not divisible by the size of the words supplied as arguments" ); static_assert( sizeof(FirstWord) * (1 + sizeof...(Rest)) <= Size, "too many words supplied to make_from_word_sequence" ); - fixed_key key; + fixed_key key; set_from_word_sequence(std::array{{ first_word, rest... }}, key); return key; } @@ -221,13 +221,13 @@ namespace eosio { } // Comparison operators - friend bool operator== <>(const fixed_key &c1, const fixed_key &c2); + friend bool operator== <>(const fixed_key &c1, const fixed_key &c2); - friend bool operator!= <>(const fixed_key &c1, const fixed_key &c2); + friend bool operator!= <>(const fixed_key &c1, const fixed_key &c2); - friend bool operator> <>(const fixed_key &c1, const fixed_key &c2); + friend bool operator> <>(const fixed_key &c1, const fixed_key &c2); - friend bool operator< <>(const fixed_key &c1, const fixed_key &c2); + friend bool operator< <>(const fixed_key &c1, const fixed_key &c2); private: @@ -242,8 +242,8 @@ namespace eosio { * @param c2 - Second fixed_key object to compare * @return if c1 == c2, return true, otherwise false */ - template - bool operator==(const fixed_key &c1, const fixed_key &c2) { + template + bool operator==(const fixed_key &c1, const fixed_key &c2) { return c1._data == c2._data; } @@ -255,8 +255,8 @@ namespace eosio { * @param c2 - Second fixed_key object to compare * @return if c1 != c2, return true, otherwise false */ - template - bool operator!=(const fixed_key &c1, const fixed_key &c2) { + template + bool operator!=(const fixed_key &c1, const fixed_key &c2) { return c1._data != c2._data; } @@ -268,8 +268,8 @@ namespace eosio { * @param c2 - Second fixed_key object to compare * @return if c1 > c2, return true, otherwise false */ - template - bool operator>(const fixed_key& c1, const fixed_key& c2) { + template + bool operator>(const fixed_key& c1, const fixed_key& c2) { return c1._data > c2._data; } @@ -281,12 +281,11 @@ namespace eosio { * @param c2 - Second fixed_key object to compare * @return if c1 < c2, return true, otherwise false */ - template - bool operator<(const fixed_key &c1, const fixed_key &c2) { + template + bool operator<(const fixed_key &c1, const fixed_key &c2) { return c1._data < c2._data; } /// @} fixed_key - typedef fixed_key<20,uint32_t> key160; typedef fixed_key<32> key256; } diff --git a/contracts/eosiolib/multi_index.hpp b/contracts/eosiolib/multi_index.hpp index 70126c40ac7..d7082559059 100644 --- a/contracts/eosiolib/multi_index.hpp +++ b/contracts/eosiolib/multi_index.hpp @@ -110,12 +110,6 @@ namespace _multi_index_detail { WRAP_SECONDARY_SIMPLE_TYPE(idx_long_double, long double) MAKE_TRAITS_FOR_ARITHMETIC_SECONDARY_KEY(long double) - WRAP_SECONDARY_ARRAY_TYPE(idx160, key160) - template<> - struct secondary_key_traits { - static constexpr key160 lowest() { return key160(); } - }; - WRAP_SECONDARY_ARRAY_TYPE(idx256, key256) template<> struct secondary_key_traits { @@ -1410,7 +1404,7 @@ class multi_index * * @tparam IndexName - the ID of the desired secondary index * - * @return An index of the appropriate type: Primitive 64-bit unsigned integer key (idx64), Primitive 128-bit unsigned integer key (idx128), 128-bit fixed-size lexicographical key (idx128), 160-bit fixed-size lexicographical key (idx160), 256-bit fixed-size lexicographical key (idx256), Floating point key, Double precision floating point key, Long Double (quadruple) precision floating point key + * @return An index of the appropriate type: Primitive 64-bit unsigned integer key (idx64), Primitive 128-bit unsigned integer key (idx128), 128-bit fixed-size lexicographical key (idx128), 256-bit fixed-size lexicographical key (idx256), Floating point key, Double precision floating point key, Long Double (quadruple) precision floating point key * * Example: * @@ -1474,7 +1468,7 @@ class multi_index * * @tparam IndexName - the ID of the desired secondary index * - * @return An index of the appropriate type: Primitive 64-bit unsigned integer key (idx64), Primitive 128-bit unsigned integer key (idx128), 128-bit fixed-size lexicographical key (idx128), 160-bit fixed-size lexicographical key (idx160), 256-bit fixed-size lexicographical key (idx256), Floating point key, Double precision floating point key, Long Double (quadruple) precision floating point key + * @return An index of the appropriate type: Primitive 64-bit unsigned integer key (idx64), Primitive 128-bit unsigned integer key (idx128), 128-bit fixed-size lexicographical key (idx128), 256-bit fixed-size lexicographical key (idx256), Floating point key, Double precision floating point key, Long Double (quadruple) precision floating point key * * Example: * diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 9214c4da1e8..408f694f71f 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -286,7 +286,6 @@ struct controller_impl { db.add_index(); db.add_index(); db.add_index(); - db.add_index(); db.add_index(); db.add_index(); db.add_index(); diff --git a/libraries/chain/include/eosio/chain/apply_context.hpp b/libraries/chain/include/eosio/chain/apply_context.hpp index fbe2b9cc5dd..ef44ca7e0df 100644 --- a/libraries/chain/include/eosio/chain/apply_context.hpp +++ b/libraries/chain/include/eosio/chain/apply_context.hpp @@ -461,7 +461,6 @@ class apply_context { ,recurse_depth(depth) ,idx64(*this) ,idx128(*this) - ,idx160(*this) ,idx256(*this) ,idx_double(*this) ,idx_long_double(*this) @@ -595,7 +594,6 @@ class apply_context { generic_index idx64; generic_index idx128; - generic_index idx160; generic_index idx256; generic_index idx_double; generic_index idx_long_double; diff --git a/libraries/chain/include/eosio/chain/contract_table_objects.hpp b/libraries/chain/include/eosio/chain/contract_table_objects.hpp index e3dc2117da8..bc2fff140c4 100644 --- a/libraries/chain/include/eosio/chain/contract_table_objects.hpp +++ b/libraries/chain/include/eosio/chain/contract_table_objects.hpp @@ -128,10 +128,6 @@ namespace eosio { namespace chain { typedef secondary_index::index_object index128_object; typedef secondary_index::index_index index128_index; - typedef std::array key160_t; - typedef secondary_index::index_object index160_object; - typedef secondary_index::index_index index160_index; - typedef std::array key256_t; typedef secondary_index::index_object index256_object; typedef secondary_index::index_index index256_index; @@ -189,12 +185,6 @@ namespace config { static const uint64_t value = 24 + 16 + overhead; ///< 24 bytes for fixed fields + 16 bytes key + overhead }; - template<> - struct billable_size { - static const uint64_t overhead = overhead_per_row_per_index_ram_bytes * 3; ///< overhead for potentially single-row table, 3x indices internal-key, primary key and primary+secondary key - static const uint64_t value = 24 + 20 + overhead; ///< 24 bytes for fixed fields + 20 bytes key + overhead - }; - template<> struct billable_size { static const uint64_t overhead = overhead_per_row_per_index_ram_bytes * 3; ///< overhead for potentially single-row table, 3x indices internal-key, primary key and primary+secondary key @@ -222,7 +212,6 @@ CHAINBASE_SET_INDEX_TYPE(eosio::chain::key_value_object, eosio::chain::key_value CHAINBASE_SET_INDEX_TYPE(eosio::chain::index64_object, eosio::chain::index64_index) CHAINBASE_SET_INDEX_TYPE(eosio::chain::index128_object, eosio::chain::index128_index) -CHAINBASE_SET_INDEX_TYPE(eosio::chain::index160_object, eosio::chain::index160_index) CHAINBASE_SET_INDEX_TYPE(eosio::chain::index256_object, eosio::chain::index256_index) CHAINBASE_SET_INDEX_TYPE(eosio::chain::index_double_object, eosio::chain::index_double_index) CHAINBASE_SET_INDEX_TYPE(eosio::chain::index_long_double_object, eosio::chain::index_long_double_index) diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index 21c9c8c6483..d031488e64b 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -122,7 +122,6 @@ namespace eosio { namespace chain { key_value_object_type, index64_object_type, index128_object_type, - index160_object_type, index256_object_type, index_double_object_type, index_long_double_object_type, @@ -192,7 +191,6 @@ FC_REFLECT_ENUM(eosio::chain::object_type, (key_value_object_type) (index64_object_type) (index128_object_type) - (index160_object_type) (index256_object_type) (index_double_object_type) (index_long_double_object_type) diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 5d0eec25fcc..abe5a745766 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -1266,7 +1266,6 @@ class database_api : public context_aware_api { DB_API_METHOD_WRAPPERS_SIMPLE_SECONDARY(idx64, uint64_t) DB_API_METHOD_WRAPPERS_SIMPLE_SECONDARY(idx128, uint128_t) - DB_API_METHOD_WRAPPERS_ARRAY_SECONDARY(idx160, 5, uint32_t) DB_API_METHOD_WRAPPERS_ARRAY_SECONDARY(idx256, 2, uint128_t) DB_API_METHOD_WRAPPERS_FLOAT_SECONDARY(idx_double, float64_t) DB_API_METHOD_WRAPPERS_FLOAT_SECONDARY(idx_long_double, float128_t) @@ -1751,7 +1750,6 @@ REGISTER_INTRINSICS( database_api, DB_SECONDARY_INDEX_METHODS_SIMPLE(idx64) DB_SECONDARY_INDEX_METHODS_SIMPLE(idx128) - DB_SECONDARY_INDEX_METHODS_ARRAY(idx160) DB_SECONDARY_INDEX_METHODS_ARRAY(idx256) DB_SECONDARY_INDEX_METHODS_SIMPLE(idx_double) DB_SECONDARY_INDEX_METHODS_SIMPLE(idx_long_double) diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index 2584a59e0fd..cfde423786b 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -517,15 +517,12 @@ class read_write { template<> struct keytype_converter { using input_type = chain::checksum160_type; - using index_type = chain::index160_index; + using index_type = chain::index256_index; static auto function() { return [](const input_type& v) { - chain::key160_t k; - k[0] = ((uint32_t *)&v._hash)[0]; - k[1] = ((uint32_t *)&v._hash)[1]; - k[2] = ((uint32_t *)&v._hash)[2]; - k[3] = ((uint32_t *)&v._hash)[3]; - k[4] = ((uint32_t *)&v._hash)[4]; + chain::key256_t k; + k[0] = ((uint32_t *)&v._hash)[0]; //0-31 + k[1] = ((uint128_t *)&v._hash)[1]; //32-160 return k; }; } From 1e06d3987328946944afd150f95ac6798176c02d Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Tue, 7 Aug 2018 01:02:56 -0400 Subject: [PATCH 142/294] update crypto.h to use const char* --- contracts/eosiolib/crypto.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/eosiolib/crypto.h b/contracts/eosiolib/crypto.h index 4fd9b04e502..916c25954f7 100644 --- a/contracts/eosiolib/crypto.h +++ b/contracts/eosiolib/crypto.h @@ -42,7 +42,7 @@ extern "C" { * eosio::print("sha256 hash generated from data equals provided hash"); * @endcode */ -void assert_sha256( char* data, uint32_t length, const checksum256* hash ); +void assert_sha256( const char* data, uint32_t length, const checksum256* hash ); /** * Tests if the sha1 hash generated from data matches the provided checksum. @@ -67,7 +67,7 @@ void assert_sha256( char* data, uint32_t length, const checksum256* hash ); * eosio::print("sha1 hash generated from data equals provided hash"); * @endcode */ -void assert_sha1( char* data, uint32_t length, const checksum160* hash ); +void assert_sha1( const char* data, uint32_t length, const checksum160* hash ); /** * Tests if the sha512 hash generated from data matches the provided checksum. @@ -92,7 +92,7 @@ void assert_sha1( char* data, uint32_t length, const checksum160* hash ); * eosio::print("sha512 hash generated from data equals provided hash"); * @endcode */ -void assert_sha512( char* data, uint32_t length, const checksum512* hash ); +void assert_sha512( const char* data, uint32_t length, const checksum512* hash ); /** * Tests if the ripemod160 hash generated from data matches the provided checksum. @@ -116,7 +116,7 @@ void assert_sha512( char* data, uint32_t length, const checksum512* hash ); * eosio::print("ripemod160 hash generated from data equals provided hash"); * @endcode */ -void assert_ripemd160( char* data, uint32_t length, const checksum160* hash ); +void assert_ripemd160( const char* data, uint32_t length, const checksum160* hash ); /** * Hashes `data` using `sha256` and stores result in memory pointed to by hash. @@ -134,7 +134,7 @@ void assert_ripemd160( char* data, uint32_t length, const checksum160* hash ); * eos_assert( calc_hash == hash, "invalid hash" ); * @endcode */ -void sha256( char* data, uint32_t length, checksum256* hash ); +void sha256( const char* data, uint32_t length, checksum256* hash ); /** * Hashes `data` using `sha1` and stores result in memory pointed to by hash. @@ -152,7 +152,7 @@ void sha256( char* data, uint32_t length, checksum256* hash ); * eos_assert( calc_hash == hash, "invalid hash" ); * @endcode */ -void sha1( char* data, uint32_t length, checksum160* hash ); +void sha1( const char* data, uint32_t length, checksum160* hash ); /** * Hashes `data` using `sha512` and stores result in memory pointed to by hash. @@ -170,7 +170,7 @@ void sha1( char* data, uint32_t length, checksum160* hash ); * eos_assert( calc_hash == hash, "invalid hash" ); * @endcode */ -void sha512( char* data, uint32_t length, checksum512* hash ); +void sha512( const char* data, uint32_t length, checksum512* hash ); /** * Hashes `data` using `ripemod160` and stores result in memory pointed to by hash. @@ -188,7 +188,7 @@ void sha512( char* data, uint32_t length, checksum512* hash ); * eos_assert( calc_hash == hash, "invalid hash" ); * @endcode */ -void ripemd160( char* data, uint32_t length, checksum160* hash ); +void ripemd160( const char* data, uint32_t length, checksum160* hash ); /** * Calculates the public key used for a given signature and hash used to create a message. From d5eb2b7c78e847a1bc0e4e8000972244fad15e0e Mon Sep 17 00:00:00 2001 From: Spartucus Date: Tue, 7 Aug 2018 15:34:17 +0800 Subject: [PATCH 143/294] Fix null pointer dereference Although it has fewer chance chain_plugin is invalid, still it's safer to assert it. --- plugins/account_history_plugin/account_history_plugin.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/account_history_plugin/account_history_plugin.cpp b/plugins/account_history_plugin/account_history_plugin.cpp index bbaca847659..b135fd430ed 100644 --- a/plugins/account_history_plugin/account_history_plugin.cpp +++ b/plugins/account_history_plugin/account_history_plugin.cpp @@ -51,7 +51,7 @@ class account_history_plugin_impl { void applied_block(const chain::block_trace&); fc::variant transaction_to_variant(const packed_transaction& pretty_input) const; - chain_plugin* chain_plug; + chain_plugin* chain_plug = nullptr; static const int64_t DEFAULT_TRANSACTION_TIME_LIMIT; int64_t transactions_time_limit = DEFAULT_TRANSACTION_TIME_LIMIT; std::set filter_on; @@ -479,6 +479,7 @@ void account_history_plugin::plugin_initialize(const variables_map& options) } my->chain_plug = app().find_plugin(); + EOS_ASSERT( my->chain_plug, chain::missing_chain_plugin_exception, "" ); my->chain_plug->chain_config().applied_block_callbacks.emplace_back( [&impl = my](const chain::block_trace& trace) { impl->check_init_db(); From 4bef9038f48d54696c400b37d2c6b7eb332852a6 Mon Sep 17 00:00:00 2001 From: Spartucus Date: Tue, 7 Aug 2018 15:37:31 +0800 Subject: [PATCH 144/294] Fix null pointer dereference Although there are fewer chances that chain_plugin is invalid, still it's safer to assert it. --- plugins/history_plugin/history_plugin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/history_plugin/history_plugin.cpp b/plugins/history_plugin/history_plugin.cpp index d8af87408ec..9b1b505174a 100644 --- a/plugins/history_plugin/history_plugin.cpp +++ b/plugins/history_plugin/history_plugin.cpp @@ -286,6 +286,7 @@ namespace eosio { } my->chain_plug = app().find_plugin(); + EOS_ASSERT( my->chain_plug, chain::missing_chain_plugin_exception, "" ); auto& chain = my->chain_plug->chain(); chain.db().add_index(); From f256c1b357752c656fdd71de801d9718d1fe6906 Mon Sep 17 00:00:00 2001 From: Spartucus Date: Tue, 7 Aug 2018 15:44:25 +0800 Subject: [PATCH 145/294] Fix null pointer dereference Although there are fewer chances that chain_plugin is invalid, still it's safer to assert it. --- plugins/net_plugin/net_plugin.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 5363f035210..45b4380c604 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -171,7 +171,7 @@ namespace eosio { fc::sha256 node_id; string user_agent_name; - chain_plugin* chain_plug; + chain_plugin* chain_plug = nullptr; int started_sessions = 0; node_transaction_index local_txns; @@ -649,7 +649,7 @@ namespace eosio { connection_ptr source; stages state; - chain_plugin* chain_plug; + chain_plugin* chain_plug = nullptr; constexpr auto stage_str(stages s ); @@ -1255,6 +1255,7 @@ namespace eosio { ,state(in_sync) { chain_plug = app( ).find_plugin( ); + EOS_ASSERT( chain_plug, chain::missing_chain_plugin_exception, "" ); } constexpr auto sync_manager::stage_str(stages s ) { @@ -2995,6 +2996,7 @@ namespace eosio { } my->chain_plug = app().find_plugin(); + EOS_ASSERT( my->chain_plug, chain::missing_chain_plugin_exception, "" ); my->chain_id = app().get_plugin().get_chain_id(); fc::rand_pseudo_bytes( my->node_id.data(), my->node_id.data_size()); ilog( "my node_id is ${id}", ("id", my->node_id)); From 0c6edfaf39744f89cfb46fae40d0c1f02ea576d3 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 7 Aug 2018 08:50:34 -0500 Subject: [PATCH 146/294] Revert unneeded test changes --- unittests/api_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index bc51ac7abec..f9ea00dfc5f 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -1016,7 +1016,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try { auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t->scheduled) { trace = t; } } ); CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {} ); BOOST_CHECK(!trace); - produce_block( fc::seconds(3) ); + produce_block( fc::seconds(2) ); //check that it gets executed afterwards BOOST_REQUIRE(trace); @@ -1084,7 +1084,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try { auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t && t->scheduled) { trace = t; } } ); CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {}); CALL_TEST_FUNCTION(*this, "test_transaction", "cancel_deferred_transaction_success", {}); - produce_block( fc::seconds(3) ); + produce_block( fc::seconds(2) ); BOOST_CHECK(!trace); c.disconnect(); } From 31d8b7e058ddccb5a1fce90ed7c4f65a2d8bbad0 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 7 Aug 2018 09:15:56 -0500 Subject: [PATCH 147/294] Assert not implicit/scheduled on push_transaction --- libraries/chain/controller.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 547e6d4fde9..fb99664f7c5 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1347,6 +1347,7 @@ void controller::push_confirmation( const header_confirmation& c ) { transaction_trace_ptr controller::push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline, uint32_t billed_cpu_time_us ) { validate_db_available_size(); + EOS_ASSERT( trx && !trx->implicit && !trx->scheduled, transaction_type_exception, "Implicit/Scheduled transaction not allowed" ); return my->push_transaction(trx, deadline, billed_cpu_time_us, billed_cpu_time_us > 0 ); } From 07c997117acffb95a86c33d57ea98c59eca42e04 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Tue, 7 Aug 2018 11:49:47 -0400 Subject: [PATCH 148/294] Update asset.hpp --- contracts/eosiolib/asset.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/contracts/eosiolib/asset.hpp b/contracts/eosiolib/asset.hpp index ce9db086e65..482ea10b38f 100644 --- a/contracts/eosiolib/asset.hpp +++ b/contracts/eosiolib/asset.hpp @@ -173,10 +173,10 @@ namespace eosio { * @post The amount of this asset is multiplied by a */ asset& operator*=( int64_t a ) { - eosio_assert( a == 0 || (amount * a) / a == amount, "multiplication overflow or underflow" ); - amount *= a; - eosio_assert( -max_amount <= amount, "multiplication underflow" ); - eosio_assert( amount <= max_amount, "multiplication overflow" ); + uint128_t tmp = (uint128_t)amount * (uint128_t)a; + eosio_assert( tmp <= max_amount, "multiplication overflow" ); + eosio_assert( tmp >= -max_amount, "multiplication underflow" ); + amount = (int64_t)tmp; return *this; } @@ -218,6 +218,8 @@ namespace eosio { * @post The amount of this asset is divided by a */ asset& operator/=( int64_t a ) { + eosio_assert( a != 0, "divide by zero" ); + eosio_assert( !(amount == LONG_MIN && a == -1), "signed division overflow" ); amount /= a; return *this; } From bfe93348b56110502eabd8e582fc1bdb2ff5bb26 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Tue, 7 Aug 2018 11:58:54 -0400 Subject: [PATCH 149/294] used numeric_limits --- contracts/eosiolib/asset.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/eosiolib/asset.hpp b/contracts/eosiolib/asset.hpp index 482ea10b38f..880e476b51d 100644 --- a/contracts/eosiolib/asset.hpp +++ b/contracts/eosiolib/asset.hpp @@ -219,7 +219,7 @@ namespace eosio { */ asset& operator/=( int64_t a ) { eosio_assert( a != 0, "divide by zero" ); - eosio_assert( !(amount == LONG_MIN && a == -1), "signed division overflow" ); + eosio_assert( !(amount == std::numeric_limits::min() && a == -1), "signed division overflow" ); amount /= a; return *this; } From 8781da24945ed219da05248783b87a2ec09b6ed9 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Tue, 7 Aug 2018 13:17:57 -0400 Subject: [PATCH 150/294] Update asset.hpp --- contracts/eosiolib/asset.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/eosiolib/asset.hpp b/contracts/eosiolib/asset.hpp index 880e476b51d..c3e454384ff 100644 --- a/contracts/eosiolib/asset.hpp +++ b/contracts/eosiolib/asset.hpp @@ -173,7 +173,7 @@ namespace eosio { * @post The amount of this asset is multiplied by a */ asset& operator*=( int64_t a ) { - uint128_t tmp = (uint128_t)amount * (uint128_t)a; + int128_t tmp = (int128_t)amount * (int128_t)a; eosio_assert( tmp <= max_amount, "multiplication overflow" ); eosio_assert( tmp >= -max_amount, "multiplication underflow" ); amount = (int64_t)tmp; From 9550f290f92ca7b935dae16590e1ccead5c422ea Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 7 Aug 2018 12:42:29 -0500 Subject: [PATCH 151/294] Consistently emit before squash/undo --- libraries/chain/controller.cpp | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index fb99664f7c5..b4a29993c70 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -495,13 +495,6 @@ struct controller_impl { trx_context.billed_cpu_time_us, trace->net_usage ); fc::move_append( pending->_actions, move(trx_context.executed) ); - auto onetrx = std::make_shared( etrx ); - onetrx->accepted = true; - onetrx->implicit = true; - emit( self.accepted_transaction, onetrx ); - - emit( self.applied_transaction, trace ); - trx_context.squash(); restore.cancel(); return trace; @@ -576,9 +569,9 @@ struct controller_impl { trace->id = gtrx.trx_id; trace->scheduled = true; trace->receipt = push_receipt( gtrx.trx_id, transaction_receipt::expired, billed_cpu_time_us, 0 ); // expire the transaction - undo_session.squash(); emit( self.accepted_transaction, trx ); emit( self.applied_transaction, trace ); + undo_session.squash(); return trace; } @@ -609,13 +602,14 @@ struct controller_impl { fc::move_append( pending->_actions, move(trx_context.executed) ); + emit( self.accepted_transaction, trx ); + emit( self.applied_transaction, trace ); + trx_context.squash(); undo_session.squash(); restore.cancel(); - emit( self.accepted_transaction, trx ); - emit( self.applied_transaction, trace ); return trace; } catch( const fc::exception& e ) { cpu_time_to_bill_us = trx_context.update_billed_cpu_time( fc::time_point::now() ); @@ -634,9 +628,9 @@ struct controller_impl { error_trace->failed_dtrx_trace = trace; trace = error_trace; if( !trace->except_ptr ) { - undo_session.squash(); emit( self.accepted_transaction, trx ); emit( self.applied_transaction, trace ); + undo_session.squash(); return trace; } trace->elapsed = fc::time_point::now() - trx_context.start; @@ -662,11 +656,18 @@ struct controller_impl { block_timestamp_type(self.pending_block_time()).slot ); // Should never fail trace->receipt = push_receipt(gtrx.trx_id, transaction_receipt::hard_fail, cpu_time_to_bill_us, 0); + + emit( self.accepted_transaction, trx ); + emit( self.applied_transaction, trace ); + undo_session.squash(); + } else { + emit( self.accepted_transaction, trx ); + emit( self.applied_transaction, trace ); + + undo_session.undo(); } - emit( self.accepted_transaction, trx ); - emit( self.applied_transaction, trace ); return trace; } FC_CAPTURE_AND_RETHROW() } /// push_scheduled_transaction @@ -789,6 +790,9 @@ struct controller_impl { unapplied_transactions.erase( trx->signed_id ); } + emit( self.accepted_transaction, trx ); + emit( self.applied_transaction, trace ); + return trace; } FC_CAPTURE_AND_RETHROW((trace)) } /// push_transaction From ff044a9b9bf553f25e1c5b5e24aa20dba3486083 Mon Sep 17 00:00:00 2001 From: Eugene Chung Date: Tue, 7 Aug 2018 16:51:34 -0700 Subject: [PATCH 152/294] Use eosio::chain::config::system_account_name constant instead of N(eosio) --- libraries/chain/controller.cpp | 2 +- libraries/testing/tester.cpp | 2 +- plugins/chain_plugin/chain_plugin.cpp | 12 ++++++---- unittests/api_tests.cpp | 8 +++---- unittests/block_tests.cpp | 4 ++-- unittests/bootseq_tests.cpp | 20 ++++++++-------- unittests/dice_tests.cpp | 6 ++--- unittests/forked_tests.cpp | 2 +- unittests/misc_tests.cpp | 4 ++-- unittests/multisig_tests.cpp | 32 ++++++++++++------------- unittests/ram_tests.cpp | 18 +++++++------- unittests/whitelist_blacklist_tests.cpp | 12 +++++----- 12 files changed, 62 insertions(+), 60 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 87a91949c80..09bc5619936 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -317,7 +317,7 @@ struct controller_impl { */ void initialize_fork_db() { wlog( " Initializing new blockchain with genesis state " ); - producer_schedule_type initial_schedule{ 0, {{N(eosio), conf.genesis.initial_key}} }; + producer_schedule_type initial_schedule{ 0, {{config::system_account_name, conf.genesis.initial_key}} }; block_header_state genheader; genheader.active_schedule = initial_schedule; diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index 82bc34efdaa..051d598d2a6 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -826,7 +826,7 @@ namespace eosio { namespace testing { transaction_trace_ptr base_tester::set_producers(const vector& producer_names) { auto schedule = get_producer_keys( producer_names ); - return push_action( N(eosio), N(setprods), N(eosio), + return push_action( config::system_account_name, N(setprods), config::system_account_name, fc::mutable_variant_object()("schedule", schedule)); } diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 894ee3986f0..4ff87f4a9b2 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1094,7 +1094,7 @@ static fc::variant get_global_row( const database& db, const abi_def& abi, const const auto table_type = get_table_type(abi, N(global)); EOS_ASSERT(table_type == read_only::KEYi64, chain::contract_table_query_exception, "Invalid table type ${type} for table global", ("type",table_type)); - const auto* const table_id = db.find(boost::make_tuple(N(eosio), N(eosio), N(global))); + const auto* const table_id = db.find(boost::make_tuple(config::system_account_name, config::system_account_name, N(global))); EOS_ASSERT(table_id, chain::contract_table_query_exception, "Missing table global"); const auto& kv_index = db.get_index(); @@ -1107,7 +1107,7 @@ static fc::variant get_global_row( const database& db, const abi_def& abi, const } read_only::get_producers_result read_only::get_producers( const read_only::get_producers_params& p ) const { - const abi_def abi = eosio::chain_apis::get_abi(db, N(eosio)); + const abi_def abi = eosio::chain_apis::get_abi(db, config::system_account_name); const auto table_type = get_table_type(abi, N(producers)); const abi_serializer abis{ abi, abi_serializer_max_time }; EOS_ASSERT(table_type == KEYi64, chain::contract_table_query_exception, "Invalid table type ${type} for table producers", ("type",table_type)); @@ -1116,8 +1116,10 @@ read_only::get_producers_result read_only::get_producers( const read_only::get_p const auto lower = name{p.lower_bound}; static const uint8_t secondary_index_num = 0; - const auto* const table_id = d.find(boost::make_tuple(N(eosio), N(eosio), N(producers))); - const auto* const secondary_table_id = d.find(boost::make_tuple(N(eosio), N(eosio), N(producers) | secondary_index_num)); + const auto* const table_id = d.find( + boost::make_tuple(config::system_account_name, config::system_account_name, N(producers))); + const auto* const secondary_table_id = d.find( + boost::make_tuple(config::system_account_name, config::system_account_name, N(producers) | secondary_index_num)); EOS_ASSERT(table_id && secondary_table_id, chain::contract_table_query_exception, "Missing producers table"); const auto& kv_index = d.get_index(); @@ -1474,7 +1476,7 @@ read_only::get_account_results read_only::get_account( const get_account_params& ++perm; } - const auto& code_account = db.db().get( N(eosio) ); + const auto& code_account = db.db().get( config::system_account_name ); abi_def abi; if( abi_serializer::to_abi(code_account.abi, abi) ) { diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 94dbf91c1f8..6c54ca3e884 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -182,7 +182,7 @@ transaction_trace_ptr CallFunction(TESTER& test, T ac, const vector& data, } #define CALL_TEST_FUNCTION(_TESTER, CLS, MTH, DATA) CallFunction(_TESTER, test_api_action{}, DATA) -#define CALL_TEST_FUNCTION_SYSTEM(_TESTER, CLS, MTH, DATA) CallFunction(_TESTER, test_chain_action{}, DATA, {N(eosio)} ) +#define CALL_TEST_FUNCTION_SYSTEM(_TESTER, CLS, MTH, DATA) CallFunction(_TESTER, test_chain_action{}, DATA, {config::system_account_name} ) #define CALL_TEST_FUNCTION_SCOPE(_TESTER, CLS, MTH, DATA, ACCOUNT) CallFunction(_TESTER, test_api_action{}, DATA, ACCOUNT) #define CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION(_TESTER, CLS, MTH, DATA, EXC, EXC_MESSAGE) \ BOOST_CHECK_EXCEPTION( \ @@ -576,7 +576,7 @@ BOOST_FIXTURE_TEST_CASE(cfa_stateful_api, TESTER) try { set_code( N(testapi), test_api_wast ); account_name a = N(testapi2); - account_name creator = N(eosio); + account_name creator = config::system_account_name; signed_transaction trx; @@ -606,7 +606,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_cfa_failed, TESTER) try { set_code( N(testapi), test_api_wast ); account_name a = N(testapi2); - account_name creator = N(eosio); + account_name creator = config::system_account_name; signed_transaction trx; @@ -642,7 +642,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_cfa_success, TESTER) try { set_code( N(testapi), test_api_wast ); account_name a = N(testapi2); - account_name creator = N(eosio); + account_name creator = config::system_account_name; signed_transaction trx; diff --git a/unittests/block_tests.cpp b/unittests/block_tests.cpp index ebece74deca..f196dbdae93 100644 --- a/unittests/block_tests.cpp +++ b/unittests/block_tests.cpp @@ -30,7 +30,7 @@ BOOST_AUTO_TEST_CASE(block_with_invalid_tx_test) act.data = fc::raw::pack(act_data); // Re-sign the transaction signed_tx.signatures.clear(); - signed_tx.sign(main.get_private_key(N(eosio), "active"), main.control->get_chain_id()); + signed_tx.sign(main.get_private_key(config::system_account_name, "active"), main.control->get_chain_id()); // Replace the valid transaction with the invalid transaction auto invalid_packed_tx = packed_transaction(signed_tx); copy_b->transactions.back().trx = invalid_packed_tx; @@ -38,7 +38,7 @@ BOOST_AUTO_TEST_CASE(block_with_invalid_tx_test) // Re-sign the block auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), main.control->head_block_state()->blockroot_merkle.get_root() ) ); auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, main.control->head_block_state()->pending_schedule_hash) ); - copy_b->producer_signature = main.get_private_key(N(eosio), "active").sign(sig_digest); + copy_b->producer_signature = main.get_private_key(config::system_account_name, "active").sign(sig_digest); // Push block with invalid transaction to other chain tester validator; diff --git a/unittests/bootseq_tests.cpp b/unittests/bootseq_tests.cpp index 204444e1168..a9d97db8580 100644 --- a/unittests/bootseq_tests.cpp +++ b/unittests/bootseq_tests.cpp @@ -74,14 +74,14 @@ class bootseq_tester : public TESTER { public: fc::variant get_global_state() { - vector data = get_row_by_account( N(eosio), N(eosio), N(global), N(global) ); + vector data = get_row_by_account( config::system_account_name, config::system_account_name, N(global), N(global) ); if (data.empty()) std::cout << "\nData is empty\n" << std::endl; return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "eosio_global_state", data, abi_serializer_max_time ); } auto buyram( name payer, name receiver, asset ram ) { - auto r = base_tester::push_action(N(eosio), N(buyram), payer, mvo() + auto r = base_tester::push_action(config::system_account_name, N(buyram), payer, mvo() ("payer", payer) ("receiver", receiver) ("quant", ram) @@ -91,7 +91,7 @@ class bootseq_tester : public TESTER { } auto delegate_bandwidth( name from, name receiver, asset net, asset cpu, uint8_t transfer = 1) { - auto r = base_tester::push_action(N(eosio), N(delegatebw), from, mvo() + auto r = base_tester::push_action(config::system_account_name, N(delegatebw), from, mvo() ("from", from ) ("receiver", receiver) ("stake_net_quantity", net) @@ -133,7 +133,7 @@ class bootseq_tester : public TESTER { } auto register_producer(name producer) { - auto r = base_tester::push_action(N(eosio), N(regproducer), producer, mvo() + auto r = base_tester::push_action(config::system_account_name, N(regproducer), producer, mvo() ("producer", name(producer)) ("producer_key", get_public_key( producer, "active" ) ) ("url", "" ) @@ -145,7 +145,7 @@ class bootseq_tester : public TESTER { auto undelegate_bandwidth( name from, name receiver, asset net, asset cpu ) { - auto r = base_tester::push_action(N(eosio), N(undelegatebw), from, mvo() + auto r = base_tester::push_action(config::system_account_name, N(undelegatebw), from, mvo() ("from", from ) ("receiver", receiver) ("unstake_net_quantity", net) @@ -163,7 +163,7 @@ class bootseq_tester : public TESTER { wdump((account)); set_code(account, wast, signer); set_abi(account, abi, signer); - if (account == N(eosio)) { + if (account == config::system_account_name) { const auto& accnt = control->db().get( account ); abi_def abi_definition; BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi_definition), true); @@ -214,11 +214,11 @@ BOOST_FIXTURE_TEST_CASE( bootseq_test, bootseq_tester ) { // Create genesis accounts for( const auto& a : test_genesis ) { - create_account( a.aname, N(eosio) ); + create_account( a.aname, config::system_account_name ); } // Set eosio.system to eosio - set_code_abi(N(eosio), eosio_system_wast, eosio_system_abi); + set_code_abi(config::system_account_name, eosio_system_wast, eosio_system_abi); // Buy ram and stake cpu and net for each genesis accounts for( const auto& a : test_genesis ) { @@ -227,7 +227,7 @@ BOOST_FIXTURE_TEST_CASE( bootseq_test, bootseq_tester ) { auto net = (ib - ram) / 2; auto cpu = ib - net - ram; - auto r = buyram(N(eosio), a.aname, asset(ram)); + auto r = buyram(config::system_account_name, a.aname, asset(ram)); BOOST_REQUIRE( !r->except_ptr ); r = delegate_bandwidth(N(eosio.stake), a.aname, asset(net), asset(cpu)); @@ -249,7 +249,7 @@ BOOST_FIXTURE_TEST_CASE( bootseq_test, bootseq_tester ) { // Vote for producers auto votepro = [&]( account_name voter, vector producers ) { std::sort( producers.begin(), producers.end() ); - base_tester::push_action(N(eosio), N(voteproducer), voter, mvo() + base_tester::push_action(config::system_account_name, N(voteproducer), voter, mvo() ("voter", name(voter)) ("proxy", name(0) ) ("producers", producers) diff --git a/unittests/dice_tests.cpp b/unittests/dice_tests.cpp index c1bc3075865..cb63511d39c 100644 --- a/unittests/dice_tests.cpp +++ b/unittests/dice_tests.cpp @@ -246,9 +246,9 @@ BOOST_FIXTURE_TEST_CASE( dice_test, dice_tester ) try { ("memo", "") ); - transfer( N(eosio), N(alice), core_from_string("10000.0000"), "", N(eosio.token) ); - transfer( N(eosio), N(bob), core_from_string("10000.0000"), "", N(eosio.token) ); - transfer( N(eosio), N(carol), core_from_string("10000.0000"), "", N(eosio.token) ); + transfer( config::system_account_name, N(alice), core_from_string("10000.0000"), "", N(eosio.token) ); + transfer( config::system_account_name, N(bob), core_from_string("10000.0000"), "", N(eosio.token) ); + transfer( config::system_account_name, N(carol), core_from_string("10000.0000"), "", N(eosio.token) ); produce_block(); diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index 52fbefdb6f8..a196a1a58fe 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -170,7 +170,7 @@ BOOST_AUTO_TEST_CASE( forking ) try { wdump((fc::json::to_pretty_string(cr))); - cr = c.push_action( N(eosio.token), N(issue), N(eosio), mutable_variant_object() + cr = c.push_action( N(eosio.token), N(issue), config::system_account_name, mutable_variant_object() ("to", "dan" ) ("quantity", core_from_string("100.0000")) ("memo", "") diff --git a/unittests/misc_tests.cpp b/unittests/misc_tests.cpp index 8b0bda313cd..9f87bdbeb2d 100644 --- a/unittests/misc_tests.cpp +++ b/unittests/misc_tests.cpp @@ -621,9 +621,9 @@ BOOST_AUTO_TEST_CASE(transaction_test) { try { trx.expiration = fc::time_point::now(); trx.validate(); BOOST_CHECK_EQUAL(0, trx.signatures.size()); - ((const signed_transaction &)trx).sign( test.get_private_key( N(eosio), "active" ), test.control->get_chain_id()); + ((const signed_transaction &)trx).sign( test.get_private_key( config::system_account_name, "active" ), test.control->get_chain_id()); BOOST_CHECK_EQUAL(0, trx.signatures.size()); - trx.sign( test.get_private_key( N(eosio), "active" ), test.control->get_chain_id() ); + trx.sign( test.get_private_key( config::system_account_name, "active" ), test.control->get_chain_id() ); BOOST_CHECK_EQUAL(1, trx.signatures.size()); trx.validate(); diff --git a/unittests/multisig_tests.cpp b/unittests/multisig_tests.cpp index 98a16efdf26..debf5d95f7c 100644 --- a/unittests/multisig_tests.cpp +++ b/unittests/multisig_tests.cpp @@ -73,14 +73,14 @@ class eosio_msig_tester : public tester { .active = authority( get_public_key( a, "active" ) ) }); - trx.actions.emplace_back( get_action( N(eosio), N(buyram), vector{{creator,config::active_name}}, + trx.actions.emplace_back( get_action( config::system_account_name, N(buyram), vector{{creator,config::active_name}}, mvo() ("payer", creator) ("receiver", a) ("quant", ramfunds) ) ); - trx.actions.emplace_back( get_action( N(eosio), N(delegatebw), vector{{creator,config::active_name}}, + trx.actions.emplace_back( get_action( config::system_account_name, N(delegatebw), vector{{creator,config::active_name}}, mvo() ("from", creator) ("receiver", a) @@ -394,10 +394,10 @@ BOOST_FIXTURE_TEST_CASE( update_system_contract_all_approve, eosio_msig_tester ) // / | \ <--- implicitly updated in onblock action // alice active bob active carol active - set_authority(N(eosio), "active", authority(1, + set_authority(config::system_account_name, "active", authority(1, vector{{get_private_key("eosio", "active").get_public_key(), 1}}, vector{{{N(eosio.prods), config::active_name}, 1}}), "owner", - { { N(eosio), "active" } }, { get_private_key( N(eosio), "active" ) }); + { { config::system_account_name, "active" } }, { get_private_key( config::system_account_name, "active" ) }); set_producers( {N(alice),N(bob),N(carol)} ); produce_blocks(50); @@ -416,9 +416,9 @@ BOOST_FIXTURE_TEST_CASE( update_system_contract_all_approve, eosio_msig_tester ) produce_blocks(); - create_account_with_resources( N(alice1111111), N(eosio), core_from_string("1.0000"), false ); - create_account_with_resources( N(bob111111111), N(eosio), core_from_string("0.4500"), false ); - create_account_with_resources( N(carol1111111), N(eosio), core_from_string("1.0000"), false ); + create_account_with_resources( N(alice1111111), config::system_account_name, core_from_string("1.0000"), false ); + create_account_with_resources( N(bob111111111), config::system_account_name, core_from_string("0.4500"), false ); + create_account_with_resources( N(carol1111111), config::system_account_name, core_from_string("1.0000"), false ); BOOST_REQUIRE_EQUAL( core_from_string("1000000000.0000"), get_balance("eosio") + get_balance("eosio.ramfee") + get_balance("eosio.stake") + get_balance("eosio.ram") ); @@ -426,7 +426,7 @@ BOOST_FIXTURE_TEST_CASE( update_system_contract_all_approve, eosio_msig_tester ) vector perm = { { N(alice), config::active_name }, { N(bob), config::active_name }, {N(carol), config::active_name} }; - vector action_perm = {{N(eosio), config::active_name}}; + vector action_perm = {{config::system_account_name, config::active_name}}; auto wasm = wast_to_wasm( test_api_wast ); @@ -496,7 +496,7 @@ BOOST_FIXTURE_TEST_CASE( update_system_contract_all_approve, eosio_msig_tester ) // can't create account because system contract was replace by the test_api contract - BOOST_REQUIRE_EXCEPTION( create_account_with_resources( N(alice1111112), N(eosio), core_from_string("1.0000"), false ), + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( N(alice1111112), config::system_account_name, core_from_string("1.0000"), false ), eosio_assert_message_exception, eosio_assert_message_is("Unknown Test") ); @@ -505,10 +505,10 @@ BOOST_FIXTURE_TEST_CASE( update_system_contract_all_approve, eosio_msig_tester ) BOOST_FIXTURE_TEST_CASE( update_system_contract_major_approve, eosio_msig_tester ) try { // set up the link between (eosio active) and (eosio.prods active) - set_authority(N(eosio), "active", authority(1, + set_authority(config::system_account_name, "active", authority(1, vector{{get_private_key("eosio", "active").get_public_key(), 1}}, vector{{{N(eosio.prods), config::active_name}, 1}}), "owner", - { { N(eosio), "active" } }, { get_private_key( N(eosio), "active" ) }); + { { config::system_account_name, "active" } }, { get_private_key( config::system_account_name, "active" ) }); create_accounts( { N(apple) } ); set_producers( {N(alice),N(bob),N(carol), N(apple)} ); @@ -527,9 +527,9 @@ BOOST_FIXTURE_TEST_CASE( update_system_contract_major_approve, eosio_msig_tester produce_blocks(); - create_account_with_resources( N(alice1111111), N(eosio), core_from_string("1.0000"), false ); - create_account_with_resources( N(bob111111111), N(eosio), core_from_string("0.4500"), false ); - create_account_with_resources( N(carol1111111), N(eosio), core_from_string("1.0000"), false ); + create_account_with_resources( N(alice1111111), config::system_account_name, core_from_string("1.0000"), false ); + create_account_with_resources( N(bob111111111), config::system_account_name, core_from_string("0.4500"), false ); + create_account_with_resources( N(carol1111111), config::system_account_name, core_from_string("1.0000"), false ); BOOST_REQUIRE_EQUAL( core_from_string("1000000000.0000"), get_balance("eosio") + get_balance("eosio.ramfee") + get_balance("eosio.stake") + get_balance("eosio.ram") ); @@ -537,7 +537,7 @@ BOOST_FIXTURE_TEST_CASE( update_system_contract_major_approve, eosio_msig_tester vector perm = { { N(alice), config::active_name }, { N(bob), config::active_name }, {N(carol), config::active_name}, {N(apple), config::active_name}}; - vector action_perm = {{N(eosio), config::active_name}}; + vector action_perm = {{config::system_account_name, config::active_name}}; auto wasm = wast_to_wasm( test_api_wast ); @@ -619,7 +619,7 @@ BOOST_FIXTURE_TEST_CASE( update_system_contract_major_approve, eosio_msig_tester // can't create account because system contract was replace by the test_api contract - BOOST_REQUIRE_EXCEPTION( create_account_with_resources( N(alice1111112), N(eosio), core_from_string("1.0000"), false ), + BOOST_REQUIRE_EXCEPTION( create_account_with_resources( N(alice1111112), config::system_account_name, core_from_string("1.0000"), false ), eosio_assert_message_exception, eosio_assert_message_is("Unknown Test") ); diff --git a/unittests/ram_tests.cpp b/unittests/ram_tests.cpp index 1f4bad446e7..14212b5b62f 100644 --- a/unittests/ram_tests.cpp +++ b/unittests/ram_tests.cpp @@ -37,10 +37,10 @@ BOOST_FIXTURE_TEST_CASE(ram_tests, eosio_system::eosio_system_tester) { try { const auto increment_contract_bytes = 10000; const auto table_allocation_bytes = 12000; BOOST_REQUIRE_MESSAGE(table_allocation_bytes > increment_contract_bytes, "increment_contract_bytes must be less than table_allocation_bytes for this test setup to work"); - buyrambytes(N(eosio), N(eosio), 70000); + buyrambytes(config::system_account_name, config::system_account_name, 70000); produce_blocks(10); - create_account_with_resources(N(testram11111),N(eosio), init_request_bytes + 40); - create_account_with_resources(N(testram22222),N(eosio), init_request_bytes + 1190); + create_account_with_resources(N(testram11111),config::system_account_name, init_request_bytes + 40); + create_account_with_resources(N(testram22222),config::system_account_name, init_request_bytes + 1190); produce_blocks(10); BOOST_REQUIRE_EQUAL( success(), stake( "eosio.stake", "testram11111", core_from_string("10.0000"), core_from_string("5.0000") ) ); produce_blocks(10); @@ -51,8 +51,8 @@ BOOST_FIXTURE_TEST_CASE(ram_tests, eosio_system::eosio_system_tester) { try { break; } catch (const ram_usage_exceeded&) { init_request_bytes += increment_contract_bytes; - buyrambytes(N(eosio), N(testram11111), increment_contract_bytes); - buyrambytes(N(eosio), N(testram22222), increment_contract_bytes); + buyrambytes(config::system_account_name, N(testram11111), increment_contract_bytes); + buyrambytes(config::system_account_name, N(testram22222), increment_contract_bytes); } } produce_blocks(10); @@ -63,8 +63,8 @@ BOOST_FIXTURE_TEST_CASE(ram_tests, eosio_system::eosio_system_tester) { try { break; } catch (const ram_usage_exceeded&) { init_request_bytes += increment_contract_bytes; - buyrambytes(N(eosio), N(testram11111), increment_contract_bytes); - buyrambytes(N(eosio), N(testram22222), increment_contract_bytes); + buyrambytes(config::system_account_name, N(testram11111), increment_contract_bytes); + buyrambytes(config::system_account_name, N(testram22222), increment_contract_bytes); } } produce_blocks(10); @@ -82,8 +82,8 @@ BOOST_FIXTURE_TEST_CASE(ram_tests, eosio_system::eosio_system_tester) { try { auto more_ram = table_allocation_bytes + init_bytes - init_request_bytes; BOOST_REQUIRE_MESSAGE(more_ram >= 0, "Underlying understanding changed, need to reduce size of init_request_bytes"); wdump((init_bytes)(initial_ram_usage)(init_request_bytes)(more_ram) ); - buyrambytes(N(eosio), N(testram11111), more_ram); - buyrambytes(N(eosio), N(testram22222), more_ram); + buyrambytes(config::system_account_name, N(testram11111), more_ram); + buyrambytes(config::system_account_name, N(testram22222), more_ram); TESTER* tester = this; // allocate just under the allocated bytes diff --git a/unittests/whitelist_blacklist_tests.cpp b/unittests/whitelist_blacklist_tests.cpp index fa26a4a2146..ed359a703a7 100644 --- a/unittests/whitelist_blacklist_tests.cpp +++ b/unittests/whitelist_blacklist_tests.cpp @@ -123,7 +123,7 @@ BOOST_AUTO_TEST_SUITE(whitelist_blacklist_tests) BOOST_AUTO_TEST_CASE( actor_whitelist ) { try { whitelist_blacklist_tester<> test; - test.actor_whitelist = {N(eosio), N(eosio.token), N(alice)}; + test.actor_whitelist = {config::system_account_name, N(eosio.token), N(alice)}; test.init(); test.transfer( N(eosio.token), N(alice), "1000.00 TOK" ); @@ -190,7 +190,7 @@ BOOST_AUTO_TEST_CASE( actor_blacklist ) { try { BOOST_AUTO_TEST_CASE( contract_whitelist ) { try { whitelist_blacklist_tester<> test; - test.contract_whitelist = {N(eosio), N(eosio.token), N(bob)}; + test.contract_whitelist = {config::system_account_name, N(eosio.token), N(bob)}; test.init(); test.transfer( N(eosio.token), N(alice), "1000.00 TOK" ); @@ -288,7 +288,7 @@ BOOST_AUTO_TEST_CASE( contract_blacklist ) { try { BOOST_AUTO_TEST_CASE( action_blacklist ) { try { whitelist_blacklist_tester<> test; - test.contract_whitelist = {N(eosio), N(eosio.token), N(bob), N(charlie)}; + test.contract_whitelist = {config::system_account_name, N(eosio.token), N(bob), N(charlie)}; test.action_blacklist = {{N(charlie), N(create)}}; test.init(); @@ -329,10 +329,10 @@ BOOST_AUTO_TEST_CASE( blacklist_eosio ) { try { whitelist_blacklist_tester tester1; tester1.init(); tester1.chain->produce_blocks(); - tester1.chain->set_code(N(eosio), eosio_token_wast); + tester1.chain->set_code(config::system_account_name, eosio_token_wast); tester1.chain->produce_blocks(); tester1.shutdown(); - tester1.contract_blacklist = {N(eosio)}; + tester1.contract_blacklist = {config::system_account_name}; tester1.init(false); whitelist_blacklist_tester tester2; @@ -422,7 +422,7 @@ BOOST_AUTO_TEST_CASE( blacklist_onerror ) { try { tester1.chain->produce_blocks(); tester1.shutdown(); - tester1.action_blacklist = {{N(eosio), N(onerror)}}; + tester1.action_blacklist = {{config::system_account_name, N(onerror)}}; tester1.init(false); tester1.chain->push_action( N(bob), N(defercall), N(alice), mvo() From b9ce3fb195c7fc5734c906df271343735c1e1be8 Mon Sep 17 00:00:00 2001 From: Scott Sallinen Date: Tue, 7 Aug 2018 16:47:36 -0700 Subject: [PATCH 153/294] Log which peer gives unexpected length messages This allows a quicker understanding of which peer is misbehaving, allowing faster turnaround time to blocking bad peers. --- plugins/net_plugin/net_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 5363f035210..cd999ddccac 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -2122,7 +2122,7 @@ namespace eosio { auto index = conn->pending_message_buffer.read_index(); conn->pending_message_buffer.peek(&message_length, sizeof(message_length), index); if(message_length > def_send_buffer_size*2 || message_length == 0) { - elog("incoming message length unexpected (${i})", ("i", message_length)); + elog("incoming message length unexpected (${i}), from ${p}", ("i", message_length)("p",boost::lexical_cast(conn->socket->remote_endpoint()))); close(conn); return; } From 09412d55f470bde516dd1d5264b4c3a71f51c936 Mon Sep 17 00:00:00 2001 From: Eugene Chung Date: Tue, 7 Aug 2018 18:13:23 -0700 Subject: [PATCH 154/294] Make customization point for executable names --- CMakeLists.txt | 2 ++ programs/cleos/CMakeLists.txt | 10 +++++----- programs/keosd/CMakeLists.txt | 10 +++++----- programs/nodeos/CMakeLists.txt | 16 ++++++++-------- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a767c0f3abc..5a8aa2d780e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,8 @@ set(VERSION_MINOR 1) set(VERSION_PATCH 3) set( CLI_CLIENT_EXECUTABLE_NAME cleos ) +set( NODE_EXECUTABLE_NAME nodeos ) +set( KEY_STORE_EXECUTABLE_NAME keosd ) set( GUI_CLIENT_EXECUTABLE_NAME eosio ) set( CUSTOM_URL_SCHEME "gcs" ) set( INSTALLER_APP_ID "68ad7005-8eee-49c9-95ce-9eed97e5b347" ) diff --git a/programs/cleos/CMakeLists.txt b/programs/cleos/CMakeLists.txt index 938032d1f19..2581a15bd4f 100644 --- a/programs/cleos/CMakeLists.txt +++ b/programs/cleos/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable( cleos main.cpp httpc.cpp help_text.cpp localize.hpp config.hpp CLI11.hpp) +add_executable( ${CLI_CLIENT_EXECUTABLE_NAME} main.cpp httpc.cpp help_text.cpp localize.hpp config.hpp CLI11.hpp) if( UNIX AND NOT APPLE ) set(rt_library rt ) endif() @@ -29,16 +29,16 @@ endif() find_package(Intl REQUIRED) set(LOCALEDIR ${CMAKE_INSTALL_PREFIX}/share/locale) -set(LOCALEDOMAIN cleos) +set(LOCALEDOMAIN ${CLI_CLIENT_EXECUTABLE_NAME}) configure_file(config.hpp.in config.hpp ESCAPE_QUOTES) -target_include_directories(cleos PUBLIC ${Intl_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) +target_include_directories(${CLI_CLIENT_EXECUTABLE_NAME} PUBLIC ${Intl_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) -target_link_libraries( cleos +target_link_libraries( ${CLI_CLIENT_EXECUTABLE_NAME} PRIVATE appbase chain_api_plugin producer_plugin chain_plugin http_plugin eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ${Intl_LIBRARIES} ) install( TARGETS - cleos + ${CLI_CLIENT_EXECUTABLE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} diff --git a/programs/keosd/CMakeLists.txt b/programs/keosd/CMakeLists.txt index de6b09d98bc..ac434e92c71 100644 --- a/programs/keosd/CMakeLists.txt +++ b/programs/keosd/CMakeLists.txt @@ -1,24 +1,24 @@ -add_executable( keosd main.cpp ) +add_executable( ${KEY_STORE_EXECUTABLE_NAME} main.cpp ) if( UNIX AND NOT APPLE ) set(rt_library rt ) endif() find_package( Gperftools QUIET ) if( GPERFTOOLS_FOUND ) - message( STATUS "Found gperftools; compiling keosd with TCMalloc") + message( STATUS "Found gperftools; compiling ${KEY_STORE_EXECUTABLE_NAME} with TCMalloc") list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc ) endif() -target_link_libraries( keosd +target_link_libraries( ${KEY_STORE_EXECUTABLE_NAME} PRIVATE appbase PRIVATE wallet_api_plugin wallet_plugin PRIVATE http_plugin PRIVATE eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) -mas_sign(keosd) +mas_sign(${KEY_STORE_EXECUTABLE_NAME}) install( TARGETS - keosd + ${KEY_STORE_EXECUTABLE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 1ab5850ef9b..02723c6692f 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -1,11 +1,11 @@ -add_executable( nodeos main.cpp ) +add_executable( ${NODE_EXECUTABLE_NAME} main.cpp ) if( UNIX AND NOT APPLE ) set(rt_library rt ) endif() find_package( Gperftools QUIET ) if( GPERFTOOLS_FOUND ) - message( STATUS "Found gperftools; compiling nodeos with TCMalloc") + message( STATUS "Found gperftools; compiling ${NODE_EXECUTABLE_NAME} with TCMalloc") list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc ) endif() @@ -28,7 +28,7 @@ endif() configure_file(config.hpp.in config.hpp ESCAPE_QUOTES) -target_include_directories(nodeos PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) +target_include_directories(${NODE_EXECUTABLE_NAME} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) if(UNIX) if(APPLE) @@ -43,7 +43,7 @@ else() set(no_whole_archive_flag "--no-whole-archive") endif() -target_link_libraries( nodeos +target_link_libraries( ${NODE_EXECUTABLE_NAME} PRIVATE appbase PRIVATE -Wl,${whole_archive_flag} login_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} history_plugin -Wl,${no_whole_archive_flag} @@ -61,15 +61,15 @@ target_link_libraries( nodeos PRIVATE eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) if(TARGET sql_db_plugin) - target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} sql_db_plugin -Wl,${no_whole_archive_flag} ) + target_link_libraries( ${NODE_EXECUTABLE_NAME} PRIVATE -Wl,${whole_archive_flag} sql_db_plugin -Wl,${no_whole_archive_flag} ) endif() if(BUILD_MONGO_DB_PLUGIN) - target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} mongo_db_plugin -Wl,${no_whole_archive_flag} ) + target_link_libraries( ${NODE_EXECUTABLE_NAME} PRIVATE -Wl,${whole_archive_flag} mongo_db_plugin -Wl,${no_whole_archive_flag} ) endif() install( TARGETS - nodeos + ${NODE_EXECUTABLE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} @@ -96,4 +96,4 @@ install(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/eosio WORLD_EXECUTE ) -mas_sign(nodeos) \ No newline at end of file +mas_sign(${NODE_EXECUTABLE_NAME}) \ No newline at end of file From 653b1f30c6486986743bf5c2790b3e23612e4bfa Mon Sep 17 00:00:00 2001 From: Eugene Chung Date: Tue, 7 Aug 2018 18:13:23 -0700 Subject: [PATCH 155/294] Make customization point for executable names --- CMakeLists.txt | 2 ++ programs/cleos/CMakeLists.txt | 10 +++++----- programs/keosd/CMakeLists.txt | 10 +++++----- programs/nodeos/CMakeLists.txt | 16 ++++++++-------- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a767c0f3abc..5a8aa2d780e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,8 @@ set(VERSION_MINOR 1) set(VERSION_PATCH 3) set( CLI_CLIENT_EXECUTABLE_NAME cleos ) +set( NODE_EXECUTABLE_NAME nodeos ) +set( KEY_STORE_EXECUTABLE_NAME keosd ) set( GUI_CLIENT_EXECUTABLE_NAME eosio ) set( CUSTOM_URL_SCHEME "gcs" ) set( INSTALLER_APP_ID "68ad7005-8eee-49c9-95ce-9eed97e5b347" ) diff --git a/programs/cleos/CMakeLists.txt b/programs/cleos/CMakeLists.txt index 938032d1f19..2581a15bd4f 100644 --- a/programs/cleos/CMakeLists.txt +++ b/programs/cleos/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable( cleos main.cpp httpc.cpp help_text.cpp localize.hpp config.hpp CLI11.hpp) +add_executable( ${CLI_CLIENT_EXECUTABLE_NAME} main.cpp httpc.cpp help_text.cpp localize.hpp config.hpp CLI11.hpp) if( UNIX AND NOT APPLE ) set(rt_library rt ) endif() @@ -29,16 +29,16 @@ endif() find_package(Intl REQUIRED) set(LOCALEDIR ${CMAKE_INSTALL_PREFIX}/share/locale) -set(LOCALEDOMAIN cleos) +set(LOCALEDOMAIN ${CLI_CLIENT_EXECUTABLE_NAME}) configure_file(config.hpp.in config.hpp ESCAPE_QUOTES) -target_include_directories(cleos PUBLIC ${Intl_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) +target_include_directories(${CLI_CLIENT_EXECUTABLE_NAME} PUBLIC ${Intl_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) -target_link_libraries( cleos +target_link_libraries( ${CLI_CLIENT_EXECUTABLE_NAME} PRIVATE appbase chain_api_plugin producer_plugin chain_plugin http_plugin eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ${Intl_LIBRARIES} ) install( TARGETS - cleos + ${CLI_CLIENT_EXECUTABLE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} diff --git a/programs/keosd/CMakeLists.txt b/programs/keosd/CMakeLists.txt index de6b09d98bc..ac434e92c71 100644 --- a/programs/keosd/CMakeLists.txt +++ b/programs/keosd/CMakeLists.txt @@ -1,24 +1,24 @@ -add_executable( keosd main.cpp ) +add_executable( ${KEY_STORE_EXECUTABLE_NAME} main.cpp ) if( UNIX AND NOT APPLE ) set(rt_library rt ) endif() find_package( Gperftools QUIET ) if( GPERFTOOLS_FOUND ) - message( STATUS "Found gperftools; compiling keosd with TCMalloc") + message( STATUS "Found gperftools; compiling ${KEY_STORE_EXECUTABLE_NAME} with TCMalloc") list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc ) endif() -target_link_libraries( keosd +target_link_libraries( ${KEY_STORE_EXECUTABLE_NAME} PRIVATE appbase PRIVATE wallet_api_plugin wallet_plugin PRIVATE http_plugin PRIVATE eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) -mas_sign(keosd) +mas_sign(${KEY_STORE_EXECUTABLE_NAME}) install( TARGETS - keosd + ${KEY_STORE_EXECUTABLE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 1ab5850ef9b..02723c6692f 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -1,11 +1,11 @@ -add_executable( nodeos main.cpp ) +add_executable( ${NODE_EXECUTABLE_NAME} main.cpp ) if( UNIX AND NOT APPLE ) set(rt_library rt ) endif() find_package( Gperftools QUIET ) if( GPERFTOOLS_FOUND ) - message( STATUS "Found gperftools; compiling nodeos with TCMalloc") + message( STATUS "Found gperftools; compiling ${NODE_EXECUTABLE_NAME} with TCMalloc") list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc ) endif() @@ -28,7 +28,7 @@ endif() configure_file(config.hpp.in config.hpp ESCAPE_QUOTES) -target_include_directories(nodeos PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) +target_include_directories(${NODE_EXECUTABLE_NAME} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) if(UNIX) if(APPLE) @@ -43,7 +43,7 @@ else() set(no_whole_archive_flag "--no-whole-archive") endif() -target_link_libraries( nodeos +target_link_libraries( ${NODE_EXECUTABLE_NAME} PRIVATE appbase PRIVATE -Wl,${whole_archive_flag} login_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} history_plugin -Wl,${no_whole_archive_flag} @@ -61,15 +61,15 @@ target_link_libraries( nodeos PRIVATE eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) if(TARGET sql_db_plugin) - target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} sql_db_plugin -Wl,${no_whole_archive_flag} ) + target_link_libraries( ${NODE_EXECUTABLE_NAME} PRIVATE -Wl,${whole_archive_flag} sql_db_plugin -Wl,${no_whole_archive_flag} ) endif() if(BUILD_MONGO_DB_PLUGIN) - target_link_libraries( nodeos PRIVATE -Wl,${whole_archive_flag} mongo_db_plugin -Wl,${no_whole_archive_flag} ) + target_link_libraries( ${NODE_EXECUTABLE_NAME} PRIVATE -Wl,${whole_archive_flag} mongo_db_plugin -Wl,${no_whole_archive_flag} ) endif() install( TARGETS - nodeos + ${NODE_EXECUTABLE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} @@ -96,4 +96,4 @@ install(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/eosio WORLD_EXECUTE ) -mas_sign(nodeos) \ No newline at end of file +mas_sign(${NODE_EXECUTABLE_NAME}) \ No newline at end of file From 16b53188031f6ab63b7c10b483ae78f2f0d30036 Mon Sep 17 00:00:00 2001 From: Alessandro Siniscalchi Date: Wed, 8 Aug 2018 09:05:32 +0200 Subject: [PATCH 156/294] removed libsoci-dev system lib from ubuntu building script --- scripts/eosio_build_ubuntu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/eosio_build_ubuntu.sh b/scripts/eosio_build_ubuntu.sh index 4d9740bb3db..a1151c51c7c 100644 --- a/scripts/eosio_build_ubuntu.sh +++ b/scripts/eosio_build_ubuntu.sh @@ -62,7 +62,7 @@ DEP_ARRAY=(clang-4.0 lldb-4.0 libclang-4.0-dev cmake make automake libbz2-dev libssl-dev \ libgmp3-dev autotools-dev build-essential libicu-dev python2.7-dev python3-dev \ - autoconf libtool curl zlib1g-dev doxygen graphviz libsoci-dev) + autoconf libtool curl zlib1g-dev doxygen graphviz) COUNT=1 DISPLAY="" DEP="" From 9ba7a0da2b7244dfb17274463cb6868e37a1b457 Mon Sep 17 00:00:00 2001 From: Eugene Chung Date: Wed, 8 Aug 2018 19:38:44 +0900 Subject: [PATCH 157/294] Support customization point for excutable names : keosd binPath of cleos --- programs/cleos/config.hpp.in | 1 + programs/cleos/main.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/programs/cleos/config.hpp.in b/programs/cleos/config.hpp.in index 37081daf3f4..18673e9c8c2 100644 --- a/programs/cleos/config.hpp.in +++ b/programs/cleos/config.hpp.in @@ -8,4 +8,5 @@ namespace eosio { namespace client { namespace config { constexpr char version_str[] = "${cleos_BUILD_VERSION}"; constexpr char locale_path[] = "${LOCALEDIR}"; constexpr char locale_domain[] = "${LOCALEDOMAIN}"; + constexpr char key_store_executable_name[] = "${KEY_STORE_EXECUTABLE_NAME}"; }}} diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index d39df3db178..90e46c86a76 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -765,9 +765,9 @@ void ensure_keosd_running(CLI::App* app) { // This extra check is necessary when running cleos like this: ./cleos ... if (binPath.filename_is_dot()) binPath.remove_filename(); - binPath.append("keosd"); // if cleos and keosd are in the same installation directory + binPath.append(key_store_executable_name); // if cleos and keosd are in the same installation directory if (!boost::filesystem::exists(binPath)) { - binPath.remove_filename().remove_filename().append("keosd").append("keosd"); + binPath.remove_filename().remove_filename().append("keosd").append(key_store_executable_name); } const auto& lo_address = resolved_url.resolved_addresses.front(); From bae241daa2e8ed913298df7a2a75014e2d9e82e7 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 27 Jul 2018 12:57:01 -0500 Subject: [PATCH 158/294] Only set irreversible when true to avoid overwriting an irreversibe=true with a irreversible=false --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index dcc9a7705bf..7a3d24195bf 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -594,8 +594,7 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti }; if( start_block_reached ) { - trans_doc.append( kvp( "trx_id", trx_id_str ), - kvp( "irreversible", b_bool{false} )); + trans_doc.append( kvp( "trx_id", trx_id_str ) ); string signing_keys_json; if( t->signing_keys.valid()) { @@ -821,9 +820,8 @@ void mongo_db_plugin_impl::_process_accepted_block( const chain::block_state_ptr auto blocks = mongo_conn[db_name][blocks_col]; auto block_doc = bsoncxx::builder::basic::document{}; - block_doc.append(kvp( "block_num", b_int32{static_cast(block_num)} ), - kvp( "block_id", block_id_str ), - kvp( "irreversible", b_bool{false} )); + block_doc.append( kvp( "block_num", b_int32{static_cast(block_num)} ), + kvp( "block_id", block_id_str ) ); auto v = to_variant_with_abi( *bs->block ); json = fc::json::to_string( v ); From b13ff27ff02bf87acd655bed9729de592a925333 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 27 Jul 2018 15:50:43 -0500 Subject: [PATCH 159/294] Upsert transactions and actions --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 7a3d24195bf..d8ba2f148f9 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -563,8 +563,8 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti auto process_action = [&](const std::string& trx_id_str, const chain::action& act, bbb::array& act_array, bool cfa) -> auto { auto act_doc = bsoncxx::builder::basic::document(); if( start_block_reached ) { - act_doc.append( kvp( "action_num", b_int32{act_num} ), - kvp( "trx_id", trx_id_str )); + act_doc.append( kvp( "trx_id", trx_id_str ), + kvp( "action_num", b_int32{act_num} ) ); act_doc.append( kvp( "cfa", b_bool{cfa} )); act_doc.append( kvp( "account", act.account.to_string())); act_doc.append( kvp( "name", act.name.to_string())); @@ -585,8 +585,11 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti if( start_block_reached ) { add_data( act_doc, act ); act_array.append( act_doc ); - mongocxx::model::insert_one insert_op{act_doc.view()}; - bulk_actions.append( insert_op ); + mongocxx::model::update_one update_op{make_document( kvp( "trx_id", trx_id_str ), + kvp( "action_num", b_int32{act_num} ) ), + make_document( kvp( "$set", act_doc.view() ) )}; + update_op.upsert( true ); + bulk_actions.append( update_op ); actions_to_write = true; } ++act_num; @@ -642,7 +645,6 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti } if( start_block_reached ) { - act_num = 0; if( !trx.context_free_actions.empty()) { bsoncxx::builder::basic::array action_array; for( const auto& cfa : trx.context_free_actions ) { @@ -708,7 +710,10 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti trans_doc.append( kvp( "createdAt", b_date{now} )); try { - if( !trans.insert_one( trans_doc.view())) { + mongocxx::options::update update_opts{}; + update_opts.upsert( true ); + if( !trans.update_one( make_document( kvp( "trx_id", trx_id_str ) ), + make_document( kvp( "$set", trans_doc.view() ) ), update_opts ) ) { EOS_ASSERT( false, chain::mongo_db_insert_fail, "Failed to insert trans ${id}", ("id", trx_id)); } } catch(...) { @@ -1287,7 +1292,7 @@ void mongo_db_plugin_impl::init() { // actions indexes auto actions = mongo_conn[db_name][actions_col]; - actions.create_index( bsoncxx::from_json( R"xxx({ "trx_id" : 1 })xxx" )); + actions.create_index( bsoncxx::from_json( R"xxx({ "trx_id" : 1, "action_num" : 1 })xxx" )); // pub_keys indexes auto pub_keys = mongo_conn[db_name][pub_keys_col]; From cb62b0c3d23c382d53996d4fc548055c74a82095 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 3 Aug 2018 14:03:13 -0500 Subject: [PATCH 160/294] Add action_traces collection --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 107 +++++++++++++++++--- 1 file changed, 94 insertions(+), 13 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index d8ba2f148f9..edfc7755c03 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -78,6 +78,9 @@ class mongo_db_plugin_impl { void purge_abi_cache(); + void add_action_trace( mongocxx::bulk_write& bulk_action_traces, const chain::action_trace& atrace, + const std::chrono::milliseconds& now ); + void add_data(bsoncxx::builder::basic::document& act_doc, const chain::action& act); void update_account(const chain::action& act); @@ -97,7 +100,7 @@ class mongo_db_plugin_impl { bool configured{false}; bool wipe_database_on_startup{false}; uint32_t start_block_num = 0; - bool start_block_reached = false; + std::atomic_bool start_block_reached{false}; std::string db_name; mongocxx::instance mongo_inst; @@ -118,8 +121,8 @@ class mongo_db_plugin_impl { boost::mutex mtx; boost::condition_variable condition; boost::thread consume_thread; - boost::atomic done{false}; - boost::atomic startup{true}; + std::atomic_bool done{false}; + std::atomic_bool startup{true}; fc::optional chain_id; fc::microseconds abi_serializer_max_time; @@ -153,6 +156,7 @@ class mongo_db_plugin_impl { static const std::string trans_col; static const std::string trans_traces_col; static const std::string actions_col; + static const std::string action_traces_col; static const std::string accounts_col; static const std::string pub_keys_col; static const std::string account_controls_col; @@ -170,6 +174,7 @@ const std::string mongo_db_plugin_impl::blocks_col = "blocks"; const std::string mongo_db_plugin_impl::trans_col = "transactions"; const std::string mongo_db_plugin_impl::trans_traces_col = "transaction_traces"; const std::string mongo_db_plugin_impl::actions_col = "actions"; +const std::string mongo_db_plugin_impl::action_traces_col = "action_traces"; const std::string mongo_db_plugin_impl::accounts_col = "accounts"; const std::string mongo_db_plugin_impl::pub_keys_col = "pub_keys"; const std::string mongo_db_plugin_impl::account_controls_col = "account_controls"; @@ -233,6 +238,11 @@ void mongo_db_plugin_impl::applied_irreversible_block( const chain::block_state_ void mongo_db_plugin_impl::accepted_block( const chain::block_state_ptr& bs ) { try { + if( !start_block_reached ) { + if( bs->block_num >= start_block_num ) { + start_block_reached = true; + } + } queue( block_state_queue, bs ); } catch (fc::exception& e) { elog("FC Exception while accepted_block ${e}", ("e", e.to_string())); @@ -476,8 +486,9 @@ fc::variant mongo_db_plugin_impl::to_variant_with_abi( const T& obj ) { void mongo_db_plugin_impl::process_accepted_transaction( const chain::transaction_metadata_ptr& t ) { try { - // always call since we need to capture setabi on accounts even if not storing transactions - _process_accepted_transaction(t); + if( start_block_reached ) { + _process_accepted_transaction( t ); + } } catch (fc::exception& e) { elog("FC Exception while processing accepted transaction metadata: ${e}", ("e", e.to_detail_string())); } catch (std::exception& e) { @@ -489,9 +500,8 @@ void mongo_db_plugin_impl::process_accepted_transaction( const chain::transactio void mongo_db_plugin_impl::process_applied_transaction( const chain::transaction_trace_ptr& t ) { try { - if( start_block_reached ) { - _process_applied_transaction( t ); - } + // always call since we need to capture setabi on accounts even if not storing transaction traces + _process_applied_transaction( t ); } catch (fc::exception& e) { elog("FC Exception while processing applied transaction trace: ${e}", ("e", e.to_detail_string())); } catch (std::exception& e) { @@ -517,11 +527,6 @@ void mongo_db_plugin_impl::process_irreversible_block(const chain::block_state_p void mongo_db_plugin_impl::process_accepted_block( const chain::block_state_ptr& bs ) { try { - if( !start_block_reached ) { - if( bs->block_num >= start_block_num ) { - start_block_reached = true; - } - } if( start_block_reached ) { _process_accepted_block( bs ); } @@ -732,16 +737,88 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti } } +void +mongo_db_plugin_impl::add_action_trace( mongocxx::bulk_write& bulk_action_traces, const chain::action_trace& atrace, + const std::chrono::milliseconds& now ) +{ + using namespace bsoncxx::types; + using bsoncxx::builder::basic::kvp; + + if( atrace.receipt.receiver == chain::config::system_account_name ) { + update_account( atrace.act ); + } + + if( start_block_reached ) { + auto action_traces_doc = bsoncxx::builder::basic::document{}; + const auto& base = static_cast(atrace); // without inline action traces + + auto v = to_variant_with_abi( base ); + string json = fc::json::to_string( v ); + try { + const auto& value = bsoncxx::from_json( json ); + action_traces_doc.append( bsoncxx::builder::concatenate_doc{value.view()} ); + } catch( bsoncxx::exception& ) { + try { + json = fc::prune_invalid_utf8( json ); + const auto& value = bsoncxx::from_json( json ); + action_traces_doc.append( bsoncxx::builder::concatenate_doc{value.view()} ); + action_traces_doc.append( kvp( "non-utf8-purged", b_bool{true} ) ); + } catch( bsoncxx::exception& e ) { + elog( "Unable to convert action trace JSON to MongoDB JSON: ${e}", ("e", e.what()) ); + elog( " JSON: ${j}", ("j", json) ); + } + } + action_traces_doc.append( kvp( "createdAt", b_date{now} ) ); + + mongocxx::model::insert_one insert_op{action_traces_doc.view()}; + bulk_action_traces.append( insert_op ); + } + + for( const auto& iline_atrace : atrace.inline_traces ) { + add_action_trace( bulk_action_traces, iline_atrace, now ); + } +} + + void mongo_db_plugin_impl::_process_applied_transaction( const chain::transaction_trace_ptr& t ) { using namespace bsoncxx::types; using bsoncxx::builder::basic::kvp; auto trans_traces = mongo_conn[db_name][trans_traces_col]; + auto action_traces = mongo_conn[db_name][action_traces_col]; auto trans_traces_doc = bsoncxx::builder::basic::document{}; auto now = std::chrono::duration_cast( std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()}); + mongocxx::options::bulk_write bulk_opts; + bulk_opts.ordered(false); + mongocxx::bulk_write bulk_action_traces = action_traces.create_bulk_write(bulk_opts); + bool write_atraces = false; + + for( const auto& atrace : t->action_traces ) { + try { + add_action_trace( bulk_action_traces, atrace, now ); + write_atraces = true; + } catch(...) { + handle_mongo_exception("add action traces", __LINE__); + } + } + + if( write_atraces && start_block_reached ) { + try { + if( !bulk_action_traces.execute() ) { + EOS_ASSERT( false, chain::mongo_db_insert_fail, "Bulk action traces insert failed for transaction trace: ${id}", ("id", t->id)); + } + } catch(...) { + handle_mongo_exception("action traces insert", __LINE__); + } + } + + if( !start_block_reached ) return; + + // transaction trace insert + auto v = to_variant_with_abi( *t ); string json = fc::json::to_string( v ); try { @@ -1294,6 +1371,10 @@ void mongo_db_plugin_impl::init() { auto actions = mongo_conn[db_name][actions_col]; actions.create_index( bsoncxx::from_json( R"xxx({ "trx_id" : 1, "action_num" : 1 })xxx" )); + // action traces indexes + auto action_traces = mongo_conn[db_name][action_traces_col]; + action_traces.create_index( bsoncxx::from_json( R"xxx({ "trx_id" : 1, "receipt.global_sequence" : 1 })xxx" )); + // pub_keys indexes auto pub_keys = mongo_conn[db_name][pub_keys_col]; pub_keys.create_index( bsoncxx::from_json( R"xxx({ "account" : 1, "permission" : 1 })xxx" )); From 90c9c9e7a01923da00ac933e9fd73367583ac35f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 3 Aug 2018 14:29:54 -0500 Subject: [PATCH 161/294] Remove actions collection in lieu of action_traces collection --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 271 +++++++++----------- 1 file changed, 118 insertions(+), 153 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index edfc7755c03..827bc34469b 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -155,7 +155,6 @@ class mongo_db_plugin_impl { static const std::string blocks_col; static const std::string trans_col; static const std::string trans_traces_col; - static const std::string actions_col; static const std::string action_traces_col; static const std::string accounts_col; static const std::string pub_keys_col; @@ -173,7 +172,6 @@ const std::string mongo_db_plugin_impl::block_states_col = "block_states"; const std::string mongo_db_plugin_impl::blocks_col = "blocks"; const std::string mongo_db_plugin_impl::trans_col = "transactions"; const std::string mongo_db_plugin_impl::trans_traces_col = "transaction_traces"; -const std::string mongo_db_plugin_impl::actions_col = "actions"; const std::string mongo_db_plugin_impl::action_traces_col = "action_traces"; const std::string mongo_db_plugin_impl::accounts_col = "accounts"; const std::string mongo_db_plugin_impl::pub_keys_col = "pub_keys"; @@ -547,194 +545,164 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti namespace bbb = bsoncxx::builder::basic; auto trans = mongo_conn[db_name][trans_col]; - auto actions = mongo_conn[db_name][actions_col]; accounts = mongo_conn[db_name][accounts_col]; auto trans_doc = bsoncxx::builder::basic::document{}; auto now = std::chrono::duration_cast( - std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()}); + std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()} ); const auto trx_id = t->id; const auto trx_id_str = trx_id.str(); const auto& trx = t->trx; const chain::transaction_header& trx_header = trx; - bool actions_to_write = false; - mongocxx::options::bulk_write bulk_opts; - bulk_opts.ordered(false); - mongocxx::bulk_write bulk_actions = actions.create_bulk_write(bulk_opts); - int32_t act_num = 0; - auto process_action = [&](const std::string& trx_id_str, const chain::action& act, bbb::array& act_array, bool cfa) -> auto { + auto process_action = [&]( const std::string& trx_id_str, const chain::action& act, bbb::array& act_array, + bool cfa ) -> auto { auto act_doc = bsoncxx::builder::basic::document(); - if( start_block_reached ) { - act_doc.append( kvp( "trx_id", trx_id_str ), - kvp( "action_num", b_int32{act_num} ) ); - act_doc.append( kvp( "cfa", b_bool{cfa} )); - act_doc.append( kvp( "account", act.account.to_string())); - act_doc.append( kvp( "name", act.name.to_string())); - act_doc.append( kvp( "authorization", [&act]( bsoncxx::builder::basic::sub_array subarr ) { - for( const auto& auth : act.authorization ) { - subarr.append( [&auth]( bsoncxx::builder::basic::sub_document subdoc ) { - subdoc.append( kvp( "actor", auth.actor.to_string()), - kvp( "permission", auth.permission.to_string())); - } ); - } - } )); - } - try { - update_account( act ); - } catch (...) { - handle_mongo_exception( "update_account", __LINE__ ); - } - if( start_block_reached ) { - add_data( act_doc, act ); - act_array.append( act_doc ); - mongocxx::model::update_one update_op{make_document( kvp( "trx_id", trx_id_str ), - kvp( "action_num", b_int32{act_num} ) ), - make_document( kvp( "$set", act_doc.view() ) )}; - update_op.upsert( true ); - bulk_actions.append( update_op ); - actions_to_write = true; - } + act_doc.append( kvp( "trx_id", trx_id_str ), + kvp( "action_num", b_int32{act_num} ) ); + act_doc.append( kvp( "cfa", b_bool{cfa} ) ); + act_doc.append( kvp( "account", act.account.to_string() ) ); + act_doc.append( kvp( "name", act.name.to_string() ) ); + act_doc.append( kvp( "authorization", [&act]( bsoncxx::builder::basic::sub_array subarr ) { + for( const auto& auth : act.authorization ) { + subarr.append( [&auth]( bsoncxx::builder::basic::sub_document subdoc ) { + subdoc.append( kvp( "actor", auth.actor.to_string() ), + kvp( "permission", auth.permission.to_string() ) ); + } ); + } + } ) ); + add_data( act_doc, act ); + act_array.append( act_doc ); ++act_num; return act_num; }; - if( start_block_reached ) { - trans_doc.append( kvp( "trx_id", trx_id_str ) ); + trans_doc.append( kvp( "trx_id", trx_id_str ) ); - string signing_keys_json; - if( t->signing_keys.valid()) { - signing_keys_json = fc::json::to_string( t->signing_keys->second ); - } else { - auto signing_keys = trx.get_signature_keys( *chain_id, false, false ); - if( !signing_keys.empty()) { - signing_keys_json = fc::json::to_string( signing_keys ); - } + string signing_keys_json; + if( t->signing_keys.valid() ) { + signing_keys_json = fc::json::to_string( t->signing_keys->second ); + } else { + auto signing_keys = trx.get_signature_keys( *chain_id, false, false ); + if( !signing_keys.empty() ) { + signing_keys_json = fc::json::to_string( signing_keys ); } - string trx_header_json = fc::json::to_string( trx_header ); + } + string trx_header_json = fc::json::to_string( trx_header ); + try { + const auto& trx_header_value = bsoncxx::from_json( trx_header_json ); + trans_doc.append( kvp( "transaction_header", trx_header_value ) ); + } catch( bsoncxx::exception& ) { try { + trx_header_json = fc::prune_invalid_utf8( trx_header_json ); const auto& trx_header_value = bsoncxx::from_json( trx_header_json ); - trans_doc.append( kvp( "transaction_header", trx_header_value )); - } catch( bsoncxx::exception& ) { - try { - trx_header_json = fc::prune_invalid_utf8( trx_header_json ); - const auto& trx_header_value = bsoncxx::from_json( trx_header_json ); - trans_doc.append( kvp( "transaction_header", trx_header_value )); - trans_doc.append( kvp( "non-utf8-purged", b_bool{true})); - } catch( bsoncxx::exception& e ) { - elog( "Unable to convert transaction header JSON to MongoDB JSON: ${e}", ("e", e.what())); - elog( " JSON: ${j}", ("j", trx_header_json)); - } + trans_doc.append( kvp( "transaction_header", trx_header_value ) ); + trans_doc.append( kvp( "non-utf8-purged", b_bool{true} ) ); + } catch( bsoncxx::exception& e ) { + elog( "Unable to convert transaction header JSON to MongoDB JSON: ${e}", ("e", e.what()) ); + elog( " JSON: ${j}", ("j", trx_header_json) ); } - if( !signing_keys_json.empty()) { - try { - const auto& keys_value = bsoncxx::from_json( signing_keys_json ); - trans_doc.append( kvp( "signing_keys", keys_value )); - } catch( bsoncxx::exception& e ) { - // should never fail, so don't attempt to remove invalid utf8 - elog( "Unable to convert signing keys JSON to MongoDB JSON: ${e}", ("e", e.what())); - elog( " JSON: ${j}", ("j", signing_keys_json)); - } + } + if( !signing_keys_json.empty() ) { + try { + const auto& keys_value = bsoncxx::from_json( signing_keys_json ); + trans_doc.append( kvp( "signing_keys", keys_value ) ); + } catch( bsoncxx::exception& e ) { + // should never fail, so don't attempt to remove invalid utf8 + elog( "Unable to convert signing keys JSON to MongoDB JSON: ${e}", ("e", e.what()) ); + elog( " JSON: ${j}", ("j", signing_keys_json) ); } } - if( !trx.actions.empty()) { + if( !trx.actions.empty() ) { bsoncxx::builder::basic::array action_array; for( const auto& act : trx.actions ) { process_action( trx_id_str, act, action_array, false ); } - trans_doc.append( kvp( "actions", action_array )); + trans_doc.append( kvp( "actions", action_array ) ); } - if( start_block_reached ) { - if( !trx.context_free_actions.empty()) { - bsoncxx::builder::basic::array action_array; - for( const auto& cfa : trx.context_free_actions ) { - process_action( trx_id_str, cfa, action_array, true ); - } - trans_doc.append( kvp( "context_free_actions", action_array )); + if( !trx.context_free_actions.empty() ) { + bsoncxx::builder::basic::array action_array; + for( const auto& cfa : trx.context_free_actions ) { + process_action( trx_id_str, cfa, action_array, true ); } + trans_doc.append( kvp( "context_free_actions", action_array ) ); + } - string trx_extensions_json = fc::json::to_string( trx.transaction_extensions ); - string trx_signatures_json = fc::json::to_string( trx.signatures ); - string trx_context_free_data_json = fc::json::to_string( trx.context_free_data ); - - try { - if( !trx_extensions_json.empty()) { - try { - const auto& trx_extensions_value = bsoncxx::from_json( trx_extensions_json ); - trans_doc.append( kvp( "transaction_extensions", trx_extensions_value )); - } catch( bsoncxx::exception& ) { - static_assert( sizeof(std::remove_pointer::type) == sizeof(std::string::value_type), "string type not storable as b_binary" ); - trans_doc.append( kvp( "transaction_extensions", - b_binary{bsoncxx::binary_sub_type::k_binary, - static_cast(trx_extensions_json.size()), - reinterpret_cast(trx_extensions_json.data())} )); - } - } else { - trans_doc.append( kvp( "transaction_extensions", make_array())); - } - - if( !trx_signatures_json.empty()) { - // signatures contain only utf8 - const auto& trx_signatures_value = bsoncxx::from_json( trx_signatures_json ); - trans_doc.append( kvp( "signatures", trx_signatures_value )); - } else { - trans_doc.append( kvp( "signatures", make_array())); - } + string trx_extensions_json = fc::json::to_string( trx.transaction_extensions ); + string trx_signatures_json = fc::json::to_string( trx.signatures ); + string trx_context_free_data_json = fc::json::to_string( trx.context_free_data ); - if( !trx_context_free_data_json.empty()) { - try { - const auto& trx_context_free_data_value = bsoncxx::from_json( trx_context_free_data_json ); - trans_doc.append( kvp( "context_free_data", trx_context_free_data_value )); - } catch( bsoncxx::exception& ) { - static_assert( sizeof(std::remove_pointer::type) == - sizeof(std::remove_pointer::type), "context_free_data not storable as b_binary" ); - bsoncxx::builder::basic::array data_array; - for (auto& cfd : trx.context_free_data) { - data_array.append( - b_binary{bsoncxx::binary_sub_type::k_binary, - static_cast(cfd.size()), - reinterpret_cast(cfd.data())} ); - } - trans_doc.append( kvp( "context_free_data", data_array.view() )); - } - } else { - trans_doc.append( kvp( "context_free_data", make_array())); + try { + if( !trx_extensions_json.empty() ) { + try { + const auto& trx_extensions_value = bsoncxx::from_json( trx_extensions_json ); + trans_doc.append( kvp( "transaction_extensions", trx_extensions_value ) ); + } catch( bsoncxx::exception& ) { + static_assert( + sizeof( std::remove_pointer::type ) == sizeof( std::string::value_type ), + "string type not storable as b_binary" ); + trans_doc.append( kvp( "transaction_extensions", + b_binary{bsoncxx::binary_sub_type::k_binary, + static_cast(trx_extensions_json.size()), + reinterpret_cast(trx_extensions_json.data())} ) ); } - } catch( std::exception& e ) { - elog( "Unable to convert transaction JSON to MongoDB JSON: ${e}", ("e", e.what())); - elog( " JSON: ${j}", ("j", trx_extensions_json)); - elog( " JSON: ${j}", ("j", trx_signatures_json)); - elog( " JSON: ${j}", ("j", trx_context_free_data_json)); + } else { + trans_doc.append( kvp( "transaction_extensions", make_array() ) ); } - trans_doc.append( kvp( "createdAt", b_date{now} )); - - try { - mongocxx::options::update update_opts{}; - update_opts.upsert( true ); - if( !trans.update_one( make_document( kvp( "trx_id", trx_id_str ) ), - make_document( kvp( "$set", trans_doc.view() ) ), update_opts ) ) { - EOS_ASSERT( false, chain::mongo_db_insert_fail, "Failed to insert trans ${id}", ("id", trx_id)); - } - } catch(...) { - handle_mongo_exception("trans insert", __LINE__); + if( !trx_signatures_json.empty() ) { + // signatures contain only utf8 + const auto& trx_signatures_value = bsoncxx::from_json( trx_signatures_json ); + trans_doc.append( kvp( "signatures", trx_signatures_value ) ); + } else { + trans_doc.append( kvp( "signatures", make_array() ) ); } - if (actions_to_write) { + if( !trx_context_free_data_json.empty() ) { try { - if( !bulk_actions.execute() ) { - EOS_ASSERT( false, chain::mongo_db_insert_fail, "Bulk actions insert failed for transaction: ${id}", ("id", trx_id_str)); + const auto& trx_context_free_data_value = bsoncxx::from_json( trx_context_free_data_json ); + trans_doc.append( kvp( "context_free_data", trx_context_free_data_value ) ); + } catch( bsoncxx::exception& ) { + static_assert( sizeof( std::remove_pointer::type ) == + sizeof( std::remove_pointer::type ), + "context_free_data not storable as b_binary" ); + bsoncxx::builder::basic::array data_array; + for( auto& cfd : trx.context_free_data ) { + data_array.append( + b_binary{bsoncxx::binary_sub_type::k_binary, + static_cast(cfd.size()), + reinterpret_cast(cfd.data())} ); } - } catch(...) { - handle_mongo_exception("actions insert", __LINE__); + trans_doc.append( kvp( "context_free_data", data_array.view() ) ); } + } else { + trans_doc.append( kvp( "context_free_data", make_array() ) ); } + } catch( std::exception& e ) { + elog( "Unable to convert transaction JSON to MongoDB JSON: ${e}", ("e", e.what()) ); + elog( " JSON: ${j}", ("j", trx_extensions_json) ); + elog( " JSON: ${j}", ("j", trx_signatures_json) ); + elog( " JSON: ${j}", ("j", trx_context_free_data_json) ); } + + trans_doc.append( kvp( "createdAt", b_date{now} ) ); + + try { + mongocxx::options::update update_opts{}; + update_opts.upsert( true ); + if( !trans.update_one( make_document( kvp( "trx_id", trx_id_str ) ), + make_document( kvp( "$set", trans_doc.view() ) ), update_opts ) ) { + EOS_ASSERT( false, chain::mongo_db_insert_fail, "Failed to insert trans ${id}", ("id", trx_id) ); + } + } catch( ... ) { + handle_mongo_exception( "trans insert", __LINE__ ); + } + } void @@ -1036,6 +1004,7 @@ void mongo_db_plugin_impl::add_data( bsoncxx::builder::basic::document& act_doc, const auto& value = bsoncxx::from_json( json ); act_doc.append( kvp( "data", value )); + act_doc.append( kvp( "hex_data", fc::variant( act.data ).as_string())); return; } catch( bsoncxx::exception& e ) { ilog( "Unable to convert EOS JSON to MongoDB JSON: ${e}", ("e", e.what())); @@ -1308,7 +1277,7 @@ void mongo_db_plugin_impl::wipe_database() { auto blocks = mongo_conn[db_name][blocks_col]; auto trans = mongo_conn[db_name][trans_col]; auto trans_traces = mongo_conn[db_name][trans_traces_col]; - auto actions = mongo_conn[db_name][actions_col]; + auto action_traces = mongo_conn[db_name][action_traces_col]; accounts = mongo_conn[db_name][accounts_col]; auto pub_keys = mongo_conn[db_name][pub_keys_col]; auto account_controls = mongo_conn[db_name][account_controls_col]; @@ -1317,7 +1286,7 @@ void mongo_db_plugin_impl::wipe_database() { blocks.drop(); trans.drop(); trans_traces.drop(); - actions.drop(); + action_traces.drop(); accounts.drop(); pub_keys.drop(); account_controls.drop(); @@ -1367,13 +1336,9 @@ void mongo_db_plugin_impl::init() { auto trans_trace = mongo_conn[db_name][trans_traces_col]; trans_trace.create_index( bsoncxx::from_json( R"xxx({ "id" : 1 })xxx" )); - // actions indexes - auto actions = mongo_conn[db_name][actions_col]; - actions.create_index( bsoncxx::from_json( R"xxx({ "trx_id" : 1, "action_num" : 1 })xxx" )); - // action traces indexes auto action_traces = mongo_conn[db_name][action_traces_col]; - action_traces.create_index( bsoncxx::from_json( R"xxx({ "trx_id" : 1, "receipt.global_sequence" : 1 })xxx" )); + action_traces.create_index( bsoncxx::from_json( R"xxx({ "trx_id" : 1 })xxx" )); // pub_keys indexes auto pub_keys = mongo_conn[db_name][pub_keys_col]; From c02c2052a29262c328bcf58a8dc6a5c3582f8f06 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 3 Aug 2018 14:45:09 -0500 Subject: [PATCH 162/294] Process transaction traces first now since abi is captured from them --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 827bc34469b..7139e5551c4 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -293,28 +293,28 @@ void mongo_db_plugin_impl::consume_blocks() { // process transactions auto start_time = fc::time_point::now(); - auto size = transaction_metadata_process_queue.size(); - while (!transaction_metadata_process_queue.empty()) { - const auto& t = transaction_metadata_process_queue.front(); - process_accepted_transaction(t); - transaction_metadata_process_queue.pop_front(); + auto size = transaction_trace_process_queue.size(); + while (!transaction_trace_process_queue.empty()) { + const auto& t = transaction_trace_process_queue.front(); + process_applied_transaction(t); + transaction_trace_process_queue.pop_front(); } auto time = fc::time_point::now() - start_time; auto per = size > 0 ? time.count()/size : 0; if( time > fc::microseconds(500000) ) // reduce logging, .5 secs - ilog( "process_accepted_transaction, time per: ${p}, size: ${s}, time: ${t}", ("s", size)( "t", time )( "p", per )); + ilog( "process_applied_transaction, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); start_time = fc::time_point::now(); - size = transaction_trace_process_queue.size(); - while (!transaction_trace_process_queue.empty()) { - const auto& t = transaction_trace_process_queue.front(); - process_applied_transaction(t); - transaction_trace_process_queue.pop_front(); + size = transaction_metadata_process_queue.size(); + while (!transaction_metadata_process_queue.empty()) { + const auto& t = transaction_metadata_process_queue.front(); + process_accepted_transaction(t); + transaction_metadata_process_queue.pop_front(); } time = fc::time_point::now() - start_time; per = size > 0 ? time.count()/size : 0; if( time > fc::microseconds(500000) ) // reduce logging, .5 secs - ilog( "process_applied_transaction, time per: ${p}, size: ${s}, time: ${t}", ("s", size)("t", time)("p", per) ); + ilog( "process_accepted_transaction, time per: ${p}, size: ${s}, time: ${t}", ("s", size)( "t", time )( "p", per )); // process blocks start_time = fc::time_point::now(); From d0f9a3706a79d6a539742659dae827cda27c8b88 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 3 Aug 2018 15:36:12 -0500 Subject: [PATCH 163/294] Add base_action_trace as a known type so action data is expanded when used --- libraries/chain/include/eosio/chain/abi_serializer.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/chain/include/eosio/chain/abi_serializer.hpp b/libraries/chain/include/eosio/chain/abi_serializer.hpp index 3d0c05caea2..3c53f903968 100644 --- a/libraries/chain/include/eosio/chain/abi_serializer.hpp +++ b/libraries/chain/include/eosio/chain/abi_serializer.hpp @@ -134,6 +134,7 @@ namespace impl { std::is_same::value || std::is_same::value || std::is_same::value || + std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value || From bdfd88a498360de1e1e366945c6cf317e2e661ef Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Sat, 4 Aug 2018 08:48:22 -0500 Subject: [PATCH 164/294] Only update account for executed actions --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 7139e5551c4..a1c44e8273d 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -79,7 +79,7 @@ class mongo_db_plugin_impl { void purge_abi_cache(); void add_action_trace( mongocxx::bulk_write& bulk_action_traces, const chain::action_trace& atrace, - const std::chrono::milliseconds& now ); + bool executed, const std::chrono::milliseconds& now ); void add_data(bsoncxx::builder::basic::document& act_doc, const chain::action& act); void update_account(const chain::action& act); @@ -707,12 +707,12 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti void mongo_db_plugin_impl::add_action_trace( mongocxx::bulk_write& bulk_action_traces, const chain::action_trace& atrace, - const std::chrono::milliseconds& now ) + bool executed, const std::chrono::milliseconds& now ) { using namespace bsoncxx::types; using bsoncxx::builder::basic::kvp; - if( atrace.receipt.receiver == chain::config::system_account_name ) { + if( executed && atrace.receipt.receiver == chain::config::system_account_name ) { update_account( atrace.act ); } @@ -743,7 +743,7 @@ mongo_db_plugin_impl::add_action_trace( mongocxx::bulk_write& bulk_action_traces } for( const auto& iline_atrace : atrace.inline_traces ) { - add_action_trace( bulk_action_traces, iline_atrace, now ); + add_action_trace( bulk_action_traces, iline_atrace, executed, now ); } } @@ -763,10 +763,11 @@ void mongo_db_plugin_impl::_process_applied_transaction( const chain::transactio bulk_opts.ordered(false); mongocxx::bulk_write bulk_action_traces = action_traces.create_bulk_write(bulk_opts); bool write_atraces = false; + bool executed = t->receipt.valid() && t->receipt->status == chain::transaction_receipt_header::executed; for( const auto& atrace : t->action_traces ) { try { - add_action_trace( bulk_action_traces, atrace, now ); + add_action_trace( bulk_action_traces, atrace, executed, now ); write_atraces = true; } catch(...) { handle_mongo_exception("add action traces", __LINE__); From 38d70e604384e5c7c67613b5250167efee149d4f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Sat, 4 Aug 2018 15:16:59 -0500 Subject: [PATCH 165/294] Add ability to override type pack/unpack --- libraries/chain/abi_serializer.cpp | 5 +++++ libraries/chain/include/eosio/chain/abi_serializer.hpp | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/libraries/chain/abi_serializer.cpp b/libraries/chain/abi_serializer.cpp index 967796d9afe..60303a5268f 100644 --- a/libraries/chain/abi_serializer.cpp +++ b/libraries/chain/abi_serializer.cpp @@ -55,6 +55,11 @@ namespace eosio { namespace chain { set_abi(abi, max_serialization_time); } + void abi_serializer::add_specialized_unpack_pack( const string& name, + std::pair unpack_pack ) { + built_in_types[name] = std::move( unpack_pack ); + } + void abi_serializer::configure_built_in_types() { built_in_types.emplace("bool", pack_unpack()); diff --git a/libraries/chain/include/eosio/chain/abi_serializer.hpp b/libraries/chain/include/eosio/chain/abi_serializer.hpp index 3c53f903968..1e5bca6b5b3 100644 --- a/libraries/chain/include/eosio/chain/abi_serializer.hpp +++ b/libraries/chain/include/eosio/chain/abi_serializer.hpp @@ -86,11 +86,13 @@ struct abi_serializer { return false; } - static const size_t max_recursion_depth = 32; // arbitrary depth to prevent infinite recursion - typedef std::function&, bool, bool)> unpack_function; typedef std::function&, bool, bool)> pack_function; + void add_specialized_unpack_pack( const string& name, std::pair unpack_pack ); + + static const size_t max_recursion_depth = 32; // arbitrary depth to prevent infinite recursion + private: map typedefs; From 4584e9b5091103b1c07971ded2487a51beeed90f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Sat, 4 Aug 2018 15:18:02 -0500 Subject: [PATCH 166/294] Override abi serialization of eosio::setabi.abi to store abi as abi_def instead of bytes in mongodb. --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 46 +++++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index a1c44e8273d..b4ac1090848 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -463,7 +463,37 @@ optional mongo_db_plugin_impl::get_abi_serializer( account_name abi_cache entry; entry.account = n; entry.last_accessed = fc::time_point::now(); - entry.serializer.emplace( abi, abi_serializer_max_time ); + abi_serializer abis; + if( n == chain::config::system_account_name ) { + // redefine eosio setabi.abi from bytes to abi_def + // Done so that abi is stored as abi_def in mongo instead of as bytes + auto itr = std::find_if( abi.structs.begin(), abi.structs.end(), + []( const auto& s ) { return s.name == "setabi"; } ); + if( itr != abi.structs.end() ) { + auto itr2 = std::find_if( itr->fields.begin(), itr->fields.end(), + []( const auto& f ) { return f.name == "abi"; } ); + if( itr2 != itr->fields.end() ) { + if( itr2->type == "bytes" ) { + itr2->type = "abi_def"; + // unpack setabi.abi as abi_def instead of as bytes + abis.add_specialized_unpack_pack( "abi_def", + std::make_pair( + []( fc::datastream& stream, bool is_array, bool is_optional ) -> fc::variant { + EOS_ASSERT( !is_array && !is_optional, chain::mongo_db_exception, "unexpected abi_def"); + chain::bytes temp; + fc::raw::unpack( stream, temp ); + return fc::variant( fc::raw::unpack( temp ) ); + }, + []( const fc::variant& var, fc::datastream& ds, bool is_array, bool is_optional ) { + EOS_ASSERT( false, chain::mongo_db_exception, "never called" ); + } + ) ); + } + } + } + } + abis.set_abi( abi, abi_serializer_max_time ); + entry.serializer.emplace( std::move( abis ) ); abi_cache_index.insert( entry ); return entry.serializer; } @@ -718,7 +748,7 @@ mongo_db_plugin_impl::add_action_trace( mongocxx::bulk_write& bulk_action_traces if( start_block_reached ) { auto action_traces_doc = bsoncxx::builder::basic::document{}; - const auto& base = static_cast(atrace); // without inline action traces + const chain::base_action_trace& base = atrace; // without inline action traces auto v = to_variant_with_abi( base ); string json = fc::json::to_string( v ); @@ -1218,13 +1248,13 @@ void mongo_db_plugin_impl::update_account(const chain::action& act) abi_cache_index.erase( setabi.account ); - auto from_account = find_account( accounts, setabi.account ); - if( !from_account ) { + auto account = find_account( accounts, setabi.account ); + if( !account ) { create_account( accounts, setabi.account, now ); - from_account = find_account( accounts, setabi.account ); + account = find_account( accounts, setabi.account ); } - if( from_account ) { - const abi_def& abi_def = fc::raw::unpack( setabi.abi ); + if( account ) { + abi_def abi_def = fc::raw::unpack( setabi.abi ); const string json_str = fc::json::to_string( abi_def ); try{ @@ -1233,7 +1263,7 @@ void mongo_db_plugin_impl::update_account(const chain::action& act) kvp( "updatedAt", b_date{now} )))); try { - if( !accounts.update_one( make_document( kvp( "_id", from_account->view()["_id"].get_oid())), + if( !accounts.update_one( make_document( kvp( "_id", account->view()["_id"].get_oid())), update_from.view())) { EOS_ASSERT( false, chain::mongo_db_update_fail, "Failed to udpdate account ${n}", ("n", setabi.account)); } From 82d9f97ac209280f76025b833ec6e1bcc5e67abe Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Sat, 4 Aug 2018 16:15:51 -0500 Subject: [PATCH 167/294] Simplified transaction serialization --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 193 +++----------------- 1 file changed, 22 insertions(+), 171 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index b4ac1090848..23ded113701 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -81,7 +81,6 @@ class mongo_db_plugin_impl { void add_action_trace( mongocxx::bulk_write& bulk_action_traces, const chain::action_trace& atrace, bool executed, const std::chrono::milliseconds& now ); - void add_data(bsoncxx::builder::basic::document& act_doc, const chain::action& act); void update_account(const chain::action& act); void add_pub_keys( const vector& keys, const account_name& name, @@ -575,42 +574,35 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti namespace bbb = bsoncxx::builder::basic; auto trans = mongo_conn[db_name][trans_col]; - accounts = mongo_conn[db_name][accounts_col]; auto trans_doc = bsoncxx::builder::basic::document{}; auto now = std::chrono::duration_cast( std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()} ); - const auto trx_id = t->id; + const auto& trx_id = t->id; const auto trx_id_str = trx_id.str(); const auto& trx = t->trx; - const chain::transaction_header& trx_header = trx; - - int32_t act_num = 0; - auto process_action = [&]( const std::string& trx_id_str, const chain::action& act, bbb::array& act_array, - bool cfa ) -> auto { - auto act_doc = bsoncxx::builder::basic::document(); - act_doc.append( kvp( "trx_id", trx_id_str ), - kvp( "action_num", b_int32{act_num} ) ); - act_doc.append( kvp( "cfa", b_bool{cfa} ) ); - act_doc.append( kvp( "account", act.account.to_string() ) ); - act_doc.append( kvp( "name", act.name.to_string() ) ); - act_doc.append( kvp( "authorization", [&act]( bsoncxx::builder::basic::sub_array subarr ) { - for( const auto& auth : act.authorization ) { - subarr.append( [&auth]( bsoncxx::builder::basic::sub_document subdoc ) { - subdoc.append( kvp( "actor", auth.actor.to_string() ), - kvp( "permission", auth.permission.to_string() ) ); - } ); - } - } ) ); - add_data( act_doc, act ); - act_array.append( act_doc ); - ++act_num; - return act_num; - }; trans_doc.append( kvp( "trx_id", trx_id_str ) ); + auto v = to_variant_with_abi( trx ); + string trx_json = fc::json::to_string( v ); + + try { + const auto& trx_value = bsoncxx::from_json( trx_json ); + trans_doc.append( bsoncxx::builder::concatenate_doc{trx_value.view()} ); + } catch( bsoncxx::exception& ) { + try { + trx_json = fc::prune_invalid_utf8( trx_json ); + const auto& trx_value = bsoncxx::from_json( trx_json ); + trans_doc.append( bsoncxx::builder::concatenate_doc{trx_value.view()} ); + trans_doc.append( kvp( "non-utf8-purged", b_bool{true} ) ); + } catch( bsoncxx::exception& e ) { + elog( "Unable to convert transaction JSON to MongoDB JSON: ${e}", ("e", e.what()) ); + elog( " JSON: ${j}", ("j", trx_json) ); + } + } + string signing_keys_json; if( t->signing_keys.valid() ) { signing_keys_json = fc::json::to_string( t->signing_keys->second ); @@ -620,22 +612,7 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti signing_keys_json = fc::json::to_string( signing_keys ); } } - string trx_header_json = fc::json::to_string( trx_header ); - try { - const auto& trx_header_value = bsoncxx::from_json( trx_header_json ); - trans_doc.append( kvp( "transaction_header", trx_header_value ) ); - } catch( bsoncxx::exception& ) { - try { - trx_header_json = fc::prune_invalid_utf8( trx_header_json ); - const auto& trx_header_value = bsoncxx::from_json( trx_header_json ); - trans_doc.append( kvp( "transaction_header", trx_header_value ) ); - trans_doc.append( kvp( "non-utf8-purged", b_bool{true} ) ); - } catch( bsoncxx::exception& e ) { - elog( "Unable to convert transaction header JSON to MongoDB JSON: ${e}", ("e", e.what()) ); - elog( " JSON: ${j}", ("j", trx_header_json) ); - } - } if( !signing_keys_json.empty() ) { try { const auto& keys_value = bsoncxx::from_json( signing_keys_json ); @@ -647,78 +624,9 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti } } - if( !trx.actions.empty() ) { - bsoncxx::builder::basic::array action_array; - for( const auto& act : trx.actions ) { - process_action( trx_id_str, act, action_array, false ); - } - trans_doc.append( kvp( "actions", action_array ) ); - } - - if( !trx.context_free_actions.empty() ) { - bsoncxx::builder::basic::array action_array; - for( const auto& cfa : trx.context_free_actions ) { - process_action( trx_id_str, cfa, action_array, true ); - } - trans_doc.append( kvp( "context_free_actions", action_array ) ); - } - - string trx_extensions_json = fc::json::to_string( trx.transaction_extensions ); - string trx_signatures_json = fc::json::to_string( trx.signatures ); - string trx_context_free_data_json = fc::json::to_string( trx.context_free_data ); - - try { - if( !trx_extensions_json.empty() ) { - try { - const auto& trx_extensions_value = bsoncxx::from_json( trx_extensions_json ); - trans_doc.append( kvp( "transaction_extensions", trx_extensions_value ) ); - } catch( bsoncxx::exception& ) { - static_assert( - sizeof( std::remove_pointer::type ) == sizeof( std::string::value_type ), - "string type not storable as b_binary" ); - trans_doc.append( kvp( "transaction_extensions", - b_binary{bsoncxx::binary_sub_type::k_binary, - static_cast(trx_extensions_json.size()), - reinterpret_cast(trx_extensions_json.data())} ) ); - } - } else { - trans_doc.append( kvp( "transaction_extensions", make_array() ) ); - } - - if( !trx_signatures_json.empty() ) { - // signatures contain only utf8 - const auto& trx_signatures_value = bsoncxx::from_json( trx_signatures_json ); - trans_doc.append( kvp( "signatures", trx_signatures_value ) ); - } else { - trans_doc.append( kvp( "signatures", make_array() ) ); - } - - if( !trx_context_free_data_json.empty() ) { - try { - const auto& trx_context_free_data_value = bsoncxx::from_json( trx_context_free_data_json ); - trans_doc.append( kvp( "context_free_data", trx_context_free_data_value ) ); - } catch( bsoncxx::exception& ) { - static_assert( sizeof( std::remove_pointer::type ) == - sizeof( std::remove_pointer::type ), - "context_free_data not storable as b_binary" ); - bsoncxx::builder::basic::array data_array; - for( auto& cfd : trx.context_free_data ) { - data_array.append( - b_binary{bsoncxx::binary_sub_type::k_binary, - static_cast(cfd.size()), - reinterpret_cast(cfd.data())} ); - } - trans_doc.append( kvp( "context_free_data", data_array.view() ) ); - } - } else { - trans_doc.append( kvp( "context_free_data", make_array() ) ); - } - } catch( std::exception& e ) { - elog( "Unable to convert transaction JSON to MongoDB JSON: ${e}", ("e", e.what()) ); - elog( " JSON: ${j}", ("j", trx_extensions_json) ); - elog( " JSON: ${j}", ("j", trx_signatures_json) ); - elog( " JSON: ${j}", ("j", trx_context_free_data_json) ); - } + trans_doc.append( kvp( "accepted", b_bool{t->accepted} ) ); + trans_doc.append( kvp( "implicit", b_bool{t->implicit} ) ); + trans_doc.append( kvp( "scheduled", b_bool{t->scheduled} ) ); trans_doc.append( kvp( "createdAt", b_date{now} ) ); @@ -1003,63 +911,6 @@ void mongo_db_plugin_impl::_process_irreversible_block(const chain::block_state_ } } -void mongo_db_plugin_impl::add_data( bsoncxx::builder::basic::document& act_doc, const chain::action& act ) -{ - using bsoncxx::builder::basic::kvp; - using bsoncxx::builder::basic::make_document; - try { - if( act.account == chain::config::system_account_name ) { - if( act.name == mongo_db_plugin_impl::setabi ) { - auto setabi = act.data_as(); - try { - const abi_def& abi_def = fc::raw::unpack( setabi.abi ); - const string json_str = fc::json::to_string( abi_def ); - - act_doc.append( - kvp( "data", make_document( kvp( "account", setabi.account.to_string()), - kvp( "abi_def", bsoncxx::from_json( json_str ))))); - return; - } catch( bsoncxx::exception& ) { - // better error handling below - } catch( fc::exception& e ) { - ilog( "Unable to convert action abi_def to json for ${n}", ("n", setabi.account.to_string())); - } - } - } - auto serializer = get_abi_serializer( act.account ); - if( serializer.valid() ) { - string json; - try { - auto v = serializer->binary_to_variant( serializer->get_action_type( act.name ), act.data, abi_serializer_max_time ); - json = fc::json::to_string( v ); - - const auto& value = bsoncxx::from_json( json ); - act_doc.append( kvp( "data", value )); - act_doc.append( kvp( "hex_data", fc::variant( act.data ).as_string())); - return; - } catch( bsoncxx::exception& e ) { - ilog( "Unable to convert EOS JSON to MongoDB JSON: ${e}", ("e", e.what())); - ilog( " EOS JSON: ${j}", ("j", json)); - ilog( " Storing data has hex." ); - } - } - } catch( std::exception& e ) { - ilog( "Unable to convert action.data to ABI: ${s}::${n}, std what: ${e}", - ("s", act.account)( "n", act.name )( "e", e.what())); - } catch (fc::exception& e) { - if (act.name != "onblock") { // eosio::onblock not in original eosio.system abi - ilog( "Unable to convert action.data to ABI: ${s}::${n}, fc exception: ${e}", - ("s", act.account)( "n", act.name )( "e", e.to_detail_string())); - } - } catch( ... ) { - ilog( "Unable to convert action.data to ABI: ${s}::${n}, unknown exception", - ("s", act.account)( "n", act.name )); - } - // if anything went wrong just store raw hex_data - act_doc.append( kvp( "hex_data", fc::variant( act.data ).as_string())); -} - - void mongo_db_plugin_impl::add_pub_keys( const vector& keys, const account_name& name, const permission_name& permission, const std::chrono::milliseconds& now ) { From efbda1015779fe1aa40d25d75bcb7e29864a6dc9 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 6 Aug 2018 13:37:26 -0500 Subject: [PATCH 168/294] Update mongo tests for new action_traces collection and new transaction layout --- tests/Node.py | 10 +++++----- tests/nodeos_run_test.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index 8f0b0deba60..89cbba5df60 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -334,8 +334,8 @@ def getBlockIdByTransId(self, transId, delayedRetry=True): key="[trx][trx][ref_block_num]" refBlockNum=trans["trx"]["trx"]["ref_block_num"] else: - key="[transaction_header][ref_block_num]" - refBlockNum=trans["transaction_header"]["ref_block_num"] + key="[ref_block_num]" + refBlockNum=trans["ref_block_num"] refBlockNum=int(refBlockNum)+1 except (TypeError, ValueError, KeyError) as _: Utils.Print("transaction%s not found. Transaction: %s" % (key, trans)) @@ -366,10 +366,10 @@ def getBlockIdByTransIdMdb(self, transId): refBlockNum=None try: - refBlockNum=trans["transaction_header"]["ref_block_num"] + refBlockNum=trans["ref_block_num"] refBlockNum=int(refBlockNum)+1 except (TypeError, ValueError, KeyError) as _: - Utils.Print("transaction[transaction_header][ref_block_num] not found. Transaction: %s" % (trans)) + Utils.Print("transaction[ref_block_num] not found. Transaction: %s" % (trans)) return None headBlockNum=self.getHeadBlockNum() @@ -688,7 +688,7 @@ def getActionsMdb(self, account, pos=-1, offset=-1, exitOnError=False): assert(isinstance(offset, int)) cmd="%s %s" % (Utils.MongoPath, self.mongoEndpointArgs) - subcommand='db.actions.find({$or: [{"data.from":"%s"},{"data.to":"%s"}]}).sort({"_id":%d}).limit(%d)' % (account.name, account.name, pos, abs(offset)) + subcommand='db.action_traces.find({$or: [{"act.data.from":"%s"},{"act.data.to":"%s"}]}).sort({"_id":%d}).limit(%d)' % (account.name, account.name, pos, abs(offset)) if Utils.Debug: Utils.Print("cmd: echo '%s' | %s" % (subcommand, cmd)) try: actions=Node.runMongoCmdReturnJson(cmd.split(), subcommand, exitOnError=exitOnError) diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index 5353fba7ae7..f5cc8676a59 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -275,7 +275,7 @@ if not enableMongo: assert(actions["actions"][0]["action_trace"]["act"]["name"] == "transfer") else: - assert(actions["name"] == "transfer") + assert(actions["act"]["name"] == "transfer") except (AssertionError, TypeError, KeyError) as _: Print("Action validation failed. Actions: %s" % (actions)) raise From 5dd4647b1f611cedec6be23d954673083728cf76 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 6 Aug 2018 16:00:19 -0500 Subject: [PATCH 169/294] -Add mongodb-filter-on and mongodb-filter-out similar to history plugin. Thanks to Scott Sallinen scotts@ece.ubc.ca for implementation. --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 103 ++++++++++++++++++-- 1 file changed, 96 insertions(+), 7 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 23ded113701..01f558da46c 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -48,6 +49,18 @@ using chain::packed_transaction; static appbase::abstract_plugin& _mongo_db_plugin = app().register_plugin(); +struct filter_entry { + name receiver; + name action; + name actor; + std::tuple key() const { + return std::make_tuple(receiver, action, actor); + } + friend bool operator<( const filter_entry& a, const filter_entry& b ) { + return a.key() < b.key(); + } +}; + class mongo_db_plugin_impl { public: mongo_db_plugin_impl(); @@ -78,7 +91,7 @@ class mongo_db_plugin_impl { void purge_abi_cache(); - void add_action_trace( mongocxx::bulk_write& bulk_action_traces, const chain::action_trace& atrace, + bool add_action_trace( mongocxx::bulk_write& bulk_action_traces, const chain::action_trace& atrace, bool executed, const std::chrono::milliseconds& now ); void update_account(const chain::action& act); @@ -91,6 +104,9 @@ class mongo_db_plugin_impl { const std::chrono::milliseconds& now ); void remove_account_control( const account_name& name, const permission_name& permission ); + /// @return true if act should be added to mongodb, false to skip it + bool filter_include( const chain::action& act ) const; + void init(); void wipe_database(); @@ -101,6 +117,10 @@ class mongo_db_plugin_impl { uint32_t start_block_num = 0; std::atomic_bool start_block_reached{false}; + bool filter_on_star = true; + std::set filter_on; + std::set filter_out; + std::string db_name; mongocxx::instance mongo_inst; mongocxx::client mongo_conn; @@ -176,6 +196,36 @@ const std::string mongo_db_plugin_impl::accounts_col = "accounts"; const std::string mongo_db_plugin_impl::pub_keys_col = "pub_keys"; const std::string mongo_db_plugin_impl::account_controls_col = "account_controls"; +bool mongo_db_plugin_impl::filter_include( const chain::action& act ) const { + bool include = false; + if( filter_on_star ) { + include = true; + } + if( filter_on.find( {act.account, act.name, 0} ) != filter_on.end() ) { + include = true; + } + for( const auto& a : act.authorization ) { + if( filter_on.find( {act.account, act.name, a.actor} ) != filter_on.end() ) { + include = true; + } + } + + if( !include ) { return false; } + + if( filter_out.find( {act.account, 0, 0} ) != filter_out.end() ) { + return false; + } + if( filter_out.find( {act.account, act.name, 0} ) != filter_out.end() ) { + return false; + } + for( const auto& a : act.authorization ) { + if( filter_out.find( {act.account, act.name, a.actor} ) != filter_out.end() ) { + return false; + } + } + return true; +} + template void mongo_db_plugin_impl::queue( Queue& queue, const Entry& e ) { boost::mutex::scoped_lock lock( mtx ); @@ -643,7 +693,7 @@ void mongo_db_plugin_impl::_process_accepted_transaction( const chain::transacti } -void +bool mongo_db_plugin_impl::add_action_trace( mongocxx::bulk_write& bulk_action_traces, const chain::action_trace& atrace, bool executed, const std::chrono::milliseconds& now ) { @@ -654,7 +704,8 @@ mongo_db_plugin_impl::add_action_trace( mongocxx::bulk_write& bulk_action_traces update_account( atrace.act ); } - if( start_block_reached ) { + bool added = false; + if( start_block_reached && filter_include( atrace.act ) ) { auto action_traces_doc = bsoncxx::builder::basic::document{}; const chain::base_action_trace& base = atrace; // without inline action traces @@ -678,11 +729,14 @@ mongo_db_plugin_impl::add_action_trace( mongocxx::bulk_write& bulk_action_traces mongocxx::model::insert_one insert_op{action_traces_doc.view()}; bulk_action_traces.append( insert_op ); + added = true; } for( const auto& iline_atrace : atrace.inline_traces ) { - add_action_trace( bulk_action_traces, iline_atrace, executed, now ); + added |= add_action_trace( bulk_action_traces, iline_atrace, executed, now ); } + + return added; } @@ -705,14 +759,13 @@ void mongo_db_plugin_impl::_process_applied_transaction( const chain::transactio for( const auto& atrace : t->action_traces ) { try { - add_action_trace( bulk_action_traces, atrace, executed, now ); - write_atraces = true; + write_atraces |= add_action_trace( bulk_action_traces, atrace, executed, now ); } catch(...) { handle_mongo_exception("add action traces", __LINE__); } } - if( write_atraces && start_block_reached ) { + if( write_atraces ) { try { if( !bulk_action_traces.execute() ) { EOS_ASSERT( false, chain::mongo_db_insert_fail, "Bulk action traces insert failed for transaction trace: ${id}", ("id", t->id)); @@ -1273,6 +1326,11 @@ void mongo_db_plugin::set_program_options(options_description& cli, options_desc "MongoDB URI connection string, see: https://docs.mongodb.com/master/reference/connection-string/." " If not specified then plugin is disabled. Default database 'EOS' is used if not specified in URI." " Example: mongodb://127.0.0.1:27017/EOS") + ("mongodb-filter-on", bpo::value>()->composing(), + "Mongodb: Track actions which match receiver:action:actor. Actor may be blank to include all. Receiver and Action may not be blank. Default is * include everything.") + ("mongodb-filter-out", bpo::value>()->composing(), + "Mongodb: Do not track actions which match receiver:action:actor. Action and Actor both blank excludes all from reciever. Actor blank excludes all from reciever:action. Receiver may not be blank.") + ; } @@ -1308,6 +1366,37 @@ void mongo_db_plugin::plugin_initialize(const variables_map& options) if( options.count( "mongodb-block-start" )) { my->start_block_num = options.at( "mongodb-block-start" ).as(); } + if( options.count( "mongodb-filter-on" )) { + auto fo = options.at( "mongodb-filter-on" ).as>(); + for( auto& s : fo ) { + if( s == "*" ) { + my->filter_on_star = true; + break; + } + std::vector v; + boost::split( v, s, boost::is_any_of( ":" )); + EOS_ASSERT( v.size() == 3, fc::invalid_arg_exception, "Invalid value ${s} for --mongodb-filter-on", ("s", s)); + filter_entry fe{v[0], v[1], v[2]}; + EOS_ASSERT( fe.receiver.value && fe.action.value, fc::invalid_arg_exception, + "Invalid value ${s} for --mongodb-filter-on", ("s", s)); + my->filter_on.insert( fe ); + } + } else { + my->filter_on_star = true; + } + if( options.count( "mongodb-filter-out" )) { + auto fo = options.at( "mongodb-filter-out" ).as>(); + for( auto& s : fo ) { + std::vector v; + boost::split( v, s, boost::is_any_of( ":" )); + EOS_ASSERT( v.size() == 3, fc::invalid_arg_exception, "Invalid value ${s} for --mongodb-filter-out", ("s", s)); + filter_entry fe{v[0], v[1], v[2]}; + EOS_ASSERT( fe.receiver.value, fc::invalid_arg_exception, + "Invalid value ${s} for --mongodb-filter-out", ("s", s)); + my->filter_out.insert( fe ); + } + } + if( my->start_block_num == 0 ) { my->start_block_reached = true; } From d24e006aa7957834e7a7212513ad93e35b64906a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 6 Aug 2018 16:45:36 -0500 Subject: [PATCH 170/294] Add options to not store blocks, block-states, transactions, transaction-traces, and action-traces --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 245 ++++++++++++-------- 1 file changed, 144 insertions(+), 101 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 01f558da46c..5fe9ac61df6 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -120,6 +120,11 @@ class mongo_db_plugin_impl { bool filter_on_star = true; std::set filter_on; std::set filter_out; + bool store_blocks = true; + bool store_block_states = true; + bool store_transactions = true; + bool store_transaction_traces = true; + bool store_action_traces = true; std::string db_name; mongocxx::instance mongo_inst; @@ -249,7 +254,9 @@ void mongo_db_plugin_impl::queue( Queue& queue, const Entry& e ) { void mongo_db_plugin_impl::accepted_transaction( const chain::transaction_metadata_ptr& t ) { try { - queue( transaction_metadata_queue, t ); + if( store_transactions ) { + queue( transaction_metadata_queue, t ); + } } catch (fc::exception& e) { elog("FC Exception while accepted_transaction ${e}", ("e", e.to_string())); } catch (std::exception& e) { @@ -261,6 +268,7 @@ void mongo_db_plugin_impl::accepted_transaction( const chain::transaction_metada void mongo_db_plugin_impl::applied_transaction( const chain::transaction_trace_ptr& t ) { try { + // always queue since account information always gathered queue( transaction_trace_queue, t ); } catch (fc::exception& e) { elog("FC Exception while applied_transaction ${e}", ("e", e.to_string())); @@ -273,7 +281,9 @@ void mongo_db_plugin_impl::applied_transaction( const chain::transaction_trace_p void mongo_db_plugin_impl::applied_irreversible_block( const chain::block_state_ptr& bs ) { try { - queue( irreversible_block_state_queue, bs ); + if( store_blocks || store_transactions ) { + queue( irreversible_block_state_queue, bs ); + } } catch (fc::exception& e) { elog("FC Exception while applied_irreversible_block ${e}", ("e", e.to_string())); } catch (std::exception& e) { @@ -290,7 +300,9 @@ void mongo_db_plugin_impl::accepted_block( const chain::block_state_ptr& bs ) { start_block_reached = true; } } - queue( block_state_queue, bs ); + if( store_blocks || store_block_states ) { + queue( block_state_queue, bs ); + } } catch (fc::exception& e) { elog("FC Exception while accepted_block ${e}", ("e", e.to_string())); } catch (std::exception& e) { @@ -705,7 +717,7 @@ mongo_db_plugin_impl::add_action_trace( mongocxx::bulk_write& bulk_action_traces } bool added = false; - if( start_block_reached && filter_include( atrace.act ) ) { + if( start_block_reached && store_action_traces && filter_include( atrace.act ) ) { auto action_traces_doc = bsoncxx::builder::basic::document{}; const chain::base_action_trace& base = atrace; // without inline action traces @@ -775,7 +787,7 @@ void mongo_db_plugin_impl::_process_applied_transaction( const chain::transactio } } - if( !start_block_reached ) return; + if( !start_block_reached || !store_transaction_traces ) return; // transaction trace insert @@ -818,78 +830,81 @@ void mongo_db_plugin_impl::_process_accepted_block( const chain::block_state_ptr auto block_num = bs->block_num; if( block_num % 1000 == 0 ) ilog( "block_num: ${b}", ("b", block_num) ); - const auto block_id = bs->id; + const auto& block_id = bs->id; const auto block_id_str = block_id.str(); - const auto prev_block_id_str = bs->block->previous.str(); auto now = std::chrono::duration_cast( std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()}); - const chain::block_header_state& bhs = *bs; + if( store_block_states ) { + auto block_states = mongo_conn[db_name][block_states_col]; + auto block_state_doc = bsoncxx::builder::basic::document{}; + block_state_doc.append( kvp( "block_num", b_int32{static_cast(block_num)} ), + kvp( "block_id", block_id_str ), + kvp( "validated", b_bool{bs->validated} ), + kvp( "in_current_chain", b_bool{bs->in_current_chain} ) ); - auto block_states = mongo_conn[db_name][block_states_col]; - auto block_state_doc = bsoncxx::builder::basic::document{}; - block_state_doc.append(kvp( "block_num", b_int32{static_cast(block_num)} ), - kvp( "block_id", block_id_str ), - kvp( "validated", b_bool{bs->validated} ), - kvp( "in_current_chain", b_bool{bs->in_current_chain} )); + const chain::block_header_state& bhs = *bs; - auto json = fc::json::to_string( bhs ); - try { - const auto& value = bsoncxx::from_json( json ); - block_state_doc.append( kvp( "block_header_state", value )); - } catch( bsoncxx::exception& ) { + auto json = fc::json::to_string( bhs ); try { - json = fc::prune_invalid_utf8(json); const auto& value = bsoncxx::from_json( json ); - block_state_doc.append( kvp( "block_header_state", value )); - block_state_doc.append( kvp( "non-utf8-purged", b_bool{true})); - } catch( bsoncxx::exception& e ) { - elog( "Unable to convert block_header_state JSON to MongoDB JSON: ${e}", ("e", e.what())); - elog( " JSON: ${j}", ("j", json)); + block_state_doc.append( kvp( "block_header_state", value ) ); + } catch( bsoncxx::exception& ) { + try { + json = fc::prune_invalid_utf8( json ); + const auto& value = bsoncxx::from_json( json ); + block_state_doc.append( kvp( "block_header_state", value ) ); + block_state_doc.append( kvp( "non-utf8-purged", b_bool{true} ) ); + } catch( bsoncxx::exception& e ) { + elog( "Unable to convert block_header_state JSON to MongoDB JSON: ${e}", ("e", e.what()) ); + elog( " JSON: ${j}", ("j", json) ); + } } - } - block_state_doc.append(kvp( "createdAt", b_date{now} )); + block_state_doc.append( kvp( "createdAt", b_date{now} ) ); - try { - if( !block_states.update_one( make_document( kvp( "block_id", block_id_str )), - make_document( kvp( "$set", block_state_doc.view())), update_opts )) { - EOS_ASSERT( false, chain::mongo_db_insert_fail, "Failed to insert block_state ${bid}", ("bid", block_id)); + try { + if( !block_states.update_one( make_document( kvp( "block_id", block_id_str ) ), + make_document( kvp( "$set", block_state_doc.view() ) ), update_opts ) ) { + EOS_ASSERT( false, chain::mongo_db_insert_fail, "Failed to insert block_state ${bid}", ("bid", block_id) ); + } + } catch( ... ) { + handle_mongo_exception( "block_states insert: " + json, __LINE__ ); } - } catch(...) { - handle_mongo_exception("block_states insert: " + json, __LINE__); } - auto blocks = mongo_conn[db_name][blocks_col]; - auto block_doc = bsoncxx::builder::basic::document{}; - block_doc.append( kvp( "block_num", b_int32{static_cast(block_num)} ), - kvp( "block_id", block_id_str ) ); + if( store_blocks ) { + auto blocks = mongo_conn[db_name][blocks_col]; + auto block_doc = bsoncxx::builder::basic::document{}; + block_doc.append( kvp( "block_num", b_int32{static_cast(block_num)} ), + kvp( "block_id", block_id_str ) ); - auto v = to_variant_with_abi( *bs->block ); - json = fc::json::to_string( v ); - try { - const auto& value = bsoncxx::from_json( json ); - block_doc.append( kvp( "block", value )); - } catch( bsoncxx::exception& ) { + auto v = to_variant_with_abi( *bs->block ); + auto json = fc::json::to_string( v ); try { - json = fc::prune_invalid_utf8(json); const auto& value = bsoncxx::from_json( json ); - block_doc.append( kvp( "block", value )); - block_doc.append( kvp( "non-utf8-purged", b_bool{true})); - } catch( bsoncxx::exception& e ) { - elog( "Unable to convert block JSON to MongoDB JSON: ${e}", ("e", e.what())); - elog( " JSON: ${j}", ("j", json)); + block_doc.append( kvp( "block", value ) ); + } catch( bsoncxx::exception& ) { + try { + json = fc::prune_invalid_utf8( json ); + const auto& value = bsoncxx::from_json( json ); + block_doc.append( kvp( "block", value ) ); + block_doc.append( kvp( "non-utf8-purged", b_bool{true} ) ); + } catch( bsoncxx::exception& e ) { + elog( "Unable to convert block JSON to MongoDB JSON: ${e}", ("e", e.what()) ); + elog( " JSON: ${j}", ("j", json) ); + } } - } - block_doc.append(kvp( "createdAt", b_date{now} )); + block_doc.append( kvp( "createdAt", b_date{now} ) ); - try { - if( !blocks.update_one( make_document( kvp( "block_id", block_id_str )), - make_document( kvp( "$set", block_doc.view())), update_opts )) { - EOS_ASSERT( false, chain::mongo_db_insert_fail, "Failed to insert block ${bid}", ("bid", block_id)); + try { + if( !blocks.update_one( make_document( kvp( "block_id", block_id_str ) ), + make_document( kvp( "$set", block_doc.view() ) ), update_opts ) ) { + EOS_ASSERT( false, chain::mongo_db_insert_fail, "Failed to insert block ${bid}", ("bid", block_id) ); + } + } catch( ... ) { + handle_mongo_exception( "blocks insert: " + json, __LINE__ ); } - } catch(...) { - handle_mongo_exception("blocks insert: " + json, __LINE__); } } @@ -910,56 +925,60 @@ void mongo_db_plugin_impl::_process_irreversible_block(const chain::block_state_ auto now = std::chrono::duration_cast( std::chrono::microseconds{fc::time_point::now().time_since_epoch().count()}); - auto ir_block = find_block(blocks, block_id_str); - if( !ir_block ) { - _process_accepted_block( bs ); - ir_block = find_block( blocks, block_id_str ); - if( !ir_block ) return; // should never happen + if( store_blocks ) { + auto ir_block = find_block( blocks, block_id_str ); + if( !ir_block ) { + _process_accepted_block( bs ); + ir_block = find_block( blocks, block_id_str ); + if( !ir_block ) return; // should never happen + } + + auto update_doc = make_document( kvp( "$set", make_document( kvp( "irreversible", b_bool{true} ), + kvp( "validated", b_bool{bs->validated} ), + kvp( "in_current_chain", b_bool{bs->in_current_chain} ), + kvp( "updatedAt", b_date{now} ) ) ) ); + + blocks.update_one( make_document( kvp( "_id", ir_block->view()["_id"].get_oid() ) ), update_doc.view() ); } - auto update_doc = make_document( kvp( "$set", make_document( kvp( "irreversible", b_bool{true} ), - kvp( "validated", b_bool{bs->validated} ), - kvp( "in_current_chain", b_bool{bs->in_current_chain} ), - kvp( "updatedAt", b_date{now})))); + if( store_transactions ) { + bool transactions_in_block = false; + mongocxx::options::bulk_write bulk_opts; + bulk_opts.ordered( false ); + auto bulk = trans.create_bulk_write( bulk_opts ); + + for( const auto& receipt : bs->block->transactions ) { + string trx_id_str; + if( receipt.trx.contains() ) { + const auto& pt = receipt.trx.get(); + // get id via get_raw_transaction() as packed_transaction.id() mutates internal transaction state + const auto& raw = pt.get_raw_transaction(); + const auto& id = fc::raw::unpack( raw ).id(); + trx_id_str = id.str(); + } else { + const auto& id = receipt.trx.get(); + trx_id_str = id.str(); + } - blocks.update_one( make_document( kvp( "_id", ir_block->view()["_id"].get_oid())), update_doc.view()); + auto update_doc = make_document( kvp( "$set", make_document( kvp( "irreversible", b_bool{true} ), + kvp( "block_id", block_id_str ), + kvp( "block_num", b_int32{static_cast(block_num)} ), + kvp( "updatedAt", b_date{now} ) ) ) ); - bool transactions_in_block = false; - mongocxx::options::bulk_write bulk_opts; - bulk_opts.ordered(false); - auto bulk = trans.create_bulk_write(bulk_opts); - - for (const auto& receipt : bs->block->transactions) { - string trx_id_str; - if( receipt.trx.contains()) { - const auto& pt = receipt.trx.get(); - // get id via get_raw_transaction() as packed_transaction.id() mutates internal transaction state - const auto& raw = pt.get_raw_transaction(); - const auto& id = fc::raw::unpack(raw).id(); - trx_id_str = id.str(); - } else { - const auto& id = receipt.trx.get(); - trx_id_str = id.str(); + mongocxx::model::update_one update_op{make_document( kvp( "trx_id", trx_id_str ) ), update_doc.view()}; + update_op.upsert( true ); + bulk.append( update_op ); + transactions_in_block = true; } - auto update_doc = make_document( kvp( "$set", make_document( kvp( "irreversible", b_bool{true} ), - kvp( "block_id", block_id_str ), - kvp( "block_num", b_int32{static_cast(block_num)} ), - kvp( "updatedAt", b_date{now} )))); - - mongocxx::model::update_one update_op{make_document( kvp( "trx_id", trx_id_str )), update_doc.view()}; - update_op.upsert( true ); - bulk.append( update_op ); - transactions_in_block = true; - } - - if( transactions_in_block ) { - try { - if( !bulk.execute()) { - EOS_ASSERT( false, chain::mongo_db_insert_fail, "Bulk transaction insert failed for block: ${bid}", ("bid", block_id)); + if( transactions_in_block ) { + try { + if( !bulk.execute() ) { + EOS_ASSERT( false, chain::mongo_db_insert_fail, "Bulk transaction insert failed for block: ${bid}", ("bid", block_id) ); + } + } catch( ... ) { + handle_mongo_exception( "bulk transaction insert", __LINE__ ); } - } catch(...) { - handle_mongo_exception("bulk transaction insert", __LINE__); } } } @@ -1326,11 +1345,20 @@ void mongo_db_plugin::set_program_options(options_description& cli, options_desc "MongoDB URI connection string, see: https://docs.mongodb.com/master/reference/connection-string/." " If not specified then plugin is disabled. Default database 'EOS' is used if not specified in URI." " Example: mongodb://127.0.0.1:27017/EOS") + ("mongodb-store-blocks", bpo::bool_switch()->default_value(true), + "Enables storing blocks in mongodb.") + ("mongodb-store-block-states", bpo::bool_switch()->default_value(true), + "Enables storing block state in mongodb.") + ("mongodb-store-transactions", bpo::bool_switch()->default_value(true), + "Enables storing transactions in mongodb.") + ("mongodb-store-transaction-traces", bpo::bool_switch()->default_value(true), + "Enables storing transaction traces in mongodb.") + ("mongodb-store-action-traces", bpo::bool_switch()->default_value(true), + "Enables storing action traces in mongodb.") ("mongodb-filter-on", bpo::value>()->composing(), "Mongodb: Track actions which match receiver:action:actor. Actor may be blank to include all. Receiver and Action may not be blank. Default is * include everything.") ("mongodb-filter-out", bpo::value>()->composing(), "Mongodb: Do not track actions which match receiver:action:actor. Action and Actor both blank excludes all from reciever. Actor blank excludes all from reciever:action. Receiver may not be blank.") - ; } @@ -1366,6 +1394,21 @@ void mongo_db_plugin::plugin_initialize(const variables_map& options) if( options.count( "mongodb-block-start" )) { my->start_block_num = options.at( "mongodb-block-start" ).as(); } + if( options.count( "mongodb-store-blocks" )) { + my->store_blocks = options.at( "mongodb-store-blocks" ).as(); + } + if( options.count( "mongodb-store-block-states" )) { + my->store_block_states = options.at( "mongodb-store-block-states" ).as(); + } + if( options.count( "mongodb-store-transactions" )) { + my->store_transactions = options.at( "mongodb-store-transactions" ).as(); + } + if( options.count( "mongodb-store-transaction-traces" )) { + my->store_transaction_traces = options.at( "mongodb-store-transaction-traces" ).as(); + } + if( options.count( "mongodb-store-action-traces" )) { + my->store_action_traces = options.at( "mongodb-store-action-traces" ).as(); + } if( options.count( "mongodb-filter-on" )) { auto fo = options.at( "mongodb-filter-on" ).as>(); for( auto& s : fo ) { From 0a49ff6ce5782968aa8d2a9ed8dc745a0048dad5 Mon Sep 17 00:00:00 2001 From: Scott Sallinen Date: Wed, 8 Aug 2018 11:02:20 -0700 Subject: [PATCH 171/294] Squash possible exception on getting remote endpoint --- plugins/net_plugin/net_plugin.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index cd999ddccac..3ef49614e72 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -2122,7 +2122,8 @@ namespace eosio { auto index = conn->pending_message_buffer.read_index(); conn->pending_message_buffer.peek(&message_length, sizeof(message_length), index); if(message_length > def_send_buffer_size*2 || message_length == 0) { - elog("incoming message length unexpected (${i}), from ${p}", ("i", message_length)("p",boost::lexical_cast(conn->socket->remote_endpoint()))); + boost::system::error_code ec; + elog("incoming message length unexpected (${i}), from ${p}", ("i", message_length)("p",boost::lexical_cast(conn->socket->remote_endpoint(ec)))); close(conn); return; } From 2e71a0c07fd09950be98eecb161ef8a9d0d58c8c Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 1 Aug 2018 17:18:32 -0400 Subject: [PATCH 172/294] - dont open database sessions when replaying irreversible blocks unless necessary - dont add transactions to dedup if they will expire before replay ends - dont do various checks that cannot fail inside trx_context --- libraries/chain/controller.cpp | 59 +++++--- .../chain/include/eosio/chain/controller.hpp | 2 + .../eosio/chain/transaction_context.hpp | 5 +- libraries/chain/transaction_context.cpp | 131 ++++++++++-------- 4 files changed, 118 insertions(+), 79 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 5254aadf519..564c6d850d3 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -28,10 +28,10 @@ using resource_limits::resource_limits_manager; struct pending_state { - pending_state( database::session&& s ) + pending_state( optional&& s ) :_db_session( move(s) ){} - database::session _db_session; + optional _db_session; block_state_ptr _pending_block_state; @@ -40,7 +40,7 @@ struct pending_state { controller::block_status _block_status = controller::block_status::incomplete; void push() { - _db_session.push(); + if (_db_session) _db_session->push(); } }; @@ -57,7 +57,8 @@ struct controller_impl { authorization_manager authorization; controller::config conf; chain_id_type chain_id; - bool replaying = false; + bool replaying= false; + optional replay_head_time; db_read_mode read_mode = db_read_mode::SPECULATIVE; bool in_trx_requiring_checks = false; ///< if true, checks that are normally skipped on replay (e.g. auth checks) cannot be skipped optional subjective_cpu_leeway; @@ -211,8 +212,10 @@ struct controller_impl { initialize_fork_db(); // set head to genesis state auto end = blog.read_head(); + auto end_time = end->timestamp.to_time_point(); if( end && end->block_num() > 1 ) { replaying = true; + replay_head_time = end_time; ilog( "existing block log, attempting to replay ${n} blocks", ("n",end->block_num()) ); auto start = fc::time_point::now(); @@ -225,6 +228,10 @@ struct controller_impl { std::cerr<< "\n"; ilog( "${n} blocks replayed", ("n", head->block_num) ); + // the irreverible log is played without undo sessions enabled, so we need to sync the + // revision ordinal to the appropriate expected value here. + db.set_revision(head->block_num); + int rev = 0; while( auto obj = reversible_blocks.find(head->block_num+1) ) { ++rev; @@ -237,6 +244,7 @@ struct controller_impl { ("n", head->block_num)("duration", (end-start).count()/1000000) ("mspb", ((end-start).count()/1000.0)/head->block_num) ); replaying = false; + replay_head_time.reset(); } else if( !end ) { blog.reset_to_genesis( conf.genesis, head->block ); @@ -547,7 +555,9 @@ struct controller_impl { transaction_trace_ptr push_scheduled_transaction( const generated_transaction_object& gto, fc::time_point deadline, uint32_t billed_cpu_time_us, bool explicit_billed_cpu_time = false ) { try { - auto undo_session = db.start_undo_session(true); + optional undo_session; + if ( !self.skip_db_sessions() ) undo_session = db.start_undo_session(true); + auto gtrx = generated_transaction(gto); // remove the generated transaction object after making a copy @@ -577,7 +587,7 @@ struct controller_impl { trace->receipt = push_receipt( gtrx.trx_id, transaction_receipt::expired, billed_cpu_time_us, 0 ); // expire the transaction emit( self.accepted_transaction, trx ); emit( self.applied_transaction, trace ); - undo_session.squash(); + if (undo_session) undo_session->squash(); return trace; } @@ -612,7 +622,7 @@ struct controller_impl { emit( self.applied_transaction, trace ); trx_context.squash(); - undo_session.squash(); + if (undo_session) undo_session->squash(); restore.cancel(); @@ -623,7 +633,7 @@ struct controller_impl { trace->except_ptr = std::current_exception(); trace->elapsed = fc::time_point::now() - trx_context.start; } - trx_context.undo_session.undo(); + trx_context.undo(); // Only subjective OR soft OR hard failure logic below: @@ -636,7 +646,7 @@ struct controller_impl { if( !trace->except_ptr ) { emit( self.accepted_transaction, trx ); emit( self.applied_transaction, trace ); - undo_session.squash(); + if (undo_session) undo_session->squash(); return trace; } trace->elapsed = fc::time_point::now() - trx_context.start; @@ -674,12 +684,10 @@ struct controller_impl { emit( self.accepted_transaction, trx ); emit( self.applied_transaction, trace ); - undo_session.squash(); + if (undo_session) undo_session->squash(); } else { emit( self.accepted_transaction, trx ); emit( self.applied_transaction, trace ); - - undo_session.undo(); } return trace; @@ -729,9 +737,11 @@ struct controller_impl { trx_context.init_for_implicit_trx(); trx_context.can_subjectively_fail = false; } else { + bool skip_recording = replay_head_time && (time_point(trx->trx.expiration) <= *replay_head_time); trx_context.init_for_input_trx( trx->packed_trx.get_unprunable_size(), trx->packed_trx.get_prunable_size(), - trx->trx.signatures.size()); + trx->trx.signatures.size(), + skip_recording); } if( trx_context.can_subjectively_fail && pending->_block_status == controller::block_status::incomplete ) { @@ -815,14 +825,19 @@ struct controller_impl { void start_block( block_timestamp_type when, uint16_t confirm_block_count, controller::block_status s ) { EOS_ASSERT( !pending, block_validate_exception, "pending block is not available" ); - EOS_ASSERT( db.revision() == head->block_num, database_exception, "db revision is not on par with head block", - ("db.revision()", db.revision())("controller_head_block", head->block_num)("fork_db_head_block", fork_db.head()->block_num) ); - auto guard_pending = fc::make_scoped_exit([this](){ pending.reset(); }); - pending = db.start_undo_session(true); + bool skip_db_sessions = !conf.force_all_checks && (s == controller::block_status::irreversible); + if (!skip_db_sessions) { + EOS_ASSERT( db.revision() == head->block_num, database_exception, "db revision is not on par with head block", + ("db.revision()", db.revision())("controller_head_block", head->block_num)("fork_db_head_block", fork_db.head()->block_num) ); + + pending.emplace(db.start_undo_session(true)); + } else { + pending.emplace(optional()); + } pending->_block_status = s; @@ -1592,10 +1607,18 @@ optional controller::proposed_producers()const { return gpo.proposed_schedule; } -bool controller::skip_auth_check()const { +bool controller::skip_auth_check() const { return my->replaying && !my->conf.force_all_checks && !my->in_trx_requiring_checks; } +bool controller::skip_db_sessions() const { + return !my->conf.force_all_checks && my->pending && !my->in_trx_requiring_checks && my->pending->_block_status == block_status::irreversible; +} + +bool controller::skip_trx_checks() const { + return !my->conf.force_all_checks &&my->pending && !my->in_trx_requiring_checks && (my->pending->_block_status == block_status::irreversible || my->pending->_block_status == block_status::validated); +} + bool controller::contracts_console()const { return my->conf.contracts_console; } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 5c6a9c6f644..dab6ffed41a 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -212,6 +212,8 @@ namespace eosio { namespace chain { int64_t set_proposed_producers( vector producers ); bool skip_auth_check()const; + bool skip_db_sessions()const; + bool skip_trx_checks()const; bool contracts_console()const; diff --git a/libraries/chain/include/eosio/chain/transaction_context.hpp b/libraries/chain/include/eosio/chain/transaction_context.hpp index e82dc234a58..e275ecf0025 100644 --- a/libraries/chain/include/eosio/chain/transaction_context.hpp +++ b/libraries/chain/include/eosio/chain/transaction_context.hpp @@ -19,7 +19,8 @@ namespace eosio { namespace chain { void init_for_input_trx( uint64_t packed_trx_unprunable_size, uint64_t packed_trx_prunable_size, - uint32_t num_signatures); + uint32_t num_signatures, + bool skip_recording); void init_for_deferred_trx( fc::time_point published ); @@ -63,7 +64,7 @@ namespace eosio { namespace chain { controller& control; const signed_transaction& trx; transaction_id_type id; - chainbase::database::session undo_session; + optional undo_session; transaction_trace_ptr trace; fc::time_point start; diff --git a/libraries/chain/transaction_context.cpp b/libraries/chain/transaction_context.cpp index 3ed10c0df70..0545c6337b8 100644 --- a/libraries/chain/transaction_context.cpp +++ b/libraries/chain/transaction_context.cpp @@ -16,12 +16,15 @@ namespace eosio { namespace chain { :control(c) ,trx(t) ,id(trx_id) - ,undo_session(c.db().start_undo_session(true)) + ,undo_session() ,trace(std::make_shared()) ,start(s) ,net_usage(trace->net_usage) ,pseudo_start(s) { + if (!c.skip_db_sessions()) { + undo_session = c.db().start_undo_session(true); + } trace->id = id; executed.reserve( trx.total_actions() ); EOS_ASSERT( trx.transaction_extensions.size() == 0, unsupported_feature, "we don't support any extensions yet" ); @@ -136,7 +139,8 @@ namespace eosio { namespace chain { void transaction_context::init_for_input_trx( uint64_t packed_trx_unprunable_size, uint64_t packed_trx_prunable_size, - uint32_t num_signatures) + uint32_t num_signatures, + bool skip_recording ) { const auto& cfg = control.get_global_properties().configuration; @@ -162,11 +166,14 @@ namespace eosio { namespace chain { published = control.pending_block_time(); is_input = true; - control.validate_expiration( trx ); - control.validate_tapos( trx ); - control.validate_referenced_accounts( trx ); + if (!control.skip_trx_checks()) { + control.validate_expiration(trx); + control.validate_tapos(trx); + control.validate_referenced_accounts(trx); + } init( initial_net_usage); - record_transaction( id, trx.expiration ); /// checks for dupes + if (!skip_recording) + record_transaction( id, trx.expiration ); /// checks for dupes } void transaction_context::init_for_deferred_trx( fc::time_point p ) @@ -250,52 +257,56 @@ namespace eosio { namespace chain { } void transaction_context::squash() { - undo_session.squash(); + if (undo_session) undo_session->squash(); } void transaction_context::undo() { - undo_session.undo(); + if (undo_session) undo_session->undo(); } void transaction_context::check_net_usage()const { - if( BOOST_UNLIKELY(net_usage > eager_net_limit) ) { - if ( net_limit_due_to_block ) { - EOS_THROW( block_net_usage_exceeded, - "not enough space left in block: ${net_usage} > ${net_limit}", - ("net_usage", net_usage)("net_limit", eager_net_limit) ); - } else if (net_limit_due_to_greylist) { - EOS_THROW( greylist_net_usage_exceeded, - "net usage of transaction is too high: ${net_usage} > ${net_limit}", - ("net_usage", net_usage)("net_limit", eager_net_limit) ); - } else { - EOS_THROW( tx_net_usage_exceeded, - "net usage of transaction is too high: ${net_usage} > ${net_limit}", - ("net_usage", net_usage)("net_limit", eager_net_limit) ); + if (!control.skip_trx_checks()) { + if( BOOST_UNLIKELY(net_usage > eager_net_limit) ) { + if ( net_limit_due_to_block ) { + EOS_THROW( block_net_usage_exceeded, + "not enough space left in block: ${net_usage} > ${net_limit}", + ("net_usage", net_usage)("net_limit", eager_net_limit) ); + } else if (net_limit_due_to_greylist) { + EOS_THROW( greylist_net_usage_exceeded, + "net usage of transaction is too high: ${net_usage} > ${net_limit}", + ("net_usage", net_usage)("net_limit", eager_net_limit) ); + } else { + EOS_THROW( tx_net_usage_exceeded, + "net usage of transaction is too high: ${net_usage} > ${net_limit}", + ("net_usage", net_usage)("net_limit", eager_net_limit) ); + } } } } void transaction_context::checktime()const { - auto now = fc::time_point::now(); - if( BOOST_UNLIKELY( now > _deadline ) ) { - // edump((now-start)(now-pseudo_start)); - if( explicit_billed_cpu_time || deadline_exception_code == deadline_exception::code_value ) { - EOS_THROW( deadline_exception, "deadline exceeded", ("now", now)("deadline", _deadline)("start", start) ); - } else if( deadline_exception_code == block_cpu_usage_exceeded::code_value ) { - EOS_THROW( block_cpu_usage_exceeded, - "not enough time left in block to complete executing transaction", - ("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) ); - } else if( deadline_exception_code == tx_cpu_usage_exceeded::code_value ) { - EOS_THROW( tx_cpu_usage_exceeded, - "transaction was executing for too long", - ("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) ); - } else if( deadline_exception_code == leeway_deadline_exception::code_value ) { - EOS_THROW( leeway_deadline_exception, - "the transaction was unable to complete by deadline, " - "but it is possible it could have succeeded if it were allowed to run to completion", - ("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) ); + if (!control.skip_trx_checks()) { + auto now = fc::time_point::now(); + if( BOOST_UNLIKELY( now > _deadline ) ) { + // edump((now-start)(now-pseudo_start)); + if( explicit_billed_cpu_time || deadline_exception_code == deadline_exception::code_value ) { + EOS_THROW( deadline_exception, "deadline exceeded", ("now", now)("deadline", _deadline)("start", start) ); + } else if( deadline_exception_code == block_cpu_usage_exceeded::code_value ) { + EOS_THROW( block_cpu_usage_exceeded, + "not enough time left in block to complete executing transaction", + ("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) ); + } else if( deadline_exception_code == tx_cpu_usage_exceeded::code_value ) { + EOS_THROW( tx_cpu_usage_exceeded, + "transaction was executing for too long", + ("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) ); + } else if( deadline_exception_code == leeway_deadline_exception::code_value ) { + EOS_THROW( leeway_deadline_exception, + "the transaction was unable to complete by deadline, " + "but it is possible it could have succeeded if it were allowed to run to completion", + ("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) ); + } + EOS_ASSERT( false, transaction_exception, "unexpected deadline exception code" ); } - EOS_ASSERT( false, transaction_exception, "unexpected deadline exception code" ); } } @@ -323,26 +334,28 @@ namespace eosio { namespace chain { } void transaction_context::validate_cpu_usage_to_bill( int64_t billed_us, bool check_minimum )const { - if( check_minimum ) { - const auto& cfg = control.get_global_properties().configuration; - EOS_ASSERT( billed_us >= cfg.min_transaction_cpu_usage, transaction_exception, - "cannot bill CPU time less than the minimum of ${min_billable} us", - ("min_billable", cfg.min_transaction_cpu_usage)("billed_cpu_time_us", billed_us) - ); - } + if (!control.skip_trx_checks()) { + if( check_minimum ) { + const auto& cfg = control.get_global_properties().configuration; + EOS_ASSERT( billed_us >= cfg.min_transaction_cpu_usage, transaction_exception, + "cannot bill CPU time less than the minimum of ${min_billable} us", + ("min_billable", cfg.min_transaction_cpu_usage)("billed_cpu_time_us", billed_us) + ); + } - if( billing_timer_exception_code == block_cpu_usage_exceeded::code_value ) { - EOS_ASSERT( billed_us <= objective_duration_limit.count(), - block_cpu_usage_exceeded, - "billed CPU time (${billed} us) is greater than the billable CPU time left in the block (${billable} us)", - ("billed", billed_us)("billable", objective_duration_limit.count()) - ); - } else { - EOS_ASSERT( billed_us <= objective_duration_limit.count(), - tx_cpu_usage_exceeded, - "billed CPU time (${billed} us) is greater than the maximum billable CPU time for the transaction (${billable} us)", - ("billed", billed_us)("billable", objective_duration_limit.count()) - ); + if( billing_timer_exception_code == block_cpu_usage_exceeded::code_value ) { + EOS_ASSERT( billed_us <= objective_duration_limit.count(), + block_cpu_usage_exceeded, + "billed CPU time (${billed} us) is greater than the billable CPU time left in the block (${billable} us)", + ("billed", billed_us)("billable", objective_duration_limit.count()) + ); + } else { + EOS_ASSERT( billed_us <= objective_duration_limit.count(), + tx_cpu_usage_exceeded, + "billed CPU time (${billed} us) is greater than the maximum billable CPU time for the transaction (${billable} us)", + ("billed", billed_us)("billable", objective_duration_limit.count()) + ); + } } } From d6f5b6bb411877786138c0d28ed86957dda6ea25 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Wed, 8 Aug 2018 14:46:55 -0400 Subject: [PATCH 173/294] Add the git "version string" to get_info --- plugins/chain_plugin/chain_plugin.cpp | 5 +++-- .../chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index cc296a6751a..5d48e71487a 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -884,9 +884,10 @@ read_only::get_info_results read_only::get_info(const read_only::get_info_params rm.get_virtual_block_cpu_limit(), rm.get_virtual_block_net_limit(), rm.get_block_cpu_limit(), - rm.get_block_net_limit() + rm.get_block_net_limit(), //std::bitset<64>(db.get_dynamic_global_properties().recent_slots_filled).to_string(), - //__builtin_popcountll(db.get_dynamic_global_properties().recent_slots_filled) / 64.0 + //__builtin_popcountll(db.get_dynamic_global_properties().recent_slots_filled) / 64.0, + app().version_string(), }; } diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index dcd6ca1c1d4..06a7bdca144 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -90,6 +90,7 @@ class read_only { uint64_t block_net_limit = 0; //string recent_slots; //double participation_rate = 0; + optional server_version_string; }; get_info_results get_info(const get_info_params&) const; @@ -544,7 +545,7 @@ class chain_plugin : public plugin { FC_REFLECT( eosio::chain_apis::permission, (perm_name)(parent)(required_auth) ) FC_REFLECT(eosio::chain_apis::empty, ) FC_REFLECT(eosio::chain_apis::read_only::get_info_results, -(server_version)(chain_id)(head_block_num)(last_irreversible_block_num)(last_irreversible_block_id)(head_block_id)(head_block_time)(head_block_producer)(virtual_block_cpu_limit)(virtual_block_net_limit)(block_cpu_limit)(block_net_limit) ) +(server_version)(chain_id)(head_block_num)(last_irreversible_block_num)(last_irreversible_block_id)(head_block_id)(head_block_time)(head_block_producer)(virtual_block_cpu_limit)(virtual_block_net_limit)(block_cpu_limit)(block_net_limit)(server_version_string) ) FC_REFLECT(eosio::chain_apis::read_only::get_block_params, (block_num_or_id)) FC_REFLECT(eosio::chain_apis::read_only::get_block_header_state_params, (block_num_or_id)) From 240ed8f5cd1adcf0f7531232eb32ada1a3bf66b1 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 8 Aug 2018 15:14:33 -0400 Subject: [PATCH 174/294] wrap optional session in a type --- libraries/chain/controller.cpp | 65 ++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 564c6d850d3..202b38f8095 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -26,12 +26,54 @@ namespace eosio { namespace chain { using resource_limits::resource_limits_manager; +class maybe_session { + public: + maybe_session() = default; + + maybe_session( maybe_session&& other) + :_session(move(other._session)) + { + } + + explicit maybe_session(database& db) { + _session = db.start_undo_session(true); + } + + maybe_session(const maybe_session&) = delete; + + void squash() { + if (_session) + _session->squash(); + } + + void undo() { + if (_session) + _session->undo(); + } + + void push() { + if (_session) + _session->push(); + } + + maybe_session& operator = ( maybe_session&& mv ) { + if (mv._session) { + _session = move(*mv._session); + mv._session.reset(); + } else { + _session.reset(); + } + }; + + private: + optional _session; +}; struct pending_state { - pending_state( optional&& s ) + pending_state( maybe_session&& s ) :_db_session( move(s) ){} - optional _db_session; + maybe_session _db_session; block_state_ptr _pending_block_state; @@ -40,7 +82,7 @@ struct pending_state { controller::block_status _block_status = controller::block_status::incomplete; void push() { - if (_db_session) _db_session->push(); + _db_session.push(); } }; @@ -555,8 +597,9 @@ struct controller_impl { transaction_trace_ptr push_scheduled_transaction( const generated_transaction_object& gto, fc::time_point deadline, uint32_t billed_cpu_time_us, bool explicit_billed_cpu_time = false ) { try { - optional undo_session; - if ( !self.skip_db_sessions() ) undo_session = db.start_undo_session(true); + maybe_session undo_session; + if ( !self.skip_db_sessions() ) + undo_session = maybe_session(db); auto gtrx = generated_transaction(gto); @@ -587,7 +630,7 @@ struct controller_impl { trace->receipt = push_receipt( gtrx.trx_id, transaction_receipt::expired, billed_cpu_time_us, 0 ); // expire the transaction emit( self.accepted_transaction, trx ); emit( self.applied_transaction, trace ); - if (undo_session) undo_session->squash(); + undo_session.squash(); return trace; } @@ -622,7 +665,7 @@ struct controller_impl { emit( self.applied_transaction, trace ); trx_context.squash(); - if (undo_session) undo_session->squash(); + undo_session.squash(); restore.cancel(); @@ -646,7 +689,7 @@ struct controller_impl { if( !trace->except_ptr ) { emit( self.accepted_transaction, trx ); emit( self.applied_transaction, trace ); - if (undo_session) undo_session->squash(); + undo_session.squash(); return trace; } trace->elapsed = fc::time_point::now() - trx_context.start; @@ -684,7 +727,7 @@ struct controller_impl { emit( self.accepted_transaction, trx ); emit( self.applied_transaction, trace ); - if (undo_session) undo_session->squash(); + undo_session.squash(); } else { emit( self.accepted_transaction, trx ); emit( self.applied_transaction, trace ); @@ -834,9 +877,9 @@ struct controller_impl { EOS_ASSERT( db.revision() == head->block_num, database_exception, "db revision is not on par with head block", ("db.revision()", db.revision())("controller_head_block", head->block_num)("fork_db_head_block", fork_db.head()->block_num) ); - pending.emplace(db.start_undo_session(true)); + pending.emplace(maybe_session(db)); } else { - pending.emplace(optional()); + pending.emplace(maybe_session()); } pending->_block_status = s; From 83a02dcfe6c5aa383a0a30db776d7ca7af3d4926 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 8 Aug 2018 15:24:56 -0400 Subject: [PATCH 175/294] create option for disabling replay opts instead of co-opting the force-all-checks flag --- libraries/chain/controller.cpp | 6 +++--- libraries/chain/include/eosio/chain/controller.hpp | 2 ++ plugins/chain_plugin/chain_plugin.cpp | 3 +++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 202b38f8095..325953708aa 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -872,7 +872,7 @@ struct controller_impl { pending.reset(); }); - bool skip_db_sessions = !conf.force_all_checks && (s == controller::block_status::irreversible); + bool skip_db_sessions = !conf.disable_replay_opts && (s == controller::block_status::irreversible); if (!skip_db_sessions) { EOS_ASSERT( db.revision() == head->block_num, database_exception, "db revision is not on par with head block", ("db.revision()", db.revision())("controller_head_block", head->block_num)("fork_db_head_block", fork_db.head()->block_num) ); @@ -1655,11 +1655,11 @@ bool controller::skip_auth_check() const { } bool controller::skip_db_sessions() const { - return !my->conf.force_all_checks && my->pending && !my->in_trx_requiring_checks && my->pending->_block_status == block_status::irreversible; + return !my->conf.disable_replay_opts && my->pending && !my->in_trx_requiring_checks && my->pending->_block_status == block_status::irreversible; } bool controller::skip_trx_checks() const { - return !my->conf.force_all_checks &&my->pending && !my->in_trx_requiring_checks && (my->pending->_block_status == block_status::irreversible || my->pending->_block_status == block_status::validated); + return !my->conf.disable_replay_opts &&my->pending && !my->in_trx_requiring_checks && (my->pending->_block_status == block_status::irreversible || my->pending->_block_status == block_status::validated); } bool controller::contracts_console()const { diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index dab6ffed41a..26941cbe833 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -57,6 +57,7 @@ namespace eosio { namespace chain { uint64_t reversible_guard_size = chain::config::default_reversible_guard_size; bool read_only = false; bool force_all_checks = false; + bool disable_replay_opts = false; bool contracts_console = false; genesis_state genesis; @@ -286,6 +287,7 @@ FC_REFLECT( eosio::chain::controller::config, (reversible_cache_size) (read_only) (force_all_checks) + (disable_replay_opts) (contracts_console) (genesis) (wasm_runtime) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index cc296a6751a..673418e3f9e 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -225,6 +225,8 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip "recovers reversible block database if that database is in a bad state") ("force-all-checks", bpo::bool_switch()->default_value(false), "do not skip any checks that can be skipped while replaying irreversible blocks") + ("disable-replay-opts", bpo::bool_switch()->default_value(false), + "disable optimizations that specifically target replay") ("replay-blockchain", bpo::bool_switch()->default_value(false), "clear chain state database and replay all blocks") ("hard-replay-blockchain", bpo::bool_switch()->default_value(false), @@ -359,6 +361,7 @@ void chain_plugin::plugin_initialize(const variables_map& options) { my->chain_config->wasm_runtime = *my->wasm_runtime; my->chain_config->force_all_checks = options.at( "force-all-checks" ).as(); + my->chain_config->disable_replay_opts = options.at( "disable-replay-opts" ).as(); my->chain_config->contracts_console = options.at( "contracts-console" ).as(); if( options.count( "extract-genesis-json" ) || options.at( "print-genesis-json" ).as()) { From 9eade4c1099e0a652c9b4c1869c7b75e7908cf1a Mon Sep 17 00:00:00 2001 From: venediktov Date: Wed, 8 Aug 2018 17:42:35 -0700 Subject: [PATCH 176/294] fix ripemd160 --- .../chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index cfde423786b..9f416c782ae 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -514,6 +514,7 @@ class read_write { } }; + //key160 support with padding zeros in the end of key256 template<> struct keytype_converter { using input_type = chain::checksum160_type; @@ -521,8 +522,8 @@ class read_write { static auto function() { return [](const input_type& v) { chain::key256_t k; - k[0] = ((uint32_t *)&v._hash)[0]; //0-31 - k[1] = ((uint128_t *)&v._hash)[1]; //32-160 + memset(k.data(), 0, sizeof(k)); + memcpy(k.data(), v._hash, sizeof(v._hash)); return k; }; } From 08dbf8b757d46e7232c6c8f4299537b347ed8891 Mon Sep 17 00:00:00 2001 From: "Sam (Sangho Kim)" Date: Thu, 9 Aug 2018 16:19:15 +0900 Subject: [PATCH 177/294] Changed install / uninstall script --- eosio_install.sh | 6 +++--- eosio_uninstall.sh | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/eosio_install.sh b/eosio_install.sh index ad6f029e669..a2981eab399 100755 --- a/eosio_install.sh +++ b/eosio_install.sh @@ -56,13 +56,13 @@ install_symlinks() { printf "\\n\\tInstalling EOSIO Binary Symlinks\\n\\n" create_symlink "cleos" - create_symlink "nodeos" create_symlink "eosio-abigen" - create_symlink "eosio-applesdemo" create_symlink "eosio-launcher" create_symlink "eosio-s2wasm" create_symlink "eosio-wast2wasm" - create_symlink "eosio-keosd" + create_symlink "eosiocpp" + create_symlink "keosd" + create_symlink "nodeos" } if [ ! -d "${BUILD_DIR}" ]; then diff --git a/eosio_uninstall.sh b/eosio_uninstall.sh index 4b559eb5024..c22fbf6e9ef 100755 --- a/eosio_uninstall.sh +++ b/eosio_uninstall.sh @@ -1,13 +1,14 @@ #! /bin/bash binaries=(cleos - nodeos - eosio-keosd eosio-abigen - eosio-applesdemo eosio-launcher eosio-s2wasm - eosio-wast2wasm) + eosio-wast2wasm + eosiocpp + keosd + nodeos + eosio-applesdemo) if [ -d "/usr/local/eosio" ]; then printf "\tDo you wish to remove this install? (requires sudo)\n" From 3e9c83dbe97d90e35b1cf7be095794c7062895d4 Mon Sep 17 00:00:00 2001 From: Kayan Date: Thu, 9 Aug 2018 17:57:08 +0800 Subject: [PATCH 178/294] prevent cpu exhaustion on p2p connection cleanup --- plugins/net_plugin/net_plugin.cpp | 35 ++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 5363f035210..af0a6558cf7 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -163,6 +163,7 @@ namespace eosio { boost::asio::steady_timer::duration txn_exp_period; boost::asio::steady_timer::duration resp_expected_period; boost::asio::steady_timer::duration keepalive_interval{std::chrono::seconds{32}}; + int max_cleanup_time_ms = 0; const std::chrono::system_clock::duration peer_authentication_interval{std::chrono::seconds{1}}; ///< Peer clock may be no more than 1 second skewed from our clock, including network latency. @@ -229,12 +230,12 @@ namespace eosio { void handle_message( connection_ptr c, const signed_block &msg); void handle_message( connection_ptr c, const packed_transaction &msg); - void start_conn_timer( ); + void start_conn_timer(boost::asio::steady_timer::duration du, std::weak_ptr from_connection); void start_txn_timer( ); void start_monitors( ); void expire_txns( ); - void connection_monitor( ); + void connection_monitor(std::weak_ptr from_connection); /** \name Peer Timestamps * Time message handling * @{ @@ -2593,15 +2594,15 @@ namespace eosio { } } - void net_plugin_impl::start_conn_timer( ) { - connector_check->expires_from_now( connector_period); - connector_check->async_wait( [this](boost::system::error_code ec) { + void net_plugin_impl::start_conn_timer(boost::asio::steady_timer::duration du, std::weak_ptr from_connection) { + connector_check->expires_from_now( du); + connector_check->async_wait( [this, from_connection](boost::system::error_code ec) { if( !ec) { - connection_monitor( ); + connection_monitor(from_connection); } else { elog( "Error from connection check monitor: ${m}",( "m", ec.message())); - start_conn_timer( ); + start_conn_timer( connector_period, std::weak_ptr()); } }); } @@ -2637,7 +2638,7 @@ namespace eosio { void net_plugin_impl::start_monitors() { connector_check.reset(new boost::asio::steady_timer( app().get_io_service())); transaction_check.reset(new boost::asio::steady_timer( app().get_io_service())); - start_conn_timer(); + start_conn_timer(connector_period, std::weak_ptr()); start_txn_timer(); } @@ -2662,10 +2663,17 @@ namespace eosio { } } - void net_plugin_impl::connection_monitor( ) { - start_conn_timer(); - auto it = connections.begin(); - while(it != connections.end()) { + void net_plugin_impl::connection_monitor(std::weak_ptr from_connection) { + auto max_time = fc::time_point::now(); + max_time += fc::milliseconds(max_cleanup_time_ms); + auto from = from_connection.lock(); + auto it = (from ? connections.find(from) : connections.begin()); + if (it == connections.end()) it = connections.begin(); + while (it != connections.end()) { + if (fc::time_point::now() >= max_time) { + start_conn_timer(std::chrono::milliseconds(1), *it); // avoid exhausting + return; + } if( !(*it)->socket->is_open() && !(*it)->connecting) { if( (*it)->peer_addr.length() > 0) { connect(*it); @@ -2677,6 +2685,7 @@ namespace eosio { } ++it; } + start_conn_timer(connector_period, std::weak_ptr()); } void net_plugin_impl::close( connection_ptr c ) { @@ -2878,6 +2887,7 @@ namespace eosio { "Tuple of [PublicKey, WIF private key] (may specify multiple times)") ( "max-clients", bpo::value()->default_value(def_max_clients), "Maximum number of clients from which connections are accepted, use 0 for no limit") ( "connection-cleanup-period", bpo::value()->default_value(def_conn_retry_wait), "number of seconds to wait before cleaning up dead connections") + ( "max-cleanup-time-msec", bpo::value()->default_value(10), "max connection cleanup time per cleanup call in millisec") ( "network-version-match", bpo::value()->default_value(false), "True to require exact match of peer network version.") ( "sync-fetch-span", bpo::value()->default_value(def_sync_fetch_span), "number of blocks to retrieve in a chunk from any individual peer during synchronization") @@ -2912,6 +2922,7 @@ namespace eosio { my->dispatcher.reset( new dispatch_manager ); my->connector_period = std::chrono::seconds( options.at( "connection-cleanup-period" ).as()); + my->max_cleanup_time_ms = options.at("max-cleanup-time-msec").as(); my->txn_exp_period = def_txn_expire_wait; my->resp_expected_period = def_resp_expected_wait; my->dispatcher->just_send_it_max = options.at( "max-implicit-request" ).as(); From ebbdb2db0a5f6ac0c1cb53bc9fe885ccf7ea14b9 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 9 Aug 2018 09:27:44 -0400 Subject: [PATCH 179/294] only fetch unapplied transactions if we need them In sync and real-time scenarios under moderate to heavy traffic load there can be a large backlog of unapplied transactions. Fetching the unapplied transactions has a cost associated due to the container conversion implied. However, that map of transactions is only useful if we are: * an RPC node that wants to persist incoming transactions state mutations until they get into a block or expire and need to determine the intersection of the persisted and unapplied transactions * OR we are a producer trying to apply transactions learned but not yet applied In all other cases, we can avoid the cost of fetching the unapplied transactions outright --- plugins/producer_plugin/producer_plugin.cpp | 110 +++++++++++--------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 07af983ec4c..ed0161e53ef 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -950,7 +950,6 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block(bool // attempt to play persisted transactions first bool exhausted = false; - auto unapplied_trxs = chain.get_unapplied_transactions(); // remove all persisted transactions that have now expired auto& persisted_by_id = _persistent_transactions.get(); @@ -960,67 +959,76 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block(bool } try { - for (auto itr = unapplied_trxs.begin(); itr != unapplied_trxs.end(); ++itr) { - const auto& trx = *itr; - if (persisted_by_id.find(trx->id) != persisted_by_id.end()) { - // this is a persisted transaction, push it into the block (even if we are speculating) with - // no deadline as it has already passed the subjective deadlines once and we want to represent - // the state of the chain including this transaction - try { - chain.push_transaction(trx, fc::time_point::maximum()); - } catch ( const guard_exception& e ) { - app().get_plugin().handle_guard_exception(e); - return start_block_result::failed; - } FC_LOG_AND_DROP(); - - // remove it from further consideration as it is applied - *itr = nullptr; - } - } - size_t orig_pending_txn_size = _pending_incoming_transactions.size(); - if (_pending_block_mode == pending_block_mode::producing) { - for (const auto& trx : unapplied_trxs) { - if (block_time <= fc::time_point::now()) exhausted = true; - if (exhausted) { - break; + if (!persisted_by_expiry.empty() || _pending_block_mode == pending_block_mode::producing) { + auto unapplied_trxs = chain.get_unapplied_transactions(); + + if (!persisted_by_expiry.empty()) { + for (auto itr = unapplied_trxs.begin(); itr != unapplied_trxs.end(); ++itr) { + const auto& trx = *itr; + if (persisted_by_id.find(trx->id) != persisted_by_id.end()) { + // this is a persisted transaction, push it into the block (even if we are speculating) with + // no deadline as it has already passed the subjective deadlines once and we want to represent + // the state of the chain including this transaction + try { + chain.push_transaction(trx, fc::time_point::maximum()); + } catch ( const guard_exception& e ) { + app().get_plugin().handle_guard_exception(e); + return start_block_result::failed; + } FC_LOG_AND_DROP(); + + // remove it from further consideration as it is applied + *itr = nullptr; + } } + } - if (!trx) { - // nulled in the loop above, skip it - continue; - } + if (_pending_block_mode == pending_block_mode::producing) { + for (const auto& trx : unapplied_trxs) { + if (block_time <= fc::time_point::now()) exhausted = true; + if (exhausted) { + break; + } - if (trx->packed_trx.expiration() < pbs->header.timestamp.to_time_point()) { - // expired, drop it - chain.drop_unapplied_transaction(trx); - continue; - } + if (!trx) { + // nulled in the loop above, skip it + continue; + } - try { - auto deadline = fc::time_point::now() + fc::milliseconds(_max_transaction_time_ms); - bool deadline_is_subjective = false; - if (_max_transaction_time_ms < 0 || (_pending_block_mode == pending_block_mode::producing && block_time < deadline)) { - deadline_is_subjective = true; - deadline = block_time; + if (trx->packed_trx.expiration() < pbs->header.timestamp.to_time_point()) { + // expired, drop it + chain.drop_unapplied_transaction(trx); + continue; } - auto trace = chain.push_transaction(trx, deadline); - if (trace->except) { - if (failure_is_subjective(*trace->except, deadline_is_subjective)) { - exhausted = true; - } else { - // this failed our configured maximum transaction time, we don't want to replay it - chain.drop_unapplied_transaction(trx); + try { + auto deadline = fc::time_point::now() + fc::milliseconds(_max_transaction_time_ms); + bool deadline_is_subjective = false; + if (_max_transaction_time_ms < 0 || (_pending_block_mode == pending_block_mode::producing && block_time < deadline)) { + deadline_is_subjective = true; + deadline = block_time; } - } - } catch ( const guard_exception& e ) { - app().get_plugin().handle_guard_exception(e); - return start_block_result::failed; - } FC_LOG_AND_DROP(); + + auto trace = chain.push_transaction(trx, deadline); + if (trace->except) { + if (failure_is_subjective(*trace->except, deadline_is_subjective)) { + exhausted = true; + } else { + // this failed our configured maximum transaction time, we don't want to replay it + chain.drop_unapplied_transaction(trx); + } + } + } catch ( const guard_exception& e ) { + app().get_plugin().handle_guard_exception(e); + return start_block_result::failed; + } FC_LOG_AND_DROP(); + } } + } + + if (_pending_block_mode == pending_block_mode::producing) { auto& blacklist_by_id = _blacklisted_transactions.get(); auto& blacklist_by_expiry = _blacklisted_transactions.get(); auto now = fc::time_point::now(); From 854c72a1c04e823e89de299627969aebc00ecf77 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 9 Aug 2018 09:30:12 -0500 Subject: [PATCH 180/294] Optimize filter_include --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 5fe9ac61df6..a55049b4fc8 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -203,15 +203,14 @@ const std::string mongo_db_plugin_impl::account_controls_col = "account_controls bool mongo_db_plugin_impl::filter_include( const chain::action& act ) const { bool include = false; - if( filter_on_star ) { + if( filter_on_star || filter_on.find( {act.account, act.name, 0} ) != filter_on.end() ) { include = true; - } - if( filter_on.find( {act.account, act.name, 0} ) != filter_on.end() ) { - include = true; - } - for( const auto& a : act.authorization ) { - if( filter_on.find( {act.account, act.name, a.actor} ) != filter_on.end() ) { - include = true; + } else { + for( const auto& a : act.authorization ) { + if( filter_on.find( {act.account, act.name, a.actor} ) != filter_on.end() ) { + include = true; + break; + } } } From ba483ebdaf3e7e17b97ebd9398c0e7e23119d879 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 9 Aug 2018 09:30:55 -0500 Subject: [PATCH 181/294] Cut down on output when mongodb fails --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index a55049b4fc8..d9d374be83e 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -451,8 +451,7 @@ void handle_mongo_exception( const std::string& desc, int line_num ) { elog( "mongo exception, ${desc}, line ${line}, code ${code}, ${details}", ("desc", desc)( "line", line_num )( "code", e.code().value() )( "details", e.code().message() )); if (e.raw_server_error()) { - elog( "mongo exception, ${desc}, line ${line}, ${details}", - ("desc", desc)( "line", line_num )( "details", bsoncxx::to_json(e.raw_server_error()->view()))); + elog( " raw_server_error: ${e}", ( "e", bsoncxx::to_json(e.raw_server_error()->view()))); } } catch( mongocxx::exception& e) { elog( "mongo exception, ${desc}, line ${line}, code ${code}, ${what}", From cbd3a3c11cf88d49473c6079e89f1f5f74bac522 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 9 Aug 2018 10:01:39 -0500 Subject: [PATCH 182/294] Use boost value instead of bool_switch since bool_switch defaults to false --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index d9d374be83e..4aaea9b3298 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -1343,15 +1343,15 @@ void mongo_db_plugin::set_program_options(options_description& cli, options_desc "MongoDB URI connection string, see: https://docs.mongodb.com/master/reference/connection-string/." " If not specified then plugin is disabled. Default database 'EOS' is used if not specified in URI." " Example: mongodb://127.0.0.1:27017/EOS") - ("mongodb-store-blocks", bpo::bool_switch()->default_value(true), + ("mongodb-store-blocks", bpo::value()->default_value(true), "Enables storing blocks in mongodb.") - ("mongodb-store-block-states", bpo::bool_switch()->default_value(true), + ("mongodb-store-block-states", bpo::value()->default_value(true), "Enables storing block state in mongodb.") - ("mongodb-store-transactions", bpo::bool_switch()->default_value(true), + ("mongodb-store-transactions", bpo::value()->default_value(true), "Enables storing transactions in mongodb.") - ("mongodb-store-transaction-traces", bpo::bool_switch()->default_value(true), + ("mongodb-store-transaction-traces", bpo::value()->default_value(true), "Enables storing transaction traces in mongodb.") - ("mongodb-store-action-traces", bpo::bool_switch()->default_value(true), + ("mongodb-store-action-traces", bpo::value()->default_value(true), "Enables storing action traces in mongodb.") ("mongodb-filter-on", bpo::value>()->composing(), "Mongodb: Track actions which match receiver:action:actor. Actor may be blank to include all. Receiver and Action may not be blank. Default is * include everything.") From 944e8caefb4618499e7e631301cbf956155c813f Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 9 Aug 2018 12:04:12 -0400 Subject: [PATCH 183/294] prevent bad access on new chains --- libraries/chain/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 325953708aa..cc9095ce744 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -254,8 +254,8 @@ struct controller_impl { initialize_fork_db(); // set head to genesis state auto end = blog.read_head(); - auto end_time = end->timestamp.to_time_point(); if( end && end->block_num() > 1 ) { + auto end_time = end->timestamp.to_time_point(); replaying = true; replay_head_time = end_time; ilog( "existing block log, attempting to replay ${n} blocks", ("n",end->block_num()) ); From d846b3a3e0cc55fe32155fe88f5497f943ff738e Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 9 Aug 2018 13:48:50 -0400 Subject: [PATCH 184/294] fix bug in wrapper classes move assignment --- libraries/chain/controller.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index cc9095ce744..449f0784588 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -63,6 +63,8 @@ class maybe_session { } else { _session.reset(); } + + return *this; }; private: From 96521344129f7c977529dcee4a389060902a21ed Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 1 Aug 2018 11:04:25 -0500 Subject: [PATCH 185/294] Minor changes to test and improved information for error reporting. GH #4971 --- tests/Cluster.py | 7 +++++++ tests/nodeos_under_min_avail_ram.py | 14 ++++++++++---- tests/testUtils.py | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index c56a1b05ae1..f2b0dc66608 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -1116,6 +1116,13 @@ def createAccounts(self, creator, waitForTransBlock=True, stakedDeposit=1000): return True + def getInfos(self, silentErrors=False, exitOnError=False): + infos=[] + for node in self.nodes: + infos.append(node.getInfo(silentErrors=silentErrors, exitOnError=exitOnError)) + + return infos + def reportStatus(self): if hasattr(self, "biosNode") and self.biosNode is not None: self.biosNode.reportStatus() diff --git a/tests/nodeos_under_min_avail_ram.py b/tests/nodeos_under_min_avail_ram.py index 55b8c7bcdc7..568ef8d2fce 100755 --- a/tests/nodeos_under_min_avail_ram.py +++ b/tests/nodeos_under_min_avail_ram.py @@ -81,7 +81,7 @@ def setName(self, num): maxRAMFlag="--chain-state-db-size-mb" maxRAMValue=1010 extraNodeosArgs=" %s %d %s %d " % (minRAMFlag, minRAMValue, maxRAMFlag, maxRAMValue) - if cluster.launch(onlyBios=False, dontKill=dontKill, pnodes=totalNodes, totalNodes=totalNodes, totalProducers=totalNodes*21, extraNodeosArgs=extraNodeosArgs) is False: + if cluster.launch(onlyBios=False, dontKill=dontKill, pnodes=totalNodes, totalNodes=totalNodes, totalProducers=totalNodes, extraNodeosArgs=extraNodeosArgs) is False: Utils.cmdError("launcher") errorExit("Failed to stand up eos cluster.") @@ -200,24 +200,29 @@ def setName(self, num): numNodes=len(nodes) maxRAMValue+=2 currentMinimumMaxRAM=maxRAMValue + enabledStaleProduction=False for i in range(numNodes): addOrSwapFlags[maxRAMFlag]=str(maxRAMValue) - if i==numNodes-1: - addOrSwapFlags["--enable-stale-production"]="" # just enable stale production for the first node + #addOrSwapFlags["--max-irreversible-block-age"]=str(-1) nodeIndex=numNodes-i-1 + if not enabledStaleProduction: + addOrSwapFlags["--enable-stale-production"]="" # just enable stale production for the first node + enabledStaleProduction=True if not nodes[nodeIndex].relaunch(nodeIndex, "", newChain=False, addOrSwapFlags=addOrSwapFlags): Utils.cmdError("Failed to restart node0 with new capacity %s" % (maxRAMValue)) errorExit("Failure - Node should have restarted") addOrSwapFlags={} maxRAMValue=currentMinimumMaxRAM+30 - time.sleep(10) + time.sleep(20) for i in range(numNodes): if not nodes[i].verifyAlive(): Utils.cmdError("Node %d should be alive" % (i)) errorExit("Failure - All Nodes should be alive") + # get all the nodes to get info, so reported status (on error) reflects their current state Print("push more actions to %s contract" % (contract)) + cluster.getInfos() action="store" keepProcessing=True count=0 @@ -278,6 +283,7 @@ def setName(self, num): Utils.cmdError("All Nodes should be alive") errorExit("Failure - All Nodes should be alive") + time.sleep(20) Print("Send 1 more action to every node") numAmount+=1 for fromIndexOffset in range(namedAccounts.numAccounts): diff --git a/tests/testUtils.py b/tests/testUtils.py index ad77cd20c4c..f3016b38013 100755 --- a/tests/testUtils.py +++ b/tests/testUtils.py @@ -30,7 +30,7 @@ def Print(*args, **kwargs): stackDepth=len(inspect.stack())-2 s=' '*stackDepth stdout.write(s) - print(*args, **kwargs) + print(*args, **kwargs, flush=True) SyncStrategy=namedtuple("ChainSyncStrategy", "name id arg") From 6b01ffaab948dc85f6e2ce164cc7385aa8a4baeb Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 1 Aug 2018 11:06:44 -0500 Subject: [PATCH 186/294] Fix to allow producing node with --enable-stale-production time to become the producting node before it decides to go from speculating to waiting. GH #4971 --- plugins/producer_plugin/producer_plugin.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 07af983ec4c..ee783139284 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -118,6 +118,7 @@ class producer_plugin_impl : public std::enable_shared_from_this_irreversible_block_time = fc::time_point::maximum(); } + my->_stale_production_window_open = my->_production_enabled; if (!my->_producers.empty()) { ilog("Launching block production for ${n} producers at ${time}.", ("n", my->_producers.size())("time",fc::time_point::now())); @@ -914,8 +916,13 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block(bool if (_pending_block_mode == pending_block_mode::speculating) { auto head_block_age = now - chain.head_block_time(); - if (head_block_age > fc::seconds(5)) - return start_block_result::waiting; + if (head_block_age > fc::seconds(5)) { + if (!_stale_production_window_open) + return start_block_result::waiting; + } else { + // once we are not behind, then the stale production window is no longer needed + _stale_production_window_open = false; + } } try { From 57da8db69484582e84638de02e279cb3b7eb7936 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 1 Aug 2018 12:32:12 -0500 Subject: [PATCH 187/294] Removed print flush flag that is not supported for CentOS. GH #4971 --- tests/testUtils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testUtils.py b/tests/testUtils.py index f3016b38013..ad77cd20c4c 100755 --- a/tests/testUtils.py +++ b/tests/testUtils.py @@ -30,7 +30,7 @@ def Print(*args, **kwargs): stackDepth=len(inspect.stack())-2 s=' '*stackDepth stdout.write(s) - print(*args, **kwargs, flush=True) + print(*args, **kwargs) SyncStrategy=namedtuple("ChainSyncStrategy", "name id arg") From 368cf3ec6cbf91265def2f564911f11f4a5ca3e0 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Thu, 9 Aug 2018 13:52:35 -0500 Subject: [PATCH 188/294] Added timer for waiting to not waste time during slots that it cannot produce. GH #4971 --- plugins/producer_plugin/producer_plugin.cpp | 124 +++++++++++--------- tests/nodeos_under_min_avail_ram.py | 2 +- 2 files changed, 70 insertions(+), 56 deletions(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index ee783139284..59cd6ae510a 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -111,14 +111,13 @@ class producer_plugin_impl : public std::enable_shared_from_this calculate_next_block_time(const account_name& producer_name) const; + optional calculate_next_block_time(const account_name& producer_name, const block_timestamp_type& current_block_time) const; void schedule_production_loop(); void produce_block(); bool maybe_produce_block(); boost::program_options::variables_map _options; bool _production_enabled = false; - bool _stale_production_window_open = false; bool _pause_production = false; uint32_t _production_skip_flags = 0; //eosio::chain::skip_nothing; @@ -417,6 +416,9 @@ class producer_plugin_impl : public std::enable_shared_from_this& weak_this, const block_timestamp_type& current_block_time); }; void new_chain_banner(const eosio::chain::controller& db) @@ -662,7 +664,6 @@ void producer_plugin::plugin_startup() my->_irreversible_block_time = fc::time_point::maximum(); } - my->_stale_production_window_open = my->_production_enabled; if (!my->_producers.empty()) { ilog("Launching block production for ${n} producers at ${time}.", ("n", my->_producers.size())("time",fc::time_point::now())); @@ -803,14 +804,11 @@ void producer_plugin::set_whitelist_blacklist(const producer_plugin::whitelist_b } -optional producer_plugin_impl::calculate_next_block_time(const account_name& producer_name) const { +optional producer_plugin_impl::calculate_next_block_time(const account_name& producer_name, const block_timestamp_type& current_block_time) const { chain::controller& chain = app().get_plugin().chain(); const auto& hbs = chain.head_block_state(); const auto& active_schedule = hbs->active_schedule.producers; - const auto& pbs = chain.pending_block_state(); - const auto& pbt = pbs->header.timestamp; - // determine if this producer is in the active schedule and if so, where auto itr = std::find_if(active_schedule.begin(), active_schedule.end(), [&](const auto& asp){ return asp.producer_name == producer_name; }); if (itr == active_schedule.end()) { @@ -829,6 +827,7 @@ optional producer_plugin_impl::calculate_next_block_time(const a auto current_watermark_itr = _producer_watermarks.find(producer_name); if (current_watermark_itr != _producer_watermarks.end()) { auto watermark = current_watermark_itr->second; + const auto& pbs = chain.pending_block_state(); if (watermark > pbs->block_num) { // if I have a watermark then I need to wait until after that watermark minimum_offset = watermark - pbs->block_num + 1; @@ -836,7 +835,7 @@ optional producer_plugin_impl::calculate_next_block_time(const a } // this producers next opportuity to produce is the next time its slot arrives after or at the calculated minimum - uint32_t minimum_slot = pbt.slot + minimum_offset; + uint32_t minimum_slot = current_block_time.slot + minimum_offset; size_t minimum_slot_producer_index = (minimum_slot % (active_schedule.size() * config::producer_repetitions)) / config::producer_repetitions; if ( producer_index == minimum_slot_producer_index ) { // this is the producer for the minimum slot, go with that @@ -858,23 +857,28 @@ optional producer_plugin_impl::calculate_next_block_time(const a } } -producer_plugin_impl::start_block_result producer_plugin_impl::start_block(bool &last_block) { - chain::controller& chain = app().get_plugin().chain(); - const auto& hbs = chain.head_block_state(); - - //Schedule for the next second's tick regardless of chain state - // If we would wait less than 50ms (1/10 of block_interval), wait for the whole block interval. - fc::time_point now = fc::time_point::now(); - fc::time_point base = std::max(now, chain.head_block_time()); - int64_t min_time_to_next_block = (config::block_interval_us) - (base.time_since_epoch().count() % (config::block_interval_us) ); +fc::time_point producer_plugin_impl::calculate_pending_block_time() const { + const chain::controller& chain = app().get_plugin().chain(); + const fc::time_point now = fc::time_point::now(); + const fc::time_point base = std::max(now, chain.head_block_time()); + const int64_t min_time_to_next_block = (config::block_interval_us) - (base.time_since_epoch().count() % (config::block_interval_us) ); fc::time_point block_time = base + fc::microseconds(min_time_to_next_block); if((block_time - now) < fc::microseconds(config::block_interval_us/10) ) { // we must sleep for at least 50ms -// ilog("Less than ${t}us to next block time, time_to_next_block_time ${bt}", -// ("t", config::block_interval_us/10)("bt", block_time)); block_time += fc::microseconds(config::block_interval_us); } + return block_time; +} + +producer_plugin_impl::start_block_result producer_plugin_impl::start_block(bool &last_block) { + chain::controller& chain = app().get_plugin().chain(); + const auto& hbs = chain.head_block_state(); + + //Schedule for the next second's tick regardless of chain state + // If we would wait less than 50ms (1/10 of block_interval), wait for the whole block interval. + const fc::time_point now = fc::time_point::now(); + const fc::time_point block_time = calculate_pending_block_time(); _pending_block_mode = pending_block_mode::producing; @@ -916,13 +920,8 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block(bool if (_pending_block_mode == pending_block_mode::speculating) { auto head_block_age = now - chain.head_block_time(); - if (head_block_age > fc::seconds(5)) { - if (!_stale_production_window_open) - return start_block_result::waiting; - } else { - // once we are not behind, then the stale production window is no longer needed - _stale_production_window_open = false; - } + if (head_block_age > fc::seconds(5)) + return start_block_result::waiting; } try { @@ -1133,9 +1132,15 @@ void producer_plugin_impl::schedule_production_loop() { self->schedule_production_loop(); } }); - } else if (result == start_block_result::waiting) { - // nothing to do until more blocks arrive - + } else if ((_pending_block_mode == pending_block_mode::speculating || result == start_block_result::waiting) && !_producers.empty() && !production_disabled_by_policy()){ + block_timestamp_type pending_block_time; + if (result == start_block_result::waiting) { + pending_block_time = calculate_pending_block_time(); + } else { + const auto& pbs = chain.pending_block_state(); + pending_block_time = pbs->header.timestamp; + } + schedule_timer(weak_this, pending_block_time); } else if (_pending_block_mode == pending_block_mode::producing) { // we succeeded but block may be exhausted @@ -1162,40 +1167,49 @@ void producer_plugin_impl::schedule_production_loop() { fc_dlog(_log, "Producing Block #${num} returned: ${res}", ("num", chain.pending_block_state()->block_num)("res", res) ); } }); - } else if (_pending_block_mode == pending_block_mode::speculating && !_producers.empty() && !production_disabled_by_policy()){ - // if we have any producers then we should at least set a timer for our next available slot - optional wake_up_time; - for (const auto&p: _producers) { - auto next_producer_block_time = calculate_next_block_time(p); - if (next_producer_block_time) { - auto producer_wake_up_time = *next_producer_block_time - fc::microseconds(config::block_interval_us); - if (wake_up_time) { - // wake up with a full block interval to the deadline - wake_up_time = std::min(*wake_up_time, producer_wake_up_time); - } else { - wake_up_time = producer_wake_up_time; - } + } else { + fc_dlog(_log, "Speculative Block Created"); + } +} + +void producer_plugin_impl::schedule_timer(const std::weak_ptr& weak_this, const block_timestamp_type& current_block_time) { + // if we have any producers then we should at least set a timer for our next available slot + optional wake_up_time; + for (const auto&p: _producers) { + auto next_producer_block_time = calculate_next_block_time(p, current_block_time); + if (next_producer_block_time) { + auto producer_wake_up_time = *next_producer_block_time - fc::microseconds(config::block_interval_us); + if (wake_up_time) { + // wake up with a full block interval to the deadline + wake_up_time = std::min(*wake_up_time, producer_wake_up_time); + } else { + wake_up_time = producer_wake_up_time; } } + } - if (wake_up_time) { + if (wake_up_time) { + if (_pending_block_mode == pending_block_mode::speculating) fc_dlog(_log, "Specualtive Block Created; Scheduling Speculative/Production Change at ${time}", ("time", wake_up_time)); - static const boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); - _timer.expires_at(epoch + boost::posix_time::microseconds(wake_up_time->time_since_epoch().count())); - _timer.async_wait([weak_this,cid=++_timer_corelation_id](const boost::system::error_code& ec) { - auto self = weak_this.lock(); - if (self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id) { - self->schedule_production_loop(); - } - }); - } else { - fc_dlog(_log, "Speculative Block Created; Not Scheduling Speculative/Production, no local producers had valid wake up times"); - } + else + fc_dlog(_log, "Waiting; Scheduling Speculative/Production Change at ${time}", ("time", wake_up_time)); + static const boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); + _timer.expires_at(epoch + boost::posix_time::microseconds(wake_up_time->time_since_epoch().count())); + _timer.async_wait([weak_this,cid=++_timer_corelation_id](const boost::system::error_code& ec) { + auto self = weak_this.lock(); + if (self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id) { + self->schedule_production_loop(); + } + }); } else { - fc_dlog(_log, "Speculative Block Created"); + if (_pending_block_mode == pending_block_mode::speculating) + fc_dlog(_log, "Speculative Block Created; Not Scheduling Speculative/Production, no local producers had valid wake up times"); + else + fc_dlog(_log, "Waiting; Not Scheduling Speculative/Production, no local producers had valid wake up times"); } } + bool producer_plugin_impl::maybe_produce_block() { auto reschedule = fc::make_scoped_exit([this]{ schedule_production_loop(); diff --git a/tests/nodeos_under_min_avail_ram.py b/tests/nodeos_under_min_avail_ram.py index 568ef8d2fce..633a7d67a13 100755 --- a/tests/nodeos_under_min_avail_ram.py +++ b/tests/nodeos_under_min_avail_ram.py @@ -272,7 +272,7 @@ def setName(self, num): currentMinimumMaxRAM=maxRAMValue addOrSwapFlags[maxRAMFlag]=str(maxRAMValue) if not nodes[len(nodes)-1].relaunch(nodeIndex, "", newChain=False, addOrSwapFlags=addOrSwapFlags): - Utils.cmdError("Failed to restart node i with new capacity %s" % (numNodes-1, maxRAMValue)) + Utils.cmdError("Failed to restart node %d with new capacity %s" % (numNodes-1, maxRAMValue)) errorExit("Failure - Node should have restarted") addOrSwapFlags={} From 2b6419bbcca340f743fd90ac613028bfdc7c2096 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 9 Aug 2018 15:10:07 -0400 Subject: [PATCH 189/294] address PR feedback --- libraries/chain/controller.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 449f0784588..adc46f9f2f1 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -868,14 +868,15 @@ struct controller_impl { void start_block( block_timestamp_type when, uint16_t confirm_block_count, controller::block_status s ) { - EOS_ASSERT( !pending, block_validate_exception, "pending block is not available" ); + EOS_ASSERT( !pending, block_validate_exception, "pending block already exists" ); auto guard_pending = fc::make_scoped_exit([this](){ pending.reset(); }); - bool skip_db_sessions = !conf.disable_replay_opts && (s == controller::block_status::irreversible); - if (!skip_db_sessions) { + pending->_block_status = s; + + if (!self.skip_db_sessions()) { EOS_ASSERT( db.revision() == head->block_num, database_exception, "db revision is not on par with head block", ("db.revision()", db.revision())("controller_head_block", head->block_num)("fork_db_head_block", fork_db.head()->block_num) ); @@ -884,8 +885,6 @@ struct controller_impl { pending.emplace(maybe_session()); } - pending->_block_status = s; - pending->_pending_block_state = std::make_shared( *head, when ); // promotes pending schedule (if any) to active pending->_pending_block_state->in_current_chain = true; @@ -1661,7 +1660,7 @@ bool controller::skip_db_sessions() const { } bool controller::skip_trx_checks() const { - return !my->conf.disable_replay_opts &&my->pending && !my->in_trx_requiring_checks && (my->pending->_block_status == block_status::irreversible || my->pending->_block_status == block_status::validated); + return !my->conf.disable_replay_opts && my->pending && !my->in_trx_requiring_checks && (my->pending->_block_status == block_status::irreversible || my->pending->_block_status == block_status::validated); } bool controller::contracts_console()const { From 894272792b90be8335c926e359a90f34864aae2f Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 9 Aug 2018 10:55:26 -0400 Subject: [PATCH 190/294] add light validation mode This mode enables skipping of auth checks, transaction signature recovery and other tests that can be inferred as passed if the node implicitly trusts the active set of producers to maintain correctness --- libraries/chain/controller.cpp | 29 +++++++++++-- .../chain/include/eosio/chain/controller.hpp | 9 +++- plugins/chain_plugin/chain_plugin.cpp | 41 +++++++++++++++++++ plugins/producer_plugin/producer_plugin.cpp | 4 ++ 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index adc46f9f2f1..21dc3a05c13 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1652,15 +1652,34 @@ optional controller::proposed_producers()const { } bool controller::skip_auth_check() const { - return my->replaying && !my->conf.force_all_checks && !my->in_trx_requiring_checks; + // replaying + bool consider_skipping = my->replaying; + + // OR in light validation mode + consider_skipping = consider_skipping || my->conf.validation_mode == validation_mode::LIGHT; + + return consider_skipping + && !my->conf.force_all_checks + && !my->in_trx_requiring_checks; } bool controller::skip_db_sessions() const { - return !my->conf.disable_replay_opts && my->pending && !my->in_trx_requiring_checks && my->pending->_block_status == block_status::irreversible; + bool consider_skipping = my->pending && my->pending->_block_status == block_status::irreversible; + return consider_skipping + && !my->conf.disable_replay_opts + && !my->in_trx_requiring_checks; } bool controller::skip_trx_checks() const { - return !my->conf.disable_replay_opts && my->pending && !my->in_trx_requiring_checks && (my->pending->_block_status == block_status::irreversible || my->pending->_block_status == block_status::validated); + // in a pending irreversible or previously validated block + bool consider_skipping = my->pending && ( my->pending->_block_status == block_status::irreversible || my->pending->_block_status == block_status::validated ); + + // OR in light validation mode + consider_skipping = consider_skipping || my->conf.validation_mode == validation_mode::LIGHT; + + return consider_skipping + && !my->conf.disable_replay_opts + && !my->in_trx_requiring_checks; } bool controller::contracts_console()const { @@ -1675,6 +1694,10 @@ db_read_mode controller::get_read_mode()const { return my->read_mode; } +validation_mode controller::get_validation_mode()const { + return my->conf.validation_mode; +} + const apply_handler* controller::find_apply_handler( account_name receiver, account_name scope, action_name act ) const { auto native_handler_scope = my->apply_handlers.find( receiver ); diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 26941cbe833..76e602ce87d 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -39,6 +39,11 @@ namespace eosio { namespace chain { IRREVERSIBLE }; + enum class validation_mode { + FULL, + LIGHT + }; + class controller { public: @@ -63,7 +68,8 @@ namespace eosio { namespace chain { genesis_state genesis; wasm_interface::vm_type wasm_runtime = chain::config::default_wasm_runtime; - db_read_mode read_mode = db_read_mode::SPECULATIVE; + db_read_mode read_mode = db_read_mode::SPECULATIVE; + validation_mode validation_mode = validation_mode::FULL; flat_set resource_greylist; }; @@ -221,6 +227,7 @@ namespace eosio { namespace chain { chain_id_type get_chain_id()const; db_read_mode get_read_mode()const; + validation_mode get_validation_mode()const; void set_subjective_cpu_leeway(fc::microseconds leeway); diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 673418e3f9e..87dc16f1300 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -72,6 +72,39 @@ void validate(boost::any& v, } } +std::ostream& operator<<(std::ostream& osm, eosio::chain::validation_mode m) { + if ( m == eosio::chain::validation_mode::FULL ) { + osm << "full"; + } else if ( m == eosio::chain::validation_mode::LIGHT ) { + osm << "light"; + } + + return osm; +} + +void validate(boost::any& v, + std::vector const& values, + eosio::chain::validation_mode* /* target_type */, + int) +{ + using namespace boost::program_options; + + // Make sure no previous assignment to 'v' was made. + validators::check_first_occurrence(v); + + // Extract the first string from 'values'. If there is more than + // one string, it's an error, and exception will be thrown. + std::string const& s = validators::get_single_string(values); + + if ( s == "full" ) { + v = boost::any(eosio::chain::validation_mode::FULL); + } else if ( s == "light" ) { + v = boost::any(eosio::chain::validation_mode::LIGHT); + } else { + throw validation_error(validation_error::invalid_option_value); + } +} + } using namespace eosio; @@ -202,6 +235,10 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip "In \"speculative\" mode database contains changes done up to the head block plus changes made by transactions not yet included to the blockchain.\n" "In \"head\" mode database contains changes done up to the current head block.\n") //"In \"irreversible\" mode database contains changes done up the current irreversible block.\n") + ("validation-mode", boost::program_options::value()->default_value(eosio::chain::validation_mode::FULL), + "Chain validation mode (\"full\" or \"light\").\n" + "In \"full\" mode all incoming blocks will be fully validated.\n" + "In \"light\" mode all incoming blocks headers will be fully validated; transactions in those validated blocks will be trusted \n") ; // TODO: rate limiting @@ -516,6 +553,10 @@ void chain_plugin::plugin_initialize(const variables_map& options) { EOS_ASSERT( my->chain_config->read_mode != db_read_mode::IRREVERSIBLE, plugin_config_exception, "irreversible mode not currently supported." ); } + if ( options.count("validation-mode") ) { + my->chain_config->validation_mode = options.at("validation-mode").as(); + } + my->chain.emplace( *my->chain_config ); my->chain_id.emplace( my->chain->get_chain_id()); diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 07af983ec4c..e54f26b2be0 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -650,6 +650,10 @@ void producer_plugin::plugin_startup() EOS_ASSERT( my->_producers.empty() || chain.get_read_mode() == chain::db_read_mode::SPECULATIVE, plugin_config_exception, "node cannot have any producer-name configured because block production is impossible when read_mode is not \"speculative\"" ); + EOS_ASSERT( my->_producers.empty() || chain.get_validation_mode() == chain::validation_mode::FULL, plugin_config_exception, + "node cannot have any producer-name configured because block production is not safe when validation_mode is not \"full\"" ); + + my->_accepted_block_connection.emplace(chain.accepted_block.connect( [this]( const auto& bsp ){ my->on_block( bsp ); } )); my->_irreversible_block_connection.emplace(chain.irreversible_block.connect( [this]( const auto& bsp ){ my->on_irreversible_block( bsp->block ); } )); From 6edc50596735e921d9693b0d889163cf43ac56da Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 9 Aug 2018 15:14:00 -0400 Subject: [PATCH 191/294] style update --- plugins/chain_plugin/chain_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 87dc16f1300..3d9804fff77 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -48,7 +48,7 @@ std::ostream& operator<<(std::ostream& osm, eosio::chain::db_read_mode m) { } void validate(boost::any& v, - std::vector const& values, + const std::vector& values, eosio::chain::db_read_mode* /* target_type */, int) { @@ -83,7 +83,7 @@ std::ostream& operator<<(std::ostream& osm, eosio::chain::validation_mode m) { } void validate(boost::any& v, - std::vector const& values, + const std::vector& values, eosio::chain::validation_mode* /* target_type */, int) { From 33a8220d5d400ad73f6d19446575a568b893e8b4 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Thu, 9 Aug 2018 15:29:49 -0400 Subject: [PATCH 192/294] resolve ambiguity between type and member --- libraries/chain/controller.cpp | 6 +++--- libraries/chain/include/eosio/chain/controller.hpp | 2 +- plugins/chain_plugin/chain_plugin.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 21dc3a05c13..66fcd93624e 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1656,7 +1656,7 @@ bool controller::skip_auth_check() const { bool consider_skipping = my->replaying; // OR in light validation mode - consider_skipping = consider_skipping || my->conf.validation_mode == validation_mode::LIGHT; + consider_skipping = consider_skipping || my->conf.block_validation_mode == validation_mode::LIGHT; return consider_skipping && !my->conf.force_all_checks @@ -1675,7 +1675,7 @@ bool controller::skip_trx_checks() const { bool consider_skipping = my->pending && ( my->pending->_block_status == block_status::irreversible || my->pending->_block_status == block_status::validated ); // OR in light validation mode - consider_skipping = consider_skipping || my->conf.validation_mode == validation_mode::LIGHT; + consider_skipping = consider_skipping || my->conf.block_validation_mode == validation_mode::LIGHT; return consider_skipping && !my->conf.disable_replay_opts @@ -1695,7 +1695,7 @@ db_read_mode controller::get_read_mode()const { } validation_mode controller::get_validation_mode()const { - return my->conf.validation_mode; + return my->conf.block_validation_mode; } const apply_handler* controller::find_apply_handler( account_name receiver, account_name scope, action_name act ) const diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 76e602ce87d..d438daef839 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -69,7 +69,7 @@ namespace eosio { namespace chain { wasm_interface::vm_type wasm_runtime = chain::config::default_wasm_runtime; db_read_mode read_mode = db_read_mode::SPECULATIVE; - validation_mode validation_mode = validation_mode::FULL; + validation_mode block_validation_mode = validation_mode::FULL; flat_set resource_greylist; }; diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 3d9804fff77..a46483ad066 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -554,7 +554,7 @@ void chain_plugin::plugin_initialize(const variables_map& options) { } if ( options.count("validation-mode") ) { - my->chain_config->validation_mode = options.at("validation-mode").as(); + my->chain_config->block_validation_mode = options.at("validation-mode").as(); } my->chain.emplace( *my->chain_config ); From e218e16069e57dcbe0a9df94ee9362a05032d51e Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Thu, 9 Aug 2018 16:17:35 -0400 Subject: [PATCH 193/294] fixed tabs to whitespace --- eosio_install.sh | 84 +++++++++++++++++++++++----------------------- eosio_uninstall.sh | 6 ++-- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/eosio_install.sh b/eosio_install.sh index a2981eab399..319430190b0 100755 --- a/eosio_install.sh +++ b/eosio_install.sh @@ -31,21 +31,21 @@ ########################################################################## - CWD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - if [ "${CWD}" != "${PWD}" ]; then - printf "\\n\\tPlease cd into directory %s to run this script.\\n \\tExiting now.\\n\\n" "${CWD}" - exit 1 - fi + CWD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + if [ "${CWD}" != "${PWD}" ]; then + printf "\\n\\tPlease cd into directory %s to run this script.\\n \\tExiting now.\\n\\n" "${CWD}" + exit 1 + fi - BUILD_DIR="${PWD}/build" - CMAKE_BUILD_TYPE=Release - TIME_BEGIN=$( date -u +%s ) + BUILD_DIR="${PWD}/build" + CMAKE_BUILD_TYPE=Release + TIME_BEGIN=$( date -u +%s ) INSTALL_PREFIX="/usr/local/eosio" - VERSION=1.2 + VERSION=1.2 - txtbld=$(tput bold) - bldred=${txtbld}$(tput setaf 1) - txtrst=$(tput sgr0) + txtbld=$(tput bold) + bldred=${txtbld}$(tput setaf 1) + txtrst=$(tput sgr0) create_symlink() { pushd /usr/local/bin &> /dev/null @@ -54,21 +54,21 @@ } install_symlinks() { - printf "\\n\\tInstalling EOSIO Binary Symlinks\\n\\n" + printf "\\n\\tInstalling EOSIO Binary Symlinks\\n\\n" create_symlink "cleos" create_symlink "eosio-abigen" create_symlink "eosio-launcher" create_symlink "eosio-s2wasm" create_symlink "eosio-wast2wasm" - create_symlink "eosiocpp" - create_symlink "keosd" + create_symlink "eosiocpp" + create_symlink "keosd" create_symlink "nodeos" } - if [ ! -d "${BUILD_DIR}" ]; then + if [ ! -d "${BUILD_DIR}" ]; then printf "\\n\\tError, eosio_build.sh has not ran. Please run ./eosio_build.sh first!\\n\\n" exit -1 - fi + fi ${PWD}/scripts/clean_old_install.sh if [ $? -ne 0 ]; then @@ -76,35 +76,35 @@ exit -1 fi - if ! pushd "${BUILD_DIR}" - then - printf "Unable to enter build directory %s.\\n Exiting now.\\n" "${BUILD_DIR}" - exit 1; - fi + if ! pushd "${BUILD_DIR}" + then + printf "Unable to enter build directory %s.\\n Exiting now.\\n" "${BUILD_DIR}" + exit 1; + fi - if ! make install - then - printf "\\n\\t>>>>>>>>>>>>>>>>>>>> MAKE installing EOSIO has exited with the above error.\\n\\n" - exit -1 - fi + if ! make install + then + printf "\\n\\t>>>>>>>>>>>>>>>>>>>> MAKE installing EOSIO has exited with the above error.\\n\\n" + exit -1 + fi popd &> /dev/null install_symlinks - printf "\n\n${bldred}\t _______ _______ _______ _________ _______\n" - printf '\t( ____ \( ___ )( ____ \\\\__ __/( ___ )\n' - printf "\t| ( \/| ( ) || ( \/ ) ( | ( ) |\n" - printf "\t| (__ | | | || (_____ | | | | | |\n" - printf "\t| __) | | | |(_____ ) | | | | | |\n" - printf "\t| ( | | | | ) | | | | | | |\n" - printf "\t| (____/\| (___) |/\____) |___) (___| (___) |\n" - printf "\t(_______/(_______)\_______)\_______/(_______)\n${txtrst}" + printf "\n\n${bldred}\t _______ _______ _______ _________ _______\n" + printf '\t( ____ \( ___ )( ____ \\\\__ __/( ___ )\n' + printf "\t| ( \/| ( ) || ( \/ ) ( | ( ) |\n" + printf "\t| (__ | | | || (_____ | | | | | |\n" + printf "\t| __) | | | |(_____ ) | | | | | |\n" + printf "\t| ( | | | | ) | | | | | | |\n" + printf "\t| (____/\| (___) |/\____) |___) (___| (___) |\n" + printf "\t(_______/(_______)\_______)\_______/(_______)\n${txtrst}" - printf "\\tTo verify your installation run the following commands:\\n" + printf "\\tTo verify your installation run the following commands:\\n" - printf "\\tFor more information:\\n" - printf "\\tEOSIO website: https://eos.io\\n" - printf "\\tEOSIO Telegram channel @ https://t.me/EOSProject\\n" - printf "\\tEOSIO resources: https://eos.io/resources/\\n" - printf "\\tEOSIO Stack Exchange: https://eosio.stackexchange.com\\n" - printf "\\tEOSIO wiki: https://github.com/EOSIO/eos/wiki\\n\\n\\n" + printf "\\tFor more information:\\n" + printf "\\tEOSIO website: https://eos.io\\n" + printf "\\tEOSIO Telegram channel @ https://t.me/EOSProject\\n" + printf "\\tEOSIO resources: https://eos.io/resources/\\n" + printf "\\tEOSIO Stack Exchange: https://eosio.stackexchange.com\\n" + printf "\\tEOSIO wiki: https://github.com/EOSIO/eos/wiki\\n\\n\\n" diff --git a/eosio_uninstall.sh b/eosio_uninstall.sh index c22fbf6e9ef..64e91d38809 100755 --- a/eosio_uninstall.sh +++ b/eosio_uninstall.sh @@ -5,10 +5,10 @@ binaries=(cleos eosio-launcher eosio-s2wasm eosio-wast2wasm - eosiocpp - keosd + eosiocpp + keosd nodeos - eosio-applesdemo) + eosio-applesdemo) if [ -d "/usr/local/eosio" ]; then printf "\tDo you wish to remove this install? (requires sudo)\n" From 2bd78af65c389383387a02c12251dce98e8daadf Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Thu, 9 Aug 2018 16:08:32 -0500 Subject: [PATCH 194/294] Changed method name and corrected logic for Pull Request changes. GH #4971 --- plugins/producer_plugin/producer_plugin.cpp | 32 ++++++++++----------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 59cd6ae510a..a4c820b76c2 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -418,7 +418,7 @@ class producer_plugin_impl : public std::enable_shared_from_this& weak_this, const block_timestamp_type& current_block_time); + void schedule_delayed_production_loop(const std::weak_ptr& weak_this, const block_timestamp_type& current_block_time); }; void new_chain_banner(const eosio::chain::controller& db) @@ -1132,15 +1132,15 @@ void producer_plugin_impl::schedule_production_loop() { self->schedule_production_loop(); } }); - } else if ((_pending_block_mode == pending_block_mode::speculating || result == start_block_result::waiting) && !_producers.empty() && !production_disabled_by_policy()){ - block_timestamp_type pending_block_time; - if (result == start_block_result::waiting) { - pending_block_time = calculate_pending_block_time(); + } else if (result == start_block_result::waiting){ + if (!_producers.empty() && !production_disabled_by_policy()) { + fc_dlog(_log, "Waiting till another block is received and scheduling Speculative/Production Change"); + schedule_delayed_production_loop(weak_this, calculate_pending_block_time()); } else { - const auto& pbs = chain.pending_block_state(); - pending_block_time = pbs->header.timestamp; + fc_dlog(_log, "Waiting till another block is received"); + // nothing to do until more blocks arrive } - schedule_timer(weak_this, pending_block_time); + } else if (_pending_block_mode == pending_block_mode::producing) { // we succeeded but block may be exhausted @@ -1167,12 +1167,16 @@ void producer_plugin_impl::schedule_production_loop() { fc_dlog(_log, "Producing Block #${num} returned: ${res}", ("num", chain.pending_block_state()->block_num)("res", res) ); } }); + } else if (_pending_block_mode == pending_block_mode::speculating && !_producers.empty() && !production_disabled_by_policy()){ + fc_dlog(_log, "Specualtive Block Created; Scheduling Speculative/Production Change"); + const auto& pbs = chain.pending_block_state(); + schedule_delayed_production_loop(weak_this, pbs->header.timestamp); } else { fc_dlog(_log, "Speculative Block Created"); } } -void producer_plugin_impl::schedule_timer(const std::weak_ptr& weak_this, const block_timestamp_type& current_block_time) { +void producer_plugin_impl::schedule_delayed_production_loop(const std::weak_ptr& weak_this, const block_timestamp_type& current_block_time) { // if we have any producers then we should at least set a timer for our next available slot optional wake_up_time; for (const auto&p: _producers) { @@ -1189,10 +1193,7 @@ void producer_plugin_impl::schedule_timer(const std::weak_ptrtime_since_epoch().count())); _timer.async_wait([weak_this,cid=++_timer_corelation_id](const boost::system::error_code& ec) { @@ -1202,10 +1203,7 @@ void producer_plugin_impl::schedule_timer(const std::weak_ptr Date: Thu, 9 Aug 2018 12:46:19 -0700 Subject: [PATCH 195/294] Fix Issue #5136 --- scripts/eosio_build_fedora.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/eosio_build_fedora.sh b/scripts/eosio_build_fedora.sh index 72b07800e83..2bfbf0ec28d 100644 --- a/scripts/eosio_build_fedora.sh +++ b/scripts/eosio_build_fedora.sh @@ -477,6 +477,18 @@ printf "\\n\\tExiting now.\\n" exit 1; fi + if ! cd "${TEMP_DIR}/llvm-compiler/llvm" + then + printf "\\n\\tUnable to enter directory %s/llvm-compiler/llvm.\\n" "${TEMP_DIR}" + printf "\\n\\tExiting now.\\n" + exit 1; + fi + if ! $(curl https://bugzilla.redhat.com/attachment.cgi?id=1389687 | git apply) + then + printf "\\n\\tUnable to apply patch https://bugzilla.redhat.com/attachment.cgi?id=1389687.\\n" + printf "\\n\\tExiting now.\\n" + exit 1; + fi if ! cd "${TEMP_DIR}/llvm-compiler/llvm/tools" then printf "\\n\\tUnable to enter directory %s/llvm-compiler/llvm/tools.\\n" "${TEMP_DIR}" From 2ac3b77b98599a7ebd798714b69bf9d928181b56 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 9 Aug 2018 18:48:38 -0500 Subject: [PATCH 196/294] Remove misleading line. --- eosio_install.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/eosio_install.sh b/eosio_install.sh index 319430190b0..0aabe8f385a 100755 --- a/eosio_install.sh +++ b/eosio_install.sh @@ -100,8 +100,6 @@ printf "\t| (____/\| (___) |/\____) |___) (___| (___) |\n" printf "\t(_______/(_______)\_______)\_______/(_______)\n${txtrst}" - printf "\\tTo verify your installation run the following commands:\\n" - printf "\\tFor more information:\\n" printf "\\tEOSIO website: https://eos.io\\n" printf "\\tEOSIO Telegram channel @ https://t.me/EOSProject\\n" From 57635bc2900acb9b44a30debf795841330af9b3f Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 10 Aug 2018 08:14:19 -0400 Subject: [PATCH 197/294] rethink a review comment that was subtly bad --- libraries/chain/controller.cpp | 17 ++++++++++++----- .../chain/include/eosio/chain/controller.hpp | 3 ++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 66fcd93624e..ba5974c8352 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -874,9 +874,7 @@ struct controller_impl { pending.reset(); }); - pending->_block_status = s; - - if (!self.skip_db_sessions()) { + if (!self.skip_db_sessions(s)) { EOS_ASSERT( db.revision() == head->block_num, database_exception, "db revision is not on par with head block", ("db.revision()", db.revision())("controller_head_block", head->block_num)("fork_db_head_block", fork_db.head()->block_num) ); @@ -885,6 +883,7 @@ struct controller_impl { pending.emplace(maybe_session()); } + pending->_block_status = s; pending->_pending_block_state = std::make_shared( *head, when ); // promotes pending schedule (if any) to active pending->_pending_block_state->in_current_chain = true; @@ -1663,13 +1662,21 @@ bool controller::skip_auth_check() const { && !my->in_trx_requiring_checks; } -bool controller::skip_db_sessions() const { - bool consider_skipping = my->pending && my->pending->_block_status == block_status::irreversible; +bool controller::skip_db_sessions( block_status bs ) const { + bool consider_skipping = bs == block_status::irreversible; return consider_skipping && !my->conf.disable_replay_opts && !my->in_trx_requiring_checks; } +bool controller::skip_db_sessions( ) const { + if (my->pending) { + return skip_db_sessions(my->pending->_block_status); + } else { + return false; + } +} + bool controller::skip_trx_checks() const { // in a pending irreversible or previously validated block bool consider_skipping = my->pending && ( my->pending->_block_status == block_status::irreversible || my->pending->_block_status == block_status::validated ); diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index d438daef839..bde7d21e680 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -219,7 +219,8 @@ namespace eosio { namespace chain { int64_t set_proposed_producers( vector producers ); bool skip_auth_check()const; - bool skip_db_sessions()const; + bool skip_db_sessions( )const; + bool skip_db_sessions( block_status bs )const; bool skip_trx_checks()const; bool contracts_console()const; From 2e289c56cfbddf75bf81cd09d71a428479a5745d Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 10 Aug 2018 11:01:36 -0400 Subject: [PATCH 198/294] adopt FC changes for host header --- libraries/fc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fc b/libraries/fc index 0882ba5ee9f..a6b2756b100 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 0882ba5ee9f3bd677a95aa93624f18fb94ea364d +Subproject commit a6b2756b100098296f7548a191e6210e770b7b3a From 1fd2a4cc2acadec0d169661b2bb422dfa40256c6 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 10 Aug 2018 12:34:21 -0500 Subject: [PATCH 199/294] Fix history plugin not enabled message --- .../account_history_api_plugin/CMakeLists.txt | 7 - .../account_history_api_plugin.cpp | 53 -- .../account_history_api_plugin.hpp | 33 -- plugins/account_history_plugin/CMakeLists.txt | 7 - .../account_history_plugin.cpp | 534 ------------------ .../account_transaction_history_object.hpp | 59 -- .../transaction_history_object.hpp | 42 -- programs/cleos/httpc.cpp | 2 +- 8 files changed, 1 insertion(+), 736 deletions(-) delete mode 100644 plugins/account_history_api_plugin/CMakeLists.txt delete mode 100644 plugins/account_history_api_plugin/account_history_api_plugin.cpp delete mode 100644 plugins/account_history_api_plugin/include/eosio/account_history_api_plugin/account_history_api_plugin.hpp delete mode 100644 plugins/account_history_plugin/CMakeLists.txt delete mode 100644 plugins/account_history_plugin/account_history_plugin.cpp delete mode 100644 plugins/account_history_plugin/include/eosio/account_history_plugin/account_transaction_history_object.hpp delete mode 100644 plugins/account_history_plugin/include/eosio/account_history_plugin/transaction_history_object.hpp diff --git a/plugins/account_history_api_plugin/CMakeLists.txt b/plugins/account_history_api_plugin/CMakeLists.txt deleted file mode 100644 index 544b7d56609..00000000000 --- a/plugins/account_history_api_plugin/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -file( GLOB HEADERS "include/eosio/account_history_api_plugin/*.hpp" ) -add_library( account_history_api_plugin - account_history_api_plugin.cpp - ${HEADERS} ) - -target_link_libraries( account_history_api_plugin account_history_plugin chain_plugin http_plugin appbase ) -target_include_directories( account_history_api_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/plugins/account_history_api_plugin/account_history_api_plugin.cpp b/plugins/account_history_api_plugin/account_history_api_plugin.cpp deleted file mode 100644 index d73298e7bb2..00000000000 --- a/plugins/account_history_api_plugin/account_history_api_plugin.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#include -#include -#include - -#include - -namespace eosio { - -using namespace eosio; - -static appbase::abstract_plugin& _account_history_api_plugin = app().register_plugin(); - -account_history_api_plugin::account_history_api_plugin(){} -account_history_api_plugin::~account_history_api_plugin(){} - -void account_history_api_plugin::set_program_options(options_description&, options_description&) {} -void account_history_api_plugin::plugin_initialize(const variables_map&) {} - -#define CALL(api_name, api_handle, api_namespace, call_name) \ -{std::string("/v1/" #api_name "/" #call_name), \ - [this, api_handle](string, string body, url_response_callback cb) mutable { \ - try { \ - if (body.empty()) body = "{}"; \ - auto result = api_handle.call_name(fc::json::from_string(body).as()); \ - cb(200, fc::json::to_string(result)); \ - } catch (...) { \ - http_plugin::handle_exception(#api_name, #call_name, body, cb); \ - } \ - }} - -#define CHAIN_RO_CALL(call_name) CALL(account_history, ro_api, account_history_apis::read_only, call_name) -#define CHAIN_RW_CALL(call_name) CALL(account_history, rw_api, account_history_apis::read_write, call_name) - -void account_history_api_plugin::plugin_startup() { - ilog( "starting account_history_api_plugin" ); - auto ro_api = app().get_plugin().get_read_only_api(); - auto rw_api = app().get_plugin().get_read_write_api(); - - app().get_plugin().add_api({ - CHAIN_RO_CALL(get_transaction), - CHAIN_RO_CALL(get_transactions), - CHAIN_RO_CALL(get_key_accounts), - CHAIN_RO_CALL(get_controlled_accounts) - }); -} - -void account_history_api_plugin::plugin_shutdown() {} - -} diff --git a/plugins/account_history_api_plugin/include/eosio/account_history_api_plugin/account_history_api_plugin.hpp b/plugins/account_history_api_plugin/include/eosio/account_history_api_plugin/account_history_api_plugin.hpp deleted file mode 100644 index 03aea1d245b..00000000000 --- a/plugins/account_history_api_plugin/include/eosio/account_history_api_plugin/account_history_api_plugin.hpp +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ - -#pragma once -#include -#include -#include - -#include - -namespace eosio { - - using namespace appbase; - - class account_history_api_plugin : public plugin { - public: - APPBASE_PLUGIN_REQUIRES((account_history_plugin)(chain_plugin)(http_plugin)) - - account_history_api_plugin(); - virtual ~account_history_api_plugin(); - - virtual void set_program_options(options_description&, options_description&) override; - - void plugin_initialize(const variables_map&); - void plugin_startup(); - void plugin_shutdown(); - - private: - }; - -} diff --git a/plugins/account_history_plugin/CMakeLists.txt b/plugins/account_history_plugin/CMakeLists.txt deleted file mode 100644 index 672ce018aa8..00000000000 --- a/plugins/account_history_plugin/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -file(GLOB HEADERS "include/eosio/account_history_plugin/*.hpp") -add_library( account_history_plugin - account_history_plugin.cpp - ${HEADERS} ) - -target_link_libraries( account_history_plugin chain_plugin eosio_chain appbase ) -target_include_directories( account_history_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/plugins/account_history_plugin/account_history_plugin.cpp b/plugins/account_history_plugin/account_history_plugin.cpp deleted file mode 100644 index b135fd430ed..00000000000 --- a/plugins/account_history_plugin/account_history_plugin.cpp +++ /dev/null @@ -1,534 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace fc { class variant; } - -namespace eosio { - -using chain::account_name; -using chain::block_id_type; -using chain::key_weight; -using chain::permission_level_weight; -using chain::permission_name; -using chain::packed_transaction; -using chain::signed_block; -using boost::multi_index_container; -using chain::transaction_id_type; -using namespace boost::multi_index; -using ordered_transaction_results = account_history_apis::read_only::ordered_transaction_results; -using get_transactions_results = account_history_apis::read_only::get_transactions_results; - -class account_history_plugin_impl { -public: - packed_transaction get_transaction(const chain::transaction_id_type& transaction_id) const; - get_transactions_results get_transactions(const account_name& account_name, const optional& skip_seq, const optional& num_seq) const; - vector get_key_accounts(const public_key_type& public_key) const; - vector get_controlled_accounts(const account_name& controlling_account) const; - void applied_block(const chain::block_trace&); - fc::variant transaction_to_variant(const packed_transaction& pretty_input) const; - - chain_plugin* chain_plug = nullptr; - static const int64_t DEFAULT_TRANSACTION_TIME_LIMIT; - int64_t transactions_time_limit = DEFAULT_TRANSACTION_TIME_LIMIT; - std::set filter_on; - - bool init_db = false; - void check_init_db() { - if( !init_db ) { - init_db = true; - auto& db = chain_plug->chain().db(); - db.add_index(); - db.add_index(); - db.add_index(); - db.add_index(); - } - } -private: - struct block_comp - { - bool operator()(const block_id_type& a, const block_id_type& b) const - { - return chain::block_header::num_from_id(a) > chain::block_header::num_from_id(b); - } - }; - typedef std::multimap block_transaction_id_map; - - optional find_block_id(const chainbase::database& db, const transaction_id_type& transaction_id) const; - packed_transaction find_transaction(const chain::transaction_id_type& transaction_id, const signed_block& block) const; - bool is_scope_relevant(const vector& scope); - get_transactions_results ordered_transactions(const block_transaction_id_map& block_transaction_ids, const fc::time_point& start_time, const uint32_t begin, const uint32_t end) const; - static void add(chainbase::database& db, const vector& keys, const account_name& account_name, const permission_name& permission); - template - static void remove(chainbase::database& db, const account_name& account_name, const permission_name& permission) - { - const auto& idx = db.get_index(); - auto& mutable_idx = db.get_mutable_index(); - while(!idx.empty()) { - auto key = boost::make_tuple(account_name, permission); - const auto& itr = idx.lower_bound(key); - if (itr == idx.end()) { - break; - } - - const auto& range_end = idx.upper_bound(key); - if (itr == range_end) { - break; - } - - mutable_idx.remove(*itr); - } - } - - static void add(chainbase::database& db, const vector& controlling_accounts, const account_name& account_name, const permission_name& permission); - bool time_exceeded(const fc::time_point& start_time) const; - - static const account_name NEW_ACCOUNT; - static const account_name UPDATE_AUTH; - static const account_name DELETE_AUTH; - static const permission_name OWNER; - static const permission_name ACTIVE; - static const permission_name RECOVERY; -}; -const int64_t account_history_plugin_impl::DEFAULT_TRANSACTION_TIME_LIMIT = 3; -const account_name account_history_plugin_impl::NEW_ACCOUNT = "newaccount"; -const account_name account_history_plugin_impl::UPDATE_AUTH = "updateauth"; -const account_name account_history_plugin_impl::DELETE_AUTH = "deleteauth"; -const permission_name account_history_plugin_impl::OWNER = "owner"; -const permission_name account_history_plugin_impl::ACTIVE = "active"; -const permission_name account_history_plugin_impl::RECOVERY = "recovery"; - -optional account_history_plugin_impl::find_block_id(const chainbase::database& db, const transaction_id_type& transaction_id) const -{ - optional block_id; - const auto& trx_idx = db.get_index(); - auto transaction_history = trx_idx.find( transaction_id ); - if (transaction_history != trx_idx.end()) - block_id = transaction_history->block_id; - - return block_id; -} - -packed_transaction account_history_plugin_impl::find_transaction(const chain::transaction_id_type& transaction_id, const chain::signed_block& block) const -{ - /* TODO: fix this - for (const packed_transaction& trx : block.input_transactions) - if (trx.get_transaction().id() == transaction_id) - return trx; - */ - - // ERROR in indexing logic - FC_THROW("Transaction with ID ${tid} was indexed as being in block ID ${bid}, but was not found in that block", ("tid", transaction_id)("bid", block.id())); -} - -packed_transaction account_history_plugin_impl::get_transaction(const chain::transaction_id_type& transaction_id) const -{ - const auto& db = chain_plug->chain().db(); - optional block_id; - db.with_read_lock( [&]() { - block_id = find_block_id(db, transaction_id); - } ); - if( block_id.valid() ) - { - auto block = chain_plug->chain().fetch_block_by_id(*block_id); - FC_ASSERT(block, "Transaction with ID ${tid} was indexed as being in block ID ${bid}, but no such block was found", ("tid", transaction_id)("bid", block_id)); - return find_transaction(transaction_id, *block); - } - - FC_THROW_EXCEPTION(chain::unknown_transaction_exception, - "Could not find transaction for: ${id}", ("id", transaction_id.str())); -} - -get_transactions_results account_history_plugin_impl::get_transactions(const account_name& account_name, const optional& skip_seq, const optional& num_seq) const -{ - fc::time_point start_time = fc::time_point::now(); - const auto& db = chain_plug->chain().db(); - - block_transaction_id_map block_transaction_ids; - db.with_read_lock( [&]() { - const auto& account_idx = db.get_index(); - auto range = account_idx.equal_range( account_name ); - for (auto obj = range.first; obj != range.second; ++obj) - { - optional block_id = find_block_id(db, obj->transaction_id); - FC_ASSERT(block_id, "Transaction with ID ${tid} was tracked for ${account}, but no corresponding block id was found", ("tid", obj->transaction_id)("account", account_name)); - block_transaction_ids.emplace(std::make_pair(*block_id, obj->transaction_id)); - } - } ); - - uint32_t begin, end; - const auto size = block_transaction_ids.size(); - if (!skip_seq) - { - begin = 0; - end = size; - } - else - { - begin = *skip_seq; - - if (!num_seq) - end = size; - else - { - end = begin + *num_seq; - if (end > size) - end = size; - } - - if (begin > size - 1 || begin >= end) - return get_transactions_results(); - } - - return ordered_transactions(block_transaction_ids, start_time, begin, end); -} - -get_transactions_results account_history_plugin_impl::ordered_transactions(const block_transaction_id_map& block_transaction_ids, const fc::time_point& start_time, const uint32_t begin, const uint32_t end) const -{ - get_transactions_results results; - results.transactions.reserve(end - begin); - - auto block_transaction = block_transaction_ids.cbegin(); - - uint32_t current = 0; - // keep iterating through each equal range - while (block_transaction != block_transaction_ids.cend() && !time_exceeded(start_time)) - { - optional block = chain_plug->chain().fetch_block_by_id(block_transaction->first); - FC_ASSERT(block, "Transaction with ID ${tid} was indexed as being in block ID ${bid}, but no such block was found", ("tid", block_transaction->second)("bid", block_transaction->first)); - - auto range = block_transaction_ids.equal_range(block_transaction->first); - - std::set trans_ids_for_block; - for (block_transaction = range.first; block_transaction != range.second; ++block_transaction) - { - trans_ids_for_block.insert(block_transaction->second); - } - - const uint32_t trx_after_block = current + trans_ids_for_block.size(); - if (trx_after_block <= begin) - { - current = trx_after_block; - continue; - } - for (auto trx = block->input_transactions.crbegin(); trx != block->input_transactions.crend() && current < trx_after_block; ++trx) - { - transaction_id_type trx_id = trx->get_transaction().id(); - if(trans_ids_for_block.count(trx_id)) - { - if(++current > begin) - { - const auto pretty_trx = transaction_to_variant(*trx); - results.transactions.emplace_back(ordered_transaction_results{(current - 1), trx_id, pretty_trx}); - - if(current >= end) - { - return results; - } - } - - // just check after finding transaction or transitioning to next block to avoid spending all our time checking - if (time_exceeded(start_time)) - { - results.time_limit_exceeded_error = true; - return results; - } - } - } - - // just check after finding transaction or transitioning to next block to avoid spending all our time checking - if (time_exceeded(start_time)) - { - results.time_limit_exceeded_error = true; - return results; - } - } - - return results; -} - -bool account_history_plugin_impl::time_exceeded(const fc::time_point& start_time) const -{ - return (fc::time_point::now() - start_time).count() > transactions_time_limit; -} - -vector account_history_plugin_impl::get_key_accounts(const public_key_type& public_key) const -{ - std::set accounts; - const auto& db = chain_plug->chain().db(); - db.with_read_lock( [&]() { - const auto& pub_key_idx = db.get_index(); - auto range = pub_key_idx.equal_range( public_key ); - for (auto obj = range.first; obj != range.second; ++obj) - { - accounts.insert(obj->name); - } - } ); - return vector(accounts.begin(), accounts.end()); -} - -vector account_history_plugin_impl::get_controlled_accounts(const account_name& controlling_account) const -{ - std::set accounts; - const auto& db = chain_plug->chain().db(); - db.with_read_lock( [&]() { - const auto& account_control_idx = db.get_index(); - auto range = account_control_idx.equal_range( controlling_account ); - for (auto obj = range.first; obj != range.second; ++obj) - { - accounts.insert(obj->controlled_account); - } - } ); - return vector(accounts.begin(), accounts.end()); -} - -static vector generated_affected_accounts(const chain::transaction_trace& trx_trace) { - vector result; - for (const auto& at: trx_trace.action_traces) { - for (const auto& auth: at.act.authorization) { - result.emplace_back(auth.actor); - } - - result.emplace_back(at.receipt.receiver); - } - - fc::deduplicate(result); - return result; -} - -void account_history_plugin_impl::applied_block(const chain::block_trace& trace) -{ - const auto& block = trace.block; - const auto block_id = block.id(); - auto& db = chain_plug->chain().db(); - const bool check_relevance = filter_on.size(); - auto process_one = [&](const chain::transaction_trace& trx_trace ) - { - auto affected_accounts = generated_affected_accounts(trx_trace); - if (check_relevance && !is_scope_relevant(affected_accounts)) - return; - - auto trx_obj_ptr = db.find(trx_trace.id); - if (trx_obj_ptr != nullptr) - return; // on restart may already have block - - db.create([&block_id,&trx_trace](transaction_history_object& transaction_history) { - transaction_history.block_id = block_id; - transaction_history.transaction_id = trx_trace.id; - transaction_history.transaction_status = trx_trace.status; - }); - - auto create_ath_object = [&trx_trace,&db](const account_name& name) { - db.create([&trx_trace,&name](account_transaction_history_object& account_transaction_history) { - account_transaction_history.name = name; - account_transaction_history.transaction_id = trx_trace.id; - }); - }; - - for (const auto& account_name : affected_accounts) - create_ath_object(account_name); - - for (const auto& act_trace : trx_trace.action_traces) - { - if (act_trace.receipt.receiver == chain::config::system_account_name) - { - if (act_trace.act.name == NEW_ACCOUNT) - { - const auto create = act_trace.act.data_as(); - add(db, create.owner.keys, create.name, OWNER); - add(db, create.active.keys, create.name, ACTIVE); - add(db, create.recovery.keys, create.name, RECOVERY); - - add(db, create.owner.accounts, create.name, OWNER); - add(db, create.active.accounts, create.name, ACTIVE); - add(db, create.recovery.accounts, create.name, RECOVERY); - } - else if (act_trace.act.name == UPDATE_AUTH) - { - const auto update = act_trace.act.data_as(); - remove(db, update.account, update.permission); - add(db, update.data.keys, update.account, update.permission); - - remove(db, update.account, update.permission); - add(db, update.data.accounts, update.account, update.permission); - } - else if (act_trace.act.name == DELETE_AUTH) - { - const auto del = act_trace.act.data_as(); - remove(db, del.account, del.permission); - - remove(db, del.account, del.permission); - } - } - } - }; - - // go through all the transaction traces - for (const auto& rt: trace.region_traces) - for(const auto& ct: rt.cycle_traces) - for(const auto& st: ct.shard_traces) - for(const auto& trx_trace: st.transaction_traces) - process_one(trx_trace); -} - -void account_history_plugin_impl::add(chainbase::database& db, const vector& keys, const account_name& name, const permission_name& permission) -{ - for (auto pub_key_weight : keys ) - { - db.create([&](public_key_history_object& obj) { - obj.public_key = pub_key_weight.key; - obj.name = name; - obj.permission = permission; - }); - } -} - -void account_history_plugin_impl::add(chainbase::database& db, const vector& controlling_accounts, const account_name& account_name, const permission_name& permission) -{ - for (auto controlling_account : controlling_accounts ) - { - db.create([&](account_control_history_object& obj) { - obj.controlled_account = account_name; - obj.controlled_permission = permission; - obj.controlling_account = controlling_account.permission.actor; - }); - } -} - -bool account_history_plugin_impl::is_scope_relevant(const vector& scope) -{ - for (const account_name& account_name : scope) - if (filter_on.count(account_name)) - return true; - - return false; -} - -fc::variant account_history_plugin_impl::transaction_to_variant(const packed_transaction& ptrx) const -{ - const chainbase::database& database = chain_plug->chain().db(); - auto resolver = [&database]( const account_name& name ) -> optional { - const auto* accnt = database.find( name ); - if (accnt != nullptr) { - abi_def abi; - if (abi_serializer::to_abi(accnt->abi, abi)) { - return abi_serializer(abi); - } - } - - return optional(); - }; - - fc::variant pretty_output; - abi_serializer::to_variant(ptrx, pretty_output, resolver, chain_plug->get_abi_serializer_max_time()); - return pretty_output; -} - - -account_history_plugin::account_history_plugin() -:my(new account_history_plugin_impl()) -{ -} - -account_history_plugin::~account_history_plugin() -{ -} - -void account_history_plugin::set_program_options(options_description& cli, options_description& cfg) -{ - cfg.add_options() - ("filter_on_accounts,f", bpo::value>()->composing(), - "Track only transactions whose scopes involve the listed accounts. Default is to track all transactions.") - ("get-transactions-time-limit", bpo::value()->default_value(account_history_plugin_impl::DEFAULT_TRANSACTION_TIME_LIMIT), - "Limits the maximum time (in milliseconds) processing a single get_transactions call.") - ; -} - -void account_history_plugin::plugin_initialize(const variables_map& options) -{ - my->transactions_time_limit = options.at("get-transactions-time-limit").as() * 1000; - - if(options.count("filter_on_accounts")) - { - auto foa = options.at("filter_on_accounts").as>(); - for(auto filter_account : foa) - my->filter_on.emplace(filter_account); - } - - my->chain_plug = app().find_plugin(); - EOS_ASSERT( my->chain_plug, chain::missing_chain_plugin_exception, "" ); - my->chain_plug->chain_config().applied_block_callbacks.emplace_back( - [&impl = my](const chain::block_trace& trace) { - impl->check_init_db(); - impl->applied_block(trace); - }); -} - - -void account_history_plugin::plugin_startup() -{ - /* - auto& db = my->chain_plug->chain().db(); - db.add_index(); - db.add_index(); - db.add_index(); - db.add_index(); - - my->chain_plug->chain().applied_block.connect ([&impl = my](const chain::block_trace& trace) { - impl->applied_block(trace); - }); - */ -} - -void account_history_plugin::plugin_shutdown() -{ -} - -namespace account_history_apis { - -read_only::get_transaction_results read_only::get_transaction(const read_only::get_transaction_params& params) const -{ - auto trx = account_history->get_transaction(params.transaction_id); - return { params.transaction_id, account_history->transaction_to_variant(trx) }; -} - -read_only::get_transactions_results read_only::get_transactions(const read_only::get_transactions_params& params) const -{ - return account_history->get_transactions(params.account_name, params.skip_seq, params.num_seq); -} - -read_only::get_key_accounts_results read_only::get_key_accounts(const get_key_accounts_params& params) const -{ - return { account_history->get_key_accounts(params.public_key) }; -} - -read_only::get_controlled_accounts_results read_only::get_controlled_accounts(const get_controlled_accounts_params& params) const -{ - return { account_history->get_controlled_accounts(params.controlling_account) }; -} - -} // namespace account_history_apis -} // namespace eosio diff --git a/plugins/account_history_plugin/include/eosio/account_history_plugin/account_transaction_history_object.hpp b/plugins/account_history_plugin/include/eosio/account_history_plugin/account_transaction_history_object.hpp deleted file mode 100644 index f7e336a68e0..00000000000 --- a/plugins/account_history_plugin/include/eosio/account_history_plugin/account_transaction_history_object.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include -#include - -namespace std { - - template<> - struct hash - { - size_t operator()( const eosio::chain::account_name& name )const - { - return (uint64_t)name; - } - }; -} - -namespace eosio { -using chain::account_name; -using chain::shared_vector; -using chain::transaction_id_type; -using namespace boost::multi_index; - -class account_transaction_history_object : public chainbase::object { - OBJECT_CTOR(account_transaction_history_object) - - id_type id; - account_name name; - transaction_id_type transaction_id; -}; - -struct by_id; -struct by_account_name; -struct by_account_name_trx_id; -using account_transaction_history_multi_index = chainbase::shared_multi_index_container< - account_transaction_history_object, - indexed_by< - ordered_unique, BOOST_MULTI_INDEX_MEMBER(account_transaction_history_object, account_transaction_history_object::id_type, id)>, - ordered_unique, - composite_key< account_transaction_history_object, - member, - member - > - > - > ->; - -typedef chainbase::generic_index account_transaction_history_index; - -} - -CHAINBASE_SET_INDEX_TYPE( eosio::account_transaction_history_object, eosio::account_transaction_history_multi_index ) - -FC_REFLECT( eosio::account_transaction_history_object, (name)(transaction_id) ) - diff --git a/plugins/account_history_plugin/include/eosio/account_history_plugin/transaction_history_object.hpp b/plugins/account_history_plugin/include/eosio/account_history_plugin/transaction_history_object.hpp deleted file mode 100644 index 8b9e51c8336..00000000000 --- a/plugins/account_history_plugin/include/eosio/account_history_plugin/transaction_history_object.hpp +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once - -#include -#include - -namespace eosio { -using chain::block_id_type; -using chain::transaction_id_type; -using chain::transaction_receipt; -using namespace boost::multi_index; - -class transaction_history_object : public chainbase::object { - OBJECT_CTOR(transaction_history_object) - - id_type id; - block_id_type block_id; - transaction_id_type transaction_id; - transaction_receipt::status_enum transaction_status; -}; - -struct by_id; -struct by_trx_id; -using transaction_history_multi_index = chainbase::shared_multi_index_container< - transaction_history_object, - indexed_by< - ordered_unique, BOOST_MULTI_INDEX_MEMBER(transaction_history_object, transaction_history_object::id_type, id)>, - ordered_unique, BOOST_MULTI_INDEX_MEMBER(transaction_history_object, transaction_id_type, transaction_id)> - > ->; - -typedef chainbase::generic_index transaction_history_index; - -} - -CHAINBASE_SET_INDEX_TYPE( eosio::transaction_history_object, eosio::transaction_history_multi_index ) - -FC_REFLECT( eosio::transaction_history_object, (block_id)(transaction_id)(transaction_status) ) - diff --git a/programs/cleos/httpc.cpp b/programs/cleos/httpc.cpp index 4f87cf6b75f..343e396ef95 100644 --- a/programs/cleos/httpc.cpp +++ b/programs/cleos/httpc.cpp @@ -255,7 +255,7 @@ namespace eosio { namespace client { namespace http { throw chain::missing_chain_api_plugin_exception(FC_LOG_MESSAGE(error, "Chain API plugin is not enabled")); } else if (url.path.compare(0, wallet_func_base.size(), wallet_func_base) == 0) { throw chain::missing_wallet_api_plugin_exception(FC_LOG_MESSAGE(error, "Wallet is not available")); - } else if (url.path.compare(0, account_history_func_base.size(), account_history_func_base) == 0) { + } else if (url.path.compare(0, history_func_base.size(), history_func_base) == 0) { throw chain::missing_history_api_plugin_exception(FC_LOG_MESSAGE(error, "History API plugin is not enabled")); } else if (url.path.compare(0, net_func_base.size(), net_func_base) == 0) { throw chain::missing_net_api_plugin_exception(FC_LOG_MESSAGE(error, "Net API plugin is not enabled")); From fd65623593e503e33d080036349e5147520b370d Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 10 Aug 2018 12:52:51 -0500 Subject: [PATCH 200/294] Fixed missed change from #4721. GH #5156 --- tests/nodeos_under_min_avail_ram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/nodeos_under_min_avail_ram.py b/tests/nodeos_under_min_avail_ram.py index 633a7d67a13..3b3b4a167f5 100755 --- a/tests/nodeos_under_min_avail_ram.py +++ b/tests/nodeos_under_min_avail_ram.py @@ -138,8 +138,8 @@ def setName(self, num): trans=nodes[0].delegatebw(contractAccount, 1000000.0000, 88000000.0000, exitOnError=True) contractDir="contracts/integration_test" - wastFile="contracts/integration_test/integration_test.wast" - abiFile="contracts/integration_test/integration_test.abi" + wastFile="integration_test.wast" + abiFile="integration_test.abi" Print("Publish contract") trans=nodes[0].publishContract(contractAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) if trans is None: From e9d30e43198b0f9de95b457eeca82c4c324aee1d Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Fri, 10 Aug 2018 14:20:55 -0400 Subject: [PATCH 201/294] Remove WAST support from cleos set code/contract The textual format of WebAssembly continues to be somewhat fluid. For example, the renaming of grow_memory with memory.grow. The binary format has been stable for some time. Only consume the binary format in cleos now -- the eosio tools (like eosiocpp) have produced binary .wasm files since the 1.0 release by default as do most other toolchains. This protects us from further deviations of the textual format --- programs/cleos/main.cpp | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index c4b0f654ae6..81365b2d92a 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -114,7 +114,6 @@ Usage: ./cleos create account [OPTIONS] creator name OwnerKey ActiveKey #include #include #include -#include #include #include @@ -2133,23 +2132,23 @@ int main( int argc, char** argv ) { // set contract subcommand string account; string contractPath; - string wastPath; + string wasmPath; string abiPath; bool shouldSend = true; auto codeSubcommand = setSubcommand->add_subcommand("code", localized("Create or update the code on an account")); codeSubcommand->add_option("account", account, localized("The account to set code for"))->required(); - codeSubcommand->add_option("code-file", wastPath, localized("The fullpath containing the contract WAST or WASM"))->required(); + codeSubcommand->add_option("code-file", wasmPath, localized("The fullpath containing the contract WASM"))->required(); auto abiSubcommand = setSubcommand->add_subcommand("abi", localized("Create or update the abi on an account")); abiSubcommand->add_option("account", account, localized("The account to set the ABI for"))->required(); - abiSubcommand->add_option("abi-file", abiPath, localized("The fullpath containing the contract WAST or WASM"))->required(); + abiSubcommand->add_option("abi-file", abiPath, localized("The fullpath containing the contract ABI"))->required(); auto contractSubcommand = setSubcommand->add_subcommand("contract", localized("Create or update the contract on an account")); contractSubcommand->add_option("account", account, localized("The account to publish a contract for")) ->required(); - contractSubcommand->add_option("contract-dir", contractPath, localized("The path containing the .wast and .abi")) + contractSubcommand->add_option("contract-dir", contractPath, localized("The path containing the .wasm and .abi")) ->required(); - contractSubcommand->add_option("wast-file", wastPath, localized("The file containing the contract WAST or WASM relative to contract-dir")); + contractSubcommand->add_option("wasm-file", wasmPath, localized("The file containing the contract WASM relative to contract-dir")); // ->check(CLI::ExistingFile); auto abi = contractSubcommand->add_option("abi-file,-a,--abi", abiPath, localized("The ABI for the contract relative to contract-dir")); // ->check(CLI::ExistingFile); @@ -2161,26 +2160,13 @@ int main( int argc, char** argv ) { if( cpath.filename().generic_string() == "." ) cpath = cpath.parent_path(); - if( wastPath.empty() ) - { - wastPath = (cpath / (cpath.filename().generic_string()+".wasm")).generic_string(); - if (!fc::exists(wastPath)) - wastPath = (cpath / (cpath.filename().generic_string()+".wast")).generic_string(); - } + if( wasmPath.empty() ) + wasmPath = (cpath / (cpath.filename().generic_string()+".wasm")).generic_string(); - std::cerr << localized(("Reading WAST/WASM from " + wastPath + "...").c_str()) << std::endl; - fc::read_file_contents(wastPath, wast); - EOS_ASSERT( !wast.empty(), wast_file_not_found, "no wast file found ${f}", ("f", wastPath) ); - vector wasm; - const string binary_wasm_header("\x00\x61\x73\x6d", 4); - if(wast.compare(0, 4, binary_wasm_header) == 0) { - std::cerr << localized("Using already assembled WASM...") << std::endl; - wasm = vector(wast.begin(), wast.end()); - } - else { - std::cerr << localized("Assembling WASM...") << std::endl; - wasm = wast_to_wasm(wast); - } + std::cerr << localized(("Reading WASM from " + wasmPath + "...").c_str()) << std::endl; + fc::read_file_contents(wasmPath, wast); + EOS_ASSERT( !wast.empty(), wast_file_not_found, "no wasm file found ${f}", ("f", wasmPath) ); + vector wasm = vector(wast.begin(), wast.end()); actions.emplace_back( create_setcode(account, bytes(wasm.begin(), wasm.end()) ) ); if ( shouldSend ) { From 1d0a1de1df7c344d0f8eea2b656d1fa75916d608 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 10 Aug 2018 13:23:34 -0500 Subject: [PATCH 202/294] Remove unused block_trace --- libraries/chain/include/eosio/chain/trace.hpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/libraries/chain/include/eosio/chain/trace.hpp b/libraries/chain/include/eosio/chain/trace.hpp index 47062e66691..41fb8f079a6 100644 --- a/libraries/chain/include/eosio/chain/trace.hpp +++ b/libraries/chain/include/eosio/chain/trace.hpp @@ -46,13 +46,6 @@ namespace eosio { namespace chain { std::exception_ptr except_ptr; }; - struct block_trace { - fc::microseconds elapsed; - uint64_t billed_cpu_usage_us; - vector trx_traces; - }; - using block_trace_ptr = std::shared_ptr; - } } /// namespace eosio::chain FC_REFLECT( eosio::chain::base_action_trace, @@ -63,4 +56,3 @@ FC_REFLECT_DERIVED( eosio::chain::action_trace, FC_REFLECT( eosio::chain::transaction_trace, (id)(receipt)(elapsed)(net_usage)(scheduled) (action_traces)(failed_dtrx_trace)(except) ) -FC_REFLECT( eosio::chain::block_trace, (elapsed)(billed_cpu_usage_us)(trx_traces) ) From b97aed4d6ec1dbafc04ca01a838598557eacf97a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 10 Aug 2018 13:23:58 -0500 Subject: [PATCH 203/294] Remove unused types --- libraries/chain/include/eosio/chain/types.hpp | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index d031488e64b..51fce8d4c40 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -118,7 +118,6 @@ namespace eosio { namespace chain { permission_object_type, permission_usage_object_type, permission_link_object_type, - action_code_object_type, key_value_object_type, index64_object_type, index128_object_type, @@ -131,24 +130,15 @@ namespace eosio { namespace chain { transaction_object_type, generated_transaction_object_type, producer_object_type, - chain_property_object_type, - account_control_history_object_type, ///< Defined by account_history_plugin - account_transaction_history_object_type, ///< Defined by account_history_plugin - transaction_history_object_type, ///< Defined by account_history_plugin - public_key_history_object_type, ///< Defined by account_history_plugin - balance_object_type, ///< Defined by native_contract library - staked_balance_object_type, ///< Defined by native_contract library - producer_votes_object_type, ///< Defined by native_contract library - producer_schedule_object_type, ///< Defined by native_contract library - proxy_vote_object_type, ///< Defined by native_contract library - scope_sequence_object_type, + account_control_history_object_type, ///< Defined by history_plugin + public_key_history_object_type, ///< Defined by history_plugin table_id_object_type, resource_limits_object_type, resource_usage_object_type, resource_limits_state_object_type, resource_limits_config_object_type, - account_history_object_type, - action_history_object_type, + account_history_object_type, ///< Defined by history_plugin + action_history_object_type, ///< Defined by history_plugin reversible_block_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -187,7 +177,6 @@ FC_REFLECT_ENUM(eosio::chain::object_type, (permission_object_type) (permission_usage_object_type) (permission_link_object_type) - (action_code_object_type) (key_value_object_type) (index64_object_type) (index128_object_type) @@ -200,17 +189,8 @@ FC_REFLECT_ENUM(eosio::chain::object_type, (transaction_object_type) (generated_transaction_object_type) (producer_object_type) - (chain_property_object_type) (account_control_history_object_type) - (account_transaction_history_object_type) - (transaction_history_object_type) (public_key_history_object_type) - (balance_object_type) - (staked_balance_object_type) - (producer_votes_object_type) - (producer_schedule_object_type) - (proxy_vote_object_type) - (scope_sequence_object_type) (table_id_object_type) (resource_limits_object_type) (resource_usage_object_type) From a1fe368ba2782746b52e98033908497e31494156 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 10 Aug 2018 13:24:56 -0500 Subject: [PATCH 204/294] Remove account_history_plugin --- plugins/CMakeLists.txt | 1 - tests/consensus-validation-malicious-producers.py | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index c9b65904499..06b0162fe5b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -10,7 +10,6 @@ add_subdirectory(producer_api_plugin) add_subdirectory(history_plugin) add_subdirectory(history_api_plugin) -#add_subdirectory(account_history_api_plugin) add_subdirectory(wallet_plugin) add_subdirectory(wallet_api_plugin) add_subdirectory(txn_test_gen_plugin) diff --git a/tests/consensus-validation-malicious-producers.py b/tests/consensus-validation-malicious-producers.py index 95357196e92..fc6353c2fc6 100755 --- a/tests/consensus-validation-malicious-producers.py +++ b/tests/consensus-validation-malicious-producers.py @@ -103,8 +103,8 @@ producer-name = initu plugin = eosio::producer_plugin plugin = eosio::chain_api_plugin -plugin = eosio::account_history_plugin -plugin = eosio::account_history_api_plugin""" +plugin = eosio::history_plugin +plugin = eosio::history_api_plugin""" config01="""genesis-json = ./genesis.json @@ -123,8 +123,8 @@ producer-name = defproducerb plugin = eosio::producer_plugin plugin = eosio::chain_api_plugin -plugin = eosio::account_history_plugin -plugin = eosio::account_history_api_plugin""" +plugin = eosio::history_plugin +plugin = eosio::history_api_plugin""" producers="""producer-name = defproducerd From 8689b87403e0020c03fd12bbc9a4d91cc09361f7 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 10 Aug 2018 13:25:43 -0500 Subject: [PATCH 205/294] Remove used url --- programs/cleos/httpc.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/programs/cleos/httpc.hpp b/programs/cleos/httpc.hpp index 54ccc977b36..0fcd9b8490d 100644 --- a/programs/cleos/httpc.hpp +++ b/programs/cleos/httpc.hpp @@ -102,9 +102,6 @@ namespace eosio { namespace client { namespace http { const string get_key_accounts_func = history_func_base + "/get_key_accounts"; const string get_controlled_accounts_func = history_func_base + "/get_controlled_accounts"; - const string account_history_func_base = "/v1/account_history"; - const string get_transactions_func = account_history_func_base + "/get_transactions"; - const string net_func_base = "/v1/net"; const string net_connect = net_func_base + "/connect"; const string net_disconnect = net_func_base + "/disconnect"; From 5761c045c62ae50b963755aba8d11ae66b7c5b1d Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 10 Aug 2018 13:26:09 -0500 Subject: [PATCH 206/294] Remove dead code --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 4aaea9b3298..1a9867f61af 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -43,7 +43,6 @@ using chain::permission_name; using chain::transaction; using chain::signed_transaction; using chain::signed_block; -using chain::block_trace; using chain::transaction_id_type; using chain::packed_transaction; From 4f6ff2a96e51b1bfb9ca473f6fd1f676fe5a3b36 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Fri, 10 Aug 2018 14:56:52 -0400 Subject: [PATCH 207/294] Fix merge goof in cleos main.cpp --- programs/cleos/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index c000882eda3..751fdd42311 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2163,7 +2163,7 @@ int main( int argc, char** argv ) { if( wasmPath.empty() ) wasmPath = (cpath / (cpath.filename().generic_string()+".wasm")).generic_string(); else - wastPath = (cpath / wastPath).generic_string(); + wasmPath = (cpath / wasmPath).generic_string(); std::cerr << localized(("Reading WASM from " + wasmPath + "...").c_str()) << std::endl; fc::read_file_contents(wasmPath, wast); From 05ceede8a317eca71d0b976f57f5189024f18ae1 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Fri, 10 Aug 2018 15:22:18 -0400 Subject: [PATCH 208/294] Replease wasts with wasms in launcher template & test scripts --- testnet.template | 2 +- tests/Cluster.py | 12 ++++++------ tests/Node.py | 4 ++-- tests/consensus-validation-malicious-producers.py | 4 ++-- tests/nodeos_run_test.py | 12 ++++++------ tests/nodeos_under_min_avail_ram.py | 4 ++-- tests/p2p_network_test.py | 6 +++--- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/testnet.template b/testnet.template index 12e49a799fc..7a7593cc83a 100644 --- a/testnet.template +++ b/testnet.template @@ -75,7 +75,7 @@ wcmd create -n ignition # ------ DO NOT ALTER THE NEXT LINE ------- ###INSERT prodkeys -ecmd set contract eosio contracts/eosio.bios eosio.bios.wast eosio.bios.abi +ecmd set contract eosio contracts/eosio.bios eosio.bios.wasm eosio.bios.abi # Create required system accounts ecmd create key diff --git a/tests/Cluster.py b/tests/Cluster.py index fde8dde4c0d..3df4ccff04d 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -726,10 +726,10 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio contract="eosio.bios" contractDir="contracts/%s" % (contract) - wastFile="%s.wast" % (contract) + wasmFile="%s.wasm" % (contract) abiFile="%s.abi" % (contract) Utils.Print("Publish %s contract" % (contract)) - trans=biosNode.publishContract(eosioAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) + trans=biosNode.publishContract(eosioAccount.name, contractDir, wasmFile, abiFile, waitForTransBlock=True) if trans is None: Utils.Print("ERROR: Failed to publish contract %s." % (contract)) return None @@ -850,10 +850,10 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio contract="eosio.token" contractDir="contracts/%s" % (contract) - wastFile="%s.wast" % (contract) + wasmFile="%s.wasm" % (contract) abiFile="%s.abi" % (contract) Utils.Print("Publish %s contract" % (contract)) - trans=biosNode.publishContract(eosioTokenAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) + trans=biosNode.publishContract(eosioTokenAccount.name, contractDir, wasmFile, abiFile, waitForTransBlock=True) if trans is None: Utils.Print("ERROR: Failed to publish contract %s." % (contract)) return None @@ -905,10 +905,10 @@ def bootstrap(totalNodes, prodCount, biosHost, biosPort, dontKill=False, onlyBio contract="eosio.system" contractDir="contracts/%s" % (contract) - wastFile="%s.wast" % (contract) + wasmFile="%s.wasm" % (contract) abiFile="%s.abi" % (contract) Utils.Print("Publish %s contract" % (contract)) - trans=biosNode.publishContract(eosioAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) + trans=biosNode.publishContract(eosioAccount.name, contractDir, wasmFile, abiFile, waitForTransBlock=True) if trans is None: Utils.Print("ERROR: Failed to publish contract %s." % (contract)) return None diff --git a/tests/Node.py b/tests/Node.py index 89cbba5df60..3f50bfbce4f 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -757,9 +757,9 @@ def getAccountCodeHash(self, account): return None # publish contract and return transaction as json object - def publishContract(self, account, contractDir, wastFile, abiFile, waitForTransBlock=False, shouldFail=False): + def publishContract(self, account, contractDir, wasmFile, abiFile, waitForTransBlock=False, shouldFail=False): cmd="%s %s -v set contract -j %s %s" % (Utils.EosClientPath, self.endpointArgs, account, contractDir) - cmd += "" if wastFile is None else (" "+ wastFile) + cmd += "" if wasmFile is None else (" "+ wasmFile) cmd += "" if abiFile is None else (" " + abiFile) if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) trans=None diff --git a/tests/consensus-validation-malicious-producers.py b/tests/consensus-validation-malicious-producers.py index 95357196e92..67d7722ed82 100755 --- a/tests/consensus-validation-malicious-producers.py +++ b/tests/consensus-validation-malicious-producers.py @@ -288,10 +288,10 @@ def myTest(transWillEnterBlock): error("Failed to create account %s" % (currencyAccount.name)) return False - wastFile="currency.wast" + wasmFile="currency.wasm" abiFile="currency.abi" Print("Publish contract") - trans=node.publishContract(currencyAccount.name, wastFile, abiFile, waitForTransBlock=True) + trans=node.publishContract(currencyAccount.name, wasmFile, abiFile, waitForTransBlock=True) if trans is None: error("Failed to publish contract.") return False diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index 40d5d8b5072..85be1bd940b 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -319,10 +319,10 @@ errorExit("FAILURE - get code currency1111 failed", raw=True) contractDir="contracts/eosio.token" - wastFile="eosio.token.wast" + wasmFile="eosio.token.wasm" abiFile="eosio.token.abi" Print("Publish contract") - trans=node.publishContract(currencyAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) + trans=node.publishContract(currencyAccount.name, contractDir, wasmFile, abiFile, waitForTransBlock=True) if trans is None: cmdError("%s set contract currency1111" % (ClientName)) errorExit("Failed to publish contract.") @@ -605,20 +605,20 @@ Print("upload exchange contract") contractDir="contracts/exchange" - wastFile="exchange.wast" + wasmFile="exchange.wasm" abiFile="exchange.abi" Print("Publish exchange contract") - trans=node.publishContract(exchangeAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) + trans=node.publishContract(exchangeAccount.name, contractDir, wasmFile, abiFile, waitForTransBlock=True) if trans is None: cmdError("%s set contract exchange" % (ClientName)) errorExit("Failed to publish contract.") contractDir="contracts/simpledb" - wastFile="simpledb.wast" + wasmFile="simpledb.wasm" abiFile="simpledb.abi" Print("Setting simpledb contract without simpledb account was causing core dump in %s." % (ClientName)) Print("Verify %s generates an error, but does not core dump." % (ClientName)) - retMap=node.publishContract("simpledb", contractDir, wastFile, abiFile, shouldFail=True) + retMap=node.publishContract("simpledb", contractDir, wasmFile, abiFile, shouldFail=True) if retMap is None: errorExit("Failed to publish, but should have returned a details map") if retMap["returncode"] == 0 or retMap["returncode"] == 139: # 139 SIGSEGV diff --git a/tests/nodeos_under_min_avail_ram.py b/tests/nodeos_under_min_avail_ram.py index 633a7d67a13..84425f275c6 100755 --- a/tests/nodeos_under_min_avail_ram.py +++ b/tests/nodeos_under_min_avail_ram.py @@ -138,10 +138,10 @@ def setName(self, num): trans=nodes[0].delegatebw(contractAccount, 1000000.0000, 88000000.0000, exitOnError=True) contractDir="contracts/integration_test" - wastFile="contracts/integration_test/integration_test.wast" + wasmFile="contracts/integration_test/integration_test.wasm" abiFile="contracts/integration_test/integration_test.abi" Print("Publish contract") - trans=nodes[0].publishContract(contractAccount.name, contractDir, wastFile, abiFile, waitForTransBlock=True) + trans=nodes[0].publishContract(contractAccount.name, contractDir, wasmFile, abiFile, waitForTransBlock=True) if trans is None: cmdError("%s set contract %s" % (ClientName, contractAccount.name)) errorExit("Failed to publish contract.") diff --git a/tests/p2p_network_test.py b/tests/p2p_network_test.py index 4d03526424e..bdd7ed16f00 100755 --- a/tests/p2p_network_test.py +++ b/tests/p2p_network_test.py @@ -144,10 +144,10 @@ Print("host %s: %s" % (hosts[i], trans)) -wastFile="eosio.system.wast" +wasmFile="eosio.system.wasm" abiFile="eosio.system.abi" -Print("\nPush system contract %s %s" % (wastFile, abiFile)) -trans=node0.publishContract(eosio.name, wastFile, abiFile, waitForTransBlock=True) +Print("\nPush system contract %s %s" % (wasmFile, abiFile)) +trans=node0.publishContract(eosio.name, wasmFile, abiFile, waitForTransBlock=True) if trans is None: Utils.errorExit("Failed to publish eosio.system.") else: From 5b7333699b3023e01470ff14bd3de01449846fd6 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 10 Aug 2018 16:10:55 -0500 Subject: [PATCH 209/294] Fix mongodb-filter-on --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 4aaea9b3298..d0424251426 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -1409,6 +1409,7 @@ void mongo_db_plugin::plugin_initialize(const variables_map& options) } if( options.count( "mongodb-filter-on" )) { auto fo = options.at( "mongodb-filter-on" ).as>(); + my->filter_on_star = false; for( auto& s : fo ) { if( s == "*" ) { my->filter_on_star = true; From 52e066f2e53eaed6d2dddca4bf94dea45a323d65 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Sat, 11 Aug 2018 11:48:44 -0400 Subject: [PATCH 210/294] Remove one more unneeded "wast" variable now that we're wasm only --- programs/cleos/main.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 751fdd42311..f1ff21d3f02 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2155,7 +2155,7 @@ int main( int argc, char** argv ) { std::vector actions; auto set_code_callback = [&]() { - std::string wast; + std::string wasm; fc::path cpath(contractPath); if( cpath.filename().generic_string() == "." ) cpath = cpath.parent_path(); @@ -2166,9 +2166,8 @@ int main( int argc, char** argv ) { wasmPath = (cpath / wasmPath).generic_string(); std::cerr << localized(("Reading WASM from " + wasmPath + "...").c_str()) << std::endl; - fc::read_file_contents(wasmPath, wast); - EOS_ASSERT( !wast.empty(), wast_file_not_found, "no wasm file found ${f}", ("f", wasmPath) ); - vector wasm = vector(wast.begin(), wast.end()); + fc::read_file_contents(wasmPath, wasm); + EOS_ASSERT( !wasm.empty(), wast_file_not_found, "no wasm file found ${f}", ("f", wasmPath) ); actions.emplace_back( create_setcode(account, bytes(wasm.begin(), wasm.end()) ) ); if ( shouldSend ) { From ac186dde6d33a5a73873a0ac6003592c6a328612 Mon Sep 17 00:00:00 2001 From: Eugene Chung Date: Sun, 12 Aug 2018 09:15:50 +0900 Subject: [PATCH 211/294] change wlog to dlog; it can generate hugh amount of warning logs --- plugins/http_plugin/http_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/http_plugin/http_plugin.cpp b/plugins/http_plugin/http_plugin.cpp index c230755d209..0a4984381ee 100644 --- a/plugins/http_plugin/http_plugin.cpp +++ b/plugins/http_plugin/http_plugin.cpp @@ -239,7 +239,7 @@ namespace eosio { } ); } else { - wlog( "404 - not found: ${ep}", ("ep", resource)); + dlog( "404 - not found: ${ep}", ("ep", resource)); error_results results{websocketpp::http::status_code::not_found, "Not Found", error_results::error_info(fc::exception( FC_LOG_MESSAGE( error, "Unknown Endpoint" )), verbose_http_errors )}; con->set_body( fc::json::to_string( results )); From b4a8dd2212d3ae066de6e0513927d0812d3e12a3 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Sat, 11 Aug 2018 22:03:58 -0500 Subject: [PATCH 212/294] Add READ_ONLY db_read_mode --- libraries/chain/include/eosio/chain/controller.hpp | 1 + unittests/forked_tests.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index bde7d21e680..c457eb68cc9 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -36,6 +36,7 @@ namespace eosio { namespace chain { enum class db_read_mode { SPECULATIVE, HEAD, + READ_ONLY, IRREVERSIBLE }; diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index a196a1a58fe..de5bb068b3f 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -453,6 +453,11 @@ BOOST_AUTO_TEST_CASE( read_modes ) try { BOOST_REQUIRE_EQUAL(head_block_num, head.control->fork_db_head_block_num()); BOOST_REQUIRE_EQUAL(head_block_num, head.control->head_block_num()); + tester read_only(true, db_read_mode::READ_ONLY); + push_blocks(c, read_only); + BOOST_REQUIRE_EQUAL(head_block_num, read_only.control->fork_db_head_block_num()); + BOOST_REQUIRE_EQUAL(head_block_num, read_only.control->head_block_num()); + tester irreversible(true, db_read_mode::IRREVERSIBLE); push_blocks(c, irreversible); BOOST_REQUIRE_EQUAL(head_block_num, irreversible.control->fork_db_head_block_num()); From 592fdfc3e1fd6ecb928220974bd5005b3dc1dbcc Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Sat, 11 Aug 2018 22:04:40 -0500 Subject: [PATCH 213/294] Don't start block when in read-only mode --- plugins/producer_plugin/producer_plugin.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 3d251573fc2..64a6a5d8cd2 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -877,6 +877,10 @@ fc::time_point producer_plugin_impl::calculate_pending_block_time() const { producer_plugin_impl::start_block_result producer_plugin_impl::start_block(bool &last_block) { chain::controller& chain = app().get_plugin().chain(); + + if( chain.get_read_mode() == chain::db_read_mode::READ_ONLY ) + return start_block_result::waiting; + const auto& hbs = chain.head_block_state(); //Schedule for the next second's tick regardless of chain state From 78cf936f068189e82ad3ad91106d642ef8d609c0 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Sat, 11 Aug 2018 22:05:46 -0500 Subject: [PATCH 214/294] Disable read-write operations on chain_api_plugin when in read-only mode --- plugins/chain_api_plugin/chain_api_plugin.cpp | 6 +++--- plugins/chain_plugin/chain_plugin.cpp | 11 +++++++++++ .../include/eosio/chain_plugin/chain_plugin.hpp | 3 +-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/plugins/chain_api_plugin/chain_api_plugin.cpp b/plugins/chain_api_plugin/chain_api_plugin.cpp index 10537b2ba40..7fba0b3a91e 100644 --- a/plugins/chain_api_plugin/chain_api_plugin.cpp +++ b/plugins/chain_api_plugin/chain_api_plugin.cpp @@ -49,7 +49,7 @@ struct async_result_visitor : public fc::visitor { #define CALL_ASYNC(api_name, api_handle, api_namespace, call_name, call_result, http_response_code) \ {std::string("/v1/" #api_name "/" #call_name), \ - [this, api_handle](string, string body, url_response_callback cb) mutable { \ + [this](string, string body, url_response_callback cb) mutable { \ if (body.empty()) body = "{}"; \ api_handle.call_name(fc::json::from_string(body).as(),\ [cb, body](const fc::static_variant& result){\ @@ -66,16 +66,16 @@ struct async_result_visitor : public fc::visitor { }\ } +#define CHAIN_RW_API app().get_plugin().get_read_write_api() #define CHAIN_RO_CALL(call_name, http_response_code) CALL(chain, ro_api, chain_apis::read_only, call_name, http_response_code) #define CHAIN_RW_CALL(call_name, http_response_code) CALL(chain, rw_api, chain_apis::read_write, call_name, http_response_code) #define CHAIN_RO_CALL_ASYNC(call_name, call_result, http_response_code) CALL_ASYNC(chain, ro_api, chain_apis::read_only, call_name, call_result, http_response_code) -#define CHAIN_RW_CALL_ASYNC(call_name, call_result, http_response_code) CALL_ASYNC(chain, rw_api, chain_apis::read_write, call_name, call_result, http_response_code) +#define CHAIN_RW_CALL_ASYNC(call_name, call_result, http_response_code) CALL_ASYNC(chain, (CHAIN_RW_API), chain_apis::read_write, call_name, call_result, http_response_code) void chain_api_plugin::plugin_startup() { ilog( "starting chain_api_plugin" ); my.reset(new chain_api_plugin_impl(app().get_plugin().chain())); auto ro_api = app().get_plugin().get_read_only_api(); - auto rw_api = app().get_plugin().get_read_write_api(); app().get_plugin().add_api({ CHAIN_RO_CALL(get_info, 200l), diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index f6148c2982d..98c30206dc9 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -40,6 +40,8 @@ std::ostream& operator<<(std::ostream& osm, eosio::chain::db_read_mode m) { osm << "speculative"; } else if ( m == eosio::chain::db_read_mode::HEAD ) { osm << "head"; + } else if ( m == eosio::chain::db_read_mode::READ_ONLY ) { + osm << "read-only"; } else if ( m == eosio::chain::db_read_mode::IRREVERSIBLE ) { osm << "irreversible"; } @@ -65,6 +67,8 @@ void validate(boost::any& v, v = boost::any(eosio::chain::db_read_mode::SPECULATIVE); } else if ( s == "head" ) { v = boost::any(eosio::chain::db_read_mode::HEAD); + } else if ( s == "read-only" ) { + v = boost::any(eosio::chain::db_read_mode::READ_ONLY); } else if ( s == "irreversible" ) { v = boost::any(eosio::chain::db_read_mode::IRREVERSIBLE); } else { @@ -657,6 +661,13 @@ void chain_plugin::plugin_shutdown() { my->chain.reset(); } +chain_apis::read_write::read_write(controller& db, const fc::microseconds& abi_serializer_max_time) +: db(db) +, abi_serializer_max_time(abi_serializer_max_time) +{ + EOS_ASSERT( db.get_read_mode() != chain::db_read_mode::READ_ONLY, missing_chain_api_plugin_exception, "Not allowed, node in read-only mode" ); +} + chain_apis::read_write chain_plugin::get_read_write_api() { return chain_apis::read_write(chain(), get_abi_serializer_max_time()); } diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index 2b1edf3299d..52365d19dd9 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -470,8 +470,7 @@ class read_write { controller& db; const fc::microseconds abi_serializer_max_time; public: - read_write(controller& db, const fc::microseconds& abi_serializer_max_time) - : db(db), abi_serializer_max_time(abi_serializer_max_time) {} + read_write(controller& db, const fc::microseconds& abi_serializer_max_time); using push_block_params = chain::signed_block; using push_block_results = empty; From 12ebfa0c820f925e56338d373fe084b91633b72e Mon Sep 17 00:00:00 2001 From: James Moore Date: Sun, 12 Aug 2018 09:11:39 -0700 Subject: [PATCH 215/294] net_plugin::plugin_startup gives more information when it attempts to bind to a port Helps people diagnose problems with issues like #3999. --- plugins/net_plugin/net_plugin.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 5363f035210..5aa6883e537 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3008,7 +3008,13 @@ namespace eosio { if( my->acceptor ) { my->acceptor->open(my->listen_endpoint.protocol()); my->acceptor->set_option(tcp::acceptor::reuse_address(true)); - my->acceptor->bind(my->listen_endpoint); + try { + my->acceptor->bind(my->listen_endpoint); + } catch (const std::exception& e) { + ilog("net_plugin::plugin_startup failed to bind to port ${port}", + ("port", my->listen_endpoint.port())); + throw e; + } my->acceptor->listen(); ilog("starting listener, max clients is ${mc}",("mc",my->max_client_count)); my->start_listen_loop(); From e7fc302e6eb8eced9c6719b2cb8e1a977ba1a807 Mon Sep 17 00:00:00 2001 From: Paul Calabrese Date: Mon, 13 Aug 2018 09:43:39 -0500 Subject: [PATCH 216/294] Fix launcher boot script template --- testnet.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testnet.template b/testnet.template index 7a7593cc83a..d12920bfd31 100644 --- a/testnet.template +++ b/testnet.template @@ -64,7 +64,7 @@ cacmd () { sleep 2 ecmd get info -wcmd create -n ignition +wcmd create --to-console -n ignition # Manual deployers, add a line below this block that looks like: # wcmd import -n ignition --private-key $PRODKEY[0] @@ -78,7 +78,7 @@ wcmd create -n ignition ecmd set contract eosio contracts/eosio.bios eosio.bios.wasm eosio.bios.abi # Create required system accounts -ecmd create key +ecmd create key --to-console pubsyskey=`grep "^Public key:" $logfile | tail -1 | sed "s/^Public key://"` prisyskey=`grep "^Private key:" $logfile | tail -1 | sed "s/^Private key://"` echo eosio.* keys: $prisyskey $pubsyskey >> $logfile From 4a4accb040a47a137ce68db61d446e24fad335bc Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 13 Aug 2018 13:57:57 -0400 Subject: [PATCH 217/294] Do not allow clients when in read-only mode --- plugins/net_plugin/net_plugin.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 1d67ad2d628..8ef46a8da4d 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -2496,6 +2496,11 @@ namespace eosio { void net_plugin_impl::handle_message( connection_ptr c, const packed_transaction &msg) { fc_dlog(logger, "got a packed transaction, cancel wait"); peer_ilog(c, "received packed_transaction"); + controller& cc = my_impl->chain_plug->chain(); + if( cc.get_read_mode() == eosio::db_read_mode::READ_ONLY ) { + fc_dlog(logger, "got a txn in read-only mode - dropping"); + return; + } if( sync_master->is_active(c) ) { fc_dlog(logger, "got a txn during sync - dropping"); return; @@ -2507,7 +2512,6 @@ namespace eosio { return; } dispatcher->recv_transaction(c, tid); - uint64_t code = 0; chain_plug->accept_transaction(msg, [=](const static_variant& result) { if (result.contains()) { auto e_ptr = result.get(); @@ -3027,8 +3031,8 @@ namespace eosio { ilog("starting listener, max clients is ${mc}",("mc",my->max_client_count)); my->start_listen_loop(); } + chain::controller&cc = my->chain_plug->chain(); { - chain::controller&cc = my->chain_plug->chain(); cc.accepted_block_header.connect( boost::bind(&net_plugin_impl::accepted_block_header, my.get(), _1)); cc.accepted_block.connect( boost::bind(&net_plugin_impl::accepted_block, my.get(), _1)); cc.irreversible_block.connect( boost::bind(&net_plugin_impl::irreversible_block, my.get(), _1)); @@ -3039,6 +3043,11 @@ namespace eosio { my->incoming_transaction_ack_subscription = app().get_channel().subscribe(boost::bind(&net_plugin_impl::transaction_ack, my.get(), _1)); + if( cc.get_read_mode() == chain::db_read_mode::READ_ONLY ) { + my->max_nodes_per_host = 0; + ilog( "node in read-only mode setting max_nodes_per_host to 0 to prevent connections" ); + } + my->start_monitors(); for( auto seed_node : my->supplied_peers ) { From 6ac32ee91be88d04425f771dfc486304bc842488 Mon Sep 17 00:00:00 2001 From: Paul Calabrese Date: Mon, 13 Aug 2018 13:03:53 -0500 Subject: [PATCH 218/294] More fixes for launch boot template --- testnet.template | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testnet.template b/testnet.template index d12920bfd31..dab3cc5b37f 100644 --- a/testnet.template +++ b/testnet.template @@ -94,9 +94,9 @@ ecmd create account eosio eosio.token $pubsyskey $pubsyskey ecmd create account eosio eosio.vpay $pubsyskey $pubsyskey ecmd create account eosio eosio.sudo $pubsyskey $pubsyskey -ecmd set contract eosio.token contracts/eosio.token eosio.token.wast eosio.token.abi -ecmd set contract eosio.msig contracts/eosio.msig eosio.msig.wast eosio.msig.abi -ecmd set contract eosio.sudo contracts/eosio.sudo eosio.sudo.wast eosio.sudo.abi +ecmd set contract eosio.token contracts/eosio.token eosio.token.wasm eosio.token.abi +ecmd set contract eosio.msig contracts/eosio.msig eosio.msig.wasm eosio.msig.abi +ecmd set contract eosio.sudo contracts/eosio.sudo eosio.sudo.wasm eosio.sudo.abi echo ===== Start: $step ============ >> $logfile echo executing: cleos --wallet-url $wdurl --url http://$bioshost:$biosport push action eosio.token create '[ "eosio", "10000000000.0000 SYS" ]' -p eosio.token | tee -a $logfile @@ -107,7 +107,7 @@ programs/cleos/cleos --wallet-url $wdurl --url http://$bioshost:$biosport push a echo ==== End: $step ============== >> $logfile step=$(($step + 1)) -ecmd set contract eosio contracts/eosio.system eosio.system.wast eosio.system.abi +ecmd set contract eosio contracts/eosio.system eosio.system.wasm eosio.system.abi # Manual deployers, add a series of lines below this block that looks like: # cacmd $PRODNAME[0] $OWNERKEY[0] $ACTIVEKEY[0] From 71f667f6b2ebb17e36310e2578cdba1346358b96 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 13 Aug 2018 14:37:46 -0400 Subject: [PATCH 219/294] Do not process incoming transactions in read-only mode --- plugins/bnet_plugin/bnet_plugin.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/bnet_plugin/bnet_plugin.cpp b/plugins/bnet_plugin/bnet_plugin.cpp index 4671e25c477..80104a15ea1 100644 --- a/plugins/bnet_plugin/bnet_plugin.cpp +++ b/plugins/bnet_plugin/bnet_plugin.cpp @@ -1038,6 +1038,8 @@ namespace eosio { peer_elog(this, "bad packed_transaction_ptr : null pointer"); EOS_THROW(transaction_exception, "bad transaction"); } + if( app().get_plugin().chain().get_read_mode() == chain::db_read_mode::READ_ONLY ) + return; auto id = p->id(); // ilog( "recv trx ${n}", ("n", id) ); @@ -1368,6 +1370,11 @@ namespace eosio { }); + if( app().get_plugin().chain().get_read_mode() == chain::db_read_mode::READ_ONLY ) { + my->_request_trx = false; + ilog( "setting bnet-no-trx to true since in read-only mode" ); + } + const auto address = boost::asio::ip::make_address( my->_bnet_endpoint_address ); my->_ioc.reset( new boost::asio::io_context{my->_num_threads} ); From 0c7413e92798a72499429754bd1fc560b9b571c9 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Mon, 13 Aug 2018 14:06:20 -0500 Subject: [PATCH 220/294] Multiple retries to identify transaction. GH #5199 --- tests/Node.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/tests/Node.py b/tests/Node.py index 3f50bfbce4f..a66b5e03fa3 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -245,27 +245,30 @@ def isBlockFinalized(self, blockNum): # pylint: disable=too-many-branches def getTransaction(self, transId, silentErrors=False, exitOnError=False, delayedRetry=True): - firstExitOnError=not delayedRetry and exitOnError + exitOnErrorForDelayed=not delayedRetry and exitOnError + timeout=3 if not self.enableMongo: cmdDesc="get transaction" cmd="%s %s" % (cmdDesc, transId) msg="(transaction id=%s)" % (transId); - trans=self.processCmd(cmd, cmdDesc, silentErrors=silentErrors, exitOnError=firstExitOnError, exitMsg=msg) - if trans is not None or not delayedRetry: - return trans - - # delay long enough to ensure - if Utils.Debug: Utils.Print("Could not find transaction with id %s, delay and retry" % (transId)) - time.sleep(1) + for i in range(0,(int(60/timeout) - 1)): + trans=self.processCmd(cmd, cmdDesc, silentErrors=silentErrors, exitOnError=exitOnErrorForDelayed, exitMsg=msg) + if trans is not None or not delayedRetry: + return trans + if Utils.Debug: Utils.Print("Could not find transaction with id %s, delay and retry" % (transId)) + time.sleep(timeout) + + # either it is there or the transaction has timed out return self.processCmd(cmd, cmdDesc, silentErrors=silentErrors, exitOnError=exitOnError, exitMsg=msg) else: - trans=self.getTransactionMdb(transId, silentErrors=silentErrors, exitOnError=firstExitOnError) - if trans is not None or not delayedRetry: - return trans - - if Utils.Debug: Utils.Print("Could not find transaction with id %s in mongodb, delay and retry" % (transId)) - time.sleep(3) - return self.getTransactionMdb(transId, silentErrors=silentErrors, exitOnError=firstExitOnError) + for i in range(0,(int(60/timeout) - 1)): + trans=self.getTransactionMdb(transId, silentErrors=silentErrors, exitOnError=exitOnErrorForDelayed) + if trans is not None or not delayedRetry: + return trans + if Utils.Debug: Utils.Print("Could not find transaction with id %s in mongodb, delay and retry" % (transId)) + time.sleep(timeout) + + return self.getTransactionMdb(transId, silentErrors=silentErrors, exitOnError=exitOnError) def getTransactionMdb(self, transId, silentErrors=False, exitOnError=False): """Get transaction from MongoDB. Since DB only contains finalized blocks, transactions can take a while to appear in DB.""" From 5bba70cb8d3d4b26963794a43f6a1fc6bb6dae78 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 13 Aug 2018 15:56:36 -0400 Subject: [PATCH 221/294] Do not allow push_transaction in read-only mode --- libraries/chain/controller.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 50966ffad29..0c582871a11 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1423,6 +1423,7 @@ void controller::push_confirmation( const header_confirmation& c ) { transaction_trace_ptr controller::push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline, uint32_t billed_cpu_time_us ) { validate_db_available_size(); + EOS_ASSERT( get_read_mode() != chain::db_read_mode::READ_ONLY, transaction_type_exception, "push transaction not allowed in read-only mode" ); EOS_ASSERT( trx && !trx->implicit && !trx->scheduled, transaction_type_exception, "Implicit/Scheduled transaction not allowed" ); return my->push_transaction(trx, deadline, billed_cpu_time_us, billed_cpu_time_us > 0 ); } From bbf50d8dfceb1ad9be5f3682bdb46094bd0a33e8 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 13 Aug 2018 16:24:48 -0400 Subject: [PATCH 222/294] Filter out transaction_trace if all action_traces filtered out --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index 0a960f7b9bc..cf0e4d1e9b3 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -785,6 +785,7 @@ void mongo_db_plugin_impl::_process_applied_transaction( const chain::transactio } if( !start_block_reached || !store_transaction_traces ) return; + if( !write_atraces ) return; //< do not insert transaction_trace if all action_traces filtered out // transaction trace insert From 52171a99383273c3bb3a96d844ba57f67a08ba3c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 13 Aug 2018 16:47:16 -0400 Subject: [PATCH 223/294] Fix for read-only test since push_transaction not allowed --- unittests/forked_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index de5bb068b3f..1ac6b7e6c19 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -453,7 +453,7 @@ BOOST_AUTO_TEST_CASE( read_modes ) try { BOOST_REQUIRE_EQUAL(head_block_num, head.control->fork_db_head_block_num()); BOOST_REQUIRE_EQUAL(head_block_num, head.control->head_block_num()); - tester read_only(true, db_read_mode::READ_ONLY); + tester read_only(false, db_read_mode::READ_ONLY); push_blocks(c, read_only); BOOST_REQUIRE_EQUAL(head_block_num, read_only.control->fork_db_head_block_num()); BOOST_REQUIRE_EQUAL(head_block_num, read_only.control->head_block_num()); From ce3744ecfcb2b837bcaee8396101ac61a27ef23c Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Mon, 13 Aug 2018 17:20:18 -0500 Subject: [PATCH 224/294] Allow eosiocpp to run without make install Add include paths and library locations to allow eosiocpp to run in- place. Now works on contract paths outside of eos hierarchy. Still works when installed. --- tools/eosiocpp.in | 59 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/tools/eosiocpp.in b/tools/eosiocpp.in index a67526c3e9c..dea17231715 100755 --- a/tools/eosiocpp.in +++ b/tools/eosiocpp.in @@ -5,7 +5,27 @@ if [ "${EOSIO_BIN_INSTALL_DIR}" == "." ]; then EOSIO_BIN_INSTALL_DIR=`pwd` fi EOSIO_INSTALL_DIR=`dirname ${EOSIO_BIN_INSTALL_DIR}` -ABIGEN=${EOSIO_INSTALL_DIR}/bin/eosio-abigen +if [ -x "@CMAKE_BINARY_DIR@/programs/eosio-abigen/eosio-abigen" ]; then + ABIGEN="@CMAKE_BINARY_DIR@/programs/eosio-abigen/eosio-abigen" +elif [ -x "${EOSIO_INSTALL_DIR}/bin/eosio-abigen" ]; then + ABIGEN=${EOSIO_INSTALL_DIR}/bin/eosio-abigen +fi +if [ -x "@CMAKE_BINARY_DIR@/externals/binaryen/bin/eosio-s2wasm" ]; then + EOSIO_S2WASM="@CMAKE_BINARY_DIR@/externals/binaryen/bin/eosio-s2wasm" +elif [ -x "${EOSIO_INSTALL_DIR}/bin/eosio-s2wasm" ]; then + EOSIO_S2WASM="${EOSIO_INSTALL_DIR}/bin/eosio-s2wasm" +else + echo "eosio-s2wasm not found either built or installed" + exit 12 +fi +if [ -x "@CMAKE_BINARY_DIR@/libraries/wasm-jit/Source/Programs/eosio-wast2wasm" ]; then + EOSIO_WAST2WASM="@CMAKE_BINARY_DIR@/libraries/wasm-jit/Source/Programs/eosio-wast2wasm" +elif [ -x "${EOSIO_INSTALL_DIR}/bin/eosio-wast2wasm" ]; then + EOSIO_WAST2WASM="${EOSIO_INSTALL_DIR}/bin/eosio-wast2wasm" +else + echo "eosio-wast2wasm not found either built or installed" + exit 14 +fi BOOST_INCLUDE_DIR=@Boost_INCLUDE_DIR@ function copy_skeleton { set -e @@ -41,8 +61,13 @@ function build_contract { ($PRINT_CMDS; @WASM_CLANG@ -emit-llvm -O3 --std=c++14 --target=wasm32 -nostdinc \ -DBOOST_DISABLE_ASSERTS -DBOOST_EXCEPTION_DISABLE \ - -nostdlib -nostdlibinc -ffreestanding -nostdlib -fno-threadsafe-statics -fno-rtti \ - -fno-exceptions -I ${EOSIO_INSTALL_DIR}/include \ + -nostdlib -nostdlibinc -ffreestanding -nostdlib \ + -fno-threadsafe-statics -fno-rtti -fno-exceptions \ + -I @CMAKE_SOURCE_DIR@/contracts \ + -I @CMAKE_SOURCE_DIR@/contracts/libc++/upstream/include \ + -I @CMAKE_SOURCE_DIR@/contracts/musl/upstream/include \ + -I @CMAKE_SOURCE_DIR@/externals/magic_get/include \ + -I ${EOSIO_INSTALL_DIR}/include \ -I${EOSIO_INSTALL_DIR}/include/libc++/upstream/include \ -I${EOSIO_INSTALL_DIR}/include/musl/upstream/include \ -I${BOOST_INCLUDE_DIR} \ @@ -53,16 +78,22 @@ function build_contract { done - ($PRINT_CMDS; @WASM_LLVM_LINK@ -only-needed -o $workdir/linked.bc $workdir/built/* \ - ${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/eosiolib.bc \ - ${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/libc++.bc \ - ${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/libc.bc - - - ) + declare -a possible_libs=("@CMAKE_BINARY_DIR@/contracts/eosiolib/eosiolib.bc" + "@CMAKE_BINARY_DIR@/contracts/libc++/libc++.bc" + "@CMAKE_BINARY_DIR@/contracts/musl/libc.bc" + "${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/eosiolib.bc" + "${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/libc++.bc" + "${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/libc.bc") + declare libs="" + for lib in "${possible_libs[@]}"; do + if [ -f "${lib}" ]; then + libs="${libs} ${lib}" + fi + done + ($PRINT_CMDS; @WASM_LLVM_LINK@ -only-needed -o $workdir/linked.bc $workdir/built/* ${libs}) ($PRINT_CMDS; @WASM_LLC@ -thread-model=single --asm-verbose=false -o $workdir/assembly.s $workdir/linked.bc) - ($PRINT_CMDS; ${EOSIO_INSTALL_DIR}/bin/eosio-s2wasm -o $outname -s 16384 $workdir/assembly.s) - ($PRINT_CMDS; ${EOSIO_INSTALL_DIR}/bin/eosio-wast2wasm $outname ${outname%.*}.wasm -n) + ($PRINT_CMDS; ${EOSIO_S2WASM} -o $outname -s 16384 $workdir/assembly.s) + ($PRINT_CMDS; ${EOSIO_WAST2WASM} $outname ${outname%.*}.wasm -n) ($PRINT_CMDS; rm -rf $workdir) set +e @@ -79,6 +110,10 @@ function generate_abi { ${ABIGEN} -extra-arg=-c -extra-arg=--std=c++14 -extra-arg=--target=wasm32 \ -extra-arg=-nostdinc -extra-arg=-nostdinc++ -extra-arg=-DABIGEN \ + -extra-arg=-I@CMAKE_SOURCE_DIR@/contracts \ + -extra-arg=-I@CMAKE_SOURCE_DIR@/contracts/libc++/upstream/include \ + -extra-arg=-I@CMAKE_SOURCE_DIR@/contracts/musl/upstream/include \ + -extra-arg=-I@CMAKE_SOURCE_DIR@/externals/magic_get/include \ -extra-arg=-I${EOSIO_INSTALL_DIR}/include/libc++/upstream/include \ -extra-arg=-I${EOSIO_INSTALL_DIR}/include/musl/upstream/include \ -extra-arg=-I${BOOST_INCLUDE_DIR} \ From 238170611f0d9456c02d73df086e5174465e4088 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Mon, 13 Aug 2018 20:26:12 -0400 Subject: [PATCH 225/294] revert change that broke upgrades without replay --- libraries/chain/include/eosio/chain/types.hpp | 54 +++++++------------ 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/libraries/chain/include/eosio/chain/types.hpp b/libraries/chain/include/eosio/chain/types.hpp index 51fce8d4c40..2a9117a99fc 100644 --- a/libraries/chain/include/eosio/chain/types.hpp +++ b/libraries/chain/include/eosio/chain/types.hpp @@ -109,15 +109,19 @@ namespace eosio { namespace chain { * wants to extend the core code then they will have to change the * packed_object::type field from enum_type to uint16 to avoid * warnings when converting packed_objects to/from json. + * + * UNUSED_ enums can be taken for new purposes but otherwise the offsets + * in this enumeration are potentially shared_memory breaking */ enum object_type { - null_object_type, + null_object_type = 0, account_object_type, account_sequence_object_type, permission_object_type, permission_usage_object_type, permission_link_object_type, + UNUSED_action_code_object_type, key_value_object_type, index64_object_type, index128_object_type, @@ -130,15 +134,24 @@ namespace eosio { namespace chain { transaction_object_type, generated_transaction_object_type, producer_object_type, - account_control_history_object_type, ///< Defined by history_plugin - public_key_history_object_type, ///< Defined by history_plugin + UNUSED_chain_property_object_type, + account_control_history_object_type, ///< Defined by history_plugin + UNUSED_account_transaction_history_object_type, + UNUSED_transaction_history_object_type, + public_key_history_object_type, ///< Defined by history_plugin + UNUSED_balance_object_type, + UNUSED_staked_balance_object_type, + UNUSED_producer_votes_object_type, + UNUSED_producer_schedule_object_type, + UNUSED_proxy_vote_object_type, + UNUSED_scope_sequence_object_type, table_id_object_type, resource_limits_object_type, resource_usage_object_type, resource_limits_state_object_type, resource_limits_config_object_type, - account_history_object_type, ///< Defined by history_plugin - action_history_object_type, ///< Defined by history_plugin + account_history_object_type, ///< Defined by history_plugin + action_history_object_type, ///< Defined by history_plugin reversible_block_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -170,35 +183,4 @@ namespace eosio { namespace chain { } } // eosio::chain -FC_REFLECT_ENUM(eosio::chain::object_type, - (null_object_type) - (account_object_type) - (account_sequence_object_type) - (permission_object_type) - (permission_usage_object_type) - (permission_link_object_type) - (key_value_object_type) - (index64_object_type) - (index128_object_type) - (index256_object_type) - (index_double_object_type) - (index_long_double_object_type) - (global_property_object_type) - (dynamic_global_property_object_type) - (block_summary_object_type) - (transaction_object_type) - (generated_transaction_object_type) - (producer_object_type) - (account_control_history_object_type) - (public_key_history_object_type) - (table_id_object_type) - (resource_limits_object_type) - (resource_usage_object_type) - (resource_limits_state_object_type) - (resource_limits_config_object_type) - (account_history_object_type) - (action_history_object_type) - (reversible_block_object_type) - (OBJECT_TYPE_COUNT) - ) FC_REFLECT( eosio::chain::void_t, ) From 63596426d90aee5afc9f0bd84603a89eb6b397f5 Mon Sep 17 00:00:00 2001 From: Leon de Rooij Date: Fri, 3 Aug 2018 08:55:24 +0000 Subject: [PATCH 226/294] add cleos convert pack_transaction and unpack_transaction --- programs/cleos/main.cpp | 65 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 68bc0ca655a..e76c3786c78 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -157,6 +157,7 @@ auto tx_expiration = fc::seconds(30); string tx_ref_block_num_or_id; bool tx_force_unique = false; bool tx_dont_broadcast = false; +bool tx_return_packed = false; bool tx_skip_sign = false; bool tx_print_json = false; bool print_request = false; @@ -185,6 +186,7 @@ void add_standard_transaction_options(CLI::App* cmd, string default_permission = cmd->add_flag("-s,--skip-sign", tx_skip_sign, localized("Specify if unlocked wallet keys should be used to sign transaction")); cmd->add_flag("-j,--json", tx_print_json, localized("print result as json")); cmd->add_flag("-d,--dont-broadcast", tx_dont_broadcast, localized("don't broadcast transaction to the network (just print to stdout)")); + cmd->add_flag("--return-packed", tx_return_packed, localized("used in conjunction with --dont-broadcast to get the packed transaction")); cmd->add_option("-r,--ref-block", tx_ref_block_num_or_id, (localized("set the reference block num or block id used for TAPOS (Transaction as Proof-of-Stake)"))); string msg = "An account and permission level to authorize, as in 'account@permission'"; @@ -301,7 +303,11 @@ fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000 if (!tx_dont_broadcast) { return call(push_txn_func, packed_transaction(trx, compression)); } else { - return fc::variant(trx); + if (!tx_return_packed) { + return fc::variant(trx); + } else { + return fc::variant(packed_transaction(trx, compression)); + } } } @@ -1741,6 +1747,47 @@ int main( int argc, char** argv ) { // create account auto createAccount = create_account_subcommand( create, true /*simple*/ ); + // convert subcommand + auto convert = app.add_subcommand("convert", localized("Pack and unpack transactions"), false); // TODO also add converting action args based on abi from here ? + convert->require_subcommand(); + + // pack transaction + string plain_signed_transaction_json; + auto pack_transaction = convert->add_subcommand("pack_transaction", localized("from plain signed json to packed form")); + pack_transaction->add_option("transaction", plain_signed_transaction_json, localized("the plain signed json (string)"))->required(); + + pack_transaction->set_callback([&] { + fc::variant trx_var; + try { + trx_var = json_from_file_or_string(plain_signed_transaction_json); + } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse plain transaction JSON '${data}'", ("data", plain_signed_transaction_json)) + signed_transaction trx = trx_var.as(); + + std::cout << fc::json::to_pretty_string(fc::variant(packed_transaction(trx, packed_transaction::none))) << std::endl; + }); + + // unpack transaction + string packed_transaction_json; + auto unpack_transaction = convert->add_subcommand("unpack_transaction", localized("from packed to plain signed json form")); + unpack_transaction->add_option("transaction", packed_transaction_json, localized("the packed transaction json (string containing packed_trx and optionally compression fields)"))->required(); + + unpack_transaction->set_callback([&] { + fc::variant trx_var; + try { + trx_var = json_from_file_or_string(packed_transaction_json); + } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse packed transaction JSON '${data}'", ("data", packed_transaction_json)) + string trx_hex; + try { + trx_hex = trx_var["packed_trx"].as_string(); + } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Missing packed_trx field in provided JSON '${data}'", ("data", packed_transaction_json)) + // TODO should I still copy the "signatures" and "context_free_data" fields from trx_var into below output trx ? + vector trx_blob(trx_hex.size()/2); + fc::from_hex(trx_hex, trx_blob.data(), trx_blob.size()); + transaction trx = fc::raw::unpack(trx_blob); + + std::cout << fc::json::to_pretty_string(fc::variant(trx)) << std::endl; + }); + // Get subcommand auto get = app.add_subcommand("get", localized("Retrieve various items and information from the blockchain"), false); get->require_subcommand(); @@ -2523,14 +2570,26 @@ int main( int argc, char** argv ) { auto trxSubcommand = push->add_subcommand("transaction", localized("Push an arbitrary JSON transaction")); trxSubcommand->add_option("transaction", trx_to_push, localized("The JSON string or filename defining the transaction to push"))->required(); + trxSubcommand->add_flag("-d,--dont-broadcast", tx_dont_broadcast, localized("don't broadcast transaction to the network (just print to stdout, essentially only validates)")); + trxSubcommand->add_flag("--return-packed", tx_return_packed, localized("used in conjunction with --dont-broadcast to get the packed transaction")); + trxSubcommand->set_callback([&] { fc::variant trx_var; try { trx_var = json_from_file_or_string(trx_to_push); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse transaction JSON '${data}'", ("data",trx_to_push)) signed_transaction trx = trx_var.as(); - auto trx_result = call(push_txn_func, packed_transaction(trx, packed_transaction::none)); - std::cout << fc::json::to_pretty_string(trx_result) << std::endl; + + if (!tx_dont_broadcast) { + auto trx_result = call(push_txn_func, packed_transaction(trx, packed_transaction::none)); + std::cout << fc::json::to_pretty_string(trx_result) << std::endl; + } else { + if (tx_return_packed) { + std::cout << fc::json::to_pretty_string(fc::variant(packed_transaction(trx, packed_transaction::none))) << std::endl; + } else { + std::cout << fc::json::to_pretty_string(fc::variant(trx)) << std::endl; + } + } }); From 06ba2a13ec5ac79b27831a4c217920d4e961afd5 Mon Sep 17 00:00:00 2001 From: Leon de Rooij Date: Sun, 5 Aug 2018 23:27:46 +0000 Subject: [PATCH 227/294] added cleos convert pack_action and unpack_action allow printing of only unpacked action when parsing a trx --- programs/cleos/main.cpp | 83 +++++++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index e76c3786c78..691194fed63 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -76,6 +76,7 @@ Usage: ./cleos create account [OPTIONS] creator name OwnerKey ActiveKey #include #include #include +#include #include #include #include @@ -364,6 +365,27 @@ bytes variant_to_bin( const account_name& account, const action_name& action, co } } +fc::variant bin_to_variant( const account_name& account, const action_name& action, const bytes& action_args) { + static unordered_map > abi_cache; + auto it = abi_cache.find( account ); + if ( it == abi_cache.end() ) { + const auto result = call(get_raw_code_and_abi_func, fc::mutable_variant_object("account_name", account)); + std::tie( it, std::ignore ) = abi_cache.emplace( account, result["abi"].as_blob().data ); + //we also received result["wasm"], but we don't use it + } + const std::vector& abi_v = it->second; + + abi_def abi; + if( abi_serializer::to_abi(abi_v, abi) ) { + abi_serializer abis( abi, fc::seconds(10) ); + auto action_type = abis.get_action_type(action); + FC_ASSERT(!action_type.empty(), "Unknown action ${action} in contract ${contract}", ("action", action)("contract", account)); + return abis.binary_to_variant(action_type, action_args, fc::seconds(10)); + } else { + FC_ASSERT(false, "No ABI found for ${contract}", ("contract", account)); + } +} + fc::variant json_from_file_or_string(const string& file_or_str, fc::json::parse_type ptype = fc::json::legacy_parser) { regex r("^[ \t]*[\{\[]"); @@ -1753,24 +1775,23 @@ int main( int argc, char** argv ) { // pack transaction string plain_signed_transaction_json; - auto pack_transaction = convert->add_subcommand("pack_transaction", localized("from plain signed json to packed form")); - pack_transaction->add_option("transaction", plain_signed_transaction_json, localized("the plain signed json (string)"))->required(); - + auto pack_transaction = convert->add_subcommand("pack_transaction", localized("From plain signed json to packed form")); + pack_transaction->add_option("transaction", plain_signed_transaction_json, localized("The plain signed json (string)"))->required(); pack_transaction->set_callback([&] { fc::variant trx_var; try { trx_var = json_from_file_or_string(plain_signed_transaction_json); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse plain transaction JSON '${data}'", ("data", plain_signed_transaction_json)) signed_transaction trx = trx_var.as(); - std::cout << fc::json::to_pretty_string(fc::variant(packed_transaction(trx, packed_transaction::none))) << std::endl; }); // unpack transaction string packed_transaction_json; - auto unpack_transaction = convert->add_subcommand("unpack_transaction", localized("from packed to plain signed json form")); - unpack_transaction->add_option("transaction", packed_transaction_json, localized("the packed transaction json (string containing packed_trx and optionally compression fields)"))->required(); - + bool unpack_action_data_flag = false; + auto unpack_transaction = convert->add_subcommand("unpack_transaction", localized("From packed to plain signed json form")); + unpack_transaction->add_option("transaction", packed_transaction_json, localized("The packed transaction json (string containing packed_trx and optionally compression fields)"))->required(); + unpack_transaction->add_flag("--unpack-action-data", unpack_action_data_flag, localized("Upack all action datas within transaction and only return those, needs interaction with nodeos")); unpack_transaction->set_callback([&] { fc::variant trx_var; try { @@ -1780,12 +1801,52 @@ int main( int argc, char** argv ) { try { trx_hex = trx_var["packed_trx"].as_string(); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Missing packed_trx field in provided JSON '${data}'", ("data", packed_transaction_json)) - // TODO should I still copy the "signatures" and "context_free_data" fields from trx_var into below output trx ? - vector trx_blob(trx_hex.size()/2); + + std::vector trx_blob(trx_hex.size()/2); fc::from_hex(trx_hex, trx_blob.data(), trx_blob.size()); - transaction trx = fc::raw::unpack(trx_blob); + transaction unpacked_trx = fc::raw::unpack(trx_blob); + // TODO would it be nice to put the unpacked_trx actions into the trx actions in a separate unpacked_data field or somesuch ? + if (unpack_action_data_flag) { + for ( const auto& a : unpacked_trx.actions ) { + fc::variant unpacked_action_data = bin_to_variant(a.account, a.name, a.data); + std::cout << fc::json::to_pretty_string(unpacked_action_data) << std::endl; + } + } else { + // TODO should the "sigantures" and "context_free_data" fields be copied from trx_var into the action field(s) ? + std::cout << fc::json::to_pretty_string(fc::variant(unpacked_trx)) << std::endl; + } + }); + + // pack action data + string unpacked_action_data_account_string; + string unpacked_action_data_name_string; + string unpacked_action_data_string; + auto pack_action_data = convert->add_subcommand("pack_action_data", localized("From json action data to packed form")); + pack_action_data->add_option("account", unpacked_action_data_account_string, localized("The name of the account that hosts the contract"))->required(); + pack_action_data->add_option("name", unpacked_action_data_name_string, localized("The name of the function that's called by this action"))->required(); + pack_action_data->add_option("unpacked_action_data", unpacked_action_data_string, localized("The action data expressed as json"))->required(); + pack_action_data->set_callback([&] { + fc::variant unpacked_action_data_json; + try { + unpacked_action_data_json = json_from_file_or_string(unpacked_action_data_string); + } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse unpacked action data JSON") + bytes packed_action_data_string = variant_to_bin(unpacked_action_data_account_string, unpacked_action_data_name_string, unpacked_action_data_json); + std::cout << fc::to_hex(packed_action_data_string.data(), packed_action_data_string.size()) << std::endl; + }); - std::cout << fc::json::to_pretty_string(fc::variant(trx)) << std::endl; + // unpack action data + string packed_action_data_account_string; + string packed_action_data_name_string; + string packed_action_data_string; + auto unpack_action_data = convert->add_subcommand("unpack_action_data", localized("From packed to json action data form")); + unpack_action_data->add_option("account", packed_action_data_account_string, localized("The name of the account that hosts the contract"))->required(); + unpack_action_data->add_option("name", packed_action_data_name_string, localized("The name of the function that's called by this action"))->required(); + unpack_action_data->add_option("packed_action_data", packed_action_data_string, localized("The action data expressed as packed hex string"))->required(); + unpack_action_data->set_callback([&] { + vector packed_action_data_blob(packed_action_data_string.size()/2); + fc::from_hex(packed_action_data_string, packed_action_data_blob.data(), packed_action_data_blob.size()); + fc::variant unpacked_action_data_json = bin_to_variant(packed_action_data_account_string, packed_action_data_name_string, packed_action_data_blob); + std::cout << fc::json::to_pretty_string(unpacked_action_data_json) << std::endl; }); // Get subcommand From eec379299e9c75a9c357d3e5082c749a81d916b1 Mon Sep 17 00:00:00 2001 From: Leon de Rooij Date: Sun, 5 Aug 2018 23:33:01 +0000 Subject: [PATCH 228/294] forgot to remove this require --- programs/cleos/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 691194fed63..39e2a291ce6 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -76,7 +76,6 @@ Usage: ./cleos create account [OPTIONS] creator name OwnerKey ActiveKey #include #include #include -#include #include #include #include From 76246ff3368eabfc71f7ede9d562b1ac72715d81 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 14 Aug 2018 08:58:00 -0400 Subject: [PATCH 229/294] Factor out abi_serializer resolver --- programs/cleos/main.cpp | 99 ++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 55 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 39e2a291ce6..78ffa681739 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -154,6 +154,7 @@ bool no_verify = false; vector headers; auto tx_expiration = fc::seconds(30); +const fc::microseconds abi_serializer_max_time = fc::seconds(1); // No risk to client side serialization taking a long time string tx_ref_block_num_or_id; bool tx_force_unique = false; bool tx_dont_broadcast = false; @@ -343,46 +344,51 @@ void print_action( const fc::variant& at ) { } } -bytes variant_to_bin( const account_name& account, const action_name& action, const fc::variant& action_args_var ) { - static unordered_map > abi_cache; +//resolver for ABI serializer to decode actions in proposed transaction in multisig contract +auto abi_serializer_resolver = [](const name& account) -> optional { + static unordered_map > abi_cache; auto it = abi_cache.find( account ); if ( it == abi_cache.end() ) { - const auto result = call(get_raw_code_and_abi_func, fc::mutable_variant_object("account_name", account)); - std::tie( it, std::ignore ) = abi_cache.emplace( account, result["abi"].as_blob().data ); - //we also received result["wasm"], but we don't use it - } - const std::vector& abi_v = it->second; - - abi_def abi; - if( abi_serializer::to_abi(abi_v, abi) ) { - abi_serializer abis( abi, fc::seconds(10) ); - auto action_type = abis.get_action_type(action); - FC_ASSERT(!action_type.empty(), "Unknown action ${action} in contract ${contract}", ("action", action)("contract", account)); - return abis.variant_to_binary(action_type, action_args_var, fc::seconds(10)); - } else { - FC_ASSERT(false, "No ABI found for ${contract}", ("contract", account)); + auto result = call(get_abi_func, fc::mutable_variant_object("account_name", account)); + auto abi_results = result.as(); + + optional abis; + if( abi_results.abi.valid() ) { + abis.emplace( *abi_results.abi, abi_serializer_max_time ); + } else { + std::cerr << "ABI for contract " << account.to_string() << " not found. Action data will be shown in hex only." << std::endl; + } + abi_cache.emplace( account, abis ); + + return abis; } + + return it->second; +}; + +bytes variant_to_bin( const account_name& account, const action_name& action, const fc::variant& action_args_var ) { + auto abis = abi_serializer_resolver( account ); + FC_ASSERT( abis.valid(), "No ABI found for ${contract}", ("contract", account)); + + auto action_type = abis->get_action_type( action ); + FC_ASSERT( !action_type.empty(), "Unknown action ${action} in contract ${contract}", ("action", action)( "contract", account )); + return abis->variant_to_binary( action_type, action_args_var, abi_serializer_max_time ); } fc::variant bin_to_variant( const account_name& account, const action_name& action, const bytes& action_args) { - static unordered_map > abi_cache; - auto it = abi_cache.find( account ); - if ( it == abi_cache.end() ) { - const auto result = call(get_raw_code_and_abi_func, fc::mutable_variant_object("account_name", account)); - std::tie( it, std::ignore ) = abi_cache.emplace( account, result["abi"].as_blob().data ); - //we also received result["wasm"], but we don't use it - } - const std::vector& abi_v = it->second; - - abi_def abi; - if( abi_serializer::to_abi(abi_v, abi) ) { - abi_serializer abis( abi, fc::seconds(10) ); - auto action_type = abis.get_action_type(action); - FC_ASSERT(!action_type.empty(), "Unknown action ${action} in contract ${contract}", ("action", action)("contract", account)); - return abis.binary_to_variant(action_type, action_args, fc::seconds(10)); - } else { - FC_ASSERT(false, "No ABI found for ${contract}", ("contract", account)); - } + auto abis = abi_serializer_resolver( account ); + FC_ASSERT( abis.valid(), "No ABI found for ${contract}", ("contract", account)); + + auto action_type = abis->get_action_type( action ); + FC_ASSERT( !action_type.empty(), "Unknown action ${action} in contract ${contract}", ("action", action)( "contract", account )); + return abis->binary_to_variant( action_type, action_args, abi_serializer_max_time ); +} + +fc::variant trx_bin_to_variant( const account_name& account, const bytes& trx_args) { + auto abis = abi_serializer_resolver( account ); + FC_ASSERT( abis.valid(), "No ABI found for ${contract}", ("contract", account)); + + return abis->binary_to_variant( "transaction", trx_args, abi_serializer_max_time ); } fc::variant json_from_file_or_string(const string& file_or_str, fc::json::parse_type ptype = fc::json::legacy_parser) @@ -1706,8 +1712,6 @@ int main( int argc, char** argv ) { textdomain(locale_domain); context = eosio::client::http::create_http_context(); - fc::microseconds abi_serializer_max_time = fc::seconds(1); // No risk to client side serialization taking a long time - CLI::App app{"Command Line Interface to EOSIO Client"}; app.require_subcommand(); app.add_option( "-H,--host", obsoleted_option_host_port, localized("the host where nodeos is running") )->group("hidden"); @@ -1790,7 +1794,7 @@ int main( int argc, char** argv ) { bool unpack_action_data_flag = false; auto unpack_transaction = convert->add_subcommand("unpack_transaction", localized("From packed to plain signed json form")); unpack_transaction->add_option("transaction", packed_transaction_json, localized("The packed transaction json (string containing packed_trx and optionally compression fields)"))->required(); - unpack_transaction->add_flag("--unpack-action-data", unpack_action_data_flag, localized("Upack all action datas within transaction and only return those, needs interaction with nodeos")); + unpack_transaction->add_flag("--unpack-action-data", unpack_action_data_flag, localized("Unpack all action datas within transaction, needs interaction with nodeos")); unpack_transaction->set_callback([&] { fc::variant trx_var; try { @@ -1804,12 +1808,9 @@ int main( int argc, char** argv ) { std::vector trx_blob(trx_hex.size()/2); fc::from_hex(trx_hex, trx_blob.data(), trx_blob.size()); transaction unpacked_trx = fc::raw::unpack(trx_blob); - // TODO would it be nice to put the unpacked_trx actions into the trx actions in a separate unpacked_data field or somesuch ? if (unpack_action_data_flag) { - for ( const auto& a : unpacked_trx.actions ) { - fc::variant unpacked_action_data = bin_to_variant(a.account, a.name, a.data); - std::cout << fc::json::to_pretty_string(unpacked_action_data) << std::endl; - } + abi_serializer::to_variant(unpacked_trx, trx_var, abi_serializer_resolver, abi_serializer_max_time); + std::cout << fc::json::to_pretty_string( trx_var ); } else { // TODO should the "sigantures" and "context_free_data" fields be copied from trx_var into the action field(s) ? std::cout << fc::json::to_pretty_string(fc::variant(unpacked_trx)) << std::endl; @@ -2759,18 +2760,6 @@ int main( int argc, char** argv ) { send_actions({chain::action{accountPermissions, "eosio.msig", "propose", variant_to_bin( N(eosio.msig), N(propose), args ) }}); }); - //resolver for ABI serializer to decode actions in proposed transaction in multisig contract - auto resolver = [abi_serializer_max_time](const name& code) -> optional { - auto result = call(get_abi_func, fc::mutable_variant_object("account_name", code.to_string())); - if (result["abi"].is_object()) { - //std::cout << "ABI: " << fc::json::to_pretty_string(result) << std::endl; - return optional(abi_serializer(result["abi"].as(), abi_serializer_max_time)); - } else { - std::cerr << "ABI for contract " << code.to_string() << " not found. Action data will be shown in hex only." << std::endl; - return optional(); - } - }; - //multisige propose transaction auto propose_trx = msig->add_subcommand("propose_trx", localized("Propose transaction")); add_standard_transaction_options(propose_trx); @@ -2846,7 +2835,7 @@ int main( int argc, char** argv ) { fc::variant trx_var; abi_serializer abi; - abi.to_variant(trx, trx_var, resolver, abi_serializer_max_time); + abi.to_variant(trx, trx_var, abi_serializer_resolver, abi_serializer_max_time); obj["transaction"] = trx_var; std::cout << fc::json::to_pretty_string(obj) << std::endl; From 10d15a912362adf7fe2411c872b710dbd7eb534b Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 14 Aug 2018 11:46:26 -0400 Subject: [PATCH 230/294] Support packing a transaction with exploded action data --- programs/cleos/main.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 78ffa681739..4fb7ed9a78d 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -154,7 +154,7 @@ bool no_verify = false; vector headers; auto tx_expiration = fc::seconds(30); -const fc::microseconds abi_serializer_max_time = fc::seconds(1); // No risk to client side serialization taking a long time +const fc::microseconds abi_serializer_max_time = fc::seconds(10); // No risk to client side serialization taking a long time string tx_ref_block_num_or_id; bool tx_force_unique = false; bool tx_dont_broadcast = false; @@ -1778,15 +1778,25 @@ int main( int argc, char** argv ) { // pack transaction string plain_signed_transaction_json; + bool pack_action_data_flag = false; auto pack_transaction = convert->add_subcommand("pack_transaction", localized("From plain signed json to packed form")); pack_transaction->add_option("transaction", plain_signed_transaction_json, localized("The plain signed json (string)"))->required(); + pack_transaction->add_flag("--pack-action-data", pack_action_data_flag, localized("Pack all action datas within transaction, needs interaction with nodeos")); pack_transaction->set_callback([&] { fc::variant trx_var; try { trx_var = json_from_file_or_string(plain_signed_transaction_json); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse plain transaction JSON '${data}'", ("data", plain_signed_transaction_json)) - signed_transaction trx = trx_var.as(); - std::cout << fc::json::to_pretty_string(fc::variant(packed_transaction(trx, packed_transaction::none))) << std::endl; + if( pack_action_data_flag ) { + signed_transaction trx; + abi_serializer::from_variant(trx_var, trx, abi_serializer_resolver, abi_serializer_max_time); + std::cout << fc::json::to_pretty_string( packed_transaction( trx, packed_transaction::none ) ) << std::endl; + } else { + try { + signed_transaction trx = trx_var.as(); + std::cout << fc::json::to_pretty_string( fc::variant( packed_transaction( trx, packed_transaction::none ))) << std::endl; + } EOS_RETHROW_EXCEPTIONS( transaction_type_exception, "Fail to convert transaction, --pack-action-data likely needed" ) + } }); // unpack transaction From d7a375a353ceda30f8a52c1032c37428021e401e Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Tue, 14 Aug 2018 13:59:23 -0400 Subject: [PATCH 231/294] Remove nodeos from being MAS signed Now that nodeos cannot be a wallet, remove it from Mac App Store signing -- it has no need to access Secure Enclave --- programs/nodeos/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 532749284ba..5611cfafe7d 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -93,5 +93,3 @@ install(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/eosio WORLD_READ WORLD_EXECUTE ) - -mas_sign(${NODE_EXECUTABLE_NAME}) From 99f8f2b2f6fba77cfca070a16c979ca905b5e632 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 14 Aug 2018 14:36:14 -0400 Subject: [PATCH 232/294] Simplify unpack_transaction --- programs/cleos/main.cpp | 46 +++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 4fb7ed9a78d..97af0248772 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -384,13 +384,6 @@ fc::variant bin_to_variant( const account_name& account, const action_name& acti return abis->binary_to_variant( action_type, action_args, abi_serializer_max_time ); } -fc::variant trx_bin_to_variant( const account_name& account, const bytes& trx_args) { - auto abis = abi_serializer_resolver( account ); - FC_ASSERT( abis.valid(), "No ABI found for ${contract}", ("contract", account)); - - return abis->binary_to_variant( "transaction", trx_args, abi_serializer_max_time ); -} - fc::variant json_from_file_or_string(const string& file_or_str, fc::json::parse_type ptype = fc::json::legacy_parser) { regex r("^[ \t]*[\{\[]"); @@ -1781,16 +1774,16 @@ int main( int argc, char** argv ) { bool pack_action_data_flag = false; auto pack_transaction = convert->add_subcommand("pack_transaction", localized("From plain signed json to packed form")); pack_transaction->add_option("transaction", plain_signed_transaction_json, localized("The plain signed json (string)"))->required(); - pack_transaction->add_flag("--pack-action-data", pack_action_data_flag, localized("Pack all action datas within transaction, needs interaction with nodeos")); + pack_transaction->add_flag("--pack-action-data", pack_action_data_flag, localized("Pack all action data within transaction, needs interaction with nodeos")); pack_transaction->set_callback([&] { fc::variant trx_var; try { - trx_var = json_from_file_or_string(plain_signed_transaction_json); - } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse plain transaction JSON '${data}'", ("data", plain_signed_transaction_json)) + trx_var = json_from_file_or_string( plain_signed_transaction_json ); + } EOS_RETHROW_EXCEPTIONS( transaction_type_exception, "Fail to parse plain transaction JSON '${data}'", ("data", plain_signed_transaction_json)) if( pack_action_data_flag ) { signed_transaction trx; - abi_serializer::from_variant(trx_var, trx, abi_serializer_resolver, abi_serializer_max_time); - std::cout << fc::json::to_pretty_string( packed_transaction( trx, packed_transaction::none ) ) << std::endl; + abi_serializer::from_variant( trx_var, trx, abi_serializer_resolver, abi_serializer_max_time ); + std::cout << fc::json::to_pretty_string( packed_transaction( trx, packed_transaction::none )) << std::endl; } else { try { signed_transaction trx = trx_var.as(); @@ -1804,27 +1797,22 @@ int main( int argc, char** argv ) { bool unpack_action_data_flag = false; auto unpack_transaction = convert->add_subcommand("unpack_transaction", localized("From packed to plain signed json form")); unpack_transaction->add_option("transaction", packed_transaction_json, localized("The packed transaction json (string containing packed_trx and optionally compression fields)"))->required(); - unpack_transaction->add_flag("--unpack-action-data", unpack_action_data_flag, localized("Unpack all action datas within transaction, needs interaction with nodeos")); + unpack_transaction->add_flag("--unpack-action-data", unpack_action_data_flag, localized("Unpack all action data within transaction, needs interaction with nodeos")); unpack_transaction->set_callback([&] { - fc::variant trx_var; - try { - trx_var = json_from_file_or_string(packed_transaction_json); - } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse packed transaction JSON '${data}'", ("data", packed_transaction_json)) - string trx_hex; + fc::variant packed_trx_var; + packed_transaction packed_trx; try { - trx_hex = trx_var["packed_trx"].as_string(); - } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Missing packed_trx field in provided JSON '${data}'", ("data", packed_transaction_json)) - - std::vector trx_blob(trx_hex.size()/2); - fc::from_hex(trx_hex, trx_blob.data(), trx_blob.size()); - transaction unpacked_trx = fc::raw::unpack(trx_blob); - if (unpack_action_data_flag) { - abi_serializer::to_variant(unpacked_trx, trx_var, abi_serializer_resolver, abi_serializer_max_time); - std::cout << fc::json::to_pretty_string( trx_var ); + packed_trx_var = json_from_file_or_string( packed_transaction_json ); + fc::from_variant( packed_trx_var, packed_trx ); + } EOS_RETHROW_EXCEPTIONS( transaction_type_exception, "Fail to parse packed transaction JSON '${data}'", ("data", packed_transaction_json)) + signed_transaction strx = packed_trx.get_signed_transaction(); + fc::variant trx_var; + if( unpack_action_data_flag ) { + abi_serializer::to_variant( strx, trx_var, abi_serializer_resolver, abi_serializer_max_time ); } else { - // TODO should the "sigantures" and "context_free_data" fields be copied from trx_var into the action field(s) ? - std::cout << fc::json::to_pretty_string(fc::variant(unpacked_trx)) << std::endl; + trx_var = strx; } + std::cout << fc::json::to_pretty_string( trx_var ) << std::endl; }); // pack action data From eea29a489abdec5a9cc7bbc243eda27ff3109d80 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 14 Aug 2018 14:36:43 -0400 Subject: [PATCH 233/294] Verify packed_action_data specified --- programs/cleos/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 97af0248772..ac66444661d 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -1841,6 +1841,7 @@ int main( int argc, char** argv ) { unpack_action_data->add_option("name", packed_action_data_name_string, localized("The name of the function that's called by this action"))->required(); unpack_action_data->add_option("packed_action_data", packed_action_data_string, localized("The action data expressed as packed hex string"))->required(); unpack_action_data->set_callback([&] { + EOS_ASSERT( packed_action_data_string.size() >= 2, transaction_type_exception, "No packed_action_data found" ); vector packed_action_data_blob(packed_action_data_string.size()/2); fc::from_hex(packed_action_data_string, packed_action_data_blob.data(), packed_action_data_blob.size()); fc::variant unpacked_action_data_json = bin_to_variant(packed_action_data_account_string, packed_action_data_name_string, packed_action_data_blob); From 3c0a4fdf2ff6e00dcdeb86d1db3845d39e7aa371 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 14 Aug 2018 15:28:34 -0400 Subject: [PATCH 234/294] Add missing read-only command line doc --- plugins/chain_plugin/chain_plugin.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index bfbf34f320c..90c28e1ef6b 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -234,9 +234,11 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip ("key-blacklist", boost::program_options::value>()->composing()->multitoken(), "Public key added to blacklist of keys that should not be included in authorities (may specify multiple times)") ("read-mode", boost::program_options::value()->default_value(eosio::chain::db_read_mode::SPECULATIVE), - "Database read mode (\"speculative\" or \"head\").\n"// or \"irreversible\").\n" + "Database read mode (\"speculative\", \"head\", or \"read-only\").\n"// or \"irreversible\").\n" "In \"speculative\" mode database contains changes done up to the head block plus changes made by transactions not yet included to the blockchain.\n" - "In \"head\" mode database contains changes done up to the current head block.\n") + "In \"head\" mode database contains changes done up to the current head block.\n" + "In \"read-only\" mode database contains incoming block changes but no speculative transaction processing.\n" + ) //"In \"irreversible\" mode database contains changes done up the current irreversible block.\n") ("validation-mode", boost::program_options::value()->default_value(eosio::chain::validation_mode::FULL), "Chain validation mode (\"full\" or \"light\").\n" From 0aef827d06faabd696e6b08cb30c43ca908ee395 Mon Sep 17 00:00:00 2001 From: Eric Iles Date: Tue, 14 Aug 2018 15:59:42 -0400 Subject: [PATCH 235/294] Fix coverage pipeline --- .buildkite/coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.buildkite/coverage.yml b/.buildkite/coverage.yml index 012dd1e412f..908d3a3bf8d 100644 --- a/.buildkite/coverage.yml +++ b/.buildkite/coverage.yml @@ -1,7 +1,7 @@ steps: - command: | echo "--- :hammer: Building" && \ - /usr/bin/cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++-4.0 -DCMAKE_C_COMPILER=clang-4.0 -DWASM_ROOT=/root/opt/wasm -DOPENSSL_ROOT_DIR=/usr/include/openssl -DBUILD_MONGO_DB_PLUGIN=true -DENABLE_COVERAGE_TESTING=true -DBUILD_DOXYGEN=false && \ + /usr/bin/cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++-4.0 -DCMAKE_C_COMPILER=clang-4.0 -DBOOST_ROOT="${BOOST_ROOT}" -DWASM_ROOT="${WASM_ROOT}" -DOPENSSL_ROOT_DIR="${OPENSSL_ROOT_DIR}" -DBUILD_MONGO_DB_PLUGIN=true -DENABLE_COVERAGE_TESTING=true -DBUILD_DOXYGEN=false && \ /usr/bin/ninja echo "--- :spiral_note_pad: Generating Code Coverage Report" && \ /usr/bin/ninja EOSIO_ut_coverage && \ @@ -21,7 +21,7 @@ steps: mounts: - /etc/buildkite-agent/config:/config environment: - - BOOST_ROOT=/root/opt/boost_1_67_0 + - BOOST_ROOT=/root/opt/boost - OPENSSL_ROOT_DIR=/usr/include/openssl - WASM_ROOT=/root/opt/wasm - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/opt/wasm/bin From 6bd422e80f203d8103429090acfa4f68a43ce89b Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Tue, 14 Aug 2018 15:12:34 -0500 Subject: [PATCH 236/294] Increased timeout for abi serialization. GH #5199 --- tests/Cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index 3df4ccff04d..9da0a5172fb 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -129,7 +129,7 @@ def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", p2pPlugin="ne if self.staging: cmdArr.append("--nogen") - nodeosArgs="--max-transaction-time 50000 --abi-serializer-max-time-ms 50000 --filter-on * --p2p-max-nodes-per-host %d" % (totalNodes) + nodeosArgs="--max-transaction-time 50000 --abi-serializer-max-time-ms 65000 --filter-on * --p2p-max-nodes-per-host %d" % (totalNodes) if not self.walletd: nodeosArgs += " --plugin eosio::wallet_api_plugin" if self.enableMongo: From a05b74699136fa7c338ee50317a5cfe641134034 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 14 Aug 2018 16:52:44 -0400 Subject: [PATCH 237/294] Add support for unpacked action data to push transaction. Also adds standard transaction options to push transaction. --- programs/cleos/main.cpp | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index ac66444661d..fd2807abd9d 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2629,26 +2629,21 @@ int main( int argc, char** argv ) { string trx_to_push; auto trxSubcommand = push->add_subcommand("transaction", localized("Push an arbitrary JSON transaction")); trxSubcommand->add_option("transaction", trx_to_push, localized("The JSON string or filename defining the transaction to push"))->required(); - - trxSubcommand->add_flag("-d,--dont-broadcast", tx_dont_broadcast, localized("don't broadcast transaction to the network (just print to stdout, essentially only validates)")); - trxSubcommand->add_flag("--return-packed", tx_return_packed, localized("used in conjunction with --dont-broadcast to get the packed transaction")); + add_standard_transaction_options(trxSubcommand); trxSubcommand->set_callback([&] { fc::variant trx_var; try { trx_var = json_from_file_or_string(trx_to_push); } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse transaction JSON '${data}'", ("data",trx_to_push)) - signed_transaction trx = trx_var.as(); - - if (!tx_dont_broadcast) { - auto trx_result = call(push_txn_func, packed_transaction(trx, packed_transaction::none)); - std::cout << fc::json::to_pretty_string(trx_result) << std::endl; - } else { - if (tx_return_packed) { - std::cout << fc::json::to_pretty_string(fc::variant(packed_transaction(trx, packed_transaction::none))) << std::endl; - } else { - std::cout << fc::json::to_pretty_string(fc::variant(trx)) << std::endl; - } + try { + signed_transaction trx = trx_var.as(); + std::cout << fc::json::to_pretty_string( push_transaction( trx )) << std::endl; + } catch( fc::exception& ) { + // unable to convert so try via abi + signed_transaction trx; + abi_serializer::from_variant( trx_var, trx, abi_serializer_resolver, abi_serializer_max_time ); + std::cout << fc::json::to_pretty_string( push_transaction( trx )) << std::endl; } }); From 4c945706b03f812a9f90cc8c156589af6d609b41 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Tue, 14 Aug 2018 18:39:39 -0400 Subject: [PATCH 238/294] add linker flag for build-id in linux --- programs/nodeos/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/programs/nodeos/CMakeLists.txt b/programs/nodeos/CMakeLists.txt index 532749284ba..4410437cd2c 100644 --- a/programs/nodeos/CMakeLists.txt +++ b/programs/nodeos/CMakeLists.txt @@ -35,13 +35,16 @@ if(UNIX) if(APPLE) set(whole_archive_flag "-force_load") set(no_whole_archive_flag "") + set(build_id_flag "") else() set(whole_archive_flag "--whole-archive") set(no_whole_archive_flag "--no-whole-archive") + set(build_id_flag "--build-id") endif() else() set(whole_archive_flag "--whole-archive") set(no_whole_archive_flag "--no-whole-archive") + set(build_id_flag "") endif() target_link_libraries( ${NODE_EXECUTABLE_NAME} @@ -57,6 +60,7 @@ target_link_libraries( ${NODE_EXECUTABLE_NAME} PRIVATE -Wl,${whole_archive_flag} txn_test_gen_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} db_size_api_plugin -Wl,${no_whole_archive_flag} PRIVATE -Wl,${whole_archive_flag} producer_api_plugin -Wl,${no_whole_archive_flag} + PRIVATE -Wl,${build_id_flag} PRIVATE chain_plugin http_plugin producer_plugin http_client_plugin PRIVATE eosio_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) From 217e0319aded54bc6b5710a80b79b65384085b19 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Tue, 14 Aug 2018 18:47:06 -0400 Subject: [PATCH 239/294] bump version to 1.2.0 --- CMakeLists.txt | 4 ++-- Docker/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0733befb253..14d8798fdbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,8 +26,8 @@ set( CMAKE_CXX_EXTENSIONS ON ) set( CXX_STANDARD_REQUIRED ON) set(VERSION_MAJOR 1) -set(VERSION_MINOR 1) -set(VERSION_PATCH 4) +set(VERSION_MINOR 2) +set(VERSION_PATCH 0) set( CLI_CLIENT_EXECUTABLE_NAME cleos ) set( NODE_EXECUTABLE_NAME nodeos ) diff --git a/Docker/README.md b/Docker/README.md index d5489a58584..db1e9d897fc 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -20,10 +20,10 @@ cd eos/Docker docker build . -t eosio/eos ``` -The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the v1.1.4 tag, you could do the following: +The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the v1.2.0 tag, you could do the following: ```bash -docker build -t eosio/eos:v1.1.4 --build-arg branch=v1.1.4 . +docker build -t eosio/eos:v1.2.0 --build-arg branch=v1.2.0 . ``` By default, the symbol in eosio.system is set to SYS. You can override this using the symbol argument while building the docker image. From 9c754ab418a79c25ab4a41b77c5c43e13cb7203f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 14 Aug 2018 19:10:56 -0400 Subject: [PATCH 240/294] None of the smoke test depend on normal abi-serializer timeouts, increase for slow servers. --- tests/Cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Cluster.py b/tests/Cluster.py index 9da0a5172fb..73be2edde19 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -129,7 +129,7 @@ def launch(self, pnodes=1, totalNodes=1, prodCount=1, topo="mesh", p2pPlugin="ne if self.staging: cmdArr.append("--nogen") - nodeosArgs="--max-transaction-time 50000 --abi-serializer-max-time-ms 65000 --filter-on * --p2p-max-nodes-per-host %d" % (totalNodes) + nodeosArgs="--max-transaction-time 50000 --abi-serializer-max-time-ms 990000 --filter-on * --p2p-max-nodes-per-host %d" % (totalNodes) if not self.walletd: nodeosArgs += " --plugin eosio::wallet_api_plugin" if self.enableMongo: From 4965bc91aa9ed0eaf720d3f5b1dda62df0b9257f Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Tue, 14 Aug 2018 19:12:28 -0400 Subject: [PATCH 241/294] #53d5d60 jgiszczak (Allow eosiocpp to run without make install) --- tools/eosiocpp.in | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/eosiocpp.in b/tools/eosiocpp.in index dea17231715..1002a704c7b 100755 --- a/tools/eosiocpp.in +++ b/tools/eosiocpp.in @@ -63,6 +63,7 @@ function build_contract { -DBOOST_DISABLE_ASSERTS -DBOOST_EXCEPTION_DISABLE \ -nostdlib -nostdlibinc -ffreestanding -nostdlib \ -fno-threadsafe-statics -fno-rtti -fno-exceptions \ + -I @CMAKE_BINARY_DIR@/contracts \ -I @CMAKE_SOURCE_DIR@/contracts \ -I @CMAKE_SOURCE_DIR@/contracts/libc++/upstream/include \ -I @CMAKE_SOURCE_DIR@/contracts/musl/upstream/include \ From 1bba310abeedbb4e9c25152324d114fe8b6723eb Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Tue, 14 Aug 2018 21:03:16 -0400 Subject: [PATCH 242/294] changes to the read-write api meant for async calls we were holding a stale reference. Reverted to the captured and therfore live object and moved the assert to a validate call which is called just-in-time --- plugins/chain_api_plugin/chain_api_plugin.cpp | 8 +++++--- plugins/chain_plugin/chain_plugin.cpp | 3 +++ .../include/eosio/chain_plugin/chain_plugin.hpp | 3 +++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/chain_api_plugin/chain_api_plugin.cpp b/plugins/chain_api_plugin/chain_api_plugin.cpp index 7fba0b3a91e..58098501f04 100644 --- a/plugins/chain_api_plugin/chain_api_plugin.cpp +++ b/plugins/chain_api_plugin/chain_api_plugin.cpp @@ -38,6 +38,7 @@ struct async_result_visitor : public fc::visitor { #define CALL(api_name, api_handle, api_namespace, call_name, http_response_code) \ {std::string("/v1/" #api_name "/" #call_name), \ [this, api_handle](string, string body, url_response_callback cb) mutable { \ + api_handle.validate(); \ try { \ if (body.empty()) body = "{}"; \ auto result = api_handle.call_name(fc::json::from_string(body).as()); \ @@ -49,8 +50,9 @@ struct async_result_visitor : public fc::visitor { #define CALL_ASYNC(api_name, api_handle, api_namespace, call_name, call_result, http_response_code) \ {std::string("/v1/" #api_name "/" #call_name), \ - [this](string, string body, url_response_callback cb) mutable { \ + [this, api_handle](string, string body, url_response_callback cb) mutable { \ if (body.empty()) body = "{}"; \ + api_handle.validate(); \ api_handle.call_name(fc::json::from_string(body).as(),\ [cb, body](const fc::static_variant& result){\ if (result.contains()) {\ @@ -66,16 +68,16 @@ struct async_result_visitor : public fc::visitor { }\ } -#define CHAIN_RW_API app().get_plugin().get_read_write_api() #define CHAIN_RO_CALL(call_name, http_response_code) CALL(chain, ro_api, chain_apis::read_only, call_name, http_response_code) #define CHAIN_RW_CALL(call_name, http_response_code) CALL(chain, rw_api, chain_apis::read_write, call_name, http_response_code) #define CHAIN_RO_CALL_ASYNC(call_name, call_result, http_response_code) CALL_ASYNC(chain, ro_api, chain_apis::read_only, call_name, call_result, http_response_code) -#define CHAIN_RW_CALL_ASYNC(call_name, call_result, http_response_code) CALL_ASYNC(chain, (CHAIN_RW_API), chain_apis::read_write, call_name, call_result, http_response_code) +#define CHAIN_RW_CALL_ASYNC(call_name, call_result, http_response_code) CALL_ASYNC(chain, rw_api, chain_apis::read_write, call_name, call_result, http_response_code) void chain_api_plugin::plugin_startup() { ilog( "starting chain_api_plugin" ); my.reset(new chain_api_plugin_impl(app().get_plugin().chain())); auto ro_api = app().get_plugin().get_read_only_api(); + auto rw_api = app().get_plugin().get_read_write_api(); app().get_plugin().add_api({ CHAIN_RO_CALL(get_info, 200l), diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 90c28e1ef6b..11b62273808 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -666,6 +666,9 @@ chain_apis::read_write::read_write(controller& db, const fc::microseconds& abi_s : db(db) , abi_serializer_max_time(abi_serializer_max_time) { +} + +void chain_apis::read_write::validate() const { EOS_ASSERT( db.get_read_mode() != chain::db_read_mode::READ_ONLY, missing_chain_api_plugin_exception, "Not allowed, node in read-only mode" ); } diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index 24b2b35bd6a..2fd665d6255 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -75,6 +75,8 @@ class read_only { read_only(const controller& db, const fc::microseconds& abi_serializer_max_time) : db(db), abi_serializer_max_time(abi_serializer_max_time) {} + void validate() const {} + using get_info_params = empty; struct get_info_results { @@ -474,6 +476,7 @@ class read_write { const fc::microseconds abi_serializer_max_time; public: read_write(controller& db, const fc::microseconds& abi_serializer_max_time); + void validate() const; using push_block_params = chain::signed_block; using push_block_results = empty; From 9cdc1699547221e8a8f80f844e2ae1cdb07d29c7 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Wed, 15 Aug 2018 12:56:48 +0000 Subject: [PATCH 243/294] fix typo --- contracts/eosiolib/types.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/eosiolib/types.h b/contracts/eosiolib/types.h index 32448f99141..120c896cd87 100644 --- a/contracts/eosiolib/types.h +++ b/contracts/eosiolib/types.h @@ -27,13 +27,13 @@ typedef uint64_t account_name; /** * @brief Name of a permission - * @details Name of an account + * @details Name of a permission */ typedef uint64_t permission_name; /** * @brief Name of a table - * @details Name of atable + * @details Name of a table */ typedef uint64_t table_name; From cc7aba178592d46391dc98adb812d8f42b750133 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Wed, 15 Aug 2018 14:34:33 +0000 Subject: [PATCH 244/294] Remove unused variable --- programs/cleos/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index fd2807abd9d..7e0782332b5 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2350,7 +2350,6 @@ int main( int argc, char** argv ) { add_standard_transaction_options(transfer, "sender@active"); transfer->set_callback([&] { - signed_transaction trx; if (tx_force_unique && memo.size() == 0) { // use the memo to add a nonce memo = generate_nonce_string(); From b8f88dafe9bd791883d62d31f3116caeee2d1772 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 15 Aug 2018 12:25:24 -0400 Subject: [PATCH 245/294] Fix for nullptr deref when producers are waiting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EOSIO/eos#4972 created a different calling path into `producer_plugin_impl::calculate_next_block_time` where there was no pending block state. As a result when the following conditions were met the code would dereference a null ptr: * the node was a producer, who _had_ produced during this processes lifetime OR replayed a block where they produced (so they have watermark data) * still in the active schedule of producers * experienced a gap in blocks of > 5 seconds (so they moved to our “waiting” mode) Credit goes to @heifner for the find --- plugins/producer_plugin/producer_plugin.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 64a6a5d8cd2..3997c997653 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -831,10 +831,13 @@ optional producer_plugin_impl::calculate_next_block_time(const a auto current_watermark_itr = _producer_watermarks.find(producer_name); if (current_watermark_itr != _producer_watermarks.end()) { auto watermark = current_watermark_itr->second; - const auto& pbs = chain.pending_block_state(); - if (watermark > pbs->block_num) { + auto block_num = chain.head_block_state()->block_num; + if (chain.pending_block_state()) { + ++block_num; + } + if (watermark > block_num) { // if I have a watermark then I need to wait until after that watermark - minimum_offset = watermark - pbs->block_num + 1; + minimum_offset = watermark - block_num + 1; } } From c3384332d5b26557c31ba2fe9a8392b654f67823 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 15 Aug 2018 12:25:24 -0400 Subject: [PATCH 246/294] Fix for nullptr deref when producers are waiting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EOSIO/eos#4972 created a different calling path into `producer_plugin_impl::calculate_next_block_time` where there was no pending block state. As a result when the following conditions were met the code would dereference a null ptr: * the node was a producer, who _had_ produced during this processes lifetime OR replayed a block where they produced (so they have watermark data) * still in the active schedule of producers * experienced a gap in blocks of > 5 seconds (so they moved to our “waiting” mode) Credit goes to @heifner for the find --- plugins/producer_plugin/producer_plugin.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 64a6a5d8cd2..3997c997653 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -831,10 +831,13 @@ optional producer_plugin_impl::calculate_next_block_time(const a auto current_watermark_itr = _producer_watermarks.find(producer_name); if (current_watermark_itr != _producer_watermarks.end()) { auto watermark = current_watermark_itr->second; - const auto& pbs = chain.pending_block_state(); - if (watermark > pbs->block_num) { + auto block_num = chain.head_block_state()->block_num; + if (chain.pending_block_state()) { + ++block_num; + } + if (watermark > block_num) { // if I have a watermark then I need to wait until after that watermark - minimum_offset = watermark - pbs->block_num + 1; + minimum_offset = watermark - block_num + 1; } } From d1267a88f4de6b881e748a05c2eb49a90aef5d37 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Wed, 15 Aug 2018 14:08:00 -0400 Subject: [PATCH 247/294] Warn when trying to set code with something non-wasm Add a warning when set code/contract detects an apparent non-wasm being used (like wast) --- programs/cleos/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index fd2807abd9d..1451cc20166 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2282,6 +2282,10 @@ int main( int argc, char** argv ) { fc::read_file_contents(wasmPath, wasm); EOS_ASSERT( !wasm.empty(), wast_file_not_found, "no wasm file found ${f}", ("f", wasmPath) ); + const string binary_wasm_header("\x00\x61\x73\x6d\x01\x00\x00\x00", 8); + if(wasm.compare(0, 8, binary_wasm_header)) + std::cerr << localized("WARNING: ") << wasmPath << localized(" doesn't look like a binary WASM file. Is it something else, like WAST? Trying anyways...") << std::endl; + actions.emplace_back( create_setcode(account, bytes(wasm.begin(), wasm.end()) ) ); if ( shouldSend ) { std::cerr << localized("Setting Code...") << std::endl; From 07eb07df9de34cfb54fafa14797cebb41b469ab4 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 15 Aug 2018 16:02:46 -0400 Subject: [PATCH 248/294] bump version to 1.2.1 --- CMakeLists.txt | 2 +- Docker/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 14d8798fdbf..c374afa42ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ set( CXX_STANDARD_REQUIRED ON) set(VERSION_MAJOR 1) set(VERSION_MINOR 2) -set(VERSION_PATCH 0) +set(VERSION_PATCH 1) set( CLI_CLIENT_EXECUTABLE_NAME cleos ) set( NODE_EXECUTABLE_NAME nodeos ) diff --git a/Docker/README.md b/Docker/README.md index db1e9d897fc..d1d38924bdb 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -20,10 +20,10 @@ cd eos/Docker docker build . -t eosio/eos ``` -The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the v1.2.0 tag, you could do the following: +The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the v1.2.1 tag, you could do the following: ```bash -docker build -t eosio/eos:v1.2.0 --build-arg branch=v1.2.0 . +docker build -t eosio/eos:v1.2.1 --build-arg branch=v1.2.1 . ``` By default, the symbol in eosio.system is set to SYS. You can override this using the symbol argument while building the docker image. From 6f2614630a447f89766fdabaa412039e4c2a92ea Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 15 Aug 2018 16:43:32 -0400 Subject: [PATCH 249/294] Return transaction_trace even if unable to serialize the action_trace action data --- plugins/chain_plugin/chain_plugin.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 11b62273808..9fcc72928df 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1404,11 +1404,15 @@ void read_write::push_transaction(const read_write::push_transaction_params& par auto trx_trace_ptr = result.get(); try { - fc::variant pretty_output; - pretty_output = db.to_variant_with_abi(*trx_trace_ptr, abi_serializer_max_time); - chain::transaction_id_type id = trx_trace_ptr->id; - next(read_write::push_transaction_results{id, pretty_output}); + fc::variant output; + try { + output = db.to_variant_with_abi( *trx_trace_ptr, abi_serializer_max_time ); + } catch( chain::abi_exception& ) { + output = *trx_trace_ptr; + } + + next(read_write::push_transaction_results{id, output}); } CATCH_AND_CALL(next); } }); From b2692773e1bf9c384ae8a63686b2a97a55deb4b2 Mon Sep 17 00:00:00 2001 From: Eric Iles Date: Wed, 15 Aug 2018 16:51:21 -0400 Subject: [PATCH 250/294] Fix eosiocpp issues --- Docker/dev/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Docker/dev/Dockerfile b/Docker/dev/Dockerfile index 1689d458e07..2b7bf84987f 100644 --- a/Docker/dev/Dockerfile +++ b/Docker/dev/Dockerfile @@ -7,8 +7,7 @@ RUN git clone -b $branch https://github.com/EOSIO/eos.git --recursive \ && cmake -H. -B"/opt/eosio" -GNinja -DCMAKE_BUILD_TYPE=Release -DWASM_ROOT=/opt/wasm -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_C_COMPILER=clang -DCMAKE_INSTALL_PREFIX=/opt/eosio -DSecp256k1_ROOT_DIR=/usr/local -DBUILD_MONGO_DB_PLUGIN=true -DCORE_SYMBOL_NAME=$symbol \ && cmake --build /opt/eosio --target install \ - && mv /eos/Docker/config.ini / && mv /opt/eosio/contracts /contracts && mv /eos/Docker/nodeosd.sh /opt/eosio/bin/nodeosd.sh && mv tutorials /tutorials \ - && rm -rf /eos + && cp /eos/Docker/config.ini / && ln -s /opt/eosio/contracts /contracts && cp /eos/Docker/nodeosd.sh /opt/eosio/bin/nodeosd.sh && ln -s /eos/tutorials /tutorials RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install openssl ca-certificates vim psmisc python3-pip && rm -rf /var/lib/apt/lists/* RUN pip3 install numpy From 913461b45879488e31483b9e8558fc7f1c287029 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 16 Aug 2018 10:19:24 -0400 Subject: [PATCH 251/294] Fix access to pending_block_state when not available. Also cleanup some logging. --- plugins/producer_plugin/producer_plugin.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 3997c997653..22ef9c692e5 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -931,8 +931,10 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block(bool if (_pending_block_mode == pending_block_mode::speculating) { auto head_block_age = now - chain.head_block_time(); - if (head_block_age > fc::seconds(5)) + if (head_block_age > fc::seconds(5)) { + fc_dlog(_log, "Greater than 5 secs behind, waiting"); return start_block_result::waiting; + } } try { @@ -1166,28 +1168,37 @@ void producer_plugin_impl::schedule_production_loop() { static const boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); if (result == start_block_result::succeeded) { // ship this block off no later than its deadline - _timer.expires_at(epoch + boost::posix_time::microseconds(chain.pending_block_time().time_since_epoch().count() + (last_block ? _last_block_time_offset_us : _produce_time_offset_us))); - fc_dlog(_log, "Scheduling Block Production on Normal Block #${num} for ${time}", ("num", chain.pending_block_state()->block_num)("time",chain.pending_block_time())); + EOS_ASSERT( chain.pending_block_state(), missing_pending_block_state, "producing without pending_block_state, start_block succeeded" ); + auto deadline = chain.pending_block_time().time_since_epoch().count() + (last_block ? _last_block_time_offset_us : _produce_time_offset_us); + _timer.expires_at( epoch + boost::posix_time::microseconds( deadline )); + fc_dlog(_log, "Scheduling Block Production on Normal Block #${num} for ${time}", ("num", chain.pending_block_state()->block_num)("time",deadline)); } else { + EOS_ASSERT( chain.pending_block_state(), missing_pending_block_state, "producing without pending_block_state" ); auto expect_time = chain.pending_block_time() - fc::microseconds(config::block_interval_us); // ship this block off up to 1 block time earlier or immediately if (fc::time_point::now() >= expect_time) { _timer.expires_from_now( boost::posix_time::microseconds( 0 )); + fc_dlog(_log, "Scheduling Block Production on Exhausted Block #${num} immediately", ("num", chain.pending_block_state()->block_num)); } else { _timer.expires_at(epoch + boost::posix_time::microseconds(expect_time.time_since_epoch().count())); + fc_dlog(_log, "Scheduling Block Production on Exhausted Block #${num} at ${time}", ("num", chain.pending_block_state()->block_num)("time",expect_time)); } - fc_dlog(_log, "Scheduling Block Production on Exhausted Block #${num} immediately", ("num", chain.pending_block_state()->block_num)); } _timer.async_wait([&chain,weak_this,cid=++_timer_corelation_id](const boost::system::error_code& ec) { auto self = weak_this.lock(); if (self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id) { auto res = self->maybe_produce_block(); - fc_dlog(_log, "Producing Block #${num} returned: ${res}", ("num", chain.pending_block_state()->block_num)("res", res) ); + if( chain.pending_block_state() ) { + fc_dlog( _log, "Producing Block #${num} returned: ${res}", ("num", chain.pending_block_state()->block_num)( "res", res )); + } else { + fc_dlog( _log, "Producing Block, head block #${num} returned: ${res}", ("num", chain.head_block_state()->block_num)( "res", res )); + } } }); } else if (_pending_block_mode == pending_block_mode::speculating && !_producers.empty() && !production_disabled_by_policy()){ fc_dlog(_log, "Specualtive Block Created; Scheduling Speculative/Production Change"); + EOS_ASSERT( chain.pending_block_state(), missing_pending_block_state, "speculating without pending_block_state" ); const auto& pbs = chain.pending_block_state(); schedule_delayed_production_loop(weak_this, pbs->header.timestamp); } else { From 2075acd356b278b5e9d99de8db39c6dc3a027631 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 16 Aug 2018 10:42:41 -0400 Subject: [PATCH 252/294] Remove redundant log output --- plugins/producer_plugin/producer_plugin.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 22ef9c692e5..02c9b16894a 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -931,10 +931,8 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block(bool if (_pending_block_mode == pending_block_mode::speculating) { auto head_block_age = now - chain.head_block_time(); - if (head_block_age > fc::seconds(5)) { - fc_dlog(_log, "Greater than 5 secs behind, waiting"); + if (head_block_age > fc::seconds(5)) return start_block_result::waiting; - } } try { From ea784b9a8c271ae7665b337cc1153502ccc92b5f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 16 Aug 2018 11:20:05 -0400 Subject: [PATCH 253/294] Log pending block number --- plugins/producer_plugin/producer_plugin.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 02c9b16894a..063558fef93 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -1186,12 +1186,10 @@ void producer_plugin_impl::schedule_production_loop() { _timer.async_wait([&chain,weak_this,cid=++_timer_corelation_id](const boost::system::error_code& ec) { auto self = weak_this.lock(); if (self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id) { + // pending_block_state expected, but can't assert inside async_wait + auto block_num = chain.pending_block_state() ? chain.pending_block_state()->block_num : 0; auto res = self->maybe_produce_block(); - if( chain.pending_block_state() ) { - fc_dlog( _log, "Producing Block #${num} returned: ${res}", ("num", chain.pending_block_state()->block_num)( "res", res )); - } else { - fc_dlog( _log, "Producing Block, head block #${num} returned: ${res}", ("num", chain.head_block_state()->block_num)( "res", res )); - } + fc_dlog( _log, "Producing Block #${num} returned: ${res}", ("num", chain.pending_block_state()->block_num)( "res", res )); } }); } else if (_pending_block_mode == pending_block_mode::speculating && !_producers.empty() && !production_disabled_by_policy()){ From e55de56185796511cb4e924bc32a69dc67b171b4 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 16 Aug 2018 11:21:36 -0400 Subject: [PATCH 254/294] Log correct value --- plugins/producer_plugin/producer_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 063558fef93..0200eb8f8ba 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -1189,7 +1189,7 @@ void producer_plugin_impl::schedule_production_loop() { // pending_block_state expected, but can't assert inside async_wait auto block_num = chain.pending_block_state() ? chain.pending_block_state()->block_num : 0; auto res = self->maybe_produce_block(); - fc_dlog( _log, "Producing Block #${num} returned: ${res}", ("num", chain.pending_block_state()->block_num)( "res", res )); + fc_dlog(_log, "Producing Block #${num} returned: ${res}", ("num", block_num)("res", res)); } }); } else if (_pending_block_mode == pending_block_mode::speculating && !_producers.empty() && !production_disabled_by_policy()){ From 75e99bf2ccc625ffefa00e671d515f5efe9be2e4 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 16 Aug 2018 15:29:03 -0400 Subject: [PATCH 255/294] Use transaction delay_sec in get_required_keys determination --- plugins/chain_plugin/chain_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 11b62273808..45ee6c522de 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1664,7 +1664,7 @@ read_only::get_required_keys_result read_only::get_required_keys( const get_requ abi_serializer::from_variant(params.transaction, pretty_input, resolver, abi_serializer_max_time); } EOS_RETHROW_EXCEPTIONS(chain::transaction_type_exception, "Invalid transaction") - auto required_keys_set = db.get_authorization_manager().get_required_keys(pretty_input, params.available_keys); + auto required_keys_set = db.get_authorization_manager().get_required_keys(pretty_input, params.available_keys, pretty_input.delay_sec); get_required_keys_result result; result.required_keys = required_keys_set; return result; From aa6f4efd50a0107ed718731e851968c27e8b47a4 Mon Sep 17 00:00:00 2001 From: jjnetcn Date: Fri, 17 Aug 2018 16:00:42 +0800 Subject: [PATCH 256/294] cleos set account permission default parent error RPC /get_account use struct get_account_results { .. vector permissions; .. } struct permission { name perm_name; name parent; authority required_auth; }; i thank this mistake maybe because of struct permission_level{account_name actor, permission_name permission;} use name 'permission' try it: ./cleos set account permission ACCOUNT active PUBKEY shoud equ ./cleos set account permission ACCOUNT active owner PUBKEY --- programs/cleos/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 1451cc20166..bcc6e6dddca 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -681,8 +681,8 @@ struct set_account_permission_subcommand { const auto& existing_permissions = account_result.get_object()["permissions"].get_array(); auto permissionPredicate = [this](const auto& perm) { return perm.is_object() && - perm.get_object().contains("permission") && - boost::equals(perm.get_object()["permission"].get_string(), permissionStr); + perm.get_object().contains("perm_name") && + boost::equals(perm.get_object()["perm_name"].get_string(), permissionStr); }; auto itr = boost::find_if(existing_permissions, permissionPredicate); From 72904cc945cac8b4d973548091e5d422b27f5567 Mon Sep 17 00:00:00 2001 From: Eric Iles Date: Fri, 17 Aug 2018 11:23:22 -0400 Subject: [PATCH 257/294] Update Docker plugin to 1.4.0 - pipeline.yml --- .buildkite/pipeline.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 16163b09a3a..c4ba85ae46e 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -22,7 +22,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu" workdir: /data/job timeout: 60 @@ -37,7 +37,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu18" workdir: /data/job timeout: 60 @@ -52,7 +52,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:fedora" workdir: /data/job timeout: 60 @@ -67,7 +67,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:centos" workdir: /data/job timeout: 60 @@ -82,7 +82,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:amazonlinux" workdir: /data/job timeout: 60 @@ -128,7 +128,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu" workdir: /data/job timeout: 60 @@ -152,7 +152,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu18" workdir: /data/job timeout: 60 @@ -176,7 +176,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:fedora" workdir: /data/job timeout: 60 @@ -200,7 +200,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:centos" workdir: /data/job timeout: 60 @@ -224,7 +224,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:amazonlinux" workdir: /data/job timeout: 60 From 10d5a45c82fdc66cc3e29e44a85ff5b0aabfe351 Mon Sep 17 00:00:00 2001 From: Eric Iles Date: Fri, 17 Aug 2018 11:25:10 -0400 Subject: [PATCH 258/294] Update Docker plugin - Long Running Tests --- .buildkite/long_running_tests.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.buildkite/long_running_tests.yml b/.buildkite/long_running_tests.yml index 28a456db1ec..d4bc7244193 100644 --- a/.buildkite/long_running_tests.yml +++ b/.buildkite/long_running_tests.yml @@ -22,7 +22,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu" workdir: /data/job timeout: 60 @@ -37,7 +37,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu18" workdir: /data/job timeout: 60 @@ -52,7 +52,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:fedora" workdir: /data/job timeout: 60 @@ -67,7 +67,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:centos" workdir: /data/job timeout: 60 @@ -82,7 +82,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:amazonlinux" workdir: /data/job timeout: 60 @@ -128,7 +128,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu" workdir: /data/job timeout: 60 @@ -152,7 +152,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu18" workdir: /data/job timeout: 60 @@ -176,7 +176,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:fedora" workdir: /data/job timeout: 60 @@ -200,7 +200,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:centos" workdir: /data/job timeout: 60 @@ -224,7 +224,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:amazonlinux" workdir: /data/job timeout: 60 From 2652fc6edb8b6a729567dae9ffb75dcf660dc2be Mon Sep 17 00:00:00 2001 From: Eric Iles Date: Fri, 17 Aug 2018 11:27:04 -0400 Subject: [PATCH 259/294] Update Docker Plugin - Debug --- .buildkite/debug.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.buildkite/debug.yml b/.buildkite/debug.yml index 8d7aaa7c502..d6f95814f54 100644 --- a/.buildkite/debug.yml +++ b/.buildkite/debug.yml @@ -22,7 +22,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu" workdir: /data/job timeout: 60 @@ -37,7 +37,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu18" workdir: /data/job timeout: 60 @@ -52,7 +52,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:fedora" workdir: /data/job timeout: 60 @@ -67,7 +67,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:centos" workdir: /data/job timeout: 60 @@ -82,7 +82,7 @@ steps: - "role=linux-builder" artifact_paths: "build.tar.gz" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:amazonlinux" workdir: /data/job timeout: 60 @@ -128,7 +128,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu" workdir: /data/job timeout: 60 @@ -152,7 +152,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu18" workdir: /data/job timeout: 60 @@ -176,7 +176,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:fedora" workdir: /data/job timeout: 60 @@ -200,7 +200,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:centos" workdir: /data/job timeout: 60 @@ -224,7 +224,7 @@ steps: - "build/genesis.json" - "build/config.ini" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:amazonlinux" workdir: /data/job timeout: 60 From 25e4884e63f6a7d14676d3e028f297c2428b12dc Mon Sep 17 00:00:00 2001 From: Eric Iles Date: Fri, 17 Aug 2018 11:28:00 -0400 Subject: [PATCH 260/294] Update Docker Plugin - Coverage --- .buildkite/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/coverage.yml b/.buildkite/coverage.yml index 908d3a3bf8d..bbb8fa91387 100644 --- a/.buildkite/coverage.yml +++ b/.buildkite/coverage.yml @@ -15,7 +15,7 @@ steps: agents: - "role=linux-coverage" plugins: - docker#v1.1.1: + docker#v1.4.0: image: "eosio/ci:ubuntu18" workdir: /data/job mounts: From 5cc9a1dec300e310107043cff885fb5e6465d3e7 Mon Sep 17 00:00:00 2001 From: chengevo Date: Fri, 17 Aug 2018 19:35:27 +0800 Subject: [PATCH 261/294] MongoDB plugin filter based on action trace --- plugins/mongo_db_plugin/mongo_db_plugin.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/mongo_db_plugin/mongo_db_plugin.cpp b/plugins/mongo_db_plugin/mongo_db_plugin.cpp index cf0e4d1e9b3..c4d11e1bdee 100644 --- a/plugins/mongo_db_plugin/mongo_db_plugin.cpp +++ b/plugins/mongo_db_plugin/mongo_db_plugin.cpp @@ -104,7 +104,7 @@ class mongo_db_plugin_impl { void remove_account_control( const account_name& name, const permission_name& permission ); /// @return true if act should be added to mongodb, false to skip it - bool filter_include( const chain::action& act ) const; + bool filter_include( const chain::action_trace& action_trace ) const; void init(); void wipe_database(); @@ -200,13 +200,13 @@ const std::string mongo_db_plugin_impl::accounts_col = "accounts"; const std::string mongo_db_plugin_impl::pub_keys_col = "pub_keys"; const std::string mongo_db_plugin_impl::account_controls_col = "account_controls"; -bool mongo_db_plugin_impl::filter_include( const chain::action& act ) const { +bool mongo_db_plugin_impl::filter_include( const chain::action_trace& action_trace ) const { bool include = false; - if( filter_on_star || filter_on.find( {act.account, act.name, 0} ) != filter_on.end() ) { + if( filter_on_star || filter_on.find( {action_trace.receipt.receiver, action_trace.act.name, 0} ) != filter_on.end() ) { include = true; } else { - for( const auto& a : act.authorization ) { - if( filter_on.find( {act.account, act.name, a.actor} ) != filter_on.end() ) { + for( const auto& a : action_trace.act.authorization ) { + if( filter_on.find( {action_trace.receipt.receiver, action_trace.act.name, a.actor} ) != filter_on.end() ) { include = true; break; } @@ -215,14 +215,14 @@ bool mongo_db_plugin_impl::filter_include( const chain::action& act ) const { if( !include ) { return false; } - if( filter_out.find( {act.account, 0, 0} ) != filter_out.end() ) { + if( filter_out.find( {action_trace.receipt.receiver, 0, 0} ) != filter_out.end() ) { return false; } - if( filter_out.find( {act.account, act.name, 0} ) != filter_out.end() ) { + if( filter_out.find( {action_trace.receipt.receiver, action_trace.act.name, 0} ) != filter_out.end() ) { return false; } - for( const auto& a : act.authorization ) { - if( filter_out.find( {act.account, act.name, a.actor} ) != filter_out.end() ) { + for( const auto& a : action_trace.act.authorization ) { + if( filter_out.find( {action_trace.receipt.receiver, action_trace.act.name, a.actor} ) != filter_out.end() ) { return false; } } @@ -714,7 +714,7 @@ mongo_db_plugin_impl::add_action_trace( mongocxx::bulk_write& bulk_action_traces } bool added = false; - if( start_block_reached && store_action_traces && filter_include( atrace.act ) ) { + if( start_block_reached && store_action_traces && filter_include( atrace ) ) { auto action_traces_doc = bsoncxx::builder::basic::document{}; const chain::base_action_trace& base = atrace; // without inline action traces From 5dd1bc1229eca79c28667c004043777a45d68902 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 17 Aug 2018 14:48:10 -0400 Subject: [PATCH 262/294] Correct conversion to microseconds --- plugins/chain_plugin/chain_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 45ee6c522de..37ef3aea3fd 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1664,7 +1664,7 @@ read_only::get_required_keys_result read_only::get_required_keys( const get_requ abi_serializer::from_variant(params.transaction, pretty_input, resolver, abi_serializer_max_time); } EOS_RETHROW_EXCEPTIONS(chain::transaction_type_exception, "Invalid transaction") - auto required_keys_set = db.get_authorization_manager().get_required_keys(pretty_input, params.available_keys, pretty_input.delay_sec); + auto required_keys_set = db.get_authorization_manager().get_required_keys( pretty_input, params.available_keys, fc::seconds( pretty_input.delay_sec )); get_required_keys_result result; result.required_keys = required_keys_set; return result; From 4ff1defefdaa7d72a49eeb0a9f865507cc62785a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 17 Aug 2018 14:49:57 -0400 Subject: [PATCH 263/294] Update to safer unsigned_int --- libraries/fc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fc b/libraries/fc index a6b2756b100..62a19a75868 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit a6b2756b100098296f7548a191e6210e770b7b3a +Subproject commit 62a19a758682679e3de27d956986eaf8b016465d From 322d2cfdfdccda55f7417476463334837f04a35c Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Tue, 21 Aug 2018 14:02:54 -0400 Subject: [PATCH 264/294] Disallow returning WAST from get_code api --- plugins/chain_plugin/chain_plugin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 11b62273808..1abd7aa0294 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1472,6 +1472,8 @@ read_only::get_code_results read_only::get_code( const get_code_params& params ) const auto& d = db.db(); const auto& accnt = d.get( params.account_name ); + EOS_ASSERT( params.code_as_wasm, unsupported_feature, "Returning WAST from get_code is no longer supported" ); + if( accnt.code.size() ) { if (params.code_as_wasm) { result.wasm = string(accnt.code.begin(), accnt.code.end()); From 364fcc75edb30c80a1f0dae39b1d26f5d6c93a4d Mon Sep 17 00:00:00 2001 From: Todd Fleming Date: Tue, 21 Aug 2018 15:49:19 -0400 Subject: [PATCH 265/294] Compat fixes for bios-boot-tutorial --- tutorials/bios-boot-tutorial/bios-boot-tutorial.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tutorials/bios-boot-tutorial/bios-boot-tutorial.py b/tutorials/bios-boot-tutorial/bios-boot-tutorial.py index 11aee4e34c1..88821bfe21c 100755 --- a/tutorials/bios-boot-tutorial/bios-boot-tutorial.py +++ b/tutorials/bios-boot-tutorial/bios-boot-tutorial.py @@ -74,7 +74,7 @@ def startWallet(): run('mkdir -p ' + os.path.abspath(args.wallet_dir)) background(args.keosd + ' --unlock-timeout %d --http-server-address 127.0.0.1:6666 --wallet-dir %s' % (unlockTimeout, os.path.abspath(args.wallet_dir))) sleep(.4) - run(args.cleos + 'wallet create') + run(args.cleos + 'wallet create --to-console') def importKeys(): run(args.cleos + 'wallet import --private-key ' + args.private_key) @@ -347,7 +347,7 @@ def stepLog(): parser.add_argument('--public-key', metavar='', help="EOSIO Public Key", default='EOS8Znrtgwt8TfpmbVpTKvA2oB8Nqey625CLN8bCN3TEbgx86Dsvr', dest="public_key") parser.add_argument('--private-Key', metavar='', help="EOSIO Private Key", default='5K463ynhZoCDDa4RDcr63cUwWLTnKqmdcoTKTHBjqoKfv4u5V7p', dest="private_key") -parser.add_argument('--cleos', metavar='', help="Cleos command", default='../../build/programs/cleos/cleos --wallet-url http://localhost:6666 ') +parser.add_argument('--cleos', metavar='', help="Cleos command", default='../../build/programs/cleos/cleos --wallet-url http://127.0.0.1:6666 ') parser.add_argument('--nodeos', metavar='', help="Path to nodeos binary", default='../../build/programs/nodeos/nodeos') parser.add_argument('--keosd', metavar='', help="Path to keosd binary", default='../../build/programs/keosd/keosd') parser.add_argument('--contracts-dir', metavar='', help="Path to contracts directory", default='../../build/contracts/') @@ -381,7 +381,7 @@ def stepLog(): args = parser.parse_args() -args.cleos += '--url http://localhost:%d ' % args.http_port +args.cleos += '--url http://127.0.0.1:%d ' % args.http_port logFile = open(args.log_path, 'a') From 122957467ea21eb3918798cd409cc869ea592884 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Wed, 15 Aug 2018 12:56:48 +0000 Subject: [PATCH 266/294] fix typo --- contracts/eosiolib/types.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/eosiolib/types.h b/contracts/eosiolib/types.h index 32448f99141..120c896cd87 100644 --- a/contracts/eosiolib/types.h +++ b/contracts/eosiolib/types.h @@ -27,13 +27,13 @@ typedef uint64_t account_name; /** * @brief Name of a permission - * @details Name of an account + * @details Name of a permission */ typedef uint64_t permission_name; /** * @brief Name of a table - * @details Name of atable + * @details Name of a table */ typedef uint64_t table_name; From 2bfd7d20233fc4b38bd700499b5e4ed3c0f3e8b3 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Wed, 15 Aug 2018 14:34:33 +0000 Subject: [PATCH 267/294] Remove unused variable --- programs/cleos/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index bcc6e6dddca..e478f42a2bd 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2354,7 +2354,6 @@ int main( int argc, char** argv ) { add_standard_transaction_options(transfer, "sender@active"); transfer->set_callback([&] { - signed_transaction trx; if (tx_force_unique && memo.size() == 0) { // use the memo to add a nonce memo = generate_nonce_string(); From edb3a4a43b89642b1b71ae4f3acfd57f52f23a5c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 16 Aug 2018 15:29:03 -0400 Subject: [PATCH 268/294] Use transaction delay_sec in get_required_keys determination --- plugins/chain_plugin/chain_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 1abd7aa0294..ded1328ab3f 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1666,7 +1666,7 @@ read_only::get_required_keys_result read_only::get_required_keys( const get_requ abi_serializer::from_variant(params.transaction, pretty_input, resolver, abi_serializer_max_time); } EOS_RETHROW_EXCEPTIONS(chain::transaction_type_exception, "Invalid transaction") - auto required_keys_set = db.get_authorization_manager().get_required_keys(pretty_input, params.available_keys); + auto required_keys_set = db.get_authorization_manager().get_required_keys(pretty_input, params.available_keys, pretty_input.delay_sec); get_required_keys_result result; result.required_keys = required_keys_set; return result; From 864c39782c51df600891540eb00e316be2c96c35 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 17 Aug 2018 14:48:10 -0400 Subject: [PATCH 269/294] Correct conversion to microseconds --- plugins/chain_plugin/chain_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index ded1328ab3f..9788531e7f1 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1666,7 +1666,7 @@ read_only::get_required_keys_result read_only::get_required_keys( const get_requ abi_serializer::from_variant(params.transaction, pretty_input, resolver, abi_serializer_max_time); } EOS_RETHROW_EXCEPTIONS(chain::transaction_type_exception, "Invalid transaction") - auto required_keys_set = db.get_authorization_manager().get_required_keys(pretty_input, params.available_keys, pretty_input.delay_sec); + auto required_keys_set = db.get_authorization_manager().get_required_keys( pretty_input, params.available_keys, fc::seconds( pretty_input.delay_sec )); get_required_keys_result result; result.required_keys = required_keys_set; return result; From f44d204fd3b0d0bdfc0c467296be914492a5fc06 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 17 Aug 2018 14:49:57 -0400 Subject: [PATCH 270/294] Update to safer unsigned_int --- libraries/fc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fc b/libraries/fc index a6b2756b100..62a19a75868 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit a6b2756b100098296f7548a191e6210e770b7b3a +Subproject commit 62a19a758682679e3de27d956986eaf8b016465d From 3644e2d1ef0b15ed8e7d3888d8288f48e4136123 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Wed, 22 Aug 2018 17:49:56 -0400 Subject: [PATCH 271/294] bump version to 1.2.2 --- CMakeLists.txt | 2 +- Docker/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c374afa42ef..76f7afa7a91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ set( CXX_STANDARD_REQUIRED ON) set(VERSION_MAJOR 1) set(VERSION_MINOR 2) -set(VERSION_PATCH 1) +set(VERSION_PATCH 2) set( CLI_CLIENT_EXECUTABLE_NAME cleos ) set( NODE_EXECUTABLE_NAME nodeos ) diff --git a/Docker/README.md b/Docker/README.md index d1d38924bdb..4bd51558e36 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -20,10 +20,10 @@ cd eos/Docker docker build . -t eosio/eos ``` -The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the v1.2.1 tag, you could do the following: +The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the v1.2.2 tag, you could do the following: ```bash -docker build -t eosio/eos:v1.2.1 --build-arg branch=v1.2.1 . +docker build -t eosio/eos:v1.2.2 --build-arg branch=v1.2.2 . ``` By default, the symbol in eosio.system is set to SYS. You can override this using the symbol argument while building the docker image. From 5b114ad976d20e6827bb2a5ad17ef945c5db5cc9 Mon Sep 17 00:00:00 2001 From: Jonathan LEI Date: Fri, 24 Aug 2018 00:10:01 +0800 Subject: [PATCH 272/294] Added contract build path to eosiocpp -g include paths --- tools/eosiocpp.in | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/eosiocpp.in b/tools/eosiocpp.in index 1002a704c7b..f0b63f9c56b 100755 --- a/tools/eosiocpp.in +++ b/tools/eosiocpp.in @@ -112,6 +112,7 @@ function generate_abi { ${ABIGEN} -extra-arg=-c -extra-arg=--std=c++14 -extra-arg=--target=wasm32 \ -extra-arg=-nostdinc -extra-arg=-nostdinc++ -extra-arg=-DABIGEN \ -extra-arg=-I@CMAKE_SOURCE_DIR@/contracts \ + -extra-arg=-I@CMAKE_SOURCE_DIR@/build/contracts \ -extra-arg=-I@CMAKE_SOURCE_DIR@/contracts/libc++/upstream/include \ -extra-arg=-I@CMAKE_SOURCE_DIR@/contracts/musl/upstream/include \ -extra-arg=-I@CMAKE_SOURCE_DIR@/externals/magic_get/include \ From b9831d182a2aaae2e51abeae92058fb7a03b50a4 Mon Sep 17 00:00:00 2001 From: Jonathan Giszczak Date: Thu, 23 Aug 2018 12:24:46 -0500 Subject: [PATCH 273/294] Fix framework casing for case sensitive MacOS builds By @aaroncox --- plugins/wallet_plugin/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/wallet_plugin/CMakeLists.txt b/plugins/wallet_plugin/CMakeLists.txt index fc27ea76e7a..8b3a6d7d7b1 100644 --- a/plugins/wallet_plugin/CMakeLists.txt +++ b/plugins/wallet_plugin/CMakeLists.txt @@ -4,10 +4,10 @@ if(APPLE) set(SE_WALLET_SOURCES se_wallet.cpp macos_user_auth.m) set_source_files_properties(macos_user_presence.m PROPERTIES COMPILE_FLAGS "-x objective-c") - find_library(security_framework security) - find_library(localauthentication_framework localauthentication) - find_library(corefoundation_framework corefoundation) - find_library(cocoa_framework cocoa) + find_library(security_framework Security) + find_library(localauthentication_framework LocalAuthentication) + find_library(corefoundation_framework CoreFoundation) + find_library(cocoa_framework Cocoa) if(MAS_KEYCHAIN_GROUP) add_definitions(-DMAS_KEYCHAIN_GROUP=${MAS_KEYCHAIN_GROUP}) From 4655c8f708a23c560c6ee5f94905c2cf5016d6ec Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Thu, 23 Aug 2018 21:57:51 -0400 Subject: [PATCH 274/294] Slight change --- tools/eosiocpp.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/eosiocpp.in b/tools/eosiocpp.in index f0b63f9c56b..3e8f84e96d7 100755 --- a/tools/eosiocpp.in +++ b/tools/eosiocpp.in @@ -112,7 +112,7 @@ function generate_abi { ${ABIGEN} -extra-arg=-c -extra-arg=--std=c++14 -extra-arg=--target=wasm32 \ -extra-arg=-nostdinc -extra-arg=-nostdinc++ -extra-arg=-DABIGEN \ -extra-arg=-I@CMAKE_SOURCE_DIR@/contracts \ - -extra-arg=-I@CMAKE_SOURCE_DIR@/build/contracts \ + -extra-arg=-I@CMAKE_BINARY_DIR@/contracts \ -extra-arg=-I@CMAKE_SOURCE_DIR@/contracts/libc++/upstream/include \ -extra-arg=-I@CMAKE_SOURCE_DIR@/contracts/musl/upstream/include \ -extra-arg=-I@CMAKE_SOURCE_DIR@/externals/magic_get/include \ From efe5fdbbb35c4282dc342f7e8e74564972c56ab8 Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 24 Aug 2018 09:59:51 -0400 Subject: [PATCH 275/294] do not apply transaction optimizations on light-nodes when speculating fixed an issue where block status was not taken into account when determining whether or not to skip some expensive checks. As a result light validation nodes would not apply those checks even when speculatively executing new transactions (which are not signed by producers). Also refactored common logic while maintaining the 2 config/parameters that control different sets of optimizations resolves EOSIO/eos#5408 --- libraries/chain/controller.cpp | 35 ++++++++++--------- .../chain/include/eosio/chain/controller.hpp | 1 + 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 0c582871a11..1a467ecbccb 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1651,16 +1651,25 @@ optional controller::proposed_producers()const { return gpo.proposed_schedule; } -bool controller::skip_auth_check() const { - // replaying - bool consider_skipping = my->replaying; +bool controller::light_validation_allowed(bool replay_opts_disabled_by_policy) const { + if (!my->pending || my->in_trx_requiring_checks) { + return false; + } - // OR in light validation mode - consider_skipping = consider_skipping || my->conf.block_validation_mode == validation_mode::LIGHT; + auto pb_status = my->pending->_block_status; - return consider_skipping - && !my->conf.force_all_checks - && !my->in_trx_requiring_checks; + // in a pending irreversible or previously validated block and we have forcing all checks + bool consider_skipping_on_replay = (pb_status == block_status::irreversible || pb_status == block_status::validated) && !replay_opts_disabled_by_policy; + + // OR in a signed block and in light validation mode + bool consider_skipping_on_validate = (pb_status == block_status::complete && my->conf.block_validation_mode == validation_mode::LIGHT); + + return consider_skipping_on_replay || consider_skipping_on_validate; +} + + +bool controller::skip_auth_check() const { + return light_validation_allowed(my->conf.force_all_checks); } bool controller::skip_db_sessions( block_status bs ) const { @@ -1679,15 +1688,7 @@ bool controller::skip_db_sessions( ) const { } bool controller::skip_trx_checks() const { - // in a pending irreversible or previously validated block - bool consider_skipping = my->pending && ( my->pending->_block_status == block_status::irreversible || my->pending->_block_status == block_status::validated ); - - // OR in light validation mode - consider_skipping = consider_skipping || my->conf.block_validation_mode == validation_mode::LIGHT; - - return consider_skipping - && !my->conf.disable_replay_opts - && !my->in_trx_requiring_checks; + light_validation_allowed(my->conf.disable_replay_opts); } bool controller::contracts_console()const { diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index c457eb68cc9..956382f6ebd 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -219,6 +219,7 @@ namespace eosio { namespace chain { int64_t set_proposed_producers( vector producers ); + bool light_validation_allowed(bool replay_opts_disabled_by_policy) const; bool skip_auth_check()const; bool skip_db_sessions( )const; bool skip_db_sessions( block_status bs )const; From 9c711b4fee40271cf966e7cba159a522bacee21b Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 24 Aug 2018 10:58:16 -0400 Subject: [PATCH 276/294] actually return the value... --- libraries/chain/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 1a467ecbccb..ebc7830afc0 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1688,7 +1688,7 @@ bool controller::skip_db_sessions( ) const { } bool controller::skip_trx_checks() const { - light_validation_allowed(my->conf.disable_replay_opts); + return light_validation_allowed(my->conf.disable_replay_opts); } bool controller::contracts_console()const { From be26f5f4ab24b90ec128129d7cf37aa9fda55b65 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 24 Aug 2018 11:37:31 -0500 Subject: [PATCH 277/294] Revert "on_accepted_block_header and on_accepted_block merged into one handler #4468" This reverts commit 8268af254e0d98d34ab97f9d38341de93afb1872. Processing of block_state before signature is problematic. --- plugins/bnet_plugin/bnet_plugin.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/plugins/bnet_plugin/bnet_plugin.cpp b/plugins/bnet_plugin/bnet_plugin.cpp index 80104a15ea1..c61274e4265 100644 --- a/plugins/bnet_plugin/bnet_plugin.cpp +++ b/plugins/bnet_plugin/bnet_plugin.cpp @@ -530,7 +530,10 @@ namespace eosio { _block_status.insert( block_status(id, false, false) ); } } + } + void on_accepted_block( const block_state_ptr& s ) { + verify_strand_in_this_thread(_strand, __func__, __LINE__); //idump((_block_status.size())(_transaction_status.size())); //ilog( "accepted block ${n}", ("n",s->block_num) ); @@ -1204,6 +1207,18 @@ namespace eosio { for_each_session( [s]( auto ses ){ ses->on_new_lib( s ); } ); } + /** + * Notify all active connections of the new accepted block so + * they can relay it. This method also pre-packages the block + * as a packed bnet_message so the connections can simply relay + * it on. + */ + void on_accepted_block( block_state_ptr s ) { + _ioc->post( [s,this] { /// post this to the thread pool because packing can be intensive + for_each_session( [s]( auto ses ){ ses->on_accepted_block( s ); } ); + }); + } + void on_accepted_block_header( block_state_ptr s ) { _ioc->post( [s,this] { /// post this to the thread pool because packing can be intensive for_each_session( [s]( auto ses ){ ses->on_accepted_block_header( s ); } ); @@ -1349,6 +1364,9 @@ namespace eosio { wlog( "bnet startup " ); + auto& chain = app().get_plugin().chain(); + FC_ASSERT ( chain.get_read_mode() != chain::db_read_mode::IRREVERSIBLE, "bnet is not compatible with \"irreversible\" read_mode"); + my->_on_appled_trx_handle = app().get_channel() .subscribe( [this]( transaction_metadata_ptr t ){ my->on_accepted_transaction(t); @@ -1359,6 +1377,11 @@ namespace eosio { my->on_irreversible_block(s); }); + my->_on_accepted_block_handle = app().get_channel() + .subscribe( [this]( block_state_ptr s ){ + my->on_accepted_block(s); + }); + my->_on_accepted_block_header_handle = app().get_channel() .subscribe( [this]( block_state_ptr s ){ my->on_accepted_block_header(s); From b41930d137d92a79e455e24244c1d947c20e4af1 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 24 Aug 2018 12:24:43 -0500 Subject: [PATCH 278/294] Add thread safe id() --- libraries/chain/include/eosio/chain/transaction.hpp | 3 ++- libraries/chain/transaction.cpp | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/chain/include/eosio/chain/transaction.hpp b/libraries/chain/include/eosio/chain/transaction.hpp index 1d325352d0d..895d3ee4fb8 100644 --- a/libraries/chain/include/eosio/chain/transaction.hpp +++ b/libraries/chain/include/eosio/chain/transaction.hpp @@ -132,7 +132,8 @@ namespace eosio { namespace chain { time_point_sec expiration()const; transaction_id_type id()const; - bytes get_raw_transaction()const; + transaction_id_type get_uncached_id()const; // thread safe + bytes get_raw_transaction()const; // thread safe vector get_context_free_data()const; transaction get_transaction()const; signed_transaction get_signed_transaction()const; diff --git a/libraries/chain/transaction.cpp b/libraries/chain/transaction.cpp index d81a35ff19b..c49f76ce771 100644 --- a/libraries/chain/transaction.cpp +++ b/libraries/chain/transaction.cpp @@ -298,6 +298,12 @@ transaction_id_type packed_transaction::id()const return get_transaction().id(); } +transaction_id_type packed_transaction::get_uncached_id()const +{ + const auto raw = get_raw_transaction(); + return fc::raw::unpack( raw ).id(); +} + void packed_transaction::local_unpack()const { if (!unpacked_trx) { From 43e6a3081ecf36e009ecc29644785e2e4dd2521c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 24 Aug 2018 12:25:39 -0500 Subject: [PATCH 279/294] Use thread safe get_uncached_id() --- plugins/bnet_plugin/bnet_plugin.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/plugins/bnet_plugin/bnet_plugin.cpp b/plugins/bnet_plugin/bnet_plugin.cpp index c61274e4265..1fcbea038a5 100644 --- a/plugins/bnet_plugin/bnet_plugin.cpp +++ b/plugins/bnet_plugin/bnet_plugin.cpp @@ -552,7 +552,9 @@ namespace eosio { */ for( const auto& receipt : s->block->transactions ) { if( receipt.trx.which() == 1 ) { - const auto tid = receipt.trx.get().id(); + const auto& pt = receipt.trx.get(); + // get id via get_uncached_id() as packed_transaction.id() mutates internal transaction state + const auto& tid = pt.get_uncached_id(); auto itr = _transaction_status.find( tid ); if( itr != _transaction_status.end() ) _transaction_status.erase(itr); @@ -1009,7 +1011,9 @@ namespace eosio { void mark_block_transactions_known_by_peer( const signed_block_ptr& b ) { for( const auto& receipt : b->transactions ) { if( receipt.trx.which() == 1 ) { - auto id = receipt.trx.get().id(); + const auto& pt = receipt.trx.get(); + // get id via get_uncached_id() as packed_transaction.id() mutates internal transaction state + const auto& id = pt.get_uncached_id(); mark_transaction_known_by_peer(id); } } @@ -1044,10 +1048,12 @@ namespace eosio { if( app().get_plugin().chain().get_read_mode() == chain::db_read_mode::READ_ONLY ) return; - auto id = p->id(); // ilog( "recv trx ${n}", ("n", id) ); if( p->expiration() < fc::time_point::now() ) return; + // get id via get_uncached_id() as packed_transaction.id() mutates internal transaction state + const auto& id = p->get_uncached_id(); + if( mark_transaction_known_by_peer( id ) ) return; From f2b49e8890f8d4eaf277903b6a48116769fd88cd Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 24 Aug 2018 12:42:30 -0500 Subject: [PATCH 280/294] Add missing id variable --- plugins/bnet_plugin/bnet_plugin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/bnet_plugin/bnet_plugin.cpp b/plugins/bnet_plugin/bnet_plugin.cpp index 1fcbea038a5..4052fce2b30 100644 --- a/plugins/bnet_plugin/bnet_plugin.cpp +++ b/plugins/bnet_plugin/bnet_plugin.cpp @@ -537,6 +537,8 @@ namespace eosio { //idump((_block_status.size())(_transaction_status.size())); //ilog( "accepted block ${n}", ("n",s->block_num) ); + const auto& id = s->id; + _local_head_block_id = id; _local_head_block_num = block_header::num_from_id(id); From c8f88db05357631a6d69fefca43ea296fcb2a154 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Sat, 25 Aug 2018 16:16:39 -0500 Subject: [PATCH 281/294] Change log to debug for exceptions as they are expected --- plugins/net_plugin/net_plugin.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index eb120521e68..ef68ab08a63 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -2491,11 +2491,8 @@ namespace eosio { dispatcher->recv_transaction(c, tid); chain_plug->accept_transaction(msg, [=](const static_variant& result) { if (result.contains()) { - auto e_ptr = result.get(); - if (e_ptr->code() != tx_duplicate::code_value && e_ptr->code() != expired_tx_exception::code_value) { - elog("accept txn threw ${m}",("m",result.get()->to_detail_string())); - peer_elog(c, "bad packed_transaction : ${m}", ("m",result.get()->what())); - } + dlog("accept txn threw ${m}",("m",result.get()->to_detail_string())); + peer_dlog(c, "bad packed_transaction : ${m}", ("m",result.get()->what())); } else { auto trace = result.get(); if (!trace->except) { From 93bd82621710b63f9267e982c28db25282fcac8c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 27 Aug 2018 09:23:59 -0500 Subject: [PATCH 282/294] dlog is on by default, remove call for accept_transaction for exceptions since they are expected --- plugins/net_plugin/net_plugin.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index ef68ab08a63..d907e7f7390 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -2491,7 +2491,6 @@ namespace eosio { dispatcher->recv_transaction(c, tid); chain_plug->accept_transaction(msg, [=](const static_variant& result) { if (result.contains()) { - dlog("accept txn threw ${m}",("m",result.get()->to_detail_string())); peer_dlog(c, "bad packed_transaction : ${m}", ("m",result.get()->what())); } else { auto trace = result.get(); From 044248513166bc99d7219f0e073a95f05279920d Mon Sep 17 00:00:00 2001 From: arhag Date: Mon, 27 Aug 2018 14:02:01 -0400 Subject: [PATCH 283/294] subjectively fail transaction if unprivileged code bills RAM to an account other than itself within a notify context --- libraries/chain/apply_context.cpp | 4 ++++ libraries/chain/controller.cpp | 4 ++++ libraries/chain/include/eosio/chain/controller.hpp | 3 +++ 3 files changed, 11 insertions(+) diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index a3399dab0d6..0bbfb8e3aec 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -301,6 +301,8 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a }); } + EOS_ASSERT( control.is_ram_billing_in_notify_allowed() || (receiver == act.account) || (receiver == payer) || privileged, + subjective_block_production_exception, "Cannot charge RAM to other accounts during notify." ); trx_context.add_ram_usage( payer, (config::billable_size_v + trx_size) ); } @@ -362,6 +364,8 @@ bytes apply_context::get_packed_transaction() { void apply_context::update_db_usage( const account_name& payer, int64_t delta ) { if( delta > 0 ) { if( !(privileged || payer == account_name(receiver)) ) { + EOS_ASSERT( control.is_ram_billing_in_notify_allowed() || (receiver == act.account), + subjective_block_production_exception, "Cannot charge RAM to other accounts during notify." ); require_authorization( payer ); } } diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 0c582871a11..6b2cfdf9baf 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1776,6 +1776,10 @@ bool controller::is_producing_block()const { return (my->pending->_block_status == block_status::incomplete); } +bool controller::is_ram_billing_in_notify_allowed()const { + return !is_producing_block() || my->conf.allow_ram_billing_in_notify; +} + void controller::validate_referenced_accounts( const transaction& trx )const { for( const auto& a : trx.context_free_actions ) { auto* code = my->db.find(a.account); diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index c457eb68cc9..7aee814e594 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -65,6 +65,7 @@ namespace eosio { namespace chain { bool force_all_checks = false; bool disable_replay_opts = false; bool contracts_console = false; + bool allow_ram_billing_in_notify = false; genesis_state genesis; wasm_interface::vm_type wasm_runtime = chain::config::default_wasm_runtime; @@ -204,6 +205,8 @@ namespace eosio { namespace chain { void check_key_list( const public_key_type& key )const; bool is_producing_block()const; + bool is_ram_billing_in_notify_allowed()const; + void add_resource_greylist(const account_name &name); void remove_resource_greylist(const account_name &name); bool is_resource_greylisted(const account_name &name) const; From 9a77456031413352ff833777b86b32eef28830bb Mon Sep 17 00:00:00 2001 From: arhag Date: Mon, 27 Aug 2018 16:06:56 -0400 Subject: [PATCH 284/294] Add support in config.ini to disable the RAM billing soft-fix in notification handlers. --- plugins/chain_plugin/chain_plugin.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 58295d52da8..67efb591611 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -244,6 +244,8 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip "Chain validation mode (\"full\" or \"light\").\n" "In \"full\" mode all incoming blocks will be fully validated.\n" "In \"light\" mode all incoming blocks headers will be fully validated; transactions in those validated blocks will be trusted \n") + ("disable-ram-billing-notify-checks", bpo::bool_switch()->default_value(false), + "Disable the check which subjectively fails a transaction if a contract bills more RAM to another account within the context of a notification handler (i.e. when the receiver is not the code of the action).") ; // TODO: rate limiting @@ -405,6 +407,7 @@ void chain_plugin::plugin_initialize(const variables_map& options) { my->chain_config->force_all_checks = options.at( "force-all-checks" ).as(); my->chain_config->disable_replay_opts = options.at( "disable-replay-opts" ).as(); my->chain_config->contracts_console = options.at( "contracts-console" ).as(); + my->chain_config->allow_ram_billing_in_notify = options.at( "disable-ram-billing-notify-checks" ).as(); if( options.count( "extract-genesis-json" ) || options.at( "print-genesis-json" ).as()) { genesis_state gs; From a1dffae2f88ae52506261f9a7688775ee6f5c3d5 Mon Sep 17 00:00:00 2001 From: arhag Date: Mon, 27 Aug 2018 18:58:21 -0400 Subject: [PATCH 285/294] add unit test for RAM billing soft-fix in notify context --- contracts/test_api/test_action.cpp | 25 +++++++++++++++++++++++++ contracts/test_api/test_api.cpp | 1 + contracts/test_api/test_api.hpp | 2 +- unittests/api_tests.cpp | 21 +++++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/contracts/test_api/test_action.cpp b/contracts/test_api/test_action.cpp index adc517453fa..1bc54cb3d02 100644 --- a/contracts/test_api/test_action.cpp +++ b/contracts/test_api/test_action.cpp @@ -225,3 +225,28 @@ void test_action::test_assert_code() { eosio_assert( total == sizeof(uint64_t), "total == sizeof(uint64_t)"); eosio_assert_code( false, code ); } + +void test_action::test_ram_billing_in_notify(uint64_t receiver, uint64_t code, uint64_t action) { + uint128_t tmp = 0; + uint32_t total = read_action_data(&tmp, sizeof(uint128_t)); + eosio_assert( total == sizeof(uint128_t), "total == sizeof(uint128_t)"); + + uint64_t to_notify = tmp >> 64; + uint64_t payer = tmp & 0xFFFFFFFFFFFFFFFFULL; + + if( code == receiver ) { + eosio::require_recipient( to_notify ); + } else { + eosio_assert( to_notify == receiver, "notified recipient other than the one specified in to_notify" ); + + // Remove main table row if it already exists. + int itr = db_find_i64( receiver, N(notifytest), N(notifytest), N(notifytest) ); + if( itr >= 0 ) + db_remove_i64( itr ); + + // Create the main table row simply for the purpose of charging code more RAM. + if( payer != 0 ) + db_store_i64(N(notifytest), N(notifytest), payer, N(notifytest), &to_notify, sizeof(to_notify) ); + } + +} diff --git a/contracts/test_api/test_api.cpp b/contracts/test_api/test_api.cpp index 3bccd9a4a8c..29f47d75f64 100644 --- a/contracts/test_api/test_api.cpp +++ b/contracts/test_api/test_api.cpp @@ -79,6 +79,7 @@ extern "C" { WASM_TEST_HANDLER_EX(test_action, test_current_receiver); WASM_TEST_HANDLER(test_action, test_publication_time); WASM_TEST_HANDLER(test_action, test_assert_code); + WASM_TEST_HANDLER_EX(test_action, test_ram_billing_in_notify); // test named actions // We enforce action name matches action data type name, so name mangling will not work for these tests. diff --git a/contracts/test_api/test_api.hpp b/contracts/test_api/test_api.hpp index b54f12bff02..b026642175e 100644 --- a/contracts/test_api/test_api.hpp +++ b/contracts/test_api/test_api.hpp @@ -57,7 +57,6 @@ struct test_print { }; struct test_action { - static void read_action_normal(); static void read_action_to_0(); static void read_action_to_64k(); @@ -73,6 +72,7 @@ struct test_action { static void test_current_receiver(uint64_t receiver, uint64_t code, uint64_t action); static void test_publication_time(); static void test_assert_code(); + static void test_ram_billing_in_notify(uint64_t receiver, uint64_t code, uint64_t action); }; struct test_db { diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 63eda3116b0..2ddf0b34253 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -437,6 +437,27 @@ BOOST_FIXTURE_TEST_CASE(action_tests, TESTER) { try { BOOST_REQUIRE_EQUAL( validate(), true ); } FC_LOG_AND_RETHROW() } +BOOST_FIXTURE_TEST_CASE(ram_billing_in_notify_tests, TESTER) { try { + produce_blocks(2); + create_account( N(testapi) ); + create_account( N(testapi2) ); + produce_blocks(10); + set_code( N(testapi), test_api_wast ); + produce_blocks(1); + set_code( N(testapi2), test_api_wast ); + produce_blocks(1); + + BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( *this, "test_action", "test_ram_billing_in_notify", fc::raw::pack( ((unsigned __int128)N(testapi2) << 64) | N(testapi) ) ), + subjective_block_production_exception, fc_exception_message_is("Cannot charge RAM to other accounts during notify.") ); + + + CALL_TEST_FUNCTION( *this, "test_action", "test_ram_billing_in_notify", fc::raw::pack( ((unsigned __int128)N(testapi2) << 64) | 0 ) ); + + CALL_TEST_FUNCTION( *this, "test_action", "test_ram_billing_in_notify", fc::raw::pack( ((unsigned __int128)N(testapi2) << 64) | N(testapi2) ) ); + + BOOST_REQUIRE_EQUAL( validate(), true ); +} FC_LOG_AND_RETHROW() } + /************************************************************************************* * context free action tests *************************************************************************************/ From 431367696f0820c3177abdd53d13b5f4039122dd Mon Sep 17 00:00:00 2001 From: Kayan Date: Tue, 28 Aug 2018 17:38:55 +0800 Subject: [PATCH 286/294] don't change transaction data in cleos push signed transaction --- programs/cleos/main.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index e478f42a2bd..5b0cba91705 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -276,18 +276,22 @@ void sign_transaction(signed_transaction& trx, fc::variant& required_keys, const fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) { auto info = get_info(); - trx.expiration = info.head_block_time + tx_expiration; + if (trx.expiration == time_point_sec()) { + trx.expiration = info.head_block_time + tx_expiration; + } // Set tapos, default to last irreversible block if it's not specified by the user - block_id_type ref_block_id = info.last_irreversible_block_id; - try { - fc::variant ref_block; - if (!tx_ref_block_num_or_id.empty()) { - ref_block = call(get_block_func, fc::mutable_variant_object("block_num_or_id", tx_ref_block_num_or_id)); - ref_block_id = ref_block["id"].as(); - } - } EOS_RETHROW_EXCEPTIONS(invalid_ref_block_exception, "Invalid reference block num or id: ${block_num_or_id}", ("block_num_or_id", tx_ref_block_num_or_id)); - trx.set_reference_block(ref_block_id); + if (trx.ref_block_prefix == 0 && trx.ref_block_num == 0) { + block_id_type ref_block_id = info.last_irreversible_block_id; + try { + fc::variant ref_block; + if (!tx_ref_block_num_or_id.empty()) { + ref_block = call(get_block_func, fc::mutable_variant_object("block_num_or_id", tx_ref_block_num_or_id)); + ref_block_id = ref_block["id"].as(); + } + } EOS_RETHROW_EXCEPTIONS(invalid_ref_block_exception, "Invalid reference block num or id: ${block_num_or_id}", ("block_num_or_id", tx_ref_block_num_or_id)); + trx.set_reference_block(ref_block_id); + } if (tx_force_unique) { trx.context_free_actions.emplace_back( generate_nonce_action() ); From 5e019870096cd72e07ab94c568f5aa31f62f535f Mon Sep 17 00:00:00 2001 From: Kayan Date: Tue, 28 Aug 2018 17:56:27 +0800 Subject: [PATCH 287/294] should not change txn content if already signed --- programs/cleos/main.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 5b0cba91705..4ce7c2dbbd3 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -276,12 +276,11 @@ void sign_transaction(signed_transaction& trx, fc::variant& required_keys, const fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) { auto info = get_info(); - if (trx.expiration == time_point_sec()) { + + if (trx.signatures.size() == 0) { // #5445 can't change txn content if already signed trx.expiration = info.head_block_time + tx_expiration; - } - // Set tapos, default to last irreversible block if it's not specified by the user - if (trx.ref_block_prefix == 0 && trx.ref_block_num == 0) { + // Set tapos, default to last irreversible block if it's not specified by the user block_id_type ref_block_id = info.last_irreversible_block_id; try { fc::variant ref_block; @@ -291,14 +290,14 @@ fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000 } } EOS_RETHROW_EXCEPTIONS(invalid_ref_block_exception, "Invalid reference block num or id: ${block_num_or_id}", ("block_num_or_id", tx_ref_block_num_or_id)); trx.set_reference_block(ref_block_id); - } - if (tx_force_unique) { - trx.context_free_actions.emplace_back( generate_nonce_action() ); - } + if (tx_force_unique) { + trx.context_free_actions.emplace_back( generate_nonce_action() ); + } - trx.max_cpu_usage_ms = tx_max_cpu_usage; - trx.max_net_usage_words = (tx_max_net_usage + 7)/8; + trx.max_cpu_usage_ms = tx_max_cpu_usage; + trx.max_net_usage_words = (tx_max_net_usage + 7)/8; + } if (!tx_skip_sign) { auto required_keys = determine_required_keys(trx); From e6633a5e9dd7dc600688167072ccd3b0d9135d43 Mon Sep 17 00:00:00 2001 From: Jonathan LEI Date: Tue, 28 Aug 2018 22:38:31 +0800 Subject: [PATCH 288/294] Fixed ignored action name in ABI generation --- libraries/abi_generator/abi_generator.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libraries/abi_generator/abi_generator.cpp b/libraries/abi_generator/abi_generator.cpp index 42ae4ace9d0..fc021c0a3fd 100644 --- a/libraries/abi_generator/abi_generator.cpp +++ b/libraries/abi_generator/abi_generator.cpp @@ -112,14 +112,19 @@ bool abi_generator::inspect_type_methods_for_actions(const Decl* decl) { try { // Try to get "action" annotation from method comment bool raw_comment_is_action = false; + string action_name_from_comment; const RawComment* raw_comment = ast_context->getRawCommentForDeclNoCache(method); if(raw_comment != nullptr) { SourceManager& source_manager = ast_context->getSourceManager(); string raw_text = raw_comment->getRawText(source_manager); - regex r(R"(@abi (action)((?: [a-z0-9]+)*))"); + regex r(R"(@abi (action) ?((?:[a-z0-9]+)*))"); smatch smatch; regex_search(raw_text, smatch, r); raw_comment_is_action = smatch.size() == 3; + + if (raw_comment_is_action) { + action_name_from_comment = smatch[2]; + } } // Check if current method is listed the EOSIO_ABI macro @@ -156,7 +161,8 @@ bool abi_generator::inspect_type_methods_for_actions(const Decl* decl) { try { full_types[method_name] = method_name; - output->actions.push_back({method_name, method_name, rc[method_name]}); + string action_name = action_name_from_comment.empty() ? method_name : action_name_from_comment; + output->actions.push_back({action_name, method_name, rc[method_name]}); at_least_one_action = true; }; From 55667540e2caf13c57c262f668fdd55d23fefce9 Mon Sep 17 00:00:00 2001 From: Kayan Date: Mon, 27 Aug 2018 15:33:49 +0800 Subject: [PATCH 289/294] suppress greylist related exception loggings --- libraries/chain/controller.cpp | 3 +- .../chain/include/eosio/chain/exceptions.hpp | 2 + .../eosio/chain/transaction_context.hpp | 4 +- libraries/chain/transaction_context.cpp | 49 +++++++++++++------ plugins/http_plugin/http_plugin.cpp | 8 +-- plugins/net_plugin/net_plugin.cpp | 4 +- 6 files changed, 49 insertions(+), 21 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 0c582871a11..8bd97bd82af 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -574,6 +574,7 @@ struct controller_impl { || (code == block_net_usage_exceeded::code_value) || (code == greylist_net_usage_exceeded::code_value) || (code == block_cpu_usage_exceeded::code_value) + || (code == greylist_cpu_usage_exceeded::code_value) || (code == deadline_exception::code_value) || (code == leeway_deadline_exception::code_value) || (code == actor_whitelist_exception::code_value) @@ -714,7 +715,7 @@ struct controller_impl { auto& rl = self.get_mutable_resource_limits_manager(); rl.update_account_usage( trx_context.bill_to_accounts, block_timestamp_type(self.pending_block_time()).slot ); int64_t account_cpu_limit = 0; - std::tie( std::ignore, account_cpu_limit, std::ignore ) = trx_context.max_bandwidth_billed_accounts_can_pay( true ); + std::tie( std::ignore, account_cpu_limit, std::ignore, std::ignore ) = trx_context.max_bandwidth_billed_accounts_can_pay( true ); cpu_time_to_bill_us = static_cast( std::min( std::min( static_cast(cpu_time_to_bill_us), account_cpu_limit ), diff --git a/libraries/chain/include/eosio/chain/exceptions.hpp b/libraries/chain/include/eosio/chain/exceptions.hpp index 544596540f0..fd49e930af5 100644 --- a/libraries/chain/include/eosio/chain/exceptions.hpp +++ b/libraries/chain/include/eosio/chain/exceptions.hpp @@ -268,6 +268,8 @@ namespace eosio { namespace chain { 3080006, "Transaction took too long" ) FC_DECLARE_DERIVED_EXCEPTION( greylist_net_usage_exceeded, resource_exhausted_exception, 3080007, "Transaction exceeded the current greylisted account network usage limit" ) + FC_DECLARE_DERIVED_EXCEPTION( greylist_cpu_usage_exceeded, resource_exhausted_exception, + 3080008, "Transaction exceeded the current greylisted account CPU usage limit" ) FC_DECLARE_DERIVED_EXCEPTION( leeway_deadline_exception, deadline_exception, 3081001, "Transaction reached the deadline set due to leeway on account CPU limits" ) diff --git a/libraries/chain/include/eosio/chain/transaction_context.hpp b/libraries/chain/include/eosio/chain/transaction_context.hpp index e275ecf0025..22e8eae36d6 100644 --- a/libraries/chain/include/eosio/chain/transaction_context.hpp +++ b/libraries/chain/include/eosio/chain/transaction_context.hpp @@ -42,7 +42,7 @@ namespace eosio { namespace chain { uint32_t update_billed_cpu_time( fc::time_point now ); - std::tuple max_bandwidth_billed_accounts_can_pay( bool force_elastic_limits = false )const; + std::tuple max_bandwidth_billed_accounts_can_pay( bool force_elastic_limits = false )const; private: @@ -98,6 +98,8 @@ namespace eosio { namespace chain { uint64_t eager_net_limit = 0; uint64_t& net_usage; /// reference to trace->net_usage + bool cpu_limit_due_to_greylist = false; + fc::microseconds initial_objective_duration_limit; fc::microseconds objective_duration_limit; fc::time_point _deadline = fc::time_point::maximum(); diff --git a/libraries/chain/transaction_context.cpp b/libraries/chain/transaction_context.cpp index 0545c6337b8..cceb2835af6 100644 --- a/libraries/chain/transaction_context.cpp +++ b/libraries/chain/transaction_context.cpp @@ -92,9 +92,10 @@ namespace eosio { namespace chain { // Calculate the highest network usage and CPU time that all of the billed accounts can afford to be billed int64_t account_net_limit = 0; int64_t account_cpu_limit = 0; - bool greylisted = false; - std::tie( account_net_limit, account_cpu_limit, greylisted ) = max_bandwidth_billed_accounts_can_pay(); + bool greylisted = false, greylistedCPU = false; + std::tie( account_net_limit, account_cpu_limit, greylisted, greylistedCPU) = max_bandwidth_billed_accounts_can_pay(); net_limit_due_to_greylist |= greylisted; + cpu_limit_due_to_greylist |= greylistedCPU; eager_net_limit = net_limit; @@ -224,9 +225,10 @@ namespace eosio { namespace chain { // Calculate the new highest network usage and CPU time that all of the billed accounts can afford to be billed int64_t account_net_limit = 0; int64_t account_cpu_limit = 0; - bool greylisted = false; - std::tie( account_net_limit, account_cpu_limit, greylisted ) = max_bandwidth_billed_accounts_can_pay(); + bool greylisted = false, greylistedCPU = false; + std::tie( account_net_limit, account_cpu_limit, greylisted, greylistedCPU) = max_bandwidth_billed_accounts_can_pay(); net_limit_due_to_greylist |= greylisted; + cpu_limit_due_to_greylist |= greylistedCPU; // Possibly lower net_limit to what the billed accounts can pay if( static_cast(account_net_limit) <= net_limit ) { @@ -296,9 +298,15 @@ namespace eosio { namespace chain { "not enough time left in block to complete executing transaction", ("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) ); } else if( deadline_exception_code == tx_cpu_usage_exceeded::code_value ) { - EOS_THROW( tx_cpu_usage_exceeded, - "transaction was executing for too long", - ("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) ); + if (cpu_limit_due_to_greylist) { + EOS_THROW( greylist_cpu_usage_exceeded, + "transaction was executing for too long", + ("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) ); + } else { + EOS_THROW( tx_cpu_usage_exceeded, + "transaction was executing for too long", + ("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) ); + } } else if( deadline_exception_code == leeway_deadline_exception::code_value ) { EOS_THROW( leeway_deadline_exception, "the transaction was unable to complete by deadline, " @@ -350,11 +358,19 @@ namespace eosio { namespace chain { ("billed", billed_us)("billable", objective_duration_limit.count()) ); } else { - EOS_ASSERT( billed_us <= objective_duration_limit.count(), - tx_cpu_usage_exceeded, - "billed CPU time (${billed} us) is greater than the maximum billable CPU time for the transaction (${billable} us)", - ("billed", billed_us)("billable", objective_duration_limit.count()) - ); + if (cpu_limit_due_to_greylist) { + EOS_ASSERT( billed_us <= objective_duration_limit.count(), + greylist_cpu_usage_exceeded, + "billed CPU time (${billed} us) is greater than the maximum greylisted billable CPU time for the transaction (${billable} us)", + ("billed", billed_us)("billable", objective_duration_limit.count()) + ); + } else { + EOS_ASSERT( billed_us <= objective_duration_limit.count(), + tx_cpu_usage_exceeded, + "billed CPU time (${billed} us) is greater than the maximum billable CPU time for the transaction (${billable} us)", + ("billed", billed_us)("billable", objective_duration_limit.count()) + ); + } } } } @@ -376,7 +392,7 @@ namespace eosio { namespace chain { return static_cast(billed_cpu_time_us); } - std::tuple transaction_context::max_bandwidth_billed_accounts_can_pay( bool force_elastic_limits )const { + std::tuple transaction_context::max_bandwidth_billed_accounts_can_pay( bool force_elastic_limits ) const{ // Assumes rl.update_account_usage( bill_to_accounts, block_timestamp_type(control.pending_block_time()).slot ) was already called prior // Calculate the new highest network usage and CPU time that all of the billed accounts can afford to be billed @@ -385,6 +401,7 @@ namespace eosio { namespace chain { int64_t account_net_limit = large_number_no_overflow; int64_t account_cpu_limit = large_number_no_overflow; bool greylisted = false; + bool greylistedCPU = false; for( const auto& a : bill_to_accounts ) { bool elastic = force_elastic_limits || !(control.is_producing_block() && control.is_resource_greylisted(a)); auto net_limit = rl.get_account_net_limit(a, elastic); @@ -393,11 +410,13 @@ namespace eosio { namespace chain { if (!elastic) greylisted = true; } auto cpu_limit = rl.get_account_cpu_limit(a, elastic); - if( cpu_limit >= 0 ) + if( cpu_limit >= 0 ) { account_cpu_limit = std::min( account_cpu_limit, cpu_limit ); + if (!elastic) greylistedCPU = true; + } } - return std::make_tuple(account_net_limit, account_cpu_limit, greylisted); + return std::make_tuple(account_net_limit, account_cpu_limit, greylisted, greylistedCPU); } void transaction_context::dispatch_action( action_trace& trace, const action& a, account_name receiver, bool context_free, uint32_t recurse_depth ) { diff --git a/plugins/http_plugin/http_plugin.cpp b/plugins/http_plugin/http_plugin.cpp index 0a4984381ee..c971f9dd040 100644 --- a/plugins/http_plugin/http_plugin.cpp +++ b/plugins/http_plugin/http_plugin.cpp @@ -468,9 +468,11 @@ namespace eosio { } catch (fc::exception& e) { error_results results{500, "Internal Service Error", error_results::error_info(e, verbose_http_errors)}; cb( 500, fc::json::to_string( results )); - elog( "FC Exception encountered while processing ${api}.${call}", - ("api", api_name)( "call", call_name )); - dlog( "Exception Details: ${e}", ("e", e.to_detail_string())); + if (e.code() != chain::greylist_net_usage_exceeded::code_value && e.code() != chain::greylist_cpu_usage_exceeded::code_value) { + elog( "FC Exception encountered while processing ${api}.${call}", + ("api", api_name)( "call", call_name )); + dlog( "Exception Details: ${e}", ("e", e.to_detail_string())); + } } catch (std::exception& e) { error_results results{500, "Internal Service Error", error_results::error_info(fc::exception( FC_LOG_MESSAGE( error, e.what())), verbose_http_errors)}; cb( 500, fc::json::to_string( results )); diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index eb120521e68..d4764884bfb 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -2492,7 +2492,9 @@ namespace eosio { chain_plug->accept_transaction(msg, [=](const static_variant& result) { if (result.contains()) { auto e_ptr = result.get(); - if (e_ptr->code() != tx_duplicate::code_value && e_ptr->code() != expired_tx_exception::code_value) { + if (e_ptr->code() != tx_duplicate::code_value && e_ptr->code() != expired_tx_exception::code_value + && e_ptr->code() != greylist_net_usage_exceeded::code_value + && e_ptr->code() != greylist_cpu_usage_exceeded::code_value) { elog("accept txn threw ${m}",("m",result.get()->to_detail_string())); peer_elog(c, "bad packed_transaction : ${m}", ("m",result.get()->what())); } From 2732be066c39a8b24964fde85251970f670b6f67 Mon Sep 17 00:00:00 2001 From: arhag Date: Tue, 28 Aug 2018 11:57:03 -0400 Subject: [PATCH 290/294] cleanup of greylisting variables --- libraries/chain/transaction_context.cpp | 28 +++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/libraries/chain/transaction_context.cpp b/libraries/chain/transaction_context.cpp index cceb2835af6..ff00ce8ecbe 100644 --- a/libraries/chain/transaction_context.cpp +++ b/libraries/chain/transaction_context.cpp @@ -92,10 +92,10 @@ namespace eosio { namespace chain { // Calculate the highest network usage and CPU time that all of the billed accounts can afford to be billed int64_t account_net_limit = 0; int64_t account_cpu_limit = 0; - bool greylisted = false, greylistedCPU = false; - std::tie( account_net_limit, account_cpu_limit, greylisted, greylistedCPU) = max_bandwidth_billed_accounts_can_pay(); - net_limit_due_to_greylist |= greylisted; - cpu_limit_due_to_greylist |= greylistedCPU; + bool greylisted_net = false, greylisted_cpu = false; + std::tie( account_net_limit, account_cpu_limit, greylisted_net, greylisted_cpu) = max_bandwidth_billed_accounts_can_pay(); + net_limit_due_to_greylist |= greylisted_net; + cpu_limit_due_to_greylist |= greylisted_cpu; eager_net_limit = net_limit; @@ -225,19 +225,21 @@ namespace eosio { namespace chain { // Calculate the new highest network usage and CPU time that all of the billed accounts can afford to be billed int64_t account_net_limit = 0; int64_t account_cpu_limit = 0; - bool greylisted = false, greylistedCPU = false; - std::tie( account_net_limit, account_cpu_limit, greylisted, greylistedCPU) = max_bandwidth_billed_accounts_can_pay(); - net_limit_due_to_greylist |= greylisted; - cpu_limit_due_to_greylist |= greylistedCPU; + bool greylisted_net = false, greylisted_cpu = false; + std::tie( account_net_limit, account_cpu_limit, greylisted_net, greylisted_cpu) = max_bandwidth_billed_accounts_can_pay(); + net_limit_due_to_greylist |= greylisted_net; + cpu_limit_due_to_greylist |= greylisted_cpu; // Possibly lower net_limit to what the billed accounts can pay if( static_cast(account_net_limit) <= net_limit ) { + // NOTE: net_limit may possibly not be objective anymore due to net greylisting, but it should still be no greater than the truly objective net_limit net_limit = static_cast(account_net_limit); net_limit_due_to_block = false; } // Possibly lower objective_duration_limit to what the billed accounts can pay if( account_cpu_limit <= objective_duration_limit.count() ) { + // NOTE: objective_duration_limit may possibly not be objective anymore due to cpu greylisting, but it should still be no greater than the truly objective objective_duration_limit objective_duration_limit = fc::microseconds(account_cpu_limit); billing_timer_exception_code = tx_cpu_usage_exceeded::code_value; } @@ -400,23 +402,23 @@ namespace eosio { namespace chain { const static int64_t large_number_no_overflow = std::numeric_limits::max()/2; int64_t account_net_limit = large_number_no_overflow; int64_t account_cpu_limit = large_number_no_overflow; - bool greylisted = false; - bool greylistedCPU = false; + bool greylisted_net = false; + bool greylisted_cpu = false; for( const auto& a : bill_to_accounts ) { bool elastic = force_elastic_limits || !(control.is_producing_block() && control.is_resource_greylisted(a)); auto net_limit = rl.get_account_net_limit(a, elastic); if( net_limit >= 0 ) { account_net_limit = std::min( account_net_limit, net_limit ); - if (!elastic) greylisted = true; + if (!elastic) greylisted_net = true; } auto cpu_limit = rl.get_account_cpu_limit(a, elastic); if( cpu_limit >= 0 ) { account_cpu_limit = std::min( account_cpu_limit, cpu_limit ); - if (!elastic) greylistedCPU = true; + if (!elastic) greylisted_cpu = true; } } - return std::make_tuple(account_net_limit, account_cpu_limit, greylisted, greylistedCPU); + return std::make_tuple(account_net_limit, account_cpu_limit, greylisted_net, greylisted_cpu); } void transaction_context::dispatch_action( action_trace& trace, const action& a, account_name receiver, bool context_free, uint32_t recurse_depth ) { From 083935ea0e4fa93e1a4a101164fe3e6182c8fcac Mon Sep 17 00:00:00 2001 From: arhag Date: Tue, 28 Aug 2018 15:07:04 -0400 Subject: [PATCH 291/294] added test case involving a deferred transaction canceling itself --- contracts/test_api/test_api.cpp | 1 + contracts/test_api/test_api.hpp | 1 + contracts/test_api/test_transaction.cpp | 20 ++++++++++++++++++++ unittests/api_tests.cpp | 20 ++++++++++++++++++++ 4 files changed, 42 insertions(+) diff --git a/contracts/test_api/test_api.cpp b/contracts/test_api/test_api.cpp index 29f47d75f64..8f1922f2d4f 100644 --- a/contracts/test_api/test_api.cpp +++ b/contracts/test_api/test_api.cpp @@ -148,6 +148,7 @@ extern "C" { WASM_TEST_HANDLER(test_transaction, context_free_api); WASM_TEST_HANDLER(test_transaction, new_feature); WASM_TEST_HANDLER(test_transaction, active_new_feature); + WASM_TEST_HANDLER_EX(test_transaction, repeat_deferred_transaction); //test chain WASM_TEST_HANDLER(test_chain, test_activeprods); diff --git a/contracts/test_api/test_api.hpp b/contracts/test_api/test_api.hpp index b026642175e..a8fa3f21d65 100644 --- a/contracts/test_api/test_api.hpp +++ b/contracts/test_api/test_api.hpp @@ -181,6 +181,7 @@ struct test_transaction { static void context_free_api(); static void new_feature(); static void active_new_feature(); + static void repeat_deferred_transaction(uint64_t receiver, uint64_t code, uint64_t action); }; struct test_chain { diff --git a/contracts/test_api/test_transaction.cpp b/contracts/test_api/test_transaction.cpp index b481e9335d4..95030419f58 100644 --- a/contracts/test_api/test_transaction.cpp +++ b/contracts/test_api/test_transaction.cpp @@ -317,3 +317,23 @@ void test_transaction::new_feature() { void test_transaction::active_new_feature() { activate_feature((int64_t)N(newfeature)); } + +void test_transaction::repeat_deferred_transaction(uint64_t receiver, uint64_t code, uint64_t action) { + using namespace eosio; + + uint128_t sender_id = 0; + + uint32_t payload = unpack_action_data(); + print( "repeat_deferred_transaction called: payload = ", payload ); + + bool res = cancel_deferred( sender_id ); + + print( "\nrepeat_deferred_transaction cancelled trx with sender_id = ", sender_id, ", result is ", res ); + + if( payload == 0 ) return; + + --payload; + transaction trx; + trx.actions.emplace_back( permission_level{receiver, N(active)}, code, action, payload ); + trx.send( sender_id, receiver ); +} diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 2ddf0b34253..8b2be0ad065 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -1119,7 +1119,27 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try { produce_blocks(10); + //repeated deferred transactions { + vector traces; + auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { + if (t && t->scheduled) { + traces.push_back( t ); + } + } ); + + CALL_TEST_FUNCTION(*this, "test_transaction", "repeat_deferred_transaction", fc::raw::pack( (uint32_t)5 ) ); + + produce_block(); + + c.disconnect(); + + BOOST_CHECK_EQUAL( traces.size(), 5 ); + } + + produce_blocks(10); + +{ // Trigger a tx which in turn sends a deferred tx with payer != receiver // Payer is alice in this case, this tx should fail since we don't have the authorization of alice dtt_action dtt_act1; From 538322410404ba86eb60e5e8ab45b09a2e8bf3a0 Mon Sep 17 00:00:00 2001 From: Bucky Kittinger Date: Tue, 28 Aug 2018 15:15:10 -0400 Subject: [PATCH 292/294] changed exception message --- libraries/chain/transaction_context.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/transaction_context.cpp b/libraries/chain/transaction_context.cpp index ff00ce8ecbe..fc790b2f496 100644 --- a/libraries/chain/transaction_context.cpp +++ b/libraries/chain/transaction_context.cpp @@ -302,7 +302,7 @@ namespace eosio { namespace chain { } else if( deadline_exception_code == tx_cpu_usage_exceeded::code_value ) { if (cpu_limit_due_to_greylist) { EOS_THROW( greylist_cpu_usage_exceeded, - "transaction was executing for too long", + "greylisted transaction was executing for too long", ("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) ); } else { EOS_THROW( tx_cpu_usage_exceeded, From 1978a79bb892688815c56251935eafa95e60cdf5 Mon Sep 17 00:00:00 2001 From: arhag Date: Tue, 28 Aug 2018 15:15:38 -0400 Subject: [PATCH 293/294] distinguish exception messages of greylisted vs non-greylisted resource exhausted errors --- libraries/chain/transaction_context.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/chain/transaction_context.cpp b/libraries/chain/transaction_context.cpp index ff00ce8ecbe..64683ebc049 100644 --- a/libraries/chain/transaction_context.cpp +++ b/libraries/chain/transaction_context.cpp @@ -277,11 +277,11 @@ namespace eosio { namespace chain { ("net_usage", net_usage)("net_limit", eager_net_limit) ); } else if (net_limit_due_to_greylist) { EOS_THROW( greylist_net_usage_exceeded, - "net usage of transaction is too high: ${net_usage} > ${net_limit}", + "greylisted transaction net usage is too high: ${net_usage} > ${net_limit}", ("net_usage", net_usage)("net_limit", eager_net_limit) ); } else { EOS_THROW( tx_net_usage_exceeded, - "net usage of transaction is too high: ${net_usage} > ${net_limit}", + "transaction net usage is too high: ${net_usage} > ${net_limit}", ("net_usage", net_usage)("net_limit", eager_net_limit) ); } } @@ -302,7 +302,7 @@ namespace eosio { namespace chain { } else if( deadline_exception_code == tx_cpu_usage_exceeded::code_value ) { if (cpu_limit_due_to_greylist) { EOS_THROW( greylist_cpu_usage_exceeded, - "transaction was executing for too long", + "greylisted transaction was executing for too long", ("now", now)("deadline", _deadline)("start", start)("billing_timer", now - pseudo_start) ); } else { EOS_THROW( tx_cpu_usage_exceeded, From 281ab1a6b3cc5cda89212990206db1078f4397a5 Mon Sep 17 00:00:00 2001 From: arhag Date: Tue, 28 Aug 2018 16:46:03 -0400 Subject: [PATCH 294/294] bump version to 1.2.3 --- CMakeLists.txt | 2 +- Docker/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76f7afa7a91..0f84b7a88bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ set( CXX_STANDARD_REQUIRED ON) set(VERSION_MAJOR 1) set(VERSION_MINOR 2) -set(VERSION_PATCH 2) +set(VERSION_PATCH 3) set( CLI_CLIENT_EXECUTABLE_NAME cleos ) set( NODE_EXECUTABLE_NAME nodeos ) diff --git a/Docker/README.md b/Docker/README.md index 4bd51558e36..cfeb4a9da4b 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -20,10 +20,10 @@ cd eos/Docker docker build . -t eosio/eos ``` -The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the v1.2.2 tag, you could do the following: +The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the v1.2.3 tag, you could do the following: ```bash -docker build -t eosio/eos:v1.2.2 --build-arg branch=v1.2.2 . +docker build -t eosio/eos:v1.2.3 --build-arg branch=v1.2.3 . ``` By default, the symbol in eosio.system is set to SYS. You can override this using the symbol argument while building the docker image.