diff --git a/.qoder/docs/witness-guard-spec.json b/.qoder/docs/witness-guard-spec.json new file mode 100644 index 0000000000..34a9730237 --- /dev/null +++ b/.qoder/docs/witness-guard-spec.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "witness-guard-spec.json", + "title": "Witness Guard Plugin Specification", + "description": "Technical specification for the automated witness key restoration plugin", + "version": "1.0.0", + + "definitions": { + "witness_entry": { + "type": "array", + "description": "A JSON array representing a witness configuration triplet", + "items": [ + { "name": "account_name", "type": "string", "description": "The name of the witness account to monitor" }, + { "name": "signing_wif", "type": "string", "description": "The WIF private key to be restored as the signing key" }, + { "name": "active_wif", "type": "string", "description": "The WIF private key with active authority to sign the update transaction" } + ], + "example": "[\"mywitness\", \"5Ksigning...\", \"5Kactive...\"]" + } + }, + + "configuration": { + "options": [ + { + "name": "witness-guard-enabled", + "type": "boolean", + "default": true, + "description": "Global toggle for the plugin logic" + }, + { + "name": "witness-guard-witness", + "type": "vector", + "description": "List of witness triplets in JSON format. Can be specified multiple times.", + "ref": "#/definitions/witness_entry" + }, + { + "name": "witness-guard-interval", + "type": "uint32", + "default": 20, + "description": "Frequency of checks measured in blocks (20 blocks is approx. 60 seconds)" + } + ] + }, + + "algorithms": { + "check_and_restore": { + "description": "Iterative process to detect and fix null signing keys", + "steps": [ + { "step": 1, "action": "Verify node sync: head_block_time must be within 2 * CHAIN_BLOCK_INTERVAL" }, + { "step": 2, "action": "Fetch witness index from database" }, + { "step": 3, "action": "For each configured witness: find record by name" }, + { "step": 4, "condition": "signing_key is NOT null", "action": "Remove witness from 'restore_sent' cache to allow future monitoring" }, + { "step": 5, "condition": "signing_key IS null AND NOT in 'restore_sent'", "action": "Invoke send_witness_update" } + ] + }, + + "send_witness_update": { + "description": "Constructs and broadcasts a witness_update_operation", + "steps": [ + { "step": 1, "action": "Extract current URL from the chain object (preserves metadata)" }, + { "step": 2, "action": "Create witness_update_operation with target signing_pub_key" }, + { "step": 3, "action": "Wrap operation in a signed_transaction" }, + { "step": 4, "action": "Set expiration (30s) and reference block (head)" }, + { "step": 5, "action": "Sign with the configured active_private_key" }, + { "step": 6, "action": "Broadcast via P2P plugin" }, + { "step": 7, "action": "Add witness name to 'restore_sent' cache to prevent duplicate broadcasts" } + ] + } + }, + + "internal_state": { + "restore_sent": { + "type": "std::set", + "description": "In-memory cache of witnesses for which a restoration transaction has been broadcast in the current block cycle or since the last key reset" + } + } +} \ No newline at end of file diff --git a/.qoder/docs/witness-guard.md b/.qoder/docs/witness-guard.md new file mode 100644 index 0000000000..9e8e9543a5 --- /dev/null +++ b/.qoder/docs/witness-guard.md @@ -0,0 +1,87 @@ +# Witness Guard Plugin + +The `witness_guard` plugin is an automated maintenance tool designed for VIZ witness node operators. Its primary purpose is to monitor configured witness accounts and automatically restore their signing keys if they are reset to a null state (effectively disabling the witness). + +## Purpose + +In Graphene-based networks like VIZ, a witness might be disabled (signing key set to null) due to manual intervention, security protocols, or certain network conditions. If an operator wants to ensure their witness stays active without manual monitoring, this plugin automates the "restore" process. + +When the plugin detects a null signing key on-chain for a monitored account, it constructs, signs, and broadcasts a `witness_update_operation` to re-enable the witness using the provided private keys. + +## Configuration + +The plugin can be configured via the `config.ini` file or command-line arguments. + +### Options + +| Option | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `witness-guard-enabled` | boolean | `true` | Enables or disables the plugin logic globally. | +| `witness-guard-interval` | uint32 | `20` | Frequency of checks measured in blocks (default 20 blocks $\approx$ 60 seconds). | +| `witness-guard-witness` | vector\ | N/A | A JSON triplet containing the witness name, the signing WIF, and the active WIF. | + +### Enabling the Plugin + +To use the plugin, add it to the list of active plugins in your `config.ini`: + +```ini +plugin = witness_guard +``` + +## Usage Example + +To monitor one or more witnesses, add the following lines to your `config.ini`. Note that the witness configuration must be a valid JSON array string. + +```ini +# Automatically restore winet1 +witness-guard-witness = ["winet1", "5K_SIGNING_PRIVATE_WIF", "5K_ACTIVE_PRIVATE_WIF"] + +# You can monitor multiple witnesses by repeating the option +witness-guard-witness = ["winet2", "5J_SIGNING_PRIVATE_WIF", "5J_ACTIVE_PRIVATE_WIF"] + +# Check every 10 blocks instead of 20 +witness-guard-interval = 10 +``` + +## Internal Logic (How it works) + +1. **Sync Check**: The plugin only executes if the node's head block time is within a reasonable range (2 * `CHAIN_BLOCK_INTERVAL`), ensuring it doesn't attempt restores while the node is still catching up to the network. +2. **On-Chain Verification**: Every `X` blocks (configured by interval), the plugin looks up the witness object for the configured account names. +3. **Null Key Detection**: If the `block_signing_key` found on the blockchain matches the `null_key` (all zeros): + * The plugin prepares a `witness_update_operation`. + * It preserves the existing `url` from the on-chain object. + * It sets the `block_signing_key` to the public key derived from the provided `signing_wif`. +4. **Signing and Broadcast**: The transaction is signed using the `active_wif`. This is necessary because updating a witness object requires active authority. +5. **Anti-Spam Protection**: Once a restore transaction is sent, the plugin will not attempt to send another one for that specific witness until the node is restarted or the key is successfully reset on-chain (preventing a loop of transactions if the first one is pending). + +## Security Considerations + +> [!CAUTION] +> **Private Key Exposure** +> This plugin requires the **Active Private Key** to be stored in plain text within your `config.ini`. Because the active key has significant control over your account (including the ability to transfer funds or change permissions), ensure that your `config.ini` file has strictly restricted file system permissions (e.g., `chmod 600 config.ini` on Linux). + +## Logs + +The plugin provides clear feedback in the node logs: + +* **Initialization**: + `witness_guard: monitoring witness 'winet1' (signing key: VIZ...)` +* **Restore Triggered**: + `witness_guard: 'winet1' has null signing key on-chain — initiating restore` +* **Success**: + `witness_guard: witness_update for 'winet1' sent successfully` +* **Failure**: + `witness_guard: witness_update FAILED for 'winet1': [error details]` + +## Troubleshooting + +**Erorr: witness-guard-witness must be triplets** +Ensure each entry is a valid JSON array with exactly 3 strings: `["name", "signing_wif", "active_wif"]`. Ensure you are using double quotes for strings inside the brackets. + +**Restore not triggering** +1. Check if `witness-guard-enabled` is set to `true`. +2. Ensure the node is fully synchronized with the network. +3. Verify that the account name provided is an actual registered witness on the network. + +**Transaction failed** +Check that the `active_wif` provided actually belongs to the witness account. If the account's active authority has been changed, the plugin will be unable to sign the update. \ No newline at end of file diff --git a/build-linux.sh b/build-linux.sh index a621451843..f0eb5c559c 100644 --- a/build-linux.sh +++ b/build-linux.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash + # ============================================================================ # VIZ Linux Build Script # diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp index d56db6311d..94eb0318c6 100644 --- a/libraries/chain/database.cpp +++ b/libraries/chain/database.cpp @@ -1456,22 +1456,6 @@ namespace graphene { namespace chain { throw; } - // Prune stale competing blocks from dead forks at this height. - // A competing block is only safe to remove if its parent is no longer - // in the fork_db — without a parent the fork cannot be extended, so - // the block is truly dead. Removing blocks whose parent is still known - // would break legitimate fork switches when later blocks arrive. - { - auto competing = _fork_db.fetch_block_by_number(new_block.block_num()); - for (const auto& cb : competing) { - if (cb->id != new_block.id() && !_fork_db.is_known_block(cb->data.previous)) { - wlog("Pruning stale competing block ${id} at height ${n} from fork_db (dead fork)", - ("id", cb->id)("n", new_block.block_num())); - _fork_db.remove(cb->id); - } - } - } - return false; } FC_CAPTURE_AND_RETHROW() } diff --git a/plugins/witness_guard/CMakeLists.txt b/plugins/witness_guard/CMakeLists.txt new file mode 100644 index 0000000000..428e7f0f44 --- /dev/null +++ b/plugins/witness_guard/CMakeLists.txt @@ -0,0 +1,44 @@ +set(CURRENT_TARGET witness_guard) + +list(APPEND CURRENT_TARGET_HEADERS + include/graphene/plugins/witness_guard/witness_guard.hpp + ) + +list(APPEND CURRENT_TARGET_SOURCES + witness_guard.cpp + ) + +if(BUILD_SHARED_LIBRARIES) + add_library(graphene_${CURRENT_TARGET} SHARED + ${CURRENT_TARGET_HEADERS} + ${CURRENT_TARGET_SOURCES} + ) +else() + add_library(graphene_${CURRENT_TARGET} STATIC + ${CURRENT_TARGET_HEADERS} + ${CURRENT_TARGET_SOURCES} + ) +endif() + +add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) +set_property(TARGET graphene_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGET}) + +target_link_libraries( + graphene_${CURRENT_TARGET} + graphene::chain_plugin + graphene::p2p + graphene::protocol + graphene_utilities + graphene_time + appbase +) + +target_include_directories(graphene_${CURRENT_TARGET} + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") + +install(TARGETS + graphene_${CURRENT_TARGET} + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) \ No newline at end of file diff --git a/plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp b/plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp new file mode 100644 index 0000000000..11fdaee2e7 --- /dev/null +++ b/plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +namespace graphene { +namespace plugins { +namespace witness_guard { + +class witness_guard_plugin final + : public appbase::plugin { +public: + APPBASE_PLUGIN_REQUIRES( + (graphene::plugins::chain::plugin) + (graphene::plugins::p2p::p2p_plugin) + ) + + constexpr static const char *plugin_name = "witness_guard"; + + static const std::string &name() { + static std::string name = plugin_name; + return name; + } + + witness_guard_plugin(); + ~witness_guard_plugin(); + + void set_program_options( + boost::program_options::options_description &command_line_options, + boost::program_options::options_description &config_file_options + ) override; + + void plugin_initialize( + const boost::program_options::variables_map &options + ) override; + + void plugin_startup() override; + void plugin_shutdown() override; + +private: + struct impl; + std::unique_ptr pimpl; +}; + +} // witness_guard +} // plugins +} // graphene \ No newline at end of file diff --git a/plugins/witness_guard/witness_guard.cpp b/plugins/witness_guard/witness_guard.cpp new file mode 100644 index 0000000000..22537a44ef --- /dev/null +++ b/plugins/witness_guard/witness_guard.cpp @@ -0,0 +1,382 @@ +#include + +#include +#include +#include +#include +#include +#include +#include // Adăugat pentru conversia fc::shared_string la std::string +#include +#include + +#include + +#include + +namespace graphene { +namespace plugins { +namespace witness_guard { + +namespace bpo = boost::program_options; + +// ─── impl ──────────────────────────────────────────────────────────────────── + +struct witness_guard_plugin::impl { + impl() + : chain_(appbase::app().get_plugin()) + , p2p_(appbase::app().get_plugin()) + {} + + graphene::chain::database& db() { return chain_.db(); } + graphene::chain::database& db() const { return chain_.db(); } + + // ── config ──────────────────────────────────────────────────────────────── + bool _enabled = true; + uint32_t _check_interval = 20; // blocks between checks + bool _initial_check_done = false; // Whether we've detected that the node is synchronized at startup + fc::connection _applied_block_connection; // Connection for applied_block signal + + // --- witness_info struct --- + struct witness_info { + fc::ecc::private_key signing_key; + fc::ecc::private_key active_key; + }; + + // Mapping witness_name -> config (keys) + std::map _witness_configs; + + // Tracking pending restores: witness_name -> expiration_time + std::map _restore_pending; + + // Tracking transaction IDs to confirm their inclusion in a block + std::map> _pending_confirmations; + + // ── core ────────────────────────────────────────────────────────────────── + void check_and_restore(); + bool check_and_restore_internal(); + void send_witness_update(const std::string& witness_name, + const graphene::chain::witness_object& obj, + const witness_info& config); + + graphene::plugins::chain::plugin& chain_; + graphene::plugins::p2p::p2p_plugin& p2p_; +}; + +// ─── check_and_restore ─────────────────────────────────────────────────────── +// Returns true if the node is in sync and a full check was performed, false otherwise. +bool witness_guard_plugin::impl::check_and_restore_internal() { + auto& database = db(); + + // Check only if the node is synchronized + // (head block within the last 2 * CHAIN_BLOCK_INTERVAL seconds) + const auto head_time = database.head_block_time(); + const auto now = fc::time_point_sec(graphene::time::now()); + if (head_time < now - fc::seconds(CHAIN_BLOCK_INTERVAL * 2)) { + dlog("witness_guard: node not in sync, skipping check"); + return false; // Node not in sync, full check not performed + } + + // Detect potential long fork by checking Last Irreversible Block (LIB) age + const auto& dgp = database.get_dynamic_global_properties(); + const uint32_t lib_num = dgp.last_irreversible_block_num; + auto lib_header = database.fetch_block_header_by_number(lib_num); + if (lib_header) { + const auto lib_time = lib_header->timestamp; + // If LIB is older than 200 seconds, we are likely on a long fork or network is stalled + if (now - lib_time > fc::seconds(200)) { + wlog("witness_guard: POTENTIAL LONG FORK DETECTED! LIB #${n} is ${sec}s old. Skipping restoration.", + ("n", lib_num)("sec", (now - lib_time).to_seconds())); + return false; + } + } + + // Clean up _pending_confirmations once per check, not inside the witness loop. + // Removes trackers that have expired or are no longer relevant. + for (auto it = _pending_confirmations.begin(); it != _pending_confirmations.end(); ) { + // If the transaction ID is expired, remove it from confirmation tracking + if (now > it->second.second) { + // If a transaction expires from _pending_confirmations, it means it was not included. + // We should also remove the corresponding entry from _restore_pending to allow a retry + // without waiting for _restore_pending's own expiration. + _restore_pending.erase(it->second.first); + it = _pending_confirmations.erase(it); + } else { + ++it; + } + } + + const auto& idx = database + .get_index() + .indices() + .get(); + + static const graphene::protocol::public_key_type null_key; + + for (const auto& [name, config] : _witness_configs) { + auto itr = idx.find(name); + if (itr == idx.end()) { + wlog("witness_guard: witness '${w}' not found in database", ("w", name)); + continue; + } + + if (itr->signing_key != null_key) { + // Key is healthy on-chain, clear any pending retry state for this witness + _restore_pending.erase(name); + continue; + } + + // If a restore is already in flight and hasn't expired yet, wait. + if (_restore_pending.count(name)) { + if (now <= _restore_pending[name]) continue; + ilog("witness_guard: previous restore for '${w}' expired, retrying", ("w", name)); + } + + ilog("witness_guard: '${w}' has null signing key on-chain — initiating restore", + ("w", name)); + send_witness_update(name, *itr, config); + } + + return true; // Node was in sync, full check performed +} + +// ─── send_witness_update ───────────────────────────────────────────────────── + +void witness_guard_plugin::impl::send_witness_update( + const std::string& witness_name, + const graphene::chain::witness_object& obj, + const witness_info& config) +{ + try { + const auto signing_pub = config.signing_key.get_public_key(); + const auto& active_priv = config.active_key; + + // Construct the operation + graphene::protocol::witness_update_operation op; + op.owner = witness_name; + op.url = std::string(obj.url); // Conversie directă sigură + op.block_signing_key = signing_pub; + + // Set expiration to 30 seconds from now + auto expiration = graphene::time::now() + fc::seconds(30); + + // Construct the transaction + graphene::chain::signed_transaction tx; + tx.operations.push_back(op); + tx.set_expiration(expiration); + tx.set_reference_block(db().head_block_id()); + + // Sign with the active key + tx.sign(active_priv, db().get_chain_id()); // tx.id() is computed here + + const auto tx_id = tx.id(); // Store tx.id() to avoid re-computation + ilog("witness_guard: broadcasting witness_update [ID: ${id}] for '${w}' — restoring key to ${k}", + ("id", tx_id)("w", witness_name)("k", signing_pub)); + + p2p_.broadcast_transaction(tx); + + _restore_pending[witness_name] = expiration; + _pending_confirmations[tx_id] = { witness_name, expiration }; + + ilog("witness_guard: witness_update for '${w}' sent successfully", ("w", witness_name)); + + } catch (const fc::exception& e) { + elog("witness_guard: witness_update FAILED for '${w}': ${e}", + ("w", witness_name)("e", e.to_detail_string())); + // Do not add to _restore_sent — we will retry at the next check + } +} + +// ─── plugin lifecycle ──────────────────────────────────────────────────────── + +witness_guard_plugin::witness_guard_plugin() = default; +witness_guard_plugin::~witness_guard_plugin() = default; + +void witness_guard_plugin::set_program_options( + bpo::options_description& cli, + bpo::options_description& cfg) +{ + cfg.add_options() + ("witness-guard-enabled", + bpo::value()->default_value(true), + "Enable witness key auto-restore. " + "When true, the plugin monitors configured witnesses and sends " + "witness_update if the on-chain signing key is reset to null.") + + ("witness-guard-witness", + bpo::value>()->composing()->multitoken(), + "Witness to monitor: name signing_wif active_wif (triplets). Can be specified multiple times.") + + ("witness-guard-interval", + bpo::value()->default_value(20), + "How often to check witness signing keys, in blocks (default: 20 ≈ 60s).") + ; + + cli.add(cfg); +} + +void witness_guard_plugin::plugin_initialize( + const bpo::variables_map& options) +{ + try { + ilog("witness_guard: plugin_initialize() begin"); + pimpl = std::make_unique(); + + // enabled flag + if (options.count("witness-guard-enabled")) { + pimpl->_enabled = options["witness-guard-enabled"].as(); + } + if (!pimpl->_enabled) { + ilog("witness_guard: disabled via config, skipping initialization"); + return; + } + + // interval + pimpl->_check_interval = options["witness-guard-interval"].as(); + if (pimpl->_check_interval == 0) pimpl->_check_interval = 1; + + // witness configs (triplets) + if (options.count("witness-guard-witness")) { + const auto& entries = options["witness-guard-witness"].as>(); + for (const auto& entry : entries) { + try { + // Parse each line as a JSON array: ["name", "signing_wif", "active_wif"] + auto arr = fc::json::from_string(entry).get_array(); + FC_ASSERT(arr.size() == 3, "witness-guard-witness expects [name, signing_wif, active_wif]"); + + std::string name = arr[0].as_string(); + auto sign_priv = graphene::utilities::wif_to_key(arr[1].as_string()); + auto active_priv = graphene::utilities::wif_to_key(arr[2].as_string()); + + FC_ASSERT(sign_priv.valid(), "witness-guard-witness: invalid signing WIF for ${n}", ("n", name)); + FC_ASSERT(active_priv.valid(), "witness-guard-witness: invalid active WIF for ${n}", ("n", name)); + + + + pimpl->_witness_configs[name] = { *sign_priv, *active_priv }; + + ilog("witness_guard: monitoring witness '${w}' (signing key: ${k})", + ("w", name)("k", sign_priv->get_public_key())); + + } catch (const fc::exception& e) { + elog("witness_guard: failed to parse witness entry '${entry}': ${e}", + ("entry", entry)("e", e.to_detail_string())); + } + } + } + + if (pimpl->_witness_configs.empty()) { + wlog("witness_guard: no witnesses configured for monitoring"); + } + + ilog("witness_guard: plugin_initialize() end — " + "monitoring ${n} witness(es), interval=${i} blocks", + ("n", pimpl->_witness_configs.size())("i", pimpl->_check_interval)); + + } FC_LOG_AND_RETHROW() +} + +void witness_guard_plugin::plugin_startup() { + ilog("witness_guard: plugin_startup() begin"); + + if (!pimpl->_enabled || pimpl->_witness_configs.empty()) { + ilog("witness_guard: nothing to monitor, plugin inactive"); + return; + } + // --- NEW: Authority Check moved here --- + // At this point, the chain_plugin has started and the database is open. + for (auto it = pimpl->_witness_configs.begin(); it != pimpl->_witness_configs.end(); ) { + const std::string& name = it->first; + try { + const auto& account_obj = pimpl->db().get_account(name); + const auto active_pub_key = it->second.active_key.get_public_key(); + bool active_key_has_authority = false; + for (const auto& auth : account_obj.active.key_auths) { + if (auth.first == active_pub_key) { + active_key_has_authority = true; + break; + } + } + if (!active_key_has_authority) { + elog("witness_guard: WARNING: Configured active key for witness '${w}' " + "does NOT have authority on-chain. Restoration will fail.", ("w", name)); + } + ++it; + } catch (const graphene::chain::unknown_account_exception& e) { + elog("witness_guard: ERROR: Account '${w}' not found on chain. Removing from monitor.", ("w", name)); + it = pimpl->_witness_configs.erase(it); + } + } + // --- END Authority Check --- + + if (pimpl->_witness_configs.empty()) return; + + // Perform an initial check at startup. + // If the node is already synchronized, mark the initial check as completed. + // The check_and_restore_internal() function now returns true if the node is in sync. + if (pimpl->check_and_restore_internal()) { + pimpl->_initial_check_done = true; + } + + // Hook on every applied block and store the connection + pimpl->_applied_block_connection = pimpl->db().applied_block.connect( + [this](const graphene::chain::signed_block& b) { + if (!pimpl->_enabled) return; + + // 1. Check for transaction confirmations in the new block + if (!pimpl->_pending_confirmations.empty()) { + for (const auto& tx : b.transactions) { + auto it = pimpl->_pending_confirmations.find(tx.id()); + if (it != pimpl->_pending_confirmations.end()) { + const auto tx_id = it->first; + const auto w_name = it->second.first; + pimpl->_restore_pending.erase(w_name); + pimpl->_pending_confirmations.erase(it); + ilog("witness_guard: CONFIRMED restoration for '${w}' in block #${n} [TX: ${id}]", + ("w", w_name)("n", b.block_num())("id", tx_id)); + + } + } + } + + // 2. Look-ahead: If any of our witnesses are scheduled in the next 3 slots, check now! + bool scheduled_soon = false; + if (pimpl->_initial_check_done) { + for (uint32_t i = 1; i <= 3; ++i) { + if (pimpl->_witness_configs.count(pimpl->db().get_scheduled_witness(i))) { + scheduled_soon = true; + break; + } + } + } + + // If the node was not synchronized at startup, check on each new block + // until we detect that synchronization has finished. + if (scheduled_soon) { + pimpl->check_and_restore_internal(); + } + else if (!pimpl->_initial_check_done && (b.block_num() % 10 == 0)) { + // The sync check is now inside check_and_restore_internal() + if (pimpl->check_and_restore_internal()) { + pimpl->_initial_check_done = true; + } + } + else if (b.block_num() % pimpl->_check_interval == 0) { + pimpl->check_and_restore_internal(); + } + } +); + + ilog("witness_guard: plugin_startup() end — active"); +} + +void witness_guard_plugin::plugin_shutdown() { + if (pimpl && pimpl->_applied_block_connection.connected()) { + pimpl->_applied_block_connection.disconnect(); + } + ilog("witness_guard: plugin_shutdown()"); +} + +} // witness_guard +} // plugins +} // graphene \ No newline at end of file diff --git a/programs/vizd/CMakeLists.txt b/programs/vizd/CMakeLists.txt index 68e43f4cc8..8126b4c519 100644 --- a/programs/vizd/CMakeLists.txt +++ b/programs/vizd/CMakeLists.txt @@ -42,6 +42,7 @@ target_link_libraries( graphene::paid_subscription_api graphene::custom_protocol_api graphene::snapshot + graphene::witness_guard ${MONGO_LIB} graphene_protocol fc diff --git a/programs/vizd/main.cpp b/programs/vizd/main.cpp index 42b19ce8e1..ccc19c6f3f 100644 --- a/programs/vizd/main.cpp +++ b/programs/vizd/main.cpp @@ -30,6 +30,8 @@ #include #include +#include + #include #include #include @@ -88,6 +90,7 @@ namespace graphene { appbase::app().register_plugin(); appbase::app().register_plugin(); appbase::app().register_plugin(); + appbase::app().register_plugin(); ///plugins }; } diff --git a/share/vizd/docker/Dockerfile-production b/share/vizd/docker/Dockerfile-production index 4c7a953261..8e74297255 100644 --- a/share/vizd/docker/Dockerfile-production +++ b/share/vizd/docker/Dockerfile-production @@ -66,7 +66,8 @@ RUN \ -DENABLE_MONGO_PLUGIN=FALSE \ .. \ && \ - make -j$(nproc) + make -j$(($(nproc)-22)) + RUN set -xe ;\ cd $APPDIR/build ;\ diff --git a/thirdparty/appbase b/thirdparty/appbase index 0bbe811c3e..02476aaf29 160000 --- a/thirdparty/appbase +++ b/thirdparty/appbase @@ -1 +1 @@ -Subproject commit 0bbe811c3e54ae6c7839de634120450bc6535e82 +Subproject commit 02476aaf2958c40f8eaf48aee642b59a2a3df5c7 diff --git a/thirdparty/chainbase b/thirdparty/chainbase index 8a9097080b..c8c527e567 160000 --- a/thirdparty/chainbase +++ b/thirdparty/chainbase @@ -1 +1 @@ -Subproject commit 8a9097080b8ff48984572934d6765ad9ed6860ca +Subproject commit c8c527e56740857e29656eee4ba9f88c63063a1b diff --git a/thirdparty/fc b/thirdparty/fc index fa5b5001af..ace1f68986 160000 --- a/thirdparty/fc +++ b/thirdparty/fc @@ -1 +1 @@ -Subproject commit fa5b5001afcdbb60667dc3a4db4acd5f907430e8 +Subproject commit ace1f68986bbb228fd8ce2522f5e6629426a3b01