diff --git a/.circleci/config.yml b/.circleci/config.yml index af2c0a0d9ba..f57cac5b566 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -129,7 +129,7 @@ commands: - run: name: Run cucumber scenarios no_output_timeout: 20m - command: cd integration_tests && mkdir -p cucumber_output && node_modules/.bin/cucumber-js --tags "not @long-running and not @broken and not @wallet-ffi" --format json:cucumber_output/tests.cucumber --exit --retry 2 --retryTagFilter "@flaky and not @broken" + command: cd integration_tests && mkdir -p cucumber_output && node_modules/.bin/cucumber-js --profile "ci" --tags "not @long-running and not @broken and not @wallet-ffi" --format json:cucumber_output/tests.cucumber --exit --retry 2 --retryTagFilter "@flaky and not @broken" - run: name: Generate report command: cd integration_tests && node ./generate_report.js @@ -137,7 +137,7 @@ commands: # Below step requires NodeJS v12 to run correctly, see explanation in WalletFFI.feature - run: name: Run FFI wallet library cucumber scenarios - command: cd integration_tests && mkdir -p cucumber_output && node_modules/.bin/cucumber-js --tags "not @long-running and not @broken and not @flaky and @wallet-ffi" --format json:cucumber_output/tests_ffi.cucumber --exit + command: cd integration_tests && mkdir -p cucumber_output && node_modules/.bin/cucumber-js --profile "ci" --tags "not @long-running and not @broken and not @flaky and @wallet-ffi" --format json:cucumber_output/tests_ffi.cucumber --exit when: always - run: name: Generate report (ffi) diff --git a/Cargo.lock b/Cargo.lock index 3535a88f881..92f0f03fe33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -698,6 +698,12 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.1" @@ -1057,6 +1063,18 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +[[package]] +name = "decimal-rs" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628081e4b054b20bc00989885feca733c66f19d0e983bb4a3b81b6edcd0b70bc" +dependencies = [ + "ethnum", + "fast-float", + "stack-buf", + "thiserror", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1104,6 +1122,19 @@ dependencies = [ "syn 1.0.75", ] +[[package]] +name = "derive_more" +version = "0.99.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" +dependencies = [ + "convert_case", + "proc-macro2 1.0.28", + "quote 1.0.9", + "rustc_version 0.3.3", + "syn 1.0.75", +] + [[package]] name = "des" version = "0.6.0" @@ -1284,6 +1315,18 @@ dependencies = [ "termcolor", ] +[[package]] +name = "ethnum" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887dc6b1e1f9ef25ac52649e19ce610a2520b4c55b48429030782b6c8a70e78a" + +[[package]] +name = "fast-float" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" + [[package]] name = "fixed-hash" version = "0.7.0" @@ -3960,6 +4003,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "stack-buf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7386b49cb287f6fafbfd3bd604914bccb99fb8d53483f40e1ecfda5d45f3370" + [[package]] name = "static_assertions" version = "1.1.0" @@ -4136,7 +4185,6 @@ dependencies = [ "tari_comms", "tari_core", "tari_crypto", - "tari_wallet", "tonic", "tonic-build", ] @@ -4161,7 +4209,6 @@ dependencies = [ "tari_core", "tari_crypto", "tari_p2p", - "tari_wallet", "thiserror", "tokio 1.11.0", "tonic", @@ -4247,6 +4294,7 @@ dependencies = [ "tari_storage", "tari_test_utils", "tempfile", + "thiserror", "toml 0.5.8", "tracing", "tracing-opentelemetry", @@ -4263,6 +4311,7 @@ dependencies = [ "rand 0.8.4", "serde 1.0.130", "tari_crypto", + "thiserror", "tokio 1.11.0", ] @@ -4431,6 +4480,8 @@ dependencies = [ "chrono", "config", "croaring", + "decimal-rs", + "derive_more", "digest", "env_logger 0.7.1", "fs2", @@ -4896,18 +4947,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "283d5230e63df9608ac7d9691adc1dfb6e701225436eb64d0b9a7f0a5a04f6ec" +checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa3884228611f5cd3608e2d409bf7dce832e4eb3135e3f11addbd7e41bd68e71" +checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" dependencies = [ "proc-macro2 1.0.28", "quote 1.0.9", diff --git a/applications/dns/.env.sample b/applications/dns/.env.sample new file mode 100644 index 00000000000..3bdb92a282a --- /dev/null +++ b/applications/dns/.env.sample @@ -0,0 +1,4 @@ +TOKEN= +ZONE_ID= +DOMAIN=updates.taripulse.com +FILE=../../meta/hashes.txt diff --git a/applications/dns/.nvmrc b/applications/dns/.nvmrc new file mode 100644 index 00000000000..518633e1687 --- /dev/null +++ b/applications/dns/.nvmrc @@ -0,0 +1 @@ +lts/fermium diff --git a/applications/dns/README.md b/applications/dns/README.md new file mode 100644 index 00000000000..ab564a40140 --- /dev/null +++ b/applications/dns/README.md @@ -0,0 +1,34 @@ +# DNS Update + +A script to update DNS records from the hashes file. + +## Setup your env + +``` +cp .env.sample .env +``` + +- Edit `.env` to include your cloudflare api TOKEN, your ZONE_ID, and the DOMAIN to update. +- Use FILE to set the name of the file to provide records, if not provided then the default `../../meta/hashes.txt` will be used. + +## Install deps + +``` +npm ci +``` + +## Run + +``` +npm run update +``` + +Deletes existing TXT records on the domain, and creates new records from each line in FILE. + +## Release process (manual) + +- push tag +- build binaries +- replace hashes in file `meta/hashes.txt` +- sign `meta/hashes.txt` with maintainer gpg key and replace sig at `meta/hashes.txt.sig` +- `npm run update` in this folder to update the DNS records to match the txt file diff --git a/applications/dns/package-lock.json b/applications/dns/package-lock.json new file mode 100644 index 00000000000..47aaa18d5a0 --- /dev/null +++ b/applications/dns/package-lock.json @@ -0,0 +1,3174 @@ +{ + "name": "tari_dns", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/compat-data": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "dev": true + }, + "@babel/core": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.8.tgz", + "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.15.8", + "@babel/generator": "^7.15.8", + "@babel/helper-compilation-targets": "^7.15.4", + "@babel/helper-module-transforms": "^7.15.8", + "@babel/helpers": "^7.15.4", + "@babel/parser": "^7.15.8", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", + "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", + "dev": true, + "requires": { + "@babel/types": "^7.15.6", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.15.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + } + }, + "@babel/helper-function-name": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-module-imports": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz", + "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.15.4", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.6" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "dev": true + }, + "@babel/helper-replace-supers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "dev": true + }, + "@babel/helpers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", + "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "dev": true, + "requires": { + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + } + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", + "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", + "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/template": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4" + } + }, + "@babel/traverse": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-hoist-variables": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", + "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.9", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.2.5.tgz", + "integrity": "sha512-smtlRF9vNKorRMCUtJ+yllIoiY8oFmfFG7xlzsAE76nKEwXNhjPOJIsc7Dv+AUitVt76t+KjIpUP9m98Crn2LQ==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.2.5", + "jest-util": "^27.2.5", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.2.5.tgz", + "integrity": "sha512-VR7mQ+jykHN4WO3OvusRJMk4xCa2MFLipMS+43fpcRGaYrN1KwMATfVEXif7ccgFKYGy5D1TVXTNE4mGq/KMMA==", + "dev": true, + "requires": { + "@jest/console": "^27.2.5", + "@jest/reporters": "^27.2.5", + "@jest/test-result": "^27.2.5", + "@jest/transform": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^27.2.5", + "jest-config": "^27.2.5", + "jest-haste-map": "^27.2.5", + "jest-message-util": "^27.2.5", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.2.5", + "jest-resolve-dependencies": "^27.2.5", + "jest-runner": "^27.2.5", + "jest-runtime": "^27.2.5", + "jest-snapshot": "^27.2.5", + "jest-util": "^27.2.5", + "jest-validate": "^27.2.5", + "jest-watcher": "^27.2.5", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.2.5.tgz", + "integrity": "sha512-XvUW3q6OUF+54SYFCgbbfCd/BKTwm5b2MGLoc2jINXQLKQDTCS2P2IrpPOtQ08WWZDGzbhAzVhOYta3J2arubg==", + "dev": true, + "requires": { + "@jest/fake-timers": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/node": "*", + "jest-mock": "^27.2.5" + } + }, + "@jest/fake-timers": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.2.5.tgz", + "integrity": "sha512-ZGUb6jg7BgwY+nmO0TW10bc7z7Hl2G/UTAvmxEyZ/GgNFoa31tY9/cgXmqcxnnZ7o5Xs7RAOz3G1SKIj8IVDlg==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "@sinonjs/fake-timers": "^8.0.1", + "@types/node": "*", + "jest-message-util": "^27.2.5", + "jest-mock": "^27.2.5", + "jest-util": "^27.2.5" + } + }, + "@jest/globals": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.2.5.tgz", + "integrity": "sha512-naRI537GM+enFVJQs6DcwGYPn/0vgJNb06zGVbzXfDfe/epDPV73hP1vqO37PqSKDeOXM2KInr6ymYbL1HTP7g==", + "dev": true, + "requires": { + "@jest/environment": "^27.2.5", + "@jest/types": "^27.2.5", + "expect": "^27.2.5" + } + }, + "@jest/reporters": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.2.5.tgz", + "integrity": "sha512-zYuR9fap3Q3mxQ454VWF8I6jYHErh368NwcKHWO2uy2fwByqBzRHkf9j2ekMDM7PaSTWcLBSZyd7NNxR1iHxzQ==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.2.5", + "@jest/test-result": "^27.2.5", + "@jest/transform": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^27.2.5", + "jest-resolve": "^27.2.5", + "jest-util": "^27.2.5", + "jest-worker": "^27.2.5", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.1.0" + } + }, + "@jest/source-map": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz", + "integrity": "sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.2.5.tgz", + "integrity": "sha512-ub7j3BrddxZ0BdSnM5JCF6cRZJ/7j3wgdX0+Dtwhw2Po+HKsELCiXUTvh+mgS4/89mpnU1CPhZxe2mTvuLPJJg==", + "dev": true, + "requires": { + "@jest/console": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.2.5.tgz", + "integrity": "sha512-8j8fHZRfnjbbdMitMAGFKaBZ6YqvFRFJlMJzcy3v75edTOqc7RY65S9JpMY6wT260zAcL2sTQRga/P4PglCu3Q==", + "dev": true, + "requires": { + "@jest/test-result": "^27.2.5", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.5", + "jest-runtime": "^27.2.5" + } + }, + "@jest/transform": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.2.5.tgz", + "integrity": "sha512-29lRtAHHYGALbZOx343v0zKmdOg4Sb0rsA1uSv0818bvwRhs3TyElOmTVXlrw0v1ZTqXJCAH/cmoDXimBhQOJQ==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.2.5", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.5", + "jest-regex-util": "^27.0.6", + "jest-util": "^27.2.5", + "micromatch": "^4.0.4", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + } + }, + "@jest/types": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz", + "integrity": "sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.0.1.tgz", + "integrity": "sha512-AU7kwFxreVd6OAXcAFlKSmZquiRUU0FvYm44k1Y1QbK7Co4m0aqfGMhjykIeQp/H6rcl+nFmj0zfdUcGVs9Dew==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, + "@types/babel__core": { + "version": "7.1.16", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", + "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", + "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", + "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/node": { + "version": "16.10.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.3.tgz", + "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==", + "dev": true + }, + "@types/prettier": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz", + "integrity": "sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "dev": true + }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true + }, + "acorn": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "autocreate": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/autocreate/-/autocreate-1.2.0.tgz", + "integrity": "sha1-UiFnmSxBcsFUeeX4jzSGpFKkDLo=" + }, + "babel-jest": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.5.tgz", + "integrity": "sha512-GC9pWCcitBhSuF7H3zl0mftoKizlswaF0E3qi+rPL417wKkCB0d+Sjjb0OfXvxj7gWiBf497ldgRMii68Xz+2g==", + "dev": true, + "requires": { + "@jest/transform": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^27.2.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz", + "integrity": "sha512-TOux9khNKdi64mW+0OIhcmbAn75tTlzKhxmiNXevQaPbrBYK7YKjP1jl6NHTJ6XR5UgUrJbCnWlKVnJn29dfjw==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz", + "integrity": "sha512-z7MgQ3peBwN5L5aCqBKnF6iqdlvZvFUQynEhu0J+X9nHLU72jO3iY331lcYrg+AssJ8q7xsv5/3AICzVmJ/wvg==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^27.2.0", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "browserslist": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.3.tgz", + "integrity": "sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001264", + "electron-to-chromium": "^1.3.857", + "escalade": "^3.1.1", + "node-releases": "^1.1.77", + "picocolors": "^0.2.1" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001265", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz", + "integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==", + "dev": true + }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "ci-info": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", + "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "cloudflare": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cloudflare/-/cloudflare-2.9.1.tgz", + "integrity": "sha512-x8yXPPoloy7xQ9GCKnsvQ3U1nwvcLndA2B3nxwSjIWxgLTUJOyakeEDsrqxZO8Dr6FkGdaXwy554fQVMpOabiw==", + "requires": { + "autocreate": "^1.1.0", + "es-class": "^2.1.1", + "got": "^6.3.0", + "https-proxy-agent": "^5.0.0", + "object-assign": "^4.1.0", + "should-proxy": "^1.0.4", + "url-pattern": "^1.0.3" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "decimal.js": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", + "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", + "dev": true + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", + "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", + "dev": true + }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } + } + }, + "dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "electron-to-chromium": { + "version": "1.3.864", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.864.tgz", + "integrity": "sha512-v4rbad8GO6/yVI92WOeU9Wgxc4NA0n4f6P1FvZTY+jyY7JHEhw3bduYu60v3Q1h81Cg6eo4ApZrFPuycwd5hGw==", + "dev": true + }, + "emittery": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "es-class": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/es-class/-/es-class-2.1.1.tgz", + "integrity": "sha1-bsIkO1oeNYHAt+7O4BMMnA1vsrc=" + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expect": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.2.5.tgz", + "integrity": "sha512-ZrO0w7bo8BgGoP/bLz+HDCI+0Hfei9jUSZs5yI/Wyn9VkG9w8oJ7rHRgYj+MA7yqqFa0IwHA3flJzZtYugShJA==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.0.6", + "jest-matcher-utils": "^27.2.5", + "jest-message-util": "^27.2.5", + "jest-regex-util": "^27.0.6" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + } + } + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "import-local": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", + "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-ci": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", + "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", + "dev": true, + "requires": { + "ci-info": "^3.1.1" + } + }, + "is-core-module": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", + "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.3.tgz", + "integrity": "sha512-0i77ZFLsb9U3DHi22WzmIngVzfoyxxbQcZRqlF3KoKmCJGq9nhFHoGi8FqBztN2rE8w6hURnZghetn0xpkVb6A==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.2.5.tgz", + "integrity": "sha512-vDMzXcpQN4Ycaqu+vO7LX8pZwNNoKMhc+gSp6q1D8S6ftRk8gNW8cni3YFxknP95jxzQo23Lul0BI2FrWgnwYQ==", + "dev": true, + "requires": { + "@jest/core": "^27.2.5", + "import-local": "^3.0.2", + "jest-cli": "^27.2.5" + }, + "dependencies": { + "jest-cli": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.2.5.tgz", + "integrity": "sha512-XzfcOXi5WQrXqFYsDxq5RDOKY4FNIgBgvgf3ZBz4e/j5/aWep5KnsAYH5OFPMdX/TP/LFsYQMRH7kzJUMh6JKg==", + "dev": true, + "requires": { + "@jest/core": "^27.2.5", + "@jest/test-result": "^27.2.5", + "@jest/types": "^27.2.5", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "jest-config": "^27.2.5", + "jest-util": "^27.2.5", + "jest-validate": "^27.2.5", + "prompts": "^2.0.1", + "yargs": "^16.2.0" + } + } + } + }, + "jest-changed-files": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.2.5.tgz", + "integrity": "sha512-jfnNJzF89csUKRPKJ4MwZ1SH27wTmX2xiAIHUHrsb/OYd9Jbo4/SXxJ17/nnx6RIifpthk3Y+LEeOk+/dDeGdw==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "execa": "^5.0.0", + "throat": "^6.0.1" + } + }, + "jest-circus": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.2.5.tgz", + "integrity": "sha512-eyL9IcrAxm3Saq3rmajFCwpaxaRMGJ1KJs+7hlTDinXpJmeR3P02bheM3CYohE7UfwOBmrFMJHjgo/WPcLTM+Q==", + "dev": true, + "requires": { + "@jest/environment": "^27.2.5", + "@jest/test-result": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.2.5", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.2.5", + "jest-matcher-utils": "^27.2.5", + "jest-message-util": "^27.2.5", + "jest-runtime": "^27.2.5", + "jest-snapshot": "^27.2.5", + "jest-util": "^27.2.5", + "pretty-format": "^27.2.5", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + } + }, + "jest-config": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.2.5.tgz", + "integrity": "sha512-QdENtn9b5rIIYGlbDNEcgY9LDL5kcokJnXrp7x8AGjHob/XFqw1Z6p+gjfna2sUulQsQ3ce2Fvntnv+7fKYDhQ==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^27.2.5", + "@jest/types": "^27.2.5", + "babel-jest": "^27.2.5", + "chalk": "^4.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "is-ci": "^3.0.0", + "jest-circus": "^27.2.5", + "jest-environment-jsdom": "^27.2.5", + "jest-environment-node": "^27.2.5", + "jest-get-type": "^27.0.6", + "jest-jasmine2": "^27.2.5", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.2.5", + "jest-runner": "^27.2.5", + "jest-util": "^27.2.5", + "jest-validate": "^27.2.5", + "micromatch": "^4.0.4", + "pretty-format": "^27.2.5" + } + }, + "jest-diff": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.2.5.tgz", + "integrity": "sha512-7gfwwyYkeslOOVQY4tVq5TaQa92mWfC9COsVYMNVYyJTOYAqbIkoD3twi5A+h+tAPtAelRxkqY6/xu+jwTr0dA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.5" + } + }, + "jest-docblock": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz", + "integrity": "sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.2.5.tgz", + "integrity": "sha512-HUPWIbJT0bXarRwKu/m7lYzqxR4GM5EhKOsu0z3t0SKtbFN6skQhpAUADM4qFShBXb9zoOuag5lcrR1x/WM+Ag==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "chalk": "^4.0.0", + "jest-get-type": "^27.0.6", + "jest-util": "^27.2.5", + "pretty-format": "^27.2.5" + } + }, + "jest-environment-jsdom": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.2.5.tgz", + "integrity": "sha512-QtRpOh/RQKuXniaWcoFE2ElwP6tQcyxHu0hlk32880g0KczdonCs5P1sk5+weu/OVzh5V4Bt1rXuQthI01mBLg==", + "dev": true, + "requires": { + "@jest/environment": "^27.2.5", + "@jest/fake-timers": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/node": "*", + "jest-mock": "^27.2.5", + "jest-util": "^27.2.5", + "jsdom": "^16.6.0" + } + }, + "jest-environment-node": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.2.5.tgz", + "integrity": "sha512-0o1LT4grm7iwrS8fIoLtwJxb/hoa3GsH7pP10P02Jpj7Mi4BXy65u46m89vEM2WfD1uFJQ2+dfDiWZNA2e6bJg==", + "dev": true, + "requires": { + "@jest/environment": "^27.2.5", + "@jest/fake-timers": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/node": "*", + "jest-mock": "^27.2.5", + "jest-util": "^27.2.5" + } + }, + "jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true + }, + "jest-haste-map": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.2.5.tgz", + "integrity": "sha512-pzO+Gw2WLponaSi0ilpzYBE0kuVJstoXBX8YWyUebR8VaXuX4tzzn0Zp23c/WaETo7XYTGv2e8KdnpiskAFMhQ==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^27.0.6", + "jest-serializer": "^27.0.6", + "jest-util": "^27.2.5", + "jest-worker": "^27.2.5", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.2.5.tgz", + "integrity": "sha512-hdxY9Cm/CjLqu2tXeAoQHPgA4vcqlweVXYOg1+S9FeFdznB9Rti+eEBKDDkmOy9iqr4Xfbq95OkC4NFbXXPCAQ==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^27.2.5", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.2.5", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.2.5", + "jest-matcher-utils": "^27.2.5", + "jest-message-util": "^27.2.5", + "jest-runtime": "^27.2.5", + "jest-snapshot": "^27.2.5", + "jest-util": "^27.2.5", + "pretty-format": "^27.2.5", + "throat": "^6.0.1" + } + }, + "jest-leak-detector": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.2.5.tgz", + "integrity": "sha512-HYsi3GUR72bYhOGB5C5saF9sPdxGzSjX7soSQS+BqDRysc7sPeBwPbhbuT8DnOpijnKjgwWQ8JqvbmReYnt3aQ==", + "dev": true, + "requires": { + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.5" + } + }, + "jest-matcher-utils": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.2.5.tgz", + "integrity": "sha512-qNR/kh6bz0Dyv3m68Ck2g1fLW5KlSOUNcFQh87VXHZwWc/gY6XwnKofx76Qytz3x5LDWT09/2+yXndTkaG4aWg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^27.2.5", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.5" + } + }, + "jest-message-util": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.2.5.tgz", + "integrity": "sha512-ggXSLoPfIYcbmZ8glgEJZ8b+e0Msw/iddRmgkoO7lDAr9SmI65IIfv7VnvTnV4FGnIIUIjzM+fHRHO5RBvyAbQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.2.5", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "pretty-format": "^27.2.5", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.2.5.tgz", + "integrity": "sha512-HiMB3LqE9RzmeMzZARi2Bz3NoymxyP0gCid4y42ca1djffNtYFKgI220aC1VP1mUZ8rbpqZbHZOJ15093bZV/Q==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "@types/node": "*" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true + }, + "jest-regex-util": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz", + "integrity": "sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==", + "dev": true + }, + "jest-resolve": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.2.5.tgz", + "integrity": "sha512-q5irwS3oS73SKy3+FM/HL2T7WJftrk9BRzrXF92f7net5HMlS7lJMg/ZwxLB4YohKqjSsdksEw7n/jvMxV7EKg==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "chalk": "^4.0.0", + "escalade": "^3.1.1", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.5", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.2.5", + "jest-validate": "^27.2.5", + "resolve": "^1.20.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.2.5.tgz", + "integrity": "sha512-BSjefped31bcvvCh++/pN9ueqqN1n0+p8/58yScuWfklLm2tbPbS9d251vJhAy0ZI2pL/0IaGhOTJrs9Y4FJlg==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "jest-regex-util": "^27.0.6", + "jest-snapshot": "^27.2.5" + } + }, + "jest-runner": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.2.5.tgz", + "integrity": "sha512-n41vw9RLg5TKAnEeJK9d6pGOsBOpwE89XBniK+AD1k26oIIy3V7ogM1scbDjSheji8MUPC9pNgCrZ/FHLVDNgg==", + "dev": true, + "requires": { + "@jest/console": "^27.2.5", + "@jest/environment": "^27.2.5", + "@jest/test-result": "^27.2.5", + "@jest/transform": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-docblock": "^27.0.6", + "jest-environment-jsdom": "^27.2.5", + "jest-environment-node": "^27.2.5", + "jest-haste-map": "^27.2.5", + "jest-leak-detector": "^27.2.5", + "jest-message-util": "^27.2.5", + "jest-resolve": "^27.2.5", + "jest-runtime": "^27.2.5", + "jest-util": "^27.2.5", + "jest-worker": "^27.2.5", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + } + }, + "jest-runtime": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.2.5.tgz", + "integrity": "sha512-N0WRZ3QszKyZ3Dm27HTBbBuestsSd3Ud5ooVho47XZJ8aSKO/X1Ag8M1dNx9XzfGVRNdB/xCA3lz8MJwIzPLLA==", + "dev": true, + "requires": { + "@jest/console": "^27.2.5", + "@jest/environment": "^27.2.5", + "@jest/fake-timers": "^27.2.5", + "@jest/globals": "^27.2.5", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.2.5", + "@jest/transform": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.5", + "jest-message-util": "^27.2.5", + "jest-mock": "^27.2.5", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.2.5", + "jest-snapshot": "^27.2.5", + "jest-util": "^27.2.5", + "jest-validate": "^27.2.5", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^16.2.0" + } + }, + "jest-serializer": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz", + "integrity": "sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==", + "dev": true, + "requires": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + } + }, + "jest-snapshot": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.2.5.tgz", + "integrity": "sha512-2/Jkn+VN6Abwz0llBltZaiJMnL8b1j5Bp/gRIxe9YR3FCEh9qp0TXVV0dcpTGZ8AcJV1SZGQkczewkI9LP5yGw==", + "dev": true, + "requires": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/parser": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.2.5", + "graceful-fs": "^4.2.4", + "jest-diff": "^27.2.5", + "jest-get-type": "^27.0.6", + "jest-haste-map": "^27.2.5", + "jest-matcher-utils": "^27.2.5", + "jest-message-util": "^27.2.5", + "jest-resolve": "^27.2.5", + "jest-util": "^27.2.5", + "natural-compare": "^1.4.0", + "pretty-format": "^27.2.5", + "semver": "^7.3.2" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "jest-util": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.2.5.tgz", + "integrity": "sha512-QRhDC6XxISntMzFRd/OQ6TGsjbzA5ONO0tlAj2ElHs155x1aEr0rkYJBEysG6H/gZVH3oGFzCdAB/GA8leh8NQ==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^3.0.0", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.5.tgz", + "integrity": "sha512-XgYtjS89nhVe+UfkbLgcm+GgXKWgL80t9nTcNeejyO3t0Sj/yHE8BtIJqjZu9NXQksYbGImoQRXmQ1gP+Guffw==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.0.6", + "leven": "^3.1.0", + "pretty-format": "^27.2.5" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.2.5.tgz", + "integrity": "sha512-umV4qGozg2Dn6DTTtqAh9puPw+DGLK9AQas7+mWjiK8t0fWMpxKg8ZXReZw7L4C88DqorsGUiDgwHNZ+jkVrkQ==", + "dev": true, + "requires": { + "@jest/test-result": "^27.2.5", + "@jest/types": "^27.2.5", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.2.5", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.5.tgz", + "integrity": "sha512-HTjEPZtcNKZ4LnhSp02NEH4vE+5OpJ0EsOWYvGQpHgUMLngydESAAMH5Wd/asPf29+XUDQZszxpLg1BkIIA2aw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "dev": true, + "requires": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "mime-db": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", + "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", + "dev": true + }, + "mime-types": { + "version": "2.1.33", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", + "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "dev": true, + "requires": { + "mime-db": "1.50.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-releases": { + "version": "1.1.77", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz", + "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "pretty-format": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.2.5.tgz", + "integrity": "sha512-+nYn2z9GgicO9JiqmY25Xtq8SYfZ/5VCpEU3pppHHNAhd1y+ZXxmNPd1evmNcAd6Hz4iBV2kf0UpGth5A/VJ7g==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "should-proxy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/should-proxy/-/should-proxy-1.0.4.tgz", + "integrity": "sha1-yAWlAav2lTlgBjSAnmL78ji6NeQ=" + }, + "signal-exit": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", + "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "throat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", + "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", + "dev": true + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "requires": { + "prepend-http": "^1.0.1" + } + }, + "url-pattern": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/url-pattern/-/url-pattern-1.0.3.tgz", + "integrity": "sha1-BAkpJHGyTyPFDWWkeTF5PStaz8E=" + }, + "v8-to-istanbul": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz", + "integrity": "sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dev": true, + "requires": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", + "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", + "dev": true + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + } + } +} diff --git a/applications/dns/package.json b/applications/dns/package.json new file mode 100644 index 00000000000..f0017dae9a1 --- /dev/null +++ b/applications/dns/package.json @@ -0,0 +1,25 @@ +{ + "name": "tari_dns", + "type": "module", + "version": "0.1.0", + "description": "Update DNS records for auto-update", + "scripts": { + "update": "DEBUG=dns node --no-deprecation update.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/tari-project/tari.git" + }, + "author": "The Tari Development Community", + "license": "MIT", + "bugs": { + "url": "https://github.com/tari-project/tari/issues" + }, + "homepage": "https://github.com/tari-project/tari#readme", + "devDependencies": {}, + "dependencies": { + "cloudflare": "^2.9.1", + "debug": "^4.3.2", + "dotenv": "^10.0.0" + } +} diff --git a/applications/dns/update.js b/applications/dns/update.js new file mode 100644 index 00000000000..91c81601633 --- /dev/null +++ b/applications/dns/update.js @@ -0,0 +1,80 @@ +import cloudflare from "cloudflare"; +import debug from "debug"; +import dotenv from "dotenv"; +import { readFileSync } from "fs"; +const log = debug("dns"); +dotenv.config(); + +function getEnv() { + if (!process.env.TOKEN) { + throw new Error("No TOKEN set in env!"); + } + if (!process.env.ZONE_ID) { + throw new Error("No ZONE_ID set in env!"); + } + if (!process.env.DOMAIN) { + throw new Error("No DOMAIN set in env!"); + } + + return { + token: process.env.TOKEN, + zoneId: process.env.ZONE_ID, + domain: process.env.DOMAIN, + filePath: process.env.FILE || "../../meta/hashes.txt", + }; +} +const { token, zoneId, domain, filePath } = getEnv(); + +const client = cloudflare({ + token, +}); + +async function deleteRecords(records) { + const del = records.map((record) => client.dnsRecords.del(zoneId, record.id)); + return await Promise.all(del); +} + +async function addRecords(records) { + let add = records.map((content) => { + let record = { + type: "TXT", + name: domain, + content: content, + ttl: 1, + }; + return client.dnsRecords.add(zoneId, record); + }); + return await Promise.all(add); +} + +const notEmptyOrComment = (line) => line && !line.trim().startsWith("#"); + +async function main(path) { + try { + log("Reading file ", path); + const file = readFileSync(path, "utf8"); + const records = file.split("\n").filter(notEmptyOrComment); + + log("Updating DNS..."); + log("Fetch TXT records..."); + const { result } = await client.dnsRecords.browse(zoneId); + const txt = result.filter((r) => r.type == "TXT"); + + log(`Deleting ${txt.length} records...`); + const deleted = await deleteRecords(txt); + let ids = deleted.map((d) => d.result.id); + log("Deleted record IDs: ", ids); + + log(`Adding ${records.length} records...`); + log(records); + + const added = await addRecords(records); + ids = added.map((a) => a.result.id); + log("Added record IDs: ", ids); + log("Done!"); + } catch (e) { + console.error("Error: ", e); + } +} + +main(filePath); diff --git a/applications/tari_app_grpc/Cargo.toml b/applications/tari_app_grpc/Cargo.toml index a50d866dc78..d4bd0436bdc 100644 --- a/applications/tari_app_grpc/Cargo.toml +++ b/applications/tari_app_grpc/Cargo.toml @@ -10,7 +10,6 @@ edition = "2018" [dependencies] tari_common_types = { version = "^0.11", path = "../../base_layer/common_types"} tari_core = { path = "../../base_layer/core"} -tari_wallet = { path = "../../base_layer/wallet", optional = true} tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } tari_comms = { path = "../../comms"} @@ -21,6 +20,3 @@ tonic = "0.5.2" [build-dependencies] tonic-build = "0.5.2" - -[features] -wallet = ["tari_wallet"] \ No newline at end of file diff --git a/applications/tari_app_grpc/proto/base_node.proto b/applications/tari_app_grpc/proto/base_node.proto index 1396613acd8..c1e3a2c5a7f 100644 --- a/applications/tari_app_grpc/proto/base_node.proto +++ b/applications/tari_app_grpc/proto/base_node.proto @@ -92,7 +92,18 @@ message SubmitBlockResponse { message TipInfoResponse { MetaData metadata = 1; bool initial_sync_achieved = 2; + BaseNodeState base_node_state = 3; } + +enum BaseNodeState{ + START_UP = 0; + HEADER_SYNC = 1; + HORIZON_SYNC = 2; + BLOCK_SYNC_STARTING= 3; + BLOCK_SYNC = 4; + LISTENING = 5; +} + /// return type of GetNewBlockTemplate message NewBlockTemplateResponse { NewBlockTemplate new_block_template = 1; diff --git a/applications/tari_app_grpc/src/conversions/base_node_state.rs b/applications/tari_app_grpc/src/conversions/base_node_state.rs new file mode 100644 index 00000000000..c14bc7f8bb7 --- /dev/null +++ b/applications/tari_app_grpc/src/conversions/base_node_state.rs @@ -0,0 +1,50 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 crate::tari_rpc as grpc; +use tari_core::base_node::state_machine_service::states::{StateInfo, StateInfo::*}; + +impl From for grpc::BaseNodeState { + fn from(info: StateInfo) -> Self { + match info { + StartUp => grpc::BaseNodeState::HeaderSync, + HeaderSync(_) => grpc::BaseNodeState::HeaderSync, + HorizonSync(_) => grpc::BaseNodeState::HorizonSync, + BlockSyncStarting => grpc::BaseNodeState::BlockSyncStarting, + BlockSync(_) => grpc::BaseNodeState::BlockSync, + Listening(_) => grpc::BaseNodeState::Listening, + } + } +} + +impl From<&StateInfo> for grpc::BaseNodeState { + fn from(info: &StateInfo) -> Self { + match info { + StartUp => grpc::BaseNodeState::HeaderSync, + HeaderSync(_) => grpc::BaseNodeState::HeaderSync, + HorizonSync(_) => grpc::BaseNodeState::HorizonSync, + BlockSyncStarting => grpc::BaseNodeState::BlockSyncStarting, + BlockSync(_) => grpc::BaseNodeState::BlockSync, + Listening(_) => grpc::BaseNodeState::Listening, + } + } +} diff --git a/applications/tari_app_grpc/src/conversions/mod.rs b/applications/tari_app_grpc/src/conversions/mod.rs index f48a1b876d6..d9ac7394617 100644 --- a/applications/tari_app_grpc/src/conversions/mod.rs +++ b/applications/tari_app_grpc/src/conversions/mod.rs @@ -21,6 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. mod aggregate_body; +mod base_node_state; mod block; mod block_header; mod chain_metadata; @@ -40,6 +41,7 @@ mod unblinded_output; pub use self::{ aggregate_body::*, + base_node_state::*, block::*, block_header::*, chain_metadata::*, diff --git a/applications/tari_app_grpc/src/conversions/transaction.rs b/applications/tari_app_grpc/src/conversions/transaction.rs index cb9e91f63af..b3401226bb3 100644 --- a/applications/tari_app_grpc/src/conversions/transaction.rs +++ b/applications/tari_app_grpc/src/conversions/transaction.rs @@ -22,6 +22,7 @@ use crate::tari_rpc as grpc; use std::convert::{TryFrom, TryInto}; +use tari_common_types::transaction::{self as tx, TxId}; use tari_core::{ crypto::{ristretto::RistrettoSecretKey, tari_utilities::ByteArray}, transactions::transaction::Transaction, @@ -54,44 +55,38 @@ impl TryFrom for Transaction { } } -#[cfg(feature = "wallet")] -mod wallet { - use super::*; - use tari_wallet::{output_manager_service::TxId, transaction_service::storage::models}; - - impl From for grpc::TransactionStatus { - fn from(status: models::TransactionStatus) -> Self { - use models::TransactionStatus::*; - match status { - Completed => grpc::TransactionStatus::Completed, - Broadcast => grpc::TransactionStatus::Broadcast, - MinedUnconfirmed => grpc::TransactionStatus::MinedUnconfirmed, - MinedConfirmed => grpc::TransactionStatus::MinedConfirmed, - Imported => grpc::TransactionStatus::Imported, - Pending => grpc::TransactionStatus::Pending, - Coinbase => grpc::TransactionStatus::Coinbase, - } +impl From for grpc::TransactionDirection { + fn from(status: tx::TransactionDirection) -> Self { + use tx::TransactionDirection::*; + match status { + Unknown => grpc::TransactionDirection::Unknown, + Inbound => grpc::TransactionDirection::Inbound, + Outbound => grpc::TransactionDirection::Outbound, } } +} - impl From for grpc::TransactionDirection { - fn from(status: models::TransactionDirection) -> Self { - use models::TransactionDirection::*; - match status { - Unknown => grpc::TransactionDirection::Unknown, - Inbound => grpc::TransactionDirection::Inbound, - Outbound => grpc::TransactionDirection::Outbound, - } +impl From for grpc::TransactionStatus { + fn from(status: tx::TransactionStatus) -> Self { + use tx::TransactionStatus::*; + match status { + Completed => grpc::TransactionStatus::Completed, + Broadcast => grpc::TransactionStatus::Broadcast, + MinedUnconfirmed => grpc::TransactionStatus::MinedUnconfirmed, + MinedConfirmed => grpc::TransactionStatus::MinedConfirmed, + Imported => grpc::TransactionStatus::Imported, + Pending => grpc::TransactionStatus::Pending, + Coinbase => grpc::TransactionStatus::Coinbase, } } +} - impl grpc::TransactionInfo { - pub fn not_found(tx_id: TxId) -> Self { - Self { - tx_id, - status: grpc::TransactionStatus::NotFound as i32, - ..Default::default() - } +impl grpc::TransactionInfo { + pub fn not_found(tx_id: TxId) -> Self { + Self { + tx_id, + status: grpc::TransactionStatus::NotFound as i32, + ..Default::default() } } } diff --git a/applications/tari_app_utilities/Cargo.toml b/applications/tari_app_utilities/Cargo.toml index fcdc4ba8ffc..8c55b72f0cc 100644 --- a/applications/tari_app_utilities/Cargo.toml +++ b/applications/tari_app_utilities/Cargo.toml @@ -10,7 +10,6 @@ tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch tari_common = { path = "../../common" } tari_common_types = { path = "../../base_layer/common_types" } tari_p2p = { path = "../../base_layer/p2p", features = ["auto-update"] } -tari_wallet = { path = "../../base_layer/wallet", optional = true } config = { version = "0.9.3" } futures = { version = "^0.3.16", default-features = false, features = ["alloc"] } @@ -33,7 +32,3 @@ features = ["transactions"] [build-dependencies] tari_common = { path = "../../common", features = ["build", "static-application-info"] } - -[features] -# TODO: This crate is supposed to hold common logic. Move code from this feature into the crate that is more specific to the wallet -wallet = ["tari_wallet"] diff --git a/applications/tari_app_utilities/src/identity_management.rs b/applications/tari_app_utilities/src/identity_management.rs index 013a0e8fc8c..f8bb2893d7c 100644 --- a/applications/tari_app_utilities/src/identity_management.rs +++ b/applications/tari_app_utilities/src/identity_management.rs @@ -20,11 +20,10 @@ // 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 crate::utilities::ExitCodes; use log::*; use rand::rngs::OsRng; use std::{clone::Clone, fs, path::Path, string::ToString, sync::Arc}; -use tari_common::configuration::bootstrap::prompt; +use tari_common::{configuration::bootstrap::prompt, exit_codes::ExitCodes}; use tari_common_types::types::PrivateKey; use tari_comms::{multiaddr::Multiaddr, peer_manager::PeerFeatures, NodeIdentity}; use tari_crypto::{ diff --git a/applications/tari_app_utilities/src/initialization.rs b/applications/tari_app_utilities/src/initialization.rs index 85c711d4e3c..ead5b89f4b1 100644 --- a/applications/tari_app_utilities/src/initialization.rs +++ b/applications/tari_app_utilities/src/initialization.rs @@ -1,9 +1,10 @@ -use crate::{consts, utilities::ExitCodes}; +use crate::consts; use config::Config; use std::{path::PathBuf, str::FromStr}; use structopt::StructOpt; use tari_common::{ configuration::{bootstrap::ApplicationType, Network}, + exit_codes::ExitCodes, ConfigBootstrap, DatabaseType, GlobalConfig, diff --git a/applications/tari_app_utilities/src/utilities.rs b/applications/tari_app_utilities/src/utilities.rs index 989c871841d..8392e20e45e 100644 --- a/applications/tari_app_utilities/src/utilities.rs +++ b/applications/tari_app_utilities/src/utilities.rs @@ -23,14 +23,11 @@ use futures::future::Either; use log::*; use std::sync::Arc; -use thiserror::Error; use tokio::{runtime, runtime::Runtime}; use tari_common::{CommsTransport, GlobalConfig, SocksAuthentication, TorControlAuthentication}; use tari_comms::{ - connectivity::ConnectivityError, - peer_manager::{NodeId, PeerManagerError}, - protocol::rpc::RpcError, + peer_manager::NodeId, socks, tor, tor::TorIdentity, @@ -47,151 +44,6 @@ use tari_comms::transports::predicate::FalsePredicate; pub const LOG_TARGET: &str = "tari::application"; -/// Enum to show failure information -#[derive(Debug, Clone, Error)] -pub enum ExitCodes { - #[error("There is an error in the wallet configuration: {0}")] - ConfigError(String), - #[error("The application exited because an unknown error occurred: {0}. Check the logs for more details.")] - UnknownError(String), - #[error("The application exited because an interface error occurred. Check the logs for details.")] - InterfaceError, - #[error("The application exited. {0}")] - WalletError(String), - #[error("The wallet was not able to start the GRPC server. {0}")] - GrpcError(String), - #[error("The application did not accept the command input: {0}")] - InputError(String), - #[error("Invalid command: {0}")] - CommandError(String), - #[error("IO error: {0}")] - IOError(String), - #[error("Recovery failed: {0}")] - RecoveryError(String), - #[error("The wallet exited because of an internal network error: {0}")] - NetworkError(String), - #[error("The wallet exited because it received a message it could not interpret: {0}")] - ConversionError(String), - #[error("Your password was incorrect.")] - IncorrectPassword, - #[error("Your application is encrypted but no password was provided.")] - NoPassword, - #[error("Tor connection is offline")] - TorOffline, - #[error("Database is in inconsistent state: {0}")] - DbInconsistentState(String), -} - -impl ExitCodes { - pub fn as_i32(&self) -> i32 { - match self { - Self::ConfigError(_) => 101, - Self::UnknownError(_) => 102, - Self::InterfaceError => 103, - Self::WalletError(_) => 104, - Self::GrpcError(_) => 105, - Self::InputError(_) => 106, - Self::CommandError(_) => 107, - Self::IOError(_) => 108, - Self::RecoveryError(_) => 109, - Self::NetworkError(_) => 110, - Self::ConversionError(_) => 111, - Self::IncorrectPassword | Self::NoPassword => 112, - Self::TorOffline => 113, - Self::DbInconsistentState(_) => 115, - } - } - - pub fn eprint_details(&self) { - use ExitCodes::*; - match self { - TorOffline => { - eprintln!("Unable to connect to the Tor control port."); - eprintln!( - "Please check that you have the Tor proxy running and that access to the Tor control port is \ - turned on.", - ); - eprintln!("If you are unsure of what to do, use the following command to start the Tor proxy:"); - eprintln!( - "tor --allow-missing-torrc --ignore-missing-torrc --clientonly 1 --socksport 9050 --controlport \ - 127.0.0.1:9051 --log \"notice stdout\" --clientuseipv6 1", - ); - }, - - e => { - eprintln!("{}", e); - }, - } - } -} - -impl From for ExitCodes { - fn from(err: tari_common::ConfigError) -> Self { - error!(target: LOG_TARGET, "{}", err); - Self::ConfigError(err.to_string()) - } -} - -impl From for ExitCodes { - fn from(err: ConnectivityError) -> Self { - error!(target: LOG_TARGET, "{}", err); - Self::NetworkError(err.to_string()) - } -} - -impl From for ExitCodes { - fn from(err: RpcError) -> Self { - error!(target: LOG_TARGET, "{}", err); - Self::NetworkError(err.to_string()) - } -} - -#[cfg(feature = "wallet")] -mod wallet { - use super::*; - use tari_wallet::{ - error::{WalletError, WalletStorageError}, - output_manager_service::error::OutputManagerError, - }; - - impl From for ExitCodes { - fn from(err: WalletError) -> Self { - error!(target: LOG_TARGET, "{}", err); - Self::WalletError(err.to_string()) - } - } - - impl From for ExitCodes { - fn from(err: OutputManagerError) -> Self { - error!(target: LOG_TARGET, "{}", err); - Self::WalletError(err.to_string()) - } - } - - impl From for ExitCodes { - fn from(err: WalletStorageError) -> Self { - use WalletStorageError::*; - match err { - NoPasswordError => ExitCodes::NoPassword, - IncorrectPassword => ExitCodes::IncorrectPassword, - e => ExitCodes::WalletError(e.to_string()), - } - } - } -} - -impl From for ExitCodes { - fn from(err: PeerManagerError) -> Self { - ExitCodes::NetworkError(err.to_string()) - } -} - -impl ExitCodes { - pub fn grpc(err: M) -> Self { - ExitCodes::GrpcError(format!("GRPC connection error: {}", err)) - } -} - /// Creates a transport type from the given configuration /// /// ## Paramters diff --git a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs index 3a24c91249c..7db518cbcc8 100644 --- a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs +++ b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs @@ -462,6 +462,9 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { let new_block = match handler.get_new_block(block_template).await { Ok(b) => b, + Err(CommsInterfaceError::ChainStorageError(ChainStorageError::InvalidArguments { message, .. })) => { + return Err(Status::invalid_argument(message)); + }, Err(CommsInterfaceError::ChainStorageError(ChainStorageError::CannotCalculateNonTipMmr(msg))) => { let status = Status::with_details( tonic::Code::FailedPrecondition, @@ -737,9 +740,11 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { // Determine if we are bootstrapped let status_watch = self.state_machine_handle.get_status_info_watch(); + let state: tari_rpc::BaseNodeState = (&(*status_watch.borrow()).state_info).into(); let response = tari_rpc::TipInfoResponse { metadata: Some(meta.into()), initial_sync_achieved: (*status_watch.borrow()).bootstrapped, + base_node_state: state.into(), }; debug!(target: LOG_TARGET, "Sending MetaData response to client"); diff --git a/applications/tari_base_node/src/main.rs b/applications/tari_base_node/src/main.rs index 6c8d10475d8..a3342dd1dd1 100644 --- a/applications/tari_base_node/src/main.rs +++ b/applications/tari_base_node/src/main.rs @@ -111,9 +111,9 @@ use tari_app_utilities::{ consts, identity_management::setup_node_identity, initialization::init_configuration, - utilities::{setup_runtime, ExitCodes}, + utilities::setup_runtime, }; -use tari_common::{configuration::bootstrap::ApplicationType, ConfigBootstrap, GlobalConfig}; +use tari_common::{configuration::bootstrap::ApplicationType, exit_codes::ExitCodes, ConfigBootstrap, GlobalConfig}; use tari_comms::{peer_manager::PeerFeatures, tor::HiddenServiceControllerError}; use tari_core::chain_storage::ChainStorageError; use tari_shutdown::{Shutdown, ShutdownSignal}; diff --git a/applications/tari_base_node/src/recovery.rs b/applications/tari_base_node/src/recovery.rs index 391423c12eb..21a29f3a68a 100644 --- a/applications/tari_base_node/src/recovery.rs +++ b/applications/tari_base_node/src/recovery.rs @@ -31,8 +31,7 @@ use std::{ use anyhow::anyhow; use log::*; -use tari_app_utilities::utilities::ExitCodes; -use tari_common::{configuration::Network, DatabaseType, GlobalConfig}; +use tari_common::{configuration::Network, exit_codes::ExitCodes, DatabaseType, GlobalConfig}; use tari_core::{ chain_storage::{ async_db::AsyncBlockchainDb, diff --git a/applications/tari_console_wallet/Cargo.toml b/applications/tari_console_wallet/Cargo.toml index cb57928049c..a16e3e8e6a9 100644 --- a/applications/tari_console_wallet/Cargo.toml +++ b/applications/tari_console_wallet/Cargo.toml @@ -8,12 +8,12 @@ edition = "2018" tari_wallet = { path = "../../base_layer/wallet", features = ["bundled_sqlite"] } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } tari_common = { path = "../../common" } -tari_app_utilities = { path = "../tari_app_utilities", features = ["wallet"] } +tari_app_utilities = { path = "../tari_app_utilities" } tari_comms = { path = "../../comms" } tari_comms_dht = { path = "../../comms/dht" } tari_common_types = { path = "../../base_layer/common_types" } tari_p2p = { path = "../../base_layer/p2p", features = ["auto-update"] } -tari_app_grpc = { path = "../tari_app_grpc", features = ["wallet"] } +tari_app_grpc = { path = "../tari_app_grpc" } tari_shutdown = { path = "../../infrastructure/shutdown" } tari_key_manager = { path = "../../base_layer/key_manager" } diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index c30dad8a7c4..6b2a9cc027a 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -23,6 +23,7 @@ use super::error::CommandError; use log::*; use std::{ + convert::TryFrom, fs::File, io::{LineWriter, Write}, str::FromStr, @@ -39,7 +40,7 @@ use crate::{ utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY}, }; use tari_common::GlobalConfig; -use tari_common_types::{emoji::EmojiId, types::PublicKey}; +use tari_common_types::{emoji::EmojiId, transaction::TxId, types::PublicKey}; use tari_comms::{ connectivity::{ConnectivityEvent, ConnectivityRequester}, multiaddr::Multiaddr, @@ -54,7 +55,7 @@ use tari_core::{ }, }; use tari_wallet::{ - output_manager_service::{handle::OutputManagerHandle, TxId}, + output_manager_service::handle::OutputManagerHandle, transaction_service::handle::{TransactionEvent, TransactionServiceHandle}, WalletSqlite, }; @@ -661,7 +662,7 @@ pub async fn command_runner( } if count > 0 { let average = f64::from(sum) / count as f64; - let average = Tari::from(average / 1_000_000f64); + let average = Tari::try_from(average / 1_000_000f64)?; println!("Average value UTXO : {}", average); } if let Some(max) = values.iter().max() { diff --git a/applications/tari_console_wallet/src/automation/error.rs b/applications/tari_console_wallet/src/automation/error.rs index d371a0c6c67..483d2ae371a 100644 --- a/applications/tari_console_wallet/src/automation/error.rs +++ b/applications/tari_console_wallet/src/automation/error.rs @@ -24,8 +24,11 @@ use std::num::{ParseFloatError, ParseIntError}; use chrono_english::DateError; use log::*; -use tari_app_utilities::utilities::ExitCodes; -use tari_core::transactions::{tari_amount::MicroTariError, transaction::TransactionError}; +use tari_common::exit_codes::ExitCodes; +use tari_core::transactions::{ + tari_amount::{MicroTariError, TariConversionError}, + transaction::TransactionError, +}; use tari_wallet::{ error::{WalletError, WalletStorageError}, output_manager_service::error::OutputManagerError, @@ -41,6 +44,8 @@ pub const LOG_TARGET: &str = "wallet::automation::error"; pub enum CommandError { #[error("Argument error - were they in the right order?")] Argument, + #[error("Tari value conversion error `{0}`")] + TariConversionError(#[from] TariConversionError), #[error("Transaction service error `{0}`")] TransactionError(#[from] TransactionError), #[error("Transaction service error `{0}`")] diff --git a/applications/tari_console_wallet/src/init/mod.rs b/applications/tari_console_wallet/src/init/mod.rs index e99289be5d7..1a68dd7a886 100644 --- a/applications/tari_console_wallet/src/init/mod.rs +++ b/applications/tari_console_wallet/src/init/mod.rs @@ -26,8 +26,8 @@ use log::*; use rpassword::prompt_password_stdout; use rustyline::Editor; -use tari_app_utilities::utilities::{create_transport_type, ExitCodes}; -use tari_common::{ConfigBootstrap, GlobalConfig}; +use tari_app_utilities::utilities::create_transport_type; +use tari_common::{exit_codes::ExitCodes, ConfigBootstrap, GlobalConfig}; use tari_common_types::types::PrivateKey; use tari_comms::{ peer_manager::{Peer, PeerFeatures}, diff --git a/applications/tari_console_wallet/src/main.rs b/applications/tari_console_wallet/src/main.rs index 23b917fb16d..59b857eaef1 100644 --- a/applications/tari_console_wallet/src/main.rs +++ b/applications/tari_console_wallet/src/main.rs @@ -22,8 +22,8 @@ use log::*; use opentelemetry::{self, global, KeyValue}; use recovery::prompt_private_key_from_seed_words; use std::{env, process}; -use tari_app_utilities::{consts, initialization::init_configuration, utilities::ExitCodes}; -use tari_common::{configuration::bootstrap::ApplicationType, ConfigBootstrap}; +use tari_app_utilities::{consts, initialization::init_configuration}; +use tari_common::{configuration::bootstrap::ApplicationType, exit_codes::ExitCodes, ConfigBootstrap}; use tari_common_types::types::PrivateKey; use tari_shutdown::Shutdown; use tracing_subscriber::{layer::SubscriberExt, Registry}; diff --git a/applications/tari_console_wallet/src/notifier/mod.rs b/applications/tari_console_wallet/src/notifier/mod.rs index 08cefc3ad60..1ec8bc7d84e 100644 --- a/applications/tari_console_wallet/src/notifier/mod.rs +++ b/applications/tari_console_wallet/src/notifier/mod.rs @@ -26,9 +26,9 @@ use std::{ path::PathBuf, process::{Command, Output}, }; +use tari_common_types::transaction::TxId; use tari_core::tari_utilities::hex::Hex; use tari_wallet::{ - output_manager_service::TxId, transaction_service::storage::models::{ CompletedTransaction, InboundTransaction, diff --git a/applications/tari_console_wallet/src/recovery.rs b/applications/tari_console_wallet/src/recovery.rs index 370d399ff5c..dde9f8a769d 100644 --- a/applications/tari_console_wallet/src/recovery.rs +++ b/applications/tari_console_wallet/src/recovery.rs @@ -24,7 +24,7 @@ use chrono::offset::Local; use futures::FutureExt; use log::*; use rustyline::Editor; -use tari_app_utilities::utilities::ExitCodes; +use tari_common::exit_codes::ExitCodes; use tari_common_types::types::PrivateKey; use tari_crypto::tari_utilities::hex::Hex; use tari_key_manager::mnemonic::to_secretkey; diff --git a/applications/tari_console_wallet/src/ui/components/send_tab.rs b/applications/tari_console_wallet/src/ui/components/send_tab.rs index 4c997e8c98c..df98d2f6fdf 100644 --- a/applications/tari_console_wallet/src/ui/components/send_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/send_tab.rs @@ -101,7 +101,7 @@ impl SendTab { Spans::from(vec![ Span::raw("Press "), Span::styled("S", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to send normal a transaction, "), + Span::raw(" to send a normal transaction, "), Span::styled("O", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to send a one-sided transaction."), ]), diff --git a/applications/tari_console_wallet/src/ui/components/transactions_tab.rs b/applications/tari_console_wallet/src/ui/components/transactions_tab.rs index 1baf29065fe..3b4331f28dc 100644 --- a/applications/tari_console_wallet/src/ui/components/transactions_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/transactions_tab.rs @@ -7,7 +7,7 @@ use crate::ui::{ MAX_WIDTH, }; use chrono::{DateTime, Local}; -use tari_wallet::transaction_service::storage::models::{TransactionDirection, TransactionStatus}; +use tari_common_types::transaction::{TransactionDirection, TransactionStatus}; use tokio::runtime::Handle; use tui::{ backend::Backend, diff --git a/applications/tari_console_wallet/src/ui/mod.rs b/applications/tari_console_wallet/src/ui/mod.rs index ac01ec6d75a..18c952fcbf9 100644 --- a/applications/tari_console_wallet/src/ui/mod.rs +++ b/applications/tari_console_wallet/src/ui/mod.rs @@ -22,7 +22,7 @@ use crate::utils::crossterm_events::CrosstermEvents; use log::error; -use tari_app_utilities::utilities::ExitCodes; +use tari_common::exit_codes::ExitCodes; mod app; mod components; diff --git a/applications/tari_console_wallet/src/ui/state/app_state.rs b/applications/tari_console_wallet/src/ui/state/app_state.rs index e3a61b10dbb..8bd616df4d7 100644 --- a/applications/tari_console_wallet/src/ui/state/app_state.rs +++ b/applications/tari_console_wallet/src/ui/state/app_state.rs @@ -38,7 +38,11 @@ use tokio::{ }; use tari_common::{configuration::Network, GlobalConfig}; -use tari_common_types::{emoji::EmojiId, types::PublicKey}; +use tari_common_types::{ + emoji::EmojiId, + transaction::{TransactionDirection, TransactionStatus, TxId}, + types::PublicKey, +}; use tari_comms::{ connectivity::ConnectivityEventRx, multiaddr::Multiaddr, @@ -52,11 +56,8 @@ use tari_wallet::{ base_node_service::{handle::BaseNodeEventReceiver, service::BaseNodeState}, connectivity_service::WalletConnectivityHandle, contacts_service::storage::database::Contact, - output_manager_service::{handle::OutputManagerEventReceiver, service::Balance, TxId}, - transaction_service::{ - handle::TransactionEventReceiver, - storage::models::{CompletedTransaction, TransactionStatus}, - }, + output_manager_service::{handle::OutputManagerEventReceiver, service::Balance}, + transaction_service::{handle::TransactionEventReceiver, storage::models::CompletedTransaction}, WalletSqlite, }; @@ -74,10 +75,7 @@ use crate::{ utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY}, wallet_modes::PeerConfig, }; -use tari_wallet::{ - output_manager_service::handle::OutputManagerHandle, - transaction_service::storage::models::TransactionDirection, -}; +use tari_wallet::output_manager_service::handle::OutputManagerHandle; const LOG_TARGET: &str = "wallet::console_wallet::app_state"; diff --git a/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs b/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs index ed42ceaeb03..4f9e8da173a 100644 --- a/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs +++ b/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs @@ -23,11 +23,12 @@ use crate::{notifier::Notifier, ui::state::AppStateInner}; use log::*; use std::sync::Arc; +use tari_common_types::transaction::TxId; use tari_comms::{connectivity::ConnectivityEvent, peer_manager::Peer}; use tari_wallet::{ base_node_service::{handle::BaseNodeEvent, service::BaseNodeState}, connectivity_service::WalletConnectivityInterface, - output_manager_service::{handle::OutputManagerEvent, TxId}, + output_manager_service::handle::OutputManagerEvent, transaction_service::handle::TransactionEvent, }; use tokio::sync::{broadcast, RwLock}; diff --git a/applications/tari_console_wallet/src/wallet_modes.rs b/applications/tari_console_wallet/src/wallet_modes.rs index 0fbc183cbb6..2193021b02f 100644 --- a/applications/tari_console_wallet/src/wallet_modes.rs +++ b/applications/tari_console_wallet/src/wallet_modes.rs @@ -31,8 +31,7 @@ use crate::{ use log::*; use rand::{rngs::OsRng, seq::SliceRandom}; use std::{fs, io::Stdout, net::SocketAddr, path::PathBuf}; -use tari_app_utilities::utilities::ExitCodes; -use tari_common::{ConfigBootstrap, GlobalConfig}; +use tari_common::{exit_codes::ExitCodes, ConfigBootstrap, GlobalConfig}; use tari_comms::peer_manager::Peer; use tari_wallet::WalletSqlite; use tokio::runtime::Handle; diff --git a/applications/tari_mining_node/src/config.rs b/applications/tari_mining_node/src/config.rs index c65446d0515..6b46e8e1954 100644 --- a/applications/tari_mining_node/src/config.rs +++ b/applications/tari_mining_node/src/config.rs @@ -108,7 +108,7 @@ impl MinerConfig { Duration::from_secs(10) } - pub fn validate_tip_timeout_sec(&self) -> Duration { + pub fn validate_tip_interval(&self) -> Duration { Duration::from_secs(self.validate_tip_timeout_sec) } } diff --git a/applications/tari_mining_node/src/main.rs b/applications/tari_mining_node/src/main.rs index 2de68f4ccb8..568bc67eb99 100644 --- a/applications/tari_mining_node/src/main.rs +++ b/applications/tari_mining_node/src/main.rs @@ -33,7 +33,11 @@ mod utils; use crate::{ miner::MiningReport, - stratum::{stratum_controller::controller::Controller, stratum_miner::miner::StratumMiner}, + stratum::{ + stratum_controller::controller::Controller, + stratum_miner::miner::StratumMiner, + stratum_statistics::stats::Statistics, + }, }; use errors::{err_empty, MinerError}; use miner::Miner; @@ -42,22 +46,29 @@ use std::{ sync::{ atomic::{AtomicBool, Ordering}, Arc, + RwLock, }, thread, time::Instant, }; use tari_app_grpc::tari_rpc::{base_node_client::BaseNodeClient, wallet_client::WalletClient}; -use tari_app_utilities::{ - initialization::init_configuration, - utilities::{ExitCodes, ExitCodes::ConfigError}, +use tari_app_utilities::initialization::init_configuration; +use tari_common::{ + configuration::bootstrap::ApplicationType, + exit_codes::{ExitCodes, ExitCodes::ConfigError}, + ConfigBootstrap, + DefaultConfigLoader, + GlobalConfig, }; -use tari_common::{configuration::bootstrap::ApplicationType, ConfigBootstrap, DefaultConfigLoader, GlobalConfig}; use tari_core::blocks::BlockHeader; use tari_crypto::{ristretto::RistrettoPublicKey, tari_utilities::hex::Hex}; use tokio::{runtime::Runtime, time::sleep}; use tonic::transport::Channel; use utils::{coinbase_request, extract_outputs_and_kernels}; +pub const LOG_TARGET: &str = "tari_mining_node::miner::main"; +pub const LOG_TARGET_FILE: &str = "tari_mining_node::logging::miner::main"; + /// Application entry point fn main() { let rt = Runtime::new().expect("Failed to start tokio runtime"); @@ -65,7 +76,7 @@ fn main() { Ok(_) => std::process::exit(0), Err(exit_code) => { eprintln!("Fatal error: {:?}", exit_code); - error!("Exiting with code: {:?}", exit_code); + error!(target: LOG_TARGET, "Exiting with code: {:?}", exit_code); std::process::exit(exit_code.as_i32()) }, } @@ -80,8 +91,8 @@ async fn main_inner() -> Result<(), ExitCodes> { config.mining_worker_name = global.mining_worker_name.clone(); config.mining_wallet_address = global.mining_wallet_address.clone(); config.mining_pool_address = global.mining_pool_address.clone(); - debug!("{:?}", bootstrap); - debug!("{:?}", config); + debug!(target: LOG_TARGET_FILE, "{:?}", bootstrap); + debug!(target: LOG_TARGET_FILE, "{:?}", config); if !config.mining_wallet_address.is_empty() && !config.mining_pool_address.is_empty() { let url = config.mining_pool_address.clone(); @@ -91,18 +102,25 @@ async fn main_inner() -> Result<(), ExitCodes> { if !config.mining_worker_name.is_empty() { miner_address += &format!("{}{}", ".", &config.mining_worker_name); } - let mut mc = Controller::new().unwrap_or_else(|e| { + let stats = Arc::new(RwLock::new(Statistics::default())); + let mut mc = Controller::new(stats.clone()).unwrap_or_else(|e| { + debug!(target: LOG_TARGET_FILE, "Error loading mining controller: {}", e); panic!("Error loading mining controller: {}", e); }); - let cc = stratum::controller::Controller::new(&url, Some(miner_address), None, None, mc.tx.clone()) - .unwrap_or_else(|e| { - panic!("Error loading stratum client controller: {:?}", e); - }); + let cc = + stratum::controller::Controller::new(&url, Some(miner_address), None, None, mc.tx.clone(), stats.clone()) + .unwrap_or_else(|e| { + debug!( + target: LOG_TARGET_FILE, + "Error loading stratum client controller: {:?}", e + ); + panic!("Error loading stratum client controller: {:?}", e); + }); let miner_stopped = Arc::new(AtomicBool::new(false)); let client_stopped = Arc::new(AtomicBool::new(false)); mc.set_client_tx(cc.tx.clone()); - let mut miner = StratumMiner::new(config); + let mut miner = StratumMiner::new(config, stats); if let Err(e) = miner.start_solvers() { println!("Error. Please check logs for further info."); println!("Error details:"); @@ -115,7 +133,7 @@ async fn main_inner() -> Result<(), ExitCodes> { .name("mining_controller".to_string()) .spawn(move || { if let Err(e) = mc.run(miner) { - error!("Error. Please check logs for further info: {:?}", e); + error!(target: LOG_TARGET, "Error. Please check logs for further info: {:?}", e); return; } miner_stopped_internal.store(true, Ordering::Relaxed); @@ -139,19 +157,21 @@ async fn main_inner() -> Result<(), ExitCodes> { Ok(()) } else { config.mine_on_tip_only = global.mine_on_tip_only; - debug!("mine_on_tip_only is {}", config.mine_on_tip_only); - + debug!( + target: LOG_TARGET_FILE, + "mine_on_tip_only is {}", config.mine_on_tip_only + ); let (mut node_conn, mut wallet_conn) = connect(&config, &global).await.map_err(ExitCodes::grpc)?; let mut blocks_found: u64 = 0; loop { - debug!("Starting new mining cycle"); + debug!(target: LOG_TARGET_FILE, "Starting new mining cycle"); match mining_cycle(&mut node_conn, &mut wallet_conn, &config, &bootstrap).await { err @ Err(MinerError::GrpcConnection(_)) | err @ Err(MinerError::GrpcStatus(_)) => { // Any GRPC error we will try to reconnect with a standard delay - error!("Connection error: {:?}", err); + error!(target: LOG_TARGET, "Connection error: {:?}", err); loop { - debug!("Holding for {:?}", config.wait_timeout()); + debug!(target: LOG_TARGET_FILE, "Holding for {:?}", config.wait_timeout()); sleep(config.wait_timeout()).await; match connect(&config, &global).await { Ok((nc, wc)) => { @@ -160,22 +180,28 @@ async fn main_inner() -> Result<(), ExitCodes> { break; }, Err(err) => { - error!("Connection error: {:?}", err); + error!(target: LOG_TARGET, "Connection error: {:?}", err); continue; }, } } }, Err(MinerError::MineUntilHeightReached(h)) => { - info!("Prescribed blockchain height {} reached. Aborting ...", h); + info!( + target: LOG_TARGET, + "Prescribed blockchain height {} reached. Aborting ...", h + ); return Ok(()); }, Err(MinerError::MinerLostBlock(h)) => { - info!("Height {} already mined by other node. Restarting ...", h); + info!( + target: LOG_TARGET, + "Height {} already mined by other node. Restarting ...", h + ); }, Err(err) => { - error!("Error: {:?}", err); - debug!("Holding for {:?}", config.wait_timeout()); + error!(target: LOG_TARGET, "Error: {:?}", err); + debug!(target: LOG_TARGET_FILE, "Holding for {:?}", config.wait_timeout()); sleep(config.wait_timeout()).await; }, Ok(submitted) => { @@ -198,10 +224,10 @@ async fn connect( global: &GlobalConfig, ) -> Result<(BaseNodeClient, WalletClient), MinerError> { let base_node_addr = config.base_node_addr(global); - info!("Connecting to base node at {}", base_node_addr); + info!(target: LOG_TARGET, "Connecting to base node at {}", base_node_addr); let node_conn = BaseNodeClient::connect(base_node_addr.clone()).await?; let wallet_addr = config.wallet_addr(global); - info!("Connecting to wallet at {}", wallet_addr); + info!(target: LOG_TARGET, "Connecting to wallet at {}", wallet_addr); let wallet_conn = WalletClient::connect(wallet_addr.clone()).await?; Ok((node_conn, wallet_conn)) @@ -264,8 +290,8 @@ async fn mining_cycle( if report.difficulty < min_diff { submit = false; debug!( - "Mined difficulty {} below minimum difficulty {}. Not submitting.", - report.difficulty, min_diff + target: LOG_TARGET_FILE, + "Mined difficulty {} below minimum difficulty {}. Not submitting.", report.difficulty, min_diff ); } } @@ -273,8 +299,10 @@ async fn mining_cycle( if report.difficulty > max_diff { submit = false; debug!( + target: LOG_TARGET_FILE, "Mined difficulty {} greater than maximum difficulty {}. Not submitting.", - report.difficulty, max_diff + report.difficulty, + max_diff ); } } @@ -282,8 +310,11 @@ async fn mining_cycle( // Mined a block fitting the difficulty let block_header = BlockHeader::try_from(header.clone()).map_err(MinerError::Conversion)?; info!( + target: LOG_TARGET, "Miner {} found block header {} with difficulty {:?}", - report.miner, block_header, report.difficulty, + report.miner, + block_header, + report.difficulty, ); let mut mined_block = block.clone(); mined_block.header = Some(header); @@ -297,7 +328,7 @@ async fn mining_cycle( } else { display_report(&report, config).await; } - if config.mine_on_tip_only && reporting_timeout.elapsed() > config.validate_tip_timeout_sec() { + if config.mine_on_tip_only && reporting_timeout.elapsed() > config.validate_tip_interval() { validate_tip(node_conn, report.height, bootstrap.mine_until_height).await?; reporting_timeout = Instant::now(); } @@ -310,6 +341,7 @@ async fn mining_cycle( async fn display_report(report: &MiningReport, config: &MinerConfig) { let hashrate = report.hashes as f64 / report.elapsed.as_micros() as f64; debug!( + target: LOG_TARGET_FILE, "Miner {} reported {:.2}MH/s with total {:.2}MH/s over {} threads. Height: {}. Target: {})", report.miner, hashrate, diff --git a/applications/tari_mining_node/src/miner.rs b/applications/tari_mining_node/src/miner.rs index e2bc4352a99..21f7b891f3e 100644 --- a/applications/tari_mining_node/src/miner.rs +++ b/applications/tari_mining_node/src/miner.rs @@ -33,6 +33,8 @@ use std::{ use tari_app_grpc::{conversions::timestamp, tari_rpc::BlockHeader}; use thread::JoinHandle; +pub const LOG_TARGET: &str = "tari_mining_node::miner::standalone"; + // Identify how often mining thread is reporting / checking context // ~400_000 hashes per second const REPORTING_FREQUENCY: u64 = 3_000_000; @@ -107,20 +109,20 @@ impl Stream for Miner { type Item = MiningReport; fn poll_next(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { - trace!("Polling Miner"); + trace!(target: LOG_TARGET, "Polling Miner"); // First poll would start all the threads passing async context waker if self.threads.is_empty() && self.num_threads > 0 { debug!( - "Starting {} mining threads for target difficulty {}", - self.num_threads, self.target_difficulty + target: LOG_TARGET, + "Starting {} mining threads for target difficulty {}", self.num_threads, self.target_difficulty ); self.start_threads(ctx); return Poll::Pending; } else if self.num_threads == 0 { - error!("Cannot mine: no mining threads"); + error!(target: LOG_TARGET, "Cannot mine: no mining threads"); return Poll::Ready(None); } else if self.channels.is_empty() { - debug!("Finished mining"); + debug!(target: LOG_TARGET, "Finished mining"); return Poll::Ready(None); } @@ -167,14 +169,14 @@ pub fn mining_task( let mut hasher = BlockHeaderSha3::new(header).unwrap(); hasher.random_nonce(); // We're mining over here! - info!("Mining thread {} started", miner); + info!(target: LOG_TARGET, "Mining thread {} started", miner); // Mining work loop { let difficulty = hasher.difficulty(); if difficulty >= target_difficulty { debug!( - "Miner {} found nonce {} with matching difficulty {}", - miner, hasher.nonce, difficulty + target: LOG_TARGET, + "Miner {} found nonce {} with matching difficulty {}", miner, hasher.nonce, difficulty ); if let Err(err) = sender.try_send(MiningReport { miner, @@ -186,10 +188,10 @@ pub fn mining_task( header: Some(hasher.into_header()), target_difficulty, }) { - error!("Miner {} failed to send report: {}", miner, err); + error!(target: LOG_TARGET, "Miner {} failed to send report: {}", miner, err); } waker.wake(); - info!("Mining thread {} stopped", miner); + info!(target: LOG_TARGET, "Mining thread {} stopped", miner); return; } if hasher.nonce % REPORTING_FREQUENCY == 0 { @@ -204,9 +206,9 @@ pub fn mining_task( target_difficulty, }); waker.clone().wake(); - trace!("Reporting from {} result {:?}", miner, res); + trace!(target: LOG_TARGET, "Reporting from {} result {:?}", miner, res); if let Err(TrySendError::Disconnected(_)) = res { - info!("Mining thread {} disconnected", miner); + info!(target: LOG_TARGET, "Mining thread {} disconnected", miner); return; } hasher.set_forward_timestamp(timestamp().seconds as u64); diff --git a/applications/tari_mining_node/src/stratum/controller.rs b/applications/tari_mining_node/src/stratum/controller.rs index 1fb6e76256d..288e94157ec 100644 --- a/applications/tari_mining_node/src/stratum/controller.rs +++ b/applications/tari_mining_node/src/stratum/controller.rs @@ -20,16 +20,19 @@ // 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 crate::stratum::{error::Error, stratum_types as types, stream::Stream}; +use crate::stratum::{error::Error, stratum_statistics::stats, stratum_types as types, stream::Stream}; use log::*; use std::{ self, io::{BufRead, ErrorKind, Write}, - sync::mpsc, + sync::{mpsc, Arc, RwLock}, thread, }; +pub const LOG_TARGET: &str = "tari_mining_node::miner::stratum::controller"; +pub const LOG_TARGET_FILE: &str = "tari_mining_node::logging::miner::stratum::controller"; + pub struct Controller { server_url: String, server_login: Option, @@ -40,6 +43,7 @@ pub struct Controller { pub tx: mpsc::Sender, miner_tx: mpsc::Sender, last_request_id: String, + stats: Arc>, } // fn invalid_error_response() -> types::RpcError { @@ -56,6 +60,7 @@ impl Controller { server_password: Option, server_tls_enabled: Option, miner_tx: mpsc::Sender, + stats: Arc>, ) -> Result { let (tx, rx) = mpsc::channel::(); Ok(Controller { @@ -68,6 +73,7 @@ impl Controller { rx, miner_tx, last_request_id: "".to_string(), + stats, }) } @@ -96,7 +102,7 @@ impl Controller { Err(ref e) if e.kind() == ErrorKind::BrokenPipe => Err(Error::Connection("broken pipe".to_string())), Err(ref e) if e.kind() == ErrorKind::WouldBlock => Ok(None), Err(e) => { - error!("Communication error with stratum server: {}", e); + error!(target: LOG_TARGET, "Communication error with stratum server: {}", e); Err(Error::Connection("broken pipe".to_string())) }, } @@ -106,7 +112,7 @@ impl Controller { if self.stream.is_none() { return Err(Error::Connection(String::from("No server connection"))); } - debug!("sending request: {}", message); + debug!(target: LOG_TARGET_FILE, "sending request: {}", message); let _ = self.stream.as_mut().unwrap().write(message.as_bytes()); let _ = self.stream.as_mut().unwrap().write(b"\n"); let _ = self.stream.as_mut().unwrap().flush(); @@ -172,7 +178,10 @@ impl Controller { } fn send_message_submit(&mut self, job_id: u64, hash: String, nonce: u64) -> Result<(), Error> { - info!("Submitting Solution with hash {} and nonce {}", hash, nonce); + info!( + target: LOG_TARGET, + "Submitting share with hash {} and nonce {}", hash, nonce + ); let params_in = types::submit_params::SubmitParams { id: self.last_request_id.to_string(), job_id, @@ -211,15 +220,15 @@ impl Controller { } pub fn handle_request(&mut self, req: types::rpc_request::RpcRequest) -> Result<(), Error> { - debug!("Received request type: {}", req.method); + debug!(target: LOG_TARGET_FILE, "Received request type: {}", req.method); match req.method.as_str() { "job" => match req.params { None => Err(Error::Request("No params in job request".to_owned())), Some(params) => { let job = serde_json::from_value::(params)?; info!( - "Got a new job for height {} with target difficulty {}", - job.height, job.target + target: LOG_TARGET, + "Got a new job for height {} with target difficulty {}", job.height, job.target ); self.send_miner_job(job) }, @@ -228,13 +237,31 @@ impl Controller { } } + fn handle_error(&mut self, error: types::rpc_error::RpcError) { + if vec![-1, 24].contains(&error.code) { + // unauthorized + let _ = self.send_login(); + } else if vec![21, 20, 22, 23, 25].contains(&error.code) { + // problem with template + let _ = self.send_message_get_job_template(); + } + } + + #[allow(clippy::cognitive_complexity)] pub fn handle_response(&mut self, res: types::rpc_response::RpcResponse) -> Result<(), Error> { - debug!("Received response with id: {}", res.id); + debug!(target: LOG_TARGET_FILE, "Received response with id: {}", res.id); match res.result { Some(result) => { let login_response = serde_json::from_value::(result.clone()); if let Ok(st) = login_response { - info!("Successful login to server, worker identifier is {}", st.id); + info!( + target: LOG_TARGET, + "Successful login to server, worker identifier is {}", st.id + ); + info!( + target: LOG_TARGET, + "Got a new job for height {} with target difficulty {}", st.job.height, st.job.target + ); self.last_request_id = st.id; let _ = self.send_miner_job(st.job); return Ok(()); @@ -242,31 +269,42 @@ impl Controller { let job_response = serde_json::from_value::(result.clone()); if let Ok(st) = job_response { info!( - "Got a new job for height {} with target difficulty {}", - st.height, st.target + target: LOG_TARGET, + "Got a new job for height {} with target difficulty {}", st.height, st.target ); let _ = self.send_miner_job(st); return Ok(()); }; + let submit_response = serde_json::from_value::(result.clone()); + if let Ok(st) = submit_response { + let error = st.error; + if let Some(error) = error { + // rejected share + self.handle_error(error.clone()); + info!(target: LOG_TARGET, "Rejected"); + debug!(target: LOG_TARGET_FILE, "Share rejected: {:?}", error); + let mut stats = self.stats.write().unwrap(); + stats.mining_stats.solution_stats.rejected += 1; + } else { + // accepted share + info!(target: LOG_TARGET, "Accepted"); + debug!(target: LOG_TARGET_FILE, "Share accepted: {:?}", st.status); + } + return Ok(()); + } let rpc_response = serde_json::from_value::(result); if let Ok(st) = rpc_response { let error = st.error; if let Some(error) = error { - if vec![-1, 24].contains(&error.code) { - // unauthorized - let _ = self.send_login(); - } else if vec![21, 20, 22, 23, 25].contains(&error.code) { - // problem with template - let _ = self.send_message_get_job_template(); - } - } else { - info!("{:?}", st.result); + self.handle_error(error); } return Ok(()); + } else { + debug!(target: LOG_TARGET_FILE, "RPC Response: {:?}", rpc_response); }; }, None => { - error!("{:?}", res); + error!(target: LOG_TARGET, "RPC error: {:?}", res); }, } Ok(()) @@ -298,7 +336,7 @@ impl Controller { self.stream = None; } else { let status = format!("Connection Status: Connected to server at {}.", self.server_url); - info!("{}", status); + info!(target: LOG_TARGET, "{}", status); } next_server_retry = time::get_time().sec + server_retry_interval; if self.stream.is_none() { @@ -319,41 +357,41 @@ impl Controller { Ok(Some(m)) => { // figure out what kind of message, // and dispatch appropriately - debug!("Received message: {}", m); + debug!(target: LOG_TARGET_FILE, "Received message: {}", m); // Deserialize to see what type of object it is if let Ok(v) = serde_json::from_str::(&m) { // Is this a response or request? if v["method"] == "job" { // this is a request match serde_json::from_str::(&m) { - Err(e) => error!("Error parsing request {} : {:?}", m, e), + Err(e) => error!(target: LOG_TARGET, "Error parsing request {} : {:?}", m, e), Ok(request) => { if let Err(err) = self.handle_request(request) { - error!("Error handling request {} : :{:?}", m, err) + error!(target: LOG_TARGET, "Error handling request {} : :{:?}", m, err) } }, } } else { // this is a response match serde_json::from_str::(&m) { - Err(e) => error!("Error parsing response {} : {:?}", m, e), + Err(e) => error!(target: LOG_TARGET, "Error parsing response {} : {:?}", m, e), Ok(response) => { if let Err(err) = self.handle_response(response) { - error!("Error handling response {} : :{:?}", m, err) + error!(target: LOG_TARGET, "Error handling response {} : :{:?}", m, err) } }, } } continue; } else { - error!("Error parsing message: {}", m) + error!(target: LOG_TARGET, "Error parsing message: {}", m) } }, Ok(None) => { // noop, nothing to read for this interval }, Err(e) => { - error!("Error reading message: {:?}", e); + error!(target: LOG_TARGET, "Error reading message: {:?}", e); self.stream = None; continue; }, @@ -364,19 +402,19 @@ impl Controller { // Talk to the miner algorithm while let Some(message) = self.rx.try_iter().next() { - debug!("Client received message: {:?}", message); + debug!(target: LOG_TARGET_FILE, "Client received message: {:?}", message); let result = match message { types::client_message::ClientMessage::FoundSolution(job_id, hash, nonce) => { self.send_message_submit(job_id, hash, nonce) }, types::client_message::ClientMessage::KeepAlive => self.send_keepalive(), types::client_message::ClientMessage::Shutdown => { - debug!("Shutting down client controller"); + debug!(target: LOG_TARGET_FILE, "Shutting down client controller"); return; }, }; if let Err(e) = result { - error!("Mining Controller Error {:?}", e); + error!(target: LOG_TARGET, "Mining Controller Error {:?}", e); self.stream = None; } } diff --git a/applications/tari_mining_node/src/stratum/mod.rs b/applications/tari_mining_node/src/stratum/mod.rs index b426a7c4c5e..b71d5ca82be 100644 --- a/applications/tari_mining_node/src/stratum/mod.rs +++ b/applications/tari_mining_node/src/stratum/mod.rs @@ -24,5 +24,6 @@ pub mod controller; pub mod error; pub mod stratum_controller; pub mod stratum_miner; +pub mod stratum_statistics; pub mod stratum_types; pub mod stream; diff --git a/applications/tari_mining_node/src/stratum/stratum_controller/controller.rs b/applications/tari_mining_node/src/stratum/stratum_controller/controller.rs index f40e432e6f1..b7315600b7c 100644 --- a/applications/tari_mining_node/src/stratum/stratum_controller/controller.rs +++ b/applications/tari_mining_node/src/stratum/stratum_controller/controller.rs @@ -22,10 +22,19 @@ // use crate::{ stratum, - stratum::{stratum_miner::miner::StratumMiner, stratum_types as types}, + stratum::{stratum_miner::miner::StratumMiner, stratum_statistics::stats, stratum_types as types}, }; use log::*; -use std::{self, sync::mpsc, thread, time::SystemTime}; +use std::{ + self, + sync::{mpsc, Arc, RwLock}, + thread, + time::{Duration, SystemTime}, +}; + +pub const LOG_TARGET: &str = "tari_mining_node::miner::stratum::controller"; +pub const LOG_TARGET_FILE: &str = "tari_mining_node::logging::miner::stratum::controller"; +const REPORTING_FREQUENCY: u64 = 20; pub struct Controller { rx: mpsc::Receiver, @@ -35,10 +44,12 @@ pub struct Controller { current_job_id: u64, current_blob: String, keep_alive_time: SystemTime, + stats: Arc>, + elapsed: SystemTime, } impl Controller { - pub fn new() -> Result { + pub fn new(stats: Arc>) -> Result { let (tx, rx) = mpsc::channel::(); Ok(Controller { rx, @@ -48,6 +59,8 @@ impl Controller { current_job_id: 0, current_blob: "".to_string(), keep_alive_time: SystemTime::now(), + stats, + elapsed: SystemTime::now(), }) } @@ -55,10 +68,66 @@ impl Controller { self.client_tx = Some(client_tx); } + fn display_stats(&mut self, elapsed: Duration) { + let mut stats = self.stats.write().unwrap(); + debug!(target: LOG_TARGET_FILE, "{:?}", stats.mining_stats); + info!( + target: LOG_TARGET, + "{}", + "--------------- Mining Statistics ---------------".to_string() + ); + info!( + target: LOG_TARGET, + "{}", + format!("Number of solver threads: {}", stats.mining_stats.solvers) + ); + if stats.mining_stats.solution_stats.found > 0 { + info!( + target: LOG_TARGET, + "{}", + format!( + "Estimated combined solver share rate: {:.1$} (S/s)", + stats.mining_stats.sols(), + 5 + ) + ); + } + info!( + target: LOG_TARGET, + "{}", + format!( + "Combined solver hash rate: {:.1$} (Mh/s)", + stats.mining_stats.hash_rate(elapsed), + 5 + ) + ); + info!( + target: LOG_TARGET, + "{}", + format!( + "Shares found: {}, accepted: {}, rejected: {}", + stats.mining_stats.solution_stats.found, + stats.mining_stats.solution_stats.found - stats.mining_stats.solution_stats.rejected, + stats.mining_stats.solution_stats.rejected + ) + ); + info!( + target: LOG_TARGET, + "{}", + "-------------------------------------------------".to_string() + ); + } + pub fn run(&mut self, mut miner: StratumMiner) -> Result<(), stratum::error::Error> { loop { + if let Ok(report) = self.elapsed.elapsed() { + if report.as_secs() >= REPORTING_FREQUENCY { + self.display_stats(report); + self.elapsed = SystemTime::now(); + } + } while let Some(message) = self.rx.try_iter().next() { - debug!("Miner received message: {:?}", message); + debug!(target: LOG_TARGET_FILE, "Miner received message: {:?}", message); let result: Result<(), stratum::error::Error> = match message { types::miner_message::MinerMessage::ReceivedJob(height, job_id, diff, blob) => { self.current_height = height; @@ -72,24 +141,27 @@ impl Controller { ) }, types::miner_message::MinerMessage::StopJob => { - debug!("Stopping jobs"); + debug!(target: LOG_TARGET_FILE, "Stopping jobs"); miner.pause_solvers(); Ok(()) }, types::miner_message::MinerMessage::ResumeJob => { - debug!("Resuming jobs"); + debug!(target: LOG_TARGET_FILE, "Resuming jobs"); miner.resume_solvers(); Ok(()) }, types::miner_message::MinerMessage::Shutdown => { - debug!("Stopping jobs and Shutting down mining controller"); + debug!( + target: LOG_TARGET_FILE, + "Stopping jobs and Shutting down mining controller" + ); miner.stop_solvers(); miner.wait_for_solver_shutdown(); Ok(()) }, }; if let Err(e) = result { - error!("Mining Controller Error {:?}", e); + error!(target: LOG_TARGET, "Mining Controller Error {:?}", e); } } @@ -103,6 +175,8 @@ impl Controller { ss.job_id, ss.hash, ss.nonce, )); self.keep_alive_time = SystemTime::now(); + let mut stats = self.stats.write().unwrap(); + stats.mining_stats.solution_stats.found += 1; } else if self.keep_alive_time.elapsed().unwrap().as_secs() >= 30 { self.keep_alive_time = SystemTime::now(); let _ = self diff --git a/applications/tari_mining_node/src/stratum/stratum_miner/miner.rs b/applications/tari_mining_node/src/stratum/stratum_miner/miner.rs index 50dc44b6c7c..303edb9f09d 100644 --- a/applications/tari_mining_node/src/stratum/stratum_miner/miner.rs +++ b/applications/tari_mining_node/src/stratum/stratum_miner/miner.rs @@ -24,11 +24,14 @@ use crate::{ config::MinerConfig, difficulty::BlockHeaderSha3, stratum, - stratum::stratum_miner::{ - control_message::ControlMessage, - job_shared_data::{JobSharedData, JobSharedDataType}, - solution::Solution, - solver_instance::SolverInstance, + stratum::{ + stratum_miner::{ + control_message::ControlMessage, + job_shared_data::{JobSharedData, JobSharedDataType}, + solution::Solution, + solver_instance::SolverInstance, + }, + stratum_statistics::stats::Statistics, }, }; use log::*; @@ -37,22 +40,31 @@ use std::{ sync::{mpsc, Arc, RwLock}, thread, time, + time::{Duration, SystemTime}, }; use tari_core::{ blocks::BlockHeader, crypto::tari_utilities::{hex::Hex, Hashable}, }; +pub const LOG_TARGET: &str = "tari_mining_node::miner::stratum::controller"; +pub const LOG_TARGET_FILE: &str = "tari_mining_node::logging::miner::stratum::controller"; + +fn calculate_sols(elapsed: Duration) -> f64 { + 1.0 / ((elapsed.as_secs() * 1_000_000_000 + elapsed.subsec_nanos() as u64) as f64 / 1_000_000_000.0) +} + pub struct StratumMiner { config: MinerConfig, pub shared_data: Arc>, control_txs: Vec>, solver_loop_txs: Vec>, solver_stopped_rxs: Vec>, + stats: Arc>, } impl StratumMiner { - pub fn new(config: MinerConfig) -> StratumMiner { + pub fn new(config: MinerConfig, stats: Arc>) -> StratumMiner { let threads = config.num_mining_threads; StratumMiner { config, @@ -60,6 +72,7 @@ impl StratumMiner { control_txs: vec![], solver_loop_txs: vec![], solver_stopped_rxs: vec![], + stats, } } @@ -70,19 +83,20 @@ impl StratumMiner { control_rx: mpsc::Receiver, solver_loop_rx: mpsc::Receiver, solver_stopped_tx: mpsc::Sender, + statistics: Arc>, ) { let stop_handle = thread::spawn(move || loop { while let Some(message) = control_rx.iter().next() { match message { ControlMessage::Stop => { - info!("Stopping Solvers"); + debug!(target: LOG_TARGET_FILE, "Stopping Solvers"); return; }, ControlMessage::Pause => { - info!("Pausing Solvers"); + debug!(target: LOG_TARGET_FILE, "Pausing Solvers"); }, ControlMessage::Resume => { - info!("Resuming Solvers"); + debug!(target: LOG_TARGET_FILE, "Resuming Solvers"); }, _ => {}, }; @@ -90,16 +104,23 @@ impl StratumMiner { }); let mut paused = true; + let mut timer = SystemTime::now(); loop { if let Some(message) = solver_loop_rx.try_iter().next() { - debug!("solver_thread - solver_loop_rx got msg: {:?}", message); + debug!( + target: LOG_TARGET_FILE, + "solver_thread - solver_loop_rx got msg: {:?}", message + ); match message { ControlMessage::Stop => break, ControlMessage::Pause => { paused = true; solver.solver_reset = true; }, - ControlMessage::Resume => paused = false, + ControlMessage::Resume => { + paused = false; + timer = SystemTime::now(); + }, _ => {}, } } @@ -115,7 +136,6 @@ impl StratumMiner { let height = { shared_data.read().unwrap().height }; let job_id = { shared_data.read().unwrap().job_id }; let target_difficulty = { shared_data.read().unwrap().difficulty }; - let mut hasher = BlockHeaderSha3::new(tari_app_grpc::tari_rpc::BlockHeader::from(header)).unwrap(); if solver.solver_reset { @@ -127,17 +147,20 @@ impl StratumMiner { hasher.inc_nonce(); solver.current_nonce = hasher.nonce; } - + let mut stats = statistics.write().unwrap(); let difficulty = hasher.difficulty(); + stats.mining_stats.add_hash(); if difficulty >= target_difficulty { let block_header: BlockHeader = BlockHeader::try_from(hasher.into_header()).unwrap(); info!( + target: LOG_TARGET, "Miner found share with hash {}, nonce {} and difficulty {:?}", block_header.hash().to_hex(), solver.current_nonce, difficulty ); debug!( + target: LOG_TARGET_FILE, "Miner found share with hash {}, difficulty {:?} and data {:?}", block_header.hash().to_hex(), difficulty, @@ -154,6 +177,11 @@ impl StratumMiner { hash: block_header.hash().to_hex(), nonce: block_header.nonce, }); + if let Ok(elapsed) = timer.elapsed() { + let sols = calculate_sols(elapsed); + stats.mining_stats.add_sols(sols); + } + timer = SystemTime::now(); } } solver.solutions = Solution::default(); @@ -169,8 +197,10 @@ impl StratumMiner { } pub fn start_solvers(&mut self) -> Result<(), stratum::error::Error> { + let mut stats = self.stats.write().unwrap(); + stats.mining_stats.solvers = self.config.num_mining_threads; let num_solvers = self.config.num_mining_threads; - info!("Spawning {} solvers", num_solvers); + info!(target: LOG_TARGET, "Spawning {} solvers", num_solvers); let mut solvers = Vec::with_capacity(num_solvers); while solvers.len() < solvers.capacity() { solvers.push(SolverInstance::new()?); @@ -183,8 +213,9 @@ impl StratumMiner { self.control_txs.push(control_tx); self.solver_loop_txs.push(solver_tx); self.solver_stopped_rxs.push(solver_stopped_rx); + let stats = self.stats.clone(); thread::spawn(move || { - StratumMiner::solver_thread(s, i, sd, control_rx, solver_rx, solver_stopped_tx); + StratumMiner::solver_thread(s, i, sd, control_rx, solver_rx, solver_stopped_tx, stats); }); } Ok(()) @@ -216,6 +247,10 @@ impl StratumMiner { sd.difficulty = difficulty; sd.header = Some(header); if paused { + info!( + target: LOG_TARGET, + "Hashing in progress... height: {}, target difficulty: {}", height, difficulty + ); self.resume_solvers(); } Ok(()) @@ -239,7 +274,7 @@ impl StratumMiner { for t in self.solver_loop_txs.iter() { let _ = t.send(ControlMessage::Stop); } - debug!("Stop message sent"); + debug!(target: LOG_TARGET_FILE, "Stop message sent"); } pub fn pause_solvers(&self) { @@ -249,7 +284,7 @@ impl StratumMiner { for t in self.solver_loop_txs.iter() { let _ = t.send(ControlMessage::Pause); } - debug!("Pause message sent"); + debug!(target: LOG_TARGET_FILE, "Pause message sent"); } pub fn resume_solvers(&self) { @@ -259,13 +294,13 @@ impl StratumMiner { for t in self.solver_loop_txs.iter() { let _ = t.send(ControlMessage::Resume); } - debug!("Resume message sent"); + debug!(target: LOG_TARGET_FILE, "Resume message sent"); } pub fn wait_for_solver_shutdown(&self) { for r in self.solver_stopped_rxs.iter() { if let Some(ControlMessage::SolverStopped(i)) = r.iter().next() { - debug!("Solver stopped: {}", i); + debug!(target: LOG_TARGET_FILE, "Solver stopped: {}", i); } } } diff --git a/applications/tari_mining_node/src/stratum/stratum_statistics/mod.rs b/applications/tari_mining_node/src/stratum/stratum_statistics/mod.rs new file mode 100644 index 00000000000..b3ca0d2f63e --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_statistics/mod.rs @@ -0,0 +1 @@ +pub(crate) mod stats; diff --git a/applications/tari_mining_node/src/stratum/stratum_statistics/stats.rs b/applications/tari_mining_node/src/stratum/stratum_statistics/stats.rs new file mode 100644 index 00000000000..cc0d758dfc0 --- /dev/null +++ b/applications/tari_mining_node/src/stratum/stratum_statistics/stats.rs @@ -0,0 +1,100 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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::time::Duration; + +#[derive(Clone, Debug)] +pub struct SolutionStatistics { + /// Total found + pub found: u32, + /// Total rejected + pub rejected: u32, +} + +impl Default for SolutionStatistics { + fn default() -> SolutionStatistics { + SolutionStatistics { found: 0, rejected: 0 } + } +} + +#[derive(Clone, Debug)] +pub struct MiningStatistics { + /// Solutions per second + sols: Vec, + /// Hashes per second + hashes: u64, + /// Number Solvers + pub solvers: usize, + /// Solution statistics + pub solution_stats: SolutionStatistics, +} + +impl Default for MiningStatistics { + fn default() -> MiningStatistics { + MiningStatistics { + solvers: 0, + sols: vec![], + hashes: 0, + solution_stats: SolutionStatistics::default(), + } + } +} + +impl MiningStatistics { + pub fn add_sols(&mut self, val: f64) { + self.sols.insert(0, val); + self.sols.truncate(60); + } + + pub fn sols(&self) -> f64 { + if self.sols.is_empty() { + 0.0 + } else { + let sum: f64 = self.sols.iter().sum(); + sum / (self.sols.len() as f64) + } + } + + pub fn add_hash(&mut self) { + self.hashes += 1; + } + + pub fn hash_rate(&mut self, elapsed: Duration) -> f64 { + let hash_rate = self.hashes as f64 / elapsed.as_micros() as f64; + // reset the total number of hashes for this interval + self.hashes = 0; + hash_rate + } +} + +#[derive(Clone, Debug)] +pub struct Statistics { + pub mining_stats: MiningStatistics, +} + +impl Default for Statistics { + fn default() -> Statistics { + Statistics { + mining_stats: MiningStatistics::default(), + } + } +} diff --git a/applications/tari_mining_node/src/stratum/stratum_types/mod.rs b/applications/tari_mining_node/src/stratum/stratum_types/mod.rs index 432ab296ce2..4142b77f469 100644 --- a/applications/tari_mining_node/src/stratum/stratum_types/mod.rs +++ b/applications/tari_mining_node/src/stratum/stratum_types/mod.rs @@ -30,5 +30,5 @@ pub(crate) mod rpc_error; pub(crate) mod rpc_request; pub(crate) mod rpc_response; pub(crate) mod submit_params; +pub(crate) mod submit_response; pub(crate) mod worker_identifier; -pub(crate) mod worker_status; diff --git a/applications/tari_mining_node/src/stratum/stratum_types/rpc_error.rs b/applications/tari_mining_node/src/stratum/stratum_types/rpc_error.rs index 118a4977ed4..0b26a4891ff 100644 --- a/applications/tari_mining_node/src/stratum/stratum_types/rpc_error.rs +++ b/applications/tari_mining_node/src/stratum/stratum_types/rpc_error.rs @@ -22,7 +22,7 @@ // use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct RpcError { pub code: i32, pub message: String, diff --git a/applications/tari_mining_node/src/stratum/stratum_types/worker_status.rs b/applications/tari_mining_node/src/stratum/stratum_types/submit_response.rs similarity index 91% rename from applications/tari_mining_node/src/stratum/stratum_types/worker_status.rs rename to applications/tari_mining_node/src/stratum/stratum_types/submit_response.rs index 142e3fcb675..6c190d860cb 100644 --- a/applications/tari_mining_node/src/stratum/stratum_types/worker_status.rs +++ b/applications/tari_mining_node/src/stratum/stratum_types/submit_response.rs @@ -20,14 +20,11 @@ // 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 crate::stratum::stratum_types::rpc_error::RpcError; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] -pub struct WorkerStatus { - pub id: String, - pub height: u64, - pub difficulty: u64, - pub accepted: u64, - pub rejected: u64, - pub stale: u64, +pub struct SubmitResponse { + pub status: Option, + pub error: Option, } diff --git a/base_layer/common_types/Cargo.toml b/base_layer/common_types/Cargo.toml index b8fa94d9702..b9ec3b98007 100644 --- a/base_layer/common_types/Cargo.toml +++ b/base_layer/common_types/Cargo.toml @@ -14,3 +14,4 @@ serde = { version = "1.0.106", features = ["derive"] } tokio = { version = "1.11", features = ["time", "sync"] } lazy_static = "1.4.0" digest = "0.9.0" +thiserror = "1.0.29" diff --git a/base_layer/common_types/src/lib.rs b/base_layer/common_types/src/lib.rs index df01ae73025..7281ebba2ac 100644 --- a/base_layer/common_types/src/lib.rs +++ b/base_layer/common_types/src/lib.rs @@ -23,6 +23,7 @@ pub mod chain_metadata; pub mod emoji; pub mod luhn; +pub mod transaction; pub mod types; pub mod waiting_requests; diff --git a/base_layer/common_types/src/transaction.rs b/base_layer/common_types/src/transaction.rs new file mode 100644 index 00000000000..16b3fba3504 --- /dev/null +++ b/base_layer/common_types/src/transaction.rs @@ -0,0 +1,108 @@ +use serde::{Deserialize, Serialize}; +use std::{ + convert::TryFrom, + fmt::{Display, Error, Formatter}, +}; +use thiserror::Error; + +pub type TxId = u64; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum TransactionStatus { + /// This transaction has been completed between the parties but has not been broadcast to the base layer network. + Completed, + /// This transaction has been broadcast to the base layer network and is currently in one or more base node + /// mempools. + Broadcast, + /// This transaction has been mined and included in a block. + MinedUnconfirmed, + /// This transaction was generated as part of importing a spendable UTXO + Imported, + /// This transaction is still being negotiated by the parties + Pending, + /// This is a created Coinbase Transaction + Coinbase, + /// This transaction is mined and confirmed at the current base node's height + MinedConfirmed, +} + +#[derive(Debug, Error)] +#[error("Invalid TransactionStatus: {code}")] +pub struct TransactionConversionError { + pub code: i32, +} + +impl TryFrom for TransactionStatus { + type Error = TransactionConversionError; + + fn try_from(value: i32) -> Result { + match value { + 0 => Ok(TransactionStatus::Completed), + 1 => Ok(TransactionStatus::Broadcast), + 2 => Ok(TransactionStatus::MinedUnconfirmed), + 3 => Ok(TransactionStatus::Imported), + 4 => Ok(TransactionStatus::Pending), + 5 => Ok(TransactionStatus::Coinbase), + 6 => Ok(TransactionStatus::MinedConfirmed), + code => Err(TransactionConversionError { code }), + } + } +} + +impl Default for TransactionStatus { + fn default() -> Self { + TransactionStatus::Pending + } +} + +impl Display for TransactionStatus { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + // No struct or tuple variants + match self { + TransactionStatus::Completed => write!(f, "Completed"), + TransactionStatus::Broadcast => write!(f, "Broadcast"), + TransactionStatus::MinedUnconfirmed => write!(f, "Mined Unconfirmed"), + TransactionStatus::MinedConfirmed => write!(f, "Mined Confirmed"), + TransactionStatus::Imported => write!(f, "Imported"), + TransactionStatus::Pending => write!(f, "Pending"), + TransactionStatus::Coinbase => write!(f, "Coinbase"), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum TransactionDirection { + Inbound, + Outbound, + Unknown, +} + +#[derive(Debug, Error)] +#[error("Invalid TransactionDirection: {code}")] +pub struct TransactionDirectionError { + pub code: i32, +} + +impl TryFrom for TransactionDirection { + type Error = TransactionDirectionError; + + fn try_from(value: i32) -> Result { + match value { + 0 => Ok(TransactionDirection::Inbound), + 1 => Ok(TransactionDirection::Outbound), + 2 => Ok(TransactionDirection::Unknown), + code => Err(TransactionDirectionError { code }), + } + } +} + +impl Display for TransactionDirection { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + // No struct or tuple variants + match self { + TransactionDirection::Inbound => write!(f, "Inbound"), + TransactionDirection::Outbound => write!(f, "Outbound"), + TransactionDirection::Unknown => write!(f, "Unknown"), + } + } +} diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index c1acfb055e2..b0217ecca83 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -39,6 +39,7 @@ sha3 = "0.9" bytes = "0.5" chrono = { version = "0.4.6", features = ["serde"] } croaring = { version = "=0.4.5", optional = true } +decimal-rs = "0.1.20" digest = "0.9.0" futures = { version = "^0.3.16", features = ["async-await"] } fs2 = "0.3.0" @@ -64,6 +65,7 @@ num-format = "0.4.0" tracing = "0.1.26" tracing-futures = "*" tracing-attributes = "*" +derive_more = "0.99.16" [dev-dependencies] tari_p2p = { version = "^0.11", path = "../../base_layer/p2p", features = ["test-mocks"] } diff --git a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs index 3511ade2061..7e10d597457 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs @@ -604,10 +604,10 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { .sync_validators .final_horizon_state .validate( + &*self.db().clone().into_inner().db_read_access()?, header.height(), &pruned_utxo_sum, &pruned_kernel_sum, - &*self.db().clone().into_inner().db_read_access()?, ) .map_err(HorizonSyncError::FinalStateValidationFailed)?; diff --git a/base_layer/core/src/chain_storage/accumulated_data.rs b/base_layer/core/src/chain_storage/accumulated_data.rs index 5c050290d49..c7eef86bd07 100644 --- a/base_layer/core/src/chain_storage/accumulated_data.rs +++ b/base_layer/core/src/chain_storage/accumulated_data.rs @@ -273,7 +273,7 @@ impl BlockHeaderAccumulatedDataBuilder<'_> { .hash .ok_or_else(|| ChainStorageError::InvalidOperation("hash not provided".to_string()))?; - if hash == self.previous_accum.hash { + if hash == previous_accum.hash { return Err(ChainStorageError::InvalidOperation( "Hash was set to the same hash that is contained in previous accumulated data".to_string(), )); diff --git a/base_layer/core/src/chain_storage/block_add_result.rs b/base_layer/core/src/chain_storage/block_add_result.rs index 26b0107ed00..4c9a783f17a 100644 --- a/base_layer/core/src/chain_storage/block_add_result.rs +++ b/base_layer/core/src/chain_storage/block_add_result.rs @@ -46,6 +46,14 @@ impl BlockAddResult { matches!(self, BlockAddResult::Ok(_)) } + pub fn is_chain_reorg(&self) -> bool { + matches!(self, BlockAddResult::ChainReorg { .. }) + } + + pub fn is_orphaned(&self) -> bool { + matches!(self, BlockAddResult::OrphanBlock) + } + pub fn assert_added(&self) -> ChainBlock { match self { BlockAddResult::ChainReorg { added, removed } => panic!( @@ -60,10 +68,7 @@ impl BlockAddResult { } pub fn assert_orphaned(&self) { - match self { - BlockAddResult::OrphanBlock => (), - _ => panic!("Result was not orphaned"), - } + assert!(self.is_orphaned(), "Result was not orphaned"); } pub fn assert_reorg(&self, num_added: usize, num_removed: usize) { diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 9fdaad98518..34a4cce5da5 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -664,21 +664,67 @@ where B: BlockchainBackend pub fn prepare_new_block(&self, template: NewBlockTemplate) -> Result { let NewBlockTemplate { header, mut body, .. } = template; + if header.height == 0 { + return Err(ChainStorageError::InvalidArguments { + func: "prepare_new_block", + arg: "template", + message: "Invalid height for NewBlockTemplate: must be greater than 0".to_string(), + }); + } + body.sort(header.version); let mut header = BlockHeader::from(header); // If someone advanced the median timestamp such that the local time is less than the median timestamp, we need // to increase the timestamp to be greater than the median timestamp - let height = header.height - 1; + let prev_block_height = header.height - 1; let min_height = header.height.saturating_sub( self.consensus_manager .consensus_constants(header.height) .get_median_timestamp_count() as u64, ); + let db = self.db_read_access()?; - let timestamps = fetch_headers(&*db, min_height, height)? + let tip_header = db.fetch_tip_header()?; + if header.height != tip_header.height() + 1 { + return Err(ChainStorageError::InvalidArguments { + func: "prepare_new_block", + arg: "template", + message: format!( + "Expected new block template height to be {} but was {}", + tip_header.height() + 1, + header.height + ), + }); + } + if header.prev_hash != *tip_header.hash() { + return Err(ChainStorageError::InvalidArguments { + func: "prepare_new_block", + arg: "template", + message: format!( + "Expected new block template previous hash to be set to the current tip hash ({}) but was {}", + tip_header.hash().to_hex(), + header.prev_hash.to_hex() + ), + }); + } + + let timestamps = fetch_headers(&*db, min_height, prev_block_height)? .iter() .map(|h| h.timestamp) .collect::>(); + if timestamps.is_empty() { + return Err(ChainStorageError::DataInconsistencyDetected { + function: "prepare_new_block", + details: format!( + "No timestamps were returned within heights {} - {} by the database despite the tip header height \ + being {}", + min_height, + prev_block_height, + tip_header.height() + ), + }); + } + let median_timestamp = calc_median_timestamp(×tamps); if median_timestamp > header.timestamp { header.timestamp = median_timestamp.increase(1); @@ -803,7 +849,7 @@ where B: BlockchainBackend fn insert_block(&self, block: Arc) -> Result<(), ChainStorageError> { let mut db = self.db_write_access()?; let mut txn = DbTransaction::new(); - insert_block(&mut txn, block)?; + insert_best_block(&mut txn, block)?; db.write(txn) } @@ -1193,8 +1239,8 @@ fn add_block( ) } -// Adds a new block onto the chain tip. -fn insert_block(txn: &mut DbTransaction, block: Arc) -> Result<(), ChainStorageError> { +/// Adds a new block onto the chain tip and sets it to the best block. +fn insert_best_block(txn: &mut DbTransaction, block: Arc) -> Result<(), ChainStorageError> { let block_hash = block.accumulated_data().hash.clone(); debug!( target: LOG_TARGET, @@ -1386,6 +1432,8 @@ fn rewind_to_height( let metadata = db.fetch_chain_metadata()?; let expected_block_hash = metadata.best_block().clone(); let last_block_height = metadata.height_of_longest_chain(); + // We use the cmp::max value here because we'll only delete headers here and leave remaining headers to be deleted + // with the whole block let steps_back = last_header_height .checked_sub(cmp::max(last_block_height, height)) .ok_or_else(|| { @@ -1415,9 +1463,8 @@ fn rewind_to_height( }); // Delete blocks - let mut steps_back = last_block_height.saturating_sub(height); - // No blocks to remove + // No blocks to remove, no need to update the best block if steps_back == 0 { db.write(txn)?; return Ok(vec![]); @@ -1431,16 +1478,17 @@ fn rewind_to_height( last_block_height - steps_back ); - let prune_past_horizon = metadata.is_pruned_node() && steps_back > metadata.pruning_horizon(); + let effective_pruning_horizon = metadata.height_of_longest_chain() - metadata.pruned_height(); + let prune_past_horizon = metadata.is_pruned_node() && steps_back > effective_pruning_horizon; if prune_past_horizon { warn!( target: LOG_TARGET, - "WARNING, reorg past pruning horizon, rewinding back to 0" + "WARNING, reorg past pruning horizon (more than {} blocks back), rewinding back to 0", + effective_pruning_horizon ); - steps_back = metadata.pruning_horizon(); + steps_back = effective_pruning_horizon; height = 0; } - let chain_header = db.fetch_chain_header_by_height(height)?; for h in 0..steps_back { info!(target: LOG_TARGET, "Deleting block {}", last_block_height - h,); @@ -1473,6 +1521,7 @@ fn rewind_to_height( } } + let chain_header = db.fetch_chain_header_by_height(height)?; // Update metadata debug!( target: LOG_TARGET, @@ -1539,7 +1588,7 @@ fn handle_possible_reorg( } // Check the accumulated difficulty of the best fork chain compared to the main chain. - let fork_header = find_strongest_orphan_tip(new_tips, chain_strength_comparer)?.ok_or_else(|| { + let fork_header = find_strongest_orphan_tip(new_tips, chain_strength_comparer).ok_or_else(|| { // This should never happen because a block is always added to the orphan pool before // checking, but just in case warn!( @@ -1608,16 +1657,15 @@ fn handle_possible_reorg( // TODO: We already have the first link in this chain, can be optimized to exclude it let reorg_chain = get_orphan_link_main_chain(db, fork_header.hash())?; - let fork_height = reorg_chain + let fork_hash = reorg_chain .front() .expect("The new orphan block should be in the queue") - .block() - .header - .height - - 1; + .header() + .prev_hash + .clone(); let num_added_blocks = reorg_chain.len(); - let removed_blocks = reorganize_chain(db, block_validator, fork_height, &reorg_chain)?; + let removed_blocks = reorganize_chain(db, block_validator, fork_hash, &reorg_chain)?; let num_removed_blocks = removed_blocks.len(); // reorg is required when any blocks are removed or more than one are added @@ -1662,15 +1710,15 @@ fn handle_possible_reorg( fn reorganize_chain( backend: &mut T, block_validator: &dyn PostOrphanBodyValidation, - fork_height: u64, + fork_hash: HashOutput, chain: &VecDeque>, ) -> Result>, ChainStorageError> { - let removed_blocks = rewind_to_height(backend, fork_height)?; + let removed_blocks = rewind_to_hash(backend, fork_hash.clone())?; debug!( target: LOG_TARGET, - "Validate and add {} chain block(s) from height {}. Rewound blocks: [{}]", + "Validate and add {} chain block(s) from block {}. Rewound blocks: [{}]", chain.len(), - fork_height, + fork_hash.to_hex(), removed_blocks .iter() .map(|b| b.height().to_string()) @@ -1680,25 +1728,25 @@ fn reorganize_chain( for block in chain { let mut txn = DbTransaction::new(); - let block_hash_hex = block.accumulated_data().hash.to_hex(); - txn.delete_orphan(block.accumulated_data().hash.clone()); + let block_hash = block.hash().clone(); + txn.delete_orphan(block_hash.clone()); let chain_metadata = backend.fetch_chain_metadata()?; - if let Err(e) = block_validator.validate_body_for_valid_orphan(block, backend, &chain_metadata) { + if let Err(e) = block_validator.validate_body_for_valid_orphan(backend, block, &chain_metadata) { warn!( target: LOG_TARGET, "Orphan block {} ({}) failed validation during chain reorg: {:?}", block.header().height, - block_hash_hex, + block_hash.to_hex(), e ); - remove_orphan(backend, block.accumulated_data().hash.clone())?; + remove_orphan(backend, block_hash)?; info!(target: LOG_TARGET, "Restoring previous chain after failed reorg."); - restore_reorged_chain(backend, fork_height, removed_blocks)?; + restore_reorged_chain(backend, fork_hash, removed_blocks)?; return Err(e.into()); } - insert_block(&mut txn, block.clone())?; + insert_best_block(&mut txn, block.clone())?; // Failed to store the block - this should typically never happen unless there is a bug in the validator // (e.g. does not catch a double spend). In any case, we still need to restore the chain to a // good state before returning. @@ -1708,7 +1756,7 @@ fn reorganize_chain( "Failed to commit reorg chain: {:?}. Restoring last chain.", e ); - restore_reorged_chain(backend, fork_height, removed_blocks)?; + restore_reorged_chain(backend, fork_hash, removed_blocks)?; return Err(e); } } @@ -1727,10 +1775,10 @@ fn reorganize_chain( fn restore_reorged_chain( db: &mut T, - height: u64, + to_hash: HashOutput, previous_chain: Vec>, ) -> Result<(), ChainStorageError> { - let invalid_chain = rewind_to_height(db, height)?; + let invalid_chain = rewind_to_hash(db, to_hash)?; debug!( target: LOG_TARGET, "Removed {} blocks during chain restore: {:?}.", @@ -1744,7 +1792,7 @@ fn restore_reorged_chain( for block in previous_chain.into_iter().rev() { txn.delete_orphan(block.accumulated_data().hash.clone()); - insert_block(&mut txn, block)?; + insert_best_block(&mut txn, block)?; } db.write(txn)?; Ok(()) @@ -1764,10 +1812,11 @@ fn insert_orphan_and_find_new_tips( return Ok(vec![]); } - let mut txn = DbTransaction::new(); let parent = match db.fetch_orphan_chain_tip_by_hash(&block.header.prev_hash)? { Some(curr_parent) => { + let mut txn = DbTransaction::new(); txn.remove_orphan_chain_tip(block.header.prev_hash.clone()); + db.write(txn)?; info!( target: LOG_TARGET, "New orphan extends a chain in the current candidate tip set" @@ -1781,22 +1830,32 @@ fn insert_orphan_and_find_new_tips( Some(curr_parent) => { info!( target: LOG_TARGET, - "New orphan does not have a parent in the current tip set. Parent is {}", + "New orphan #{} ({}) does not have a parent in the current tip set. Parent is {}", + block.header.height, + hash.to_hex(), curr_parent.hash().to_hex() ); curr_parent }, None => { - info!( - target: LOG_TARGET, - "Orphan {} was not connected to any previous headers. Inserting as true orphan", - hash.to_hex() - ); - - if !db.contains(&DbKey::OrphanBlock(hash))? { + let hash_hex = hash.to_hex(); + if db.contains(&DbKey::OrphanBlock(hash))? { + info!( + target: LOG_TARGET, + "Orphan #{} ({}) already found in orphan database", block.header.height, hash_hex + ); + } else { + info!( + target: LOG_TARGET, + "Orphan #{} ({}) was not connected to any previous headers. Inserting as true orphan", + block.header.height, + hash_hex + ); + + let mut txn = DbTransaction::new(); txn.insert_orphan(block); + db.write(txn)?; } - db.write(txn)?; return Ok(vec![]); }, }, @@ -1815,10 +1874,16 @@ fn insert_orphan_and_find_new_tips( let chain_header = chain_block.to_chain_header(); // Extend orphan chain tip. - txn.insert_chained_orphan(Arc::new(chain_block)); + let mut txn = DbTransaction::new(); + if !db.contains(&DbKey::OrphanBlock(chain_block.accumulated_data().hash.clone()))? { + txn.insert_orphan(chain_block.to_arc_block()); + } + txn.set_accumulated_data_for_orphan(chain_block.accumulated_data().clone()); + db.write(txn)?; - let tips = find_orphan_descendant_tips_of(&*db, &chain_header, validator, difficulty_calculator, &mut txn)?; + let tips = find_orphan_descendant_tips_of(db, chain_header, validator, difficulty_calculator)?; debug!(target: LOG_TARGET, "Found {} new orphan tips", tips.len()); + let mut txn = DbTransaction::new(); for new_tip in &tips { txn.insert_orphan_chain_tip(new_tip.hash().clone()); } @@ -1829,11 +1894,10 @@ fn insert_orphan_and_find_new_tips( // Find the tip set of any orphans that have hash as an ancestor fn find_orphan_descendant_tips_of( - db: &T, - prev_chain_header: &ChainHeader, + db: &mut T, + prev_chain_header: ChainHeader, validator: &dyn HeaderValidation, difficulty_calculator: &DifficultyCalculator, - txn: &mut DbTransaction, ) -> Result, ChainStorageError> { let children = db.fetch_orphan_children_of(prev_chain_header.hash().clone())?; if children.is_empty() { @@ -1843,11 +1907,19 @@ fn find_orphan_descendant_tips_of( prev_chain_header.height(), prev_chain_header.hash().to_hex() ); - return Ok(vec![prev_chain_header.clone()]); + return Ok(vec![prev_chain_header]); } let mut res = vec![]; for child in children { + debug!( + target: LOG_TARGET, + "Validating header #{} ({}), descendant of #{} ({})", + child.header.height, + child.hash().to_hex(), + prev_chain_header.height(), + prev_chain_header.hash().to_hex() + ); match validator.validate(db, &child.header, difficulty_calculator) { Ok(achieved_target) => { let child_hash = child.hash(); @@ -1865,10 +1937,10 @@ fn find_orphan_descendant_tips_of( })?; // Set/overwrite accumulated data for this orphan block - txn.set_accumulated_data_for_orphan(chain_header.clone()); - - let children = - find_orphan_descendant_tips_of(db, &chain_header, validator, difficulty_calculator, txn)?; + let mut txn = DbTransaction::new(); + txn.set_accumulated_data_for_orphan(chain_header.accumulated_data().clone()); + db.write(txn)?; + let children = find_orphan_descendant_tips_of(db, chain_header, validator, difficulty_calculator)?; res.extend(children); }, Err(e) => { @@ -1879,7 +1951,9 @@ fn find_orphan_descendant_tips_of( child.hash().to_hex(), e ); + let mut txn = DbTransaction::new(); txn.delete_orphan(child.hash()); + db.write(txn)?; }, }; } @@ -1893,7 +1967,7 @@ fn remove_orphan(db: &mut T, hash: HashOutput) -> Result<( db.write(txn) } -/// Gets all blocks ordered from the orphan tip to the point (exclusive) where it connects to the best chain. +/// Gets all blocks ordered from the the block that connects (via prev_hash) to the main chain, to the orphan tip. // TODO: this would probably perform better if it reused the db transaction #[allow(clippy::ptr_arg)] fn get_orphan_link_main_chain( @@ -1925,7 +1999,7 @@ fn get_orphan_link_main_chain( fn find_strongest_orphan_tip( orphan_chain_tips: Vec, chain_strength_comparer: &dyn ChainStrengthComparer, -) -> Result, ChainStorageError> { +) -> Option { let mut best_block_header: Option = None; for tip in orphan_chain_tips { best_block_header = match best_block_header { @@ -1937,7 +2011,7 @@ fn find_strongest_orphan_tip( }; } - Ok(best_block_header) + best_block_header } // Perform a comprehensive search to remove all the minimum height orphans to maintain the configured orphan pool @@ -2046,24 +2120,29 @@ fn convert_to_option_bounds>(bounds: T) -> (Option, Opt mod test { use super::*; use crate::{ + block_specs, consensus::{ chain_strength_comparer::strongest_chain, consensus_constants::PowAlgorithmConstants, ConsensusConstantsBuilder, ConsensusManager, }, - test_helpers::blockchain::{ - create_chained_blocks, - create_main_chain, - create_new_blockchain, - create_orphan_chain, - create_test_blockchain_db, - TempDatabase, + test_helpers::{ + blockchain::{ + create_chained_blocks, + create_main_chain, + create_new_blockchain, + create_orphan_chain, + create_test_blockchain_db, + TempDatabase, + }, + BlockSpecs, }, validation::{header_validator::HeaderValidator, mocks::MockValidator}, }; - use std::collections::HashMap; + use std::{collections::HashMap, sync}; use tari_common::configuration::Network; + use tari_test_utils::unpack_enum; #[test] fn lmdb_fetch_monero_seeds() { @@ -2162,7 +2241,7 @@ mod test { let db = create_new_blockchain(); let validator = MockValidator::new(true); let genesis_block = db.fetch_block(0).unwrap().try_into_chain_block().map(Arc::new).unwrap(); - let (_, chain) = create_chained_blocks(&[("A->GB", 1, 120)], genesis_block); + let (_, chain) = create_chained_blocks(&[("A->GB", 1u64, 120u64)], genesis_block); let block = chain.get("A").unwrap().clone(); let mut access = db.db_write_access().unwrap(); let chain = insert_orphan_and_find_new_tips( @@ -2250,6 +2329,118 @@ mod test { } } + mod handle_possible_reorg { + use super::*; + + #[test] + fn it_links_many_orphan_branches_to_main_chain() { + let test = TestHarness::setup(); + + let (_, main_chain) = + create_main_chain(&test.db, block_specs!(["1a->GB"], ["2a->1a"], ["3a->2a"], ["4a->3a"])); + let genesis = main_chain.get("GB").unwrap().clone(); + + let fork_root = main_chain.get("1a").unwrap().clone(); + let (_, orphan_chain_b) = create_chained_blocks( + block_specs!(["2b->GB"], ["3b->2b"], ["4b->3b"], ["5b->4b"], ["6b->5b"]), + fork_root, + ); + + // Add orphans out of height order + for name in ["5b", "3b", "4b", "6b"] { + let block = orphan_chain_b.get(name).unwrap().clone(); + let result = test.handle_possible_reorg(block.to_arc_block()).unwrap(); + assert!(result.is_orphaned()); + } + + // Add chain c orphans branching from chain b + let fork_root = orphan_chain_b.get("3b").unwrap().clone(); + let (_, orphan_chain_c) = + create_chained_blocks(block_specs!(["4c->GB"], ["5c->4c"], ["6c->5c"], ["7c->6c"]), fork_root); + + for name in ["7c", "5c", "6c", "4c"] { + let block = orphan_chain_c.get(name).unwrap().clone(); + let result = test.handle_possible_reorg(block.to_arc_block()).unwrap(); + assert!(result.is_orphaned()); + } + + let fork_root = orphan_chain_c.get("6c").unwrap().clone(); + let (_, orphan_chain_d) = create_chained_blocks(block_specs!(["7d->GB", difficulty: 10]), fork_root); + + let block = orphan_chain_d.get("7d").unwrap().clone(); + let result = test.handle_possible_reorg(block.to_arc_block()).unwrap(); + assert!(result.is_orphaned()); + + // Now, connect the chain and check that the c branch is the tip + let block = orphan_chain_b.get("2b").unwrap().clone(); + let result = test.handle_possible_reorg(block.to_arc_block()).unwrap(); + result.assert_reorg(6, 3); + + { + // Check 2b was added + let access = test.db_write_access(); + let block = orphan_chain_b.get("2b").unwrap().clone(); + assert!(access.contains(&DbKey::BlockHash(block.hash().clone())).unwrap()); + + // Check 7d is the tip + let block = orphan_chain_d.get("7d").unwrap().clone(); + let tip = access.fetch_tip_header().unwrap(); + assert_eq!(tip.hash(), block.hash()); + let metadata = access.fetch_chain_metadata().unwrap(); + assert_eq!(metadata.best_block(), block.hash()); + assert_eq!(metadata.height_of_longest_chain(), block.height()); + assert!(access.contains(&DbKey::BlockHash(block.hash().clone())).unwrap()); + + let mut all_blocks = main_chain + .into_iter() + .chain(orphan_chain_b) + .chain(orphan_chain_c) + .chain(orphan_chain_d) + .collect::>(); + all_blocks.insert("GB".to_string(), genesis); + // Check the chain heights + let expected_chain = ["GB", "1a", "2b", "3b", "4c", "5c", "6c", "7d"]; + for (height, name) in expected_chain.iter().enumerate() { + let expected_block = all_blocks.get(*name).unwrap(); + unpack_enum!( + DbValue::BlockHeader(found_block) = + access.fetch(&DbKey::BlockHeader(height as u64)).unwrap().unwrap() + ); + assert_eq!(*found_block, *expected_block.header()); + } + } + } + + #[test] + fn it_errors_if_reorging_to_an_invalid_height() { + let test = TestHarness::setup(); + let (_, main_chain) = + create_main_chain(&test.db, block_specs!(["1a->GB"], ["2a->1a"], ["3a->2a"], ["4a->3a"])); + + let fork_root = main_chain.get("1a").unwrap().clone(); + let (_, orphan_chain_b) = + create_chained_blocks(block_specs!(["2b->GB", height: 10, difficulty: 10]), fork_root); + + let block = orphan_chain_b.get("2b").unwrap().clone(); + let err = test.handle_possible_reorg(block.to_arc_block()).unwrap_err(); + unpack_enum!(ChainStorageError::InvalidOperation(_) = err); + } + + #[test] + fn it_allows_orphan_blocks_with_any_height() { + let test = TestHarness::setup(); + let (_, main_chain) = create_main_chain(&test.db, block_specs!(["1a->GB", difficulty: 2])); + + let fork_root = main_chain.get("GB").unwrap().clone(); + let (_, orphan_chain_b) = create_chained_blocks(block_specs!(["1b->GB", height: 10]), fork_root); + + let block = orphan_chain_b.get("1b").unwrap().clone(); + test.handle_possible_reorg(block.to_arc_block()) + .unwrap() + .assert_orphaned(); + } + } + #[test] fn test_handle_possible_reorg_case1() { // Normal chain @@ -2638,33 +2829,71 @@ mod test { assert_eq!(accum_difficulty, values); } - #[allow(clippy::type_complexity)] - fn test_case_handle_possible_reorg( - blocks: &[(&str, u64, u64)], - ) -> Result<(Vec, HashMap>), ChainStorageError> { - let db = create_new_blockchain(); - let genesis_block = db.fetch_block(0).unwrap().try_into_chain_block().map(Arc::new).unwrap(); - let (block_names, chain) = create_chained_blocks(blocks, genesis_block); - let mock_validator = Box::new(MockValidator::new(true)); - // A real validator is needed here to test target difficulties + struct TestHarness { + db: BlockchainDatabase, + chain_strength_comparer: Box, + difficulty_calculator: DifficultyCalculator, + post_orphan_body_validator: Box>, + header_validator: Box>, + } - let consensus = ConsensusManager::builder(Network::LocalNet) - .add_consensus_constants( - ConsensusConstantsBuilder::new(Network::LocalNet) - .clear_proof_of_work() - .add_proof_of_work(PowAlgorithm::Sha3, PowAlgorithmConstants { - max_target_time: 1200, - min_difficulty: 1.into(), - max_difficulty: 100.into(), - target_time: 120, - }) - .build(), + impl TestHarness { + pub fn setup() -> Self { + let consensus = create_consensus_rules(); + let header_validator = Box::new(HeaderValidator::new(consensus)); + let mock_validator = Box::new(MockValidator::new(true)); + Self::new(header_validator, mock_validator) + } + + pub fn new( + header_validator: Box>, + post_orphan_body_validator: Box>, + ) -> Self { + let db = create_new_blockchain(); + let consensus = create_consensus_rules(); + let difficulty_calculator = DifficultyCalculator::new(consensus, Default::default()); + let chain_strength_comparer = strongest_chain().by_sha3_difficulty().build(); + Self { + db, + chain_strength_comparer, + difficulty_calculator, + header_validator, + post_orphan_body_validator, + } + } + + pub fn db_write_access(&self) -> sync::RwLockWriteGuard<'_, TempDatabase> { + self.db.db_write_access().unwrap() + } + + pub fn handle_possible_reorg(&self, block: Arc) -> Result { + let mut access = self.db_write_access(); + handle_possible_reorg( + &mut *access, + &*self.post_orphan_body_validator, + &*self.header_validator, + &self.difficulty_calculator, + &*self.chain_strength_comparer, + block, ) - .build(); + } + } + + #[allow(clippy::type_complexity)] + fn test_case_handle_possible_reorg>( + blocks: T, + ) -> Result<(Vec, HashMap>), ChainStorageError> { + let test = TestHarness::setup(); + // let db = create_new_blockchain(); + let genesis_block = test + .db + .fetch_block(0) + .unwrap() + .try_into_chain_block() + .map(Arc::new) + .unwrap(); + let (block_names, chain) = create_chained_blocks(blocks.into(), genesis_block); - let difficulty_calculator = DifficultyCalculator::new(consensus.clone(), Default::default()); - let header_validator = Box::new(HeaderValidator::new(consensus)); - let chain_strength_comparer = strongest_chain().by_sha3_difficulty().build(); let mut results = vec![]; for name in block_names { let block = chain.get(&name.to_string()).unwrap(); @@ -2674,15 +2903,24 @@ mod test { block.hash().to_hex(), block.header().prev_hash.to_hex() ); - results.push(handle_possible_reorg( - &mut *db.db_write_access()?, - &*mock_validator, - &*header_validator, - &difficulty_calculator, - &*chain_strength_comparer, - block.to_arc_block(), - )?); + results.push(test.handle_possible_reorg(block.to_arc_block()).unwrap()); } Ok((results, chain)) } + + fn create_consensus_rules() -> ConsensusManager { + ConsensusManager::builder(Network::LocalNet) + .add_consensus_constants( + ConsensusConstantsBuilder::new(Network::LocalNet) + .clear_proof_of_work() + .add_proof_of_work(PowAlgorithm::Sha3, PowAlgorithmConstants { + max_target_time: 1200, + min_difficulty: 1.into(), + max_difficulty: 100.into(), + target_time: 120, + }) + .build(), + ) + .build() + } } diff --git a/base_layer/core/src/chain_storage/db_transaction.rs b/base_layer/core/src/chain_storage/db_transaction.rs index e004682da6c..e3bd5fc6458 100644 --- a/base_layer/core/src/chain_storage/db_transaction.rs +++ b/base_layer/core/src/chain_storage/db_transaction.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ blocks::{Block, BlockHeader}, - chain_storage::{error::ChainStorageError, ChainBlock, ChainHeader, MmrTree}, + chain_storage::{error::ChainStorageError, BlockHeaderAccumulatedData, ChainBlock, ChainHeader, MmrTree}, transactions::transaction::{TransactionKernel, TransactionOutput}, }; use croaring::Bitmap; @@ -214,9 +214,9 @@ impl DbTransaction { /// Sets accumulated data for the orphan block, "upgrading" the orphan block to a chained orphan. /// Any existing accumulated data is overwritten. /// The transaction will rollback and write will return an error if the orphan block does not exist. - pub fn set_accumulated_data_for_orphan(&mut self, chain_header: ChainHeader) -> &mut Self { + pub fn set_accumulated_data_for_orphan(&mut self, accumulated_data: BlockHeaderAccumulatedData) -> &mut Self { self.operations - .push(WriteOperation::SetAccumulatedDataForOrphan(chain_header)); + .push(WriteOperation::SetAccumulatedDataForOrphan(accumulated_data)); self } @@ -318,7 +318,7 @@ pub enum WriteOperation { header_hash: HashOutput, kernel_sum: Commitment, }, - SetAccumulatedDataForOrphan(ChainHeader), + SetAccumulatedDataForOrphan(BlockHeaderAccumulatedData), SetBestBlock { height: u64, hash: HashOutput, @@ -415,8 +415,8 @@ impl fmt::Display for WriteOperation { horizon ), UpdateKernelSum { header_hash, .. } => write!(f, "Update kernel sum for block: {}", header_hash.to_hex()), - SetAccumulatedDataForOrphan(chain_header) => { - write!(f, "Set accumulated data for orphan {}", chain_header.hash().to_hex()) + SetAccumulatedDataForOrphan(accumulated_data) => { + write!(f, "Set accumulated data for orphan {}", accumulated_data) }, SetBestBlock { height, diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index f58eea66bec..1bae3c9ab2c 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -203,12 +203,12 @@ impl LMDBDatabase { _file_lock: Arc::new(file_lock), }; - db.build_indexes()?; + db.check_if_rebuild_required()?; Ok(db) } - fn build_indexes(&self) -> Result<(), ChainStorageError> { + fn check_if_rebuild_required(&self) -> Result<(), ChainStorageError> { let txn = self.read_transaction()?; if lmdb_len(&txn, &self.deleted_txo_mmr_position_to_height_index)? == 0 && lmdb_len(&txn, &self.inputs_db)? > 0 { @@ -299,20 +299,12 @@ impl LMDBDatabase { InsertMoneroSeedHeight(data, height) => { self.insert_monero_seed_height(&write_txn, data, *height)?; }, - SetAccumulatedDataForOrphan(chain_header) => { - self.set_accumulated_data_for_orphan( - &write_txn, - chain_header.hash(), - chain_header.accumulated_data(), - )?; + SetAccumulatedDataForOrphan(accumulated_data) => { + self.set_accumulated_data_for_orphan(&write_txn, accumulated_data)?; }, InsertChainOrphanBlock(chain_block) => { self.insert_orphan_block(&write_txn, chain_block.block())?; - self.set_accumulated_data_for_orphan( - &write_txn, - chain_block.hash(), - chain_block.accumulated_data(), - )?; + self.set_accumulated_data_for_orphan(&write_txn, chain_block.accumulated_data())?; }, UpdatePrunedHashSet { mmr_tree, @@ -651,24 +643,22 @@ impl LMDBDatabase { Ok(()) } - #[allow(clippy::ptr_arg)] fn set_accumulated_data_for_orphan( &self, txn: &WriteTransaction<'_>, - header_hash: &HashOutput, accumulated_data: &BlockHeaderAccumulatedData, ) -> Result<(), ChainStorageError> { - if !lmdb_exists(txn, &self.orphans_db, header_hash.as_slice())? { + if !lmdb_exists(txn, &self.orphans_db, accumulated_data.hash.as_slice())? { return Err(ChainStorageError::InvalidOperation(format!( "set_accumulated_data_for_orphan: orphan {} does not exist", - header_hash.to_hex() + accumulated_data.hash.to_hex() ))); } lmdb_insert( txn, &self.orphan_header_accumulated_data_db, - header_hash.as_slice(), + accumulated_data.hash.as_slice(), &accumulated_data, "orphan_header_accumulated_data_db", )?; @@ -704,10 +694,9 @@ impl LMDBDatabase { if let Some(ref last_header) = self.fetch_last_header_in_txn(txn)? { if last_header.height != header.height.saturating_sub(1) { return Err(ChainStorageError::InvalidOperation(format!( - "Attempted to insert a header out of order. Was expecting chain height to be {} but current last \ - header height is {}", - header.height - 1, - last_header.height + "Attempted to insert a header out of order. The last header height is {} but attempted to insert \ + a header with height {}", + last_header.height, header.height, ))); } @@ -953,47 +942,56 @@ impl LMDBDatabase { } fn delete_orphan(&self, txn: &WriteTransaction<'_>, hash: &HashOutput) -> Result<(), ChainStorageError> { - if let Some(orphan) = lmdb_get::<_, Block>(txn, &self.orphans_db, hash.as_slice())? { - let parent_hash = orphan.header.prev_hash; - lmdb_delete_key_value(txn, &self.orphan_parent_map_index, parent_hash.as_slice(), &hash)?; + let orphan = match lmdb_get::<_, Block>(txn, &self.orphans_db, hash.as_slice())? { + Some(orphan) => orphan, + None => { + // delete_orphan is idempotent + debug!( + target: LOG_TARGET, + "delete_orphan: request to delete orphan block {} that was not found.", + hash.to_hex() + ); + return Ok(()); + }, + }; - // Orphan is a tip hash - if lmdb_exists(txn, &self.orphan_chain_tips_db, hash.as_slice())? { - lmdb_delete(txn, &self.orphan_chain_tips_db, hash.as_slice(), "orphan_chain_tips_db")?; + let parent_hash = orphan.header.prev_hash; + lmdb_delete_key_value(txn, &self.orphan_parent_map_index, parent_hash.as_slice(), &hash)?; - // Parent becomes a tip hash - if lmdb_exists(txn, &self.orphans_db, parent_hash.as_slice())? { - lmdb_insert( - txn, - &self.orphan_chain_tips_db, - parent_hash.as_slice(), - &parent_hash, - "orphan_chain_tips_db", - )?; - } - } + // Orphan is a tip hash + if lmdb_exists(txn, &self.orphan_chain_tips_db, hash.as_slice())? { + lmdb_delete(txn, &self.orphan_chain_tips_db, hash.as_slice(), "orphan_chain_tips_db")?; - if lmdb_exists(txn, &self.orphan_header_accumulated_data_db, hash.as_slice())? { - lmdb_delete( + // Parent becomes a tip hash + if lmdb_exists(txn, &self.orphans_db, parent_hash.as_slice())? { + lmdb_insert( txn, - &self.orphan_header_accumulated_data_db, - hash.as_slice(), - "orphan_header_accumulated_data_db", + &self.orphan_chain_tips_db, + parent_hash.as_slice(), + &parent_hash, + "orphan_chain_tips_db", )?; } + } - if lmdb_get::<_, BlockHeaderAccumulatedData>(txn, &self.orphan_header_accumulated_data_db, hash.as_slice())? - .is_some() - { - lmdb_delete( - txn, - &self.orphan_header_accumulated_data_db, - hash.as_slice(), - "orphan_header_accumulated_data_db", - )?; - } - lmdb_delete(txn, &self.orphans_db, hash.as_slice(), "orphans_db")?; + if lmdb_exists(txn, &self.orphan_header_accumulated_data_db, hash.as_slice())? { + lmdb_delete( + txn, + &self.orphan_header_accumulated_data_db, + hash.as_slice(), + "orphan_header_accumulated_data_db", + )?; + } + + if lmdb_exists(txn, &self.orphan_header_accumulated_data_db, hash.as_slice())? { + lmdb_delete( + txn, + &self.orphan_header_accumulated_data_db, + hash.as_slice(), + "orphan_header_accumulated_data_db", + )?; } + lmdb_delete(txn, &self.orphans_db, hash.as_slice(), "orphans_db")?; Ok(()) } @@ -1514,7 +1512,7 @@ impl BlockchainBackend for LMDBDatabase { } Err(ChainStorageError::ValueNotFound { - entity: "chain_header_in_all_chains", + entity: "chain header (in chain_header_in_all_chains)", field: "hash", value: hash.to_hex(), }) @@ -2015,14 +2013,15 @@ impl BlockchainBackend for LMDBDatabase { Ok(Some(chain_header)) } - fn fetch_orphan_children_of(&self, hash: HashOutput) -> Result, ChainStorageError> { + fn fetch_orphan_children_of(&self, parent_hash: HashOutput) -> Result, ChainStorageError> { trace!( target: LOG_TARGET, "Call to fetch_orphan_children_of({})", - hash.to_hex() + parent_hash.to_hex() ); let txn = self.read_transaction()?; - let orphan_hashes: Vec = lmdb_get_multiple(&txn, &self.orphan_parent_map_index, hash.as_slice())?; + let orphan_hashes: Vec = + lmdb_get_multiple(&txn, &self.orphan_parent_map_index, parent_hash.as_slice())?; let mut res = Vec::with_capacity(orphan_hashes.len()); for hash in orphan_hashes { res.push(lmdb_get(&txn, &self.orphans_db, hash.as_slice())?.ok_or_else(|| { diff --git a/base_layer/core/src/chain_storage/tests/blockchain_database.rs b/base_layer/core/src/chain_storage/tests/blockchain_database.rs index f0eabd8a481..f5cb1b9802a 100644 --- a/base_layer/core/src/chain_storage/tests/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/tests/blockchain_database.rs @@ -21,16 +21,20 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - blocks::Block, - chain_storage::BlockchainDatabase, + blocks::{Block, BlockHeader, NewBlockTemplate}, + chain_storage::{BlockchainDatabase, ChainStorageError}, consensus::ConsensusManager, + proof_of_work::Difficulty, tari_utilities::Hashable, test_helpers::{ blockchain::{create_new_blockchain, TempDatabase}, create_block, BlockSpec, }, - transactions::transaction::{Transaction, UnblindedOutput}, + transactions::{ + tari_amount::T, + transaction::{Transaction, UnblindedOutput}, + }, }; use std::sync::Arc; use tari_common::configuration::Network; @@ -160,6 +164,12 @@ mod fetch_headers { let db = setup(); let headers = db.fetch_headers(0..).unwrap(); assert_eq!(headers.len(), 1); + let headers = db.fetch_headers(0..0).unwrap(); + assert_eq!(headers.len(), 1); + let headers = db.fetch_headers(0..=0).unwrap(); + assert_eq!(headers.len(), 1); + let headers = db.fetch_headers(..).unwrap(); + assert_eq!(headers.len(), 1); } #[test] @@ -432,3 +442,41 @@ mod fetch_total_size_stats { assert_eq!(stats.sizes().len(), 20); } } + +mod prepare_new_block { + use super::*; + + #[test] + fn it_errors_for_genesis_block() { + let db = setup(); + let genesis = db.fetch_block(0).unwrap(); + let template = NewBlockTemplate::from_block(genesis.block().clone(), Difficulty::min(), 5000 * T); + let err = db.prepare_new_block(template).unwrap_err(); + assert!(matches!(err, ChainStorageError::InvalidArguments { .. })); + } + + #[test] + fn it_errors_for_non_tip_template() { + let db = setup(); + let genesis = db.fetch_block(0).unwrap(); + let next_block = BlockHeader::from_previous(genesis.header()); + let mut template = NewBlockTemplate::from_block(next_block.into_builder().build(), Difficulty::min(), 5000 * T); + // This would cause a panic if the sanity checks were not there + template.header.height = 100; + let err = db.prepare_new_block(template.clone()).unwrap_err(); + assert!(matches!(err, ChainStorageError::InvalidArguments { .. })); + template.header.height = 1; + template.header.prev_hash[0] += 1; + let err = db.prepare_new_block(template).unwrap_err(); + assert!(matches!(err, ChainStorageError::InvalidArguments { .. })); + } + #[test] + fn it_prepares_the_first_block() { + let db = setup(); + let genesis = db.fetch_block(0).unwrap(); + let next_block = BlockHeader::from_previous(genesis.header()); + let template = NewBlockTemplate::from_block(next_block.into_builder().build(), Difficulty::min(), 5000 * T); + let block = db.prepare_new_block(template).unwrap(); + assert_eq!(block.header.height, 1); + } +} diff --git a/base_layer/core/src/lib.rs b/base_layer/core/src/lib.rs index 81da0f0c850..8d6b4fc3b5a 100644 --- a/base_layer/core/src/lib.rs +++ b/base_layer/core/src/lib.rs @@ -45,6 +45,7 @@ pub mod proof_of_work; pub mod validation; #[cfg(any(test, feature = "base_node"))] +#[macro_use] pub mod test_helpers; #[cfg(any(feature = "base_node", feature = "base_node_proto"))] diff --git a/base_layer/core/src/test_helpers/block_spec.rs b/base_layer/core/src/test_helpers/block_spec.rs new file mode 100644 index 00000000000..997e05ef292 --- /dev/null +++ b/base_layer/core/src/test_helpers/block_spec.rs @@ -0,0 +1,238 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 crate::{ + proof_of_work::Difficulty, + transactions::{tari_amount::MicroTari, transaction::Transaction}, +}; + +pub struct BlockSpecs { + pub specs: Vec, +} + +impl BlockSpecs { + pub fn len(&self) -> usize { + self.specs.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn into_vec(self) -> Vec { + self.specs + } +} + +impl From> for BlockSpecs { + fn from(specs: Vec) -> Self { + Self { specs } + } +} + +impl<'a, const N: usize> From<&'a [(&'static str, u64, u64); N]> for BlockSpecs { + fn from(arr: &'a [(&'static str, u64, u64); N]) -> Self { + BlockSpecs::from(&arr[..]) + } +} + +impl<'a> From<&'a [(&'static str, u64, u64)]> for BlockSpecs { + fn from(arr: &'a [(&'static str, u64, u64)]) -> Self { + Self { + specs: arr + .iter() + .map(|(name, diff, time)| { + BlockSpec::builder() + .with_name(name) + .with_block_time(*time) + .with_difficulty((*diff).into()) + .finish() + }) + .collect(), + } + } +} + +impl IntoIterator for BlockSpecs { + type IntoIter = std::vec::IntoIter; + type Item = BlockSpec; + + fn into_iter(self) -> Self::IntoIter { + self.specs.into_iter() + } +} + +#[macro_export] +macro_rules! block_spec { + (@ { $spec:ident }) => {}; + + (@ { $spec: ident } height: $height:expr, $($tail:tt)*) => { + $spec = $spec.with_height($height); + $crate::block_spec!(@ { $spec } $($tail)*) + }; + (@ { $spec: ident } difficulty: $difficulty:expr, $($tail:tt)*) => { + $spec = $spec.with_difficulty($difficulty.into()); + $crate::block_spec!(@ { $spec } $($tail)*) + }; + (@ { $spec: ident } reward: $reward:expr, $($tail:tt)*) => { + $spec = $spec.with_reward($reward.into()); + $crate::block_spec!(@ { spec } $($tail)*) + }; + + (@ { $spec: ident } $k:ident: $v:expr $(,)?) => { $crate::block_spec!(@ { $spec } $k: $v,) }; + + ($name:expr, $($tail:tt)+) => {{ + let mut spec = $crate::block_spec!($name); + $crate::block_spec!(@ { spec } $($tail)+); + spec.finish() + }}; + ($name:expr $(,)?) => { + $crate::test_helpers::BlockSpec::builder().with_name($name).finish() + }; +} + +/// Usage: +/// ```ignore +/// block_specs!(["1a->GB"], ["2a->1a"], ["3a->2a", difficulty: 2], ["4a->3a", reward: 50000]); +/// ``` +#[macro_export] +macro_rules! block_specs { + (@ { $specs:ident }) => {}; + + (@ { $specs:ident } [$name:expr, $($k:ident: $v:expr),*], $($tail:tt)*) => { + $specs.push($crate::block_spec!($name, $($k: $v),*)); + block_specs!(@ { $specs } $($tail)*) + }; + + (@ { $specs:ident } [$name:expr $(,)?], $($tail:tt)*) => { block_specs!(@ { $specs } [$name,], $($tail)*) }; + + (@ { $specs:ident } [$name:expr $(,)?]$(,)?) => { block_specs!(@ { $specs } [$name,],) }; + + (@ { $specs:ident } [$name:expr, $($k:ident: $v:expr),* $(,)?] $(,)?) => { block_specs!(@ { $specs } [$name, $($k: $v),*],) }; + + // Entrypoints + ([$name:expr, $($k:ident: $v:expr),*], $($tail:tt)*) => { + #[allow(clippy::vec_init_then_push)] + { + let mut specs = Vec::new(); + $crate::block_specs!(@ { specs } [$name, $($k: $v),*], $($tail)*); + BlockSpecs::from(specs) + } + }; + ([$name:expr, $($k:ident: $v:expr),* $(,)?] $(,)*) => {{ + $crate::block_specs!([$name, $($k: $v),*],) + }}; + + ([$name:expr], $($tail:tt)*) => {{ $crate::block_specs!([$name,], $($tail)*) }}; + + ([$name:expr]) => {{ $crate::block_specs!([$name,],) }}; + + () => { BlockSpecs::from(Vec::new()) }; +} + +#[derive(Debug, Clone)] +pub struct BlockSpec { + pub name: &'static str, + pub prev_block: &'static str, + pub version: u16, + pub difficulty: Difficulty, + pub block_time: u64, + pub reward_override: Option, + pub height_override: Option, + pub transactions: Vec, + pub skip_coinbase: bool, +} + +impl BlockSpec { + pub fn new() -> Self { + Default::default() + } + + pub fn builder() -> Self { + Default::default() + } + + pub fn with_name(mut self, name: &'static str) -> Self { + let mut split = name.splitn(2, "->"); + let name = split.next().unwrap_or(""); + self.name = name; + if let Some(prev_block) = split.next() { + self.prev_block = prev_block; + } + self + } + + pub fn with_prev_block(mut self, prev_block_name: &'static str) -> Self { + self.prev_block = prev_block_name; + self + } + + pub fn with_height(mut self, height: u64) -> Self { + self.height_override = Some(height); + self + } + + pub fn with_difficulty(mut self, difficulty: Difficulty) -> Self { + self.difficulty = difficulty; + self + } + + pub fn with_block_time(mut self, block_time: u64) -> Self { + self.block_time = block_time; + self + } + + pub fn with_reward(mut self, reward: MicroTari) -> Self { + self.reward_override = Some(reward); + self + } + + pub fn skip_coinbase(mut self) -> Self { + self.skip_coinbase = true; + self + } + + pub fn with_transactions(mut self, transactions: Vec) -> Self { + self.transactions = transactions; + self + } + + pub fn finish(self) -> Self { + self + } +} + +impl Default for BlockSpec { + fn default() -> Self { + Self { + name: "", + prev_block: "", + version: 0, + difficulty: 1.into(), + block_time: 120, + height_override: None, + reward_override: None, + transactions: vec![], + skip_coinbase: false, + } + } +} diff --git a/base_layer/core/src/test_helpers/blockchain.rs b/base_layer/core/src/test_helpers/blockchain.rs index c6e05896663..a98a3189472 100644 --- a/base_layer/core/src/test_helpers/blockchain.rs +++ b/base_layer/core/src/test_helpers/blockchain.rs @@ -49,7 +49,7 @@ use crate::{ consensus::{chain_strength_comparer::ChainStrengthComparerBuilder, ConsensusConstantsBuilder, ConsensusManager}, crypto::tari_utilities::Hashable, proof_of_work::{AchievedTargetDifficulty, Difficulty, PowAlgorithm}, - test_helpers::BlockSpec, + test_helpers::{block_spec::BlockSpecs, BlockSpec}, transactions::{ transaction::{TransactionInput, TransactionKernel, UnblindedOutput}, CryptoFactories, @@ -381,32 +381,25 @@ impl BlockchainBackend for TempDatabase { } } -pub fn create_chained_blocks( - blocks: &[(&str, u64, u64)], +pub fn create_chained_blocks>( + blocks: T, genesis_block: Arc, ) -> (Vec, HashMap>) { let mut block_hashes = HashMap::new(); block_hashes.insert("GB".to_string(), genesis_block); let rules = ConsensusManager::builder(Network::LocalNet).build(); - + let blocks: BlockSpecs = blocks.into(); let mut block_names = Vec::with_capacity(blocks.len()); - for (name, difficulty, time) in blocks { - let split = name.split("->").collect::>(); - let to = split[0].to_string(); - let from = split[1].to_string(); - + for block_spec in blocks { let prev_block = block_hashes - .get(&from) - .unwrap_or_else(|| panic!("Could not find block {}", from)); - let block_spec = BlockSpec::new() - .with_difficulty((*difficulty).into()) - .with_block_time(*time) - .finish(); + .get(block_spec.prev_block) + .unwrap_or_else(|| panic!("Could not find block {}", block_spec.prev_block)); + let name = block_spec.name; + let difficulty = block_spec.difficulty; let (block, _) = create_block(&rules, prev_block.block(), block_spec); - let block = mine_block(block, prev_block.accumulated_data(), (*difficulty).into()); - - block_names.push(to.clone()); - block_hashes.insert(to, block); + let block = mine_block(block, prev_block.accumulated_data(), difficulty); + block_names.push(name.to_string()); + block_hashes.insert(name.to_string(), block); } (block_names, block_hashes) } @@ -425,9 +418,9 @@ fn mine_block(block: Block, prev_block_accum: &BlockHeaderAccumulatedData, diffi Arc::new(ChainBlock::try_construct(Arc::new(block), accum).unwrap()) } -pub fn create_main_chain( +pub fn create_main_chain>( db: &BlockchainDatabase, - blocks: &[(&str, u64, u64)], + blocks: T, ) -> (Vec, HashMap>) { let genesis_block = db.fetch_block(0).unwrap().try_into_chain_block().map(Arc::new).unwrap(); let (names, chain) = create_chained_blocks(blocks, genesis_block); @@ -439,9 +432,9 @@ pub fn create_main_chain( (names, chain) } -pub fn create_orphan_chain( +pub fn create_orphan_chain>( db: &BlockchainDatabase, - blocks: &[(&str, u64, u64)], + blocks: T, root_block: Arc, ) -> (Vec, HashMap>) { let (names, chain) = create_chained_blocks(blocks, root_block); diff --git a/base_layer/core/src/test_helpers/mod.rs b/base_layer/core/src/test_helpers/mod.rs index 691623b2926..278d9791b8a 100644 --- a/base_layer/core/src/test_helpers/mod.rs +++ b/base_layer/core/src/test_helpers/mod.rs @@ -23,6 +23,10 @@ //! Common test helper functions that are small and useful enough to be included in the main crate, rather than the //! integration test folder. +#[macro_use] +mod block_spec; +pub use block_spec::{BlockSpec, BlockSpecs}; + pub mod blockchain; use crate::{ @@ -32,7 +36,6 @@ use crate::{ crypto::tari_utilities::Hashable, proof_of_work::{sha3_difficulty, AchievedTargetDifficulty, Difficulty}, transactions::{ - tari_amount::MicroTari, transaction::{Transaction, UnblindedOutput}, CoinbaseBuilder, CryptoFactories, @@ -43,64 +46,6 @@ use std::{iter, path::Path, sync::Arc}; use tari_comms::PeerManager; use tari_storage::{lmdb_store::LMDBBuilder, LMDBWrapper}; -#[derive(Debug, Clone)] -pub struct BlockSpec { - version: u16, - difficulty: Difficulty, - block_time: u64, - reward_override: Option, - transactions: Vec, - skip_coinbase: bool, -} - -impl BlockSpec { - pub fn new() -> Self { - Default::default() - } - - pub fn with_difficulty(mut self, difficulty: Difficulty) -> Self { - self.difficulty = difficulty; - self - } - - pub fn with_block_time(mut self, block_time: u64) -> Self { - self.block_time = block_time; - self - } - - pub fn with_reward(mut self, reward: MicroTari) -> Self { - self.reward_override = Some(reward); - self - } - - pub fn skip_coinbase(mut self) -> Self { - self.skip_coinbase = true; - self - } - - pub fn with_transactions(mut self, transactions: Vec) -> Self { - self.transactions = transactions; - self - } - - pub fn finish(self) -> Self { - self - } -} - -impl Default for BlockSpec { - fn default() -> Self { - Self { - version: 0, - difficulty: 1.into(), - block_time: 120, - reward_override: None, - transactions: vec![], - skip_coinbase: false, - } - } -} - /// Create a partially constructed block using the provided set of transactions /// is chain_block, or rename it to `create_orphan_block` and drop the prev_block argument pub fn create_orphan_block(block_height: u64, transactions: Vec, consensus: &ConsensusManager) -> Block { @@ -111,7 +56,7 @@ pub fn create_orphan_block(block_height: u64, transactions: Vec, co pub fn create_block(rules: &ConsensusManager, prev_block: &Block, spec: BlockSpec) -> (Block, UnblindedOutput) { let mut header = BlockHeader::new(spec.version); - let block_height = prev_block.header.height + 1; + let block_height = spec.height_override.unwrap_or(prev_block.header.height + 1); header.height = block_height; header.prev_hash = prev_block.hash(); let reward = spec.reward_override.unwrap_or_else(|| { diff --git a/base_layer/core/src/transactions/helpers.rs b/base_layer/core/src/transactions/helpers.rs index 568a5c17ba9..3738a40667c 100644 --- a/base_layer/core/src/transactions/helpers.rs +++ b/base_layer/core/src/transactions/helpers.rs @@ -22,7 +22,6 @@ use std::sync::Arc; -use num::pow; use rand::rngs::OsRng; use tari_crypto::{ commitment::HomomorphicCommitmentFactory, @@ -565,51 +564,44 @@ pub fn schema_to_transaction(txns: &[TransactionSchema]) -> (Vec String { - let whole = value as usize; - let decimal = ((value - whole as f64) * pow(10_f64, precision)).round() as usize; - let formatted_whole_value = whole - .to_string() - .chars() - .rev() - .enumerate() - .fold(String::new(), |acc, (i, c)| { - if i != 0 && i % 3 == 0 { - format!("{}{}{}", acc, separator, c) - } else { - format!("{}{}", acc, c) - } - }) - .chars() - .rev() - .collect::(); - - if precision > 0 { - format!("{}.{:0>2$}", formatted_whole_value, decimal, precision) - } else { - formatted_whole_value +pub fn format_currency(value: &str, separator: char) -> String { + let full_len = value.len(); + let mut buffer = String::with_capacity(full_len / 3 + full_len); + let mut iter = value.splitn(2, '.'); + let whole = iter.next().unwrap_or(""); + let mut idx = whole.len() as isize - 1; + for c in whole.chars() { + buffer.push(c); + if idx > 0 && idx % 3 == 0 { + buffer.push(separator); + } + idx -= 1; } + if let Some(decimal) = iter.next() { + buffer.push('.'); + buffer.push_str(decimal); + } + buffer } #[cfg(test)] #[allow(clippy::excessive_precision)] mod test { + use super::format_currency; + #[test] - fn display_currency() { - assert_eq!(String::from("0.00"), super::display_currency(0.0f64, 2, ",")); - assert_eq!(String::from("0.000000000000"), super::display_currency(0.0f64, 12, ",")); - assert_eq!( - String::from("123,456.123456789"), - super::display_currency(123_456.123_456_789_012_f64, 9, ",") - ); - assert_eq!( - String::from("123,456"), - super::display_currency(123_456.123_456_789_012_f64, 0, ",") - ); - assert_eq!(String::from("1,234"), super::display_currency(1234.1f64, 0, ",")); + fn test_format_currency() { + assert_eq!("0.00", format_currency("0.00", ',')); + assert_eq!("0.000000000000", format_currency("0.000000000000", ',')); + assert_eq!("123,456.123456789", format_currency("123456.123456789", ',')); + assert_eq!("123,456", format_currency("123456", ',')); + assert_eq!("123", format_currency("123", ',')); + assert_eq!("7,123", format_currency("7123", ',')); + assert_eq!(".00", format_currency(".00", ',')); + assert_eq!("00.", format_currency("00.", ',')); } } diff --git a/base_layer/core/src/transactions/tari_amount.rs b/base_layer/core/src/transactions/tari_amount.rs index cd955f0763d..db78ac6999d 100644 --- a/base_layer/core/src/transactions/tari_amount.rs +++ b/base_layer/core/src/transactions/tari_amount.rs @@ -23,8 +23,11 @@ use newtype_ops::newtype_ops; use serde::{Deserialize, Serialize}; -use crate::transactions::helpers::display_currency; +use crate::transactions::helpers; +use decimal_rs::{Decimal, DecimalConvertError}; +use derive_more::{Add, AddAssign, Div, From, FromStr, Into, Mul, Rem, Sub, SubAssign}; use std::{ + convert::{TryFrom, TryInto}, fmt::{Display, Error, Formatter}, iter::Sum, ops::{Add, Mul}, @@ -49,6 +52,8 @@ pub struct MicroTari(pub u64); pub enum MicroTariError { #[error("Failed to parse value: {0}")] ParseError(String), + #[error("Failed to convert value: {0}")] + ConversionError(#[from] DecimalConvertError), } /// A convenience constant that makes it easier to define Tari amounts. /// ```edition2018 @@ -140,26 +145,30 @@ impl std::str::FromStr for MicroTari { if is_micro_tari { processed .parse::() + // TODO: Why we compare it with `0` here? It's unsigned. .map(|v| MicroTari::from(v.max(0))) .map_err(|e| MicroTariError::ParseError(e.to_string())) } else { processed - .parse::() + .parse::() .map_err(|e| MicroTariError::ParseError(e.to_string())) .map(|v| { - if v < 0.0 { + if v.is_sign_negative() { Err(MicroTariError::ParseError("value cannot be negative".to_string())) } else { - Ok(MicroTari::from(Tari::from(v.max(0.0)))) + // TODO: Check. It can't be `NaN` anymore. Still we need `.max(0.0)` check? + Tari::from(v).try_into().map_err(MicroTariError::from) } })? } } } -impl From for MicroTari { - fn from(v: Tari) -> Self { - MicroTari((v.0 * 1e6) as u64) +impl TryFrom for MicroTari { + type Error = DecimalConvertError; + + fn try_from(v: Tari) -> Result { + (v.0 * 1_000_000u32).try_into().map(Self) } } @@ -204,12 +213,16 @@ impl From for FormattedMicroTari { impl Display for FormattedMicroTari { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - write!(f, "{} µT", display_currency(self.0 as f64, 0, ",")) + let value = format!("{}", self.0); + let formatted = helpers::format_currency(&value, ','); + f.write_str(&formatted)?; + f.write_str(" µT")?; + Ok(()) } } #[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] -pub struct FormattedTari(pub f64); +pub struct FormattedTari(pub Decimal); impl From for FormattedTari { fn from(v: Tari) -> Self { @@ -219,17 +232,20 @@ impl From for FormattedTari { impl Display for FormattedTari { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - write!(f, "{} T", display_currency(self.0, 2, ",")) + let value = format!("{:.2}", self.0); + let formatted = helpers::format_currency(&value, ','); + f.write_str(&formatted)?; + f.write_str(" T")?; + Ok(()) } } /// A convenience struct for representing full Tari. You should **never** use Tari in consensus calculations, because /// Tari wraps a floating point value. Use MicroTari for that instead. -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] -pub struct Tari(f64); - -newtype_ops! { [Tari] {add sub} {:=} Self Self } -newtype_ops! { [Tari] {mul div rem} {:=} Self f64 } +#[derive( + Copy, Clone, Debug, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Mul, Div, Rem, From, Into, FromStr, +)] +pub struct Tari(Decimal); impl Tari { pub fn formatted(self) -> FormattedTari { @@ -243,28 +259,28 @@ impl Display for Tari { } } -impl From for f64 { - fn from(v: Tari) -> Self { - v.0 - } -} +pub type TariConversionError = DecimalConvertError; -impl From for Tari { - fn from(v: f64) -> Self { - Tari(v) +// TODO: Remove `f64` completely! Using it is the bad idea in general. +impl TryFrom for Tari { + type Error = TariConversionError; + + fn try_from(v: f64) -> Result { + Decimal::try_from(v).map(Self) } } impl From for Tari { fn from(v: MicroTari) -> Self { - Tari(v.0 as f64 * 1e-6) + Self(Decimal::from(v.0) / 1_000_000) } } #[cfg(test)] mod test { use super::{MicroTari, Tari}; - use std::str::FromStr; + use std::{convert::TryFrom, str::FromStr}; + #[test] fn micro_tari_arithmetic() { let mut a = MicroTari::from(500); @@ -301,9 +317,12 @@ mod test { let micro_tari = MicroTari::from(99_100_000); let s = format!("{}", micro_tari.formatted()); assert_eq!(micro_tari, MicroTari::from_str(s.as_str()).unwrap()); - let tari = Tari::from(1.12); + let tari = Tari::try_from(1.12).unwrap(); let s = format!("{}", tari.formatted()); - assert_eq!(MicroTari::from(tari), MicroTari::from_str(s.as_str()).unwrap()); + assert_eq!( + MicroTari::try_from(tari).unwrap(), + MicroTari::from_str(s.as_str()).unwrap() + ); assert_eq!(MicroTari::from(5_000_000), MicroTari::from_str("5000000").unwrap()); assert_eq!(MicroTari::from(5_000_000), MicroTari::from_str("5,000,000").unwrap()); assert_eq!(MicroTari::from(5_000_000), MicroTari::from_str("5,000,000 uT").unwrap()); @@ -314,41 +333,47 @@ mod test { assert!(MicroTari::from_str("5garbage T").is_err()); } + /// With `Decimal` the test with floats is not valid anymore: + /// ``` + /// thread 'transactions::tari_amount::test::add_tari_and_microtari' panicked at 'assertion failed: `(left == right)` + /// left: `Tari(Decimal { int_val: 33000000000000001000000000000000000000, scale: 38, negative: false })`, + /// right: `Tari(Decimal { int_val: 33000000000000002, scale: 17, negative: false })`', + /// ``` #[test] fn add_tari_and_microtari() { let a = MicroTari::from(100_000); - let b = Tari::from(0.23); + let b = Tari::from_str("0.23").unwrap(); let sum: Tari = b + a.into(); - assert_eq!(sum, Tari::from(0.33)); + assert_eq!(sum, Tari::from_str("0.33").unwrap()); } #[test] fn tari_arithmetic() { - let mut a = Tari::from(1.5); - let b = Tari::from(2.25); - assert_eq!(a + b, Tari::from(3.75)); - assert_eq!(a - b, Tari::from(-0.75)); - assert_eq!(a * 10.0, Tari::from(15.0)); - assert_eq!(b / 2.0, Tari::from(1.125)); + let mut a = Tari::try_from(1.5).unwrap(); + let b = Tari::try_from(2.25).unwrap(); + assert_eq!(a + b, Tari::try_from(3.75).unwrap()); + assert_eq!(a - b, Tari::try_from(-0.75).unwrap()); + assert_eq!(a * 10.0, Tari::try_from(15.0).unwrap()); + assert_eq!(b / 2.0, Tari::try_from(1.125).unwrap()); a += b; - assert_eq!(a, Tari::from(3.75)); - a -= Tari::from(0.75); - assert_eq!(a, Tari::from(3.0)); + assert_eq!(a, Tari::try_from(3.75).unwrap()); + a -= Tari::try_from(0.75).unwrap(); + assert_eq!(a, Tari::try_from(3.0).unwrap()); } #[test] fn tari_display() { - let s = format!("{}", Tari::from(1.234)); + let s = format!("{}", Tari::try_from(1.234).unwrap()); assert_eq!(s, "1.234000 T"); - let s = format!("{}", Tari::from(99.100)); + let s = format!("{}", Tari::try_from(99.100).unwrap()); assert_eq!(s, "99.100000 T"); } #[test] fn formatted_tari_display() { - let s = format!("{}", Tari::from(1.234).formatted()); + let s = format!("{}", Tari::try_from(1.234).unwrap().formatted()); assert_eq!(s, "1.23 T"); - let s = format!("{}", Tari::from(99999.100).formatted()); + let s = format!("{}", Tari::try_from(99999.100).unwrap().formatted()); assert_eq!(s, "99,999.10 T"); } } diff --git a/base_layer/core/src/validation/block_validators/body_only.rs b/base_layer/core/src/validation/block_validators/body_only.rs index 3c88d75640f..2e722654f76 100644 --- a/base_layer/core/src/validation/block_validators/body_only.rs +++ b/base_layer/core/src/validation/block_validators/body_only.rs @@ -46,22 +46,22 @@ impl PostOrphanBodyValidation for BodyOnlyValidator { /// 1. Are the block header MMR roots valid? fn validate_body_for_valid_orphan( &self, - block: &ChainBlock, backend: &B, + block: &ChainBlock, metadata: &ChainMetadata, ) -> Result<(), ValidationError> { - if block.header().height != metadata.height_of_longest_chain() + 1 { - return Err(ValidationError::IncorrectNextTipHeight { - expected: metadata.height_of_longest_chain() + 1, - block_height: block.height(), - }); - } if block.header().prev_hash != *metadata.best_block() { return Err(ValidationError::IncorrectPreviousHash { expected: metadata.best_block().to_hex(), block_hash: block.hash().to_hex(), }); } + if block.height() != metadata.height_of_longest_chain() + 1 { + return Err(ValidationError::IncorrectNextTipHeight { + expected: metadata.height_of_longest_chain() + 1, + block_height: block.height(), + }); + } let block_id = format!("block #{} ({})", block.header().height, block.hash().to_hex()); helpers::check_inputs_are_utxos(backend, &block.block().body)?; diff --git a/base_layer/core/src/validation/chain_balance.rs b/base_layer/core/src/validation/chain_balance.rs index f6bf03e8987..582c4402d9e 100644 --- a/base_layer/core/src/validation/chain_balance.rs +++ b/base_layer/core/src/validation/chain_balance.rs @@ -55,10 +55,10 @@ impl ChainBalanceValidator { impl FinalHorizonStateValidation for ChainBalanceValidator { fn validate( &self, + backend: &B, height: u64, total_utxo_sum: &Commitment, total_kernel_sum: &Commitment, - backend: &B, ) -> Result<(), ValidationError> { let emission_h = self.get_emission_commitment_at(height); let total_offset = self.fetch_total_offset_commitment(height, backend)?; diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index b76c065a453..ef6bb8e0db3 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -75,6 +75,10 @@ pub fn check_timestamp_ftl( } /// Returns the median timestamp for the provided timestamps. +/// +/// ## Panics +/// When an empty slice is given as this is undefined for median average. +/// https://math.stackexchange.com/a/3451015 pub fn calc_median_timestamp(timestamps: &[EpochTime]) -> EpochTime { assert!( !timestamps.is_empty(), diff --git a/base_layer/core/src/validation/mocks.rs b/base_layer/core/src/validation/mocks.rs index 891e7125a05..d5d347133b2 100644 --- a/base_layer/core/src/validation/mocks.rs +++ b/base_layer/core/src/validation/mocks.rs @@ -82,7 +82,7 @@ impl BlockSyncBodyValidation for MockValidator { } impl PostOrphanBodyValidation for MockValidator { - fn validate_body_for_valid_orphan(&self, _: &ChainBlock, _: &B, _: &ChainMetadata) -> Result<(), ValidationError> { + fn validate_body_for_valid_orphan(&self, _: &B, _: &ChainBlock, _: &ChainMetadata) -> Result<(), ValidationError> { if self.is_valid.load(Ordering::SeqCst) { Ok(()) } else { @@ -143,10 +143,10 @@ impl MempoolTransactionValidation for MockValidator { impl FinalHorizonStateValidation for MockValidator { fn validate( &self, + _backend: &B, _height: u64, _total_utxo_sum: &Commitment, _total_kernel_sum: &Commitment, - _backend: &B, ) -> Result<(), ValidationError> { if self.is_valid.load(Ordering::SeqCst) { Ok(()) diff --git a/base_layer/core/src/validation/test.rs b/base_layer/core/src/validation/test.rs index f60f28a2656..886a878dcb2 100644 --- a/base_layer/core/src/validation/test.rs +++ b/base_layer/core/src/validation/test.rs @@ -137,7 +137,7 @@ fn chain_balance_validation() { let validator = ChainBalanceValidator::new(consensus_manager.clone(), factories.clone()); // Validate the genesis state validator - .validate(0, &utxo_sum, &kernel_sum, &*db.db_read_access().unwrap()) + .validate(&*db.db_read_access().unwrap(), 0, &utxo_sum, &kernel_sum) .unwrap(); //---------------------------------- Add a new coinbase and header --------------------------------------------// @@ -187,7 +187,7 @@ fn chain_balance_validation() { utxo_sum = &coinbase.commitment + &utxo_sum; kernel_sum = &kernel.excess + &kernel_sum; validator - .validate(1, &utxo_sum, &kernel_sum, &*db.db_read_access().unwrap()) + .validate(&*db.db_read_access().unwrap(), 1, &utxo_sum, &kernel_sum) .unwrap(); //---------------------------------- Try to inflate --------------------------------------------// @@ -231,6 +231,6 @@ fn chain_balance_validation() { db.commit(txn).unwrap(); validator - .validate(2, &utxo_sum, &kernel_sum, &*db.db_read_access().unwrap()) + .validate(&*db.db_read_access().unwrap(), 2, &utxo_sum, &kernel_sum) .unwrap_err(); } diff --git a/base_layer/core/src/validation/traits.rs b/base_layer/core/src/validation/traits.rs index ad77c387e28..684e68600a3 100644 --- a/base_layer/core/src/validation/traits.rs +++ b/base_layer/core/src/validation/traits.rs @@ -41,8 +41,8 @@ pub trait BlockSyncBodyValidation: Send + Sync { pub trait PostOrphanBodyValidation: Send + Sync { fn validate_body_for_valid_orphan( &self, - block: &ChainBlock, backend: &B, + block: &ChainBlock, metadata: &ChainMetadata, ) -> Result<(), ValidationError>; } @@ -67,9 +67,9 @@ pub trait HeaderValidation: Send + Sync { pub trait FinalHorizonStateValidation: Send + Sync { fn validate( &self, + backend: &B, height: u64, total_utxo_sum: &Commitment, total_kernel_sum: &Commitment, - backend: &B, ) -> Result<(), ValidationError>; } diff --git a/base_layer/core/tests/block_validation.rs b/base_layer/core/tests/block_validation.rs index 8b7303ed47b..a05688f5ef0 100644 --- a/base_layer/core/tests/block_validation.rs +++ b/base_layer/core/tests/block_validation.rs @@ -439,7 +439,7 @@ OutputFeatures::default()), let metadata = db.get_chain_metadata().unwrap(); // this block should be okay assert!(body_only_validator - .validate_body_for_valid_orphan(&chain_block, &*db.db_read_access().unwrap(), &metadata) + .validate_body_for_valid_orphan(&*db.db_read_access().unwrap(), &chain_block, &metadata) .is_ok()); // lets break the chain sequence @@ -464,7 +464,7 @@ OutputFeatures::default()), let chain_block = ChainBlock::try_construct(Arc::new(new_block), accumulated_data).unwrap(); let metadata = db.get_chain_metadata().unwrap(); assert!(body_only_validator - .validate_body_for_valid_orphan(&chain_block, &*db.db_read_access().unwrap(), &metadata) + .validate_body_for_valid_orphan(&*db.db_read_access().unwrap(), &chain_block, &metadata) .is_err()); // lets have unknown inputs; @@ -503,7 +503,7 @@ OutputFeatures::default()), let chain_block = ChainBlock::try_construct(Arc::new(new_block), accumulated_data).unwrap(); let metadata = db.get_chain_metadata().unwrap(); assert!(body_only_validator - .validate_body_for_valid_orphan(&chain_block, &*db.db_read_access().unwrap(), &metadata) + .validate_body_for_valid_orphan(&*db.db_read_access().unwrap(), &chain_block, &metadata) .is_err()); // lets check duplicate txos @@ -533,7 +533,7 @@ OutputFeatures::default()), let chain_block = ChainBlock::try_construct(Arc::new(new_block), accumulated_data).unwrap(); let metadata = db.get_chain_metadata().unwrap(); assert!(body_only_validator - .validate_body_for_valid_orphan(&chain_block, &*db.db_read_access().unwrap(), &metadata) + .validate_body_for_valid_orphan(&*db.db_read_access().unwrap(), &chain_block, &metadata) .is_err()); // check mmr roots @@ -560,7 +560,7 @@ OutputFeatures::default()), let chain_block = ChainBlock::try_construct(Arc::new(new_block), accumulated_data).unwrap(); let metadata = db.get_chain_metadata().unwrap(); assert!(body_only_validator - .validate_body_for_valid_orphan(&chain_block, &*db.db_read_access().unwrap(), &metadata) + .validate_body_for_valid_orphan(&*db.db_read_access().unwrap(), &chain_block, &metadata) .is_err()); } diff --git a/base_layer/p2p/src/auto_update/mod.rs b/base_layer/p2p/src/auto_update/mod.rs index 4ec18e8535e..e70118ff36e 100644 --- a/base_layer/p2p/src/auto_update/mod.rs +++ b/base_layer/p2p/src/auto_update/mod.rs @@ -169,6 +169,7 @@ const MAINTAINERS: &[&str] = &[ include_str!("../../../../meta/gpg_keys/philipr-za.asc"), include_str!("../../../../meta/gpg_keys/sdbondi.asc"), include_str!("../../../../meta/gpg_keys/swvheerden.asc"), + include_str!("../../../../meta/gpg_keys/delta1.asc"), ]; fn maintainers() -> impl Iterator { diff --git a/base_layer/wallet/src/error.rs b/base_layer/wallet/src/error.rs index bfd57766c4e..a79548ac0ed 100644 --- a/base_layer/wallet/src/error.rs +++ b/base_layer/wallet/src/error.rs @@ -31,6 +31,7 @@ use crate::{ use diesel::result::Error as DieselError; use log::SetLoggerError; use serde_json::Error as SerdeJsonError; +use tari_common::exit_codes::ExitCodes; use tari_comms::{ connectivity::ConnectivityError, multiaddr, @@ -85,6 +86,16 @@ pub enum WalletError { UtxoScannerError(#[from] UtxoScannerError), } +pub const LOG_TARGET: &str = "tari::application"; + +impl From for ExitCodes { + fn from(err: WalletError) -> Self { + // TODO: Log that outside + log::error!(target: LOG_TARGET, "{}", err); + Self::WalletError(err.to_string()) + } +} + #[derive(Debug, Error)] pub enum WalletStorageError { #[error("Tried to insert an output that already exists in the database")] @@ -142,3 +153,14 @@ pub enum WalletStorageError { #[error("Deprecated operation error")] DeprecatedOperation, } + +impl From for ExitCodes { + fn from(err: WalletStorageError) -> Self { + use WalletStorageError::*; + match err { + NoPasswordError => ExitCodes::NoPassword, + IncorrectPassword => ExitCodes::IncorrectPassword, + e => ExitCodes::WalletError(e.to_string()), + } + } +} diff --git a/base_layer/wallet/src/output_manager_service/error.rs b/base_layer/wallet/src/output_manager_service/error.rs index 87c87f7baec..8d6d8a20ac7 100644 --- a/base_layer/wallet/src/output_manager_service/error.rs +++ b/base_layer/wallet/src/output_manager_service/error.rs @@ -22,6 +22,7 @@ use crate::base_node_service::error::BaseNodeServiceError; use diesel::result::Error as DieselError; +use tari_common::exit_codes::ExitCodes; use tari_comms::{connectivity::ConnectivityError, peer_manager::node_id::NodeIdError, protocol::rpc::RpcError}; use tari_comms_dht::outbound::DhtOutboundError; use tari_core::transactions::{ @@ -168,6 +169,13 @@ pub enum OutputManagerStorageError { ScriptError(#[from] ScriptError), } +impl From for ExitCodes { + fn from(err: OutputManagerError) -> Self { + log::error!(target: crate::error::LOG_TARGET, "{}", err); + Self::WalletError(err.to_string()) + } +} + /// This error type is used to return OutputManagerError from inside a Output Manager Service protocol but also /// include the ID of the protocol #[derive(Debug)] diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index 4c9fd404984..39f31aefea4 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -24,11 +24,10 @@ use crate::output_manager_service::{ error::OutputManagerError, service::Balance, storage::models::KnownOneSidedPaymentScript, - TxId, }; use aes_gcm::Aes256Gcm; use std::{fmt, sync::Arc}; -use tari_common_types::types::PublicKey; +use tari_common_types::{transaction::TxId, types::PublicKey}; use tari_core::transactions::{ tari_amount::MicroTari, transaction::{Transaction, TransactionOutput, UnblindedOutput}, diff --git a/base_layer/wallet/src/output_manager_service/mod.rs b/base_layer/wallet/src/output_manager_service/mod.rs index cd13c3ecb16..0858229578b 100644 --- a/base_layer/wallet/src/output_manager_service/mod.rs +++ b/base_layer/wallet/src/output_manager_service/mod.rs @@ -61,8 +61,6 @@ mod tasks; const LOG_TARGET: &str = "wallet::output_manager_service::initializer"; -pub type TxId = u64; - pub struct OutputManagerServiceInitializer where T: OutputManagerBackend { diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 238e3dd4767..242aa0f0d26 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -35,7 +35,6 @@ use crate::{ }, tasks::TxoValidationTask, MasterKeyManager, - TxId, }, transaction_service::handle::TransactionServiceHandle, types::HashDigest, @@ -50,7 +49,10 @@ use std::{ fmt::{self, Display}, sync::Arc, }; -use tari_common_types::types::{PrivateKey, PublicKey}; +use tari_common_types::{ + transaction::TxId, + types::{PrivateKey, PublicKey}, +}; use tari_comms::types::{CommsPublicKey, CommsSecretKey}; use tari_core::{ consensus::ConsensusConstants, diff --git a/base_layer/wallet/src/output_manager_service/storage/database.rs b/base_layer/wallet/src/output_manager_service/storage/database.rs index f70197c95ec..0a232aec446 100644 --- a/base_layer/wallet/src/output_manager_service/storage/database.rs +++ b/base_layer/wallet/src/output_manager_service/storage/database.rs @@ -24,7 +24,6 @@ use crate::output_manager_service::{ error::OutputManagerStorageError, service::Balance, storage::models::{DbUnblindedOutput, KnownOneSidedPaymentScript, OutputStatus}, - TxId, }; use aes_gcm::Aes256Gcm; use log::*; @@ -32,7 +31,10 @@ use std::{ fmt::{Display, Error, Formatter}, sync::Arc, }; -use tari_common_types::types::{BlindingFactor, Commitment, HashOutput, PrivateKey}; +use tari_common_types::{ + transaction::TxId, + types::{BlindingFactor, Commitment, HashOutput, PrivateKey}, +}; use tari_core::transactions::transaction::TransactionOutput; const LOG_TARGET: &str = "wallet::output_manager_service::database"; diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db.rs index 8a448ff1fb2..f74966522ea 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db.rs @@ -28,7 +28,6 @@ use crate::{ database::{DbKey, DbKeyValuePair, DbValue, KeyManagerState, OutputManagerBackend, WriteOperation}, models::{DbUnblindedOutput, KnownOneSidedPaymentScript, OutputStatus}, }, - TxId, }, schema::{key_manager_states, known_one_sided_payment_scripts, outputs}, storage::sqlite_utilities::WalletDbConnection, @@ -46,7 +45,10 @@ use std::{ str::from_utf8, sync::{Arc, RwLock}, }; -use tari_common_types::types::{ComSignature, Commitment, PrivateKey, PublicKey}; +use tari_common_types::{ + transaction::TxId, + types::{ComSignature, Commitment, PrivateKey, PublicKey}, +}; use tari_core::{ tari_utilities::hash::Hashable, transactions::{ @@ -63,6 +65,7 @@ use tari_crypto::{ ByteArray, }, }; +use tokio::time::Instant; const LOG_TARGET: &str = "wallet::output_manager_service::database::sqlite_db"; @@ -148,7 +151,9 @@ impl OutputManagerSqliteDatabase { impl OutputManagerBackend for OutputManagerSqliteDatabase { #[allow(clippy::cognitive_complexity)] fn fetch(&self, key: &DbKey) -> Result, OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let result = match key { DbKey::SpentOutput(k) => match OutputSql::find_status(&k.to_vec(), OutputStatus::Spent, &(*conn)) { @@ -177,7 +182,6 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { None }, }, - DbKey::AnyOutputByCommitment(commitment) => { match OutputSql::find_by_commitment(&commitment.to_vec(), &(*conn)) { Ok(mut o) => { @@ -244,17 +248,19 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { .collect::, _>>()?, )) }, - DbKey::KeyManagerState => match KeyManagerStateSql::get_state(&(*conn)).ok() { - None => None, - Some(mut km) => { - self.decrypt_if_necessary(&mut km)?; - - // TODO: This is a problem because the keymanager state does not have an index - // meaning that update round trips to the database can't be found again. - // I would suggest changing this to a different pattern for retrieval, perhaps - // only returning the columns that are needed. - Some(DbValue::KeyManagerState(KeyManagerState::try_from(km)?)) - }, + DbKey::KeyManagerState => { + match KeyManagerStateSql::get_state(&(*conn)).ok() { + None => None, + Some(mut km) => { + self.decrypt_if_necessary(&mut km)?; + + // TODO: This is a problem because the keymanager state does not have an index + // meaning that update round trips to the database can't be found again. + // I would suggest changing this to a different pattern for retrieval, perhaps + // only returning the columns that are needed. + Some(DbValue::KeyManagerState(KeyManagerState::try_from(km)?)) + }, + } }, DbKey::InvalidOutputs => { let mut outputs = OutputSql::index_status(OutputStatus::Invalid, &(*conn))?; @@ -283,16 +289,33 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { )) }, }; + trace!( + target: LOG_TARGET, + "sqlite profile - fetch '{}': lock {} + db_op {} = {} ms", + key, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(result) } fn fetch_mined_unspent_outputs(&self) -> Result, OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let mut outputs = OutputSql::index_marked_deleted_in_block_is_null(&(*conn))?; for output in outputs.iter_mut() { self.decrypt_if_necessary(output)?; } + trace!( + target: LOG_TARGET, + "sqlite profile - fetch_mined_unspent_outputs: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); outputs .into_iter() @@ -301,11 +324,20 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { } fn fetch_unconfirmed_outputs(&self) -> Result, OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let mut outputs = OutputSql::index_unconfirmed(&(*conn))?; for output in outputs.iter_mut() { self.decrypt_if_necessary(output)?; } + trace!( + target: LOG_TARGET, + "sqlite profile - fetch_unconfirmed_outputs: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); outputs .into_iter() @@ -313,26 +345,10 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { .collect::, _>>() } - fn fetch_pending_incoming_outputs(&self) -> Result, OutputManagerStorageError> { - let conn = self.database_connection.acquire_lock(); - - let mut outputs = OutputSql::index_status(OutputStatus::EncumberedToBeReceived, &conn)?; - outputs.extend(OutputSql::index_status( - OutputStatus::ShortTermEncumberedToBeReceived, - &conn, - )?); - outputs.extend(OutputSql::index_status(OutputStatus::UnspentMinedUnconfirmed, &conn)?); - for o in outputs.iter_mut() { - self.decrypt_if_necessary(o)?; - } - outputs - .iter() - .map(|o| DbUnblindedOutput::try_from(o.clone())) - .collect::, _>>() - } - fn write(&self, op: WriteOperation) -> Result, OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); match op { WriteOperation::Insert(kvp) => self.insert(kvp, &conn)?, @@ -343,6 +359,13 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { Ok(mut o) => { o.delete(&(*conn))?; self.decrypt_if_necessary(&mut o)?; + trace!( + target: LOG_TARGET, + "sqlite profile - write Remove: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); return Ok(Some(DbValue::AnyOutput(Box::new(DbUnblindedOutput::try_from(o)?)))); }, Err(e) => { @@ -364,24 +387,42 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { DbKey::OutputsByTxIdAndStatus(_, _) => return Err(OutputManagerStorageError::OperationNotSupported), }, } + trace!( + target: LOG_TARGET, + "sqlite profile - write Insert: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(None) } - fn set_output_to_unmined(&self, hash: Vec) -> Result<(), OutputManagerStorageError> { + fn fetch_pending_incoming_outputs(&self) -> Result, OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); - // Only allow updating of non-deleted utxos - diesel::update(outputs::table.filter(outputs::hash.eq(hash).and(outputs::marked_deleted_at_height.is_null()))) - .set(( - outputs::mined_height.eq::>(None), - outputs::mined_in_block.eq::>>(None), - outputs::mined_mmr_position.eq::>(None), - outputs::status.eq(OutputStatus::Invalid as i32), - )) - .execute(&(*conn)) - .num_rows_affected_or_not_found(1)?; + let acquire_lock = start.elapsed(); - Ok(()) + let mut outputs = OutputSql::index_status(OutputStatus::EncumberedToBeReceived, &conn)?; + outputs.extend(OutputSql::index_status( + OutputStatus::ShortTermEncumberedToBeReceived, + &conn, + )?); + outputs.extend(OutputSql::index_status(OutputStatus::UnspentMinedUnconfirmed, &conn)?); + for o in outputs.iter_mut() { + self.decrypt_if_necessary(o)?; + } + trace!( + target: LOG_TARGET, + "sqlite profile - fetch_pending_incoming_outputs: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + outputs + .iter() + .map(|o| DbUnblindedOutput::try_from(o.clone())) + .collect::, _>>() } fn set_received_output_mined_height( @@ -392,13 +433,15 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { mmr_position: u64, confirmed: bool, ) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let status = if confirmed { OutputStatus::Unspent as i32 } else { OutputStatus::UnspentMinedUnconfirmed as i32 }; - error!( + debug!( target: LOG_TARGET, "`set_received_output_mined_height` status: {}", status ); @@ -412,6 +455,38 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { )) .execute(&(*conn)) .num_rows_affected_or_not_found(1)?; + trace!( + target: LOG_TARGET, + "sqlite profile - set_received_output_mined_height: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + + Ok(()) + } + + fn set_output_to_unmined(&self, hash: Vec) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); + let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); + // Only allow updating of non-deleted utxos + diesel::update(outputs::table.filter(outputs::hash.eq(hash).and(outputs::marked_deleted_at_height.is_null()))) + .set(( + outputs::mined_height.eq::>(None), + outputs::mined_in_block.eq::>>(None), + outputs::mined_mmr_position.eq::>(None), + outputs::status.eq(OutputStatus::Invalid as i32), + )) + .execute(&(*conn)) + .num_rows_affected_or_not_found(1)?; + trace!( + target: LOG_TARGET, + "sqlite profile - set_output_to_unmined: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } @@ -423,7 +498,9 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { mark_deleted_in_block: Vec, confirmed: bool, ) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let status = if confirmed { OutputStatus::Spent as i32 } else { @@ -446,12 +523,21 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { )) .execute(&(*conn)) .num_rows_affected_or_not_found(1)?; + trace!( + target: LOG_TARGET, + "sqlite profile - mark_output_as_spent: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn mark_output_as_unspent(&self, hash: Vec) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); debug!(target: LOG_TARGET, "mark_output_as_unspent({})", hash.to_hex()); diesel::update( @@ -469,40 +555,13 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { )) .execute(&(*conn)) .num_rows_affected_or_not_found(1)?; - - Ok(()) - } - - fn set_coinbase_abandoned(&self, tx_id: TxId, abandoned: bool) -> Result<(), OutputManagerStorageError> { - let conn = self.database_connection.acquire_lock(); - - if abandoned { - debug!( - target: LOG_TARGET, - "set_coinbase_abandoned(TxID: {}) as {}", tx_id, abandoned - ); - diesel::update( - outputs::table.filter( - outputs::received_in_tx_id - .eq(Some(tx_id as i64)) - .and(outputs::coinbase_block_height.is_not_null()), - ), - ) - .set((outputs::status.eq(OutputStatus::AbandonedCoinbase as i32),)) - .execute(&(*conn)) - .num_rows_affected_or_not_found(1)?; - } else { - let output = OutputSql::find_by_tx_id_and_status(tx_id, OutputStatus::AbandonedCoinbase, &conn)?; - for o in output.into_iter() { - o.update( - UpdateOutput { - status: Some(OutputStatus::EncumberedToBeReceived), - ..Default::default() - }, - &conn, - )?; - } - }; + trace!( + target: LOG_TARGET, + "sqlite profile - mark_output_as_unspent: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } @@ -513,7 +572,9 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { outputs_to_send: &[DbUnblindedOutput], outputs_to_receive: &[DbUnblindedOutput], ) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let mut outputs_to_be_spent = Vec::with_capacity(outputs_to_send.len()); for i in outputs_to_send { @@ -545,12 +606,21 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { self.encrypt_if_necessary(&mut new_output)?; new_output.commit(&(*conn))?; } + trace!( + target: LOG_TARGET, + "sqlite profile - short_term_encumber_outputs: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn confirm_encumbered_outputs(&self, tx_id: TxId) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let outputs_to_be_received = OutputSql::find_by_tx_id_and_status(tx_id, OutputStatus::ShortTermEncumberedToBeReceived, &conn)?; @@ -575,12 +645,21 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { &(*conn), )?; } + trace!( + target: LOG_TARGET, + "sqlite profile - confirm_encumbered_outputs: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn clear_short_term_encumberances(&self) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let outputs_to_be_received = OutputSql::index_status(OutputStatus::ShortTermEncumberedToBeReceived, &conn)?; for o in outputs_to_be_received.iter() { @@ -603,14 +682,30 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { &(*conn), )?; } + trace!( + target: LOG_TARGET, + "sqlite profile - clear_short_term_encumberances: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn get_last_mined_output(&self) -> Result, OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let output = OutputSql::first_by_mined_height_desc(&(*conn))?; + trace!( + target: LOG_TARGET, + "sqlite profile - get_last_mined_output: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); match output { Some(mut o) => { self.decrypt_if_necessary(&mut o)?; @@ -621,9 +716,18 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { } fn get_last_spent_output(&self) -> Result, OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let output = OutputSql::first_by_marked_deleted_height_desc(&(*conn))?; + trace!( + target: LOG_TARGET, + "sqlite profile - get_last_spent_output: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); match output { Some(mut o) => { self.decrypt_if_necessary(&mut o)?; @@ -637,13 +741,25 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { &self, current_tip_for_time_lock_calculation: Option, ) -> Result { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); - OutputSql::get_balance(current_tip_for_time_lock_calculation, &(*conn)) + let result = OutputSql::get_balance(current_tip_for_time_lock_calculation, &(*conn)); + trace!( + target: LOG_TARGET, + "sqlite profile - get_balance: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + result } fn cancel_pending_transaction(&self, tx_id: TxId) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let outputs = OutputSql::find_by_tx_id_and_encumbered(tx_id, &conn)?; @@ -671,41 +787,55 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { )?; } } - - Ok(()) - } - - fn clear_pending_coinbase_transaction_at_block_height( - &self, - block_height: u64, - ) -> Result<(), OutputManagerStorageError> { - let conn = self.database_connection.acquire_lock(); - - let output = OutputSql::find_pending_coinbase_at_block_height(block_height, &conn)?; - - output.delete(&conn)?; + trace!( + target: LOG_TARGET, + "sqlite profile - cancel_pending_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn increment_key_index(&self) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); KeyManagerStateSql::increment_index(&(*conn))?; + trace!( + target: LOG_TARGET, + "sqlite profile - increment_key_index: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn set_key_index(&self, index: u64) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); KeyManagerStateSql::set_index(index, &(*conn))?; + trace!( + target: LOG_TARGET, + "sqlite profile - set_key_index: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn update_output_metadata_signature(&self, output: &TransactionOutput) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let db_output = OutputSql::find_by_commitment_and_cancelled(&output.commitment.to_vec(), false, &conn)?; db_output.update( UpdateOutput { @@ -715,12 +845,21 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { }, &(*conn), )?; + trace!( + target: LOG_TARGET, + "sqlite profile - update_output_metadata_signature: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn revalidate_unspent_output(&self, commitment: &Commitment) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let output = OutputSql::find_by_commitment_and_cancelled(&commitment.to_vec(), false, &conn)?; if OutputStatus::try_from(output.status)? != OutputStatus::Invalid { @@ -733,22 +872,13 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { }, &(*conn), )?; - Ok(()) - } - - fn reinstate_cancelled_inbound_output(&self, tx_id: TxId) -> Result<(), OutputManagerStorageError> { - let conn = self.database_connection.acquire_lock(); - let outputs = OutputSql::find_by_tx_id_and_status(tx_id, OutputStatus::CancelledInbound, &conn)?; - - for o in outputs { - o.update( - UpdateOutput { - status: Some(OutputStatus::EncumberedToBeReceived), - ..Default::default() - }, - &(*conn), - )?; - } + trace!( + target: LOG_TARGET, + "sqlite profile - revalidate_unspent_output: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } @@ -759,7 +889,9 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { return Err(OutputManagerStorageError::AlreadyEncrypted); } + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let mut outputs = OutputSql::index(&conn)?; // If the db is already encrypted then the very first output we try to encrypt will fail. @@ -809,6 +941,13 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { } (*current_cipher) = Some(cipher); + trace!( + target: LOG_TARGET, + "sqlite profile - apply_encryption: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } @@ -820,7 +959,9 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { } else { return Ok(()); }; + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let mut outputs = OutputSql::index(&conn)?; for o in outputs.iter_mut() { @@ -846,6 +987,103 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { // Now that all the decryption has been completed we can safely remove the cipher fully let _ = (*current_cipher).take(); + trace!( + target: LOG_TARGET, + "sqlite profile - remove_encryption: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + Ok(()) + } + + fn clear_pending_coinbase_transaction_at_block_height( + &self, + block_height: u64, + ) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); + let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); + + let output = OutputSql::find_pending_coinbase_at_block_height(block_height, &conn)?; + + output.delete(&conn)?; + trace!( + target: LOG_TARGET, + "sqlite profile - clear_pending_coinbase_transaction_at_block_height: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + + Ok(()) + } + + fn set_coinbase_abandoned(&self, tx_id: TxId, abandoned: bool) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); + let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); + + if abandoned { + debug!( + target: LOG_TARGET, + "set_coinbase_abandoned(TxID: {}) as {}", tx_id, abandoned + ); + diesel::update( + outputs::table.filter( + outputs::received_in_tx_id + .eq(Some(tx_id as i64)) + .and(outputs::coinbase_block_height.is_not_null()), + ), + ) + .set((outputs::status.eq(OutputStatus::AbandonedCoinbase as i32),)) + .execute(&(*conn)) + .num_rows_affected_or_not_found(1)?; + } else { + let output = OutputSql::find_by_tx_id_and_status(tx_id, OutputStatus::AbandonedCoinbase, &conn)?; + for o in output.into_iter() { + o.update( + UpdateOutput { + status: Some(OutputStatus::EncumberedToBeReceived), + ..Default::default() + }, + &conn, + )?; + } + }; + trace!( + target: LOG_TARGET, + "sqlite profile - set_coinbase_abandoned: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + + Ok(()) + } + + fn reinstate_cancelled_inbound_output(&self, tx_id: TxId) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); + let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); + let outputs = OutputSql::find_by_tx_id_and_status(tx_id, OutputStatus::CancelledInbound, &conn)?; + + for o in outputs { + o.update( + UpdateOutput { + status: Some(OutputStatus::EncumberedToBeReceived), + ..Default::default() + }, + &(*conn), + )?; + } + trace!( + target: LOG_TARGET, + "sqlite profile - reinstate_cancelled_inbound_output: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } } diff --git a/base_layer/wallet/src/storage/sqlite_db.rs b/base_layer/wallet/src/storage/sqlite_db.rs index 52f4f4df997..e9bf03c5691 100644 --- a/base_layer/wallet/src/storage/sqlite_db.rs +++ b/base_layer/wallet/src/storage/sqlite_db.rs @@ -55,6 +55,7 @@ use tari_crypto::{ ByteArray, }, }; +use tokio::time::Instant; const LOG_TARGET: &str = "wallet::storage::sqlite_db"; @@ -214,15 +215,21 @@ impl WalletSqliteDatabase { } fn insert_key_value_pair(&self, kvp: DbKeyValuePair) -> Result, WalletStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); + let kvp_text; match kvp { DbKeyValuePair::MasterSecretKey(sk) => { + kvp_text = "MasterSecretKey"; self.set_master_secret_key(&sk, &(*conn))?; }, DbKeyValuePair::TorId(node_id) => { + kvp_text = "TorId"; self.set_tor_id(node_id, &(*conn))?; }, DbKeyValuePair::BaseNodeChainMetadata(metadata) => { + kvp_text = "BaseNodeChainMetadata"; self.set_chain_metadata(metadata, &(*conn))?; }, DbKeyValuePair::ClientKeyValue(k, v) => { @@ -238,28 +245,47 @@ impl WalletSqliteDatabase { self.encrypt_if_necessary(&mut client_key_value)?; client_key_value.set(&conn)?; + trace!( + target: LOG_TARGET, + "sqlite profile - insert_key_value_pair 'ClientKeyValue': lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); return Ok(value_to_return.map(|v| DbValue::ClientValue(v.value))); }, DbKeyValuePair::CommsAddress(ca) => { + kvp_text = "CommsAddress"; WalletSettingSql::new(DbKey::CommsAddress.to_string(), ca.to_string()).set(&conn)?; }, DbKeyValuePair::CommsFeatures(cf) => { + kvp_text = "CommsFeatures"; WalletSettingSql::new(DbKey::CommsFeatures.to_string(), cf.bits().to_string()).set(&conn)?; }, } + trace!( + target: LOG_TARGET, + "sqlite profile - insert_key_value_pair '{}': lock {} + db_op {} = {} ms", + kvp_text, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(None) } fn remove_key(&self, k: DbKey) -> Result, WalletStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); match k { DbKey::MasterSecretKey => { let _ = WalletSettingSql::clear(DbKey::MasterSecretKey.to_string(), &conn)?; }, DbKey::MasterPublicKey => return Err(WalletStorageError::OperationNotSupported), - DbKey::ClientKey(k) => { - if ClientKeyValueSql::clear(&k, &conn)? { + DbKey::ClientKey(ref k) => { + if ClientKeyValueSql::clear(k, &conn)? { return Ok(Some(DbValue::ValueCleared)); } }, @@ -276,13 +302,23 @@ impl WalletSqliteDatabase { let _ = WalletSettingSql::clear(DbKey::TorId.to_string(), &conn)?; }, }; + trace!( + target: LOG_TARGET, + "sqlite profile - remove_key '{}': lock {} + db_op {} = {} ms", + k, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(None) } } impl WalletBackend for WalletSqliteDatabase { fn fetch(&self, key: &DbKey) -> Result, WalletStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let result = match key { DbKey::MasterSecretKey => self.get_master_secret_key(&conn)?.map(DbValue::MasterSecretKey), @@ -305,6 +341,14 @@ impl WalletBackend for WalletSqliteDatabase { DbKey::CommsFeatures => self.get_comms_features(&conn)?.map(DbValue::CommsFeatures), DbKey::BaseNodeChainMetadata => self.get_chain_metadata(&conn)?.map(DbValue::BaseNodeChainMetadata), }; + trace!( + target: LOG_TARGET, + "sqlite profile - fetch '{}': lock {} + db_op {} = {} ms", + key, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(result) } @@ -322,7 +366,9 @@ impl WalletBackend for WalletSqliteDatabase { return Err(WalletStorageError::AlreadyEncrypted); } + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let secret_key_str = match WalletSettingSql::get(DbKey::MasterSecretKey.to_string(), &conn)? { None => return Err(WalletStorageError::ValueNotFound(DbKey::MasterSecretKey)), Some(sk) => sk, @@ -352,6 +398,13 @@ impl WalletBackend for WalletSqliteDatabase { } (*current_cipher) = Some(cipher); + trace!( + target: LOG_TARGET, + "sqlite profile - apply_encryption: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } @@ -363,7 +416,9 @@ impl WalletBackend for WalletSqliteDatabase { } else { return Ok(()); }; + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let secret_key_str = match WalletSettingSql::get(DbKey::MasterSecretKey.to_string(), &conn)? { None => return Err(WalletStorageError::ValueNotFound(DbKey::MasterSecretKey)), Some(sk) => sk, @@ -397,6 +452,13 @@ impl WalletBackend for WalletSqliteDatabase { // Now that all the decryption has been completed we can safely remove the cipher fully let _ = (*current_cipher).take(); + trace!( + target: LOG_TARGET, + "sqlite profile - remove_encryption: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } @@ -411,7 +473,9 @@ fn check_db_encryption_status( database_connection: &WalletDbConnection, cipher: Option, ) -> Result<(), WalletStorageError> { + let start = Instant::now(); let conn = database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let secret_key = WalletSettingSql::get(DbKey::MasterSecretKey.to_string(), &conn)?; let db_public_key = WalletSettingSql::get(DbKey::MasterPublicKey.to_string(), &conn)?; @@ -498,6 +562,13 @@ fn check_db_encryption_status( WalletSettingSql::new(DbKey::MasterPublicKey.to_string(), public_key_hex).set(&conn)?; } } + trace!( + target: LOG_TARGET, + "sqlite profile - check_db_encryption_status: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } diff --git a/base_layer/wallet/src/transaction_service/error.rs b/base_layer/wallet/src/transaction_service/error.rs index d3984aa25cb..6eac17cfac0 100644 --- a/base_layer/wallet/src/transaction_service/error.rs +++ b/base_layer/wallet/src/transaction_service/error.rs @@ -22,15 +22,17 @@ use crate::{ error::WalletStorageError, - output_manager_service::{error::OutputManagerError, TxId}, - transaction_service::storage::database::DbKey, + output_manager_service::error::OutputManagerError, + transaction_service::storage::{database::DbKey, sqlite_db::CompletedTransactionConversionError}, }; use diesel::result::Error as DieselError; use futures::channel::oneshot::Canceled; use serde_json::Error as SerdeJsonError; +use tari_common_types::transaction::{TransactionConversionError, TransactionDirectionError, TxId}; use tari_comms::{connectivity::ConnectivityError, peer_manager::node_id::NodeIdError, protocol::rpc::RpcError}; use tari_comms_dht::outbound::DhtOutboundError; use tari_core::transactions::{transaction::TransactionError, transaction_protocol::TransactionProtocolError}; +use tari_crypto::tari_utilities::ByteArrayError; use tari_p2p::services::liveness::error::LivenessError; use tari_service_framework::reply_channel::TransportChannelError; use thiserror::Error; @@ -110,7 +112,9 @@ pub enum TransactionServiceError { #[error("Transaction error: `{0}`")] TransactionError(#[from] TransactionError), #[error("Conversion error: `{0}`")] - ConversionError(String), + ConversionError(#[from] TransactionConversionError), + #[error("duration::OutOfRangeError: {0}")] + DurationOutOfRange(#[from] OutOfRangeError), #[error("Node ID error: `{0}`")] NodeIdError(#[from] NodeIdError), #[error("Broadcast recv error: `{0}`")] @@ -156,6 +160,14 @@ pub enum TransactionServiceError { BaseNodeNotSynced, } +#[derive(Debug, Error)] +pub enum TransactionKeyError { + #[error("Invalid source Publickey")] + Source(ByteArrayError), + #[error("Invalid destination PublicKey")] + Destination(ByteArrayError), +} + #[derive(Debug, Error)] pub enum TransactionStorageError { #[error("Tried to insert an output that already exists in the database")] @@ -171,9 +183,15 @@ pub enum TransactionStorageError { #[error("Transaction is already present in the database")] TransactionAlreadyExists, #[error("Out of range error: `{0}`")] + TransactionKeyError(#[from] TransactionKeyError), + #[error("Transaction direction error: `{0}`")] + TransactionDirectionError(#[from] TransactionDirectionError), + #[error("Error converting a type: `{0}`")] OutOfRangeError(#[from] OutOfRangeError), #[error("Error converting a type: `{0}`")] - ConversionError(String), + ConversionError(#[from] TransactionConversionError), + #[error("Completed transaction conversion error: `{0}`")] + CompletedConversionError(#[from] CompletedTransactionConversionError), #[error("Serde json error: `{0}`")] SerdeJsonError(#[from] SerdeJsonError), #[error("R2d2 error")] diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 852b4e89ba6..0d5b2790946 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -20,15 +20,13 @@ // 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 crate::{ - output_manager_service::TxId, - transaction_service::{ - error::TransactionServiceError, - storage::models::{CompletedTransaction, InboundTransaction, OutboundTransaction, WalletTransaction}, - }, +use crate::transaction_service::{ + error::TransactionServiceError, + storage::models::{CompletedTransaction, InboundTransaction, OutboundTransaction, WalletTransaction}, }; use aes_gcm::Aes256Gcm; use std::{collections::HashMap, fmt, sync::Arc}; +use tari_common_types::transaction::TxId; use tari_comms::types::CommsPublicKey; use tari_core::transactions::{tari_amount::MicroTari, transaction::Transaction}; use tari_service_framework::reply_channel::SenderService; diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_broadcast_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_broadcast_protocol.rs index 61b83891de2..ec32c5e2af6 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_broadcast_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_broadcast_protocol.rs @@ -22,15 +22,11 @@ use crate::{ connectivity_service::WalletConnectivityInterface, - output_manager_service::TxId, transaction_service::{ error::{TransactionServiceError, TransactionServiceProtocolError}, handle::TransactionEvent, service::TransactionServiceResources, - storage::{ - database::TransactionBackend, - models::{CompletedTransaction, TransactionStatus}, - }, + storage::{database::TransactionBackend, models::CompletedTransaction}, }, }; use futures::FutureExt; @@ -40,7 +36,10 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use tari_common_types::types::Signature; +use tari_common_types::{ + transaction::{TransactionStatus, TxId}, + types::Signature, +}; use tari_core::{ base_node::{ proto::wallet_rpc::{TxLocation, TxQueryResponse, TxSubmissionRejectionReason, TxSubmissionResponse}, diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs index fe3ac75d504..8709acb120b 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs @@ -20,23 +20,21 @@ // 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 crate::{ - output_manager_service::TxId, - transaction_service::{ - error::{TransactionServiceError, TransactionServiceProtocolError}, - handle::TransactionEvent, - service::TransactionServiceResources, - storage::{ - database::TransactionBackend, - models::{CompletedTransaction, InboundTransaction, TransactionDirection, TransactionStatus}, - }, - tasks::send_transaction_reply::send_transaction_reply, +use crate::transaction_service::{ + error::{TransactionServiceError, TransactionServiceProtocolError}, + handle::TransactionEvent, + service::TransactionServiceResources, + storage::{ + database::TransactionBackend, + models::{CompletedTransaction, InboundTransaction}, }, + tasks::send_transaction_reply::send_transaction_reply, }; use chrono::Utc; use futures::future::FutureExt; use log::*; use std::sync::Arc; +use tari_common_types::transaction::{TransactionDirection, TransactionStatus, TxId}; use tari_comms::types::CommsPublicKey; use tokio::sync::{mpsc, oneshot}; @@ -242,12 +240,7 @@ where .naive_utc() .signed_duration_since(inbound_tx.timestamp) .to_std() - .map_err(|_| { - TransactionServiceProtocolError::new( - self.id, - TransactionServiceError::ConversionError("duration::OutOfRangeError".to_string()), - ) - })?; + .map_err(|e| TransactionServiceProtocolError::new(self.id, e.into()))?; let timeout_duration = match self .resources @@ -272,12 +265,7 @@ where .naive_utc() .signed_duration_since(timestamp) .to_std() - .map_err(|_| { - TransactionServiceProtocolError::new( - self.id, - TransactionServiceError::ConversionError("duration::OutOfRangeError".to_string()), - ) - })?; + .map_err(|e| TransactionServiceProtocolError::new(self.id, e.into()))?; elapsed_time > self.resources.config.transaction_resend_period }, }; diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs index c4e3db99bc9..87036b199c4 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs @@ -29,7 +29,7 @@ use crate::{ service::TransactionServiceResources, storage::{ database::TransactionBackend, - models::{CompletedTransaction, OutboundTransaction, TransactionDirection, TransactionStatus}, + models::{CompletedTransaction, OutboundTransaction}, }, tasks::{ send_finalized_transaction::send_finalized_transaction_message, @@ -42,6 +42,7 @@ use chrono::Utc; use futures::FutureExt; use log::*; use std::sync::Arc; +use tari_common_types::transaction::{TransactionDirection, TransactionStatus}; use tari_comms::{peer_manager::NodeId, types::CommsPublicKey}; use tari_comms_dht::{ domain_message::OutboundDomainMessage, @@ -328,12 +329,7 @@ where .naive_utc() .signed_duration_since(outbound_tx.timestamp) .to_std() - .map_err(|_| { - TransactionServiceProtocolError::new( - self.id, - TransactionServiceError::ConversionError("duration::OutOfRangeError".to_string()), - ) - })?; + .map_err(|e| TransactionServiceProtocolError::new(self.id, e.into()))?; let timeout_duration = match self .resources @@ -358,12 +354,7 @@ where .naive_utc() .signed_duration_since(timestamp) .to_std() - .map_err(|_| { - TransactionServiceProtocolError::new( - self.id, - TransactionServiceError::ConversionError("duration::OutOfRangeError".to_string()), - ) - })?; + .map_err(|e| TransactionServiceProtocolError::new(self.id, e.into()))?; elapsed_time > self.resources.config.transaction_resend_period }, }; @@ -374,7 +365,7 @@ where outbound_tx .sender_protocol .get_single_round_message() - .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?, + .map_err(|e| TransactionServiceProtocolError::new(self.id, e.into()))?, ) .await { @@ -387,7 +378,7 @@ where .db .increment_send_count(self.id) .await - .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; + .map_err(|e| TransactionServiceProtocolError::new(self.id, e.into()))?; } let mut shutdown = self.resources.shutdown_signal.clone(); diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs index 3f09efa2a3d..fff77520b0c 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs @@ -29,7 +29,7 @@ use crate::{ handle::{TransactionEvent, TransactionEventSender}, storage::{ database::{TransactionBackend, TransactionDatabase}, - models::{CompletedTransaction, TransactionStatus}, + models::CompletedTransaction, }, }, }; @@ -39,7 +39,7 @@ use std::{ convert::{TryFrom, TryInto}, sync::Arc, }; -use tari_common_types::types::BlockHash; +use tari_common_types::{transaction::TransactionStatus, types::BlockHash}; use tari_comms::protocol::rpc::{RpcError::RequestFailed, RpcStatusCode::NotFound}; use tari_core::{ base_node::{ @@ -271,14 +271,6 @@ where }) .await?; - if !batch_response.is_synced { - info!( - target: LOG_TARGET, - "Base Node reports not being synced, aborting transaction validation" - ); - return Err(TransactionServiceError::BaseNodeNotSynced); - } - for response_proto in batch_response.responses { let response = TxQueryBatchResponse::try_from(response_proto) .map_err(TransactionServiceError::ProtobufConversionError)?; diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 9ff5169477f..cca61d66e9a 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -23,7 +23,7 @@ use crate::{ base_node_service::handle::{BaseNodeEvent, BaseNodeServiceHandle}, connectivity_service::WalletConnectivityInterface, - output_manager_service::{handle::OutputManagerHandle, TxId}, + output_manager_service::handle::OutputManagerHandle, storage::database::{WalletBackend, WalletDatabase}, transaction_service::{ config::TransactionServiceConfig, @@ -37,7 +37,7 @@ use crate::{ }, storage::{ database::{TransactionBackend, TransactionDatabase}, - models::{CompletedTransaction, TransactionDirection, TransactionStatus}, + models::CompletedTransaction, }, tasks::{ send_finalized_transaction::send_finalized_transaction_message, @@ -60,7 +60,10 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use tari_common_types::types::PrivateKey; +use tari_common_types::{ + transaction::{TransactionDirection, TransactionStatus, TxId}, + types::PrivateKey, +}; use tari_comms::{peer_manager::NodeIdentity, types::CommsPublicKey}; use tari_comms_dht::outbound::OutboundMessageRequester; use tari_core::{ @@ -1258,13 +1261,7 @@ where } // Check if the last reply is beyond the resend cooldown if let Some(timestamp) = inbound_tx.last_send_timestamp { - let elapsed_time = Utc::now() - .naive_utc() - .signed_duration_since(timestamp) - .to_std() - .map_err(|_| { - TransactionServiceError::ConversionError("duration::OutOfRangeError".to_string()) - })?; + let elapsed_time = Utc::now().naive_utc().signed_duration_since(timestamp).to_std()?; if elapsed_time < self.resources.config.resend_response_cooldown { trace!( target: LOG_TARGET, diff --git a/base_layer/wallet/src/transaction_service/storage/database.rs b/base_layer/wallet/src/transaction_service/storage/database.rs index 93574b9965b..52e2f512929 100644 --- a/base_layer/wallet/src/transaction_service/storage/database.rs +++ b/base_layer/wallet/src/transaction_service/storage/database.rs @@ -20,18 +20,9 @@ // 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 crate::{ - output_manager_service::TxId, - transaction_service::{ - error::TransactionStorageError, - storage::models::{ - CompletedTransaction, - InboundTransaction, - OutboundTransaction, - TransactionDirection, - TransactionStatus, - }, - }, +use crate::transaction_service::{ + error::TransactionStorageError, + storage::models::{CompletedTransaction, InboundTransaction, OutboundTransaction}, }; use aes_gcm::Aes256Gcm; use chrono::Utc; @@ -44,7 +35,10 @@ use std::{ fmt::{Display, Error, Formatter}, sync::Arc, }; -use tari_common_types::types::{BlindingFactor, BlockHash}; +use tari_common_types::{ + transaction::{TransactionDirection, TransactionStatus, TxId}, + types::{BlindingFactor, BlockHash}, +}; use tari_comms::types::CommsPublicKey; use tari_core::transactions::{tari_amount::MicroTari, transaction::Transaction}; diff --git a/base_layer/wallet/src/transaction_service/storage/models.rs b/base_layer/wallet/src/transaction_service/storage/models.rs index c3fbd274b69..2125081a0b9 100644 --- a/base_layer/wallet/src/transaction_service/storage/models.rs +++ b/base_layer/wallet/src/transaction_service/storage/models.rs @@ -20,14 +20,12 @@ // 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 crate::{output_manager_service::TxId, transaction_service::error::TransactionStorageError}; use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; -use std::{ - convert::TryFrom, - fmt::{Display, Error, Formatter}, +use tari_common_types::{ + transaction::{TransactionDirection, TransactionStatus, TxId}, + types::{BlockHash, PrivateKey}, }; -use tari_common_types::types::{BlockHash, PrivateKey}; use tari_comms::types::CommsPublicKey; use tari_core::transactions::{ tari_amount::MicroTari, @@ -36,65 +34,6 @@ use tari_core::transactions::{ SenderTransactionProtocol, }; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum TransactionStatus { - /// This transaction has been completed between the parties but has not been broadcast to the base layer network. - Completed, - /// This transaction has been broadcast to the base layer network and is currently in one or more base node - /// mempools. - Broadcast, - /// This transaction has been mined and included in a block. - MinedUnconfirmed, - /// This transaction was generated as part of importing a spendable UTXO - Imported, - /// This transaction is still being negotiated by the parties - Pending, - /// This is a created Coinbase Transaction - Coinbase, - /// This transaction is mined and confirmed at the current base node's height - MinedConfirmed, -} - -impl TryFrom for TransactionStatus { - type Error = TransactionStorageError; - - fn try_from(value: i32) -> Result { - match value { - 0 => Ok(TransactionStatus::Completed), - 1 => Ok(TransactionStatus::Broadcast), - 2 => Ok(TransactionStatus::MinedUnconfirmed), - 3 => Ok(TransactionStatus::Imported), - 4 => Ok(TransactionStatus::Pending), - 5 => Ok(TransactionStatus::Coinbase), - 6 => Ok(TransactionStatus::MinedConfirmed), - _ => Err(TransactionStorageError::ConversionError( - "Invalid TransactionStatus".to_string(), - )), - } - } -} - -impl Default for TransactionStatus { - fn default() -> Self { - TransactionStatus::Pending - } -} - -impl Display for TransactionStatus { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - // No struct or tuple variants - match self { - TransactionStatus::Completed => write!(f, "Completed"), - TransactionStatus::Broadcast => write!(f, "Broadcast"), - TransactionStatus::MinedUnconfirmed => write!(f, "Mined Unconfirmed"), - TransactionStatus::MinedConfirmed => write!(f, "Mined Confirmed"), - TransactionStatus::Imported => write!(f, "Imported"), - TransactionStatus::Pending => write!(f, "Pending"), - TransactionStatus::Coinbase => write!(f, "Coinbase"), - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct InboundTransaction { pub tx_id: TxId, @@ -246,39 +185,6 @@ impl CompletedTransaction { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum TransactionDirection { - Inbound, - Outbound, - Unknown, -} - -impl TryFrom for TransactionDirection { - type Error = TransactionStorageError; - - fn try_from(value: i32) -> Result { - match value { - 0 => Ok(TransactionDirection::Inbound), - 1 => Ok(TransactionDirection::Outbound), - 2 => Ok(TransactionDirection::Unknown), - _ => Err(TransactionStorageError::ConversionError( - "Invalid TransactionDirection".to_string(), - )), - } - } -} - -impl Display for TransactionDirection { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - // No struct or tuple variants - match self { - TransactionDirection::Inbound => write!(f, "Inbound"), - TransactionDirection::Outbound => write!(f, "Outbound"), - TransactionDirection::Unknown => write!(f, "Unknown"), - } - } -} - impl From for InboundTransaction { fn from(ct: CompletedTransaction) -> Self { Self { diff --git a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs index 41fd022d7b4..83c30f0c046 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -21,21 +21,13 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - output_manager_service::TxId, schema::{completed_transactions, inbound_transactions, outbound_transactions}, storage::sqlite_utilities::WalletDbConnection, transaction_service::{ - error::TransactionStorageError, + error::{TransactionKeyError, TransactionStorageError}, storage::{ database::{DbKey, DbKeyValuePair, DbValue, TransactionBackend, WriteOperation}, - models::{ - CompletedTransaction, - InboundTransaction, - OutboundTransaction, - TransactionDirection, - TransactionStatus, - WalletTransaction, - }, + models::{CompletedTransaction, InboundTransaction, OutboundTransaction, WalletTransaction}, }, }, util::{ @@ -53,13 +45,24 @@ use std::{ str::from_utf8, sync::{Arc, MutexGuard, RwLock}, }; -use tari_common_types::types::{BlockHash, PublicKey}; +use tari_common_types::{ + transaction::{ + TransactionConversionError, + TransactionDirection, + TransactionDirectionError, + TransactionStatus, + TxId, + }, + types::{BlockHash, PublicKey}, +}; use tari_comms::types::CommsPublicKey; use tari_core::transactions::tari_amount::MicroTari; use tari_crypto::tari_utilities::{ hex::{from_hex, Hex}, ByteArray, }; +use thiserror::Error; +use tokio::time::Instant; const LOG_TARGET: &str = "wallet::transaction_service::database::sqlite_db"; @@ -217,7 +220,9 @@ impl TransactionServiceSqliteDatabase { impl TransactionBackend for TransactionServiceSqliteDatabase { fn fetch(&self, key: &DbKey) -> Result, TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let result = match key { DbKey::PendingOutboundTransaction(t) => { @@ -370,12 +375,22 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { } }, }; + trace!( + target: LOG_TARGET, + "sqlite profile - fetch '{}': lock {} + db_op {} = {} ms", + key, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(result) } fn contains(&self, key: &DbKey) -> Result { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let result = match key { DbKey::PendingOutboundTransaction(k) => { @@ -403,43 +418,94 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { OutboundTransactionSql::find(*k, &(*conn)).is_ok() }, }; + trace!( + target: LOG_TARGET, + "sqlite profile - contains '{}': lock {} + db_op {} = {} ms", + key, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(result) } fn write(&self, op: WriteOperation) -> Result, TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); + let key_text; - match op { - WriteOperation::Insert(kvp) => self.insert(kvp, conn).map(|_| None), - WriteOperation::Remove(key) => self.remove(key, conn), - } + let result = match op { + WriteOperation::Insert(kvp) => { + key_text = "Insert"; + self.insert(kvp, conn).map(|_| None) + }, + WriteOperation::Remove(key) => { + key_text = "Remove"; + self.remove(key, conn) + }, + }; + trace!( + target: LOG_TARGET, + "sqlite profile - write '{}': lock {} + db_op {} = {} ms", + key_text, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + + result } fn transaction_exists(&self, tx_id: u64) -> Result { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); - - Ok( - OutboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok() || - InboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok() || - CompletedTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok(), - ) + let acquire_lock = start.elapsed(); + + let result = OutboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok() || + InboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok() || + CompletedTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok(); + trace!( + target: LOG_TARGET, + "sqlite profile - transaction_exists: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + Ok(result) } fn get_pending_transaction_counterparty_pub_key_by_tx_id( &self, tx_id: u64, ) -> Result { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); if let Ok(mut outbound_tx_sql) = OutboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { self.decrypt_if_necessary(&mut outbound_tx_sql)?; let outbound_tx = OutboundTransaction::try_from(outbound_tx_sql)?; + trace!( + target: LOG_TARGET, + "sqlite profile - get_pending_transaction_counterparty_pub_key_by_tx_id: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); return Ok(outbound_tx.destination_public_key); } if let Ok(mut inbound_tx_sql) = InboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { self.decrypt_if_necessary(&mut inbound_tx_sql)?; let inbound_tx = InboundTransaction::try_from(inbound_tx_sql)?; + trace!( + target: LOG_TARGET, + "sqlite profile - get_pending_transaction_counterparty_pub_key_by_tx_id: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); return Ok(inbound_tx.source_public_key); } @@ -451,7 +517,9 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { tx_id: u64, completed_transaction: CompletedTransaction, ) -> Result<(), TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); if CompletedTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok() { return Err(TransactionStorageError::TransactionAlreadyExists); @@ -471,6 +539,13 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), }; + trace!( + target: LOG_TARGET, + "sqlite profile - complete_outbound_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } @@ -479,7 +554,9 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { tx_id: u64, completed_transaction: CompletedTransaction, ) -> Result<(), TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); if CompletedTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok() { return Err(TransactionStorageError::TransactionAlreadyExists); @@ -499,11 +576,20 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), }; + trace!( + target: LOG_TARGET, + "sqlite profile - complete_inbound_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn broadcast_completed_transaction(&self, tx_id: u64) -> Result<(), TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); match CompletedTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { Ok(v) => { @@ -524,11 +610,20 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), }; + trace!( + target: LOG_TARGET, + "sqlite profile - broadcast_completed_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn cancel_completed_transaction(&self, tx_id: u64) -> Result<(), TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); match CompletedTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { Ok(v) => { v.cancel(&(*conn))?; @@ -540,6 +635,13 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), }; + trace!( + target: LOG_TARGET, + "sqlite profile - cancel_completed_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } @@ -548,7 +650,9 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { tx_id: u64, cancelled: bool, ) -> Result<(), TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); match InboundTransactionSql::find(tx_id, &(*conn)) { Ok(v) => { v.set_cancelled(cancelled, &(*conn))?; @@ -565,11 +669,20 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }; }, }; + trace!( + target: LOG_TARGET, + "sqlite profile - set_pending_transaction_cancellation_status: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn mark_direct_send_success(&self, tx_id: u64) -> Result<(), TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); match InboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { Ok(v) => { v.update( @@ -604,6 +717,13 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }; }, }; + trace!( + target: LOG_TARGET, + "sqlite profile - mark_direct_send_success: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } @@ -614,7 +734,9 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { return Err(TransactionStorageError::AlreadyEncrypted); } + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let mut inbound_txs = InboundTransactionSql::index(&conn)?; // If the db is already encrypted then the very first output we try to encrypt will fail. @@ -665,6 +787,13 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { } (*current_cipher) = Some(cipher); + trace!( + target: LOG_TARGET, + "sqlite profile - apply_encryption: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } @@ -677,7 +806,9 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { } else { return Ok(()); }; + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let mut inbound_txs = InboundTransactionSql::index(&conn)?; @@ -704,17 +835,33 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { // Now that all the decryption has been completed we can safely remove the cipher fully let _ = (*current_cipher).take(); + trace!( + target: LOG_TARGET, + "sqlite profile - remove_encryption: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn cancel_coinbase_transaction_at_block_height(&self, block_height: u64) -> Result<(), TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let coinbase_txs = CompletedTransactionSql::index_coinbase_at_block_height(block_height as i64, &conn)?; for c in coinbase_txs.iter() { c.cancel(&conn)?; } + trace!( + target: LOG_TARGET, + "sqlite profile - cancel_coinbase_transaction_at_block_height: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } @@ -724,25 +871,33 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { block_height: u64, amount: MicroTari, ) -> Result, TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let mut coinbase_txs = CompletedTransactionSql::index_coinbase_at_block_height(block_height as i64, &conn)?; for c in coinbase_txs.iter_mut() { self.decrypt_if_necessary(c)?; - let completed_tx = CompletedTransaction::try_from(c.clone()).map_err(|_| { - TransactionStorageError::ConversionError("Error converting to CompletedTransaction".to_string()) - })?; - + let completed_tx = CompletedTransaction::try_from(c.clone())?; if completed_tx.amount == amount { return Ok(Some(completed_tx)); } } + trace!( + target: LOG_TARGET, + "sqlite profile - find_coinbase_transaction_at_block_height: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(None) } fn increment_send_count(&self, tx_id: u64) -> Result<(), TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); if let Ok(tx) = CompletedTransactionSql::find(tx_id, &conn) { let update = UpdateCompletedTransactionSql { @@ -772,6 +927,13 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { } else { return Err(TransactionStorageError::ValuesNotFound); } + trace!( + target: LOG_TARGET, + "sqlite profile - increment_send_count: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } @@ -785,7 +947,9 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { num_confirmations: u64, is_confirmed: bool, ) -> Result<(), TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); match CompletedTransactionSql::find(tx_id, &(*conn)) { Ok(v) => { v.update_mined_height( @@ -804,27 +968,46 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), }; + trace!( + target: LOG_TARGET, + "sqlite profile - update_mined_height: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } fn fetch_last_mined_transaction(&self) -> Result, TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let tx = completed_transactions::table .filter(completed_transactions::mined_height.is_not_null()) .order_by(completed_transactions::mined_height.desc()) .first::(&*conn) .optional()?; - Ok(match tx { + let result = match tx { Some(mut tx) => { self.decrypt_if_necessary(&mut tx)?; Some(tx.try_into()?) }, None => None, - }) + }; + trace!( + target: LOG_TARGET, + "sqlite profile - fetch_last_mined_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + Ok(result) } fn fetch_unconfirmed_transactions(&self) -> Result, TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); let txs = completed_transactions::table .filter( completed_transactions::mined_height @@ -840,12 +1023,21 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { self.decrypt_if_necessary(&mut tx)?; result.push(tx.try_into()?); } + trace!( + target: LOG_TARGET, + "sqlite profile - fetch_unconfirmed_transactions: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(result) } fn set_transaction_as_unmined(&self, tx_id: u64) -> Result<(), TransactionStorageError> { + let start = Instant::now(); let conn = self.database_connection.acquire_lock(); + let acquire_lock = start.elapsed(); match CompletedTransactionSql::find(tx_id, &(*conn)) { Ok(v) => { v.set_as_unmined(&(*conn))?; @@ -857,6 +1049,13 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), }; + trace!( + target: LOG_TARGET, + "sqlite profile - set_transaction_as_unmined: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); Ok(()) } } @@ -1016,8 +1215,7 @@ impl TryFrom for InboundTransaction { fn try_from(i: InboundTransactionSql) -> Result { Ok(Self { tx_id: i.tx_id as u64, - source_public_key: PublicKey::from_vec(&i.source_public_key) - .map_err(|_| TransactionStorageError::ConversionError("Invalid Source Publickey".to_string()))?, + source_public_key: PublicKey::from_vec(&i.source_public_key).map_err(TransactionKeyError::Source)?, amount: MicroTari::from(i.amount as u64), receiver_protocol: serde_json::from_str(&i.receiver_protocol)?, status: TransactionStatus::Pending, @@ -1189,7 +1387,7 @@ impl TryFrom for OutboundTransaction { Ok(Self { tx_id: o.tx_id as u64, destination_public_key: PublicKey::from_vec(&o.destination_public_key) - .map_err(|_| TransactionStorageError::ConversionError("Invalid destination PublicKey".to_string()))?, + .map_err(TransactionKeyError::Destination)?, amount: MicroTari::from(o.amount as u64), fee: MicroTari::from(o.fee as u64), sender_protocol: serde_json::from_str(&o.sender_protocol)?, @@ -1443,16 +1641,27 @@ impl TryFrom for CompletedTransactionSql { } } +#[derive(Debug, Error)] +pub enum CompletedTransactionConversionError { + #[error("CompletedTransaction conversion failed by wrong direction: {0}")] + DirectionError(#[from] TransactionDirectionError), + #[error("CompletedTransaction conversion failed with transaction conversion: {0}")] + ConversionError(#[from] TransactionConversionError), + #[error("CompletedTransaction conversion failed with json error: {0}")] + JsonError(#[from] serde_json::Error), + #[error("CompletedTransaction conversion failed with key error: {0}")] + KeyError(#[from] TransactionKeyError), +} + impl TryFrom for CompletedTransaction { - type Error = TransactionStorageError; + type Error = CompletedTransactionConversionError; fn try_from(c: CompletedTransactionSql) -> Result { Ok(Self { tx_id: c.tx_id as u64, - source_public_key: PublicKey::from_vec(&c.source_public_key) - .map_err(|_| TransactionStorageError::ConversionError("Invalid source Publickey".to_string()))?, + source_public_key: PublicKey::from_vec(&c.source_public_key).map_err(TransactionKeyError::Source)?, destination_public_key: PublicKey::from_vec(&c.destination_public_key) - .map_err(|_| TransactionStorageError::ConversionError("Invalid destination PublicKey".to_string()))?, + .map_err(TransactionKeyError::Destination)?, amount: MicroTari::from(c.amount as u64), fee: MicroTari::from(c.fee as u64), transaction: serde_json::from_str(&c.transaction_protocol)?, @@ -1506,7 +1715,10 @@ mod test { }; use tempfile::tempdir; - use tari_common_types::types::{HashDigest, PrivateKey, PublicKey}; + use tari_common_types::{ + transaction::{TransactionDirection, TransactionStatus}, + types::{HashDigest, PrivateKey, PublicKey}, + }; use tari_core::transactions::{ helpers::{create_unblinded_output, TestParams}, tari_amount::MicroTari, @@ -1522,13 +1734,7 @@ mod test { storage::sqlite_utilities::WalletDbConnection, transaction_service::storage::{ database::{DbKey, TransactionBackend}, - models::{ - CompletedTransaction, - InboundTransaction, - OutboundTransaction, - TransactionDirection, - TransactionStatus, - }, + models::{CompletedTransaction, InboundTransaction, OutboundTransaction}, sqlite_db::{ CompletedTransactionSql, InboundTransactionSql, diff --git a/base_layer/wallet/src/transaction_service/tasks/send_finalized_transaction.rs b/base_layer/wallet/src/transaction_service/tasks/send_finalized_transaction.rs index db28a7db584..cf723bbfad2 100644 --- a/base_layer/wallet/src/transaction_service/tasks/send_finalized_transaction.rs +++ b/base_layer/wallet/src/transaction_service/tasks/send_finalized_transaction.rs @@ -19,16 +19,14 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // 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 crate::{ - output_manager_service::TxId, - transaction_service::{ - config::TransactionRoutingMechanism, - error::TransactionServiceError, - tasks::wait_on_dial::wait_on_dial, - }, +use crate::transaction_service::{ + config::TransactionRoutingMechanism, + error::TransactionServiceError, + tasks::wait_on_dial::wait_on_dial, }; use log::*; use std::time::Duration; +use tari_common_types::transaction::TxId; use tari_comms::{peer_manager::NodeId, types::CommsPublicKey}; use tari_comms_dht::{ domain_message::OutboundDomainMessage, diff --git a/base_layer/wallet/src/transaction_service/tasks/send_transaction_cancelled.rs b/base_layer/wallet/src/transaction_service/tasks/send_transaction_cancelled.rs index 326093d9869..f7049db69aa 100644 --- a/base_layer/wallet/src/transaction_service/tasks/send_transaction_cancelled.rs +++ b/base_layer/wallet/src/transaction_service/tasks/send_transaction_cancelled.rs @@ -19,7 +19,8 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // 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 crate::{output_manager_service::TxId, transaction_service::error::TransactionServiceError}; +use crate::transaction_service::error::TransactionServiceError; +use tari_common_types::transaction::TxId; use tari_comms::{peer_manager::NodeId, types::CommsPublicKey}; use tari_comms_dht::{ domain_message::OutboundDomainMessage, diff --git a/base_layer/wallet/src/transaction_service/tasks/send_transaction_reply.rs b/base_layer/wallet/src/transaction_service/tasks/send_transaction_reply.rs index fb9a44b5d27..adc26c4ab23 100644 --- a/base_layer/wallet/src/transaction_service/tasks/send_transaction_reply.rs +++ b/base_layer/wallet/src/transaction_service/tasks/send_transaction_reply.rs @@ -20,7 +20,6 @@ // 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 crate::output_manager_service::TxId; use log::*; use tari_comms_dht::{domain_message::OutboundDomainMessage, outbound::SendMessageResponse}; use tari_p2p::tari_message::TariMessageType; @@ -32,6 +31,7 @@ use crate::transaction_service::{ tasks::wait_on_dial::wait_on_dial, }; use std::time::Duration; +use tari_common_types::transaction::TxId; use tari_comms::{peer_manager::NodeId, types::CommsPublicKey}; use tari_comms_dht::outbound::{OutboundEncryption, OutboundMessageRequester}; use tari_core::transactions::transaction_protocol::proto; diff --git a/base_layer/wallet/src/transaction_service/tasks/wait_on_dial.rs b/base_layer/wallet/src/transaction_service/tasks/wait_on_dial.rs index d9c6328dbbb..9d881701e47 100644 --- a/base_layer/wallet/src/transaction_service/tasks/wait_on_dial.rs +++ b/base_layer/wallet/src/transaction_service/tasks/wait_on_dial.rs @@ -20,9 +20,9 @@ // 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 crate::output_manager_service::TxId; use log::*; use std::time::Duration; +use tari_common_types::transaction::TxId; use tari_comms::types::CommsPublicKey; use tari_comms_dht::outbound::MessageSendStates; diff --git a/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs b/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs index c12d1bdcce1..d5afe96fd10 100644 --- a/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs +++ b/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs @@ -35,7 +35,7 @@ use log::*; use serde::{Deserialize, Serialize}; use tokio::{sync::broadcast, task, time}; -use tari_common_types::types::HashOutput; +use tari_common_types::{transaction::TxId, types::HashOutput}; use tari_comms::{ peer_manager::NodeId, protocol::rpc::{RpcError, RpcStatus}, @@ -61,7 +61,7 @@ use tari_shutdown::ShutdownSignal; use crate::{ connectivity_service::WalletConnectivityInterface, error::WalletError, - output_manager_service::{handle::OutputManagerHandle, TxId}, + output_manager_service::handle::OutputManagerHandle, storage::{ database::{WalletBackend, WalletDatabase}, sqlite_db::WalletSqliteDatabase, diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 01c55ab3e61..ab34f91a9dd 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -76,7 +76,6 @@ use crate::{ handle::OutputManagerHandle, storage::{database::OutputManagerBackend, models::KnownOneSidedPaymentScript}, OutputManagerServiceInitializer, - TxId, }, storage::database::{WalletBackend, WalletDatabase}, transaction_service::{ @@ -87,6 +86,7 @@ use crate::{ types::KeyDigest, utxo_scanner_service::{handle::UtxoScannerHandle, UtxoScannerServiceInitializer}, }; +use tari_common_types::transaction::TxId; const LOG_TARGET: &str = "wallet"; diff --git a/base_layer/wallet/tests/output_manager_service/service.rs b/base_layer/wallet/tests/output_manager_service/service.rs index 46f781d45cd..4fec5916c50 100644 --- a/base_layer/wallet/tests/output_manager_service/service.rs +++ b/base_layer/wallet/tests/output_manager_service/service.rs @@ -26,7 +26,10 @@ use crate::support::{ }; use rand::{rngs::OsRng, RngCore}; use std::{collections::HashMap, sync::Arc, time::Duration}; -use tari_common_types::types::{PrivateKey, PublicKey}; +use tari_common_types::{ + transaction::TxId, + types::{PrivateKey, PublicKey}, +}; use tari_comms::{ peer_manager::{NodeIdentity, PeerFeatures}, protocol::rpc::{mock::MockRpcServer, NamedProtocolService}, @@ -75,7 +78,6 @@ use tari_wallet::{ database::{OutputManagerBackend, OutputManagerDatabase}, sqlite_db::OutputManagerSqliteDatabase, }, - TxId, }, transaction_service::handle::TransactionServiceHandle, }; diff --git a/base_layer/wallet/tests/transaction_service/service.rs b/base_layer/wallet/tests/transaction_service/service.rs index b7cff07ba99..64b82541891 100644 --- a/base_layer/wallet/tests/transaction_service/service.rs +++ b/base_layer/wallet/tests/transaction_service/service.rs @@ -42,6 +42,7 @@ use std::{ }; use tari_common_types::{ chain_metadata::ChainMetadata, + transaction::{TransactionDirection, TransactionStatus}, types::{PrivateKey, PublicKey, Signature}, }; use tari_comms::{ @@ -137,13 +138,7 @@ use tari_wallet::{ service::TransactionService, storage::{ database::{DbKeyValuePair, TransactionBackend, TransactionDatabase, WriteOperation}, - models::{ - CompletedTransaction, - InboundTransaction, - OutboundTransaction, - TransactionDirection, - TransactionStatus, - }, + models::{CompletedTransaction, InboundTransaction, OutboundTransaction}, sqlite_db::TransactionServiceSqliteDatabase, }, TransactionServiceInitializer, diff --git a/base_layer/wallet/tests/transaction_service/storage.rs b/base_layer/wallet/tests/transaction_service/storage.rs index 662c265eb7c..b0262ef810d 100644 --- a/base_layer/wallet/tests/transaction_service/storage.rs +++ b/base_layer/wallet/tests/transaction_service/storage.rs @@ -34,7 +34,10 @@ use tari_crypto::{ use tempfile::tempdir; use tokio::runtime::Runtime; -use tari_common_types::types::{HashDigest, PrivateKey, PublicKey}; +use tari_common_types::{ + transaction::{TransactionDirection, TransactionStatus}, + types::{HashDigest, PrivateKey, PublicKey}, +}; use tari_core::transactions::{ helpers::{create_unblinded_output, TestParams}, tari_amount::{uT, MicroTari}, @@ -49,14 +52,7 @@ use tari_wallet::{ storage::sqlite_utilities::run_migration_and_create_sqlite_connection, transaction_service::storage::{ database::{TransactionBackend, TransactionDatabase}, - models::{ - CompletedTransaction, - InboundTransaction, - OutboundTransaction, - TransactionDirection, - TransactionStatus, - WalletTransaction, - }, + models::{CompletedTransaction, InboundTransaction, OutboundTransaction, WalletTransaction}, sqlite_db::TransactionServiceSqliteDatabase, }, }; diff --git a/base_layer/wallet/tests/transaction_service/transaction_protocols.rs b/base_layer/wallet/tests/transaction_service/transaction_protocols.rs index f8582a3b85c..cf6aca4ccc8 100644 --- a/base_layer/wallet/tests/transaction_service/transaction_protocols.rs +++ b/base_layer/wallet/tests/transaction_service/transaction_protocols.rs @@ -28,6 +28,7 @@ use chrono::Utc; use futures::StreamExt; use rand::rngs::OsRng; use std::{collections::HashMap, sync::Arc, time::Duration}; +use tari_common_types::transaction::{TransactionDirection, TransactionStatus, TxId}; use tari_comms::{ peer_manager::PeerFeatures, protocol::rpc::{mock::MockRpcServer, NamedProtocolService}, @@ -66,12 +67,11 @@ use tari_wallet::{ output_manager_service::{ error::OutputManagerError, handle::{OutputManagerHandle, OutputManagerRequest, OutputManagerResponse}, - TxId, }, storage::sqlite_utilities::run_migration_and_create_sqlite_connection, transaction_service::{ config::TransactionServiceConfig, - error::{TransactionServiceError, TransactionServiceProtocolError}, + error::TransactionServiceError, handle::{TransactionEvent, TransactionEventReceiver, TransactionEventSender}, protocols::{ transaction_broadcast_protocol::TransactionBroadcastProtocol, @@ -80,7 +80,7 @@ use tari_wallet::{ service::TransactionServiceResources, storage::{ database::TransactionDatabase, - models::{CompletedTransaction, TransactionDirection, TransactionStatus}, + models::CompletedTransaction, sqlite_db::TransactionServiceSqliteDatabase, }, }, @@ -781,33 +781,6 @@ async fn tx_validation_protocol_tx_becomes_mined_unconfirmed_then_confirmed() { rpc_service_state.set_transaction_query_batch_responses(batch_query_response.clone()); - rpc_service_state.set_is_synced(false); - - wallet_connectivity.notify_base_node_set(server_node_identity.to_peer()); - - let protocol = TransactionValidationProtocol::new( - 1, - resources.db.clone(), - wallet_connectivity.clone(), - resources.config.clone(), - resources.event_publisher.clone(), - resources.output_manager_service.clone(), - ); - - let join_handle = task::spawn(protocol.execute()); - - // Check that the protocol ends with error due to base node not being synced - let result = join_handle.await.unwrap(); - assert!(matches!( - result, - Err(TransactionServiceProtocolError { - id: 1, - error: TransactionServiceError::BaseNodeNotSynced, - }) - )); - - rpc_service_state.set_is_synced(true); - let protocol = TransactionValidationProtocol::new( 2, resources.db.clone(), diff --git a/base_layer/wallet_ffi/src/callback_handler.rs b/base_layer/wallet_ffi/src/callback_handler.rs index 42067d555a5..dff1e1c175b 100644 --- a/base_layer/wallet_ffi/src/callback_handler.rs +++ b/base_layer/wallet_ffi/src/callback_handler.rs @@ -49,14 +49,12 @@ //! and false that the process timed out and new one will be started use log::*; +use tari_common_types::transaction::TxId; use tari_comms::types::CommsPublicKey; use tari_comms_dht::event::{DhtEvent, DhtEventReceiver}; use tari_shutdown::ShutdownSignal; use tari_wallet::{ - output_manager_service::{ - handle::{OutputManagerEvent, OutputManagerEventReceiver}, - TxId, - }, + output_manager_service::handle::{OutputManagerEvent, OutputManagerEventReceiver}, transaction_service::{ handle::{TransactionEvent, TransactionEventReceiver}, storage::{ @@ -520,7 +518,10 @@ mod test { thread, time::Duration, }; - use tari_common_types::types::{BlindingFactor, PrivateKey, PublicKey}; + use tari_common_types::{ + transaction::{TransactionDirection, TransactionStatus}, + types::{BlindingFactor, PrivateKey, PublicKey}, + }; use tari_comms_dht::event::DhtEvent; use tari_core::transactions::{ tari_amount::{uT, MicroTari}, @@ -537,13 +538,7 @@ mod test { handle::TransactionEvent, storage::{ database::TransactionDatabase, - models::{ - CompletedTransaction, - InboundTransaction, - OutboundTransaction, - TransactionDirection, - TransactionStatus, - }, + models::{CompletedTransaction, InboundTransaction, OutboundTransaction}, sqlite_db::TransactionServiceSqliteDatabase, }, }, diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 571561e48c3..dd9c52c7c46 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -146,6 +146,7 @@ use tokio::runtime::Runtime; use error::LibWalletError; use tari_common_types::{ emoji::{emoji_set, EmojiId, EmojiIdError}, + transaction::{TransactionDirection, TransactionStatus}, types::{ComSignature, PublicKey}, }; use tari_comms::{ @@ -176,13 +177,7 @@ use tari_wallet::{ error::TransactionServiceError, storage::{ database::TransactionDatabase, - models::{ - CompletedTransaction, - InboundTransaction, - OutboundTransaction, - TransactionDirection, - TransactionStatus, - }, + models::{CompletedTransaction, InboundTransaction, OutboundTransaction}, }, }, utxo_scanner_service::utxo_scanning::{UtxoScannerService, RECOVERY_KEY}, @@ -5152,12 +5147,9 @@ mod test { use libc::{c_char, c_uchar, c_uint}; use tempfile::tempdir; - use tari_common_types::emoji; + use tari_common_types::{emoji, transaction::TransactionStatus}; use tari_test_utils::random; - use tari_wallet::{ - storage::sqlite_utilities::run_migration_and_create_sqlite_connection, - transaction_service::storage::models::TransactionStatus, - }; + use tari_wallet::storage::sqlite_utilities::run_migration_and_create_sqlite_connection; use crate::*; diff --git a/common/Cargo.toml b/common/Cargo.toml index 0aff89b6e06..9e9033bb12a 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -38,6 +38,7 @@ anyhow = { version = "1.0", optional = true } git2 = { version = "0.8", optional = true } prost-build = { version = "0.8.0", optional = true } toml = { version = "0.5", optional = true } +thiserror = "1.0.29" [dev-dependencies] tari_test_utils = { version = "^0.11", path = "../infrastructure/test_utils"} diff --git a/common/logging/log4rs_sample_mining_node.yml b/common/logging/log4rs_sample_mining_node.yml index 16c4c437393..c3e85184ecc 100644 --- a/common/logging/log4rs_sample_mining_node.yml +++ b/common/logging/log4rs_sample_mining_node.yml @@ -44,11 +44,14 @@ loggers: appenders: - mining_node additive: false - tari_mining_node: + tari_mining_node::logging: level: debug appenders: - mining_node + additive: false + tari_mining_node::miner: + level: info + appenders: - stdout additive: false - diff --git a/common/src/exit_codes.rs b/common/src/exit_codes.rs new file mode 100644 index 00000000000..ffd4790406b --- /dev/null +++ b/common/src/exit_codes.rs @@ -0,0 +1,93 @@ +use thiserror::Error; + +/// Enum to show failure information +#[derive(Debug, Clone, Error)] +pub enum ExitCodes { + #[error("There is an error in the wallet configuration: {0}")] + ConfigError(String), + #[error("The application exited because an unknown error occurred: {0}. Check the logs for more details.")] + UnknownError(String), + #[error("The application exited because an interface error occurred. Check the logs for details.")] + InterfaceError, + #[error("The application exited. {0}")] + WalletError(String), + #[error("The wallet was not able to start the GRPC server. {0}")] + GrpcError(String), + #[error("The application did not accept the command input: {0}")] + InputError(String), + #[error("Invalid command: {0}")] + CommandError(String), + #[error("IO error: {0}")] + IOError(String), + #[error("Recovery failed: {0}")] + RecoveryError(String), + #[error("The wallet exited because of an internal network error: {0}")] + NetworkError(String), + #[error("The wallet exited because it received a message it could not interpret: {0}")] + ConversionError(String), + #[error("Your password was incorrect.")] + IncorrectPassword, + #[error("Your application is encrypted but no password was provided.")] + NoPassword, + #[error("Tor connection is offline")] + TorOffline, + #[error("Database is in inconsistent state: {0}")] + DbInconsistentState(String), +} + +impl ExitCodes { + pub fn as_i32(&self) -> i32 { + match self { + Self::ConfigError(_) => 101, + Self::UnknownError(_) => 102, + Self::InterfaceError => 103, + Self::WalletError(_) => 104, + Self::GrpcError(_) => 105, + Self::InputError(_) => 106, + Self::CommandError(_) => 107, + Self::IOError(_) => 108, + Self::RecoveryError(_) => 109, + Self::NetworkError(_) => 110, + Self::ConversionError(_) => 111, + Self::IncorrectPassword | Self::NoPassword => 112, + Self::TorOffline => 113, + Self::DbInconsistentState(_) => 115, + } + } + + pub fn eprint_details(&self) { + use ExitCodes::*; + match self { + TorOffline => { + eprintln!("Unable to connect to the Tor control port."); + eprintln!( + "Please check that you have the Tor proxy running and that access to the Tor control port is \ + turned on.", + ); + eprintln!("If you are unsure of what to do, use the following command to start the Tor proxy:"); + eprintln!( + "tor --allow-missing-torrc --ignore-missing-torrc --clientonly 1 --socksport 9050 --controlport \ + 127.0.0.1:9051 --log \"notice stdout\" --clientuseipv6 1", + ); + }, + + e => { + eprintln!("{}", e); + }, + } + } +} + +impl From for ExitCodes { + fn from(err: super::ConfigError) -> Self { + // TODO: Move it out + // error!(target: LOG_TARGET, "{}", err); + Self::ConfigError(err.to_string()) + } +} + +impl ExitCodes { + pub fn grpc(err: M) -> Self { + ExitCodes::GrpcError(format!("GRPC connection error: {}", err)) + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index cb2c99d0c16..12a47d62fde 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -78,6 +78,7 @@ #[cfg(any(feature = "build", feature = "static-application-info"))] pub mod build; +pub mod exit_codes; #[macro_use] mod logging; diff --git a/comms/Cargo.toml b/comms/Cargo.toml index 60c3abb4100..b7015b8f1de 100644 --- a/comms/Cargo.toml +++ b/comms/Cargo.toml @@ -10,6 +10,7 @@ version = "0.11.0" edition = "2018" [dependencies] +tari_common = { path = "../common" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } tari_storage = { version = "^0.11", path = "../infrastructure/storage" } tari_shutdown = { version = "^0.11", path = "../infrastructure/shutdown" } diff --git a/comms/src/peer_manager/error.rs b/comms/src/peer_manager/error.rs index 07db078b4f4..5f5a493e316 100644 --- a/comms/src/peer_manager/error.rs +++ b/comms/src/peer_manager/error.rs @@ -21,6 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE use std::sync::PoisonError; +use tari_common::exit_codes::ExitCodes; use tari_storage::KeyValStoreError; use thiserror::Error; @@ -36,6 +37,12 @@ pub enum PeerManagerError { MigrationError(String), } +impl From for ExitCodes { + fn from(err: PeerManagerError) -> Self { + ExitCodes::NetworkError(err.to_string()) + } +} + impl PeerManagerError { /// Returns true if this error indicates that the peer is not found, otherwise false pub fn is_peer_not_found(&self) -> bool { diff --git a/integration_tests/cucumber.js b/integration_tests/cucumber.js index 988c7217a36..ed54e8a5068 100644 --- a/integration_tests/cucumber.js +++ b/integration_tests/cucumber.js @@ -1,4 +1,6 @@ module.exports = { - default: "", + default: + "--tags 'not @long-running and not @wallet-ffi and not @broken' --fail-fast", + ci: " ", critical: "--format @cucumber/pretty-formatter --tags @critical", }; diff --git a/integration_tests/features/Mempool.feature b/integration_tests/features/Mempool.feature index 5eab9f3c714..2365be2ae88 100644 --- a/integration_tests/features/Mempool.feature +++ b/integration_tests/features/Mempool.feature @@ -96,9 +96,8 @@ Feature: Mempool Then SENDER has TX1 in NOT_STORED state Then SENDER has TX2 in MINED state - @critical + @critical @flaky Scenario: Mempool clearing out invalid transactions after a reorg - Given I do not expect all automated transactions to succeed Given I have a seed node SEED_A And I have a base node NODE_A connected to seed SEED_A When I mine a block on NODE_A with coinbase CB_A diff --git a/integration_tests/features/Reorgs.feature b/integration_tests/features/Reorgs.feature index 399218ccbf3..34338bc6d24 100644 --- a/integration_tests/features/Reorgs.feature +++ b/integration_tests/features/Reorgs.feature @@ -96,7 +96,6 @@ Feature: Reorgs @critical @reorg Scenario: Zero-conf reorg with spending - Given I do not expect all automated transactions to succeed Given I have a base node NODE1 connected to all seed nodes Given I have a base node NODE2 connected to node NODE1 When I mine 14 blocks on NODE1 @@ -143,7 +142,6 @@ Feature: Reorgs # Chain 1a: # Mine X1 blocks (orphan_storage_capacity default set to 10) # - Given I do not expect all automated transactions to succeed Given I have a seed node SEED_A1 # Add multiple base nodes to ensure more robust comms And I have a base node NODE_A1 connected to seed SEED_A1 @@ -167,6 +165,8 @@ Feature: Reorgs And I connect node NODE_A1 to node NODE_A3 and wait 1 seconds And I connect node NODE_A2 to node NODE_A4 and wait 1 seconds And I connect node SEED_A1 to node SEED_A2 and wait seconds + Then node SEED_A1 is in state LISTENING + Then node SEED_A2 is in state LISTENING When I mine 10 blocks on SEED_A1 Then all nodes are on the same chain tip # @@ -197,6 +197,8 @@ Feature: Reorgs And I connect node NODE_B1 to node NODE_B3 and wait 1 seconds And I connect node NODE_B2 to node NODE_B4 and wait 1 seconds And I connect node SEED_B1 to node SEED_B2 and wait seconds + Then node SEED_B2 is in state LISTENING + Then node SEED_B1 is in state LISTENING When I mine 10 blocks on SEED_B1 Then node SEED_B2 is at the same height as node SEED_B1 Then node NODE_B1 is at the same height as node SEED_B1 @@ -209,6 +211,8 @@ Feature: Reorgs And I connect node NODE_A1 to node NODE_B1 and wait 1 seconds And I connect node NODE_A3 to node NODE_B3 and wait 1 seconds And I connect node SEED_A1 to node SEED_B1 and wait seconds + Then node SEED_A1 is in state LISTENING + Then node SEED_B1 is in state LISTENING When I mine 10 blocks on SEED_A1 Then all nodes are on the same chain tip diff --git a/integration_tests/features/StressTest.feature b/integration_tests/features/StressTest.feature index f01e0663b3b..559dc712bee 100644 --- a/integration_tests/features/StressTest.feature +++ b/integration_tests/features/StressTest.feature @@ -4,32 +4,35 @@ Feature: Stress Test Scenario Outline: Ramped Stress Test Given I have a seed node NODE1 And I have stress-test wallet WALLET_A connected to the seed node NODE1 with broadcast monitoring timeout - And I have a merge mining proxy PROXY connected to NODE1 and WALLET_A with default config + And I have mining node MINER connected to base node NODE1 and wallet WALLET_A # We mine some blocks before starting the other nodes to avoid a spinning sync state when all the nodes are at height 0 - When I merge mine 6 blocks via PROXY + When mining node MINER mines 6 blocks And I have a seed node NODE2 And I have base nodes connected to all seed nodes And I have stress-test wallet WALLET_B connected to the seed node NODE2 with broadcast monitoring timeout # There need to be at least as many mature coinbase UTXOs in the wallet coin splits required for the number of transactions - When I merge mine blocks via PROXY + When mining node MINER mines blocks Then all nodes are on the same chain tip When I wait for wallet WALLET_A to have at least 5100000000 uT Then I coin split tari in wallet WALLET_A to produce UTXOs of 5000 uT each with fee_per_gram 20 uT - When I merge mine 3 blocks via PROXY - When I merge mine blocks via PROXY + When I wait 30 seconds + When mining node MINER mines 3 blocks + When mining node MINER mines blocks Then all nodes are on the same chain tip Then wallet WALLET_A detects all transactions as Mined_Confirmed When I send transactions of 1111 uT each from wallet WALLET_A to wallet WALLET_B at fee_per_gram 20 # Mine enough blocks for the first block of transactions to be confirmed. - When I merge mine 4 blocks via PROXY + When mining node MINER mines 4 blocks Then all nodes are on the same chain tip # Now wait until all transactions are detected as confirmed in WALLET_A, continue to mine blocks if transactions # are not found to be confirmed as sometimes the previous mining occurs faster than transactions are submitted # to the mempool - Then while merge mining via PROXY all transactions in wallet WALLET_A are found to be Mined_Confirmed + Then while mining via SHA3 miner MINER all transactions in wallet WALLET_A are found to be Mined_Confirmed # Then wallet WALLET_B detects all transactions as Mined_Confirmed - Then while mining via NODE1 all transactions in wallet WALLET_B are found to be Mined_Confirmed + Then while mining via node NODE1 all transactions in wallet WALLET_B are found to be Mined_Confirmed + + @flaky @current Examples: | NumTransactions | NumCoinsplitsNeeded | NumNodes | MonitoringTimeout | | 10 | 1 | 3 | 10 | @@ -49,31 +52,31 @@ Feature: Stress Test Scenario: Simple Stress Test Given I have a seed node NODE1 And I have stress-test wallet WALLET_A connected to the seed node NODE1 with broadcast monitoring timeout 60 - And I have a merge mining proxy PROXY connected to NODE1 and WALLET_A with default config - When I merge mine 1 blocks via PROXY + And I have mining node MINER connected to base node NODE1 and wallet WALLET_A + When mining node MINER mines 1 blocks And I have a seed node NODE2 And I have 1 base nodes connected to all seed nodes And I have stress-test wallet WALLET_B connected to the seed node NODE2 with broadcast monitoring timeout 60 # We need to ensure the coinbase lock heights are reached; mine enough blocks # The following line is how you could mine directly on the node - When I merge mine 8 blocks via PROXY - Then all nodes are at current tip height + When mining node MINER mines 8 blocks + Then all nodes are on the same chain tip When I wait for wallet WALLET_A to have at least 15100000000 uT Then I coin split tari in wallet WALLET_A to produce 2000 UTXOs of 5000 uT each with fee_per_gram 20 uT # Make sure enough blocks are mined for the coin split transaction to be confirmed - When I merge mine 8 blocks via PROXY + When mining node MINER mines 8 blocks - Then all nodes are at current tip height + Then all nodes are on the same chain tip Then wallet WALLET_A detects all transactions as Mined_Confirmed When I send 2000 transactions of 1111 uT each from wallet WALLET_A to wallet WALLET_B at fee_per_gram 20 # Mine enough blocks for the first block of transactions to be confirmed. - When I merge mine 4 blocks via PROXY - Then all nodes are at current tip height + When mining node MINER mines 4 blocks + Then all nodes are on the same chain tip # Now wait until all transactions are detected as confirmed in WALLET_A, continue to mine blocks if transactions # are not found to be confirmed as sometimes the previous mining occurs faster than transactions are submitted # to the mempool - Then while merge mining via PROXY all transactions in wallet WALLET_A are found to be Mined_Confirmed + Then while mining via SHA3 miner MINER all transactions in wallet WALLET_A are found to be Mined_Confirmed # Then wallet WALLET_B detects all transactions as Mined_Confirmed - Then while mining via NODE1 all transactions in wallet WALLET_B are found to be Mined_Confirmed + Then while mining via node NODE1 all transactions in wallet WALLET_B are found to be Mined_Confirmed diff --git a/integration_tests/features/Sync.feature b/integration_tests/features/Sync.feature index 8627c76b9d1..224c868873b 100644 --- a/integration_tests/features/Sync.feature +++ b/integration_tests/features/Sync.feature @@ -75,7 +75,6 @@ Feature: Block Sync When I stop node NODE1 Given I have a base node NODE2 connected to node PNODE1 Given I have a pruned node PNODE2 connected to node PNODE1 with pruning horizon set to 5 - Given I do not expect all automated transactions to succeed When I mine 5 blocks on NODE2 Then node NODE2 is at height 5 Then node PNODE2 is at height 40 @@ -84,8 +83,8 @@ Feature: Block Sync And I connect node NODE2 to node NODE1 and wait 1 seconds # NODE2 may initially try to sync from PNODE1 and PNODE2, then eventually try to sync from NODE1; mining blocks # on NODE1 will make this test less flaky and force NODE2 to sync from NODE1 much quicker - Given I expect all automated transactions to succeed When I mine 10 blocks on NODE1 + Then all transactions must have succeeded Then all nodes are at height 50 Scenario Outline: Syncing node while also mining before tip sync @@ -102,20 +101,20 @@ Feature: Block Sync # Try to mine much faster than block sync, but still producing a lower accumulated difficulty And mining node MINER2 mines blocks with min difficulty 1 and max difficulty 10 # Allow reorg to filter through - When I wait seconds + Then node SYNCER is in state LISTENING Then node SYNCER is at the same height as node SEED @critical Examples: - | X1 | Y1 | SYNC_TIME | - | 101 | 10 | 1 | + | X1 | Y1 | + | 101 | 10 | @long-running Examples: - | X1 | Y1 | SYNC_TIME | - | 501 | 50 | 20 | - | 999 | 50 | 60 | - | 1000 | 50 | 60 | - | 1001 | 50 | 60 | + | X1 | Y1 | + | 501 | 50 | + | 999 | 50 | + | 1000 | 50 | + | 1001 | 50 | Scenario: Pruned mode network only diff --git a/integration_tests/features/TransactionInfo.feature b/integration_tests/features/TransactionInfo.feature index aa381408367..c7d00db5ec8 100644 --- a/integration_tests/features/TransactionInfo.feature +++ b/integration_tests/features/TransactionInfo.feature @@ -8,9 +8,9 @@ Scenario: Get Transaction Info And I have a SHA3 miner MINER connected to all seed nodes And I have wallet WALLET_A connected to all seed nodes And I have wallet WALLET_B connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and WALLET_A with default config + And I have mining node MINER connected to base node NODE and wallet WALLET_A # We need to ensure the coinbase lock heights are gone; mine enough blocks - When I merge mine 4 blocks via PROXY + When mining node MINER mines 4 blocks Then all nodes are at height 4 Then I list all COINBASE transactions for wallet WALLET_A When I wait for wallet WALLET_A to have at least 1002000 uT diff --git a/integration_tests/features/WalletMonitoring.feature b/integration_tests/features/WalletMonitoring.feature index 2ef86918597..48c664fd344 100644 --- a/integration_tests/features/WalletMonitoring.feature +++ b/integration_tests/features/WalletMonitoring.feature @@ -1,6 +1,8 @@ @wallet-monitoring @wallet Feature: Wallet Monitoring + +@flaky Scenario: Wallets monitoring coinbase after a reorg # # Chain 1: @@ -49,7 +51,6 @@ Feature: Wallet Monitoring # 18+ mins on circle ci @long-running Scenario: Wallets monitoring normal transactions after a reorg - Given I do not expect all automated transactions to succeed # # Chain 1: # Collects 10 coinbases into one wallet, send 7 transactions diff --git a/integration_tests/features/WalletRecovery.feature b/integration_tests/features/WalletRecovery.feature index e333a2ea17b..a4996617404 100644 --- a/integration_tests/features/WalletRecovery.feature +++ b/integration_tests/features/WalletRecovery.feature @@ -6,8 +6,8 @@ Feature: Wallet Recovery Given I have a seed node NODE And I have 1 base nodes connected to all seed nodes And I have wallet WALLET_A connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and WALLET_A with default config - When I merge mine 10 blocks via PROXY + And I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 10 blocks When I mine 5 blocks on NODE When I wait for wallet WALLET_A to have at least 55000000000 uT Then all nodes are at height 15 @@ -23,8 +23,8 @@ Feature: Wallet Recovery 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 a merge mining proxy PROXY connected to NODE and WALLET_A with default config - When I merge mine 15 blocks via PROXY + 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 When I recover wallet WALLET_A into wallets connected to all seed nodes @@ -48,26 +48,26 @@ Feature: Wallet Recovery Given I have a seed node NODE And I have 1 base nodes connected to all seed nodes And I have wallet WALLET_A connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and WALLET_A with default config - When I merge mine 10 blocks via PROXY + And I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 10 blocks Then all nodes are at height 10 And I have wallet WALLET_B connected to all seed nodes And I stop wallet WALLET_B # Send 2 one-sided payments to WALLET_B so it can spend them in two cases Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 - When I merge mine 5 blocks via PROXY + When mining node MINER mines 5 blocks Then all nodes are at height 15 When I recover wallet WALLET_B into wallet WALLET_C connected to all seed nodes Then I wait for wallet WALLET_C to have at least 2000000 uT # Send one of the recovered outputs back to Wallet A as a one-sided transactions Then I send a one-sided transaction of 900000 uT from WALLET_C to WALLET_A at fee 100 - When I merge mine 5 blocks via PROXY + When mining node MINER mines 5 blocks Then all nodes are at height 20 Then I wait for wallet WALLET_C to have less than 1100000 uT # Send the remaining recovered UTXO to self in standard MW transaction Then I send 1000000 uT from wallet WALLET_C to wallet WALLET_C at fee 100 Then I wait for wallet WALLET_C to have less than 100000 uT - When I merge mine 5 blocks via PROXY + When mining node MINER mines 5 blocks Then all nodes are at height 25 Then I wait for wallet WALLET_C to have at least 1000000 uT diff --git a/integration_tests/features/WalletTransactions.feature b/integration_tests/features/WalletTransactions.feature index 96fe0ea4a06..fc75f4bc1a7 100644 --- a/integration_tests/features/WalletTransactions.feature +++ b/integration_tests/features/WalletTransactions.feature @@ -1,33 +1,33 @@ @wallet-transact @wallet Feature: Wallet Transactions - @critical @flaky + @critical Scenario: Wallet sending and receiving one-sided transactions Given I have a seed node NODE And I have 1 base nodes connected to all seed nodes And I have wallet WALLET_A connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and WALLET_A with default config - When I merge mine 15 blocks via PROXY + And I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 15 blocks Then all nodes are at height 15 When I wait for wallet WALLET_A to have at least 55000000000 uT And I have wallet WALLET_B connected to all seed nodes Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 Then wallet WALLET_A detects all transactions are at least Broadcast - When I merge mine 5 blocks via PROXY + When mining node MINER mines 5 blocks Then all nodes are at height 20 Then I wait for wallet WALLET_B to have at least 2000000 uT # Spend one of the recovered UTXOs to self in a standard MW transaction Then I send 900000 uT from wallet WALLET_B to wallet WALLET_B at fee 100 Then I wait for wallet WALLET_B to have less than 1100000 uT - When I merge mine 5 blocks via PROXY + When mining node MINER mines 5 blocks Then all nodes are at height 25 Then I wait for wallet WALLET_B to have at least 1900000 uT # Make a one-sided payment to a new wallet that is big enough to ensure the second recovered output is spent And I have wallet WALLET_C connected to all seed nodes Then I send a one-sided transaction of 1500000 uT from WALLET_B to WALLET_C at fee 100 Then I wait for wallet WALLET_B to have less than 1000000 uT - When I merge mine 5 blocks via PROXY + When mining node MINER mines 5 blocks Then all nodes are at height 30 Then I wait for wallet WALLET_C to have at least 1500000 uT @@ -35,14 +35,14 @@ Feature: Wallet Transactions Given I have a seed node NODE And I have 1 base nodes connected to all seed nodes And I have wallet WALLET_A connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and WALLET_A with default config - When I merge mine 5 blocks via PROXY + And I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 5 blocks Then all nodes are at height 5 Then I wait for wallet WALLET_A to have at least 10000000000 uT Then I have wallet WALLET_B connected to all seed nodes And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 When wallet WALLET_A detects all transactions are at least Broadcast - Then I merge mine 5 blocks via PROXY + Then mining node MINER mines 5 blocks Then all nodes are at height 10 Then I wait for wallet WALLET_B to have at least 1000000 uT Then I stop wallet WALLET_B @@ -58,19 +58,19 @@ Feature: Wallet Transactions Given I have a seed node NODE And I have 1 base nodes connected to all seed nodes And I have wallet WALLET_A connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and WALLET_A with default config - When I merge mine 5 blocks via PROXY + And I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 5 blocks Then all nodes are at height 5 Then I wait for wallet WALLET_A to have at least 10000000000 uT Then I have wallet WALLET_B connected to all seed nodes And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 When wallet WALLET_A detects all transactions are at least Broadcast - Then I merge mine 5 blocks via PROXY + Then mining node MINER mines 5 blocks Then all nodes are at height 10 Then I wait for wallet WALLET_B to have at least 1000000 uT When I send 900000 uT from wallet WALLET_B to wallet WALLET_A at fee 100 And wallet WALLET_B detects all transactions are at least Broadcast - Then I merge mine 5 blocks via PROXY + Then mining node MINER mines 5 blocks Then all nodes are at height 15 When I wait for wallet WALLET_B to have at least 50000 uT Then I stop wallet WALLET_B @@ -133,31 +133,33 @@ Feature: Wallet Transactions Given I have a seed node NODE And I have 1 base nodes connected to all seed nodes And I have wallet WALLET_A connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and WALLET_A with default config - When I merge mine 5 blocks via PROXY + And I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 5 blocks Then all nodes are at height 5 Then I wait for wallet WALLET_A to have at least 10000000000 uT When I have wallet WALLET_B connected to all seed nodes And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - When I merge mine 5 blocks via PROXY - Then all nodes are at height 10 + When I wait 10 seconds + When mining node MINER mines 6 blocks + Then all nodes are at height 11 Then I wait for wallet WALLET_B to have at least 1000000 uT Then I stop wallet WALLET_B When I have wallet WALLET_C connected to all seed nodes Then I import WALLET_B unspent outputs as faucet outputs to WALLET_C Then I wait for wallet WALLET_C to have at least 1000000 uT And I send 500000 uT from wallet WALLET_C to wallet WALLET_A at fee 100 + When I wait 10 seconds Then wallet WALLET_C detects all transactions are at least Broadcast - When I merge mine 5 blocks via PROXY - Then all nodes are at height 15 + When mining node MINER mines 6 blocks + Then all nodes are at height 17 Then I wait for wallet WALLET_C to have at least 400000 uT Scenario: Wallet should display all transactions made Given I have a seed node NODE And I have 1 base nodes connected to all seed nodes And I have wallet WALLET_A connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and WALLET_A with default config - When I merge mine 10 blocks via PROXY + And I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 10 blocks Then all nodes are at height 10 Then I wait for wallet WALLET_A to have at least 10000000000 uT Then I have wallet WALLET_B connected to all seed nodes @@ -166,8 +168,9 @@ Feature: Wallet Transactions And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + When I wait 30 seconds When wallet WALLET_A detects all transactions are at least Broadcast - Then I merge mine 5 blocks via PROXY + Then mining node MINER mines 5 blocks Then all nodes are at height 15 Then I wait for wallet WALLET_B to have at least 500000 uT Then I check if wallet WALLET_B has 5 transactions @@ -180,7 +183,6 @@ Feature: Wallet Transactions # Collects 7 coinbases into one wallet, send 7 transactions # Stronger chain # - Given I do not expect all automated transactions to succeed Given I have a seed node SEED_A And I have a base node NODE_A1 connected to seed SEED_A And I have wallet WALLET_A1 connected to seed node SEED_A @@ -235,7 +237,6 @@ Feature: Wallet Transactions # Collects 7 coinbases into one wallet, send 7 transactions # Stronger chain # - Given I do not expect all automated transactions to succeed Given I have a seed node SEED_A And I have a base node NODE_A1 connected to seed SEED_A And I have wallet WALLET_A1 connected to seed node SEED_A @@ -326,4 +327,4 @@ Feature: Wallet Transactions Then I restart wallet WALLET_RECV And I wait for 5 seconds Then I restart wallet WALLET_RECV - Then I wait for wallet WALLET_RECV to have at least 1000000 uT \ No newline at end of file + Then I wait for wallet WALLET_RECV to have at least 1000000 uT diff --git a/integration_tests/features/WalletTransfer.feature b/integration_tests/features/WalletTransfer.feature index 8aa40b99e34..33f8b2b172c 100644 --- a/integration_tests/features/WalletTransfer.feature +++ b/integration_tests/features/WalletTransfer.feature @@ -7,11 +7,10 @@ Feature: Wallet Transfer # Add a 2nd node otherwise initial sync will not succeed And I have 1 base nodes connected to all seed nodes And I have wallet Wallet_A connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and Wallet_A with default config And I have mining node MINER connected to base node NODE and wallet Wallet_A And I have wallet Wallet_B connected to all seed nodes And I have wallet Wallet_C connected to all seed nodes - When I merge mine 2 blocks via PROXY + When mining node MINER mines 2 blocks Then all nodes are at height 2 # Ensure the coinbase lock heights have expired And mining node MINER mines 3 blocks @@ -30,8 +29,8 @@ Feature: Wallet Transfer # Add a 2nd node otherwise initial sync will not succeed And I have 1 base nodes connected to all seed nodes And I have wallet Wallet_A connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and Wallet_A with default config - When I merge mine 5 blocks via PROXY + And I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 5 blocks Then all nodes are at height 5 # Ensure the coinbase lock heights have expired When I mine 5 blocks on NODE diff --git a/integration_tests/features/support/steps.js b/integration_tests/features/support/steps.js index 9a3590f73ca..7ba479aa51d 100644 --- a/integration_tests/features/support/steps.js +++ b/integration_tests/features/support/steps.js @@ -39,21 +39,9 @@ Given("I have {int} seed nodes", { timeout: 20 * 1000 }, async function (n) { await Promise.all(promises); }); -Given( - /I do not expect all automated transactions to succeed/, - { timeout: 20 * 1000 }, - async function () { - this.checkAutoTransactions = false; - } -); - -Given( - /I expect all automated transactions to succeed/, - { timeout: 20 * 1000 }, - async function () { - this.checkAutoTransactions = true; - } -); +Then(/all transactions must have succeeded/, function () { + expect(this.lastTransactionsSucceeded).to.be(true); +}); Given( /I have a base node (.*) connected to all seed nodes/, @@ -903,7 +891,11 @@ Then( await this.forEachClientAsync(async (client, name) => { await waitFor(async () => client.getTipHeight(), height, 115 * 1000); const currTip = await client.getTipHeader(); - console.log("the node is at tip ", currTip); + console.log( + `${client.name} is at tip ${currTip.height} (${currTip.hash.toString( + "hex" + )})` + ); expect(currTip.height).to.equal(height); if (!tipHash) { tipHash = currTip.hash.toString("hex"); @@ -929,7 +921,6 @@ Then( let height = null; let result = true; await this.forEachClientAsync(async (client, name) => { - await waitFor(async () => client.getTipHeight(), 115 * 1000); const currTip = await client.getTipHeader(); if (!tipHash) { tipHash = currTip.hash.toString("hex"); @@ -995,6 +986,22 @@ Then( } ); +Then( + /node (.*) is in state (.*)/, + { timeout: 21 * 60 * 1000 }, + async function (node, state) { + const client = this.getClient(node); + await waitForPredicate( + async () => (await client.get_node_state()) == state, + 20 * 60 * 1000, + 1000 + ); + let result = await this.getClient(node).get_node_state(); + console.log(`Node ${node} is in the current state: ${result}`); + expect(result).to.equal(state); + } +); + Then( /(.*) does not have a new software update/, { timeout: 1200 * 1000 }, @@ -2676,7 +2683,7 @@ Then( ); Then( - /while mining via (.*) all transactions in wallet (.*) are found to be Mined_Confirmed/, + /while mining via node (.*) all transactions in wallet (.*) are found to be Mined_Confirmed/, { timeout: 1200 * 1000 }, async function (nodeName, walletName) { const wallet = this.getWallet(walletName); @@ -2735,12 +2742,13 @@ Then( ); Then( - /while merge mining via (.*) all transactions in wallet (.*) are found to be Mined_Confirmed/, + /while mining via SHA3 miner (.*) all transactions in wallet (.*) are found to be Mined_Confirmed/, { timeout: 3600 * 1000 }, - async function (mmProxy, walletName) { + async function (miner, walletName) { const wallet = this.getWallet(walletName); const walletClient = await wallet.connectClient(); const walletInfo = await walletClient.identify(); + const miningNode = this.getMiningNode(miner); const txIds = this.transactionsMap.get(walletInfo.public_key); if (txIds === undefined) { @@ -2771,7 +2779,8 @@ Then( if (await walletClient.isTransactionMinedConfirmed(txIds[i])) { return true; } else { - await this.mergeMineBlock(mmProxy); + await miningNode.init(1, null, 1, 100000, false, null); + await miningNode.startNew(); return false; } }, diff --git a/integration_tests/features/support/world.js b/integration_tests/features/support/world.js index a47c2e424f5..97100cfccfd 100644 --- a/integration_tests/features/support/world.js +++ b/integration_tests/features/support/world.js @@ -16,7 +16,6 @@ class CustomWorld { constructor({ attach, parameters }) { // this.variable = 0; this.attach = attach; - this.checkAutoTransactions = true; this.seeds = {}; this.nodes = {}; this.proxies = {}; @@ -139,43 +138,37 @@ class CustomWorld { } async createTransactions(name, height) { + this.lastTransactionsSucceeded = true; let result = true; const txInputs = this.transactionOutputs[height]; if (txInputs == null) { return result; } - // This function is called from steps with timeout = -1. So we need to - // write something to the console from time to time. Because otherwise - // it will timeout and the tests will be killed. - let keepAlive = setInterval(() => { - console.log("."); - }, 1000 * 60 * 10); let i = 0; + const client = this.getClient(name); for (const input of txInputs) { + // console.log(input); + // console.log(await client.fetchMatchingUtxos(input.hash)); + const txn = new TransactionBuilder(); txn.addInput(input); const txOutput = txn.addOutput(txn.getSpendableAmount()); - this.addTransactionOutput(height + 1, txOutput); const completedTx = txn.build(); - const submitResult = await this.getClient(name).submitTransaction( - completedTx - ); - if (this.checkAutoTransactions && submitResult.result != "ACCEPTED") { - console.log( - "Automated transaction failed. If this is not intended add step :", - "`I do not expect all automated transactions to succeed` !" - ); - result = false; - } - if (submitResult.result == "ACCEPTED") { - i++; + + const submitResult = await client.submitTransaction(completedTx); + if (submitResult.result != "ACCEPTED") { + this.lastTransactionsSucceeded = false; + // result = false; + } else { + // Add the output to be spent... assumes it has been mined. + this.addTransactionOutput(height + 1, txOutput); } + i++; if (i > 9) { //this is to make sure the blocks stay relatively empty so that the tests don't take too long break; } } - clearInterval(keepAlive); console.log( `Created ${i} transactions for node: ${name} at height: ${height}` ); @@ -363,6 +356,9 @@ class CustomWorld { setWorldConstructor(CustomWorld); BeforeAll({ timeout: 2400000 }, async function () { + console.log( + "NOTE: Some tests may be excluded based on the profile used in /integration_tests/cucumber.js. If none was specified, `default` profile is used." + ); const baseNode = new BaseNodeProcess("compile"); console.log("Compiling base node..."); await baseNode.init(); diff --git a/integration_tests/helpers/baseNodeClient.js b/integration_tests/helpers/baseNodeClient.js index 1fa3b0f0f8d..e51bd4d0b58 100644 --- a/integration_tests/helpers/baseNodeClient.js +++ b/integration_tests/helpers/baseNodeClient.js @@ -439,6 +439,11 @@ class BaseNodeClient { return result.initial_sync_achieved; } + async get_node_state() { + let result = await this.client.GetTipInfo().sendMessage({}); + return result.base_node_state; + } + static async create(port) { const client = new BaseNodeClient(); await client.connect(port); diff --git a/integration_tests/helpers/config.js b/integration_tests/helpers/config.js index 200fac6fbb4..812eb11b6b3 100644 --- a/integration_tests/helpers/config.js +++ b/integration_tests/helpers/config.js @@ -24,7 +24,7 @@ function mapEnvs(options) { res.TARI_WALLET__TRANSACTION_BROADCAST_MONITORING_TIMEOUT = 3; } if ("mineOnTipOnly" in options) { - res.TARI_MINING_NODE__MINE_ON_TIP_ONLY = options.mineOnTipOnly; + res.TARI_MINING_NODE__MINE_ON_TIP_ONLY = options.mineOnTipOnly.toString(); } if (options.numMiningThreads) { res.TARI_MINING_NODE__NUM_MINING_THREADS = options.numMiningThreads; diff --git a/integration_tests/helpers/mergeMiningProxyClient.js b/integration_tests/helpers/mergeMiningProxyClient.js index b990d9e4e52..4477b73532d 100644 --- a/integration_tests/helpers/mergeMiningProxyClient.js +++ b/integration_tests/helpers/mergeMiningProxyClient.js @@ -38,6 +38,7 @@ class MergeMiningProxyClient { id: "0", method: "submit_block", params: [block], + timeout: 60, }); return res.data; } @@ -96,7 +97,9 @@ class MergeMiningProxyClient { } await this.submitBlock(block); } while (tipHeight + 1 < height); - return await this.baseNodeClient.getTipHeight(); + tipHeight = await this.baseNodeClient.getTipHeight(); + console.log("[mmProxy client] Tip is at target height", tipHeight); + return tipHeight; } } diff --git a/integration_tests/helpers/miningNodeProcess.js b/integration_tests/helpers/miningNodeProcess.js index 002a72c9385..7a09c987676 100644 --- a/integration_tests/helpers/miningNodeProcess.js +++ b/integration_tests/helpers/miningNodeProcess.js @@ -175,7 +175,9 @@ class MiningNodeProcess { await this.init(numBlocks, height, minDifficulty, 9999999999, true, 1); await this.startNew(); await this.stop(); - return await this.baseNodeClient.getTipHeight(); + const tipHeight = await this.baseNodeClient.getTipHeight(); + console.log(`[${this.name}] Tip at ${tipHeight}`); + return tipHeight; } } diff --git a/integration_tests/log4rs/base_node.yml b/integration_tests/log4rs/base_node.yml index 78802da9cc4..4d44f9aeb39 100644 --- a/integration_tests/log4rs/base_node.yml +++ b/integration_tests/log4rs/base_node.yml @@ -14,8 +14,13 @@ appenders: # An appender named "stdout" that writes to stdout stdout: kind: console + # Put all errors and warns on stderr so that it gets printed in circle ci + target: stderr encoder: - pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{t}] {h({l}):5} {m}{n}" + pattern: "{d(%H:%M)} {h({l}):5} {m}{n}" + filters: + - kind: threshold + level: warn # An appender named "network" that writes to a file with a custom pattern encoder network: @@ -92,82 +97,75 @@ root: - stdout loggers: - # base_layer - c: - level: debug - appenders: - - base_layer - additive: false - c::cs::lmdb_db::lmdb_db: - level: debug + # Route log events common to every application to all appenders + tari::application: + level: info appenders: - base_layer + - network + - other additive: false - c::pow::lwma_diff: - level: debug + + # Route log events sent to the "core" logger to the "base_layer" appender + c: + level: info appenders: - base_layer - additive: false - c::bn::pow::sha3: - level: debug + tari: + level: info appenders: - base_layer - additive: false - base_node: - level: debug + + # Route log events sent to the "wallet" logger to the "base_layer" appender + wallet: + level: info appenders: - base_layer - additive: false - # network + # Route log events sent to the "comms" logger to the "network" appender comms: - level: debug - appenders: - - network - additive: false - comms::noise: - level: error + level: info appenders: - network - additive: false + # Route log events sent to the "p2p" logger to the "network" appender p2p: - level: debug + level: info appenders: - network - additive: false + + # Route log events sent to the "yamux" logger to the "network" appender yamux: - level: warn + level: info appenders: - network - additive: false + # Route log events sent to the "mio" logger to the "network" appender mio: - level: warn - appenders: - - network - additive: false - tracing: level: error appenders: - network - additive: false - # other + # Route log events sent to the "rustyline" logger to the "other" appender rustyline: - level: warn + level: error appenders: - other additive: false + + # Route log events sent to the "tokio_util" logger to the "other" appender tokio_util: - level: warn + level: error appenders: - other - additive: false - h2: - level: error + # Route PGP log events + pgp: + level: warn appenders: - other - additive: false - # stress_test + # Route log events sent to the "tari_mm_proxy" logger to the "base_layer" appender + tari_mm_proxy: + level: info + appenders: + - base_layer + # Route log events sent to the "stress_test" logger to the "base_layer" appender stress_test: - level: debug + level: info appenders: - - stress_test - additive: false + - base_layer diff --git a/integration_tests/package-lock.json b/integration_tests/package-lock.json index 10202d187c1..5f4b48cd146 100644 --- a/integration_tests/package-lock.json +++ b/integration_tests/package-lock.json @@ -4271,6 +4271,7 @@ "dependencies": { "@babel/code-frame": { "version": "7.15.8", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", "dev": true, "requires": { "@babel/highlight": "^7.14.5" @@ -4278,10 +4279,12 @@ }, "@babel/compat-data": { "version": "7.15.0", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", "dev": true }, "@babel/core": { "version": "7.15.8", + "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", "dev": true, "requires": { "@babel/code-frame": "^7.15.8", @@ -4303,6 +4306,7 @@ }, "@babel/eslint-parser": { "version": "7.15.8", + "integrity": "sha512-fYP7QFngCvgxjUuw8O057SVH5jCXsbFFOoE77CFDcvzwBVgTOkMD/L4mIC5Ud1xf8chK/no2fRbSSn1wvNmKuQ==", "dev": true, "requires": { "eslint-scope": "^5.1.1", @@ -4312,6 +4316,7 @@ }, "@babel/eslint-plugin": { "version": "7.14.5", + "integrity": "sha512-nzt/YMnOOIRikvSn2hk9+W2omgJBy6U8TN0R+WTTmqapA+HnZTuviZaketdTE9W7/k/+E/DfZlt1ey1NSE39pg==", "dev": true, "requires": { "eslint-rule-composer": "^0.3.0" @@ -4319,6 +4324,7 @@ }, "@babel/generator": { "version": "7.15.8", + "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", "dev": true, "requires": { "@babel/types": "^7.15.6", @@ -4328,6 +4334,7 @@ }, "@babel/helper-compilation-targets": { "version": "7.15.4", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", "dev": true, "requires": { "@babel/compat-data": "^7.15.0", @@ -4338,6 +4345,7 @@ }, "@babel/helper-function-name": { "version": "7.15.4", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.15.4", @@ -4347,6 +4355,7 @@ }, "@babel/helper-get-function-arity": { "version": "7.15.4", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -4354,6 +4363,7 @@ }, "@babel/helper-hoist-variables": { "version": "7.15.4", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -4361,6 +4371,7 @@ }, "@babel/helper-member-expression-to-functions": { "version": "7.15.4", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -4368,6 +4379,7 @@ }, "@babel/helper-module-imports": { "version": "7.15.4", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -4375,6 +4387,7 @@ }, "@babel/helper-module-transforms": { "version": "7.15.8", + "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.15.4", @@ -4389,6 +4402,7 @@ }, "@babel/helper-optimise-call-expression": { "version": "7.15.4", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -4396,6 +4410,7 @@ }, "@babel/helper-replace-supers": { "version": "7.15.4", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", "dev": true, "requires": { "@babel/helper-member-expression-to-functions": "^7.15.4", @@ -4406,6 +4421,7 @@ }, "@babel/helper-simple-access": { "version": "7.15.4", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -4413,6 +4429,7 @@ }, "@babel/helper-split-export-declaration": { "version": "7.15.4", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", "dev": true, "requires": { "@babel/types": "^7.15.4" @@ -4420,14 +4437,17 @@ }, "@babel/helper-validator-identifier": { "version": "7.15.7", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", "dev": true }, "@babel/helper-validator-option": { "version": "7.14.5", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", "dev": true }, "@babel/helpers": { "version": "7.15.4", + "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", "dev": true, "requires": { "@babel/template": "^7.15.4", @@ -4437,6 +4457,7 @@ }, "@babel/highlight": { "version": "7.14.5", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.14.5", @@ -4446,10 +4467,12 @@ }, "@babel/parser": { "version": "7.15.8", + "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", "dev": true }, "@babel/runtime-corejs3": { "version": "7.15.4", + "integrity": "sha512-lWcAqKeB624/twtTc3w6w/2o9RqJPaNBhPGK6DKLSiwuVWC7WFkypWyNg+CpZoyJH0jVzv1uMtXZ/5/lQOLtCg==", "dev": true, "requires": { "core-js-pure": "^3.16.0", @@ -4458,6 +4481,7 @@ }, "@babel/template": { "version": "7.15.4", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", "dev": true, "requires": { "@babel/code-frame": "^7.14.5", @@ -4467,6 +4491,7 @@ }, "@babel/traverse": { "version": "7.15.4", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", "dev": true, "requires": { "@babel/code-frame": "^7.14.5", @@ -4482,6 +4507,7 @@ }, "@babel/types": { "version": "7.15.6", + "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.14.9", @@ -4490,6 +4516,7 @@ }, "@eslint/eslintrc": { "version": "0.4.3", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -4505,6 +4532,7 @@ "dependencies": { "globals": { "version": "13.11.0", + "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -4512,12 +4540,14 @@ }, "type-fest": { "version": "0.20.2", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true } } }, "@grpc/grpc-js": { "version": "1.3.7", + "integrity": "sha512-CKQVuwuSPh40tgOkR7c0ZisxYRiN05PcKPW72mQL5y++qd7CwBRoaJZvU5xfXnCJDFBmS3qZGQ71Frx6Ofo2XA==", "dev": true, "requires": { "@types/node": ">=12.12.47" @@ -4525,6 +4555,7 @@ }, "@grpc/proto-loader": { "version": "0.5.6", + "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", "dev": true, "requires": { "lodash.camelcase": "^4.3.0", @@ -4533,6 +4564,7 @@ }, "@humanwhocodes/config-array": { "version": "0.5.0", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.0", @@ -4542,26 +4574,32 @@ }, "@humanwhocodes/object-schema": { "version": "1.2.0", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", "dev": true }, "@protobufjs/aspromise": { "version": "1.1.2", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", "dev": true }, "@protobufjs/base64": { "version": "1.1.2", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", "dev": true }, "@protobufjs/codegen": { "version": "2.0.4", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", "dev": true }, "@protobufjs/eventemitter": { "version": "1.1.0", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", "dev": true }, "@protobufjs/fetch": { "version": "1.1.0", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", "dev": true, "requires": { "@protobufjs/aspromise": "^1.1.1", @@ -4570,47 +4608,58 @@ }, "@protobufjs/float": { "version": "1.0.2", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", "dev": true }, "@protobufjs/inquire": { "version": "1.1.0", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", "dev": true }, "@protobufjs/path": { "version": "1.1.2", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", "dev": true }, "@protobufjs/pool": { "version": "1.1.0", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", "dev": true }, "@protobufjs/utf8": { "version": "1.1.0", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", "dev": true }, "@types/json5": { "version": "0.0.29", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, "@types/long": { "version": "4.0.1", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", "dev": true }, "@types/node": { "version": "16.10.3", + "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==", "dev": true }, "acorn": { "version": "7.4.1", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, "acorn-jsx": { "version": "5.3.2", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "requires": {} }, "ajv": { "version": "6.12.6", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -4621,14 +4670,17 @@ }, "ansi-colors": { "version": "4.1.1", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-regex": { "version": "5.0.1", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { "version": "3.2.1", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -4636,10 +4688,12 @@ }, "any-promise": { "version": "1.3.0", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", "dev": true }, "archiver": { "version": "5.3.0", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", "requires": { "archiver-utils": "^2.1.0", "async": "^3.2.0", @@ -4652,6 +4706,7 @@ }, "archiver-utils": { "version": "2.1.0", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "requires": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", @@ -4667,6 +4722,7 @@ "dependencies": { "readable-stream": { "version": "2.3.7", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4679,6 +4735,7 @@ }, "string_decoder": { "version": "1.1.1", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } @@ -4687,6 +4744,7 @@ }, "argparse": { "version": "1.0.10", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { "sprintf-js": "~1.0.2" @@ -4694,6 +4752,7 @@ }, "array-includes": { "version": "3.1.4", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -4705,6 +4764,7 @@ }, "array.prototype.flat": { "version": "1.2.5", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -4714,14 +4774,17 @@ }, "assert-plus": { "version": "1.0.0", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "assertion-error": { "version": "1.1.0", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, "assertion-error-formatter": { "version": "3.0.0", + "integrity": "sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==", "dev": true, "requires": { "diff": "^4.0.1", @@ -4731,35 +4794,43 @@ }, "astral-regex": { "version": "2.0.0", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, "async": { - "version": "3.2.1" + "version": "3.2.1", + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" }, "axios": { "version": "0.21.4", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", "requires": { "follow-redirects": "^1.14.0" } }, "balanced-match": { - "version": "1.0.2" + "version": "1.0.2", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { - "version": "1.5.1" + "version": "1.5.1", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "becke-ch--regex--s0-0-v1--base--pl--lib": { "version": "1.4.0", + "integrity": "sha1-Qpzuu/pffpNueNc/vcfacWKyDiA=", "dev": true }, "bindings": { "version": "1.5.0", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "requires": { "file-uri-to-path": "1.0.0" } }, "bl": { "version": "4.1.0", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -4768,6 +4839,7 @@ "dependencies": { "buffer": { "version": "5.7.1", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -4777,14 +4849,17 @@ }, "blakejs": { "version": "1.1.1", + "integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==", "dev": true }, "bluebird": { "version": "3.7.2", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, "brace-expansion": { "version": "1.1.11", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4792,6 +4867,7 @@ }, "browserslist": { "version": "4.17.3", + "integrity": "sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ==", "dev": true, "requires": { "caniuse-lite": "^1.0.30001264", @@ -4803,16 +4879,19 @@ }, "buffer": { "version": "6.0.3", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "requires": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "buffer-crc32": { - "version": "0.2.13" + "version": "0.2.13", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" }, "call-bind": { "version": "1.0.2", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "dev": true, "requires": { "function-bind": "^1.1.1", @@ -4821,14 +4900,17 @@ }, "callsites": { "version": "3.1.0", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, "caniuse-lite": { "version": "1.0.30001265", + "integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==", "dev": true }, "chai": { "version": "4.3.4", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", "dev": true, "requires": { "assertion-error": "^1.1.0", @@ -4841,6 +4923,7 @@ }, "chalk": { "version": "2.4.2", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -4850,10 +4933,12 @@ }, "check-error": { "version": "1.0.2", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, "cli-table3": { "version": "0.5.1", + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", "dev": true, "requires": { "colors": "^1.1.2", @@ -4863,6 +4948,7 @@ }, "clone-deep": { "version": "4.0.1", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "requires": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", @@ -4871,6 +4957,7 @@ }, "color-convert": { "version": "1.9.3", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { "color-name": "1.1.3" @@ -4878,18 +4965,22 @@ }, "color-name": { "version": "1.1.3", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "colors": { "version": "1.4.0", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true }, "commander": { "version": "3.0.2", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", "dev": true }, "compress-commons": { "version": "4.1.1", + "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", "requires": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", @@ -4898,10 +4989,12 @@ } }, "concat-map": { - "version": "0.0.1" + "version": "0.0.1", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "convert-source-map": { "version": "1.8.0", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -4909,13 +5002,16 @@ }, "core-js-pure": { "version": "3.18.2", + "integrity": "sha512-4hMMLUlZhKJKOWbbGD1/VDUxGPEhEoN/T01k7bx271WiBKCvCfkgPzy0IeRS4PB50p6/N1q/SZL4B/TRsTE5bA==", "dev": true }, "core-util-is": { - "version": "1.0.2" + "version": "1.0.2", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "crc-32": { "version": "1.2.0", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", "requires": { "exit-on-epipe": "~1.0.1", "printj": "~1.1.0" @@ -4923,6 +5019,7 @@ }, "crc32-stream": { "version": "4.0.2", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", "requires": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -4930,6 +5027,7 @@ }, "cross-spawn": { "version": "7.0.3", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -4939,12 +5037,14 @@ }, "csv-parser": { "version": "3.0.0", + "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", "requires": { "minimist": "^1.2.0" } }, "cucumber": { "version": "6.0.5", + "integrity": "sha512-x+W9Fwk6TvcapQsYMxwFU5AsQJDOIJVGrPKmH15OC7jzb9/Dk7Hb0ZAyw4WcpaDcUDRc8bi2k2yJejDp5eTRlg==", "dev": true, "requires": { "assertion-error-formatter": "^3.0.0", @@ -4978,12 +5078,14 @@ "dependencies": { "escape-string-regexp": { "version": "2.0.0", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true } } }, "cucumber-expressions": { "version": "8.3.0", + "integrity": "sha512-cP2ya0EiorwXBC7Ll7Cj7NELYbasNv9Ty42L4u7sso9KruWemWG1ZiTq4PMqir3SNDSrbykoqI5wZgMbLEDjLQ==", "dev": true, "requires": { "becke-ch--regex--s0-0-v1--base--pl--lib": "^1.4.0", @@ -4992,6 +5094,7 @@ }, "cucumber-html-reporter": { "version": "5.5.0", + "integrity": "sha512-kF7vIwvTe7we7Wp/5uNZVZk+Ryozb688LpNvCNhou6N0RmLYPqaoV2aiN8GIB94JUBpribtlq6kDkEUHwxBVeQ==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -5007,6 +5110,7 @@ }, "cucumber-pretty": { "version": "6.0.0", + "integrity": "sha512-ddx/VInPVKFB7N86QujgLivihJhuzexKwExMuFaUjSlEs5zVVqBgaf55f88h97VafXTWX+ZAcxTUwMBS4mYj/g==", "dev": true, "requires": { "cli-table3": "^0.5.1", @@ -5016,10 +5120,12 @@ }, "cucumber-tag-expressions": { "version": "2.0.3", + "integrity": "sha512-+x5j1IfZrBtbvYHuoUX0rl4nUGxaey6Do9sM0CABmZfDCcWXuuRm1fQeCaklIYQgOFHQ6xOHvDSdkMHHpni6tQ==", "dev": true }, "d": { "version": "1.0.1", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", "dev": true, "requires": { "es5-ext": "^0.10.50", @@ -5027,22 +5133,26 @@ } }, "dateformat": { - "version": "3.0.3" + "version": "3.0.3", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" }, "deasync": { "version": "0.1.23", + "integrity": "sha512-CGZSokFwidI50GOAmkz/7z3QdMzTQqAiUOzt95PuhKgi6VVztn9D03ZCzzi93uUWlp/v6A9osvNWpIvqHvKjTA==", "requires": { "bindings": "^1.5.0", "node-addon-api": "^1.7.1" }, "dependencies": { "node-addon-api": { - "version": "1.7.2" + "version": "1.7.2", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==" } } }, "debug": { "version": "4.3.2", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { "ms": "2.1.2" @@ -5050,6 +5160,7 @@ }, "deep-eql": { "version": "3.0.1", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { "type-detect": "^4.0.0" @@ -5057,10 +5168,12 @@ }, "deep-is": { "version": "0.1.4", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "define-properties": { "version": "1.1.3", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { "object-keys": "^1.0.12" @@ -5068,10 +5181,12 @@ }, "diff": { "version": "4.0.2", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, "doctrine": { "version": "3.0.0", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { "esutils": "^2.0.2" @@ -5079,6 +5194,7 @@ }, "duration": { "version": "0.2.2", + "integrity": "sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg==", "dev": true, "requires": { "d": "1", @@ -5087,20 +5203,24 @@ }, "electron-to-chromium": { "version": "1.3.864", + "integrity": "sha512-v4rbad8GO6/yVI92WOeU9Wgxc4NA0n4f6P1FvZTY+jyY7JHEhw3bduYu60v3Q1h81Cg6eo4ApZrFPuycwd5hGw==", "dev": true }, "emoji-regex": { "version": "8.0.0", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "end-of-stream": { "version": "1.4.4", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "requires": { "once": "^1.4.0" } }, "enquirer": { "version": "2.3.6", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "requires": { "ansi-colors": "^4.1.1" @@ -5108,6 +5228,7 @@ }, "error-ex": { "version": "1.3.2", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { "is-arrayish": "^0.2.1" @@ -5115,6 +5236,7 @@ }, "error-stack-parser": { "version": "2.0.6", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", "dev": true, "requires": { "stackframe": "^1.1.1" @@ -5122,6 +5244,7 @@ }, "es-abstract": { "version": "1.19.1", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -5148,6 +5271,7 @@ }, "es-to-primitive": { "version": "1.2.1", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { "is-callable": "^1.1.4", @@ -5157,6 +5281,7 @@ }, "es5-ext": { "version": "0.10.53", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", "dev": true, "requires": { "es6-iterator": "~2.0.3", @@ -5166,6 +5291,7 @@ }, "es6-iterator": { "version": "2.0.3", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { "d": "1", @@ -5175,6 +5301,7 @@ }, "es6-symbol": { "version": "3.1.3", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { "d": "^1.0.1", @@ -5183,14 +5310,17 @@ }, "escalade": { "version": "3.1.1", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, "escape-string-regexp": { "version": "1.0.5", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "eslint": { "version": "7.32.0", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", @@ -5237,6 +5367,7 @@ "dependencies": { "@babel/code-frame": { "version": "7.12.11", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, "requires": { "@babel/highlight": "^7.10.4" @@ -5244,6 +5375,7 @@ }, "ansi-styles": { "version": "4.3.0", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" @@ -5251,6 +5383,7 @@ }, "chalk": { "version": "4.1.2", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -5259,6 +5392,7 @@ }, "color-convert": { "version": "2.0.1", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" @@ -5266,14 +5400,17 @@ }, "color-name": { "version": "1.1.4", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "escape-string-regexp": { "version": "4.0.0", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, "globals": { "version": "13.11.0", + "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -5281,10 +5418,12 @@ }, "has-flag": { "version": "4.0.0", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "semver": { "version": "7.3.5", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -5292,6 +5431,7 @@ }, "supports-color": { "version": "7.2.0", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -5299,22 +5439,26 @@ }, "type-fest": { "version": "0.20.2", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true } } }, "eslint-config-prettier": { "version": "8.3.0", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", "dev": true, "requires": {} }, "eslint-config-standard": { "version": "16.0.3", + "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", "dev": true, "requires": {} }, "eslint-import-resolver-node": { "version": "0.3.6", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", "dev": true, "requires": { "debug": "^3.2.7", @@ -5323,6 +5467,7 @@ "dependencies": { "debug": { "version": "3.2.7", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { "ms": "^2.1.1" @@ -5332,6 +5477,7 @@ }, "eslint-module-utils": { "version": "2.6.2", + "integrity": "sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q==", "dev": true, "requires": { "debug": "^3.2.7", @@ -5340,6 +5486,7 @@ "dependencies": { "debug": { "version": "3.2.7", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { "ms": "^2.1.1" @@ -5349,6 +5496,7 @@ }, "eslint-plugin-import": { "version": "2.24.2", + "integrity": "sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q==", "dev": true, "requires": { "array-includes": "^3.1.3", @@ -5370,6 +5518,7 @@ "dependencies": { "debug": { "version": "2.6.9", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" @@ -5377,6 +5526,7 @@ }, "doctrine": { "version": "2.1.0", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { "esutils": "^2.0.2" @@ -5384,12 +5534,14 @@ }, "ms": { "version": "2.0.0", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } } }, "eslint-plugin-node": { "version": "11.1.0", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, "requires": { "eslint-plugin-es": "^3.0.0", @@ -5402,6 +5554,7 @@ "dependencies": { "eslint-plugin-es": { "version": "3.0.1", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", "dev": true, "requires": { "eslint-utils": "^2.0.0", @@ -5410,12 +5563,14 @@ }, "ignore": { "version": "5.1.8", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true } } }, "eslint-plugin-prettier": { "version": "3.4.1", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0" @@ -5423,14 +5578,17 @@ }, "eslint-plugin-promise": { "version": "4.3.1", + "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", "dev": true }, "eslint-rule-composer": { "version": "0.3.0", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", "dev": true }, "eslint-scope": { "version": "5.1.1", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -5439,6 +5597,7 @@ }, "eslint-utils": { "version": "2.1.0", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "requires": { "eslint-visitor-keys": "^1.1.0" @@ -5446,16 +5605,19 @@ "dependencies": { "eslint-visitor-keys": { "version": "1.3.0", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true } } }, "eslint-visitor-keys": { "version": "2.1.0", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, "espree": { "version": "7.3.1", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { "acorn": "^7.4.0", @@ -5465,16 +5627,19 @@ "dependencies": { "eslint-visitor-keys": { "version": "1.3.0", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true } } }, "esprima": { "version": "4.0.1", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esquery": { "version": "1.4.0", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -5482,12 +5647,14 @@ "dependencies": { "estraverse": { "version": "5.2.0", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } } }, "esrecurse": { "version": "4.3.0", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { "estraverse": "^5.2.0" @@ -5495,23 +5662,28 @@ "dependencies": { "estraverse": { "version": "5.2.0", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } } }, "estraverse": { "version": "4.3.0", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, "esutils": { "version": "2.0.3", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "exit-on-epipe": { - "version": "1.0.1" + "version": "1.0.1", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" }, "ext": { "version": "1.6.0", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", "dev": true, "requires": { "type": "^2.5.0" @@ -5519,32 +5691,39 @@ "dependencies": { "type": { "version": "2.5.0", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==", "dev": true } } }, "extsprintf": { "version": "1.4.0", + "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=", "dev": true }, "fast-deep-equal": { "version": "3.1.3", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "fast-diff": { "version": "1.2.0", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "fast-levenshtein": { "version": "2.0.6", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, "ffi-napi": { "version": "4.0.3", + "integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==", "dev": true, "requires": { "debug": "^4.1.1", @@ -5557,6 +5736,7 @@ }, "figures": { "version": "3.2.0", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" @@ -5564,16 +5744,19 @@ }, "file-entry-cache": { "version": "6.0.1", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { "flat-cache": "^3.0.4" } }, "file-uri-to-path": { - "version": "1.0.0" + "version": "1.0.0", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, "find": { "version": "0.3.0", + "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==", "dev": true, "requires": { "traverse-chain": "~0.1.0" @@ -5581,6 +5764,7 @@ }, "find-up": { "version": "2.1.0", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { "locate-path": "^2.0.0" @@ -5588,6 +5772,7 @@ }, "flat-cache": { "version": "3.0.4", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { "flatted": "^3.1.0", @@ -5596,16 +5781,20 @@ }, "flatted": { "version": "3.2.2", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", "dev": true }, "follow-redirects": { - "version": "1.14.4" + "version": "1.14.4", + "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" }, "fs-constants": { - "version": "1.0.0" + "version": "1.0.0", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { "version": "8.1.0", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { "graceful-fs": "^4.2.0", @@ -5615,6 +5804,7 @@ "dependencies": { "jsonfile": { "version": "4.0.0", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { "graceful-fs": "^4.1.6" @@ -5623,26 +5813,32 @@ } }, "fs.realpath": { - "version": "1.0.0" + "version": "1.0.0", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "function-bind": { "version": "1.1.1", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "functional-red-black-tree": { "version": "1.0.1", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, "gensync": { "version": "1.0.0-beta.2", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, "get-func-name": { "version": "2.0.0", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, "get-intrinsic": { "version": "1.1.1", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "dev": true, "requires": { "function-bind": "^1.1.1", @@ -5652,6 +5848,7 @@ }, "get-symbol-description": { "version": "1.0.0", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -5660,10 +5857,12 @@ }, "get-symbol-from-current-process-h": { "version": "1.0.2", + "integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==", "dev": true }, "get-uv-event-loop-napi-h": { "version": "1.0.6", + "integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==", "dev": true, "requires": { "get-symbol-from-current-process-h": "^1.0.1" @@ -5671,10 +5870,12 @@ }, "gherkin": { "version": "5.0.0", + "integrity": "sha1-lt70EZjsOQgli1Ea909lWidk0qE=", "dev": true }, "glob": { "version": "7.2.0", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5686,6 +5887,7 @@ }, "glob-parent": { "version": "5.1.2", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -5693,17 +5895,21 @@ }, "globals": { "version": "11.12.0", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, "graceful-fs": { - "version": "4.2.8" + "version": "4.2.8", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, "grpc-promise": { "version": "1.4.0", + "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==", "dev": true }, "has": { "version": "1.0.3", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { "function-bind": "^1.1.1" @@ -5711,46 +5917,57 @@ }, "has-bigints": { "version": "1.0.1", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", "dev": true }, "has-flag": { "version": "3.0.0", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-symbols": { "version": "1.0.2", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, "has-tostringtag": { "version": "1.0.0", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "dev": true, "requires": { "has-symbols": "^1.0.2" } }, "hex64": { - "version": "0.4.0" + "version": "0.4.0", + "integrity": "sha1-rRB4rIHVfXLeYjKxADvE9vsCh8A=" }, "hosted-git-info": { "version": "2.8.9", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "husky": { "version": "6.0.0", + "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", "dev": true }, "ieee754": { - "version": "1.2.1" + "version": "1.2.1", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { "version": "4.0.6", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "immediate": { - "version": "3.0.6" + "version": "3.0.6", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "import-fresh": { "version": "3.3.0", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -5759,24 +5976,29 @@ }, "imurmurhash": { "version": "0.1.4", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "indent-string": { "version": "4.0.0", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, "inflight": { "version": "1.0.6", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", "wrappy": "1" } }, "inherits": { - "version": "2.0.4" + "version": "2.0.4", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "internal-slot": { "version": "1.0.3", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", "dev": true, "requires": { "get-intrinsic": "^1.1.0", @@ -5786,10 +6008,12 @@ }, "is-arrayish": { "version": "0.2.1", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, "is-bigint": { "version": "1.0.4", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, "requires": { "has-bigints": "^1.0.1" @@ -5797,6 +6021,7 @@ }, "is-boolean-object": { "version": "1.1.2", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -5805,10 +6030,12 @@ }, "is-callable": { "version": "1.2.4", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", "dev": true }, "is-core-module": { "version": "2.7.0", + "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", "dev": true, "requires": { "has": "^1.0.3" @@ -5816,6 +6043,7 @@ }, "is-date-object": { "version": "1.0.5", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, "requires": { "has-tostringtag": "^1.0.0" @@ -5823,18 +6051,22 @@ }, "is-extglob": { "version": "2.1.1", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "is-generator": { "version": "1.0.3", + "integrity": "sha1-wUwhBX7TbjKNuANHlmxpP4hjifM=", "dev": true }, "is-glob": { "version": "4.0.3", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -5842,10 +6074,12 @@ }, "is-negative-zero": { "version": "2.0.1", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", "dev": true }, "is-number-object": { "version": "1.0.6", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", "dev": true, "requires": { "has-tostringtag": "^1.0.0" @@ -5853,12 +6087,14 @@ }, "is-plain-object": { "version": "2.0.4", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "requires": { "isobject": "^3.0.1" } }, "is-regex": { "version": "1.1.4", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -5867,14 +6103,17 @@ }, "is-shared-array-buffer": { "version": "1.0.1", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", "dev": true }, "is-stream": { "version": "2.0.1", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, "is-string": { "version": "1.0.7", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, "requires": { "has-tostringtag": "^1.0.0" @@ -5882,6 +6121,7 @@ }, "is-symbol": { "version": "1.0.4", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, "requires": { "has-symbols": "^1.0.2" @@ -5889,6 +6129,7 @@ }, "is-weakref": { "version": "1.0.1", + "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", "dev": true, "requires": { "call-bind": "^1.0.0" @@ -5896,28 +6137,35 @@ }, "is-wsl": { "version": "1.1.0", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true }, "isarray": { - "version": "1.0.0" + "version": "1.0.0", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "isobject": { - "version": "3.0.1" + "version": "3.0.1", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "js-base64": { "version": "2.6.4", + "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", "dev": true }, "js-tokens": { "version": "4.0.0", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { "version": "3.14.1", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -5926,22 +6174,27 @@ }, "jsesc": { "version": "2.5.2", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, "json-parse-better-errors": { "version": "1.0.2", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, "json-schema-traverse": { "version": "0.4.1", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, "json5": { "version": "2.2.0", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "dev": true, "requires": { "minimist": "^1.2.5" @@ -5949,6 +6202,7 @@ }, "jsonfile": { "version": "5.0.0", + "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", "dev": true, "requires": { "graceful-fs": "^4.1.6", @@ -5957,6 +6211,7 @@ }, "jszip": { "version": "3.7.1", + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", "requires": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -5966,6 +6221,7 @@ "dependencies": { "readable-stream": { "version": "2.3.7", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -5978,6 +6234,7 @@ }, "string_decoder": { "version": "1.1.1", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } @@ -5985,10 +6242,12 @@ } }, "kind-of": { - "version": "6.0.3" + "version": "6.0.3", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "knuth-shuffle-seeded": { "version": "1.0.6", + "integrity": "sha1-AfG2VzOqdUDuCNiwF0Fk0iCB5OE=", "dev": true, "requires": { "seed-random": "~2.2.0" @@ -5996,12 +6255,14 @@ }, "lazystream": { "version": "1.0.0", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "requires": { "readable-stream": "^2.0.5" }, "dependencies": { "readable-stream": { "version": "2.3.7", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6014,6 +6275,7 @@ }, "string_decoder": { "version": "1.1.1", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } @@ -6022,6 +6284,7 @@ }, "levn": { "version": "0.4.1", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { "prelude-ls": "^1.2.1", @@ -6030,12 +6293,14 @@ }, "lie": { "version": "3.3.0", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "requires": { "immediate": "~3.0.5" } }, "load-json-file": { "version": "4.0.0", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -6046,6 +6311,7 @@ }, "locate-path": { "version": "2.0.0", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { "p-locate": "^2.0.0", @@ -6054,49 +6320,62 @@ }, "lodash": { "version": "4.17.21", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash.camelcase": { "version": "4.3.0", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, "lodash.clonedeep": { "version": "4.5.0", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, "lodash.defaults": { - "version": "4.2.0" + "version": "4.2.0", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" }, "lodash.difference": { - "version": "4.5.0" + "version": "4.5.0", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" }, "lodash.flatten": { - "version": "4.4.0" + "version": "4.4.0", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" }, "lodash.isplainobject": { - "version": "4.0.6" + "version": "4.0.6", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, "lodash.merge": { "version": "4.6.2", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, "lodash.truncate": { "version": "4.4.2", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, "lodash.union": { - "version": "4.6.0" + "version": "4.6.0", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, "long": { "version": "4.0.0", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", "dev": true }, "lower-case": { "version": "1.1.4", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", "dev": true }, "lru-cache": { "version": "6.0.0", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { "yallist": "^4.0.0" @@ -6104,19 +6383,23 @@ }, "minimatch": { "version": "3.0.4", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "1.2.5" + "version": "1.2.5", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "ms": { "version": "2.1.2", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "mz": { "version": "2.7.0", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, "requires": { "any-promise": "^1.0.0", @@ -6126,14 +6409,17 @@ }, "natural-compare": { "version": "1.4.0", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, "next-tick": { "version": "1.0.0", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, "no-case": { "version": "2.3.2", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", "dev": true, "requires": { "lower-case": "^1.1.1" @@ -6141,10 +6427,12 @@ }, "node-addon-api": { "version": "3.2.1", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true }, "node-emoji": { "version": "1.11.0", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", "dev": true, "requires": { "lodash": "^4.17.21" @@ -6152,14 +6440,17 @@ }, "node-gyp-build": { "version": "4.3.0", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", "dev": true }, "node-releases": { "version": "1.1.77", + "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==", "dev": true }, "normalize-package-data": { "version": "2.5.0", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", @@ -6170,27 +6461,33 @@ "dependencies": { "semver": { "version": "5.7.1", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true } } }, "normalize-path": { - "version": "3.0.0" + "version": "3.0.0", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "object-assign": { "version": "4.1.1", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, "object-inspect": { "version": "1.11.0", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", "dev": true }, "object-keys": { "version": "1.1.1", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, "object.assign": { "version": "4.1.2", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "dev": true, "requires": { "call-bind": "^1.0.0", @@ -6201,6 +6498,7 @@ }, "object.values": { "version": "1.1.5", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -6210,12 +6508,14 @@ }, "once": { "version": "1.4.0", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, "open": { "version": "6.4.0", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", "dev": true, "requires": { "is-wsl": "^1.1.0" @@ -6223,6 +6523,7 @@ }, "optionator": { "version": "0.9.1", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { "deep-is": "^0.1.3", @@ -6235,6 +6536,7 @@ }, "p-limit": { "version": "1.3.0", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { "p-try": "^1.0.0" @@ -6242,6 +6544,7 @@ }, "p-locate": { "version": "2.0.0", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { "p-limit": "^1.1.0" @@ -6249,20 +6552,24 @@ }, "p-try": { "version": "1.0.0", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "pad-right": { "version": "0.2.2", + "integrity": "sha1-b7ySQEXSRPKiokRQMGDTv8YAl3Q=", "dev": true, "requires": { "repeat-string": "^1.5.2" } }, "pako": { - "version": "1.0.11" + "version": "1.0.11", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parent-module": { "version": "1.0.1", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { "callsites": "^3.0.0" @@ -6270,6 +6577,7 @@ }, "parse-json": { "version": "4.0.0", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { "error-ex": "^1.3.1", @@ -6278,21 +6586,26 @@ }, "path-exists": { "version": "3.0.0", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "path-is-absolute": { - "version": "1.0.1" + "version": "1.0.1", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "3.1.1", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { "version": "1.0.7", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "path-type": { "version": "3.0.0", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { "pify": "^3.0.0" @@ -6300,18 +6613,22 @@ }, "pathval": { "version": "1.1.1", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, "picocolors": { "version": "0.2.1", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", "dev": true }, "pify": { "version": "3.0.0", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "pkg-dir": { "version": "2.0.0", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { "find-up": "^2.1.0" @@ -6319,6 +6636,7 @@ }, "pkg-up": { "version": "2.0.0", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", "dev": true, "requires": { "find-up": "^2.1.0" @@ -6326,31 +6644,38 @@ }, "prelude-ls": { "version": "1.2.1", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "prettier": { "version": "2.4.1", + "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", "dev": true }, "prettier-linter-helpers": { "version": "1.0.0", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, "requires": { "fast-diff": "^1.1.2" } }, "printj": { - "version": "1.1.2" + "version": "1.1.2", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" }, "process-nextick-args": { - "version": "2.0.1" + "version": "2.0.1", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "protobufjs": { "version": "6.11.2", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "dev": true, "requires": { "@protobufjs/aspromise": "^1.1.2", @@ -6370,10 +6695,12 @@ }, "punycode": { "version": "2.1.1", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, "read-pkg": { "version": "3.0.0", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { "load-json-file": "^4.0.0", @@ -6383,6 +6710,7 @@ }, "read-pkg-up": { "version": "3.0.0", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", "dev": true, "requires": { "find-up": "^2.0.0", @@ -6391,6 +6719,7 @@ }, "readable-stream": { "version": "3.6.0", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -6399,12 +6728,14 @@ }, "readdir-glob": { "version": "1.1.1", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", "requires": { "minimatch": "^3.0.4" } }, "ref-napi": { "version": "3.0.3", + "integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==", "dev": true, "requires": { "debug": "^4.1.1", @@ -6415,6 +6746,7 @@ }, "ref-struct-di": { "version": "1.1.1", + "integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==", "dev": true, "requires": { "debug": "^3.1.0" @@ -6422,6 +6754,7 @@ "dependencies": { "debug": { "version": "3.2.7", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { "ms": "^2.1.1" @@ -6431,22 +6764,27 @@ }, "regenerator-runtime": { "version": "0.13.9", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", "dev": true }, "regexpp": { "version": "3.2.0", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "repeat-string": { "version": "1.6.1", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, "require-from-string": { "version": "2.0.2", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, "resolve": { "version": "1.20.0", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "dev": true, "requires": { "is-core-module": "^2.2.0", @@ -6455,50 +6793,60 @@ }, "resolve-from": { "version": "4.0.0", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "rimraf": { "version": "3.0.2", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" } }, "safe-buffer": { - "version": "5.1.2" + "version": "5.1.2", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "seed-random": { "version": "2.2.0", + "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=", "dev": true }, "semver": { "version": "6.3.0", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "serialize-error": { "version": "4.1.0", + "integrity": "sha512-5j9GgyGsP9vV9Uj1S0lDCvlsd+gc2LEPVK7HHHte7IyPwOD4lVQFeaX143gx3U5AnoCi+wbcb3mvaxVysjpxEw==", "dev": true, "requires": { "type-fest": "^0.3.0" } }, "set-immediate-shim": { - "version": "1.0.1" + "version": "1.0.1", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" }, "sha3": { "version": "2.1.4", + "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", "requires": { "buffer": "6.0.3" } }, "shallow-clone": { "version": "3.0.1", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "requires": { "kind-of": "^6.0.2" } }, "shebang-command": { "version": "2.0.0", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { "shebang-regex": "^3.0.0" @@ -6506,10 +6854,12 @@ }, "shebang-regex": { "version": "3.0.0", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "side-channel": { "version": "1.0.4", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "dev": true, "requires": { "call-bind": "^1.0.0", @@ -6519,6 +6869,7 @@ }, "slice-ansi": { "version": "4.0.0", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { "ansi-styles": "^4.0.0", @@ -6528,6 +6879,7 @@ "dependencies": { "ansi-styles": { "version": "4.3.0", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" @@ -6535,6 +6887,7 @@ }, "color-convert": { "version": "2.0.1", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" @@ -6542,20 +6895,24 @@ }, "color-name": { "version": "1.1.4", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true } } }, "source-map": { "version": "0.5.7", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, "spdx-correct": { "version": "3.1.1", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -6564,10 +6921,12 @@ }, "spdx-exceptions": { "version": "2.3.0", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, "spdx-expression-parse": { "version": "3.0.1", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -6576,18 +6935,22 @@ }, "spdx-license-ids": { "version": "3.0.10", + "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", "dev": true }, "sprintf-js": { "version": "1.0.3", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "stack-chain": { "version": "2.0.0", + "integrity": "sha512-GGrHXePi305aW7XQweYZZwiRwR7Js3MWoK/EHzzB9ROdc75nCnjSJVi21rdAGxFl+yCx2L2qdfl5y7NO4lTyqg==", "dev": true }, "stack-generator": { "version": "2.0.5", + "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", "dev": true, "requires": { "stackframe": "^1.1.1" @@ -6595,10 +6958,12 @@ }, "stackframe": { "version": "1.2.0", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", "dev": true }, "stacktrace-gps": { "version": "3.0.4", + "integrity": "sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==", "dev": true, "requires": { "source-map": "0.5.6", @@ -6607,12 +6972,14 @@ "dependencies": { "source-map": { "version": "0.5.6", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true } } }, "stacktrace-js": { "version": "2.0.2", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", "dev": true, "requires": { "error-stack-parser": "^2.0.6", @@ -6622,21 +6989,25 @@ }, "string_decoder": { "version": "1.3.0", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { "safe-buffer": "~5.2.0" }, "dependencies": { "safe-buffer": { - "version": "5.2.1" + "version": "5.2.1", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, "string-argv": { "version": "0.3.1", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", "dev": true }, "string-width": { "version": "2.1.1", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -6645,10 +7016,12 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "strip-ansi": { "version": "4.0.0", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -6658,6 +7031,7 @@ }, "string.prototype.trimend": { "version": "1.0.4", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -6666,6 +7040,7 @@ }, "string.prototype.trimstart": { "version": "1.0.4", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -6674,6 +7049,7 @@ }, "strip-ansi": { "version": "6.0.1", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { "ansi-regex": "^5.0.1" @@ -6681,14 +7057,17 @@ }, "strip-bom": { "version": "3.0.0", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, "strip-json-comments": { "version": "3.1.1", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { "version": "5.5.0", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -6696,12 +7075,14 @@ }, "synchronized-promise": { "version": "0.3.1", + "integrity": "sha512-Iy+JzrERSUrwpOHUDku8HHIddk8V6iLG9bPIzboP2i5RYkn2eSmRB8waSaX7Rc/+DUUsnFsoOHrmniwOp9BOgw==", "requires": { "deasync": "^0.1.15" } }, "table": { "version": "6.7.2", + "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", "dev": true, "requires": { "ajv": "^8.0.1", @@ -6714,6 +7095,7 @@ "dependencies": { "ajv": { "version": "8.6.3", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -6724,14 +7106,17 @@ }, "is-fullwidth-code-point": { "version": "3.0.0", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "json-schema-traverse": { "version": "1.0.0", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "string-width": { "version": "4.2.3", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", @@ -6743,6 +7128,7 @@ }, "tar-stream": { "version": "2.2.0", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "requires": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -6752,14 +7138,17 @@ } }, "tari_crypto": { - "version": "0.9.1" + "version": "0.9.1", + "integrity": "sha512-K7LAtwQQKCeTH5CyyO8d/TiPDEePRaJ4e6+hrxpWv6jlkkAiS4m6csBuVqpSjyAlKeP8cQJpUQX2n22akOuZVg==" }, "text-table": { "version": "0.2.0", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, "thenify": { "version": "3.3.1", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, "requires": { "any-promise": "^1.0.0" @@ -6767,6 +7156,7 @@ }, "thenify-all": { "version": "1.6.0", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", "dev": true, "requires": { "thenify": ">= 3.1.0 < 4" @@ -6774,6 +7164,7 @@ }, "title-case": { "version": "2.1.1", + "integrity": "sha1-PhJyFtpY0rxb7PE3q5Ha46fNj6o=", "dev": true, "requires": { "no-case": "^2.2.0", @@ -6782,14 +7173,17 @@ }, "to-fast-properties": { "version": "2.0.0", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, "traverse-chain": { "version": "0.1.0", + "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=", "dev": true }, "tsconfig-paths": { "version": "3.11.0", + "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", "dev": true, "requires": { "@types/json5": "^0.0.29", @@ -6800,6 +7194,7 @@ "dependencies": { "json5": { "version": "1.0.1", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -6809,10 +7204,12 @@ }, "type": { "version": "1.2.0", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", "dev": true }, "type-check": { "version": "0.4.0", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { "prelude-ls": "^1.2.1" @@ -6820,14 +7217,17 @@ }, "type-detect": { "version": "4.0.8", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "type-fest": { "version": "0.3.1", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", "dev": true }, "unbox-primitive": { "version": "1.0.1", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", "dev": true, "requires": { "function-bind": "^1.1.1", @@ -6838,39 +7238,48 @@ }, "universalify": { "version": "0.1.2", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, "upper-case": { "version": "1.1.3", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", "dev": true }, "uri-js": { "version": "4.4.1", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { "punycode": "^2.1.0" } }, "utf8": { - "version": "3.0.0" + "version": "3.0.0", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" }, "util-arity": { "version": "1.1.0", + "integrity": "sha1-WdAa8f2z/t4KxOYysKtfbOl8kzA=", "dev": true }, "util-deprecate": { - "version": "1.0.2" + "version": "1.0.2", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { "version": "3.4.0", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, "v8-compile-cache": { "version": "2.3.0", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "validate-npm-package-license": { "version": "3.0.4", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -6879,6 +7288,7 @@ }, "verror": { "version": "1.10.0", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { "assert-plus": "^1.0.0", @@ -6896,68 +7306,86 @@ "dependencies": { "@grpc/grpc-js": { "version": "1.3.6", + "integrity": "sha512-v7+LQFbqZKmd/Tvf5/j1Xlbq6jXL/4d+gUtm2TNX4QiEC3ELWADmGr2dGlUyLl6aKTuYfsN72vAsO5zmavYkEg==", "requires": { "@types/node": ">=12.12.47" } }, "@grpc/proto-loader": { "version": "0.5.6", + "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", "requires": { "lodash.camelcase": "^4.3.0", "protobufjs": "^6.8.6" } }, "@protobufjs/aspromise": { - "version": "1.1.2" + "version": "1.1.2", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" }, "@protobufjs/base64": { - "version": "1.1.2" + "version": "1.1.2", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" }, "@protobufjs/codegen": { - "version": "2.0.4" + "version": "2.0.4", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" }, "@protobufjs/eventemitter": { - "version": "1.1.0" + "version": "1.1.0", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" }, "@protobufjs/fetch": { "version": "1.1.0", + "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", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" }, "@protobufjs/inquire": { - "version": "1.1.0" + "version": "1.1.0", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" }, "@protobufjs/path": { - "version": "1.1.2" + "version": "1.1.2", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" }, "@protobufjs/pool": { - "version": "1.1.0" + "version": "1.1.0", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" }, "@protobufjs/utf8": { - "version": "1.1.0" + "version": "1.1.0", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "@types/long": { - "version": "4.0.1" + "version": "4.0.1", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "@types/node": { - "version": "16.3.2" + "version": "16.3.2", + "integrity": "sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==" }, "grpc-promise": { - "version": "1.4.0" + "version": "1.4.0", + "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==" }, "lodash.camelcase": { - "version": "4.3.0" + "version": "4.3.0", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, "long": { - "version": "4.0.0" + "version": "4.0.0", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "protobufjs": { "version": "6.11.2", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -6978,6 +7406,7 @@ }, "which": { "version": "2.0.2", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -6985,6 +7414,7 @@ }, "which-boxed-primitive": { "version": "1.0.2", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, "requires": { "is-bigint": "^1.0.1", @@ -6996,13 +7426,16 @@ }, "word-wrap": { "version": "1.2.3", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, "wrappy": { - "version": "1.0.2" + "version": "1.0.2", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "xregexp": { "version": "4.4.1", + "integrity": "sha512-2u9HwfadaJaY9zHtRRnH6BY6CQVNQKkYm3oLtC9gJXXzfsbACg5X5e4EZZGVAH+YIfa+QA9lsFQTTe3HURF3ag==", "dev": true, "requires": { "@babel/runtime-corejs3": "^7.12.1" @@ -7010,10 +7443,12 @@ }, "yallist": { "version": "4.0.0", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "zip-stream": { "version": "4.1.0", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", "requires": { "archiver-utils": "^2.1.0", "compress-commons": "^4.1.0", diff --git a/meta/gpg_keys/tari-bot.asc b/meta/gpg_keys/tari-bot.asc new file mode 100644 index 00000000000..11a2a404fdb --- /dev/null +++ b/meta/gpg_keys/tari-bot.asc @@ -0,0 +1,51 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGFgMJcBEADVSUEftbMVQz4kljQ+Z81CMT17OUJxklYcRUmd5XxKjRyAwUxm +K61fv0HNOewWfpR2scvGuBr612t6ULpvkjJ0po0lYSYBB+eFlicN9cPfvllFOcNo +n2WCv+uIn5cEkKFtdMOjHBEmIJEFDELEK/yCmkqlWR7LcuBusuxh+DE5A/5KW/g/ +fDeWM7aJ/AYipROCET4ZJ+eYnUZa2QMBsczYeh322++5WtwUxf5fQRVhZMYoON/k +ncaqCp/LegmLIs1YX3wySbb22ParnU6Vd5Lc3+37wQ9mEfU24nERiyre8+kBmVDA +wy6utDch6WecUdkDhTK6OYZjy5FAhcfsUt21fk2WRaMJNmpUqhHnT87PHLd6/f/k +Ew4japxntEksEwsyEMbwu4vz7X0DvA1JGJuPxvSDhT9WgofHpF7J2k0KC8ADOnQk +W/2B/T1LvUbNBp4uS75/dE0geaO3VQf0hLH1WMQT8gfBOQOY+7hbyqW5TkL3MH9q +BDU6OGPEVpWHSq+I2EDtu44/YHH+SH42ft8zDrAJEMauOb8ZnyY3MplDp4zzAFJD +2luiqkoMxfzUCNvVfV2h4YD7HsHofY7Bi54f39fzEyKacOEBUb+Z3W/ubKW1sa/x +n0j3S3uTPqiWtvWVKQclWu3qoEtBCd/nZyVRr/XK2Yhjm/IXug1b15PRWwARAQAB +tCJ0YXJpLWRlcGxveSA8YnVnX3JlcG9ydHNAdGFyaS5jb20+iQJOBBMBCgA4FiEE +4awPVibQ+2EF4lwlMpI6HwoN0dkFAmFgMJcCGwMFCwkIBwIGFQoJCAsCBBYCAwEC +HgECF4AACgkQMpI6HwoN0dl+ww//afaEYAlevnFxBNpWVNOccVznNfTL2ht8Wfco +FpkQOybCdtahUnY8XcbqjvKT0krs8+NoSHoNgbfA6Cviqb8QIMgVEbxD1OqC8LxN +tgSnYPd4AJ1888SkmeH+n0h8+L5Lx84QzgN6nXCBNnkVnxu8WTPxBNqGrkNzHEdG +x3XRIJZZ8lw1D9uADo6C5cKFYgWlBYkIW3Lr7TxDO/eCoiM5dNc8zu0su2EIIads +BDtCYekdWzcAes2aDGJaO2uGBvn1cDxf1TDEJuB7Ps95gNk1KqwYuWDq4ekZbGYD +JrI62zsDgvz4sWs0+/UUn4lfdOvoNdmgE3TEDAMEt4sj0q4Low+Fa1UjLLqHBRw3 +iAtS63DDZhTgzMeirfXJCd4G8UuKsJetv5ts40A+jaLwK21iYq2zEe4g+V5rfUgr +Q1ECNyVOSWFgz9SAgpLjfdxiuQtVw2rAIneECOo0uJV8E7rgLUrbqinS33fTgbgL +bU5Wdax1tUuGqnPecHyOjrsJ5Yg006RW07azhprngG0G86H6/KOBIdRBppJcfu7H +bvBOWAaEdfpGs048gvc10Vp3Tx4xGNrEaDUHNmWGWVTYTE8SahyyE8H64BEH5T3d +Zc3ICRCMnoVv2FTJnk1a7u6gds1CHDgglk5GRzC/r1YwP4PxQA+xlVsjjtAQvaYa +NiUSp+a5Ag0EYWAwlwEQANBaPPsj3GoNLjjZjOXm56P7fUTARz9+6hnf5QanW+RK +PdZxNqkq2bIwHTeCBeYVMYPYQpJP7oUgA++nl/BMIfJFYEYGlzugIVDnKnNzjY4A +wsCP/ddjFIpriysrJNSA2UJGDzGiiqgXuQqVLVRbYQqyx3GaaevQKuCU0WJnE16V +Y9+vCsnoeKq1SXenNV2GoKlOAl9SAMe45Kv0jL6PiFmXcgEedEUtcCp7G08vqjmR +ALUVmlxJhYjjA6067mIPrHvQSohScDPpD5Cw6Hjtv7TOQEWQucEBJghVqXliUXzb +hDCQxCWVlS9RJMiucKjo2I1Jcpy5jg1+4DLnoZlVp7TYnRl641/nhgknp8FhJjPb +X48QfPTVLo2TmpTL5setab7zP88abSz17nXy0c4jsvaNT7kcLVW4jAsu5OYYxxBA +ojTfNImXpCW533ReDUREOM+E4/iZnSAtraO9xg8lOJhnAA8KEJRk2PQQz2PF+Lwf +1kw5h2ZzsRz+eG5AUB2utWIQU+YIL4rilqazEM4VoFM7GUnW3X22ucXjDpLqkfJ0 +6BqOvB/xDLvFwnv2cDD8tdpaEMh9PBF8rwTk/XyK8Ai6WmCtcNCzCGlWbWgLlVod +ohc7bgg81mmauJjYAo5v86EF9rBxxuckRCTY3gq89QiRKOOTFsILEaiqkPpwVb61 +ABEBAAGJAjYEGAEKACAWIQThrA9WJtD7YQXiXCUykjofCg3R2QUCYWAwlwIbDAAK +CRAykjofCg3R2fUEEACj41tKTSQ/ZxFU7LwGOLVNOobDVbqGkuGCSncRgeExFAB9 +15b77CUUz4Pg/TwusfgyYFVziEMOIZz6sQI6LLm8mm1rrT+lzsnoJBcvi1dEI9g0 +9+Pv5iLf0p681A2/KqPjlMR9QTx1xXLHRIDQ7w4oUHeebm48IJ700SrNP5AVjMJX +67TPuB82wHqHzPc8/dFh0kVak+JcoIKlhuFzj5uvJjRd6k+yc+p8LUtRToISJExY +r252x/vJKjSUD1xsqWeexk8RFYwc8N4ZwF/qrLO1gh6ghdn42tTXy4azzIiDiF47 +kgGWi0/QzZN3J0AWG14H4+fzG6pW9K/8AwcqTL0AKLyIi9jsX7wwthzqcZMdh6/N +hsYe9EmJDKVghX7WfBkt543k+OyRAWHp3V2BmeX5YPYcCm8oHrwu7HkPakhNjT3l +/tw6IE/Z4pnmNg2uONeYi+3vyfrJ3kHCSPmMuhKFDd3VuGH/HDvAHA2LSlwEjQ56 +zHOkccwk8MrW6tkfY7Py+Rf++YC/QK4IhJyToLkfxSRfvTpyrEajTNoMxo5sUBwg +x3INoDiSzVh+oTC4PS81f+pUTziryDez8/QIrItRzoF+SYF7Gw4CR/1R+++cptra +sTuY6gsdJ3X5+cbhX1wsK53KyXtkAaz/ca6vhBtwsuYpYcxKFySG81wC95K5oA== +=LDCN +-----END PGP PUBLIC KEY BLOCK-----