From beb299e69ee1af7ec4e46889191051ce49dd1d50 Mon Sep 17 00:00:00 2001 From: Philip Robinson Date: Thu, 3 Feb 2022 12:27:25 +0200 Subject: [PATCH] fix: coinbase output recovery bug (#3789) Description --- A bug was discovered when recovering Coinbase outputs that was revealed when the Key Manager was fixed to actually use branch seeds. Coinbase output keys are derived on a separate key manager branch so when the UTXO scanner tried to update the key manager index it would not find the coinabse key in the main spending key branch which caused an error. This PR updates the UTXO scanner to check if a recovered output has the coinbase flag or not and then searches on the Coinbase branch when looking for that outputs key index and script key. How Has This Been Tested? --- The PR updates the `Wallet recovery with connected base node staying online` cucumber test to include recovering some coinbase outputs. The PR also updates the `Multiple Wallet recovery from seed node` cucumber test to work now that the coinbase issue is fixed. The test is also updated so that it recovered N distinct wallets to the same seed node where as before it would create N wallet with the same seed words and thus network identity. --- .../transaction/output_features.rs | 4 + .../src/output_manager_service/error.rs | 2 + .../master_key_manager.rs | 69 ++++++++++---- .../recovery/standard_outputs_recoverer.rs | 37 +++++--- .../features/WalletRecovery.feature | 30 +++---- .../features/support/mining_node_steps.js | 30 +++++++ .../features/support/node_steps.js | 31 ++----- .../features/support/wallet_steps.js | 89 ++++++++++++++----- integration_tests/features/support/world.js | 26 +++++- integration_tests/package-lock.json | 84 ++++++++++++----- 10 files changed, 288 insertions(+), 114 deletions(-) diff --git a/base_layer/core/src/transactions/transaction/output_features.rs b/base_layer/core/src/transactions/transaction/output_features.rs index 9effe8a30e..f7f4fc0793 100644 --- a/base_layer/core/src/transactions/transaction/output_features.rs +++ b/base_layer/core/src/transactions/transaction/output_features.rs @@ -209,6 +209,10 @@ impl OutputFeatures { pub fn is_non_fungible_burn(&self) -> bool { self.flags.contains(OutputFlags::BURN_NON_FUNGIBLE) } + + pub fn is_coinbase(&self) -> bool { + self.flags.contains(OutputFlags::COINBASE_OUTPUT) + } } impl ConsensusEncoding for OutputFeatures { diff --git a/base_layer/wallet/src/output_manager_service/error.rs b/base_layer/wallet/src/output_manager_service/error.rs index b1175ebd6e..8c0390566b 100644 --- a/base_layer/wallet/src/output_manager_service/error.rs +++ b/base_layer/wallet/src/output_manager_service/error.rs @@ -120,6 +120,8 @@ pub enum OutputManagerError { }, #[error("Invalid message received:{0}")] InvalidMessageError(String), + #[error("Operation not support on this Key Manager branch")] + KeyManagerBranchNotSupported, } #[derive(Debug, Error)] diff --git a/base_layer/wallet/src/output_manager_service/master_key_manager.rs b/base_layer/wallet/src/output_manager_service/master_key_manager.rs index eaaaaaa892..c796625896 100644 --- a/base_layer/wallet/src/output_manager_service/master_key_manager.rs +++ b/base_layer/wallet/src/output_manager_service/master_key_manager.rs @@ -20,6 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::fmt::{Display, Error, Formatter}; + use futures::lock::Mutex; use log::*; use tari_common_types::types::{PrivateKey, PublicKey}; @@ -41,14 +43,32 @@ use crate::{ }; const LOG_TARGET: &str = "wallet::output_manager_service::master_key_manager"; - -const KEY_MANAGER_COINBASE_BRANCH_KEY: &str = "coinbase"; -const KEY_MANAGER_COINBASE_SCRIPT_BRANCH_KEY: &str = "coinbase_script"; -const KEY_MANAGER_SCRIPT_BRANCH_KEY: &str = "script"; -const KEY_MANAGER_RECOVERY_VIEWONLY_BRANCH_KEY: &str = "recovery_viewonly"; -const KEY_MANAGER_RECOVERY_BLINDING_BRANCH_KEY: &str = "recovery_blinding"; const KEY_MANAGER_MAX_SEARCH_DEPTH: u64 = 1_000_000; +#[derive(Clone, Copy)] +pub enum KeyManagerBranch { + Spend, + SpendScript, + Coinbase, + CoinbaseScript, + RecoveryViewOnly, + RecoveryBlinding, +} + +impl Display for KeyManagerBranch { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> { + let response = match self { + KeyManagerBranch::Spend => "", + KeyManagerBranch::SpendScript => "script", + KeyManagerBranch::Coinbase => "coinbase", + KeyManagerBranch::CoinbaseScript => "coinbase_script", + KeyManagerBranch::RecoveryViewOnly => "recovery_viewonly", + KeyManagerBranch::RecoveryBlinding => "recovery_blinding", + }; + fmt.write_str(response) + } +} + pub(crate) struct MasterKeyManager { utxo_key_manager: Mutex>, utxo_script_key_manager: Mutex>, @@ -67,7 +87,7 @@ where TBackend: OutputManagerBackend + 'static None => { let starting_state = KeyManagerState { seed: master_seed, - branch_seed: "".to_string(), + branch_seed: KeyManagerBranch::Spend.to_string(), primary_key_index: 0, }; db.set_key_manager_state(starting_state.clone()).await?; @@ -89,32 +109,32 @@ where TBackend: OutputManagerBackend + 'static let utxo_script_key_manager = KeyManager::::from( key_manager_state.seed.clone(), - KEY_MANAGER_SCRIPT_BRANCH_KEY.to_string(), + KeyManagerBranch::SpendScript.to_string(), key_manager_state.primary_key_index, ); let coinbase_key_manager = KeyManager::::from( key_manager_state.seed.clone(), - KEY_MANAGER_COINBASE_BRANCH_KEY.to_string(), + KeyManagerBranch::Coinbase.to_string(), 0, ); let coinbase_script_key_manager = KeyManager::::from( key_manager_state.seed.clone(), - KEY_MANAGER_COINBASE_SCRIPT_BRANCH_KEY.to_string(), + KeyManagerBranch::CoinbaseScript.to_string(), 0, ); let rewind_key_manager = KeyManager::::from( key_manager_state.seed.clone(), - KEY_MANAGER_RECOVERY_VIEWONLY_BRANCH_KEY.to_string(), + KeyManagerBranch::RecoveryViewOnly.to_string(), 0, ); let rewind_key = rewind_key_manager.derive_key(0)?.k; let rewind_blinding_key_manager = KeyManager::::from( key_manager_state.seed, - KEY_MANAGER_RECOVERY_BLINDING_BRANCH_KEY.to_string(), + KeyManagerBranch::RecoveryBlinding.to_string(), 0, ); let rewind_blinding_key = rewind_blinding_key_manager.derive_key(0)?.k; @@ -158,6 +178,12 @@ where TBackend: OutputManagerBackend + 'static Ok(script_key.k) } + pub async fn get_coinbase_script_key_at_index(&self, index: u64) -> Result { + let skm = self.coinbase_script_key_manager.lock().await; + let script_key = skm.derive_key(index)?; + Ok(script_key.k) + } + pub async fn get_coinbase_spend_and_script_key_for_height( &self, height: u64, @@ -185,14 +211,19 @@ where TBackend: OutputManagerBackend + 'static } } - /// Search the current key manager key chain to find the index of the specified key. - pub async fn find_utxo_key_index(&self, key: PrivateKey) -> Result { - let utxo_key_manager = self.utxo_key_manager.lock().await; - let current_index = (*utxo_key_manager).key_index(); + /// Search the specified branch key manager key chain to find the index of the specified key. + pub async fn find_key_index(&self, key: PrivateKey, branch: KeyManagerBranch) -> Result { + let key_manager = match branch { + KeyManagerBranch::Spend => self.utxo_key_manager.lock().await, + KeyManagerBranch::Coinbase => self.coinbase_key_manager.lock().await, + _ => return Err(OutputManagerError::KeyManagerBranchNotSupported), + }; + + let current_index = (*key_manager).key_index(); for i in 0u64..current_index + KEY_MANAGER_MAX_SEARCH_DEPTH { - if (*utxo_key_manager).derive_key(i)?.k == key { - trace!(target: LOG_TARGET, "Key found in Key Chain at index {}", i); + if (*key_manager).derive_key(i)?.k == key { + trace!(target: LOG_TARGET, "Key found in {} Key Chain at index {}", branch, i); return Ok(i); } } @@ -201,7 +232,7 @@ where TBackend: OutputManagerBackend + 'static } /// If the supplied index is higher than the current UTXO key chain indices then they will be updated. - pub async fn update_current_index_if_higher(&self, index: u64) -> Result<(), OutputManagerError> { + pub async fn update_current_spend_key_index_if_higher(&self, index: u64) -> Result<(), OutputManagerError> { let mut utxo_key_manager = self.utxo_key_manager.lock().await; let mut utxo_script_key_manager = self.utxo_script_key_manager.lock().await; let current_index = (*utxo_key_manager).key_index(); diff --git a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs index f1f7c73f83..d6f4200e0c 100644 --- a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs +++ b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs @@ -37,6 +37,7 @@ use tari_crypto::{ use crate::output_manager_service::{ error::{OutputManagerError, OutputManagerStorageError}, + master_key_manager::KeyManagerBranch, storage::{ database::{OutputManagerBackend, OutputManagerDatabase}, models::DbUnblindedOutput, @@ -164,18 +165,30 @@ where TBackend: OutputManagerBackend + 'static &mut self, output: &mut UnblindedOutput, ) -> Result<(), OutputManagerError> { - let found_index = self - .master_key_manager - .find_utxo_key_index(output.spending_key.clone()) - .await?; - - self.master_key_manager - .update_current_index_if_higher(found_index) - .await?; - - let script_private_key = self.master_key_manager.get_script_key_at_index(found_index).await?; - output.input_data = inputs!(PublicKey::from_secret_key(&script_private_key)); - output.script_private_key = script_private_key; + let script_key = if output.features.is_coinbase() { + let found_index = self + .master_key_manager + .find_key_index(output.spending_key.clone(), KeyManagerBranch::Coinbase) + .await?; + + self.master_key_manager + .get_coinbase_script_key_at_index(found_index) + .await? + } else { + let found_index = self + .master_key_manager + .find_key_index(output.spending_key.clone(), KeyManagerBranch::Spend) + .await?; + + self.master_key_manager + .update_current_spend_key_index_if_higher(found_index) + .await?; + + self.master_key_manager.get_script_key_at_index(found_index).await? + }; + + output.input_data = inputs!(PublicKey::from_secret_key(&script_key)); + output.script_private_key = script_key; Ok(()) } } diff --git a/integration_tests/features/WalletRecovery.feature b/integration_tests/features/WalletRecovery.feature index bda987d481..55a7eb97a6 100644 --- a/integration_tests/features/WalletRecovery.feature +++ b/integration_tests/features/WalletRecovery.feature @@ -13,41 +13,39 @@ Feature: Wallet Recovery When I wait for wallet WALLET_A to have at least 55000000000 uT Then all nodes are at height 15 And I send 200000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + And I have mining node MINER_B connected to base node NODE and wallet WALLET_B + When mining node MINER_B mines 2 blocks When I mine 5 blocks on NODE - Then all nodes are at height 20 + Then all nodes are at height 22 Then I stop wallet WALLET_B When I recover wallet WALLET_B into wallet WALLET_C connected to all seed nodes - When I wait for wallet WALLET_C to have at least 200000 uT + When I wait for wallet WALLET_C to have at least 10000200000 uT And I have wallet WALLET_D connected to all seed nodes And I send 100000 uT from wallet WALLET_C to wallet WALLET_D at fee 100 When I mine 5 blocks on NODE - Then all nodes are at height 25 + Then all nodes are at height 27 Then I wait for wallet WALLET_D to have at least 100000 uT - @broken Scenario Outline: Multiple Wallet recovery from seed node Given I have a seed node NODE - And I have wallet WALLET_A connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - When mining node MINER mines 15 blocks - When I wait for wallet WALLET_A to have at least 55000000000 uT - Then all nodes are at height 15 - Then I stop wallet WALLET_A - When I recover wallet WALLET_A into wallets connected to all seed nodes - When I wait for wallets to have at least 55000000000 uT - # TODO: having multiple wallet with the same network id is problematic, use N separate wallets or ensure that both are not trying to connect to the same base node - # Then Wallet WALLET_A and wallets have the same balance + And I have non-default wallets connected to all seed nodes using DirectAndStoreAndForward + And I have individual mining nodes connected to each wallet and base node NODE + Then I have each mining node mine 3 blocks + Then all nodes are at height 3* + Then I stop all wallets + When I recover all wallets connected to all seed nodes + Then I wait for recovered wallets to have at least 15000000000 uT @critical Examples: | NumWallets | - | 1 | + | 4 | @long-running Examples: | NumWallets | - | 2 | | 5 | | 10 | + | 20 | @critical diff --git a/integration_tests/features/support/mining_node_steps.js b/integration_tests/features/support/mining_node_steps.js index d1c82a05ef..add119e098 100644 --- a/integration_tests/features/support/mining_node_steps.js +++ b/integration_tests/features/support/mining_node_steps.js @@ -103,6 +103,36 @@ Given( } ); +Given( + /I have individual mining nodes connected to each wallet and (?:base|seed) node (.*)/, + async function (node) { + let walletNames = Object.keys(this.wallets); + const promises = []; + for (let i = 0; i < walletNames.length; i++) { + let name = "Miner_" + String(i).padStart(2, "0"); + promises.push(this.createMiningNode(name, node, walletNames[i])); + } + await Promise.all(promises); + } +); + +Given( + /I have each mining node mine (\d+) blocks?$/, + { timeout: 1200 * 1000 }, // Must allow many blocks to be mined; dynamic time out below limits actual time + async function (numBlocks) { + let miningNodes = Object.keys(this.miners); + for (let i = 0; i < miningNodes.length; i++) { + console.log("MN", miningNodes[i]); + const miningNode = this.getMiningNode(miningNodes[i]); + await miningNode.init(numBlocks, null, 1, i + 2, false, null); + await withTimeout( + (10 + parseInt(numBlocks * miningNodes.length) * 1) * 1000, + await miningNode.startNew() + ); + } + } +); + Given( /I have mine-before-tip mining node (.*) connected to base node (.*) and wallet (.*)/, function (miner, node, wallet) { diff --git a/integration_tests/features/support/node_steps.js b/integration_tests/features/support/node_steps.js index aa3cccaf72..bc99131ad8 100644 --- a/integration_tests/features/support/node_steps.js +++ b/integration_tests/features/support/node_steps.js @@ -354,28 +354,15 @@ Then( "all nodes are at height {int}", { timeout: 800 * 1000 }, async function (height) { - await waitFor( - async () => { - let result = true; - await this.forEachClientAsync(async (client, name) => { - await waitFor( - async () => await client.getTipHeight(), - height, - 5 * height * 1000 /* 5 seconds per block */ - ); - const currTip = await client.getTipHeight(); - console.log( - `Node ${name} is at tip: ${currTip} (should be ${height})` - ); - result = result && currTip == height; - }); - return result; - }, - true, - 600 * 1000, - 5 * 1000, - 5 - ); + await this.all_nodes_are_at_height(height); + } +); + +Then( + "all nodes are at height {int}*{int}", + { timeout: 800 * 1000 }, + async function (a, b) { + await this.all_nodes_are_at_height(a * b); } ); diff --git a/integration_tests/features/support/wallet_steps.js b/integration_tests/features/support/wallet_steps.js index ebf857f8d2..1828e32fc4 100644 --- a/integration_tests/features/support/wallet_steps.js +++ b/integration_tests/features/support/wallet_steps.js @@ -207,7 +207,7 @@ Given( // mechanism: DirectOnly, StoreAndForwardOnly, DirectAndStoreAndForward const promises = []; for (let i = 0; i < n; i++) { - let name = "Wallet_" + String(n).padStart(2, "0"); + let name = "Wallet_" + String(i).padStart(2, "0"); promises.push( this.createAndAddWallet(name, [this.seedAddresses()], { routingMechanism: mechanism, @@ -248,6 +248,42 @@ Given( } ); +Given( + /I recover all wallets connected to all seed nodes/, + { timeout: 120 * 1000 }, + async function () { + for (let walletName in this.wallets) { + let wallet = this.getWallet(walletName); + const seedWords = wallet.getSeedWords(); + let recoveredWalletName = "recovered_" + wallet.name; + console.log( + "Recover " + + wallet.name + + " into " + + recoveredWalletName + + ", seed words:\n " + + seedWords + ); + const walletB = new WalletProcess( + recoveredWalletName, + false, + {}, + this.logFilePathWallet, + seedWords + ); + + walletB.setPeerSeeds([this.seedAddresses()]); + await walletB.startNew(); + this.addWallet(recoveredWalletName, walletB); + let walletClient = await this.getWallet( + recoveredWalletName + ).connectClient(); + let walletInfo = await walletClient.identify(); + this.addWalletPubkey(recoveredWalletName, walletInfo.public_key); + } + } +); + Given( /I recover wallet (.*) into (\d+) wallets connected to all seed nodes/, { timeout: 30 * 1000 }, @@ -281,28 +317,34 @@ Given( ); Then( - /I wait for (\d+) wallets to have at least (\d+) uT/, + /I wait for recovered wallets to have at least (\d+) uT/, { timeout: 60 * 1000 }, - async function (numwallets, amount) { - for (let i = 1; i <= numwallets; i++) { - const walletClient = await this.getWallet(i.toString()).connectClient(); - console.log("\n"); - console.log( - "Waiting for wallet " + i + " balance to be at least " + amount + " uT" - ); + async function (amount) { + for (let walletName in this.wallets) { + if (walletName.split("_")[0] == "recovered") { + const walletClient = await this.getWallet(walletName).connectClient(); + console.log("\n"); + console.log( + "Waiting for wallet " + + walletName + + " balance to be at least " + + amount + + " uT" + ); - await waitFor( - async () => walletClient.isBalanceAtLeast(amount), - true, - 20 * 1000, - 5 * 1000, - 5 - ); - consoleLogBalance(await walletClient.getBalance()); - if (!(await walletClient.isBalanceAtLeast(amount))) { - console.log("Balance not adequate!"); + await waitFor( + async () => walletClient.isBalanceAtLeast(amount), + true, + 20 * 1000, + 5 * 1000, + 5 + ); + consoleLogBalance(await walletClient.getBalance()); + if (!(await walletClient.isBalanceAtLeast(amount))) { + console.log("Balance not adequate!"); + } + expect(await walletClient.isBalanceAtLeast(amount)).to.equal(true); } - expect(await walletClient.isBalanceAtLeast(amount)).to.equal(true); } } ); @@ -326,6 +368,13 @@ When(/I stop wallet ([^\s]+)/, async function (walletName) { await wallet.stop(); }); +When(/I stop all wallets/, async function () { + for (let walletName in this.wallets) { + let wallet = this.getWallet(walletName); + await wallet.stop(); + } +}); + When(/I start wallet (.*)/, async function (walletName) { let wallet = this.getWallet(walletName); await wallet.start(); diff --git a/integration_tests/features/support/world.js b/integration_tests/features/support/world.js index 39f3c6d00c..412e288f0f 100644 --- a/integration_tests/features/support/world.js +++ b/integration_tests/features/support/world.js @@ -20,7 +20,6 @@ const { waitFor, sleep, consoleLogBalance } = require("../../helpers/util"); const { PaymentType } = require("../../helpers/types"); const { expect } = require("chai"); const InterfaceFFI = require("../../helpers/ffi/ffiInterface"); -// const InterfaceFFI = require("../../helpers/ffi/ffiInterface"); class CustomWorld { constructor({ attach, parameters }) { @@ -593,6 +592,31 @@ class CustomWorld { } expect(await walletClient.isBalanceAtLeast(amount)).to.equal(true); } + + async all_nodes_are_at_height(height) { + await waitFor( + async () => { + let result = true; + await this.forEachClientAsync(async (client, name) => { + await waitFor( + async () => await client.getTipHeight(), + height, + 5 * height * 1000 /* 5 seconds per block */ + ); + const currTip = await client.getTipHeight(); + console.log( + `Node ${name} is at tip: ${currTip} (should be ${height})` + ); + result = result && currTip == height; + }); + return result; + }, + true, + 600 * 1000, + 5 * 1000, + 5 + ); + } } setWorldConstructor(CustomWorld); diff --git a/integration_tests/package-lock.json b/integration_tests/package-lock.json index 27e1c628b1..868ccd2cda 100644 --- a/integration_tests/package-lock.json +++ b/integration_tests/package-lock.json @@ -753,7 +753,7 @@ }, "assertion-error": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "resolved": false, "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, @@ -935,7 +935,7 @@ }, "check-error": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "resolved": false, "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, @@ -1128,7 +1128,7 @@ }, "deep-eql": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "resolved": false, "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { @@ -1882,7 +1882,7 @@ }, "get-func-name": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "resolved": false, "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, @@ -1946,7 +1946,7 @@ }, "globals": { "version": "11.12.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, @@ -2252,7 +2252,7 @@ }, "jsesc": { "version": "2.5.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, @@ -2715,7 +2715,7 @@ }, "pathval": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "resolved": false, "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, @@ -3038,7 +3038,7 @@ }, "source-map": { "version": "0.5.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, @@ -3290,7 +3290,7 @@ }, "to-fast-properties": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, @@ -3351,7 +3351,7 @@ }, "type-detect": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "resolved": false, "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, @@ -3440,68 +3440,104 @@ "dependencies": { "@grpc/grpc-js": { "version": "1.3.6", + "resolved": false, + "integrity": "sha512-v7+LQFbqZKmd/Tvf5/j1Xlbq6jXL/4d+gUtm2TNX4QiEC3ELWADmGr2dGlUyLl6aKTuYfsN72vAsO5zmavYkEg==", "requires": { "@types/node": ">=12.12.47" } }, "@grpc/proto-loader": { "version": "0.5.6", + "resolved": false, + "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", "requires": { "lodash.camelcase": "^4.3.0", "protobufjs": "^6.8.6" } }, "@protobufjs/aspromise": { - "version": "1.1.2" + "version": "1.1.2", + "resolved": false, + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" }, "@protobufjs/base64": { - "version": "1.1.2" + "version": "1.1.2", + "resolved": false, + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" }, "@protobufjs/codegen": { - "version": "2.0.4" + "version": "2.0.4", + "resolved": false, + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" }, "@protobufjs/eventemitter": { - "version": "1.1.0" + "version": "1.1.0", + "resolved": false, + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" }, "@protobufjs/fetch": { "version": "1.1.0", + "resolved": false, + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", "requires": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "@protobufjs/float": { - "version": "1.0.2" + "version": "1.0.2", + "resolved": false, + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" }, "@protobufjs/inquire": { - "version": "1.1.0" + "version": "1.1.0", + "resolved": false, + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" }, "@protobufjs/path": { - "version": "1.1.2" + "version": "1.1.2", + "resolved": false, + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" }, "@protobufjs/pool": { - "version": "1.1.0" + "version": "1.1.0", + "resolved": false, + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" }, "@protobufjs/utf8": { - "version": "1.1.0" + "version": "1.1.0", + "resolved": false, + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "@types/long": { - "version": "4.0.1" + "version": "4.0.1", + "resolved": false, + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "@types/node": { - "version": "16.3.2" + "version": "16.3.2", + "resolved": false, + "integrity": "sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==" }, "grpc-promise": { - "version": "1.4.0" + "version": "1.4.0", + "resolved": false, + "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==" }, "lodash.camelcase": { - "version": "4.3.0" + "version": "4.3.0", + "resolved": false, + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, "long": { - "version": "4.0.0" + "version": "4.0.0", + "resolved": false, + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "protobufjs": { "version": "6.11.2", + "resolved": false, + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2",