diff --git a/.pnp.cjs b/.pnp.cjs index 7bd36798d56..fb560b39ab7 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -2698,7 +2698,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "./packages/bench-suite/",\ "packageDependencies": [\ ["@dashevo/bench-suite", "workspace:packages/bench-suite"],\ - ["@dashevo/dashcore-lib", "npm:0.21.0"],\ + ["@dashevo/dashcore-lib", "npm:0.21.1"],\ ["@dashevo/dpns-contract", "workspace:packages/dpns-contract"],\ ["@dashevo/wallet-lib", "workspace:packages/wallet-lib"],\ ["@dashevo/wasm-dpp", "workspace:packages/wasm-dpp"],\ @@ -2734,7 +2734,7 @@ const RAW_RUNTIME_STATE = ["@dashevo/bls", "npm:1.2.9"],\ ["@dashevo/dapi-client", "workspace:packages/js-dapi-client"],\ ["@dashevo/dapi-grpc", "workspace:packages/dapi-grpc"],\ - ["@dashevo/dashcore-lib", "npm:0.21.0"],\ + ["@dashevo/dashcore-lib", "npm:0.21.1"],\ ["@dashevo/dashd-rpc", "npm:18.2.0"],\ ["@dashevo/dp-services-ctl", "https://github.com/dashevo/js-dp-services-ctl.git#commit=3976076b0018c5b4632ceda4c752fc597f27a640"],\ ["@dashevo/grpc-common", "workspace:packages/js-grpc-common"],\ @@ -2779,7 +2779,7 @@ const RAW_RUNTIME_STATE = ["@babel/core", "npm:7.23.3"],\ ["@dashevo/dapi-grpc", "workspace:packages/dapi-grpc"],\ ["@dashevo/dash-spv", "workspace:packages/dash-spv"],\ - ["@dashevo/dashcore-lib", "npm:0.21.0"],\ + ["@dashevo/dashcore-lib", "npm:0.21.1"],\ ["@dashevo/grpc-common", "workspace:packages/js-grpc-common"],\ ["@dashevo/wasm-dpp", "workspace:packages/wasm-dpp"],\ ["assert-browserify", "npm:2.0.0"],\ @@ -2871,7 +2871,7 @@ const RAW_RUNTIME_STATE = ["@dashevo/dash-spv", "workspace:packages/dash-spv"],\ ["@dashevo/dark-gravity-wave", "npm:1.1.1"],\ ["@dashevo/dash-util", "npm:2.0.3"],\ - ["@dashevo/dashcore-lib", "npm:0.21.0"],\ + ["@dashevo/dashcore-lib", "npm:0.21.1"],\ ["chai", "npm:4.3.10"],\ ["eslint", "npm:8.53.0"],\ ["eslint-config-airbnb-base", "virtual:e2d057e7cc143d3cb9bec864f4a2d862441b5a09f81f8e6c46e7a098cbc89e4d07017cc6e2e2142d5704bb55da853cbec2d025ebc0b30e8696c31380c00f2c7d#npm:15.0.0"],\ @@ -2898,10 +2898,10 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["@dashevo/dashcore-lib", [\ - ["npm:0.21.0", {\ - "packageLocation": "./.yarn/cache/@dashevo-dashcore-lib-npm-0.21.0-1997e852e6-fb4419623f.zip/node_modules/@dashevo/dashcore-lib/",\ + ["npm:0.21.1", {\ + "packageLocation": "./.yarn/cache/@dashevo-dashcore-lib-npm-0.21.1-2fe9b7647d-68502e4078.zip/node_modules/@dashevo/dashcore-lib/",\ "packageDependencies": [\ - ["@dashevo/dashcore-lib", "npm:0.21.0"],\ + ["@dashevo/dashcore-lib", "npm:0.21.1"],\ ["@dashevo/bls", "npm:1.2.9"],\ ["@dashevo/x11-hash-js", "npm:1.0.2"],\ ["@types/node", "npm:12.20.37"],\ @@ -3084,13 +3084,14 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["@dashevo/platform-test-suite", "workspace:packages/platform-test-suite"],\ ["@dashevo/dapi-client", "workspace:packages/js-dapi-client"],\ - ["@dashevo/dashcore-lib", "npm:0.21.0"],\ + ["@dashevo/dashcore-lib", "npm:0.21.1"],\ ["@dashevo/dpns-contract", "workspace:packages/dpns-contract"],\ ["@dashevo/feature-flags-contract", "workspace:packages/feature-flags-contract"],\ ["@dashevo/grpc-common", "workspace:packages/js-grpc-common"],\ ["@dashevo/masternode-reward-shares-contract", "workspace:packages/masternode-reward-shares-contract"],\ ["@dashevo/wallet-lib", "workspace:packages/wallet-lib"],\ ["@dashevo/wasm-dpp", "workspace:packages/wasm-dpp"],\ + ["@dashevo/withdrawals-contract", "workspace:packages/withdrawals-contract"],\ ["assert", "npm:2.0.0"],\ ["assert-browserify", "npm:2.0.0"],\ ["browserify-zlib", "npm:0.2.0"],\ @@ -3177,7 +3178,7 @@ const RAW_RUNTIME_STATE = ["@dashevo/wallet-lib", "workspace:packages/wallet-lib"],\ ["@dashevo/dapi-client", "workspace:packages/js-dapi-client"],\ ["@dashevo/dash-spv", "workspace:packages/dash-spv"],\ - ["@dashevo/dashcore-lib", "npm:0.21.0"],\ + ["@dashevo/dashcore-lib", "npm:0.21.1"],\ ["@dashevo/grpc-common", "workspace:packages/js-grpc-common"],\ ["@dashevo/wasm-dpp", "workspace:packages/wasm-dpp"],\ ["@yarnpkg/pnpify", "npm:4.0.0-rc.42"],\ @@ -3239,7 +3240,7 @@ const RAW_RUNTIME_STATE = ["@babel/core", "npm:7.23.3"],\ ["@babel/preset-env", "virtual:e2d057e7cc143d3cb9bec864f4a2d862441b5a09f81f8e6c46e7a098cbc89e4d07017cc6e2e2142d5704bb55da853cbec2d025ebc0b30e8696c31380c00f2c7d#npm:7.23.3"],\ ["@dashevo/bls", "npm:1.2.9"],\ - ["@dashevo/dashcore-lib", "npm:0.21.0"],\ + ["@dashevo/dashcore-lib", "npm:0.21.1"],\ ["@dashevo/dpns-contract", "workspace:packages/dpns-contract"],\ ["@types/bs58", "npm:4.0.1"],\ ["@types/node", "npm:14.17.34"],\ @@ -8625,13 +8626,14 @@ const RAW_RUNTIME_STATE = ["@dashevo/bls", "npm:1.2.9"],\ ["@dashevo/dapi-client", "workspace:packages/js-dapi-client"],\ ["@dashevo/dapi-grpc", "workspace:packages/dapi-grpc"],\ - ["@dashevo/dashcore-lib", "npm:0.21.0"],\ + ["@dashevo/dashcore-lib", "npm:0.21.1"],\ ["@dashevo/dashpay-contract", "workspace:packages/dashpay-contract"],\ ["@dashevo/dpns-contract", "workspace:packages/dpns-contract"],\ ["@dashevo/grpc-common", "workspace:packages/js-grpc-common"],\ ["@dashevo/masternode-reward-shares-contract", "workspace:packages/masternode-reward-shares-contract"],\ ["@dashevo/wallet-lib", "workspace:packages/wallet-lib"],\ ["@dashevo/wasm-dpp", "workspace:packages/wasm-dpp"],\ + ["@dashevo/withdrawals-contract", "workspace:packages/withdrawals-contract"],\ ["@types/chai", "npm:4.2.22"],\ ["@types/dirty-chai", "npm:2.0.2"],\ ["@types/mocha", "npm:8.2.3"],\ @@ -8703,7 +8705,7 @@ const RAW_RUNTIME_STATE = ["@babel/eslint-parser", "virtual:880cda903c2a2be387819a3f857d21494004437a03c92969b9853f7bdeebdfed08d417e68364ee9e158338603a6d78d690c457a55ab11e56398bc10f0ad232fc#npm:7.23.3"],\ ["@dashevo/bls", "npm:1.2.9"],\ ["@dashevo/dapi-client", "workspace:packages/js-dapi-client"],\ - ["@dashevo/dashcore-lib", "npm:0.21.0"],\ + ["@dashevo/dashcore-lib", "npm:0.21.1"],\ ["@dashevo/dashd-rpc", "npm:18.2.0"],\ ["@dashevo/dashpay-contract", "workspace:packages/dashpay-contract"],\ ["@dashevo/docker-compose", "npm:0.24.4"],\ diff --git a/.yarn/cache/@dashevo-dashcore-lib-npm-0.21.0-1997e852e6-fb4419623f.zip b/.yarn/cache/@dashevo-dashcore-lib-npm-0.21.0-1997e852e6-fb4419623f.zip deleted file mode 100644 index 81560d8bc59..00000000000 Binary files a/.yarn/cache/@dashevo-dashcore-lib-npm-0.21.0-1997e852e6-fb4419623f.zip and /dev/null differ diff --git a/.yarn/cache/@dashevo-dashcore-lib-npm-0.21.1-2fe9b7647d-68502e4078.zip b/.yarn/cache/@dashevo-dashcore-lib-npm-0.21.1-2fe9b7647d-68502e4078.zip new file mode 100644 index 00000000000..56bf009b704 Binary files /dev/null and b/.yarn/cache/@dashevo-dashcore-lib-npm-0.21.1-2fe9b7647d-68502e4078.zip differ diff --git a/Cargo.lock b/Cargo.lock index 1f32aa20cba..a7ff27e8cb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -944,7 +944,7 @@ dependencies = [ "prost 0.11.9", "serde", "serde_bytes", - "tenderdash-proto", + "tenderdash-proto 0.14.0-dev.6 (git+https://github.com/dashpay/rs-tenderdash-abci)", "tonic", "tonic-build", ] @@ -997,7 +997,7 @@ dependencies = [ [[package]] name = "dashcore" version = "0.30.0" -source = "git+https://github.com/dashpay/rust-dashcore?branch=master#35a166e76256edc5ca491163d9050ead0e0f50d5" +source = "git+https://github.com/dashpay/rust-dashcore?branch=master#25053c8e31e57d26f5876eff50bfc06ad9ee8b72" dependencies = [ "anyhow", "bech32", @@ -1013,12 +1013,12 @@ dependencies = [ [[package]] name = "dashcore-private" version = "0.1.0" -source = "git+https://github.com/dashpay/rust-dashcore?branch=master#35a166e76256edc5ca491163d9050ead0e0f50d5" +source = "git+https://github.com/dashpay/rust-dashcore?branch=master#25053c8e31e57d26f5876eff50bfc06ad9ee8b72" [[package]] name = "dashcore-rpc" version = "0.15.1" -source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=master#d7df63efcd59815bb9a5bbc05d6c82dfc24df2c6" +source = "git+https://github.com/dashpay/rust-dashcore-rpc?tag=v0.15.2#9e596142f589d32a7347872b8f5c2b3b2ed54257" dependencies = [ "dashcore-private", "dashcore-rpc-json", @@ -1033,7 +1033,7 @@ dependencies = [ [[package]] name = "dashcore-rpc-json" version = "0.15.1" -source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=master#d7df63efcd59815bb9a5bbc05d6c82dfc24df2c6" +source = "git+https://github.com/dashpay/rust-dashcore-rpc?tag=v0.15.2#9e596142f589d32a7347872b8f5c2b3b2ed54257" dependencies = [ "bincode 2.0.0-rc.3", "dashcore", @@ -1047,7 +1047,7 @@ dependencies = [ [[package]] name = "dashcore_hashes" version = "0.12.0" -source = "git+https://github.com/dashpay/rust-dashcore?branch=master#35a166e76256edc5ca491163d9050ead0e0f50d5" +source = "git+https://github.com/dashpay/rust-dashcore?branch=master#25053c8e31e57d26f5876eff50bfc06ad9ee8b72" dependencies = [ "dashcore-private", "rs-x11-hash", @@ -1261,7 +1261,6 @@ dependencies = [ "base64 0.20.0", "bincode 2.0.0-rc.3", "bs58 0.4.0", - "bytes", "chrono", "ciborium", "clap 4.4.18", @@ -4133,7 +4132,7 @@ dependencies = [ [[package]] name = "tenderdash-abci" version = "0.14.0-dev.6" -source = "git+https://github.com/dashpay/rs-tenderdash-abci?rev=251386f9e4109c89ca628226c981c489b80aed80#251386f9e4109c89ca628226c981c489b80aed80" +source = "git+https://github.com/dashpay/rs-tenderdash-abci?tag=v0.14.0-dev.6#701ba0fa92cb0f6bb5b986ec1ad141390040116c" dependencies = [ "bytes", "futures", @@ -4141,7 +4140,7 @@ dependencies = [ "lhash", "prost 0.12.3", "semver", - "tenderdash-proto", + "tenderdash-proto 0.14.0-dev.6 (git+https://github.com/dashpay/rs-tenderdash-abci?tag=v0.14.0-dev.6)", "thiserror", "tokio", "tokio-util", @@ -4154,7 +4153,25 @@ dependencies = [ [[package]] name = "tenderdash-proto" version = "0.14.0-dev.6" -source = "git+https://github.com/dashpay/rs-tenderdash-abci?rev=251386f9e4109c89ca628226c981c489b80aed80#251386f9e4109c89ca628226c981c489b80aed80" +source = "git+https://github.com/dashpay/rs-tenderdash-abci?tag=v0.14.0-dev.6#701ba0fa92cb0f6bb5b986ec1ad141390040116c" +dependencies = [ + "bytes", + "chrono", + "derive_more", + "flex-error", + "num-derive", + "num-traits", + "prost 0.12.3", + "serde", + "subtle-encoding", + "tenderdash-proto-compiler 0.14.0-dev.6 (git+https://github.com/dashpay/rs-tenderdash-abci?tag=v0.14.0-dev.6)", + "time", +] + +[[package]] +name = "tenderdash-proto" +version = "0.14.0-dev.6" +source = "git+https://github.com/dashpay/rs-tenderdash-abci#a2151d40e80d4ce7076160017a6fcbe21d6e252e" dependencies = [ "bytes", "chrono", @@ -4165,14 +4182,28 @@ dependencies = [ "prost 0.12.3", "serde", "subtle-encoding", - "tenderdash-proto-compiler", + "tenderdash-proto-compiler 0.14.0-dev.6 (git+https://github.com/dashpay/rs-tenderdash-abci)", "time", ] [[package]] name = "tenderdash-proto-compiler" version = "0.14.0-dev.6" -source = "git+https://github.com/dashpay/rs-tenderdash-abci?rev=251386f9e4109c89ca628226c981c489b80aed80#251386f9e4109c89ca628226c981c489b80aed80" +source = "git+https://github.com/dashpay/rs-tenderdash-abci?tag=v0.14.0-dev.6#701ba0fa92cb0f6bb5b986ec1ad141390040116c" +dependencies = [ + "fs_extra", + "prost-build 0.12.3", + "regex", + "tempfile", + "ureq", + "walkdir", + "zip", +] + +[[package]] +name = "tenderdash-proto-compiler" +version = "0.14.0-dev.6" +source = "git+https://github.com/dashpay/rs-tenderdash-abci#a2151d40e80d4ce7076160017a6fcbe21d6e252e" dependencies = [ "fs_extra", "prost-build 0.12.3", diff --git a/packages/bench-suite/package.json b/packages/bench-suite/package.json index 3d249a0e74e..ecaff2a10c8 100644 --- a/packages/bench-suite/package.json +++ b/packages/bench-suite/package.json @@ -8,7 +8,7 @@ "lint": "" }, "dependencies": { - "@dashevo/dashcore-lib": "~0.21.0", + "@dashevo/dashcore-lib": "~0.21.1", "@dashevo/dpns-contract": "workspace:*", "@dashevo/wallet-lib": "workspace:*", "@dashevo/wasm-dpp": "workspace:*", diff --git a/packages/dapi-grpc/Cargo.toml b/packages/dapi-grpc/Cargo.toml index e16b24b1f36..d57450e00d6 100644 --- a/packages/dapi-grpc/Cargo.toml +++ b/packages/dapi-grpc/Cargo.toml @@ -30,7 +30,7 @@ tonic = { version = "0.9.2", features = [ ], default-features = false } serde = { version = "1.0.171", optional = true, features = ["derive"] } serde_bytes = { version = "0.11.12", optional = true } -tenderdash-proto = { git = "https://github.com/dashpay/rs-tenderdash-abci", rev = "251386f9e4109c89ca628226c981c489b80aed80" } +tenderdash-proto = { git = "https://github.com/dashpay/rs-tenderdash-abci", version = "0.14.0-dev.6" } dapi-grpc-macros = { path = "../rs-dapi-grpc-macros" } platform-version = { path = "../rs-platform-version" } diff --git a/packages/dapi/package.json b/packages/dapi/package.json index 982f6b9312d..1ba96917fab 100644 --- a/packages/dapi/package.json +++ b/packages/dapi/package.json @@ -35,7 +35,7 @@ "dependencies": { "@dashevo/bls": "~1.2.9", "@dashevo/dapi-grpc": "workspace:*", - "@dashevo/dashcore-lib": "~0.21.0", + "@dashevo/dashcore-lib": "~0.21.1", "@dashevo/dashd-rpc": "^18.2.0", "@dashevo/grpc-common": "workspace:*", "@dashevo/wasm-dpp": "workspace:*", diff --git a/packages/dash-spv/package.json b/packages/dash-spv/package.json index 90a85daeae4..3a10406d157 100644 --- a/packages/dash-spv/package.json +++ b/packages/dash-spv/package.json @@ -14,7 +14,7 @@ "dependencies": { "@dashevo/dark-gravity-wave": "^1.1.1", "@dashevo/dash-util": "^2.0.3", - "@dashevo/dashcore-lib": "~0.21.0", + "@dashevo/dashcore-lib": "~0.21.1", "levelup": "^4.4.0", "memdown": "^5.1.0", "wasm-x11-hash": "~0.0.2" diff --git a/packages/dashmate/configs/defaults/getBaseConfigFactory.js b/packages/dashmate/configs/defaults/getBaseConfigFactory.js index ab4227806da..62b20e446dc 100644 --- a/packages/dashmate/configs/defaults/getBaseConfigFactory.js +++ b/packages/dashmate/configs/defaults/getBaseConfigFactory.js @@ -72,7 +72,7 @@ export default function getBaseConfigFactory(homeDir) { port: 3001, }, docker: { - image: 'dashpay/dashd:20.1.0-devpr5806.a1814ce2', commandArgs: [], + image: 'dashpay/dashd:20', commandArgs: [], }, p2p: { host: '0.0.0.0', diff --git a/packages/dashmate/configs/defaults/getLocalConfigFactory.js b/packages/dashmate/configs/defaults/getLocalConfigFactory.js index b5462511965..aecfba296f5 100644 --- a/packages/dashmate/configs/defaults/getLocalConfigFactory.js +++ b/packages/dashmate/configs/defaults/getLocalConfigFactory.js @@ -24,6 +24,9 @@ export default function getLocalConfigFactory(getBaseConfig) { }, }, core: { + docker: { + image: 'dashpay/dashd:20.1.0-devpr5855.4d19698f', commandArgs: [], + }, p2p: { port: 20001, }, diff --git a/packages/dashmate/configs/defaults/getTestnetConfigFactory.js b/packages/dashmate/configs/defaults/getTestnetConfigFactory.js index 83bdb95383c..3edc08edb89 100644 --- a/packages/dashmate/configs/defaults/getTestnetConfigFactory.js +++ b/packages/dashmate/configs/defaults/getTestnetConfigFactory.js @@ -24,6 +24,9 @@ export default function getTestnetConfigFactory(homeDir, getBaseConfig) { }, }, core: { + docker: { + image: 'dashpay/dashd:20.1.0-devpr5855.4d19698f', commandArgs: [], + }, p2p: { port: 19999, }, diff --git a/packages/dashmate/configs/getConfigFileMigrationsFactory.js b/packages/dashmate/configs/getConfigFileMigrationsFactory.js index b68d792c31b..0e28e6b9cbd 100644 --- a/packages/dashmate/configs/getConfigFileMigrationsFactory.js +++ b/packages/dashmate/configs/getConfigFileMigrationsFactory.js @@ -23,6 +23,22 @@ export default function getConfigFileMigrationsFactory(homeDir, defaultConfigs) const base = defaultConfigs.get('base'); const testnet = defaultConfigs.get('testnet'); + /** + * @param {string} name + * @param {string} group + * @return {Config} + */ + function getDefaultConfigByNameOrGroup(name, group) { + let baseConfigName = name; + if (group !== null && defaultConfigs.has(group)) { + baseConfigName = group; + } else if (!defaultConfigs.has(baseConfigName)) { + baseConfigName = 'base'; + } + + return defaultConfigs.get(baseConfigName); + } + return { '0.24.0': (configFile) => { Object.entries(configFile.configs) @@ -426,6 +442,17 @@ export default function getConfigFileMigrationsFactory(homeDir, defaultConfigs) return configFile; }, + '1.0.0-dev.4': (configFile) => { + Object.entries(configFile.configs) + .forEach(([name, options]) => { + const defaultConfig = getDefaultConfigByNameOrGroup(name, options.group); + options.core.docker.image = defaultConfig.get('core.docker.image'); + + options.platform.drive.tenderdash.docker.image = defaultConfig.get('platform.drive.tenderdash.docker.image'); + }); + + return configFile; + }, }; } diff --git a/packages/dashmate/package.json b/packages/dashmate/package.json index eddc62b2a02..9cdde2c9c64 100644 --- a/packages/dashmate/package.json +++ b/packages/dashmate/package.json @@ -58,7 +58,7 @@ "@babel/eslint-parser": "^7.23.3", "@dashevo/bls": "~1.2.9", "@dashevo/dapi-client": "workspace:*", - "@dashevo/dashcore-lib": "~0.21.0", + "@dashevo/dashcore-lib": "~0.21.1", "@dashevo/dashd-rpc": "^18.2.0", "@dashevo/dashpay-contract": "workspace:*", "@dashevo/docker-compose": "^0.24.4", diff --git a/packages/dashmate/src/core/CoreService.js b/packages/dashmate/src/core/CoreService.js index 73c2c243156..88bd883bd00 100644 --- a/packages/dashmate/src/core/CoreService.js +++ b/packages/dashmate/src/core/CoreService.js @@ -8,6 +8,7 @@ export default class CoreService { constructor(config, rpcClient, dockerContainer) { this.config = config; this.rpcClient = rpcClient; + rpcClient.setTimeout(240 * 1000); this.dockerContainer = dockerContainer; } diff --git a/packages/js-dapi-client/package.json b/packages/js-dapi-client/package.json index 7567d81fa9d..2bef1169e5a 100644 --- a/packages/js-dapi-client/package.json +++ b/packages/js-dapi-client/package.json @@ -28,7 +28,7 @@ "dependencies": { "@dashevo/dapi-grpc": "workspace:*", "@dashevo/dash-spv": "workspace:*", - "@dashevo/dashcore-lib": "~0.21.0", + "@dashevo/dashcore-lib": "~0.21.1", "@dashevo/grpc-common": "workspace:*", "@dashevo/wasm-dpp": "workspace:*", "bs58": "^4.0.1", diff --git a/packages/js-dash-sdk/package.json b/packages/js-dash-sdk/package.json index 78da9c3cb84..932fdc438da 100644 --- a/packages/js-dash-sdk/package.json +++ b/packages/js-dash-sdk/package.json @@ -41,13 +41,14 @@ "@dashevo/bls": "~1.2.9", "@dashevo/dapi-client": "workspace:*", "@dashevo/dapi-grpc": "workspace:*", - "@dashevo/dashcore-lib": "~0.21.0", + "@dashevo/dashcore-lib": "~0.21.1", "@dashevo/dashpay-contract": "workspace:*", "@dashevo/dpns-contract": "workspace:*", "@dashevo/grpc-common": "workspace:*", "@dashevo/masternode-reward-shares-contract": "workspace:*", "@dashevo/wallet-lib": "workspace:*", "@dashevo/wasm-dpp": "workspace:*", + "@dashevo/withdrawals-contract": "workspace:*", "bs58": "^4.0.1", "node-inspect-extracted": "^1.0.8", "winston": "^3.2.1" diff --git a/packages/js-dash-sdk/src/SDK/Client/Client.ts b/packages/js-dash-sdk/src/SDK/Client/Client.ts index c5ed17cec57..aab3eecfbab 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Client.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Client.ts @@ -6,6 +6,7 @@ import DAPIClient from '@dashevo/dapi-client'; import { contractId as dpnsContractId } from '@dashevo/dpns-contract/lib/systemIds'; import { contractId as dashpayContractId } from '@dashevo/dashpay-contract/lib/systemIds'; import { contractId as masternodeRewardSharesContractId } from '@dashevo/masternode-reward-shares-contract/lib/systemIds'; +import { contractId as withdrawalsContractId } from '@dashevo/withdrawals-contract/lib/systemIds'; import { Platform } from './Platform'; import { ClientApps, ClientAppsOptions } from './ClientApps'; @@ -138,6 +139,9 @@ export class Client extends EventEmitter { masternodeRewardShares: { contractId: masternodeRewardSharesContractId, }, + withdrawals: { + contractId: withdrawalsContractId, + }, ...this.options.apps, }); diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/Fetcher/withRetry.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/Fetcher/withRetry.ts index b13472a06db..031601620ed 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/Fetcher/withRetry.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/Fetcher/withRetry.ts @@ -1,4 +1,4 @@ -import fibonacci from '../../../../utils/fibonacci'; +import { fibonacci } from '../../../../utils/fibonacci'; import { wait } from '../../../../utils/wait'; /** diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts index 07e26cb522b..b689488bfd0 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts @@ -21,6 +21,7 @@ import getIdentity from './methods/identities/get'; import registerIdentity from './methods/identities/register'; import topUpIdentity from './methods/identities/topUp'; import creditTransferIdentity from './methods/identities/creditTransfer'; +import creditWithdrawal from './methods/identities/creditWithdrawal'; import updateIdentity from './methods/identities/update'; import createIdentityCreateTransition from './methods/identities/internal/createIdentityCreateTransition'; import createIdentityTopUpTransition from './methods/identities/internal/createIdentityTopUpTransition'; @@ -77,6 +78,7 @@ interface Identities { register: Function, topUp: Function, creditTransfer: Function, + withdrawCredits: Function, update: Function, utils: { createAssetLockTransaction: Function @@ -181,6 +183,7 @@ export class Platform { topUp: topUpIdentity.bind(this), creditTransfer: creditTransferIdentity.bind(this), update: updateIdentity.bind(this), + withdrawCredits: creditWithdrawal.bind(this), utils: { createAssetLockProof: createAssetLockProof.bind(this), createAssetLockTransaction: createAssetLockTransaction.bind(this), diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/creditWithdrawal.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/creditWithdrawal.ts new file mode 100644 index 00000000000..3d1ad5e9907 --- /dev/null +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/creditWithdrawal.ts @@ -0,0 +1,114 @@ +import { Identity } from '@dashevo/wasm-dpp'; +import { Address, Script } from '@dashevo/dashcore-lib'; +import broadcastStateTransition from '../../broadcastStateTransition'; +import { Platform } from '../../Platform'; +import { signStateTransition } from '../../signStateTransition'; +import { nearestGreaterFibonacci } from '../../../../../utils/fibonacci'; + +export const STATUSES = { + PENDING: 0, + POOLED: 1, + BROADCASTED: 2, + COMPLETED: 3, +}; + +// Implement remaining pooling types when they ready on drive side +const DEFAULT_POOLING = 0; + +// TODO: add to dashcore-lib +// Asset unlock TX size is fixed with the default pooling +// since it has zero inputs and only one output +const ASSET_UNLOCK_TX_SIZE = 190; + +// Minimal accepted core fee per byte to avoid low fee error from core +const MIN_ASSET_UNLOCK_CORE_FEE_PER_BYTE = 5; + +// Minimal withdrawal amount in credits to avoid dust error from core +const MINIMAL_WITHDRAWAL_AMOUNT = ASSET_UNLOCK_TX_SIZE * MIN_ASSET_UNLOCK_CORE_FEE_PER_BYTE * 1000; + +type WithdrawalOptions = { + signingKeyIndex: number +}; + +/** Creates platform credits withdrawal request + * @param identity - identity to withdraw from + * @param amount - amount of credits to withdraw + * @param to - Dash L1 address + * @param options - withdrawal options + */ +export async function creditWithdrawal( + this: Platform, + identity: Identity, + amount: number, + to: string, + options: WithdrawalOptions = { + signingKeyIndex: 2, + }, +): Promise { + await this.initialize(); + + const { dpp } = this; + + let toAddress: Address; + try { + toAddress = new Address(to, this.client.network); + } catch (e) { + throw new Error(`Invalid core recipient "${to}" for network ${this.client.network}`); + } + this.logger.debug(`[Identity#creditWithdrawal] credits withdrawal from ${identity.getId().toString()} to ${toAddress.toString()} with amount ${amount}`); + + const outputScript = Script.buildPublicKeyHashOut(toAddress); + + const balance = identity.getBalance(); + if (amount > balance) { + throw new Error(`Withdrawal amount "${amount}" is bigger that identity balance "${balance}"`); + } + + if (amount < MINIMAL_WITHDRAWAL_AMOUNT) { + throw new Error(`Withdrawal amount "${amount}" is less than minimal allowed withdrawal amount "${MINIMAL_WITHDRAWAL_AMOUNT}"`); + } + + if (!this.client.wallet) { + throw new Error('Wallet is not initialized'); + } + + // Divide by 1000 as stated in policy for GetDustThreshold + // https://github.com/dashpay/dash/blob/master/src/policy/policy.cpp#L23 + const minRelayFeePerByte = Math.ceil(this.client.wallet.storage + .getDefaultChainStore().state.fees.minRelay / 1000); + + const coreFeePerByte = minRelayFeePerByte > MIN_ASSET_UNLOCK_CORE_FEE_PER_BYTE + ? nearestGreaterFibonacci(minRelayFeePerByte) : MIN_ASSET_UNLOCK_CORE_FEE_PER_BYTE; + + const revision = identity.getRevision(); + + const identityCreditWithdrawalTransition = dpp.identity + .createIdentityCreditWithdrawalTransition( + identity.getId(), + BigInt(amount), + coreFeePerByte, + DEFAULT_POOLING, + // @ts-ignore + outputScript.toBuffer(), + BigInt(revision + 1), + ); + + this.logger.silly('[Identity#creditWithdrawal] Created IdentityCreditWithdrawalTransition'); + + await signStateTransition( + this, + identityCreditWithdrawalTransition, + identity, + options.signingKeyIndex, + ); + + await broadcastStateTransition(this, identityCreditWithdrawalTransition, { + skipValidation: true, + }); + + this.logger.silly('[Identity#creditWithdrawal] Broadcasted IdentityCreditWithdrawalTransition'); + + return true; +} + +export default creditWithdrawal; diff --git a/packages/js-dash-sdk/src/utils/fibonacci.ts b/packages/js-dash-sdk/src/utils/fibonacci.ts index 22ceb5c1c4e..3a8fd66b88a 100644 --- a/packages/js-dash-sdk/src/utils/fibonacci.ts +++ b/packages/js-dash-sdk/src/utils/fibonacci.ts @@ -1,8 +1,16 @@ -const fibonacci = (n) => { +export const fibonacci = (n: number): number => { if (n < 2) { return n; } return fibonacci(n - 1) + fibonacci(n - 2); }; -export default fibonacci; +export const nearestGreaterFibonacci = (value: number) => { + const phi = (1 + Math.sqrt(5)) / 2; + + // Use the rearranged Binet's formula to find the nearest index + const n = Math.ceil(Math.log(value * Math.sqrt(5) + 0.5) / Math.log(phi)); + + // Calculate the Fibonacci number using Binet's formula + return Math.round(phi ** n / Math.sqrt(5)); +}; diff --git a/packages/platform-test-suite/package.json b/packages/platform-test-suite/package.json index d07d51f736b..8ced8299459 100644 --- a/packages/platform-test-suite/package.json +++ b/packages/platform-test-suite/package.json @@ -24,13 +24,14 @@ "homepage": "https://github.com/dashevo/platform-test-suite#readme", "dependencies": { "@dashevo/dapi-client": "workspace:*", - "@dashevo/dashcore-lib": "~0.21.0", + "@dashevo/dashcore-lib": "~0.21.1", "@dashevo/dpns-contract": "workspace:*", "@dashevo/feature-flags-contract": "workspace:*", "@dashevo/grpc-common": "workspace:*", "@dashevo/masternode-reward-shares-contract": "workspace:*", "@dashevo/wallet-lib": "workspace:*", "@dashevo/wasm-dpp": "workspace:*", + "@dashevo/withdrawals-contract": "workspace:*", "assert": "^2.0.0", "assert-browserify": "^2.0.0", "browserify-zlib": "^0.2.0", diff --git a/packages/platform-test-suite/test/e2e/withdrawals.spec.js b/packages/platform-test-suite/test/e2e/withdrawals.spec.js new file mode 100644 index 00000000000..50b3b43b82f --- /dev/null +++ b/packages/platform-test-suite/test/e2e/withdrawals.spec.js @@ -0,0 +1,243 @@ +const { expect } = require('chai'); + +const wait = require('@dashevo/dapi-client/lib/utils/wait'); +const { STATUSES: WITHDRAWAL_STATUSES } = require('dash/build/SDK/Client/Platform/methods/identities/creditWithdrawal'); + +const createClientWithFundedWallet = require('../../lib/test/createClientWithFundedWallet'); +const waitForSTPropagated = require('../../lib/waitForSTPropagated'); + +describe('Withdrawals', function withdrawalsTest() { + this.bail(true); + let client; + let identity; + + before(async () => { + client = await createClientWithFundedWallet( + 10000000, + ); + + await client.platform.initialize(); + }); + + after(async () => { + if (client) { + await client.disconnect(); + } + }); + + describe('Any Identity', () => { + const INITIAL_BALANCE = 1000000; + + before(async () => { + identity = await client.platform.identities.register(INITIAL_BALANCE); + + // Additional wait time to mitigate testnet latency + await waitForSTPropagated(); + }); + + it('should be able to withdraw credits', async () => { + const account = await client.getWalletAccount(); + const walletBalanceBefore = account.getTotalBalance(); + const identityBalanceBefore = identity.getBalance(); + const withdrawTo = await account.getUnusedAddress(); + const amountToWithdraw = 1000000; + + await client.platform.identities.withdrawCredits( + identity, + BigInt(amountToWithdraw), + withdrawTo.address, + ); + + // Re-fetch identity to obtain latest core chain lock height + identity = await client.platform.identities.get(identity.getId()); + const identityMetadata = identity.getMetadata().toObject(); + const { coreChainLockedHeight: initialCoreChainLockedHeight } = identityMetadata; + + // Wait for core chain lock update. + // After that drive should update document status to completed. + // (Wait 2 chainlocks on regtest since they are processed quicker, + // and withdrawal might not complete yet) + const chainLocksToWait = process.env.NETWORK === 'regtest' ? 2 : 1; + const { promise } = await client.platform.identities.utils + .waitForCoreChainLockedHeight(initialCoreChainLockedHeight + chainLocksToWait); + await promise; + + // Wait for document completion to propagate + await waitForSTPropagated(); + + // Wait for document status to be changed to COMPLETED. + let withdrawalCompleted = false; + let withdrawalDocument; + for (let i = 0; i < 10; i++) { + const withdrawals = await client.platform + .documents.get( + 'withdrawals.withdrawal', + { + where: [['$ownerId', '==', identity.getId()]], + }, + ); + + withdrawalDocument = withdrawals[withdrawals.length - 1]; + withdrawalCompleted = withdrawalDocument.get('status') === WITHDRAWAL_STATUSES.COMPLETED; + + if (withdrawalCompleted) { + break; + } + + await waitForSTPropagated(); + } + + expect(withdrawalCompleted).to.be.true(); + + const walletBalanceUpdated = account.getTotalBalance(); + + identity = await client.platform.identities.get(identity.getId()); + const identityBalanceUpdated = identity.getBalance(); + + // Should ensure balances are right + expect(walletBalanceUpdated).to.be.greaterThan(walletBalanceBefore); + expect(identityBalanceUpdated).to.be.lessThan(identityBalanceBefore); + + // Should allow deleting of the withdrawal document + await client.platform.documents.broadcast({ + delete: [withdrawalDocument], + }, identity); + }); + + it('should not be able to withdraw more than balance available', async () => { + const account = await client.getWalletAccount(); + const identityBalanceBefore = identity.getBalance(); + const withdrawTo = await account.getUnusedAddress(); + const amountToWithdraw = identityBalanceBefore * 2; + + await expect(client.platform.identities.withdrawCredits( + identity, + BigInt(amountToWithdraw), + withdrawTo.address, + )).to.be.rejectedWith(`Withdrawal amount "${amountToWithdraw}" is bigger that identity balance "${identityBalanceBefore}"`); + }); + + it('should not allow to create withdrawal with wrong security key type', async () => { + const account = await client.getWalletAccount(); + const identityBalanceBefore = identity.getBalance(); + const withdrawTo = await account.getUnusedAddress(); + const amountToWithdraw = identityBalanceBefore / 2; + + await expect(client.platform.identities.withdrawCredits( + identity, + BigInt(amountToWithdraw), + withdrawTo.address, + { + signingKeyIndex: 1, + }, + )).to.be.rejectedWith('Error conversion not implemented: Invalid public key security level HIGH. The state transition requires one of CRITICAL'); + }); + + it('should not be able to create withdrawal document', async () => { + const withdrawal = await client.platform.documents.create( + 'withdrawals.withdrawal', + identity, + { + amount: 1000, + coreFeePerByte: 1365, + pooling: 0, + outputScript: Buffer.alloc(23), + status: 0, + }, + ); + const stateTransition = client.platform.dpp.document.createStateTransition({ + create: [withdrawal], + }); + + stateTransition.setSignaturePublicKeyId(1); + + const account = await client.getWalletAccount(); + + const { privateKey } = account.identities.getIdentityHDKeyById( + identity.getId().toString(), + 1, + ); + + await stateTransition.sign( + identity.getPublicKeyById(1), + privateKey.toBuffer(), + ); + + try { + await client.platform.documents.broadcast({ + create: [withdrawal], + }, identity); + + expect.fail('should throw broadcast error'); + } catch (e) { + expect(e.message).to.be.equal('Action is not allowed'); + expect(e.code).to.equal(4001); + } + }); + + it('should not be able to delete incomplete withdrawal document', async () => { + const account = await client.getWalletAccount(); + const withdrawTo = await account.getUnusedAddress(); + + await client.platform.identities.withdrawCredits( + identity, + BigInt(1000000), + withdrawTo.address, + ); + + await waitForSTPropagated(); + + let withdrawalBroadcasted = false; + let withdrawalDocument; + // Wait for withdrawal to broadcast, otherwise there's a chance + // that test will try update document at the same time with the drive itself + while (!withdrawalBroadcasted) { + ([withdrawalDocument] = await client.platform + .documents.get( + 'withdrawals.withdrawal', + { + where: [['$ownerId', '==', identity.getId()]], + }, + )); + + withdrawalBroadcasted = withdrawalDocument.get('status') === WITHDRAWAL_STATUSES.BROADCASTED; + + await wait(1000); + } + + try { + await client.platform.documents.broadcast({ + delete: [withdrawalDocument], + }, identity); + + expect.fail('should throw broadcast error'); + } catch (e) { + expect(e.message).to.be.equal('withdrawal deletion is allowed only for COMPLETE statuses'); + expect(e.code).to.equal(4001); + } + }); + + it('should not be able to update withdrawal document', async () => { + const [withdrawalDocument] = await client.platform + .documents.get( + 'withdrawals.withdrawal', + { + where: [['$ownerId', '==', identity.getId()]], + }, + ); + + withdrawalDocument.set('status', 3); + + try { + await client.platform.documents.broadcast({ + replace: [withdrawalDocument], + }, identity); + + expect.fail('should throw broadcast error'); + } catch (e) { + expect(e.message).to.be.equal('Action is not allowed'); + expect(e.code).to.equal(4001); + } + }); + }); +}); diff --git a/packages/rs-dpp/src/balances/credits.rs b/packages/rs-dpp/src/balances/credits.rs index 6febee1005e..e9568bec741 100644 --- a/packages/rs-dpp/src/balances/credits.rs +++ b/packages/rs-dpp/src/balances/credits.rs @@ -25,6 +25,8 @@ pub type SignedCredits = i64; pub const MAX_CREDITS: Credits = 9223372036854775807 as Credits; //i64 Max +pub const CREDITS_PER_DUFF: Credits = 1000; + /// Trait for signed and unsigned credits pub trait Creditable { diff --git a/packages/rs-dpp/src/errors/consensus/basic/identity/invalid_identity_credit_withdrawal_transition_core_fee_error.rs b/packages/rs-dpp/src/errors/consensus/basic/identity/invalid_identity_credit_withdrawal_transition_core_fee_error.rs index 357b3389fd5..2c3be767a38 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/identity/invalid_identity_credit_withdrawal_transition_core_fee_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/identity/invalid_identity_credit_withdrawal_transition_core_fee_error.rs @@ -10,7 +10,7 @@ use bincode::{Decode, Encode}; #[derive( Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, )] -#[error("Core fee per byte {core_fee_per_byte:?} must be part of fibonacci sequence")] +#[error("Core fee per byte {core_fee_per_byte:?} must be part of fibonacci sequence and not less than {min_core_fee_per_byte:?}")] #[platform_serialize(unversioned)] pub struct InvalidIdentityCreditWithdrawalTransitionCoreFeeError { /* @@ -19,16 +19,23 @@ pub struct InvalidIdentityCreditWithdrawalTransitionCoreFeeError { */ core_fee_per_byte: u32, + min_core_fee_per_byte: u32, } impl InvalidIdentityCreditWithdrawalTransitionCoreFeeError { - pub fn new(core_fee_per_byte: u32) -> Self { - Self { core_fee_per_byte } + pub fn new(core_fee_per_byte: u32, min_core_fee_per_byte: u32) -> Self { + Self { + core_fee_per_byte, + min_core_fee_per_byte, + } } pub fn core_fee_per_byte(&self) -> u32 { self.core_fee_per_byte } + pub fn min_core_fee_per_byte(&self) -> u32 { + self.min_core_fee_per_byte + } } impl From for ConsensusError { diff --git a/packages/rs-dpp/src/identity/identity_facade.rs b/packages/rs-dpp/src/identity/identity_facade.rs index d3899bda965..d3de5380064 100644 --- a/packages/rs-dpp/src/identity/identity_facade.rs +++ b/packages/rs-dpp/src/identity/identity_facade.rs @@ -5,7 +5,7 @@ use std::collections::BTreeMap; use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof; use crate::identity::state_transition::asset_lock_proof::{AssetLockProof, InstantAssetLockProof}; use crate::identity::{Identity, IdentityPublicKey, KeyID, TimestampMillis}; -use crate::prelude::Identifier; +use crate::prelude::{Identifier, Revision}; use crate::identity::identity_factory::IdentityFactory; #[cfg(feature = "state-transitions")] @@ -19,6 +19,9 @@ use crate::state_transition::identity_update_transition::IdentityUpdateTransitio #[cfg(feature = "state-transitions")] use crate::state_transition::public_key_in_creation::IdentityPublicKeyInCreation; +use crate::identity::core_script::CoreScript; +use crate::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition; +use crate::withdrawal::Pooling; use crate::ProtocolError; #[derive(Clone)] @@ -109,6 +112,26 @@ impl IdentityFacade { .create_identity_credit_transfer_transition(identity, recipient_id, amount) } + #[cfg(feature = "state-transitions")] + pub fn create_identity_credit_withdrawal_transition( + &self, + identity_id: Identifier, + amount: u64, + core_fee_per_byte: u32, + pooling: Pooling, + output_script: CoreScript, + revision: Revision, + ) -> Result { + self.factory.create_identity_credit_withdrawal_transition( + identity_id, + amount, + core_fee_per_byte, + pooling, + output_script, + revision, + ) + } + #[cfg(feature = "state-transitions")] pub fn create_identity_update_transition( &self, diff --git a/packages/rs-dpp/src/identity/identity_factory.rs b/packages/rs-dpp/src/identity/identity_factory.rs index b00060d7f3c..8384f55ae1d 100644 --- a/packages/rs-dpp/src/identity/identity_factory.rs +++ b/packages/rs-dpp/src/identity/identity_factory.rs @@ -23,6 +23,8 @@ use crate::identity::accessors::IdentityGettersV0; #[cfg(all(feature = "validation", feature = "identity-value-conversion"))] use crate::identity::conversion::platform_value::IdentityPlatformValueConversionMethodsV0; +use crate::identity::core_script::CoreScript; +use crate::prelude::Revision; #[cfg(all(feature = "identity-serialization", feature = "client"))] use crate::serialization::PlatformDeserializable; #[cfg(all(feature = "state-transitions", feature = "client"))] @@ -33,6 +35,8 @@ use crate::state_transition::identity_create_transition::IdentityCreateTransitio use crate::state_transition::identity_credit_transfer_transition::v0::IdentityCreditTransferTransitionV0; #[cfg(all(feature = "state-transitions", feature = "client"))] use crate::state_transition::identity_credit_transfer_transition::IdentityCreditTransferTransition; +use crate::state_transition::identity_credit_withdrawal_transition::v0::IdentityCreditWithdrawalTransitionV0; +use crate::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition; #[cfg(all(feature = "state-transitions", feature = "client"))] use crate::state_transition::identity_topup_transition::accessors::IdentityTopUpTransitionAccessorsV0; #[cfg(all(feature = "state-transitions", feature = "client"))] @@ -48,6 +52,7 @@ use crate::state_transition::identity_update_transition::IdentityUpdateTransitio #[cfg(all(feature = "state-transitions", feature = "client"))] use crate::state_transition::public_key_in_creation::IdentityPublicKeyInCreation; use crate::version::PlatformVersion; +use crate::withdrawal::Pooling; #[cfg(any( all(feature = "identity-serialization", feature = "client"), feature = "identity-value-conversion" @@ -212,6 +217,30 @@ impl IdentityFactory { )) } + #[cfg(all(feature = "state-transitions", feature = "client"))] + pub fn create_identity_credit_withdrawal_transition( + &self, + identity_id: Identifier, + amount: u64, + core_fee_per_byte: u32, + pooling: Pooling, + output_script: CoreScript, + revision: Revision, + ) -> Result { + let mut identity_credit_withdrawal_transition = + IdentityCreditWithdrawalTransitionV0::default(); + identity_credit_withdrawal_transition.identity_id = identity_id; + identity_credit_withdrawal_transition.amount = amount; + identity_credit_withdrawal_transition.core_fee_per_byte = core_fee_per_byte; + identity_credit_withdrawal_transition.pooling = pooling; + identity_credit_withdrawal_transition.output_script = output_script; + identity_credit_withdrawal_transition.revision = revision; + + Ok(IdentityCreditWithdrawalTransition::from( + identity_credit_withdrawal_transition, + )) + } + #[cfg(all(feature = "state-transitions", feature = "client"))] pub fn create_identity_update_transition( &self, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/accessors/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/accessors/mod.rs index 187a59c4ae2..60b8d22f975 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/accessors/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/accessors/mod.rs @@ -16,12 +16,28 @@ impl IdentityCreditWithdrawalTransitionAccessorsV0 for IdentityCreditWithdrawalT } } + fn set_identity_id(&mut self, identity_id: Identifier) { + match self { + IdentityCreditWithdrawalTransition::V0(transition) => { + transition.identity_id = identity_id; + } + } + } + fn amount(&self) -> u64 { match self { IdentityCreditWithdrawalTransition::V0(transition) => transition.amount, } } + fn set_amount(&mut self, amount: u64) { + match self { + IdentityCreditWithdrawalTransition::V0(transition) => { + transition.amount = amount; + } + } + } + fn set_revision(&mut self, revision: Revision) { match self { IdentityCreditWithdrawalTransition::V0(transition) => transition.revision = revision, @@ -40,15 +56,39 @@ impl IdentityCreditWithdrawalTransitionAccessorsV0 for IdentityCreditWithdrawalT } } + fn set_pooling(&mut self, pooling: Pooling) { + match self { + IdentityCreditWithdrawalTransition::V0(transition) => { + transition.pooling = pooling; + } + } + } + fn core_fee_per_byte(&self) -> u32 { match self { IdentityCreditWithdrawalTransition::V0(transition) => transition.core_fee_per_byte, } } + fn set_core_fee_per_byte(&mut self, core_fee_per_byte: u32) { + match self { + IdentityCreditWithdrawalTransition::V0(transition) => { + transition.core_fee_per_byte = core_fee_per_byte; + } + } + } + fn output_script(&self) -> CoreScript { match self { IdentityCreditWithdrawalTransition::V0(transition) => transition.output_script.clone(), } } + + fn set_output_script(&mut self, output_script: CoreScript) { + match self { + IdentityCreditWithdrawalTransition::V0(transition) => { + transition.output_script = output_script; + } + } + } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/accessors/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/accessors/v0/mod.rs index f59529f8f88..540c1e8bb86 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/accessors/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/accessors/v0/mod.rs @@ -5,10 +5,15 @@ use platform_value::Identifier; pub trait IdentityCreditWithdrawalTransitionAccessorsV0 { fn identity_id(&self) -> Identifier; + fn set_identity_id(&mut self, identity_id: Identifier); fn amount(&self) -> u64; - fn set_revision(&mut self, revision: Revision); + fn set_amount(&mut self, amount: u64); fn revision(&self) -> Revision; + fn set_revision(&mut self, revision: Revision); fn pooling(&self) -> Pooling; + fn set_pooling(&mut self, pooling: Pooling); fn core_fee_per_byte(&self) -> u32; + fn set_core_fee_per_byte(&mut self, amount: u32); fn output_script(&self) -> CoreScript; + fn set_output_script(&mut self, output_script: CoreScript); } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/mod.rs index 87786d4a3ea..0f9533af4a1 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/mod.rs @@ -1,7 +1,7 @@ use crate::state_transition::identity_credit_withdrawal_transition::v0::IdentityCreditWithdrawalTransitionV0; pub mod accessors; -mod fields; +pub mod fields; mod identity_signed; #[cfg(feature = "state-transition-json-conversion")] mod json_conversion; @@ -22,6 +22,7 @@ use data_contracts::withdrawals_contract::v1::document_types::withdrawal::proper use derive_more::From; use fields::*; use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_version::version::PlatformVersion; use platform_versioning::PlatformVersioned; use serde::{Deserialize, Serialize}; @@ -53,6 +54,25 @@ pub enum IdentityCreditWithdrawalTransition { V0(IdentityCreditWithdrawalTransitionV0), } +impl IdentityCreditWithdrawalTransition { + pub fn default_versioned(platform_version: &PlatformVersion) -> Result { + match platform_version + .dpp + .identity_versions + .identity_structure_version + { + 0 => Ok(IdentityCreditWithdrawalTransition::V0( + IdentityCreditWithdrawalTransitionV0::default(), + )), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "IdentityCreditWithdrawalTransition::default_versioned".to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} + impl StateTransitionFieldTypes for IdentityCreditWithdrawalTransition { fn signature_property_paths() -> Vec<&'static str> { vec![SIGNATURE, SIGNATURE_PUBLIC_KEY_ID] diff --git a/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/v0/mod.rs index 485801e5486..33caa66c3d5 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_credit_withdrawal_transition/v0/mod.rs @@ -8,17 +8,26 @@ mod value_conversion; mod version; use bincode::{Decode, Encode}; +use dashcore::transaction::special_transaction::asset_unlock::qualified_asset_unlock::ASSET_UNLOCK_TX_SIZE; use platform_serialization_derive::PlatformSignable; use platform_value::BinaryData; use serde::{Deserialize, Serialize}; +use crate::balances::credits::CREDITS_PER_DUFF; use crate::{ identity::{core_script::CoreScript, KeyID}, prelude::{Identifier, Revision}, + withdrawal::Pooling, ProtocolError, }; -use crate::withdrawal::Pooling; +// TODO: unsafe - we must use actual relay fee from core +/// Minimal core per byte. Must be a fibonacci number +pub const MIN_CORE_FEE_PER_BYTE: u32 = 5; + +/// Minimal amount in credits (x1000) to avoid "dust" error in Core +pub const MIN_WITHDRAWAL_AMOUNT: u64 = + (ASSET_UNLOCK_TX_SIZE as u64) * (MIN_CORE_FEE_PER_BYTE as u64) * CREDITS_PER_DUFF; #[derive(Debug, Clone, Encode, Decode, PlatformSignable, PartialEq)] #[cfg_attr( diff --git a/packages/rs-dpp/src/state_transition/traits/state_transition_like.rs b/packages/rs-dpp/src/state_transition/traits/state_transition_like.rs index 28d53f16dc7..d9282eeb2f0 100644 --- a/packages/rs-dpp/src/state_transition/traits/state_transition_like.rs +++ b/packages/rs-dpp/src/state_transition/traits/state_transition_like.rs @@ -11,11 +11,12 @@ use crate::state_transition::{StateTransition, StateTransitionFieldTypes}; pub const DOCUMENT_TRANSITION_TYPES: [StateTransitionType; 1] = [StateTransitionType::DocumentsBatch]; -pub const IDENTITY_TRANSITION_TYPE: [StateTransitionType; 4] = [ +pub const IDENTITY_TRANSITION_TYPE: [StateTransitionType; 5] = [ StateTransitionType::IdentityCreate, StateTransitionType::IdentityTopUp, StateTransitionType::IdentityUpdate, StateTransitionType::IdentityCreditTransfer, + StateTransitionType::IdentityCreditWithdrawal, ]; pub const DATA_CONTRACT_TRANSITION_TYPES: [StateTransitionType; 2] = [ diff --git a/packages/rs-dpp/src/util/is_fibonacci_number.rs b/packages/rs-dpp/src/util/is_fibonacci_number.rs index fab42b93875..4c736e0a9a0 100644 --- a/packages/rs-dpp/src/util/is_fibonacci_number.rs +++ b/packages/rs-dpp/src/util/is_fibonacci_number.rs @@ -1,7 +1,20 @@ -fn is_perfect_square(number: u32) -> bool { +fn is_perfect_square(number: u64) -> bool { (number as f64).sqrt().fract() == 0.0 } -pub fn is_fibonacci_number(number: u32) -> bool { - is_perfect_square(5 * number * number + 4) || is_perfect_square(5 * number * number - 4) +pub fn is_fibonacci_number(number: u64) -> bool { + let square_check_up = 5u64 + .checked_mul(number) + .and_then(|n| n.checked_mul(number)) + .and_then(|n| n.checked_add(4)); + + let square_check_down = 5u64 + .checked_mul(number) + .and_then(|n| n.checked_mul(number)) + .and_then(|n| n.checked_sub(4)); + + match (square_check_up, square_check_down) { + (Some(n1), Some(n2)) => is_perfect_square(n1) || is_perfect_square(n2), + _ => false, // Return false if either calculation overflows + } } diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index 10f1ec47447..5e8d4954b85 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -35,13 +35,12 @@ bs58 = "0.4.0" hex = "0.4.3" indexmap = { version = "1.9.3", features = ["serde"] } sha2 = "0.10.6" -dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore-rpc", branch = "master" } +dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore-rpc", tag = "v0.15.2" } dpp = { path = "../rs-dpp", features = ["abci"] } simple-signer = { path = "../simple-signer" } rust_decimal = "1.2.5" rust_decimal_macros = "1.25.0" mockall = { version = "0.11", optional = true } -bytes = { version = "1.4.0", default-features = false } prost = { version = "0.11.6", default-features = false } tracing = { version = "0.1.37", default-features = false, features = [] } clap = { version = "4.1.8", optional = true, features = ["derive"] } @@ -58,7 +57,7 @@ tracing-subscriber = { version = "0.3.16", default-features = false, features = "tracing-log", ], optional = false } atty = { version = "0.2.14", optional = false } -tenderdash-abci = { git = "https://github.com/dashpay/rs-tenderdash-abci", rev = "251386f9e4109c89ca628226c981c489b80aed80" } +tenderdash-abci = { git = "https://github.com/dashpay/rs-tenderdash-abci", tag = "v0.14.0-dev.6" } lazy_static = "1.4.0" itertools = { version = "0.10.5" } file-rotate = { version = "0.7.3" } diff --git a/packages/rs-drive-abci/src/abci/error.rs b/packages/rs-drive-abci/src/abci/error.rs index 800ee6eaed0..9f0c31d6635 100644 --- a/packages/rs-drive-abci/src/abci/error.rs +++ b/packages/rs-drive-abci/src/abci/error.rs @@ -1,4 +1,6 @@ use dpp::bls_signatures::BlsError; +use tenderdash_abci::proto::abci::ExtendVoteExtension; +use tenderdash_abci::proto::types::VoteExtension; // @append_only /// Error returned within ABCI server @@ -10,10 +12,13 @@ pub enum AbciError { /// Request does not match currently processed block #[error("request does not match current block: {0}")] RequestForWrongBlockReceived(String), - /// Withdrawal transactions mismatch + /// Withdrawal vote extensions mismatch #[error("vote extensions mismatch: got {got:?}, expected {expected:?}")] #[allow(missing_docs)] - VoteExtensionMismatchReceived { got: String, expected: String }, + VoteExtensionMismatchReceived { + got: Vec, + expected: Vec, + }, /// Vote extensions signature is invalid #[error("one of vote extension signatures is invalid")] VoteExtensionsSignatureInvalid, diff --git a/packages/rs-drive-abci/src/abci/handler/mod.rs b/packages/rs-drive-abci/src/abci/handler/mod.rs index 514f046b870..8797a3b726e 100644 --- a/packages/rs-drive-abci/src/abci/handler/mod.rs +++ b/packages/rs-drive-abci/src/abci/handler/mod.rs @@ -40,7 +40,10 @@ mod execution_result; use crate::abci::server::AbciApplication; use crate::error::execution::ExecutionError; +use dashcore_rpc::dashcore::consensus::Encodable; +use dashcore_rpc::dashcore::hashes::HashEngine; +use super::AbciError; use crate::error::Error; use crate::rpc::core::CoreRPCLike; use dpp::errors::consensus::codes::ErrorWithCode; @@ -48,14 +51,12 @@ use tenderdash_abci::proto::abci as proto; use tenderdash_abci::proto::abci::response_verify_vote_extension::VerifyStatus; use tenderdash_abci::proto::abci::tx_record::TxAction; use tenderdash_abci::proto::abci::{ - ExecTxResult, RequestCheckTx, RequestFinalizeBlock, RequestInitChain, RequestPrepareProposal, - RequestProcessProposal, RequestQuery, ResponseCheckTx, ResponseFinalizeBlock, - ResponseInitChain, ResponsePrepareProposal, ResponseProcessProposal, ResponseQuery, TxRecord, + ExecTxResult, ExtendVoteExtension, RequestCheckTx, RequestFinalizeBlock, RequestInitChain, + RequestPrepareProposal, RequestProcessProposal, RequestQuery, ResponseCheckTx, + ResponseFinalizeBlock, ResponseInitChain, ResponsePrepareProposal, ResponseProcessProposal, + ResponseQuery, TxRecord, }; use tenderdash_abci::proto::types::CoreChainLock; -use tenderdash_abci::proto::types::VoteExtensionType; - -use super::AbciError; use crate::execution::types::block_execution_context::v0::{ BlockExecutionContextV0Getters, BlockExecutionContextV0MutableGetters, @@ -69,7 +70,6 @@ use crate::platform_types::block_proposal::v0::BlockProposal; use crate::platform_types::platform_state::v0::PlatformStateV0Methods; use crate::platform_types::platform_state::PlatformState; use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult; -use crate::platform_types::withdrawal::withdrawal_txs; use dpp::dashcore::hashes::Hash; use dpp::fee::SignedCredits; use dpp::version::TryIntoPlatformVersioned; @@ -275,11 +275,10 @@ where // We need to let Tenderdash know about the transactions we should remove from execution let valid_tx_count = state_transitions_result.valid_count(); - let invalid_all_tx_count = state_transitions_result.invalid_count(); let failed_tx_count = state_transitions_result.failed_count(); let delayed_tx_count = transactions_exceeding_max_block_size.len(); - let mut invalid_paid_tx_count = state_transitions_result.invalid_count(); - let mut invalid_unpaid_tx_count = state_transitions_result.invalid_count(); + let invalid_paid_tx_count = state_transitions_result.invalid_paid_count(); + let invalid_unpaid_tx_count = state_transitions_result.invalid_unpaid_count(); let mut tx_results = Vec::new(); let mut tx_records = Vec::new(); @@ -292,21 +291,13 @@ where let tx_action = match &state_transition_execution_result { StateTransitionExecutionResult::SuccessfulExecution(_, _) => TxAction::Unmodified, // We have identity to pay for the state transition, so we keep it in the block - StateTransitionExecutionResult::PaidConsensusError(_) => { - invalid_paid_tx_count += 1; - - TxAction::Unmodified - } + StateTransitionExecutionResult::PaidConsensusError(_) => TxAction::Unmodified, // We don't have any associated identity to pay for the state transition, // so we remove it from the block to prevent spam attacks. // Such state transitions must be invalidated by check tx, but they might // still be added to mempool due to inconsistency between check tx and tx processing // (fees calculation) or malicious proposer. - StateTransitionExecutionResult::UnpaidConsensusError(_) => { - invalid_unpaid_tx_count += 1; - - TxAction::Removed - } + StateTransitionExecutionResult::UnpaidConsensusError(_) => TxAction::Removed, // We shouldn't include in the block any state transitions that produced an internal error // during execution StateTransitionExecutionResult::DriveAbciError(_) => TxAction::Removed, @@ -365,7 +356,6 @@ where valid_tx_count, delayed_tx_count, failed_tx_count, - invalid_unpaid_tx_count, "Prepared proposal with {} transitions for height: {}, round: {} in {} ms", valid_tx_count + invalid_paid_tx_count, request.height, @@ -558,7 +548,7 @@ where let platform_version = PlatformVersion::get(protocol_version) .expect("must be set in run block proposer from existing platform version"); - let invalid_tx_count = state_transition_results.invalid_count(); + let invalid_tx_count = state_transition_results.invalid_paid_count(); let valid_tx_count = state_transition_results.valid_count(); let tx_results = state_transition_results @@ -630,27 +620,21 @@ where round as u32, block_hash.clone(), )? { - Err(Error::from(AbciError::RequestForWrongBlockReceived(format!( + return Err(Error::from(AbciError::RequestForWrongBlockReceived(format!( "received extend vote request for height: {} round: {}, block: {}; expected height: {} round: {}, block: {}", height, round, hex::encode(block_hash), block_state_info.height(), block_state_info.round(), block_state_info.block_hash().map(hex::encode).unwrap_or("None".to_string()) ))) - .into()) - } else { - // we only want to sign the hash of the transaction - let extensions = block_execution_context - .withdrawal_transactions() - .keys() - .map(|tx_id| proto::ExtendVoteExtension { - r#type: VoteExtensionType::ThresholdRecover as i32, - extension: tx_id.to_byte_array().to_vec(), - sign_request_id: None, - }) - .collect(); - Ok(proto::ResponseExtendVote { - vote_extensions: extensions, - }) + .into()); } + + // Extend vote with unsigned withdrawal transactions + // we only want to sign the hash of the transaction + let vote_extensions = block_execution_context + .unsigned_withdrawal_transactions() + .into(); + + Ok(proto::ResponseExtendVote { vote_extensions }) } /// Todo: Verify vote extension not really needed because extend vote is deterministic @@ -660,6 +644,7 @@ where ) -> Result { let _timer = crate::metrics::abci_request_duration("verify_vote_extension"); + // Verify that this is a vote extension for our current executed block and our proposer let proto::RequestVerifyVoteExtension { height, round, @@ -670,10 +655,11 @@ where let height: u64 = height as u64; let round: u32 = round as u32; + // Make sure we are in a block execution phase let guarded_block_execution_context = self.platform.block_execution_context.read().unwrap(); let Some(block_execution_context) = guarded_block_execution_context.as_ref() else { tracing::warn!( - "vote extension for height: {}, round: {} is rejected because we are not in a block execution phase", + "vote extensions for height: {}, round: {} are rejected because we are not in a block execution phase", height, round, ); @@ -683,52 +669,20 @@ where }); }; - let platform_version = block_execution_context - .block_platform_state() - .current_platform_version()?; - - let got: withdrawal_txs::v0::WithdrawalTxs = vote_extensions.into(); - let expected = block_execution_context - .withdrawal_transactions() - .keys() - .map(|tx_id| proto::ExtendVoteExtension { - r#type: VoteExtensionType::ThresholdRecover as i32, - extension: tx_id.to_byte_array().to_vec(), - sign_request_id: None, - }) - .collect::>() - .into(); - - // let state = self.platform.state.read().unwrap(); - // - // let quorum = state.current_validator_set()?; - - // let validator_pro_tx_hash = ProTxHash::from_slice(validator_pro_tx_hash.as_slice()) - // .map_err(|_| { - // Error::Abci(AbciError::BadRequestDataSize(format!( - // "invalid vote extension protxhash: {}", - // hex::encode(validator_pro_tx_hash.as_slice()) - // ))) - // })?; - // - // let Some(validator) = quorum.validator_set.get(&validator_pro_tx_hash) else { - // return Ok(proto::ResponseVerifyVoteExtension { - // status: VerifyStatus::Unknown.into(), - // }); - // }; + // Make sure vote extension is for our currently executing block let block_state_info = block_execution_context.block_state_info(); - //// Verification that vote extension is for our current executed block - // When receiving the vote extension, we need to make sure that info matches our current block - - if block_state_info.height() != height || block_state_info.round() != round { + // We might get vote extension to verify for previous (in case if other node is behind) + // or future round (in case if the current node is behind), so we make sure that only height + // is matching. It's fine because withdrawal transactions to sign are the same for any round + // of the same height + if block_state_info.height() != height { tracing::warn!( - "vote extension for height: {}, round: {} is rejected because we are at height: {} round {}", + "vote extensions for height: {}, round: {} are rejected because we are at height: {}", height, round, block_state_info.height(), - block_state_info.round() ); return Ok(proto::ResponseVerifyVoteExtension { @@ -736,39 +690,35 @@ where }); } - let validation_result = self.platform.check_withdrawals( - &got, - &expected, - height as u64, - round as u32, - None, - None, - platform_version, - )?; + // Verify that a validator is requesting a signatures + // for a correct set of withdrawal transactions - if validation_result.is_valid() { - tracing::debug!( - "vote extension for height: {}, round: {} is successfully verified", - height, - round, - ); + let expected_withdrawals = block_execution_context.unsigned_withdrawal_transactions(); + + if expected_withdrawals != &vote_extensions { + let expected_extensions: Vec = expected_withdrawals.into(); - Ok(proto::ResponseVerifyVoteExtension { - status: VerifyStatus::Accept.into(), - }) - } else { tracing::error!( - ?got, - ?expected, - ?validation_result.errors, - "vote extension for height: {}, round: {} mismatch", + received_extensions = ?vote_extensions, + ?expected_extensions, + "vote extensions for height: {}, round: {} mismatch", height, round ); - Ok(proto::ResponseVerifyVoteExtension { + return Ok(proto::ResponseVerifyVoteExtension { status: VerifyStatus::Reject.into(), - }) + }); } + + tracing::debug!( + "vote extensions for height: {}, round: {} are successfully verified", + height, + round, + ); + + Ok(proto::ResponseVerifyVoteExtension { + status: VerifyStatus::Accept.into(), + }) } fn finalize_block( diff --git a/packages/rs-drive-abci/src/execution/engine/finalize_block_proposal/v0/mod.rs b/packages/rs-drive-abci/src/execution/engine/finalize_block_proposal/v0/mod.rs index b949f9c7603..b807a1e5642 100644 --- a/packages/rs-drive-abci/src/execution/engine/finalize_block_proposal/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/engine/finalize_block_proposal/v0/mod.rs @@ -1,3 +1,4 @@ +use dashcore_rpc::dashcore::transaction::special_transaction::TransactionPayload::AssetUnlockPayloadType; use dpp::block::epoch::Epoch; use dpp::validation::SimpleValidationResult; @@ -8,6 +9,9 @@ use dpp::block::block_info::BlockInfo; use dpp::block::extended_block_info::v0::ExtendedBlockInfoV0; use dpp::version::PlatformVersion; +use dpp::dashcore::bls_sig_utils::BLSSignature; +use dpp::dashcore::consensus::Encodable; +use dpp::dashcore::hashes::Hash; use tenderdash_abci::{ proto::{serializers::timestamp::ToMilis, types::BlockId as ProtoBlockId}, signatures::Hashable, @@ -17,7 +21,9 @@ use crate::abci::AbciError; use crate::error::execution::ExecutionError; use crate::error::Error; -use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0Getters; +use crate::execution::types::block_execution_context::v0::{ + BlockExecutionContextV0Getters, BlockExecutionContextV0MutableGetters, +}; use crate::execution::types::block_state_info::v0::{ BlockStateInfoV0Getters, BlockStateInfoV0Methods, }; @@ -56,7 +62,7 @@ where /// pub(super) fn finalize_block_proposal_v0( &self, - request_finalize_block: FinalizeBlockCleanedRequest, + mut request_finalize_block: FinalizeBlockCleanedRequest, transaction: &Transaction, _platform_version: &PlatformVersion, ) -> Result { @@ -81,7 +87,7 @@ where // Let's decompose the request let FinalizeBlockCleanedRequest { - commit: commit_info, + commit: mut commit_info, misbehavior: _, hash, height, @@ -105,7 +111,7 @@ where .expect("invalid sha256 length"); //// Verification that commit is for our current executed block - // When receiving the finalized block, we need to make sure that info matches our current block + // When receiving the finalized block, we need to make sure info matches our current block // First let's check the basics, height, round and hash if !block_state_info.matches_expected_block_info( @@ -140,7 +146,23 @@ where return Ok(validation_result.into()); } - let quorum_public_key = &state_cache.current_validator_set()?.threshold_public_key(); + // Verify vote extensions + // We don't need to verify vote extension signatures once again after tenderdash + // here, because we will do it bellow broadcasting withdrawal transactions. + // The sendrawtransaction RPC method returns an error if quorum signature is invalid + let expected_withdrawal_transactions = + block_execution_context.unsigned_withdrawal_transactions(); + + if !expected_withdrawal_transactions + .are_matching_with_vote_extensions(&commit_info.threshold_vote_extensions) + { + validation_result.add_error(AbciError::VoteExtensionMismatchReceived { + got: commit_info.threshold_vote_extensions, + expected: expected_withdrawal_transactions.into(), + }); + + return Ok(validation_result.into()); + } // In production this will always be true if self @@ -150,7 +172,9 @@ where { // Verify commit + let quorum_public_key = &state_cache.current_validator_set()?.threshold_public_key(); let quorum_type = self.config.validator_set_quorum_type(); + // TODO: We already had commit in the function above, why do we need to create it again with clone? let commit = Commit::new_from_cleaned( commit_info.clone(), block_id, @@ -168,23 +192,6 @@ where } drop(state_cache); - // Verify vote extensions - // let received_withdrawals = WithdrawalTxs::from(&commit.threshold_vote_extensions); - // let our_withdrawals = WithdrawalTxs::load(Some(transaction), &self.drive) - // .map_err(|e| AbciError::WithdrawalTransactionsDBLoadError(e.to_string()))?; - //todo: reenable check - // - // if let Err(e) = self.check_withdrawals( - // &received_withdrawals, - // &our_withdrawals, - // Some(quorum_public_key), - // ) { - // validation_result.add_error(e); - // return Ok(validation_result.into()); - // } - - // Next let's check that the hash received is the same as the hash we expect - if height == self.config.abci.genesis_height { self.drive .set_genesis_time(block_state_info.block_time_ms()); @@ -195,17 +202,56 @@ where .expect("current epoch info should be in range"), ); - // we need to add the block time to_commit_block_info.time_ms = block_header.time.to_milis(); to_commit_block_info.core_height = block_header.core_chain_locked_height; - // // Finalize withdrawal processing - // our_withdrawals.finalize(Some(transaction), &self.drive, &to_commit_block_info)?; + drop(guarded_block_execution_context); - // At the end we update the state cache + // Append signatures and broadcast asset unlock transactions to Core - drop(guarded_block_execution_context); + // Borrow execution context as mutable + let mut mutable_block_execution_context_guard = + self.block_execution_context.write().unwrap(); + let mutable_block_execution_context = + mutable_block_execution_context_guard + .as_mut() + .ok_or(Error::Execution(ExecutionError::CorruptedCodeExecution( + "block execution context must be set in block begin handler for finalize block proposal", + )))?; + + // Drain withdrawal transaction instead of cloning + let unsigned_withdrawal_transactions = mutable_block_execution_context + .unsigned_withdrawal_transactions_mut() + .drain(); + + drop(mutable_block_execution_context_guard); + + // Drain signatures instead of cloning + let signatures = commit_info + .threshold_vote_extensions + .drain(..) + .into_iter() + .map(|vote_extension| { + let signature_bytes: [u8; 96] = + vote_extension.signature.try_into().map_err(|e| { + AbciError::BadRequestDataSize(format!( + "invalid vote extension signature size: {}", + hex::encode(e) + )) + })?; + + Ok(BLSSignature::from(signature_bytes)) + }) + .collect::>()?; + + self.append_signatures_and_broadcast_withdrawal_transactions( + unsigned_withdrawal_transactions, + signatures, + platform_version, + )?; + + // Update platform (drive abci) state let extended_block_info = ExtendedBlockInfoV0 { basic_info: to_commit_block_info, diff --git a/packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs b/packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs index b10494886a9..8c06b7344f5 100644 --- a/packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs @@ -1,6 +1,3 @@ -use dashcore_rpc::dashcore::hashes::Hash; -use dashcore_rpc::dashcore::Txid; - use dpp::block::epoch::Epoch; use dpp::validation::ValidationResult; @@ -8,7 +5,6 @@ use drive::error::Error::GroveDB; use dpp::version::PlatformVersion; use drive::grovedb::Transaction; -use std::collections::BTreeMap; use crate::abci::AbciError; use crate::error::execution::ExecutionError; @@ -238,7 +234,7 @@ where block_state_info: block_state_info.into(), epoch_info: epoch_info.clone(), hpmn_count: hpmn_list_len as u32, - withdrawal_transactions: BTreeMap::new(), + unsigned_withdrawal_transactions: Default::default(), block_platform_state, proposer_results: None, }; @@ -320,43 +316,38 @@ where let mut block_execution_context: BlockExecutionContext = block_execution_context.into(); - // >>>>>> Withdrawal Status Update <<<<<<< - // Only update the broadcasted withdrawal statuses if the core chain lock height has - // changed. If it hasn't changed there should be no way a status could update - + // Mark all previously broadcasted and chainlocked withdrawals as complete + // only when we are on a new core height if block_execution_context .block_state_info() .core_chain_locked_height() != last_block_core_height { - self.update_broadcasted_withdrawal_transaction_statuses( - &block_execution_context, + self.update_broadcasted_withdrawal_statuses( + &block_info, transaction, platform_version, )?; } - // This takes withdrawals from the transaction queue - let unsigned_withdrawal_transaction_bytes = self - .fetch_and_prepare_unsigned_withdrawal_transactions( - validator_set_quorum_hash, - &block_execution_context, - transaction, - platform_version, - )?; + // Preparing withdrawal transactions for signing and broadcasting + { + // To process withdrawals we need to dequeue untiled transactions from the withdrawal transactions queue + // Untiled transactions then converted to unsigned transactions, appending current block information + // required for signature verification (core height and quorum hash) + let unsigned_withdrawal_transaction_bytes = self + .dequeue_and_build_unsigned_withdrawal_transactions( + validator_set_quorum_hash, + &block_info, + Some(transaction), + platform_version, + )?; - // Set the withdrawal transactions that were done in the previous block - block_execution_context.set_withdrawal_transactions( - unsigned_withdrawal_transaction_bytes - .into_iter() - .map(|withdrawal_transaction| { - ( - Txid::hash(withdrawal_transaction.as_slice()), - withdrawal_transaction, - ) - }) - .collect(), - ); + // Save unsigned transaction bytes to block execution context + // to be signed (on extend_vote), verified (on verify_vote) and broadcasted (on finalize_block) + block_execution_context + .set_unsigned_withdrawal_transactions(unsigned_withdrawal_transaction_bytes); + } let state_transitions_result = self.process_raw_state_transitions( raw_state_transitions, @@ -368,14 +359,17 @@ where let mut block_execution_context: BlockExecutionContext = block_execution_context; + // Pool withdrawals into transactions queue + + // Takes queued withdrawals, creates untiled withdrawal transaction payload, saves them to queue + // Corresponding withdrawal documents are changed from queued to pooled self.pool_withdrawals_into_transactions_queue( - &block_execution_context, - transaction, + &block_info, + Some(transaction), platform_version, )?; // while we have the state transitions executed, we now need to process the block fees - let block_fees_v0: BlockFeesV0 = state_transitions_result.aggregated_fees().clone().into(); // Process fees diff --git a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/fetch_and_prepare_unsigned_withdrawal_transactions/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/fetch_and_prepare_unsigned_withdrawal_transactions/v0/mod.rs deleted file mode 100644 index 8a218da798d..00000000000 --- a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/fetch_and_prepare_unsigned_withdrawal_transactions/v0/mod.rs +++ /dev/null @@ -1,168 +0,0 @@ -use dashcore_rpc::dashcore::{ - blockdata::transaction::special_transaction::asset_unlock::request_info::AssetUnlockRequestInfo, - hashes::Hash, QuorumHash, -}; -use dpp::block::block_info::BlockInfo; -use dpp::block::epoch::Epoch; -use dpp::data_contract::accessors::v0::DataContractV0Getters; -use dpp::document::document_methods::DocumentMethodsV0; -use dpp::document::{Document, DocumentV0Setters}; -use dpp::version::PlatformVersion; - -use drive::dpp::system_data_contracts::withdrawals_contract; -use drive::dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; - -use drive::dpp::util::hash; - -use drive::drive::batch::DriveOperation; -use drive::grovedb::Transaction; - -use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0Getters; -use crate::execution::types::block_execution_context::BlockExecutionContext; -use crate::execution::types::block_state_info::v0::BlockStateInfoV0Getters; -use crate::platform_types::epoch_info::v0::EpochInfoV0Getters; -use crate::{ - error::{execution::ExecutionError, Error}, - platform_types::platform::Platform, - rpc::core::CoreRPCLike, -}; - -const WITHDRAWAL_TRANSACTIONS_QUERY_LIMIT: u16 = 16; - -impl Platform -where - C: CoreRPCLike, -{ - /// Prepares a list of an unsigned withdrawal transaction bytes - pub(super) fn fetch_and_prepare_unsigned_withdrawal_transactions_v0( - &self, - validator_set_quorum_hash: [u8; 32], - block_execution_context: &BlockExecutionContext, - transaction: &Transaction, - platform_version: &PlatformVersion, - ) -> Result>, Error> { - let block_info = BlockInfo { - time_ms: block_execution_context.block_state_info().block_time_ms(), - height: block_execution_context.block_state_info().height(), - core_height: block_execution_context - .block_state_info() - .core_chain_locked_height(), - epoch: Epoch::new(block_execution_context.epoch_info().current_epoch_index())?, - }; - - let data_contract_id = withdrawals_contract::ID; - - let (_, Some(contract_fetch_info)) = self.drive.get_contract_with_fetch_info_and_fee( - data_contract_id.to_buffer(), - None, - true, - Some(transaction), - platform_version, - )? - else { - return Err(Error::Execution(ExecutionError::CorruptedCodeExecution( - "can't fetch withdrawal data contract", - ))); - }; - - let mut drive_operations: Vec = vec![]; - - // Get 16 latest withdrawal transactions from the queue - let untied_withdrawal_transactions = self.drive.dequeue_withdrawal_transactions( - WITHDRAWAL_TRANSACTIONS_QUERY_LIMIT, - Some(transaction), - &mut drive_operations, - )?; - - if untied_withdrawal_transactions.is_empty() { - return Ok(Vec::new()); - } - - // Appending request_height and quorum_hash to withdrawal transaction - // and pass it to JS Drive for singing and broadcasting - let (unsigned_withdrawal_transactions, documents_to_update): (Vec<_>, Vec<_>) = - untied_withdrawal_transactions - .into_iter() - .map(|(_, untied_transaction_bytes)| { - let request_info = AssetUnlockRequestInfo { - request_height: block_execution_context - .block_state_info() - .core_chain_locked_height(), - quorum_hash: QuorumHash::hash(&validator_set_quorum_hash), - }; - - let mut unsigned_transaction_bytes = vec![]; - - request_info - .consensus_append_to_base_encode( - untied_transaction_bytes.clone(), - &mut unsigned_transaction_bytes, - ) - .map_err(|_| { - Error::Execution(ExecutionError::CorruptedCodeExecution( - "could not add additional request info to asset unlock transaction", - )) - })?; - - let original_transaction_id = hash::hash_to_vec(untied_transaction_bytes); - let update_transaction_id = - hash::hash_to_vec(unsigned_transaction_bytes.clone()); - - let mut document = self.drive.find_withdrawal_document_by_transaction_id( - &original_transaction_id, - Some(transaction), - platform_version, - )?; - - document.set_bytes( - withdrawal::properties::TRANSACTION_ID, - update_transaction_id, - ); - - document.set_i64( - withdrawal::properties::UPDATED_AT, - block_info.time_ms.try_into().map_err(|_| { - Error::Execution(ExecutionError::CorruptedCodeExecution( - "Can't convert u64 block time to i64 updated_at", - )) - })?, - ); - - document.increment_revision().map_err(|_| { - Error::Execution(ExecutionError::CorruptedCodeExecution( - "Could not increment document revision", - )) - })?; - - Ok((unsigned_transaction_bytes, document)) - }) - .collect::, Document)>, Error>>()? - .into_iter() - .unzip(); - - self.drive.add_update_multiple_documents_operations( - &documents_to_update, - &contract_fetch_info.contract, - contract_fetch_info - .contract - .document_type_for_name(withdrawal::NAME) - .map_err(|_| { - Error::Execution(ExecutionError::CorruptedCodeExecution( - "could not get document type", - )) - })?, - &mut drive_operations, - &platform_version.drive, - )?; - - self.drive.apply_drive_operations( - drive_operations, - true, - &block_info, - Some(transaction), - platform_version, - )?; - - Ok(unsigned_withdrawal_transactions) - } -} diff --git a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/fetch_transactions_block_inclusion_status/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/fetch_transactions_block_inclusion_status/v0/mod.rs deleted file mode 100644 index 79620e55f0c..00000000000 --- a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/fetch_transactions_block_inclusion_status/v0/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -use dpp::dashcore::hashes::Hash; -use dpp::dashcore::Txid; -use std::collections::BTreeMap; - -use crate::{error::Error, platform_types::platform::Platform, rpc::core::CoreRPCLike}; - -impl Platform -where - C: CoreRPCLike, -{ - /// Fetch Core transactions by range of Core heights - pub(super) fn fetch_transactions_block_inclusion_status_v0( - &self, - current_chain_locked_core_height: u32, - transaction_identifiers: Vec<[u8; 32]>, - ) -> Result, Error> { - let tx_ids: Vec = transaction_identifiers - .iter() - .map(|transaction_id| Txid::from_byte_array(*transaction_id)) - .collect(); - let transactions_are_chain_locked_result = - self.core_rpc.get_transactions_are_chain_locked(tx_ids)?; - - Ok(transactions_are_chain_locked_result - .into_iter() - .zip(transaction_identifiers) - .map(|(lock_result, identifier)| { - let Some(mined_height) = lock_result.height else { - return (identifier, false); - }; - let withdrawal_chain_locked = - lock_result.chain_lock && current_chain_locked_core_height >= mined_height; - (identifier, withdrawal_chain_locked) - }) - .collect()) - } -} diff --git a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/mod.rs deleted file mode 100644 index 60843ec380d..00000000000 --- a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub(in crate::execution) mod build_withdrawal_transactions_from_documents; -pub(in crate::execution) mod fetch_and_prepare_unsigned_withdrawal_transactions; -mod fetch_transactions_block_inclusion_status; -pub(in crate::execution) mod pool_withdrawals_into_transactions_queue; -pub(in crate::execution) mod update_broadcasted_withdrawal_transaction_statuses; diff --git a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/update_broadcasted_withdrawal_transaction_statuses/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/update_broadcasted_withdrawal_transaction_statuses/v0/mod.rs deleted file mode 100644 index 83d3cec0232..00000000000 --- a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/update_broadcasted_withdrawal_transaction_statuses/v0/mod.rs +++ /dev/null @@ -1,462 +0,0 @@ -use dpp::block::block_info::BlockInfo; -use dpp::block::epoch::Epoch; -use dpp::data_contract::accessors::v0::DataContractV0Getters; -use dpp::data_contracts::withdrawals_contract::WithdrawalStatus; -use dpp::document::document_methods::DocumentMethodsV0; -use dpp::document::{Document, DocumentV0Getters, DocumentV0Setters}; -use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; -use dpp::system_data_contracts::withdrawals_contract; -use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; -use dpp::version::PlatformVersion; -use std::collections::BTreeMap; - -use drive::drive::batch::DriveOperation; -use drive::grovedb::Transaction; - -use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0Getters; -use crate::execution::types::block_execution_context::BlockExecutionContext; -use crate::execution::types::block_state_info::v0::BlockStateInfoV0Getters; -use crate::platform_types::epoch_info::v0::EpochInfoV0Getters; -use crate::{ - error::{execution::ExecutionError, Error}, - platform_types::platform::Platform, - rpc::core::CoreRPCLike, -}; - -const NUMBER_OF_BLOCKS_BEFORE_EXPIRED: u32 = 48; - -impl Platform -where - C: CoreRPCLike, -{ - /// Update statuses for broadcasted withdrawals - pub(super) fn update_broadcasted_withdrawal_transaction_statuses_v0( - &self, - block_execution_context: &BlockExecutionContext, - transaction: &Transaction, - platform_version: &PlatformVersion, - ) -> Result<(), Error> { - let block_info = BlockInfo { - time_ms: block_execution_context.block_state_info().block_time_ms(), - height: block_execution_context.block_state_info().height(), - core_height: block_execution_context - .block_state_info() - .core_chain_locked_height(), - epoch: Epoch::new(block_execution_context.epoch_info().current_epoch_index())?, - }; - - let data_contract_id = withdrawals_contract::ID; - - let (_, Some(contract_fetch_info)) = self.drive.get_contract_with_fetch_info_and_fee( - data_contract_id.to_buffer(), - None, - true, - Some(transaction), - platform_version, - )? - else { - return Err(Error::Execution(ExecutionError::CorruptedCodeExecution( - "can't fetch withdrawal data contract", - ))); - }; - - let broadcasted_withdrawal_documents = self.drive.fetch_withdrawal_documents_by_status( - withdrawals_contract::WithdrawalStatus::BROADCASTED.into(), - Some(transaction), - platform_version, - )?; - - // Collecting only documents that have been updated - let transactions_to_check: Vec<[u8; 32]> = broadcasted_withdrawal_documents - .iter() - .map(|document| { - document - .properties() - .get_hash256_bytes(withdrawal::properties::TRANSACTION_ID) - .map_err(|_| { - Error::Execution(ExecutionError::CorruptedDriveResponse( - "Can't get transactionId from withdrawal document".to_string(), - )) - }) - }) - .collect::, Error>>()?; - - let core_transactions_statuses = if transactions_to_check.is_empty() { - BTreeMap::new() - } else { - self.fetch_transactions_block_inclusion_status( - block_execution_context - .block_state_info() - .core_chain_locked_height(), - transactions_to_check, - platform_version, - )? - }; - - let mut drive_operations: Vec = vec![]; - - // Collecting only documents that have been updated - let documents_to_update: Vec = broadcasted_withdrawal_documents - .into_iter() - .map(|mut document| { - let transaction_sign_height: u32 = document - .properties() - .get_optional_integer(withdrawal::properties::TRANSACTION_SIGN_HEIGHT)? - .ok_or(Error::Execution(ExecutionError::CorruptedDriveResponse( - "Can't get transactionSignHeight from withdrawal document".to_string(), - )))?; - - let transaction_id = document - .properties() - .get_optional_hash256_bytes(withdrawal::properties::TRANSACTION_ID)? - .ok_or(Error::Execution(ExecutionError::CorruptedDriveResponse( - "Can't get transactionId from withdrawal document".to_string(), - )))?; - - let transaction_index = document - .properties() - .get_optional_integer(withdrawal::properties::TRANSACTION_INDEX)? - .ok_or(Error::Execution(ExecutionError::CorruptedDriveResponse( - "Can't get transaction index from withdrawal document".to_string(), - )))?; - - let current_status: WithdrawalStatus = document - .properties() - .get_optional_integer::(withdrawal::properties::STATUS)? - .ok_or(Error::Execution(ExecutionError::CorruptedDriveResponse( - "Can't get transaction index from withdrawal document".to_string(), - )))? - .try_into() - .map_err(|_| { - Error::Execution(ExecutionError::CorruptedDriveResponse( - "Withdrawal status unknown".to_string(), - )) - })?; - - let block_height_difference = block_execution_context - .block_state_info() - .core_chain_locked_height() - - transaction_sign_height; - - let is_chain_locked = - *core_transactions_statuses - .get(&transaction_id) - .ok_or(Error::Execution(ExecutionError::CorruptedCodeExecution( - "we should always have a withdrawal status", - )))?; - - let mut status = current_status; - - if is_chain_locked { - status = WithdrawalStatus::COMPLETE; - } else if block_height_difference > NUMBER_OF_BLOCKS_BEFORE_EXPIRED { - status = WithdrawalStatus::EXPIRED; - } else { - // todo: there could be a problem here where we always get the same withdrawals - // and don't cycle them most likely when we query withdrawals - return Ok(None); - } - - document.set_u8(withdrawal::properties::STATUS, status.into()); - - document.set_u64(withdrawal::properties::UPDATED_AT, block_info.time_ms); - - document.increment_revision().map_err(Error::Protocol)?; - - if status == WithdrawalStatus::EXPIRED { - self.drive.add_insert_expired_index_operation( - transaction_index, - &mut drive_operations, - ); - } - - Ok(Some(document)) - }) - .collect::>, Error>>()? - .into_iter() - .flatten() - .collect(); - - self.drive.add_update_multiple_documents_operations( - &documents_to_update, - &contract_fetch_info.contract, - contract_fetch_info - .contract - .document_type_for_name(withdrawal::NAME) - .map_err(|_| { - Error::Execution(ExecutionError::CorruptedCodeExecution( - "Can't fetch withdrawal data contract", - )) - })?, - &mut drive_operations, - &platform_version.drive, - )?; - - self.drive.apply_drive_operations( - drive_operations, - true, - &block_info, - Some(transaction), - platform_version, - )?; - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0; - use crate::execution::types::block_state_info::v0::BlockStateInfoV0; - use crate::platform_types::epoch_info::v0::EpochInfoV0; - use crate::platform_types::platform_state::v0::PlatformStateV0; - use crate::rpc::core::MockCoreRPCLike; - use crate::test::helpers::setup::TestPlatformBuilder; - use dashcore_rpc::dashcore::{BlockHash, QuorumHash}; - use dashcore_rpc::dashcore_rpc_json::GetTransactionLockedResult; - use dpp::dashcore::hashes::Hash; - use dpp::dashcore::Txid; - use dpp::data_contract::accessors::v0::DataContractV0Getters; - use dpp::document::DocumentV0Getters; - use dpp::identity::core_script::CoreScript; - use dpp::platform_value::platform_value; - use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; - use dpp::version::PlatformVersion; - use dpp::withdrawal::Pooling; - use dpp::{ - data_contracts::withdrawals_contract, tests::fixtures::get_withdrawal_document_fixture, - }; - use dpp::{ - prelude::Identifier, - system_data_contracts::{load_system_data_contract, SystemDataContract}, - }; - use drive::tests::helpers::setup::setup_document; - use drive::tests::helpers::setup::setup_system_data_contract; - use serde_json::json; - use std::str::FromStr; - - #[test] - fn test_statuses_are_updated() { - let platform_version = PlatformVersion::latest(); - let mut platform = TestPlatformBuilder::new() - .build_with_mock_rpc() - .set_initial_state_structure(); - - let mut mock_rpc_client = MockCoreRPCLike::new(); - - mock_rpc_client - .expect_get_transactions_are_chain_locked() - .returning(move |tx_ids: Vec| { - Ok(tx_ids - .into_iter() - .map(|tx_id| { - if tx_id.to_byte_array() - == [ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ] - { - GetTransactionLockedResult { - height: Some(93), - chain_lock: true, - } - } else { - GetTransactionLockedResult { - height: None, - chain_lock: false, - } - } - }) - .collect()) - }); - - mock_rpc_client - .expect_get_block_hash() - .withf(|height| *height == 95) - .returning(|_| { - Ok(BlockHash::from_str( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap()) - }); - - mock_rpc_client - .expect_get_block_hash() - .withf(|height| *height == 96) - .returning(|_| { - Ok(BlockHash::from_str( - "1111111111111111111111111111111111111111111111111111111111111111", - ) - .unwrap()) - }); - - mock_rpc_client - .expect_get_block_json() - .withf(|bh| { - hex::encode(bh) - == "0000000000000000000000000000000000000000000000000000000000000000" - }) - .returning(|_| { - Ok(json!({ - "tx": ["0101010101010101010101010101010101010101010101010101010101010101"] - })) - }); - - mock_rpc_client - .expect_get_block_json() - .withf(|bh| { - hex::encode(bh) - == "1111111111111111111111111111111111111111111111111111111111111111" - }) - .returning(|_| { - Ok(json!({ - "tx": ["0202020202020202020202020202020202020202020202020202020202020202"] - })) - }); - - platform.core_rpc = mock_rpc_client; - - let transaction = platform.drive.grove.start_transaction(); - - let block_execution_context = BlockExecutionContextV0 { - block_state_info: BlockStateInfoV0 { - height: 1, - round: 0, - block_time_ms: 1, - previous_block_time_ms: Some(1), - proposer_pro_tx_hash: [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - ], - core_chain_locked_height: 96, - block_hash: None, - app_hash: None, - } - .into(), - epoch_info: EpochInfoV0 { - current_epoch_index: 1, - previous_epoch_index: None, - is_epoch_change: false, - } - .into(), - hpmn_count: 100, - withdrawal_transactions: Default::default(), - block_platform_state: PlatformStateV0 { - last_committed_block_info: None, - current_protocol_version_in_consensus: 0, - next_epoch_protocol_version: 0, - current_validator_set_quorum_hash: QuorumHash::all_zeros(), - next_validator_set_quorum_hash: None, - validator_sets: Default::default(), - chain_lock_validating_quorums: Default::default(), - previous_height_chain_lock_validating_quorums: None, - full_masternode_list: Default::default(), - hpmn_masternode_list: Default::default(), - genesis_block_info: None, - } - .into(), - proposer_results: None, - }; - - let data_contract = - load_system_data_contract(SystemDataContract::Withdrawals, &platform_version) - .expect("to load system data contract"); - - setup_system_data_contract(&platform.drive, &data_contract, Some(&transaction)); - - let owner_id = Identifier::new([1u8; 32]); - - let document_1 = get_withdrawal_document_fixture( - &data_contract, - owner_id, - platform_value!({ - "amount": 1000u64, - "coreFeePerByte": 1u32, - "pooling": Pooling::Never, - "outputScript": CoreScript::from_bytes((0..23).collect::>()), - "status": withdrawals_contract::WithdrawalStatus::BROADCASTED as u8, - "transactionIndex": 1u64, - "transactionSignHeight": 93u64, - "transactionId": Identifier::new([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), - }), - None, - platform_version.protocol_version, - ).expect("expected withdrawal document"); - - let document_type = data_contract - .document_type_for_name(withdrawal::NAME) - .expect("expected to get document type"); - - setup_document( - &platform.drive, - &document_1, - &data_contract, - document_type, - Some(&transaction), - ); - - let document_2 = get_withdrawal_document_fixture( - &data_contract, - owner_id, - platform_value!({ - "amount": 1000u64, - "coreFeePerByte": 1u32, - "pooling": Pooling::Never as u8, - "outputScript": CoreScript::from_bytes((0..23).collect::>()), - "status": withdrawals_contract::WithdrawalStatus::BROADCASTED as u8, - "transactionIndex": 2u64, - "transactionSignHeight": 10u64, - "transactionId": Identifier::new([3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), - }), - None, - platform_version.protocol_version, - ) - .expect("expected withdrawal document"); - - setup_document( - &platform.drive, - &document_2, - &data_contract, - document_type, - Some(&transaction), - ); - - platform - .update_broadcasted_withdrawal_transaction_statuses_v0( - &block_execution_context.into(), - &transaction, - platform_version, - ) - .expect("to update withdrawal statuses"); - - let documents = platform - .drive - .fetch_withdrawal_documents_by_status( - withdrawals_contract::WithdrawalStatus::EXPIRED.into(), - Some(&transaction), - platform_version, - ) - .expect("to fetch documents by status"); - - assert_eq!(documents.len(), 1); - assert_eq!( - documents.get(0).unwrap().id().to_vec(), - document_2.id().to_vec() - ); - - let documents = platform - .drive - .fetch_withdrawal_documents_by_status( - withdrawals_contract::WithdrawalStatus::COMPLETE.into(), - Some(&transaction), - platform_version, - ) - .expect("to fetch documents by status"); - - assert_eq!(documents.len(), 1); - assert_eq!( - documents.get(0).unwrap().id().to_vec(), - document_1.id().to_vec() - ); - } -} diff --git a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/v0/mod.rs index 8c034f320a6..62e472e41d8 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/v0/mod.rs @@ -311,8 +311,8 @@ mod tests { assert_eq!( root_hash, [ - 148, 196, 101, 163, 1, 56, 137, 50, 117, 196, 107, 103, 222, 55, 110, 59, 11, - 177, 172, 37, 17, 234, 56, 154, 232, 24, 208, 220, 27, 128, 201, 97 + 27, 145, 206, 187, 152, 191, 68, 167, 189, 194, 115, 111, 104, 166, 61, 254, + 94, 149, 9, 131, 231, 106, 224, 64, 180, 247, 129, 223, 128, 12, 42, 39 ] ) } diff --git a/packages/rs-drive-abci/src/execution/platform_events/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/mod.rs index 7e79189addd..79821827bea 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/mod.rs @@ -12,15 +12,13 @@ pub(in crate::execution) mod epoch; pub(in crate::execution) mod fee_pool_inwards_distribution; /// Fee pool outwards distribution towards proposers pub(in crate::execution) mod fee_pool_outwards_distribution; -/// Functions related to IdentityCreditWithdrawalTransaction -pub(in crate::execution) mod identity_credit_withdrawal; /// Initialization pub(in crate::execution) mod initialization; /// Protocol upgrade events pub(in crate::execution) mod protocol_upgrade; /// State transition processing pub(in crate::execution) mod state_transition_processing; -/// Platform withdrawals +/// Withdrawal methods pub(in crate::execution) mod withdrawals; /// Events happening what starting to process a block diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/append_signatures_and_broadcast_withdrawal_transactions/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/append_signatures_and_broadcast_withdrawal_transactions/mod.rs new file mode 100644 index 00000000000..de42c73bda7 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/append_signatures_and_broadcast_withdrawal_transactions/mod.rs @@ -0,0 +1,39 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::platform_types::platform::Platform; +use crate::platform_types::withdrawal::unsigned_withdrawal_txs::v0::UnsignedWithdrawalTxs; +use crate::rpc::core::CoreRPCLike; +use dpp::dashcore::bls_sig_utils::BLSSignature; +use dpp::version::PlatformVersion; + +mod v0; + +impl Platform +where + C: CoreRPCLike, +{ + /// Appends signatures to unsigned withdrawal transactions and broadcast them to Core + pub(in crate::execution) fn append_signatures_and_broadcast_withdrawal_transactions( + &self, + unsigned_withdrawal_transactions: UnsignedWithdrawalTxs, + signatures: Vec, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + match platform_version + .drive_abci + .methods + .withdrawals + .append_signatures_and_broadcast_withdrawal_transactions + { + 0 => self.append_signatures_and_broadcast_withdrawal_transactions_v0( + unsigned_withdrawal_transactions, + signatures, + ), + version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { + method: "append_signatures_and_broadcast_withdrawal_transactions".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/append_signatures_and_broadcast_withdrawal_transactions/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/append_signatures_and_broadcast_withdrawal_transactions/v0/mod.rs new file mode 100644 index 00000000000..035bc11a79c --- /dev/null +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/append_signatures_and_broadcast_withdrawal_transactions/v0/mod.rs @@ -0,0 +1,91 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::platform_types::platform::Platform; +use crate::platform_types::withdrawal::unsigned_withdrawal_txs::v0::UnsignedWithdrawalTxs; +use crate::rpc::core::{ + CoreRPCLike, CORE_RPC_ERROR_ASSET_UNLOCK_EXPIRED, CORE_RPC_ERROR_ASSET_UNLOCK_NO_ACTIVE_QUORUM, + CORE_RPC_TX_ALREADY_IN_CHAIN, +}; +use dashcore_rpc::jsonrpc; +use dashcore_rpc::Error as CoreRPCError; +use dpp::dashcore::bls_sig_utils::BLSSignature; +use dpp::dashcore::consensus; +use dpp::dashcore::transaction::special_transaction::TransactionPayload::AssetUnlockPayloadType; + +impl Platform +where + C: CoreRPCLike, +{ + pub(super) fn append_signatures_and_broadcast_withdrawal_transactions_v0( + &self, + unsigned_withdrawal_transactions: UnsignedWithdrawalTxs, + signatures: Vec, + ) -> Result<(), Error> { + if unsigned_withdrawal_transactions.is_empty() { + return Ok(()); + } + + if unsigned_withdrawal_transactions.len() != signatures.len() { + return Err(Error::Execution(ExecutionError::CorruptedCodeExecution( + "number of signatures must match number of withdrawal transactions", + ))); + } + + tracing::debug!( + "Broadcasting {} withdrawal transactions", + unsigned_withdrawal_transactions.len(), + ); + + for (mut transaction, signature) in + unsigned_withdrawal_transactions.into_iter().zip(signatures) + { + let Some(AssetUnlockPayloadType(mut payload)) = transaction.special_transaction_payload + else { + return Err(Error::Execution(ExecutionError::CorruptedCodeExecution( + "withdrawal transaction payload must be AssetUnlockPayloadType", + ))); + }; + + payload.quorum_sig = signature; + + let index = payload.base.index; + + transaction.special_transaction_payload = Some(AssetUnlockPayloadType(payload)); + + let tx_bytes = consensus::serialize(&transaction); + + // TODO: We need to broadcast all or none of the transactions (in case of error) + // will be fixed in upcoming PR + match self.core_rpc.send_raw_transaction(&tx_bytes) { + Ok(_) => { + tracing::debug!( + tx_id = transaction.txid().to_hex(), + index, + "Successfully broadcasted withdrawal transaction with index {}", + index + ); + } + // Ignore errors that can happen during blockchain catching. + // They will be logged with dashcore_rpc + Err(CoreRPCError::JsonRpc(jsonrpc::error::Error::Rpc(e))) + if e.code == CORE_RPC_TX_ALREADY_IN_CHAIN + || e.message == CORE_RPC_ERROR_ASSET_UNLOCK_NO_ACTIVE_QUORUM + || e.message == CORE_RPC_ERROR_ASSET_UNLOCK_EXPIRED => {} + // Errors that can happen if we created invalid tx or Core isn't responding + Err(e) => { + tracing::error!( + tx_id = transaction.txid().to_string(), + index, + "Failed to broadcast asset unlock transaction {}: {}", + index, + e + ); + + return Err(e.into()); + } + } + } + + Ok(()) + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/build_withdrawal_transactions_from_documents/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/mod.rs similarity index 64% rename from packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/build_withdrawal_transactions_from_documents/mod.rs rename to packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/mod.rs index 5ee1549005f..e4cd110298d 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/build_withdrawal_transactions_from_documents/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/mod.rs @@ -5,9 +5,9 @@ use crate::rpc::core::CoreRPCLike; use dpp::document::Document; use dpp::identifier::Identifier; use dpp::version::PlatformVersion; -use drive::drive::batch::DriveOperation; -use drive::drive::identity::withdrawals::WithdrawalTransactionIdAndBytes; -use drive::grovedb::TransactionArg; +use drive::drive::identity::withdrawals::{ + WithdrawalTransactionIndex, WithdrawalTransactionIndexAndBytes, +}; use std::collections::HashMap; mod v0; @@ -30,26 +30,23 @@ where /// # Returns /// /// * `Result, Error>` - Returns a HashMap containing withdrawal transactions if found, otherwise returns an `Error`. - pub(in crate::execution::platform_events::identity_credit_withdrawal) fn build_withdrawal_transactions_from_documents( + pub(in crate::execution::platform_events::withdrawals) fn build_untied_withdrawal_transactions_from_documents( &self, documents: &[Document], - drive_operation_types: &mut Vec, - transaction: TransactionArg, + start_index: WithdrawalTransactionIndex, platform_version: &PlatformVersion, - ) -> Result, Error> { + ) -> Result, Error> { match platform_version .drive_abci .methods - .identity_credit_withdrawal - .build_withdrawal_transactions_from_documents + .withdrawals + .build_untied_withdrawal_transactions_from_documents { - 0 => self.build_withdrawal_transactions_from_documents_v0( - documents, - drive_operation_types, - transaction, - ), + 0 => { + self.build_untied_withdrawal_transactions_from_documents_v0(documents, start_index) + } version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { - method: "build_withdrawal_transactions_from_documents".to_string(), + method: "build_untied_withdrawal_transactions_from_documents".to_string(), known_versions: vec![0], received: version, })), diff --git a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/build_withdrawal_transactions_from_documents/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/v0/mod.rs similarity index 77% rename from packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/build_withdrawal_transactions_from_documents/v0/mod.rs rename to packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/v0/mod.rs index e69975245d3..e86aa33f61a 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/build_withdrawal_transactions_from_documents/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/v0/mod.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; use dashcore_rpc::dashcore::{ - blockdata::transaction::special_transaction::asset_unlock::unqualified_asset_unlock::{ - AssetUnlockBasePayload, AssetUnlockBaseTransactionInfo, + blockdata::transaction::special_transaction::asset_unlock::{ + qualified_asset_unlock::ASSET_UNLOCK_TX_SIZE, + unqualified_asset_unlock::{AssetUnlockBasePayload, AssetUnlockBaseTransactionInfo}, }, consensus::Encodable, ScriptBuf, TxOut, @@ -13,8 +14,9 @@ use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdr use drive::dpp::identifier::Identifier; use drive::dpp::identity::convert_credits_to_duffs; -use drive::drive::identity::withdrawals::WithdrawalTransactionIdAndBytes; -use drive::{drive::batch::DriveOperation, query::TransactionArg}; +use drive::drive::identity::withdrawals::{ + WithdrawalTransactionIndex, WithdrawalTransactionIndexAndBytes, +}; use crate::{ error::{execution::ExecutionError, Error}, @@ -27,20 +29,13 @@ where C: CoreRPCLike, { /// Build list of Core transactions from withdrawal documents - pub(super) fn build_withdrawal_transactions_from_documents_v0( + pub(super) fn build_untied_withdrawal_transactions_from_documents_v0( &self, documents: &[Document], - drive_operation_types: &mut Vec, - transaction: TransactionArg, - ) -> Result, Error> { - let mut withdrawals: HashMap = HashMap::new(); - - let latest_withdrawal_index = self - .drive - .fetch_and_remove_latest_withdrawal_transaction_index_operations( - drive_operation_types, - transaction, - )?; + start_index: WithdrawalTransactionIndex, + ) -> Result, Error> { + let mut withdrawals: HashMap = + HashMap::new(); for (i, document) in documents.iter().enumerate() { let output_script_bytes = document @@ -70,8 +65,6 @@ where )) })?; - let state_transition_size = 190; - let output_script = ScriptBuf::from_bytes(output_script_bytes); let tx_out = TxOut { @@ -79,7 +72,7 @@ where script_pubkey: output_script, }; - let transaction_index = latest_withdrawal_index + i as u64; + let transaction_index = start_index + i as WithdrawalTransactionIndex; let withdrawal_transaction = AssetUnlockBaseTransactionInfo { version: 1, @@ -88,7 +81,7 @@ where base_payload: AssetUnlockBasePayload { version: 1, index: transaction_index, - fee: (state_transition_size * core_fee_per_byte * 1000), + fee: ASSET_UNLOCK_TX_SIZE as u32 * core_fee_per_byte, }, }; @@ -102,10 +95,7 @@ where )) })?; - withdrawals.insert( - document.id(), - (transaction_index.to_be_bytes().to_vec(), transaction_buffer), - ); + withdrawals.insert(document.id(), (transaction_index, transaction_buffer)); } Ok(withdrawals) @@ -122,8 +112,6 @@ mod tests { use drive::tests::helpers::setup::setup_document; mod build_withdrawal_transactions_from_documents { - use dpp::block::block_info::BlockInfo; - use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contracts::withdrawals_contract::v1::document_types::withdrawal; use dpp::identity::core_script::CoreScript; @@ -131,7 +119,7 @@ mod tests { use dpp::prelude::Identifier; use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; use dpp::version::PlatformVersion; - use drive::drive::identity::withdrawals::WithdrawalTransactionIdAndBytes; + use drive::drive::identity::withdrawals::WithdrawalTransactionIndexAndBytes; use drive::tests::helpers::setup::setup_system_data_contract; use itertools::Itertools; @@ -149,7 +137,7 @@ mod tests { let transaction = platform.drive.grove.start_transaction(); let data_contract = - load_system_data_contract(SystemDataContract::Withdrawals, &platform_version) + load_system_data_contract(SystemDataContract::Withdrawals, platform_version) .expect("to load system data contract"); setup_system_data_contract(&platform.drive, &data_contract, Some(&transaction)); @@ -210,54 +198,37 @@ mod tests { let documents = vec![document_1, document_2]; - let mut batch = vec![]; - let transactions = platform - .build_withdrawal_transactions_from_documents_v0( - &documents, - &mut batch, - Some(&transaction), - ) + .build_untied_withdrawal_transactions_from_documents_v0(&documents, 50) .expect("to build transactions from documents"); - platform - .drive - .apply_drive_operations( - batch, - true, - &BlockInfo::default(), - Some(&transaction), - platform_version, - ) - .expect("to apply drive op batch"); - assert_eq!( transactions .values() .cloned() .sorted() - .collect::>(), + .collect::>(), vec![ ( - vec![0, 0, 0, 0, 0, 0, 0, 0], + 50, vec![ 1, 0, 9, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 48, 230, 2, 0 + 1, 50, 0, 0, 0, 0, 0, 0, 0, 190, 0, 0, 0 ], ), ( - vec![0, 0, 0, 0, 0, 0, 0, 1], + 51, vec![ 1, 0, 9, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 0, 0, 0, 0, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 48, 230, 2, 0 + 1, 51, 0, 0, 0, 0, 0, 0, 0, 190, 0, 0, 0 ], ), ] .into_iter() .sorted() - .collect::>(), + .collect::>(), ); } } diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/check_withdrawals/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/check_withdrawals/mod.rs deleted file mode 100644 index a1f73a4753a..00000000000 --- a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/check_withdrawals/mod.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::abci::AbciError; -use crate::error::execution::ExecutionError; -use crate::error::Error; -use crate::platform_types::platform::Platform; -use crate::platform_types::withdrawal::withdrawal_txs; -use crate::rpc::core::CoreRPCLike; -use dpp::bls_signatures; -use dpp::validation::SimpleValidationResult; -use dpp::version::PlatformVersion; - -mod v0; - -impl Platform -where - C: CoreRPCLike, -{ - /// Version-dependent method that checks the validity of withdrawal transactions. - /// - /// Based on the `platform_version` passed, this function will route to the appropriate versioned - /// implementation of the `check_withdrawals` method. Each implementation will compare the received - /// withdrawal transactions with the expected ones, returning an error if they do not match. - /// - /// If a validator public key is provided, each versioned method also verifies the withdrawal - /// transactions' signatures. The `platform_version` parameter dictates which version of the - /// method to call. If an unsupported version is passed, the function will return an - /// `Error::Execution` with an `ExecutionError::UnknownVersionMismatch` error. - /// - /// # Arguments - /// - /// * `received_withdrawals` - The withdrawal transactions received. - /// * `our_withdrawals` - The expected withdrawal transactions. - /// * `height` - The block height. - /// * `round` - The consensus round. - /// * `verify_with_validator_public_key` - An optional reference to a validator public key. - /// * `quorum_hash` - An optional byte slice reference containing the quorum hash. - /// * `platform_version` - A `PlatformVersion` reference dictating which version of the method to call. - /// - /// # Returns - /// - /// * `Result, Error>` - On success, a `SimpleValidationResult` - /// containing an `AbciError` is returned. On error, an `Error` is returned. - /// - pub(crate) fn check_withdrawals( - &self, - received_withdrawals: &withdrawal_txs::v0::WithdrawalTxs, - our_withdrawals: &withdrawal_txs::v0::WithdrawalTxs, - height: u64, - round: u32, - verify_with_validator_public_key: Option<&bls_signatures::PublicKey>, - quorum_hash: Option<&[u8]>, - platform_version: &PlatformVersion, - ) -> Result, Error> { - match platform_version - .drive_abci - .methods - .withdrawals - .check_withdrawals - { - 0 => Ok(self.check_withdrawals_v0( - received_withdrawals, - our_withdrawals, - height, - round, - verify_with_validator_public_key, - quorum_hash, - )), - version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { - method: "check_withdrawals".to_string(), - known_versions: vec![0], - received: version, - })), - } - } -} diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/check_withdrawals/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/check_withdrawals/v0/mod.rs deleted file mode 100644 index 32a957cf4f0..00000000000 --- a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/check_withdrawals/v0/mod.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::abci::AbciError; -use crate::platform_types::platform::Platform; -use crate::platform_types::withdrawal::withdrawal_txs; -use crate::rpc::core::CoreRPCLike; -use dpp::bls_signatures; -use dpp::validation::SimpleValidationResult; - -impl Platform -where - C: CoreRPCLike, -{ - /// Checks if the received withdrawal transactions are correct and match the expected withdrawal transactions. - /// - /// This function compares the received withdrawal transactions with the expected ones. If they don't match, - /// an error is returned. If a validator public key is provided, the function also verifies the withdrawal - /// transactions' signatures. - /// - /// # Arguments - /// - /// * `received_withdrawals` - The withdrawal transactions received. - /// * `our_withdrawals` - The expected withdrawal transactions. - /// * `height` - The block height. - /// * `round` - The consensus round. - /// * `verify_with_validator_public_key` - An optional reference to a validator public key. - /// * `quorum_hash` - An optional byte slice reference containing the quorum hash. - /// - /// # Returns - /// - /// * `SimpleValidationResult` - If the received withdrawal transactions match the expected ones - /// and the signatures are valid (if provided), it returns a default `SimpleValidationResult`. Otherwise, - /// it returns a `SimpleValidationResult` with an error. - /// - pub(super) fn check_withdrawals_v0( - &self, - received_withdrawals: &withdrawal_txs::v0::WithdrawalTxs, - our_withdrawals: &withdrawal_txs::v0::WithdrawalTxs, - height: u64, - round: u32, - verify_with_validator_public_key: Option<&bls_signatures::PublicKey>, - quorum_hash: Option<&[u8]>, - ) -> SimpleValidationResult { - if received_withdrawals.ne(our_withdrawals) { - return SimpleValidationResult::new_with_error( - AbciError::VoteExtensionMismatchReceived { - got: received_withdrawals.to_string(), - expected: our_withdrawals.to_string(), - }, - ); - } - - // we only verify if verify_with_validator_public_key exists - if let Some(validator_public_key) = verify_with_validator_public_key { - let quorum_hash = quorum_hash.expect("quorum hash is required to verify signature"); - let validation_result = received_withdrawals.verify_signatures( - &self.config.abci.chain_id, - self.config.validator_set_quorum_type(), - quorum_hash, - height, - round, - validator_public_key, - ); - - if validation_result.is_valid() { - SimpleValidationResult::default() - } else { - SimpleValidationResult::new_with_error( - validation_result - .errors - .into_iter() - .next() - .expect("expected an error"), - ) - } - } else { - SimpleValidationResult::default() - } - } -} diff --git a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/fetch_and_prepare_unsigned_withdrawal_transactions/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/dequeue_and_build_unsigned_withdrawal_transactions/mod.rs similarity index 72% rename from packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/fetch_and_prepare_unsigned_withdrawal_transactions/mod.rs rename to packages/rs-drive-abci/src/execution/platform_events/withdrawals/dequeue_and_build_unsigned_withdrawal_transactions/mod.rs index aa0430c75fe..b67a25a1b31 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/fetch_and_prepare_unsigned_withdrawal_transactions/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/dequeue_and_build_unsigned_withdrawal_transactions/mod.rs @@ -1,10 +1,11 @@ use crate::error::execution::ExecutionError; use crate::error::Error; -use crate::execution::types::block_execution_context::BlockExecutionContext; use crate::platform_types::platform::Platform; +use crate::platform_types::withdrawal::unsigned_withdrawal_txs::v0::UnsignedWithdrawalTxs; use crate::rpc::core::CoreRPCLike; +use dpp::block::block_info::BlockInfo; use dpp::version::PlatformVersion; -use drive::grovedb::Transaction; +use drive::grovedb::TransactionArg; mod v0; @@ -26,27 +27,27 @@ where /// # Returns /// /// * `Result>, Error>` - Returns a Vector of unsigned withdrawal transactions if found, otherwise returns an `Error`. - pub(in crate::execution) fn fetch_and_prepare_unsigned_withdrawal_transactions( + pub(in crate::execution) fn dequeue_and_build_unsigned_withdrawal_transactions( &self, validator_set_quorum_hash: [u8; 32], - block_execution_context: &BlockExecutionContext, - transaction: &Transaction, + block_info: &BlockInfo, + transaction: TransactionArg, platform_version: &PlatformVersion, - ) -> Result>, Error> { + ) -> Result { match platform_version .drive_abci .methods - .identity_credit_withdrawal - .fetch_and_prepare_unsigned_withdrawal_transactions + .withdrawals + .dequeue_and_build_unsigned_withdrawal_transactions { - 0 => self.fetch_and_prepare_unsigned_withdrawal_transactions_v0( + 0 => self.dequeue_and_build_unsigned_withdrawal_transactions_v0( validator_set_quorum_hash, - block_execution_context, + block_info, transaction, platform_version, ), version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { - method: "fetch_and_prepare_unsigned_withdrawal_transactions".to_string(), + method: "dequeue_and_build_unsigned_withdrawal_transactions".to_string(), known_versions: vec![0], received: version, })), diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/dequeue_and_build_unsigned_withdrawal_transactions/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/dequeue_and_build_unsigned_withdrawal_transactions/v0/mod.rs new file mode 100644 index 00000000000..b654a1e56be --- /dev/null +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/dequeue_and_build_unsigned_withdrawal_transactions/v0/mod.rs @@ -0,0 +1,199 @@ +use dashcore_rpc::dashcore::{ + blockdata::transaction::special_transaction::asset_unlock::request_info::AssetUnlockRequestInfo, + hashes::Hash, QuorumHash, +}; +use dpp::block::block_info::BlockInfo; +use dpp::dashcore::transaction::special_transaction::asset_unlock::qualified_asset_unlock::build_asset_unlock_tx; +use dpp::dashcore::Transaction; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::document::document_methods::DocumentMethodsV0; +use dpp::document::{Document, DocumentV0Setters}; +use dpp::version::PlatformVersion; + +use drive::dpp::system_data_contracts::withdrawals_contract; +use drive::dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; + +use drive::drive::batch::DriveOperation; +use drive::drive::identity::withdrawals::WithdrawalTransactionIndex; +use drive::query::TransactionArg; + +use crate::platform_types::withdrawal::unsigned_withdrawal_txs::v0::UnsignedWithdrawalTxs; +use crate::rpc::core::CoreHeight; +use crate::{ + error::{execution::ExecutionError, Error}, + platform_types::platform::Platform, + rpc::core::CoreRPCLike, +}; +use dpp::errors::ProtocolError; +use drive::drive::config::DEFAULT_QUERY_LIMIT; + +const WITHDRAWAL_TRANSACTIONS_QUERY_LIMIT: u16 = 16; + +impl Platform +where + C: CoreRPCLike, +{ + /// Prepares a list of an unsigned withdrawal transaction bytes + pub(super) fn dequeue_and_build_unsigned_withdrawal_transactions_v0( + &self, + validator_set_quorum_hash: [u8; 32], + block_info: &BlockInfo, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let mut drive_operations: Vec = vec![]; + + // Get 16 latest withdrawal transactions from the queue + let untied_withdrawal_transactions = self.drive.dequeue_untied_withdrawal_transactions( + WITHDRAWAL_TRANSACTIONS_QUERY_LIMIT, + transaction, + &mut drive_operations, + platform_version, + )?; + + if untied_withdrawal_transactions.is_empty() { + return Ok(UnsignedWithdrawalTxs::default()); + } + + let transaction_indices = untied_withdrawal_transactions + .iter() + .map(|(transaction_id, _)| *transaction_id) + .collect::>(); + + let documents = self.fetch_and_modify_withdrawal_documents_to_broadcasted_by_indices( + &transaction_indices, + block_info, + transaction, + platform_version, + )?; + + tracing::debug!( + "Deque {} unsigned withdrawal transactions for signing with indices from {} to {}", + documents.len(), + transaction_indices.first().expect("must be present"), + transaction_indices.last().expect("must be present") + ); + + let cache = self.drive.cache.read().unwrap(); + + self.drive.add_update_multiple_documents_operations( + &documents, + &cache.system_data_contracts.withdrawals, + cache + .system_data_contracts + .withdrawals + .document_type_for_name(withdrawal::NAME) + .map_err(|_| { + Error::Execution(ExecutionError::CorruptedCodeExecution( + "could not get document type", + )) + })?, + &mut drive_operations, + &platform_version.drive, + )?; + + // Appending request_height and quorum_hash to withdrawal transaction + let unsigned_withdrawal_transactions = untied_withdrawal_transactions + .into_iter() + .map(|(_, untied_transaction_bytes)| { + build_unsigned_transaction( + untied_transaction_bytes, + validator_set_quorum_hash, + block_info.core_height, + ) + }) + .collect::>()?; + + self.drive.apply_drive_operations( + drive_operations, + true, + block_info, + transaction, + platform_version, + )?; + + Ok(UnsignedWithdrawalTxs::from_vec( + unsigned_withdrawal_transactions, + )) + } + + fn fetch_and_modify_withdrawal_documents_to_broadcasted_by_indices( + &self, + transaction_indices: &[WithdrawalTransactionIndex], + block_info: &BlockInfo, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let documents = self + .drive + .find_withdrawal_documents_by_status_and_transaction_indices( + withdrawals_contract::WithdrawalStatus::POOLED, + transaction_indices, + DEFAULT_QUERY_LIMIT, + transaction, + platform_version, + )?; + + documents + .into_iter() + .map(|mut document| { + document.set_i64( + withdrawal::properties::STATUS, + withdrawals_contract::WithdrawalStatus::BROADCASTED as i64, + ); + + document.set_u64( + withdrawal::properties::TRANSACTION_SIGN_HEIGHT, + block_info.core_height as u64, + ); + + document.set_i64( + withdrawal::properties::UPDATED_AT, + block_info.time_ms.try_into().map_err(|_| { + Error::Execution(ExecutionError::CorruptedCodeExecution( + "Can't convert u64 block time to i64 updated_at", + )) + })?, + ); + + document.increment_revision().map_err(|_| { + Error::Execution(ExecutionError::CorruptedCodeExecution( + "Could not increment document revision", + )) + })?; + + Ok(document) + }) + .collect() + } +} + +fn build_unsigned_transaction( + untied_transaction_bytes: Vec, + mut validator_set_quorum_hash: [u8; 32], + core_chain_locked_height: CoreHeight, +) -> Result { + // Core expects it reversed + validator_set_quorum_hash.reverse(); + + let request_info = AssetUnlockRequestInfo { + request_height: core_chain_locked_height, + quorum_hash: QuorumHash::from_byte_array(validator_set_quorum_hash), + }; + + let mut unsigned_transaction_bytes = vec![]; + + request_info + .consensus_append_to_base_encode( + untied_transaction_bytes.clone(), + &mut unsigned_transaction_bytes, + ) + .map_err(|_| { + Error::Execution(ExecutionError::CorruptedCodeExecution( + "could not add additional request info to asset unlock transaction", + )) + })?; + + build_asset_unlock_tx(&unsigned_transaction_bytes) + .map_err(|error| Error::Protocol(ProtocolError::DashCoreError(error))) +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/fetch_transactions_block_inclusion_status/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/fetch_transactions_block_inclusion_status/mod.rs similarity index 79% rename from packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/fetch_transactions_block_inclusion_status/mod.rs rename to packages/rs-drive-abci/src/execution/platform_events/withdrawals/fetch_transactions_block_inclusion_status/mod.rs index d882062c882..0a4b17062eb 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/fetch_transactions_block_inclusion_status/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/fetch_transactions_block_inclusion_status/mod.rs @@ -2,7 +2,9 @@ use crate::error::execution::ExecutionError; use crate::error::Error; use crate::platform_types::platform::Platform; use crate::rpc::core::CoreRPCLike; +use dashcore_rpc::json::AssetUnlockStatus; use dpp::version::PlatformVersion; +use drive::drive::identity::withdrawals::WithdrawalTransactionIndex; use std::collections::BTreeMap; mod v0; @@ -24,21 +26,21 @@ where /// # Returns /// /// * `Result, Error>` - Returns a Vector of transaction hashes if found, otherwise returns an `Error`. - pub(in crate::execution::platform_events::identity_credit_withdrawal) fn fetch_transactions_block_inclusion_status( + pub(in crate::execution::platform_events::withdrawals) fn fetch_transactions_block_inclusion_status( &self, current_chain_locked_core_height: u32, - transaction_identifiers: Vec<[u8; 32]>, + withdrawal_indices: &[WithdrawalTransactionIndex], platform_version: &PlatformVersion, - ) -> Result, Error> { + ) -> Result, Error> { match platform_version .drive_abci .methods - .identity_credit_withdrawal + .withdrawals .fetch_transactions_block_inclusion_status { 0 => self.fetch_transactions_block_inclusion_status_v0( current_chain_locked_core_height, - transaction_identifiers, + withdrawal_indices, ), version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { method: "fetch_transactions_block_inclusion_status".to_string(), diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/fetch_transactions_block_inclusion_status/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/fetch_transactions_block_inclusion_status/v0/mod.rs new file mode 100644 index 00000000000..86433d8c03f --- /dev/null +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/fetch_transactions_block_inclusion_status/v0/mod.rs @@ -0,0 +1,31 @@ +use dashcore_rpc::dashcore_rpc_json::AssetUnlockStatus; +use drive::drive::identity::withdrawals::WithdrawalTransactionIndex; +use std::collections::BTreeMap; + +use crate::{error::Error, platform_types::platform::Platform, rpc::core::CoreRPCLike}; + +impl Platform +where + C: CoreRPCLike, +{ + /// Fetch Core transactions by range of Core heights + pub(super) fn fetch_transactions_block_inclusion_status_v0( + &self, + current_chain_locked_core_height: u32, + withdrawal_indices: &[WithdrawalTransactionIndex], + ) -> Result, Error> { + let asset_unlock_statuses_result = self + .core_rpc + .get_asset_unlock_statuses(withdrawal_indices, current_chain_locked_core_height)?; + + Ok(asset_unlock_statuses_result + .into_iter() + .map(|asset_unlock_status_result| { + ( + asset_unlock_status_result.index, + asset_unlock_status_result.status, + ) + }) + .collect()) + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/mod.rs index 2c5c4be28ed..75412110566 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/mod.rs @@ -1 +1,6 @@ -mod check_withdrawals; +pub(in crate::execution) mod append_signatures_and_broadcast_withdrawal_transactions; +pub(in crate::execution) mod build_untied_withdrawal_transactions_from_documents; +pub(in crate::execution) mod dequeue_and_build_unsigned_withdrawal_transactions; +pub(in crate::execution) mod fetch_transactions_block_inclusion_status; +pub(in crate::execution) mod pool_withdrawals_into_transactions_queue; +pub(in crate::execution) mod update_broadcasted_withdrawal_statuses; diff --git a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/pool_withdrawals_into_transactions_queue/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/mod.rs similarity index 85% rename from packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/pool_withdrawals_into_transactions_queue/mod.rs rename to packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/mod.rs index fded57be054..5d79a1c6018 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/pool_withdrawals_into_transactions_queue/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/mod.rs @@ -1,10 +1,10 @@ use crate::error::execution::ExecutionError; use crate::error::Error; -use crate::execution::types::block_execution_context::BlockExecutionContext; use crate::platform_types::platform::Platform; use crate::rpc::core::CoreRPCLike; +use dpp::block::block_info::BlockInfo; use dpp::version::PlatformVersion; -use drive::grovedb::Transaction; +use drive::grovedb::TransactionArg; mod v0; @@ -27,18 +27,18 @@ where /// * `Result<(), Error>` - Returns `Ok(())` if successful, otherwise returns an `Error`. pub(in crate::execution) fn pool_withdrawals_into_transactions_queue( &self, - block_execution_context: &BlockExecutionContext, - transaction: &Transaction, + block_info: &BlockInfo, + transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result<(), Error> { match platform_version .drive_abci .methods - .identity_credit_withdrawal + .withdrawals .pool_withdrawals_into_transactions_queue { 0 => self.pool_withdrawals_into_transactions_queue_v0( - block_execution_context, + block_info, transaction, platform_version, ), diff --git a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/pool_withdrawals_into_transactions_queue/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v0/mod.rs similarity index 52% rename from packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/pool_withdrawals_into_transactions_queue/v0/mod.rs rename to packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v0/mod.rs index 5778cc00b95..8562c69f257 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/pool_withdrawals_into_transactions_queue/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v0/mod.rs @@ -5,17 +5,16 @@ use dpp::document::document_methods::DocumentMethodsV0; use dpp::document::{DocumentV0Getters, DocumentV0Setters}; use dpp::version::PlatformVersion; -use drive::dpp::util::hash; -use drive::drive::identity::withdrawals::WithdrawalTransactionIdAndBytes; -use drive::grovedb::Transaction; +use drive::drive::identity::withdrawals::{ + WithdrawalTransactionIndex, WithdrawalTransactionIndexAndBytes, +}; +use drive::grovedb::TransactionArg; use dpp::system_data_contracts::withdrawals_contract; use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; +use drive::drive::config::DEFAULT_QUERY_LIMIT; +use drive::query::DriveQuery; -use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0Getters; -use crate::execution::types::block_execution_context::BlockExecutionContext; -use crate::execution::types::block_state_info::v0::BlockStateInfoV0Getters; -use crate::platform_types::epoch_info::v0::EpochInfoV0Getters; use crate::{ error::{execution::ExecutionError, Error}, platform_types::platform::Platform, @@ -29,37 +28,14 @@ where /// Pool withdrawal documents into transactions pub(super) fn pool_withdrawals_into_transactions_queue_v0( &self, - block_execution_context: &BlockExecutionContext, - transaction: &Transaction, + block_info: &BlockInfo, + transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result<(), Error> { - let block_info = BlockInfo { - time_ms: block_execution_context.block_state_info().block_time_ms(), - height: block_execution_context.block_state_info().height(), - core_height: block_execution_context - .block_state_info() - .core_chain_locked_height(), - epoch: Epoch::new(block_execution_context.epoch_info().current_epoch_index())?, - }; - - let data_contract_id = withdrawals_contract::ID; - - let (_, Some(contract_fetch_info)) = self.drive.get_contract_with_fetch_info_and_fee( - data_contract_id.to_buffer(), - None, - true, - Some(transaction), - platform_version, - )? - else { - return Err(Error::Execution(ExecutionError::CorruptedCodeExecution( - "can't fetch withdrawal data contract", - ))); - }; - - let mut documents = self.drive.fetch_withdrawal_documents_by_status( + let mut documents = self.drive.fetch_oldest_withdrawal_documents_by_status( withdrawals_contract::WithdrawalStatus::QUEUED.into(), - Some(transaction), + DEFAULT_QUERY_LIMIT, + transaction, platform_version, )?; @@ -67,27 +43,28 @@ where return Ok(()); } - let mut drive_operations = vec![]; + let start_transaction_index = self + .drive + .fetch_next_withdrawal_transaction_index(transaction, platform_version)?; - let withdrawal_transactions = self.build_withdrawal_transactions_from_documents( - &documents, - &mut drive_operations, - Some(transaction), - platform_version, - )?; + let untied_withdrawal_transactions = self + .build_untied_withdrawal_transactions_from_documents( + &documents, + start_transaction_index, + platform_version, + )?; for document in documents.iter_mut() { - let Some((_, transaction_bytes)) = withdrawal_transactions.get(&document.id()) else { + let Some((transaction_index, _)) = untied_withdrawal_transactions.get(&document.id()) + else { return Err(Error::Execution(ExecutionError::CorruptedCodeExecution( "transactions must contain a transaction", ))); }; - let transaction_id = hash::hash_to_vec(transaction_bytes); - - document.set_bytes( - withdrawal::properties::TRANSACTION_ID, - transaction_id.clone(), + document.set_u64( + withdrawal::properties::TRANSACTION_INDEX, + *transaction_index, ); document.set_u8( @@ -111,11 +88,45 @@ where })?; } + let withdrawal_transactions: Vec = + untied_withdrawal_transactions.into_values().collect(); + + let withdrawal_transactions_count = withdrawal_transactions.len(); + + let mut drive_operations = Vec::new(); + + self.drive + .add_enqueue_untied_withdrawal_transaction_operations( + withdrawal_transactions, + &mut drive_operations, + platform_version, + )?; + + let end_transaction_index = start_transaction_index + withdrawal_transactions_count as u64; + + self.drive + .add_update_next_withdrawal_transaction_index_operation( + end_transaction_index, + &mut drive_operations, + platform_version, + )?; + + tracing::debug!( + "Pooled {} withdrawal documents into {} transactions with indices from {} to {}", + documents.len(), + withdrawal_transactions_count, + start_transaction_index, + end_transaction_index, + ); + + let cache = self.drive.cache.read().unwrap(); + self.drive.add_update_multiple_documents_operations( &documents, - &contract_fetch_info.contract, - contract_fetch_info - .contract + &cache.system_data_contracts.withdrawals, + cache + .system_data_contracts + .withdrawals .document_type_for_name(withdrawal::NAME) .map_err(|_| { Error::Execution(ExecutionError::CorruptedCodeExecution( @@ -126,19 +137,11 @@ where &platform_version.drive, )?; - let withdrawal_transactions: Vec = - withdrawal_transactions.values().cloned().collect(); - - self.drive.add_enqueue_withdrawal_transaction_operations( - &withdrawal_transactions, - &mut drive_operations, - ); - self.drive.apply_drive_operations( drive_operations, true, - &block_info, - Some(transaction), + block_info, + transaction, platform_version, )?; @@ -149,8 +152,6 @@ where #[cfg(test)] mod tests { use super::*; - use dashcore_rpc::dashcore::QuorumHash; - use dpp::dashcore::hashes::Hash; use dpp::data_contracts::SystemDataContract; use dpp::identifier::Identifier; @@ -159,10 +160,6 @@ mod tests { use dpp::withdrawal::Pooling; use drive::tests::helpers::setup::{setup_document, setup_system_data_contract}; - use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0; - use crate::execution::types::block_state_info::v0::BlockStateInfoV0; - use crate::platform_types::epoch_info::v0::EpochInfoV0; - use crate::platform_types::platform_state::v0::PlatformStateV0; use crate::test::helpers::setup::TestPlatformBuilder; use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; use dpp::platform_value::platform_value; @@ -178,51 +175,15 @@ mod tests { let transaction = platform.drive.grove.start_transaction(); - platform.block_execution_context.write().unwrap().replace( - BlockExecutionContextV0 { - block_state_info: BlockStateInfoV0 { - height: 1, - round: 0, - block_time_ms: 1, - previous_block_time_ms: Some(1), - proposer_pro_tx_hash: [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ], - core_chain_locked_height: 96, - block_hash: None, - app_hash: None, - } - .into(), - epoch_info: EpochInfoV0 { - current_epoch_index: 1, - previous_epoch_index: None, - is_epoch_change: false, - } - .into(), - hpmn_count: 100, - withdrawal_transactions: Default::default(), - block_platform_state: PlatformStateV0 { - last_committed_block_info: None, - current_protocol_version_in_consensus: 0, - next_epoch_protocol_version: 0, - current_validator_set_quorum_hash: QuorumHash::all_zeros(), - next_validator_set_quorum_hash: None, - validator_sets: Default::default(), - chain_lock_validating_quorums: Default::default(), - previous_height_chain_lock_validating_quorums: None, - full_masternode_list: Default::default(), - hpmn_masternode_list: Default::default(), - genesis_block_info: None, - } - .into(), - proposer_results: None, - } - .into(), - ); + let block_info = BlockInfo { + time_ms: 1, + height: 1, + core_height: 96, + epoch: Epoch::default(), + }; let data_contract = - load_system_data_contract(SystemDataContract::Withdrawals, &platform_version) + load_system_data_contract(SystemDataContract::Withdrawals, platform_version) .expect("to load system data contract"); setup_system_data_contract(&platform.drive, &data_contract, Some(&transaction)); @@ -281,41 +242,33 @@ mod tests { Some(&transaction), ); - let guarded_block_execution_context = platform.block_execution_context.write().unwrap(); - let block_execution_context = guarded_block_execution_context.as_ref().unwrap(); platform .pool_withdrawals_into_transactions_queue_v0( - block_execution_context, - &transaction, + &block_info, + Some(&transaction), platform_version, ) .expect("to pool withdrawal documents into transactions"); let updated_documents = platform .drive - .fetch_withdrawal_documents_by_status( + .fetch_oldest_withdrawal_documents_by_status( withdrawals_contract::WithdrawalStatus::POOLED.into(), + DEFAULT_QUERY_LIMIT, Some(&transaction), platform_version, ) .expect("to fetch withdrawal documents"); - let tx_ids = [ - "4b74f91644215904ff1aa4122b204ba674aea74d99a17c03fbda483692bf735b", - "897ec16cb13d802ee6acdaf55274c59f3509a4929d726bab919a962ed4a8703c", - ]; - - for document in updated_documents { + for (i, document) in updated_documents.into_iter().enumerate() { assert_eq!(document.revision(), Some(2)); - let tx_id: Vec = document + let tx_index = document .properties() - .get_bytes("transactionId") - .expect("to get transactionId"); - - let tx_id_hex = hex::encode(tx_id); + .get_u64(withdrawal::properties::TRANSACTION_INDEX) + .expect("to get transactionIndex"); - assert!(tx_ids.contains(&tx_id_hex.as_str())); + assert_eq!(tx_index, i as u64); } } } diff --git a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/update_broadcasted_withdrawal_transaction_statuses/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/update_broadcasted_withdrawal_statuses/mod.rs similarity index 76% rename from packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/update_broadcasted_withdrawal_transaction_statuses/mod.rs rename to packages/rs-drive-abci/src/execution/platform_events/withdrawals/update_broadcasted_withdrawal_statuses/mod.rs index d764c5a33ce..af763467638 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/identity_credit_withdrawal/update_broadcasted_withdrawal_transaction_statuses/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/update_broadcasted_withdrawal_statuses/mod.rs @@ -1,8 +1,8 @@ use crate::error::execution::ExecutionError; use crate::error::Error; -use crate::execution::types::block_execution_context::BlockExecutionContext; use crate::platform_types::platform::Platform; use crate::rpc::core::CoreRPCLike; +use dpp::block::block_info::BlockInfo; use dpp::version::PlatformVersion; use drive::grovedb::Transaction; @@ -14,7 +14,7 @@ where { /// Update statuses for broadcasted withdrawals /// - /// This method is responsible for updating the status of withdrawal transactions that have been broadcasted. + /// This method is responsible for updating the status of withdrawal transactions that have been broadcasted and reached finality. /// This is done based on the height of the last synced core block, which helps in determining whether the withdrawal /// transaction has been completed or expired. /// @@ -35,25 +35,25 @@ where /// * There is an issue interacting with the underlying storage. /// * There is an error fetching the withdrawal data contract. /// * There is an error getting the transactionId or transactionSignHeight from the withdrawal document. - pub fn update_broadcasted_withdrawal_transaction_statuses( + pub fn update_broadcasted_withdrawal_statuses( &self, - block_execution_context: &BlockExecutionContext, + block_info: &BlockInfo, transaction: &Transaction, platform_version: &PlatformVersion, ) -> Result<(), Error> { match platform_version .drive_abci .methods - .identity_credit_withdrawal - .update_broadcasted_withdrawal_transaction_statuses + .withdrawals + .update_broadcasted_withdrawal_statuses { - 0 => self.update_broadcasted_withdrawal_transaction_statuses_v0( - block_execution_context, + 0 => self.update_broadcasted_withdrawal_statuses_v0( + block_info, transaction, platform_version, ), version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { - method: "update_broadcasted_withdrawal_transaction_statuses".to_string(), + method: "update_broadcasted_withdrawal_statuses".to_string(), known_versions: vec![0], received: version, })), diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/update_broadcasted_withdrawal_statuses/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/update_broadcasted_withdrawal_statuses/v0/mod.rs new file mode 100644 index 00000000000..d0927690218 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/update_broadcasted_withdrawal_statuses/v0/mod.rs @@ -0,0 +1,327 @@ +use dashcore_rpc::json::AssetUnlockStatus; +use dpp::block::block_info::BlockInfo; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contracts::withdrawals_contract::WithdrawalStatus; +use dpp::document::document_methods::DocumentMethodsV0; +use dpp::document::{DocumentV0Getters, DocumentV0Setters}; +use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; +use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; +use dpp::version::PlatformVersion; +use itertools::Itertools; +use std::collections::HashSet; + +use drive::drive::batch::DriveOperation; +use drive::drive::config::DEFAULT_QUERY_LIMIT; +use drive::drive::identity::withdrawals::WithdrawalTransactionIndex; +use drive::grovedb::Transaction; + +use crate::{ + error::{execution::ExecutionError, Error}, + platform_types::platform::Platform, + rpc::core::CoreRPCLike, +}; + +const NUMBER_OF_BLOCKS_BEFORE_EXPIRED: u32 = 48; + +impl Platform +where + C: CoreRPCLike, +{ + /// Update statuses for broadcasted withdrawals + pub(super) fn update_broadcasted_withdrawal_statuses_v0( + &self, + block_info: &BlockInfo, + transaction: &Transaction, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + let broadcasted_withdrawal_documents = + self.drive.fetch_oldest_withdrawal_documents_by_status( + WithdrawalStatus::BROADCASTED.into(), + DEFAULT_QUERY_LIMIT, + transaction.into(), + platform_version, + )?; + + if broadcasted_withdrawal_documents.is_empty() { + return Ok(()); + } + + // Collecting unique withdrawal indices + let broadcasted_withdrawal_indices = broadcasted_withdrawal_documents + .iter() + .map(|document| { + document + .properties() + .get_optional_u64(withdrawal::properties::TRANSACTION_INDEX)? + .ok_or(Error::Execution(ExecutionError::CorruptedDriveResponse( + "Can't get transaction index from withdrawal document".to_string(), + ))) + }) + .collect::, Error>>()? + .into_iter() + .collect_vec(); + + let withdrawal_transaction_statuses = self.fetch_transactions_block_inclusion_status( + block_info.core_height, + &broadcasted_withdrawal_indices, + platform_version, + )?; + + let mut drive_operations: Vec = vec![]; + + // Collecting only documents that have been updated + let mut documents_to_update = Vec::new(); + + for mut document in broadcasted_withdrawal_documents { + let withdrawal_index = document + .properties() + .get_optional_u64(withdrawal::properties::TRANSACTION_INDEX)? + .ok_or(Error::Execution(ExecutionError::CorruptedDriveResponse( + "Can't get transaction index from withdrawal document".to_string(), + )))?; + + let transaction_sign_height = document + .properties() + .get_optional_u64(withdrawal::properties::TRANSACTION_SIGN_HEIGHT)? + .ok_or(Error::Execution(ExecutionError::CorruptedDriveResponse( + "Can't get transaction sign height from withdrawal document".to_string(), + )))? as u32; + + let withdrawal_transaction_status = withdrawal_transaction_statuses + .get(&withdrawal_index) + .unwrap_or_else(|| { + tracing::warn!( + "Withdrawal transaction with index {} is not found in Core", + withdrawal_index + ); + + &AssetUnlockStatus::Unknown + }); + + let block_height_difference = block_info.core_height - transaction_sign_height; + + let status = if withdrawal_transaction_status == &AssetUnlockStatus::Chainlocked { + tracing::debug!( + transaction_sign_height, + "Withdrawal with transaction index {} is marked as complete", + withdrawal_index + ); + + WithdrawalStatus::COMPLETE + } else if block_height_difference > NUMBER_OF_BLOCKS_BEFORE_EXPIRED { + tracing::debug!( + transaction_sign_height, + "Withdrawal with transaction index {} is marked as expired", + withdrawal_index + ); + + WithdrawalStatus::EXPIRED + } else { + continue; + }; + + document.set_u8(withdrawal::properties::STATUS, status.into()); + + document.set_u64(withdrawal::properties::UPDATED_AT, block_info.time_ms); + + document.increment_revision().map_err(Error::Protocol)?; + + documents_to_update.push(document); + } + + if documents_to_update.is_empty() { + return Ok(()); + } + + let cache = self.drive.cache.read().unwrap(); + + self.drive.add_update_multiple_documents_operations( + &documents_to_update, + &cache.system_data_contracts.withdrawals, + cache + .system_data_contracts + .withdrawals + .document_type_for_name(withdrawal::NAME) + .map_err(|_| { + Error::Execution(ExecutionError::CorruptedCodeExecution( + "Can't fetch withdrawal data contract", + )) + })?, + &mut drive_operations, + &platform_version.drive, + )?; + + self.drive.apply_drive_operations( + drive_operations, + true, + block_info, + transaction.into(), + platform_version, + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::rpc::core::MockCoreRPCLike; + use crate::test::helpers::setup::TestPlatformBuilder; + use dashcore_rpc::json::{AssetUnlockStatus, AssetUnlockStatusResult}; + use dpp::data_contract::accessors::v0::DataContractV0Getters; + use dpp::document::DocumentV0Getters; + use dpp::identity::core_script::CoreScript; + use dpp::platform_value::platform_value; + use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; + use dpp::version::PlatformVersion; + use dpp::withdrawal::Pooling; + use dpp::{ + data_contracts::withdrawals_contract, tests::fixtures::get_withdrawal_document_fixture, + }; + use dpp::{ + prelude::Identifier, + system_data_contracts::{load_system_data_contract, SystemDataContract}, + }; + use drive::tests::helpers::setup::setup_document; + use drive::tests::helpers::setup::setup_system_data_contract; + + #[test] + fn test_statuses_are_updated() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let mut mock_rpc_client = MockCoreRPCLike::new(); + + mock_rpc_client + .expect_get_asset_unlock_statuses() + .returning(move |indices: &[u64], _core_chain_locked_height| { + Ok(indices + .iter() + .map(|index| { + let status = if index == &1 { + AssetUnlockStatus::Chainlocked + } else { + AssetUnlockStatus::Unknown + }; + + AssetUnlockStatusResult { + index: *index, + status, + } + }) + .collect()) + }); + + platform.core_rpc = mock_rpc_client; + + let transaction = platform.drive.grove.start_transaction(); + + let block_info = BlockInfo { + time_ms: 0, + height: 1, + core_height: 96, + epoch: Default::default(), + }; + + let data_contract = + load_system_data_contract(SystemDataContract::Withdrawals, platform_version) + .expect("to load system data contract"); + + setup_system_data_contract(&platform.drive, &data_contract, Some(&transaction)); + + let owner_id = Identifier::new([1u8; 32]); + + let document_1 = get_withdrawal_document_fixture( + &data_contract, + owner_id, + platform_value!({ + "amount": 1000u64, + "coreFeePerByte": 1u32, + "pooling": Pooling::Never, + "outputScript": CoreScript::from_bytes((0..23).collect::>()), + "status": withdrawals_contract::WithdrawalStatus::BROADCASTED as u8, + "transactionIndex": 1u64, + "transactionSignHeight": 1, + }), + None, + platform_version.protocol_version, + ) + .expect("expected withdrawal document"); + + let document_type = data_contract + .document_type_for_name(withdrawal::NAME) + .expect("expected to get document type"); + + setup_document( + &platform.drive, + &document_1, + &data_contract, + document_type, + Some(&transaction), + ); + + let document_2 = get_withdrawal_document_fixture( + &data_contract, + owner_id, + platform_value!({ + "amount": 1000u64, + "coreFeePerByte": 1u32, + "pooling": Pooling::Never as u8, + "outputScript": CoreScript::from_bytes((0..23).collect::>()), + "status": withdrawals_contract::WithdrawalStatus::BROADCASTED as u8, + "transactionIndex": 2u64, + "transactionSignHeight": 1, + }), + None, + platform_version.protocol_version, + ) + .expect("expected withdrawal document"); + + setup_document( + &platform.drive, + &document_2, + &data_contract, + document_type, + Some(&transaction), + ); + + platform + .update_broadcasted_withdrawal_statuses_v0(&block_info, &transaction, platform_version) + .expect("to update withdrawal statuses"); + + let documents = platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + WithdrawalStatus::EXPIRED.into(), + DEFAULT_QUERY_LIMIT, + Some(&transaction), + platform_version, + ) + .expect("to fetch documents by status"); + + assert_eq!(documents.len(), 1); + assert_eq!( + documents.first().unwrap().id().to_vec(), + document_2.id().to_vec() + ); + + let documents = platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + WithdrawalStatus::COMPLETE.into(), + DEFAULT_QUERY_LIMIT, + Some(&transaction), + platform_version, + ) + .expect("to fetch documents by status"); + + assert_eq!(documents.len(), 1); + assert_eq!( + documents.first().unwrap().id().to_vec(), + document_1.id().to_vec() + ); + } +} diff --git a/packages/rs-drive-abci/src/execution/types/block_execution_context/mod.rs b/packages/rs-drive-abci/src/execution/types/block_execution_context/mod.rs index 89e78136e97..a0054fff7a9 100644 --- a/packages/rs-drive-abci/src/execution/types/block_execution_context/mod.rs +++ b/packages/rs-drive-abci/src/execution/types/block_execution_context/mod.rs @@ -9,9 +9,8 @@ use crate::execution::types::block_execution_context::v0::{ use crate::execution::types::block_state_info::BlockStateInfo; use crate::platform_types::epoch_info::EpochInfo; use crate::platform_types::platform_state::PlatformState; +use crate::platform_types::withdrawal::unsigned_withdrawal_txs::v0::UnsignedWithdrawalTxs; use derive_more::From; -use dpp::dashcore::Txid; -use std::collections::BTreeMap; use tenderdash_abci::proto::abci::ResponsePrepareProposal; /// The versioned block execution context @@ -49,9 +48,9 @@ impl BlockExecutionContextV0Getters for BlockExecutionContext { } } - fn withdrawal_transactions(&self) -> &BTreeMap> { + fn unsigned_withdrawal_transactions(&self) -> &UnsignedWithdrawalTxs { match self { - BlockExecutionContext::V0(v0) => &v0.withdrawal_transactions, + BlockExecutionContext::V0(v0) => &v0.unsigned_withdrawal_transactions, } } @@ -87,9 +86,9 @@ impl BlockExecutionContextV0Setters for BlockExecutionContext { } } - fn set_withdrawal_transactions(&mut self, transactions: BTreeMap>) { + fn set_unsigned_withdrawal_transactions(&mut self, transactions: UnsignedWithdrawalTxs) { match self { - BlockExecutionContext::V0(v0) => v0.withdrawal_transactions = transactions, + BlockExecutionContext::V0(v0) => v0.unsigned_withdrawal_transactions = transactions, } } @@ -119,12 +118,6 @@ impl BlockExecutionContextV0MutableGetters for BlockExecutionContext { } } - fn withdrawal_transactions_mut(&mut self) -> &mut BTreeMap> { - match self { - BlockExecutionContext::V0(v0) => v0.withdrawal_transactions_mut(), - } - } - fn block_platform_state_mut(&mut self) -> &mut PlatformState { match self { BlockExecutionContext::V0(v0) => v0.block_platform_state_mut(), @@ -136,6 +129,12 @@ impl BlockExecutionContextV0MutableGetters for BlockExecutionContext { BlockExecutionContext::V0(v0) => v0.proposer_results_mut(), } } + + fn unsigned_withdrawal_transactions_mut(&mut self) -> &mut UnsignedWithdrawalTxs { + match self { + BlockExecutionContext::V0(v0) => v0.unsigned_withdrawal_transactions_mut(), + } + } } impl BlockExecutionContextV0OwnedGetters for BlockExecutionContext { @@ -153,13 +152,6 @@ impl BlockExecutionContextV0OwnedGetters for BlockExecutionContext { } } - /// Consumes the object and returns the owned `withdrawal_transactions`. - fn withdrawal_transactions_owned(self) -> BTreeMap> { - match self { - BlockExecutionContext::V0(v0) => v0.withdrawal_transactions, - } - } - /// Consumes the object and returns the owned `PlatformState`. fn block_platform_state_owned(self) -> PlatformState { match self { diff --git a/packages/rs-drive-abci/src/execution/types/block_execution_context/v0/mod.rs b/packages/rs-drive-abci/src/execution/types/block_execution_context/v0/mod.rs index 587407bb47b..0ae169080c8 100644 --- a/packages/rs-drive-abci/src/execution/types/block_execution_context/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/types/block_execution_context/v0/mod.rs @@ -31,6 +31,7 @@ use crate::execution::types::block_state_info::BlockStateInfo; use crate::platform_types::epoch_info::EpochInfo; use crate::platform_types::platform_state::PlatformState; +use crate::platform_types::withdrawal::unsigned_withdrawal_txs::v0::UnsignedWithdrawalTxs; use dashcore_rpc::dashcore::Txid; use std::collections::BTreeMap; use tenderdash_abci::proto::abci::ResponsePrepareProposal; @@ -44,8 +45,8 @@ pub struct BlockExecutionContextV0 { pub epoch_info: EpochInfo, /// Total hpmn count pub hpmn_count: u32, - /// Current withdrawal transactions hash -> Transaction - pub withdrawal_transactions: BTreeMap>, + /// Unsigned withdrawal transactions to be available for extend and verify vote handlers + pub unsigned_withdrawal_transactions: UnsignedWithdrawalTxs, /// Block state pub block_platform_state: PlatformState, /// The response prepare proposal if proposed by us @@ -63,7 +64,7 @@ pub trait BlockExecutionContextV0Getters { fn hpmn_count(&self) -> u32; /// Returns a reference of the withdrawal_transactions field. - fn withdrawal_transactions(&self) -> &BTreeMap>; + fn unsigned_withdrawal_transactions(&self) -> &UnsignedWithdrawalTxs; /// Returns a reference of the block_platform_state field. fn block_platform_state(&self) -> &PlatformState; @@ -84,7 +85,7 @@ pub trait BlockExecutionContextV0Setters { fn set_hpmn_count(&mut self, count: u32); /// Sets the withdrawal_transactions field. - fn set_withdrawal_transactions(&mut self, transactions: BTreeMap>); + fn set_unsigned_withdrawal_transactions(&mut self, transactions: UnsignedWithdrawalTxs); /// Sets the block_platform_state field. fn set_block_platform_state(&mut self, state: PlatformState); @@ -101,14 +102,14 @@ pub trait BlockExecutionContextV0MutableGetters { /// Returns a mutable reference to the epoch_info field. fn epoch_info_mut(&mut self) -> &mut EpochInfo; - /// Returns a mutable reference to the withdrawal_transactions field. - fn withdrawal_transactions_mut(&mut self) -> &mut BTreeMap>; - /// Returns a mutable reference to the block_platform_state field. fn block_platform_state_mut(&mut self) -> &mut PlatformState; /// Returns a mutable reference to the proposer_results field. fn proposer_results_mut(&mut self) -> Option<&mut ResponsePrepareProposal>; + + /// Returns a mut reference of the withdrawal_transactions field. + fn unsigned_withdrawal_transactions_mut(&mut self) -> &mut UnsignedWithdrawalTxs; } /// A trait defining methods for interacting with a BlockExecutionContextV0. @@ -119,9 +120,6 @@ pub trait BlockExecutionContextV0OwnedGetters { /// Consumes the BlockExecutionContextV0 and returns the epoch_info field. fn epoch_info_owned(self) -> EpochInfo; - /// Consumes the BlockExecutionContextV0 and returns the withdrawal_transactions field. - fn withdrawal_transactions_owned(self) -> BTreeMap>; - /// Consumes the BlockExecutionContextV0 and returns the block_platform_state field. fn block_platform_state_owned(self) -> PlatformState; @@ -145,9 +143,9 @@ impl BlockExecutionContextV0Getters for BlockExecutionContextV0 { self.hpmn_count } - /// Returns a reference to the withdrawal_transactions field. - fn withdrawal_transactions(&self) -> &BTreeMap> { - &self.withdrawal_transactions + /// Returns a reference to the unsigned withdrawal transactions + fn unsigned_withdrawal_transactions(&self) -> &UnsignedWithdrawalTxs { + &self.unsigned_withdrawal_transactions } /// Returns a reference to the block_platform_state field. @@ -175,8 +173,8 @@ impl BlockExecutionContextV0Setters for BlockExecutionContextV0 { self.hpmn_count = count; } /// Sets the withdrawal_transactions field. - fn set_withdrawal_transactions(&mut self, transactions: BTreeMap>) { - self.withdrawal_transactions = transactions; + fn set_unsigned_withdrawal_transactions(&mut self, transactions: UnsignedWithdrawalTxs) { + self.unsigned_withdrawal_transactions = transactions; } /// Sets the block_platform_state field. fn set_block_platform_state(&mut self, state: PlatformState) { @@ -199,11 +197,6 @@ impl BlockExecutionContextV0MutableGetters for BlockExecutionContextV0 { &mut self.epoch_info } - /// Returns a mutable reference to the withdrawal_transactions field. - fn withdrawal_transactions_mut(&mut self) -> &mut BTreeMap> { - &mut self.withdrawal_transactions - } - /// Returns a mutable reference to the block_platform_state field. fn block_platform_state_mut(&mut self) -> &mut PlatformState { &mut self.block_platform_state @@ -213,6 +206,10 @@ impl BlockExecutionContextV0MutableGetters for BlockExecutionContextV0 { fn proposer_results_mut(&mut self) -> Option<&mut ResponsePrepareProposal> { self.proposer_results.as_mut() } + + fn unsigned_withdrawal_transactions_mut(&mut self) -> &mut UnsignedWithdrawalTxs { + &mut self.unsigned_withdrawal_transactions + } } impl BlockExecutionContextV0OwnedGetters for BlockExecutionContextV0 { @@ -226,11 +223,6 @@ impl BlockExecutionContextV0OwnedGetters for BlockExecutionContextV0 { self.epoch_info } - /// Consumes the object and returns the owned `BTreeMap` of withdrawal transactions. - fn withdrawal_transactions_owned(self) -> BTreeMap> { - self.withdrawal_transactions - } - /// Consumes the object and returns the owned `PlatformState`. fn block_platform_state_owned(self) -> PlatformState { self.block_platform_state diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/data_triggers/triggers/withdrawals/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/data_triggers/triggers/withdrawals/v0/mod.rs index 3e6e7937a7c..3f70ebd708b 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/data_triggers/triggers/withdrawals/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/documents_batch/data_triggers/triggers/withdrawals/v0/mod.rs @@ -104,13 +104,11 @@ pub fn delete_withdrawal_data_trigger_v0( .get_integer("status") .map_err(ProtocolError::ValueError)?; - if status != withdrawals_contract::WithdrawalStatus::COMPLETE as u8 - || status != withdrawals_contract::WithdrawalStatus::EXPIRED as u8 - { + if status != withdrawals_contract::WithdrawalStatus::COMPLETE as u8 { let err = DataTriggerConditionError::new( data_contract.id(), dt_delete.base().id(), - "withdrawal deletion is allowed only for COMPLETE and EXPIRED statuses".to_string(), + "withdrawal deletion is allowed only for COMPLETE statuses".to_string(), ); result.add_error(err); @@ -221,7 +219,6 @@ mod tests { "status": withdrawals_contract::WithdrawalStatus::BROADCASTED as u8, "transactionIndex": 1u64, "transactionSignHeight": 93u64, - "transactionId": Bytes32::new([1;32]), }), None, platform_version.protocol_version, @@ -275,7 +272,6 @@ mod tests { "status": withdrawals_contract::WithdrawalStatus::BROADCASTED as u8, "transactionIndex": 1u64, "transactionSignHeight": 93u64, - "transactionId": Bytes32::new([1;32]), }), None, platform_version.protocol_version, @@ -332,7 +328,7 @@ mod tests { assert_eq!( error.to_string(), - "withdrawal deletion is allowed only for COMPLETE and EXPIRED statuses" + "withdrawal deletion is allowed only for COMPLETE statuses" ); } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create/state/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create/state/v0/mod.rs index 04e89cb3675..97f654619a9 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create/state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create/state/v0/mod.rs @@ -1,6 +1,7 @@ use crate::error::Error; use crate::platform_types::platform::PlatformRef; use crate::rpc::core::CoreRPCLike; +use dpp::balances::credits::CREDITS_PER_DUFF; use dpp::consensus::signature::{BasicECDSAError, SignatureError}; use dpp::consensus::state::identity::invalid_asset_lock_proof_value::InvalidAssetLockProofValueError; @@ -162,7 +163,10 @@ impl IdentityCreateStateTransitionStateValidationV0 for IdentityCreateTransition )); } - match IdentityCreateTransitionAction::try_from_borrowed(self, tx_out.value * 1000) { + match IdentityCreateTransitionAction::try_from_borrowed( + self, + tx_out.value * CREDITS_PER_DUFF, + ) { Ok(action) => { validation_result.set_data(action.into()); } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/structure/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/structure/v0/mod.rs index d50df91f7c7..f1680dbcd0a 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/structure/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/structure/v0/mod.rs @@ -8,13 +8,14 @@ use dpp::consensus::ConsensusError; use crate::error::Error; use dpp::state_transition::identity_credit_withdrawal_transition::accessors::IdentityCreditWithdrawalTransitionAccessorsV0; +use dpp::state_transition::identity_credit_withdrawal_transition::v0::{ + MIN_CORE_FEE_PER_BYTE, MIN_WITHDRAWAL_AMOUNT, +}; use dpp::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition; use dpp::util::is_fibonacci_number::is_fibonacci_number; use dpp::validation::SimpleConsensusValidationResult; use dpp::withdrawal::Pooling; -const MIN_WITHDRAWAL_AMOUNT: u64 = 1000; - pub(in crate::execution::validation::state_transition::state_transitions::identity_credit_withdrawal) trait IdentityCreditWithdrawalStateTransitionStructureValidationV0 { fn validate_base_structure_v0(&self) -> Result; } @@ -47,10 +48,20 @@ impl IdentityCreditWithdrawalStateTransitionStructureValidationV0 } // validate core_fee is in fibonacci sequence + if !is_fibonacci_number(self.core_fee_per_byte() as u64) { + result.add_error(InvalidIdentityCreditWithdrawalTransitionCoreFeeError::new( + self.core_fee_per_byte(), + MIN_CORE_FEE_PER_BYTE, + )); + + return Ok(result); + } - if !is_fibonacci_number(self.core_fee_per_byte()) { + // validate core_fee is in not less than minimal relay fee + if self.core_fee_per_byte() < MIN_CORE_FEE_PER_BYTE { result.add_error(InvalidIdentityCreditWithdrawalTransitionCoreFeeError::new( self.core_fee_per_byte(), + MIN_CORE_FEE_PER_BYTE, )); return Ok(result); diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up/state/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up/state/v0/mod.rs index a78c0724656..6532f3ba87b 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up/state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up/state/v0/mod.rs @@ -1,6 +1,7 @@ use crate::error::Error; use crate::platform_types::platform::PlatformRef; use crate::rpc::core::CoreRPCLike; +use dpp::balances::credits::CREDITS_PER_DUFF; use dpp::consensus::signature::{BasicECDSAError, SignatureError}; use dpp::consensus::state::identity::invalid_asset_lock_proof_value::InvalidAssetLockProofValueError; @@ -125,7 +126,10 @@ impl IdentityTopUpStateTransitionStateValidationV0 for IdentityTopUpTransition { )); } - match IdentityTopUpTransitionAction::try_from_borrowed(self, &tx_out.value * 1000) { + match IdentityTopUpTransitionAction::try_from_borrowed( + self, + tx_out.value * CREDITS_PER_DUFF, + ) { Ok(action) => { validation_result.set_data(action.into()); } diff --git a/packages/rs-drive-abci/src/mimic/mod.rs b/packages/rs-drive-abci/src/mimic/mod.rs index a3942c779fc..05780e1dbf9 100644 --- a/packages/rs-drive-abci/src/mimic/mod.rs +++ b/packages/rs-drive-abci/src/mimic/mod.rs @@ -2,36 +2,36 @@ use crate::abci::server::AbciApplication; use crate::abci::AbciError; use crate::error::execution::ExecutionError; use crate::error::Error; -use bytes::Buf; +use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0Getters; +use crate::execution::types::block_state_info::v0::BlockStateInfoV0Getters; +use crate::mimic::test_quorum::TestQuorumInfo; +use crate::platform_types::withdrawal::unsigned_withdrawal_txs::v0::{ + make_extend_vote_request_id, UnsignedWithdrawalTxs, +}; use crate::rpc::core::CoreRPCLike; -use dashcore_rpc::dashcore::blockdata::transaction::special_transaction::asset_unlock::qualified_asset_unlock::AssetUnlockPayload; -use dashcore_rpc::dashcore::blockdata::transaction::special_transaction::asset_unlock::request_info::AssetUnlockRequestInfo; -use dashcore_rpc::dashcore::blockdata::transaction::special_transaction::asset_unlock::unqualified_asset_unlock::AssetUnlockBaseTransactionInfo; -use dashcore_rpc::dashcore::blockdata::transaction::special_transaction::TransactionPayload::AssetUnlockPayloadType; -use dashcore_rpc::dashcore::bls_sig_utils::BLSSignature; -use dashcore_rpc::dashcore::consensus::Decodable; -use dashcore_rpc::dashcore; -use rand::rngs::StdRng; -use rand::{Rng, SeedableRng}; -use dpp::dashcore::hashes::Hash; use dpp::block::block_info::BlockInfo; +use dpp::dashcore::hashes::Hash; use dpp::serialization::PlatformSerializable; use dpp::state_transition::StateTransition; use dpp::util::deserializer::ProtocolVersion; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; use tenderdash_abci::proto::abci::response_verify_vote_extension::VerifyStatus; -use tenderdash_abci::proto::abci::{CommitInfo, ExecTxResult, RequestExtendVote, RequestFinalizeBlock, RequestPrepareProposal, RequestProcessProposal, RequestVerifyVoteExtension, ResponsePrepareProposal, ValidatorSetUpdate}; +use tenderdash_abci::proto::abci::tx_record::TxAction; +use tenderdash_abci::proto::abci::{ + CommitInfo, ExecTxResult, RequestExtendVote, RequestFinalizeBlock, RequestPrepareProposal, + RequestProcessProposal, RequestVerifyVoteExtension, ResponsePrepareProposal, + ValidatorSetUpdate, +}; use tenderdash_abci::proto::google::protobuf::Timestamp; use tenderdash_abci::proto::serializers::timestamp::ToMilis; use tenderdash_abci::proto::types::{ - Block, BlockId, Data, EvidenceList, Header, PartSetHeader, VoteExtension, VoteExtensionType, StateId, CanonicalVote, SignedMsgType, + Block, BlockId, CanonicalVote, Data, EvidenceList, Header, PartSetHeader, SignedMsgType, + StateId, VoteExtension, VoteExtensionType, }; use tenderdash_abci::signatures::Hashable; -use tenderdash_abci::{signatures::Signable, proto::version::Consensus, Application}; -use tenderdash_abci::proto::abci::tx_record::TxAction; -use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0Getters; -use crate::execution::types::block_state_info::v0::BlockStateInfoV0Getters; -use crate::mimic::test_quorum::TestQuorumInfo; +use tenderdash_abci::{proto::version::Consensus, signatures::Signable, Application}; /// Test quorum for mimic block execution pub mod test_quorum; @@ -44,7 +44,7 @@ pub struct MimicExecuteBlockOutcome { /// state transaction results pub state_transaction_results: Vec<(StateTransition, ExecTxResult)>, /// withdrawal transactions - pub withdrawal_transactions: Vec, + pub withdrawal_transactions: UnsignedWithdrawalTxs, /// The next validators pub validator_set_update: Option, /// The next validators hash @@ -403,45 +403,23 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { )))?.v0()?; let extensions = block_execution_context - .withdrawal_transactions - .keys() - .map(|tx_id| { + .unsigned_withdrawal_transactions + .iter() + .map(|tx| { + let sign_request_id = Some(make_extend_vote_request_id(tx)); + VoteExtension { - r#type: VoteExtensionType::ThresholdRecover as i32, - extension: tx_id.to_byte_array().to_vec(), - signature: vec![], //todo: signature - sign_request_id: None, + r#type: VoteExtensionType::ThresholdRecoverRaw as i32, + extension: tx.txid().to_byte_array().to_vec(), + sign_request_id, + signature: vec![0; 96], //todo: signature } }) .collect(); - //todo: tidy up and fix - let withdrawals = block_execution_context - .withdrawal_transactions - .values() - .map(|transaction| { - let AssetUnlockBaseTransactionInfo { - version, - lock_time, - output, - base_payload, - } = Decodable::consensus_decode(&mut transaction.reader()).expect("a"); - dashcore::Transaction { - version, - lock_time, - input: vec![], - output, - special_transaction_payload: Some(AssetUnlockPayloadType(AssetUnlockPayload { - base: base_payload, - request_info: AssetUnlockRequestInfo { - request_height: core_height, - quorum_hash: current_quorum.quorum_hash, - }, - quorum_sig: BLSSignature::from([0; 96]), - })), - } - }) - .collect(); + let withdrawal_transactions = block_execution_context + .unsigned_withdrawal_transactions + .clone(); drop(guarded_block_execution_context); @@ -583,7 +561,7 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { Ok(MimicExecuteBlockOutcome { state_transaction_results, app_version: APP_VERSION, - withdrawal_transactions: withdrawals, + withdrawal_transactions, validator_set_update, next_validator_set_hash, root_app_hash: app_hash diff --git a/packages/rs-drive-abci/src/platform_types/state_transitions_processing_result/mod.rs b/packages/rs-drive-abci/src/platform_types/state_transitions_processing_result/mod.rs index d565277a724..0e07ae1d8b0 100644 --- a/packages/rs-drive-abci/src/platform_types/state_transitions_processing_result/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/state_transitions_processing_result/mod.rs @@ -27,7 +27,8 @@ pub enum StateTransitionExecutionResult { #[derive(Debug, Default, Clone)] pub struct StateTransitionsProcessingResult { execution_results: Vec, - invalid_count: usize, + invalid_paid_count: usize, + invalid_unpaid_count: usize, valid_count: usize, failed_count: usize, fees: FeeResult, @@ -40,9 +41,11 @@ impl StateTransitionsProcessingResult { StateTransitionExecutionResult::DriveAbciError(_) => { self.failed_count += 1; } - StateTransitionExecutionResult::PaidConsensusError(_) - | StateTransitionExecutionResult::UnpaidConsensusError(_) => { - self.invalid_count += 1; + StateTransitionExecutionResult::PaidConsensusError(_) => { + self.invalid_paid_count += 1; + } + StateTransitionExecutionResult::UnpaidConsensusError(_) => { + self.invalid_unpaid_count += 1; } StateTransitionExecutionResult::SuccessfulExecution(_, actual_fees) => { self.valid_count += 1; @@ -56,9 +59,14 @@ impl StateTransitionsProcessingResult { Ok(()) } - /// Returns the number of invalid state transitions - pub fn invalid_count(&self) -> usize { - self.invalid_count + /// Returns the number of paid invalid state transitions + pub fn invalid_paid_count(&self) -> usize { + self.invalid_paid_count + } + + /// Returns the number of unpaid invalid state transitions + pub fn invalid_unpaid_count(&self) -> usize { + self.invalid_unpaid_count } /// Returns the number of valid state transitions diff --git a/packages/rs-drive-abci/src/platform_types/withdrawal/mod.rs b/packages/rs-drive-abci/src/platform_types/withdrawal/mod.rs index 39627a36d3d..ec9a669431d 100644 --- a/packages/rs-drive-abci/src/platform_types/withdrawal/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/withdrawal/mod.rs @@ -1 +1,2 @@ -pub(crate) mod withdrawal_txs; +/// Collection of unsigned withdrawal transactions +pub mod unsigned_withdrawal_txs; diff --git a/packages/rs-drive-abci/src/platform_types/withdrawal/withdrawal_txs/mod.rs b/packages/rs-drive-abci/src/platform_types/withdrawal/unsigned_withdrawal_txs/mod.rs similarity index 100% rename from packages/rs-drive-abci/src/platform_types/withdrawal/withdrawal_txs/mod.rs rename to packages/rs-drive-abci/src/platform_types/withdrawal/unsigned_withdrawal_txs/mod.rs diff --git a/packages/rs-drive-abci/src/platform_types/withdrawal/unsigned_withdrawal_txs/v0/mod.rs b/packages/rs-drive-abci/src/platform_types/withdrawal/unsigned_withdrawal_txs/v0/mod.rs new file mode 100644 index 00000000000..626a9ed7106 --- /dev/null +++ b/packages/rs-drive-abci/src/platform_types/withdrawal/unsigned_withdrawal_txs/v0/mod.rs @@ -0,0 +1,142 @@ +//! Withdrawal transactions definitions and processing +use dpp::dashcore::consensus::Encodable; +use dpp::dashcore::hashes::Hash; +use dpp::dashcore::transaction::special_transaction::TransactionPayload::AssetUnlockPayloadType; +use dpp::dashcore::{Transaction, VarInt}; +use std::fmt::Display; +use tenderdash_abci::proto::types::VoteExtension; +use tenderdash_abci::proto::{abci::ExtendVoteExtension, types::VoteExtensionType}; + +/// Collection of withdrawal transactions processed at some height/round +#[derive(Debug, Default, Clone)] +pub struct UnsignedWithdrawalTxs(Vec); + +impl UnsignedWithdrawalTxs { + /// Returns iterator over borrowed withdrawal transactions + pub fn iter(&self) -> std::slice::Iter { + self.0.iter() + } + /// Returns a number of withdrawal transactions + pub fn len(&self) -> usize { + self.0.len() + } + /// Returns a reference to the first withdrawal transaction + pub fn first(&self) -> Option<&Transaction> { + self.0.first() + } + /// Returns a reference to the last withdrawal transaction + pub fn last(&self) -> Option<&Transaction> { + self.0.last() + } + /// Returns true if there are no withdrawal transactions + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + /// Creates a new collection of withdrawal transactions for Vec + pub fn from_vec(transactions: Vec) -> Self { + Self(transactions) + } + /// Drains all withdrawal transactions from the collection + + pub fn drain(&mut self) -> UnsignedWithdrawalTxs { + Self(self.0.drain(..).collect()) + } + + /// Appends another collection of unsigned withdrawal transactions + pub fn append(&mut self, mut other: Self) { + self.0.append(&mut other.0); + } + + /// Verifies that the collection of unsigned withdrawal transactions matches the given vote extensions + /// created based on these transactions + pub fn are_matching_with_vote_extensions(&self, other: &Vec) -> bool { + if self.0.len() != other.len() { + return false; + }; + + !self.0.iter().zip(other.iter()).any(|(tx, vote_extension)| { + let extend_vote_extension = tx_to_extend_vote_extension(tx); + + vote_extension.r#type != extend_vote_extension.r#type + || vote_extension.sign_request_id != extend_vote_extension.sign_request_id + || vote_extension.extension != extend_vote_extension.extension + }) + } +} + +impl IntoIterator for UnsignedWithdrawalTxs { + type Item = Transaction; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Display for UnsignedWithdrawalTxs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("txs:["))?; + for tx in &self.0 { + f.write_fmt(format_args!("{}", tx.txid().to_hex()))?; + } + f.write_str("]\n")?; + Ok(()) + } +} + +impl PartialEq> for UnsignedWithdrawalTxs { + fn eq(&self, other: &Vec) -> bool { + if self.0.len() != other.len() { + return false; + }; + + !self + .0 + .iter() + .zip(other.iter()) + .any(|(tx, other_vote_extension)| { + let self_vote_extension = tx_to_extend_vote_extension(tx); + + &self_vote_extension != other_vote_extension + }) + } +} + +impl From<&UnsignedWithdrawalTxs> for Vec { + fn from(value: &UnsignedWithdrawalTxs) -> Self { + value + .0 + .iter() + .map(tx_to_extend_vote_extension) + .collect::>() + } +} + +fn tx_to_extend_vote_extension(tx: &Transaction) -> ExtendVoteExtension { + let request_id = make_extend_vote_request_id(tx); + let extension = tx.txid().as_byte_array().to_vec(); + + ExtendVoteExtension { + r#type: VoteExtensionType::ThresholdRecoverRaw as i32, + extension, + sign_request_id: Some(request_id), + } +} + +pub(crate) fn make_extend_vote_request_id(asset_unlock_tx: &Transaction) -> Vec { + let Some(AssetUnlockPayloadType(ref payload)) = asset_unlock_tx.special_transaction_payload + else { + panic!("expected to get AssetUnlockPayloadType"); + }; + + let mut request_id = vec![]; + const ASSET_UNLOCK_REQUEST_ID_PREFIX: &str = "plwdtx"; + let prefix_len = VarInt(ASSET_UNLOCK_REQUEST_ID_PREFIX.len() as u64); + let index = payload.base.index.to_le_bytes(); + + prefix_len.consensus_encode(&mut request_id).unwrap(); + request_id.extend_from_slice(ASSET_UNLOCK_REQUEST_ID_PREFIX.as_bytes()); + request_id.extend_from_slice(&index); + + request_id +} diff --git a/packages/rs-drive-abci/src/platform_types/withdrawal/withdrawal_txs/v0/mod.rs b/packages/rs-drive-abci/src/platform_types/withdrawal/withdrawal_txs/v0/mod.rs deleted file mode 100644 index a1e129a4b5f..00000000000 --- a/packages/rs-drive-abci/src/platform_types/withdrawal/withdrawal_txs/v0/mod.rs +++ /dev/null @@ -1,295 +0,0 @@ -//! Withdrawal transactions definitions and processing -use crate::abci::AbciError; -use dashcore_rpc::dashcore_rpc_json::QuorumType; -use dpp::block::block_info::BlockInfo; -use dpp::bls_signatures; -use dpp::fee::fee_result::FeeResult; -use dpp::validation::SimpleValidationResult; -use dpp::version::PlatformVersion; -use drive::{ - drive::{batch::DriveOperation, Drive}, - query::TransactionArg, -}; -use std::fmt::Display; -use tenderdash_abci::proto::{ - abci::ExtendVoteExtension, - types::{VoteExtension, VoteExtensionType}, -}; -use tenderdash_abci::signatures::Signable; - -const MAX_WITHDRAWAL_TXS: u16 = 16; - -/// Collection of withdrawal transactions processed at some height/round -#[derive(Debug)] -pub struct WithdrawalTxs<'a> { - inner: Vec, - drive_operations: Vec>, -} - -impl<'a> WithdrawalTxs<'a> { - /// Load pending withdrawal transactions from database - pub fn load(transaction: TransactionArg, drive: &Drive) -> Result { - let mut drive_operations = Vec::::new(); - - let inner = drive - .dequeue_withdrawal_transactions(MAX_WITHDRAWAL_TXS, transaction, &mut drive_operations) - .map_err(|e| AbciError::WithdrawalTransactionsDBLoadError(e.to_string()))? - .into_iter() - .map(|(_k, v)| VoteExtension { - r#type: VoteExtensionType::ThresholdRecover.into(), - extension: v, - signature: Default::default(), - sign_request_id: None, - }) - .collect::>(); - - Ok(Self { - drive_operations, - inner, - }) - } - - /// Basic validation of withdrawals. - /// - /// TODO: validate signature, etc. - pub fn validate(&self) -> Result<(), AbciError> { - if self.drive_operations.len() != self.inner.len() { - return Err(AbciError::InvalidState(format!( - "num of drive operations {} must match num of withdrawal transactions {}", - self.drive_operations.len(), - self.inner.len(), - ))); - } - - Ok(()) - } - - /// Finalize operations related to this withdrawal, as part of FinalizeBlock logic. - /// - /// Deletes withdrawal transactions that were executed. - pub fn finalize( - &self, - transaction: TransactionArg, - drive: &Drive, - block_info: &BlockInfo, - platform_version: &PlatformVersion, - ) -> Result { - self.validate()?; - // TODO: Do we need to do sth with withdrawal txs to actually execute them? - // FIXME: check if this is correct, esp. "apply" arg - drive - .apply_drive_operations( - self.drive_operations.clone(), - true, - block_info, - transaction, - platform_version, - ) - .map_err(|e| AbciError::WithdrawalTransactionsDBLoadError(e.to_string())) - } -} - -impl<'a> WithdrawalTxs<'a> { - /// Convert withdrawal transactions to vector of ExtendVoteExtension - pub fn to_vec(&self) -> Vec { - self.inner - .iter() - .map(|v| ExtendVoteExtension { - r#type: v.r#type, - extension: v.extension.clone(), - sign_request_id: None, - }) - .collect::>() - } - /// Convert withdrawal transactions to vector of ExtendVoteExtension - pub fn into_vec(self) -> Vec { - self.inner - .into_iter() - .map(|v| ExtendVoteExtension { - r#type: v.r#type, - extension: v.extension, - sign_request_id: None, - }) - .collect::>() - } - - /// Verify signatures of all withdrawal TXs - /// - /// ## Return value - /// - /// There are the following types of errors during verification: - /// - /// 1. The signature was invalid, most likely due to change in the data; in this case, - /// [AbciError::VoteExtensionsSignatureInvalid] is returned. - /// 2. Signature or public key is malformed - in this case, [AbciError::BlsErrorOfTenderdashThresholdMechanism] is returned - /// 3. Provided data is invalid - [AbciError::TenderdashProto] is returned - /// - /// As all these conditions, in normal circumstances, should cause processing to be terminated, they are all - /// treated as errors. - pub fn verify_signatures( - &self, - chain_id: &str, - quorum_type: QuorumType, - quorum_hash: &[u8], - height: u64, - round: u32, - public_key: &bls_signatures::PublicKey, - ) -> SimpleValidationResult { - for s in &self.inner { - let hash = match s.sign_digest( - chain_id, - quorum_type as u8, - quorum_hash.try_into().expect("invalid quorum hash length"), - height as i64, - round as i32, - ) { - Ok(h) => h, - Err(e) => return SimpleValidationResult::new_with_error(AbciError::Tenderdash(e)), - }; - - let signature = match bls_signatures::Signature::from_bytes(&s.signature) { - Ok(s) => s, - Err(e) => { - return SimpleValidationResult::new_with_error( - AbciError::BlsErrorOfTenderdashThresholdMechanism( - e, - "signature withdrawal verification".to_string(), - ), - ) - } - }; - - if !public_key.verify(&signature, &hash) { - return SimpleValidationResult::new_with_error( - AbciError::VoteExtensionsSignatureInvalid, - ); - } - } - - SimpleValidationResult::default() - } -} - -impl<'a> Display for WithdrawalTxs<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("txs:["))?; - for item in &self.inner { - f.write_fmt(format_args!( - "tx:{},sig:{}\n", - hex::encode(&item.extension), - hex::encode(&item.signature) - ))?; - } - f.write_str("]\n")?; - Ok(()) - } -} -impl<'a> From> for WithdrawalTxs<'a> { - fn from(value: Vec) -> Self { - WithdrawalTxs { - inner: value - .into_iter() - .map(|v| VoteExtension { - r#type: v.r#type, - extension: v.extension, - signature: Default::default(), - sign_request_id: None, - }) - .collect::>(), - drive_operations: Vec::::new(), - } - } -} - -impl<'a> From<&Vec> for WithdrawalTxs<'a> { - fn from(value: &Vec) -> Self { - WithdrawalTxs { - inner: value.clone(), - drive_operations: Vec::::new(), - } - } -} - -impl<'a> PartialEq for WithdrawalTxs<'a> { - /// Two sets of withdrawal transactions are equal if all their inner raw transactions are equal. - /// - /// ## Notes - /// - /// 1. We don't compare `drive_operations`, as this is internal utility fields - /// 2. For a transaction, we don't compare signatures if at least one of them is empty - fn eq(&self, other: &Self) -> bool { - if self.inner.len() != other.inner.len() { - return false; - } - - std::iter::zip(&self.inner, &other.inner).all(|(left, right)| { - left.r#type == right.r#type - && left.extension == right.extension - && (left.signature.is_empty() - || right.signature.is_empty() - || left.signature == right.signature) - }) - } -} -#[cfg(test)] -mod test { - use dashcore_rpc::dashcore_rpc_json::QuorumType; - use dpp::bls_signatures; - use tenderdash_abci::proto::types::{VoteExtension, VoteExtensionType}; - - #[test] - fn verify_signature() { - const HEIGHT: u64 = 100; - const ROUND: u32 = 0; - const CHAIN_ID: &str = "test-chain"; - - let quorum_hash = - hex::decode("D6711FA18C7DA6D3FF8615D3CD3C14500EE91DA5FA942425B8E2B79A30FD8E6C") - .unwrap(); - - let mut wt = super::WithdrawalTxs { - inner: Vec::new(), - drive_operations: Vec::new(), - }; - let pubkey = hex::decode("8280cb6694f181db486c59dfa0c6d12d1c4ca26789340aebad0540ffe2edeac387aceec979454c2cfbe75fd8cf04d56d").unwrap(); - let pubkey = bls_signatures::PublicKey::from_bytes(&pubkey).unwrap(); - - let signature = hex::decode("A1022D9503CCAFC94FF76FA2E58E10A0474E6EB46305009274FAFCE57E28C7DE57602277777D07855567FAEF6A2F27590258858A875707F4DA32936DDD556BA28455AB04D9301E5F6F0762AC5B9FC036A302EE26116B1F89B74E1457C2D7383A").unwrap(); - // check if signature is correct - bls_signatures::Signature::from_bytes(&signature).unwrap(); - wt.inner.push(VoteExtension { - extension: [ - 82u8, 79, 29, 3, 209, 216, 30, 148, 160, 153, 4, 39, 54, 212, 11, 217, 104, 27, - 134, 115, 33, 68, 63, 245, 138, 69, 104, 226, 116, 219, 216, 59, - ] - .into(), - signature, - r#type: VoteExtensionType::ThresholdRecover.into(), - sign_request_id: None, - }); - - assert!(wt - .verify_signatures( - CHAIN_ID, - QuorumType::LlmqTest, - &quorum_hash, - HEIGHT, - ROUND, - &pubkey - ) - .is_valid()); - - // Now break the data - wt.inner[0].extension[3] = 0; - assert!(!wt - .verify_signatures( - CHAIN_ID, - QuorumType::LlmqTest, - &quorum_hash, - HEIGHT, - ROUND, - &pubkey - ) - .is_valid()); - } -} diff --git a/packages/rs-drive-abci/src/rpc/core.rs b/packages/rs-drive-abci/src/rpc/core.rs index 941459325f5..4693c20ffb7 100644 --- a/packages/rs-drive-abci/src/rpc/core.rs +++ b/packages/rs-drive-abci/src/rpc/core.rs @@ -1,9 +1,9 @@ use dashcore_rpc::dashcore::ephemerealdata::chain_lock::ChainLock; use dashcore_rpc::dashcore::{Block, BlockHash, QuorumHash, Transaction, Txid}; use dashcore_rpc::dashcore_rpc_json::{ - ExtendedQuorumDetails, ExtendedQuorumListResult, GetBestChainLockResult, GetChainTipsResult, - GetTransactionLockedResult, MasternodeListDiff, MnSyncStatus, QuorumInfoResult, QuorumType, - SoftforkInfo, + AssetUnlockStatusResult, ExtendedQuorumDetails, ExtendedQuorumListResult, + GetBestChainLockResult, GetChainTipsResult, GetTransactionLockedResult, MasternodeListDiff, + MnSyncStatus, QuorumInfoResult, QuorumType, SoftforkInfo, }; use dashcore_rpc::json::GetRawTransactionResult; use dashcore_rpc::{Auth, Client, Error, RpcApi}; @@ -33,11 +33,12 @@ pub trait CoreRPCLike { /// Get transaction fn get_transaction(&self, tx_id: &Txid) -> Result; - /// Get transaction finalization status - fn get_transactions_are_chain_locked( + /// Get asset unlock statuses + fn get_asset_unlock_statuses( &self, - tx_ids: Vec, - ) -> Result, Error>; + indices: &[u64], + core_chain_locked_height: u32, + ) -> Result, Error>; /// Get transaction fn get_transaction_extended_info(&self, tx_id: &Txid) @@ -116,6 +117,9 @@ pub trait CoreRPCLike { /// Returns masternode sync status fn masternode_sync_status(&self) -> Result; + + /// Sends raw transaction to the network + fn send_raw_transaction(&self, transaction: &Vec) -> Result; } #[derive(Debug)] @@ -124,6 +128,12 @@ pub struct DefaultCoreRPC { inner: Client, } +// TODO: Create errors for these error codes in dashcore_rpc + +/// TX is invalid due to consensus rules +pub const CORE_RPC_TX_CONSENSUS_ERROR: i32 = -26; +/// Tx already broadcasted and included in the chain +pub const CORE_RPC_TX_ALREADY_IN_CHAIN: i32 = -27; /// Client still warming up pub const CORE_RPC_ERROR_IN_WARMUP: i32 = -28; /// Dash is not connected @@ -137,6 +147,11 @@ pub const CORE_RPC_INVALID_ADDRESS_OR_KEY: i32 = -5; /// Invalid, missing or duplicate parameter pub const CORE_RPC_INVALID_PARAMETER: i32 = -8; +/// Asset Unlock consenus error "bad-assetunlock-not-active-quorum" +pub const CORE_RPC_ERROR_ASSET_UNLOCK_NO_ACTIVE_QUORUM: &str = "bad-assetunlock-not-active-quorum"; +/// Asset Unlock consenus error "bad-assetunlock-not-active-quorum" +pub const CORE_RPC_ERROR_ASSET_UNLOCK_EXPIRED: &str = "bad-assetunlock-too-late"; + macro_rules! retry { ($action:expr) => {{ /// Maximum number of retry attempts @@ -211,13 +226,6 @@ impl CoreRPCLike for DefaultCoreRPC { retry!(self.inner.get_raw_transaction(tx_id, None)) } - fn get_transactions_are_chain_locked( - &self, - tx_ids: Vec, - ) -> Result, Error> { - retry!(self.inner.get_transaction_are_locked(&tx_ids)) - } - fn get_transaction_extended_info( &self, tx_id: &Txid, @@ -305,4 +313,18 @@ impl CoreRPCLike for DefaultCoreRPC { fn masternode_sync_status(&self) -> Result { retry!(self.inner.mnsync_status()) } + + fn send_raw_transaction(&self, transaction: &Vec) -> Result { + retry!(self.inner.send_raw_transaction(transaction.as_slice())) + } + + fn get_asset_unlock_statuses( + &self, + indices: &[u64], + core_chain_locked_height: u32, + ) -> Result, Error> { + retry!(self + .inner + .get_asset_unlock_statuses(indices, Some(core_chain_locked_height))) + } } diff --git a/packages/rs-drive-abci/tests/strategy_tests/execution.rs b/packages/rs-drive-abci/tests/strategy_tests/execution.rs index 57f809f922e..4d4ac7c6ff5 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/execution.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/execution.rs @@ -31,7 +31,8 @@ use drive_abci::mimic::{MimicExecuteBlockOptions, MimicExecuteBlockOutcome}; use drive_abci::platform_types::epoch_info::v0::EpochInfoV0; use drive_abci::platform_types::platform::Platform; use drive_abci::platform_types::platform_state::v0::PlatformStateV0Methods; -use drive_abci::rpc::core::{CoreRPCLike, MockCoreRPCLike}; +use drive_abci::platform_types::withdrawal::unsigned_withdrawal_txs::v0::UnsignedWithdrawalTxs; +use drive_abci::rpc::core::MockCoreRPCLike; use drive_abci::test::fixture::abci::static_init_chain_request; use platform_version::version::PlatformVersion; use rand::prelude::{SliceRandom, StdRng}; @@ -43,6 +44,8 @@ use tenderdash_abci::proto::google::protobuf::Timestamp; use tenderdash_abci::proto::serializers::timestamp::FromMilis; use tenderdash_abci::Application; +pub const GENESIS_TIME_MS: u64 = 1681094380000; + pub(crate) fn run_chain_for_strategy( platform: &mut Platform, block_count: u64, @@ -767,8 +770,8 @@ pub(crate) fn start_chain_for_strategy( quorums, current_quorum_hash, current_proposer_versions: None, - start_time_ms: 1681094380000, - current_time_ms: 1681094380000, + start_time_ms: GENESIS_TIME_MS, + current_time_ms: GENESIS_TIME_MS, }, strategy, config, @@ -822,7 +825,7 @@ pub(crate) fn continue_chain_for_strategy( let mut current_core_height = core_height_start; - let mut total_withdrawals = vec![]; + let mut total_withdrawals = UnsignedWithdrawalTxs::default(); let mut current_quorum_with_test_info = quorums.get::(¤t_quorum_hash).unwrap(); @@ -971,7 +974,7 @@ pub(crate) fn continue_chain_for_strategy( let platform_version = platform_state.current_platform_version().unwrap(); - total_withdrawals.append(&mut withdrawals_this_block); + total_withdrawals.append(withdrawals_this_block); for finalize_block_operation in finalize_block_operations { match finalize_block_operation { diff --git a/packages/rs-drive-abci/tests/strategy_tests/main.rs b/packages/rs-drive-abci/tests/strategy_tests/main.rs index 6a528ee5ad8..3a749b6fc8f 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/main.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/main.rs @@ -40,6 +40,8 @@ use drive_abci::test::helpers::setup::TestPlatformBuilder; use drive_abci::{config::PlatformConfig, test::helpers::setup::TempPlatform}; use strategy_tests::frequency::Frequency; +use dpp::dashcore::transaction::special_transaction::TransactionPayload::AssetUnlockPayloadType; +use dpp::dashcore::Transaction; use std::collections::BTreeMap; use strategy::{ @@ -63,35 +65,43 @@ pub type BlockHeight = u64; #[cfg(test)] mod tests { use super::*; - use crate::execution::{continue_chain_for_strategy, run_chain_for_strategy}; + use crate::execution::{continue_chain_for_strategy, run_chain_for_strategy, GENESIS_TIME_MS}; use crate::query::QueryStrategy; use crate::strategy::{FailureStrategy, MasternodeListChangesStrategy}; use dashcore_rpc::dashcore::hashes::Hash; use dashcore_rpc::dashcore::BlockHash; - use dashcore_rpc::dashcore_rpc_json::ExtendedQuorumDetails; + use dashcore_rpc::dashcore_rpc_json::{AssetUnlockStatus, ExtendedQuorumDetails}; + use dashcore_rpc::json::AssetUnlockStatusResult; use dpp::block::extended_block_info::v0::ExtendedBlockInfoV0Getters; + use std::sync::{Arc, Mutex}; use strategy_tests::operations::DocumentAction::DocumentActionReplace; use strategy_tests::operations::{ DocumentAction, DocumentOp, IdentityUpdateOp, Operation, OperationType, }; use crate::strategy::CoreHeightIncrease::RandomCoreHeightIncrease; + use dpp::dashcore::bls_sig_utils::BLSSignature; use dpp::dashcore::ChainLock; + use dpp::dashcore::Txid; use dpp::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters}; use dpp::data_contract::document_type::random_document::{ DocumentFieldFillSize, DocumentFieldFillType, }; use dpp::identity::accessors::IdentityGettersV0; use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; + use dpp::system_data_contracts::withdrawals_contract; use dpp::tests::json_document::json_document_to_created_contract; use dpp::util::hash::hash_to_hex_string; use dpp::version::PlatformVersion; + use drive::drive::config::DEFAULT_QUERY_LIMIT; + use drive::drive::identity::withdrawals::WithdrawalTransactionIndex; use drive_abci::config::{ExecutionConfig, PlatformTestConfig}; + use drive_abci::error::execution::ExecutionError; + use drive_abci::error::Error; use drive_abci::logging::LogLevel; use drive_abci::platform_types::platform_state::v0::PlatformStateV0Methods; use drive_abci::rpc::core::QuorumListExtendedInfo; use itertools::Itertools; - use rand::distributions::uniform::SampleBorrow; use tenderdash_abci::proto::abci::{RequestInfo, ResponseInfo}; use tenderdash_abci::proto::types::CoreChainLock; use tenderdash_abci::Application; @@ -1114,7 +1124,7 @@ mod tests { .unwrap() .unwrap() ), - "b1717304d3ce607569ca53d7f801df874578f510b352f23d8e4c087cdfed5697".to_string() + "fa6e1d4977b35a5b9d9be63afb65d9cd3cf607e79a733bf743b96bfd6de48eed".to_string() ) } @@ -1670,7 +1680,7 @@ mod tests { .unwrap() .unwrap() ), - "40b51654a73ab89e98076819498a746dc179d54c53f599fa5ceeec57cec3576e".to_string() + "301a9e2630e5017a1d45a492a1e23837ab7369049c617d8202db22f924a911f3".to_string() ) } @@ -2137,6 +2147,7 @@ mod tests { #[test] fn run_chain_top_up_and_withdraw_from_identities() { + let platform_version = PlatformVersion::latest(); let strategy = NetworkStrategy { strategy: Strategy { contracts_with_updates: vec![], @@ -2191,14 +2202,511 @@ mod tests { testing_configs: PlatformTestConfig::default_with_no_block_signing(), ..Default::default() }; + let mut platform = TestPlatformBuilder::new() .with_config(config.clone()) .build_with_mock_rpc(); - let outcome = run_chain_for_strategy(&mut platform, 10, strategy, config, 15); + platform + .core_rpc + .expect_send_raw_transaction() + .returning(move |_| Ok(Txid::all_zeros())); + + struct CoreState { + asset_unlock_statuses: BTreeMap, + chain_lock: ChainLock, + } + + let mut chain_locked_height = 1; + + // Have to go with a complicated shared object for the core state because we need to change + // rpc response along the way but we can't mutate `platform.core_rpc` later + // because platform reference is moved into the AbciApplication. + let shared_core_state = Arc::new(Mutex::new(CoreState { + asset_unlock_statuses: BTreeMap::new(), + chain_lock: ChainLock { + block_height: chain_locked_height, + block_hash: BlockHash::from_byte_array([1; 32]), + signature: BLSSignature::from([2; 96]), + }, + })); + + // Set up Core RPC responses + { + let core_state = shared_core_state.clone(); + + platform + .core_rpc + .expect_get_asset_unlock_statuses() + .returning(move |indices, _| { + Ok(indices + .iter() + .map(|index| { + core_state + .lock() + .unwrap() + .asset_unlock_statuses + .get(index) + .map(|status| status.clone()) + .unwrap() + }) + .collect()) + }); + + let core_state = shared_core_state.clone(); + platform + .core_rpc + .expect_get_best_chain_lock() + .returning(move || Ok(core_state.lock().unwrap().chain_lock.clone())); + } + + // Run first two blocks: + // - Block 1: creates identity + // - Block 2: tops up identity and initiates withdrawals + let ( + ChainExecutionOutcome { + abci_app, + proposers, + quorums, + current_quorum_hash, + current_proposer_versions, + end_time_ms, + .. + }, + last_block_pooled_withdrawals_amount, + ) = { + let outcome = + run_chain_for_strategy(&mut platform, 2, strategy.clone(), config.clone(), 1); + + // Withdrawal transactions are not populated to block execution context yet + assert_eq!(outcome.withdrawals.len(), 0); + + // Withdrawal documents with pooled status should exist. + let withdrawal_documents_pooled = outcome + .abci_app + .platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::POOLED.into(), + DEFAULT_QUERY_LIMIT, + None, + platform_version, + ) + .unwrap(); + assert!(withdrawal_documents_pooled.len() > 0); + let pooled_withdrawals = withdrawal_documents_pooled.len(); + + (outcome, pooled_withdrawals) + }; + + // Run block 3 + // Should broadcast previously pooled withdrawals to core + let ChainExecutionOutcome { + abci_app, + proposers, + quorums, + current_quorum_hash, + current_proposer_versions, + end_time_ms, + withdrawals: last_block_withdrawals, + .. + } = { + let outcome = continue_chain_for_strategy( + abci_app, + ChainExecutionParameters { + block_start: 3, + core_height_start: 1, + block_count: 1, + proposers, + quorums, + current_quorum_hash, + current_proposer_versions: Some(current_proposer_versions), + start_time_ms: GENESIS_TIME_MS, + current_time_ms: end_time_ms, + }, + strategy.clone(), + config.clone(), + StrategyRandomness::SeedEntropy(2), + ); + + // Withdrawal documents with pooled status should exist. + let withdrawal_documents_broadcasted = outcome + .abci_app + .platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::BROADCASTED.into(), + DEFAULT_QUERY_LIMIT, + None, + platform_version, + ) + .unwrap(); + + // In this block all previously pooled withdrawals should be broadcasted + assert_eq!( + outcome.withdrawals.len(), + last_block_pooled_withdrawals_amount + ); + assert_eq!( + withdrawal_documents_broadcasted.len(), + last_block_pooled_withdrawals_amount + ); + + outcome + }; + + // Update core state before running next block. + // Asset unlocks broadcasted in the last block should have Unknown status + { + let mut core_state = shared_core_state.lock().unwrap(); + last_block_withdrawals.iter().for_each(|tx| { + let index = asset_unlock_index(tx); + + core_state.asset_unlock_statuses.insert( + index, + AssetUnlockStatusResult { + index, + status: AssetUnlockStatus::Unknown, + }, + ); + }); + } + + // Run block 4 + // Should change pooled status to broadcasted + let last_block_broadcased_withdrawals_amount = last_block_withdrawals.len(); + let ( + ChainExecutionOutcome { + abci_app, + proposers, + quorums, + current_quorum_hash, + current_proposer_versions, + end_time_ms, + withdrawals: last_block_withdrawals, + .. + }, + last_block_broadcased_withdrawals_amount, + ) = { + let outcome = continue_chain_for_strategy( + abci_app, + ChainExecutionParameters { + block_start: 4, + core_height_start: 1, + block_count: 1, + proposers, + quorums, + current_quorum_hash, + current_proposer_versions: Some(current_proposer_versions), + start_time_ms: GENESIS_TIME_MS, + current_time_ms: end_time_ms + 1000, + }, + strategy.clone(), + config.clone(), + StrategyRandomness::SeedEntropy(3), + ); + + let withdrawal_documents_pooled = outcome + .abci_app + .platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::POOLED.into(), + DEFAULT_QUERY_LIMIT, + None, + platform_version, + ) + .unwrap(); + + let withdrawal_documents_broadcasted = outcome + .abci_app + .platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::BROADCASTED.into(), + DEFAULT_QUERY_LIMIT, + None, + platform_version, + ) + .unwrap(); + + // In this block we should have new withdrawals pooled + assert!(withdrawal_documents_pooled.len() > 0); + + // And extra withdrawals broadcasted + let withdrawals_broadcasted_expected = + last_block_broadcased_withdrawals_amount + outcome.withdrawals.len(); + assert_eq!( + withdrawal_documents_broadcasted.len(), + withdrawals_broadcasted_expected + ); + + (outcome, withdrawal_documents_broadcasted.len()) + }; - assert_eq!(outcome.identities.len(), 10); - assert_eq!(outcome.withdrawals.len(), 18); + // Update core state for newly broadcasted transactions + { + let mut core_state = shared_core_state.lock().unwrap(); + + // First, set all previously broadcasted transactions to Chainlocked + core_state + .asset_unlock_statuses + .iter_mut() + .for_each(|(index, status_result)| { + // Do not settle yet transactions that were broadcasted in the last block + status_result.index = *index; + status_result.status = AssetUnlockStatus::Chainlocked; + }); + + // Then increase chainlocked height, so that withdrawals for chainlocked tranasctions + // could be completed in the next block + // TODO: do we need this var? + chain_locked_height += 1; + core_state.chain_lock.block_height = chain_locked_height; + + // Then set all newly broadcasted transactions to Unknown + last_block_withdrawals.iter().for_each(|tx| { + let index = asset_unlock_index(tx); + + core_state.asset_unlock_statuses.insert( + index, + AssetUnlockStatusResult { + index, + status: AssetUnlockStatus::Unknown, + }, + ); + }); + + drop(core_state); + } + + // Run block 5 + // Previously broadcasted transactions should be settled after block 5, + // and their corresponding statuses should be changed to COMPLETED + let ( + ChainExecutionOutcome { + abci_app, + proposers, + quorums, + current_quorum_hash, + current_proposer_versions, + end_time_ms, + withdrawals: last_block_withdrawals, + .. + }, + last_block_withdrawals_completed_amount, + ) = { + let outcome = continue_chain_for_strategy( + abci_app, + ChainExecutionParameters { + block_start: 5, + core_height_start: 1, + block_count: 1, + proposers, + quorums, + current_quorum_hash, + current_proposer_versions: Some(current_proposer_versions), + start_time_ms: GENESIS_TIME_MS, + current_time_ms: end_time_ms + 1000, + }, + strategy.clone(), + config.clone(), + StrategyRandomness::SeedEntropy(4), + ); + + let withdrawal_documents_pooled = outcome + .abci_app + .platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::POOLED.into(), + DEFAULT_QUERY_LIMIT, + None, + platform_version, + ) + .unwrap(); + + let withdrawal_documents_broadcasted = outcome + .abci_app + .platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::BROADCASTED.into(), + DEFAULT_QUERY_LIMIT, + None, + platform_version, + ) + .unwrap(); + + let withdrawal_documents_completed = outcome + .abci_app + .platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::COMPLETE.into(), + DEFAULT_QUERY_LIMIT, + None, + platform_version, + ) + .unwrap(); + + // In this block we should have new withdrawals pooled + assert!(withdrawal_documents_pooled.len() > 0); + + // And some withdrawals completed + let withdrawals_completed_expected = + // Withdrawals issued on {previous_block - 1} considered completed + last_block_broadcased_withdrawals_amount - last_block_withdrawals.len(); + assert_eq!( + withdrawal_documents_completed.len(), + withdrawals_completed_expected + ); + + // And extra withdrawals broadcasted + let withdrawals_broadcasted_expected = + // Withdrawals issued on previous block + withdrawals from this block are still in broadcasted state + last_block_withdrawals.len() + outcome.withdrawals.len(); + + assert_eq!( + withdrawal_documents_broadcasted.len(), + withdrawals_broadcasted_expected + ); + + (outcome, withdrawal_documents_completed.len()) + }; + + // Update state of the core before proceeding to the next block + { + // Simulate transactions being added to the core mempool + let mut core_state = shared_core_state.lock().unwrap(); + + let number_of_blocks_before_expiration: u32 = 48; + chain_locked_height += number_of_blocks_before_expiration; + + core_state.chain_lock.block_height = chain_locked_height; + + last_block_withdrawals.iter().for_each(|tx| { + let index = asset_unlock_index(tx); + + core_state.asset_unlock_statuses.insert( + index, + AssetUnlockStatusResult { + index, + status: AssetUnlockStatus::Unknown, + }, + ); + }); + } + + // Run block 6. + // Tests withdrawal expiration + let ChainExecutionOutcome { + abci_app, + proposers, + quorums, + current_quorum_hash, + current_proposer_versions, + end_time_ms, + withdrawals, + .. + } = { + let outcome = continue_chain_for_strategy( + abci_app, + ChainExecutionParameters { + block_start: 6, + core_height_start: 1, + block_count: 1, + proposers, + quorums, + current_quorum_hash, + current_proposer_versions: Some(current_proposer_versions), + start_time_ms: GENESIS_TIME_MS, + current_time_ms: end_time_ms + 1000, + }, + strategy.clone(), + config.clone(), + StrategyRandomness::SeedEntropy(5), + ); + + let withdrawal_documents_pooled = outcome + .abci_app + .platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::POOLED.into(), + DEFAULT_QUERY_LIMIT, + None, + platform_version, + ) + .unwrap(); + + let withdrawal_documents_broadcasted = outcome + .abci_app + .platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::BROADCASTED.into(), + DEFAULT_QUERY_LIMIT, + None, + platform_version, + ) + .unwrap(); + + let withdrawal_documents_completed = outcome + .abci_app + .platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::COMPLETE.into(), + DEFAULT_QUERY_LIMIT, + None, + platform_version, + ) + .unwrap(); + + let withdrawal_documents_expired = outcome + .abci_app + .platform + .drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::EXPIRED.into(), + DEFAULT_QUERY_LIMIT, + None, + platform_version, + ) + .unwrap(); + + // In this block we should have new withdrawals pooled + assert!(withdrawal_documents_pooled.len() > 0); + + // Amount of completed withdrawals stays the same as in the last block + assert_eq!( + withdrawal_documents_completed.len(), + last_block_withdrawals_completed_amount + ); + + // And some withdrawals got expired + let withdrawals_expired_expected = + // Withdrawals issued on {previous_block - 1}, but not chainlocked yet, considered expired + last_block_broadcased_withdrawals_amount - last_block_withdrawals.len(); + + assert_eq!( + withdrawal_documents_expired.len(), + withdrawals_expired_expected + ); + + // And extra withdrawals broadcasted + let withdrawals_broadcasted_expected = + // Withdrawals issued on previous block + withdrawals from this block are still in broadcasted state + last_block_withdrawals.len() + outcome.withdrawals.len(); + + assert_eq!( + withdrawal_documents_broadcasted.len(), + withdrawals_broadcasted_expected + ); + + outcome + }; } #[test] @@ -2856,3 +3364,10 @@ mod tests { assert_eq!(state_transitions.len(), 3); } } + +fn asset_unlock_index(tx: &Transaction) -> u64 { + let Some(AssetUnlockPayloadType(ref payload)) = tx.special_transaction_payload else { + panic!("expected to get AssetUnlockPayloadType"); + }; + payload.base.index +} diff --git a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs index 1cae313520e..5dcc6c55f81 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs @@ -50,6 +50,7 @@ use dpp::state_transition::documents_batch_transition::{DocumentsBatchTransition use dpp::state_transition::documents_batch_transition::document_transition::{DocumentDeleteTransition, DocumentReplaceTransition}; use drive::drive::document::query::QueryDocumentsOutcomeV0Methods; use dpp::state_transition::data_contract_create_transition::methods::v0::DataContractCreateTransitionMethodsV0; +use drive_abci::platform_types::withdrawal::unsigned_withdrawal_txs::v0::UnsignedWithdrawalTxs; use crate::strategy::CoreHeightIncrease::NoCoreHeightIncrease; use simple_signer::signer::SimpleSigner; @@ -1018,7 +1019,7 @@ pub struct ChainExecutionOutcome<'a> { pub end_epoch_index: u16, pub end_time_ms: u64, pub strategy: NetworkStrategy, - pub withdrawals: Vec, + pub withdrawals: UnsignedWithdrawalTxs, /// height to the validator set update at that height pub validator_set_updates: BTreeMap, pub state_transition_results_per_block: BTreeMap>, diff --git a/packages/rs-drive-proof-verifier/Cargo.toml b/packages/rs-drive-proof-verifier/Cargo.toml index 45a8a436ee1..b0768476c54 100644 --- a/packages/rs-drive-proof-verifier/Cargo.toml +++ b/packages/rs-drive-proof-verifier/Cargo.toml @@ -20,8 +20,7 @@ drive = { path = "../rs-drive", default-features = false, features = [ "verify", ] } dpp = { path = "../rs-dpp" } -tenderdash-abci = { git = "https://github.com/dashpay/rs-tenderdash-abci", rev = "251386f9e4109c89ca628226c981c489b80aed80" } -# tenderdash-abci = { path = "../../../rs-tenderdash-abci/abci" } +tenderdash-abci = { git = "https://github.com/dashpay/rs-tenderdash-abci", tag = "v0.14.0-dev.6" } tracing = { version = "0.1.37" } serde = { version = "1.0.171", default-features = false, optional = true } serde_json = { version = "1.0.103", features = [ diff --git a/packages/rs-drive/src/drive/batch/drive_op_batch/mod.rs b/packages/rs-drive/src/drive/batch/drive_op_batch/mod.rs index ba0769328bc..123580df302 100644 --- a/packages/rs-drive/src/drive/batch/drive_op_batch/mod.rs +++ b/packages/rs-drive/src/drive/batch/drive_op_batch/mod.rs @@ -65,7 +65,7 @@ pub enum DriveOperation<'a> { /// A document operation DocumentOperation(DocumentOperationType<'a>), /// Withdrawal operation - WithdrawalOperation(WithdrawalOperationType<'a>), + WithdrawalOperation(WithdrawalOperationType), /// An identity operation IdentityOperation(IdentityOperationType), /// A system operation diff --git a/packages/rs-drive/src/drive/batch/drive_op_batch/withdrawals.rs b/packages/rs-drive/src/drive/batch/drive_op_batch/withdrawals.rs index 9032fa60647..d7ec21a09ff 100644 --- a/packages/rs-drive/src/drive/batch/drive_op_batch/withdrawals.rs +++ b/packages/rs-drive/src/drive/batch/drive_op_batch/withdrawals.rs @@ -7,11 +7,12 @@ use grovedb::{batch::KeyInfoPath, EstimatedLayerInformation, TransactionArg}; use crate::drive::grove_operations::BatchDeleteApplyType; use crate::drive::identity::withdrawals::paths::{ - get_withdrawal_root_path_vec, get_withdrawal_transactions_expired_ids_path, - get_withdrawal_transactions_expired_ids_path_vec, get_withdrawal_transactions_queue_path, - get_withdrawal_transactions_queue_path_vec, WITHDRAWAL_TRANSACTIONS_COUNTER_ID, + get_withdrawal_root_path_vec, get_withdrawal_transactions_queue_path, + get_withdrawal_transactions_queue_path_vec, WITHDRAWAL_TRANSACTIONS_NEXT_INDEX_KEY, +}; +use crate::drive::identity::withdrawals::{ + WithdrawalTransactionIndex, WithdrawalTransactionIndexAndBytes, }; -use crate::drive::identity::withdrawals::WithdrawalTransactionIdAndBytes; use crate::drive::object_size_info::PathKeyElementInfo; use crate::{drive::Drive, error::Error, fee::op::LowLevelDriveOperation}; @@ -19,35 +20,25 @@ use super::DriveLowLevelOperationConverter; /// Operations for Withdrawals #[derive(Clone, Debug)] -pub enum WithdrawalOperationType<'a> { - /// Inserts expired index into it's tree - InsertExpiredIndex { - /// index value - index: u64, - }, - /// Removes expired index from the tree - DeleteExpiredIndex { - /// index value - key: Vec, - }, +pub enum WithdrawalOperationType { /// Update index counter UpdateIndexCounter { /// index counter value - index: u64, + index: WithdrawalTransactionIndex, }, /// Insert Core Transaction into queue InsertTransactions { /// transaction id bytes - withdrawal_transactions: &'a [WithdrawalTransactionIdAndBytes], + withdrawal_transactions: Vec, }, /// Delete withdrawal DeleteWithdrawalTransaction { /// withdrawal transaction tuple with id and bytes - id: Vec, + index: WithdrawalTransactionIndex, }, } -impl DriveLowLevelOperationConverter for WithdrawalOperationType<'_> { +impl DriveLowLevelOperationConverter for WithdrawalOperationType { fn into_low_level_drive_operations( self, drive: &Drive, @@ -59,43 +50,6 @@ impl DriveLowLevelOperationConverter for WithdrawalOperationType<'_> { platform_version: &PlatformVersion, ) -> Result, Error> { match self { - WithdrawalOperationType::InsertExpiredIndex { index } => { - let mut drive_operations = vec![]; - - let index_bytes = index.to_be_bytes(); - - let path = get_withdrawal_transactions_expired_ids_path_vec(); - - drive.batch_insert( - PathKeyElementInfo::PathKeyElement::<'_, 1>(( - path, - index_bytes.to_vec(), - Element::Item(vec![], None), - )), - &mut drive_operations, - &platform_version.drive, - )?; - - Ok(drive_operations) - } - WithdrawalOperationType::DeleteExpiredIndex { key } => { - let mut drive_operations = vec![]; - - let path: [&[u8]; 2] = get_withdrawal_transactions_expired_ids_path(); - - drive.batch_delete( - (&path).into(), - &key, - BatchDeleteApplyType::StatefulBatchDelete { - is_known_to_be_subtree_with_sum: Some((false, false)), - }, - transaction, - &mut drive_operations, - &platform_version.drive, - )?; - - Ok(drive_operations) - } WithdrawalOperationType::UpdateIndexCounter { index } => { let mut drive_operations = vec![]; @@ -104,7 +58,7 @@ impl DriveLowLevelOperationConverter for WithdrawalOperationType<'_> { drive.batch_insert( PathKeyElementInfo::PathKeyRefElement::<'_, 1>(( path, - &WITHDRAWAL_TRANSACTIONS_COUNTER_ID, + &WITHDRAWAL_TRANSACTIONS_NEXT_INDEX_KEY, Element::Item(index.to_be_bytes().to_vec(), None), )), &mut drive_operations, @@ -120,12 +74,12 @@ impl DriveLowLevelOperationConverter for WithdrawalOperationType<'_> { let path = get_withdrawal_transactions_queue_path_vec(); - for (id, bytes) in withdrawal_transactions { + for (index, bytes) in withdrawal_transactions { drive.batch_insert( PathKeyElementInfo::PathKeyElement::<'_, 1>(( path.clone(), - id.clone(), - Element::Item(bytes.clone(), None), + index.to_be_bytes().to_vec(), + Element::Item(bytes, None), )), &mut drive_operations, &platform_version.drive, @@ -134,14 +88,14 @@ impl DriveLowLevelOperationConverter for WithdrawalOperationType<'_> { Ok(drive_operations) } - WithdrawalOperationType::DeleteWithdrawalTransaction { id } => { + WithdrawalOperationType::DeleteWithdrawalTransaction { index } => { let mut drive_operations = vec![]; let path = get_withdrawal_transactions_queue_path(); drive.batch_delete( (&path).into(), - &id, + &index.to_be_bytes(), // we know that we are not deleting a subtree BatchDeleteApplyType::StatefulBatchDelete { is_known_to_be_subtree_with_sum: Some((false, false)), diff --git a/packages/rs-drive/src/drive/batch/transitions/identity/identity_credit_withdrawal_transition.rs b/packages/rs-drive/src/drive/batch/transitions/identity/identity_credit_withdrawal_transition.rs index fc03bd39ac0..b9034fcc184 100644 --- a/packages/rs-drive/src/drive/batch/transitions/identity/identity_credit_withdrawal_transition.rs +++ b/packages/rs-drive/src/drive/batch/transitions/identity/identity_credit_withdrawal_transition.rs @@ -1,6 +1,8 @@ use crate::drive::batch::transitions::DriveHighLevelOperationConverter; -use crate::drive::batch::DriveOperation::{DocumentOperation, IdentityOperation}; -use crate::drive::batch::{DocumentOperationType, DriveOperation, IdentityOperationType}; +use crate::drive::batch::DriveOperation::{DocumentOperation, IdentityOperation, SystemOperation}; +use crate::drive::batch::{ + DocumentOperationType, DriveOperation, IdentityOperationType, SystemOperationType, +}; use crate::drive::object_size_info::{DocumentInfo, OwnedDocumentInfo}; use crate::error::Error; use dpp::block::epoch::Epoch; @@ -16,9 +18,14 @@ impl DriveHighLevelOperationConverter for IdentityCreditWithdrawalTransitionActi ) -> Result>, Error> { let identity_id = self.identity_id(); let revision = self.revision(); + let balance_to_remove = self.amount(); let prepared_withdrawal_document = self.prepared_withdrawal_document_owned(); let drive_operations = vec![ + IdentityOperation(IdentityOperationType::RemoveFromIdentityBalance { + identity_id: identity_id.to_buffer(), + balance_to_remove, + }), IdentityOperation(IdentityOperationType::UpdateIdentityRevision { identity_id: identity_id.into_buffer(), revision, @@ -32,6 +39,9 @@ impl DriveHighLevelOperationConverter for IdentityCreditWithdrawalTransitionActi owner_id: None, }, }), + SystemOperation(SystemOperationType::RemoveFromSystemCredits { + amount: balance_to_remove, + }), ]; Ok(drive_operations) diff --git a/packages/rs-drive/src/drive/config.rs b/packages/rs-drive/src/drive/config.rs index 0f4e417d77b..3a7f2adcb42 100644 --- a/packages/rs-drive/src/drive/config.rs +++ b/packages/rs-drive/src/drive/config.rs @@ -38,7 +38,7 @@ pub const DEFAULT_GROVE_BATCHING_CONSISTENCY_VERIFICATION_ENABLED: bool = false; /// Boolean if GroveDB has_raw in enabled by default pub const DEFAULT_GROVE_HAS_RAW_ENABLED: bool = true; /// The default default query limit -pub const DEFAULT_DEFAULT_QUERY_LIMIT: u16 = 100; +pub const DEFAULT_QUERY_LIMIT: u16 = 100; /// The default max query limit pub const DEFAULT_MAX_QUERY_LIMIT: u16 = 100; /// Default maximum number of contracts in cache @@ -126,7 +126,7 @@ fn default_has_raw_enabled() -> bool { } fn default_default_query_limit() -> u16 { - DEFAULT_DEFAULT_QUERY_LIMIT + DEFAULT_QUERY_LIMIT } fn default_epochs_per_era() -> u16 { @@ -147,7 +147,7 @@ impl Default for DriveConfig { batching_consistency_verification: DEFAULT_GROVE_BATCHING_CONSISTENCY_VERIFICATION_ENABLED, has_raw_enabled: DEFAULT_GROVE_HAS_RAW_ENABLED, - default_query_limit: DEFAULT_DEFAULT_QUERY_LIMIT, + default_query_limit: DEFAULT_QUERY_LIMIT, epochs_per_era: DEFAULT_EPOCHS_PER_ERA, max_query_limit: DEFAULT_MAX_QUERY_LIMIT, default_genesis_time: None, diff --git a/packages/rs-drive/src/drive/identity/withdrawals/document/fetch_oldest_withdrawal_documents_by_status/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/document/fetch_oldest_withdrawal_documents_by_status/mod.rs new file mode 100644 index 00000000000..a82c2cc2db1 --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/document/fetch_oldest_withdrawal_documents_by_status/mod.rs @@ -0,0 +1,40 @@ +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use dpp::document::Document; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +mod v0; + +impl Drive { + /// Fetch withdrawal documents by it's status ordered by updated_at ascending + pub fn fetch_oldest_withdrawal_documents_by_status( + &self, + status: u8, + limit: u16, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .identity + .withdrawals + .document + .fetch_oldest_withdrawal_documents_by_status + { + 0 => self.fetch_oldest_withdrawal_documents_by_status_v0( + status, + limit, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_up_to_100_oldest_withdrawal_documents_by_status".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/document/fetch_oldest_withdrawal_documents_by_status/v0/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/document/fetch_oldest_withdrawal_documents_by_status/v0/mod.rs new file mode 100644 index 00000000000..deb00e91544 --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/document/fetch_oldest_withdrawal_documents_by_status/v0/mod.rs @@ -0,0 +1,215 @@ +use crate::drive::document::query::QueryDocumentsOutcomeV0Methods; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::query::{DriveQuery, InternalClauses, OrderClause, WhereClause}; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contracts::withdrawals_contract; +use dpp::data_contracts::withdrawals_contract::v1::document_types::withdrawal; +use dpp::document::Document; +use dpp::platform_value::Value; +use grovedb::TransactionArg; +use indexmap::IndexMap; +use platform_version::version::PlatformVersion; +use std::collections::BTreeMap; + +impl Drive { + pub(super) fn fetch_oldest_withdrawal_documents_by_status_v0( + &self, + status: u8, + limit: u16, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let data_contract_id = withdrawals_contract::ID; + + let contract_fetch_info = self + .get_contract_with_fetch_info_and_fee( + data_contract_id.to_buffer(), + None, + true, + transaction, + platform_version, + )? + .1 + .ok_or_else(|| { + Error::Drive(DriveError::CorruptedCodeExecution( + "Can't fetch data contract", + )) + })?; + + let document_type = contract_fetch_info + .contract + .document_type_for_name(withdrawal::NAME)?; + + let mut where_clauses = BTreeMap::new(); + + //todo: make this lazy loaded or const + where_clauses.insert( + withdrawal::properties::STATUS.to_string(), + WhereClause { + field: withdrawal::properties::STATUS.to_string(), + operator: crate::query::WhereOperator::Equal, + value: Value::U8(status), + }, + ); + + let mut order_by = IndexMap::new(); + + order_by.insert( + withdrawal::properties::UPDATED_AT.to_string(), + OrderClause { + field: withdrawal::properties::UPDATED_AT.to_string(), + ascending: true, + }, + ); + + let drive_query = DriveQuery { + contract: &contract_fetch_info.contract, + document_type, + internal_clauses: InternalClauses { + primary_key_in_clause: None, + primary_key_equal_clause: None, + in_clause: None, + range_clause: None, + equal_clauses: where_clauses, + }, + offset: None, + limit: Some(limit), + order_by, + start_at: None, + start_at_included: false, + block_time_ms: None, + }; + + let outcome = self.query_documents( + drive_query, + None, + false, + transaction, + Some(platform_version.protocol_version), + )?; + + Ok(outcome.documents_owned()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::drive::config::DEFAULT_QUERY_LIMIT; + use crate::tests::helpers::setup::{ + setup_document, setup_drive_with_initial_state_structure, setup_system_data_contract, + }; + use dpp::data_contract::accessors::v0::DataContractV0Getters; + use dpp::identifier::Identifier; + use dpp::identity::core_script::CoreScript; + use dpp::platform_value::platform_value; + use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; + use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; + use dpp::tests::fixtures::get_withdrawal_document_fixture; + use dpp::version::PlatformVersion; + use dpp::withdrawal::Pooling; + + #[test] + fn test_return_list_of_documents() { + let drive = setup_drive_with_initial_state_structure(); + + let transaction = drive.grove.start_transaction(); + + let platform_version = PlatformVersion::latest(); + + let data_contract = + load_system_data_contract(SystemDataContract::Withdrawals, platform_version) + .expect("to load system data contract"); + + setup_system_data_contract(&drive, &data_contract, Some(&transaction)); + + let documents = drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::QUEUED.into(), + DEFAULT_QUERY_LIMIT, + Some(&transaction), + platform_version, + ) + .expect("to fetch documents by status"); + + assert_eq!(documents.len(), 0); + + let owner_id = Identifier::new([1u8; 32]); + + let document = get_withdrawal_document_fixture( + &data_contract, + owner_id, + platform_value!({ + "amount": 1000u64, + "coreFeePerByte": 1u32, + "pooling": Pooling::Never as u8, + "outputScript": CoreScript::from_bytes((0..23).collect::>()), + "status": withdrawals_contract::WithdrawalStatus::QUEUED as u8, + "transactionIndex": 1u64, + }), + None, + platform_version.protocol_version, + ) + .expect("expected withdrawal document"); + + let document_type = data_contract + .document_type_for_name(withdrawal::NAME) + .expect("expected to get document type"); + + setup_document( + &drive, + &document, + &data_contract, + document_type, + Some(&transaction), + ); + + let document = get_withdrawal_document_fixture( + &data_contract, + owner_id, + platform_value!({ + "amount": 1000u64, + "coreFeePerByte": 1u32, + "pooling": Pooling::Never as u8, + "outputScript": CoreScript::from_bytes((0..23).collect::>()), + "status": withdrawals_contract::WithdrawalStatus::POOLED, + "transactionIndex": 2u64, + }), + None, + platform_version.protocol_version, + ) + .expect("expected withdrawal document"); + + setup_document( + &drive, + &document, + &data_contract, + document_type, + Some(&transaction), + ); + + let documents = drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::QUEUED.into(), + DEFAULT_QUERY_LIMIT, + Some(&transaction), + platform_version, + ) + .expect("to fetch documents by status"); + + assert_eq!(documents.len(), 1); + + let documents = drive + .fetch_oldest_withdrawal_documents_by_status( + withdrawals_contract::WithdrawalStatus::POOLED.into(), + DEFAULT_QUERY_LIMIT, + Some(&transaction), + platform_version, + ) + .expect("to fetch documents by status"); + + assert_eq!(documents.len(), 1); + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/mod.rs new file mode 100644 index 00000000000..a3545ad5397 --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/mod.rs @@ -0,0 +1,45 @@ +mod v0; + +use crate::drive::identity::withdrawals::WithdrawalTransactionIndex; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use dpp::data_contracts::withdrawals_contract; +use dpp::document::Document; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Find withdrawal documents by status and transaction indices + pub fn find_withdrawal_documents_by_status_and_transaction_indices( + &self, + status: withdrawals_contract::WithdrawalStatus, + transaction_indices: &[WithdrawalTransactionIndex], + limit: u16, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .identity + .withdrawals + .document + .find_up_to_100_withdrawal_documents_by_status_and_transaction_indices + { + 0 => self.find_withdrawal_documents_by_status_and_transaction_indices_v0( + status, + transaction_indices, + limit, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "find_up_to_100_withdrawal_documents_by_status_and_transaction_indices" + .to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/v0/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/v0/mod.rs new file mode 100644 index 00000000000..ad7536faebf --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/v0/mod.rs @@ -0,0 +1,177 @@ +use crate::drive::document::query::QueryDocumentsOutcomeV0Methods; +use crate::drive::identity::withdrawals::WithdrawalTransactionIndex; +use crate::drive::Drive; +use crate::error::Error; +use crate::query::{DriveQuery, InternalClauses, OrderClause, WhereClause}; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contracts::withdrawals_contract; +use dpp::data_contracts::withdrawals_contract::v1::document_types::withdrawal; +use dpp::document::Document; +use dpp::platform_value::Value; +use grovedb::TransactionArg; +use indexmap::IndexMap; +use platform_version::version::PlatformVersion; +use std::collections::BTreeMap; + +impl Drive { + // TODO(withdrawals): Currently it queries only up to 100 documents. + // It works while we don't have pooling + + pub(super) fn find_withdrawal_documents_by_status_and_transaction_indices_v0( + &self, + status: withdrawals_contract::WithdrawalStatus, + transaction_indices: &[WithdrawalTransactionIndex], + limit: u16, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let mut where_clauses = BTreeMap::new(); + + where_clauses.insert( + withdrawal::properties::STATUS.to_string(), + WhereClause { + field: withdrawal::properties::STATUS.to_string(), + operator: crate::query::WhereOperator::Equal, + value: Value::U8(status as u8), + }, + ); + + where_clauses.insert( + withdrawal::properties::TRANSACTION_INDEX.to_string(), + WhereClause { + field: withdrawal::properties::TRANSACTION_INDEX.to_string(), + operator: crate::query::WhereOperator::In, + value: Value::Array( + transaction_indices + .iter() + .map(|index| Value::U64(*index)) + .collect::>(), + ), + }, + ); + + let mut order_by = IndexMap::new(); + + order_by.insert( + withdrawal::properties::TRANSACTION_INDEX.to_string(), + OrderClause { + field: withdrawal::properties::TRANSACTION_INDEX.to_string(), + ascending: true, + }, + ); + + let cache = self.cache.read().unwrap(); + + let document_type = cache + .system_data_contracts + .withdrawals + .document_type_for_name(withdrawal::NAME)?; + + let drive_query = DriveQuery { + contract: &cache.system_data_contracts.withdrawals, + document_type, + internal_clauses: InternalClauses { + primary_key_in_clause: None, + primary_key_equal_clause: None, + in_clause: None, + range_clause: None, + equal_clauses: where_clauses, + }, + offset: None, + limit: Some(limit), + order_by, + start_at: None, + start_at_included: false, + block_time_ms: None, + }; + + let outcome = self.query_documents( + drive_query, + None, + false, + transaction, + Some(platform_version.protocol_version), + )?; + + Ok(outcome.documents_owned()) + } +} + +#[cfg(test)] +mod tests { + use crate::drive::config::DEFAULT_QUERY_LIMIT; + use crate::drive::identity::withdrawals::WithdrawalTransactionIndex; + use crate::tests::helpers::setup::{ + setup_document, setup_drive_with_initial_state_structure, setup_system_data_contract, + }; + use dpp::data_contract::accessors::v0::DataContractV0Getters; + use dpp::identifier::Identifier; + use dpp::identity::core_script::CoreScript; + use dpp::platform_value::platform_value; + use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; + use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; + use dpp::tests::fixtures::get_withdrawal_document_fixture; + use dpp::version::PlatformVersion; + use dpp::withdrawal::Pooling; + + use super::*; + + #[test] + fn test_find_pooled_withdrawal_documents_by_transaction_index() { + let drive = setup_drive_with_initial_state_structure(); + + let transaction = drive.grove.start_transaction(); + + let platform_version = PlatformVersion::latest(); + + let data_contract = + load_system_data_contract(SystemDataContract::Withdrawals, platform_version) + .expect("to load system data contract"); + + setup_system_data_contract(&drive, &data_contract, Some(&transaction)); + + let owner_id = Identifier::new([1u8; 32]); + + let transaction_index: WithdrawalTransactionIndex = 1; + + let document = get_withdrawal_document_fixture( + &data_contract, + owner_id, + platform_value!({ + "amount": 1000u64, + "coreFeePerByte": 1u32, + "pooling": Pooling::Never as u8, + "outputScript": CoreScript::from_bytes((0..23).collect::>()), + "status": withdrawals_contract::WithdrawalStatus::POOLED as u8, + "transactionIndex": transaction_index, + }), + None, + platform_version.protocol_version, + ) + .expect("expected to get withdrawal document"); + + let document_type = data_contract + .document_type_for_name(withdrawal::NAME) + .expect("expected to get document type"); + + setup_document( + &drive, + &document, + &data_contract, + document_type, + Some(&transaction), + ); + + let found_document = drive + .find_withdrawal_documents_by_status_and_transaction_indices( + withdrawals_contract::WithdrawalStatus::POOLED, + &[transaction_index], + DEFAULT_QUERY_LIMIT, + Some(&transaction), + platform_version, + ) + .expect("to find document by it's transaction id"); + + assert_eq!(found_document.len(), 1); + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/document/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/document/mod.rs new file mode 100644 index 00000000000..0f3e6aec298 --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/document/mod.rs @@ -0,0 +1,4 @@ +/// This module dedicated for a versioned fetch_up_to_100_oldest_withdrawal_documents_by_status +pub mod fetch_oldest_withdrawal_documents_by_status; +/// This module dedicated for a versioned find_up_to_100_withdrawal_documents_by_status_and_transaction_indices +pub mod find_withdrawal_documents_by_status_and_transaction_indices; diff --git a/packages/rs-drive/src/drive/identity/withdrawals/documents.rs b/packages/rs-drive/src/drive/identity/withdrawals/documents.rs deleted file mode 100644 index 998e3c296fe..00000000000 --- a/packages/rs-drive/src/drive/identity/withdrawals/documents.rs +++ /dev/null @@ -1,369 +0,0 @@ -use std::collections::BTreeMap; - -use dpp::data_contract::accessors::v0::DataContractV0Getters; - -use dpp::document::Document; -use dpp::platform_value::Value; -use dpp::system_data_contracts::withdrawals_contract; -use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; -use dpp::version::PlatformVersion; -use grovedb::TransactionArg; -use indexmap::IndexMap; - -use crate::drive::document::query::QueryDocumentsOutcomeV0Methods; -use crate::{ - drive::Drive, - error::{drive::DriveError, Error}, - query::{DriveQuery, InternalClauses, OrderClause, WhereClause}, -}; - -impl Drive { - /// Fetch withdrawal documents by it's status - pub fn fetch_withdrawal_documents_by_status( - &self, - status: u8, - transaction: TransactionArg, - platform_version: &PlatformVersion, - ) -> Result, Error> { - let data_contract_id = withdrawals_contract::ID; - - let contract_fetch_info = self - .get_contract_with_fetch_info_and_fee( - data_contract_id.to_buffer(), - None, - true, - transaction, - platform_version, - )? - .1 - .ok_or_else(|| { - Error::Drive(DriveError::CorruptedCodeExecution( - "Can't fetch data contract", - )) - })?; - - let document_type = contract_fetch_info - .contract - .document_type_for_name(withdrawal::NAME)?; - - let mut where_clauses = BTreeMap::new(); - - //todo: make this lazy loaded or const - where_clauses.insert( - withdrawal::properties::STATUS.to_string(), - WhereClause { - field: withdrawal::properties::STATUS.to_string(), - operator: crate::query::WhereOperator::Equal, - value: Value::U8(status), - }, - ); - - let mut order_by = IndexMap::new(); - - order_by.insert( - withdrawal::properties::UPDATED_AT.to_string(), - OrderClause { - field: withdrawal::properties::UPDATED_AT.to_string(), - ascending: true, - }, - ); - - let drive_query = DriveQuery { - contract: &contract_fetch_info.contract, - document_type, - internal_clauses: InternalClauses { - primary_key_in_clause: None, - primary_key_equal_clause: None, - in_clause: None, - range_clause: None, - equal_clauses: where_clauses, - }, - offset: None, - limit: Some(100), - order_by, - start_at: None, - start_at_included: false, - block_time_ms: None, - }; - - let outcome = self.query_documents( - drive_query, - None, - false, - transaction, - Some(platform_version.protocol_version), - )?; - - Ok(outcome.documents_owned()) - } - - /// Find one document by it's transactionId field - pub fn find_withdrawal_document_by_transaction_id( - &self, - original_transaction_id: &[u8], - transaction: TransactionArg, - platform_version: &PlatformVersion, - ) -> Result { - let data_contract_id = withdrawals_contract::ID; - - let contract_fetch_info = self - .get_contract_with_fetch_info_and_fee( - data_contract_id.to_buffer(), - None, - true, - transaction, - platform_version, - )? - .1 - .ok_or_else(|| { - Error::Drive(DriveError::CorruptedCodeExecution( - "Can't fetch data contract", - )) - })?; - - let document_type = contract_fetch_info - .contract - .document_type_for_name(withdrawal::NAME)?; - - let mut where_clauses = BTreeMap::new(); - - where_clauses.insert( - withdrawal::properties::TRANSACTION_ID.to_string(), - WhereClause { - field: withdrawal::properties::TRANSACTION_ID.to_string(), - operator: crate::query::WhereOperator::Equal, - value: Value::Bytes(original_transaction_id.to_vec()), - }, - ); - - where_clauses.insert( - withdrawal::properties::STATUS.to_string(), - WhereClause { - field: withdrawal::properties::STATUS.to_string(), - operator: crate::query::WhereOperator::Equal, - value: Value::U8(withdrawals_contract::WithdrawalStatus::POOLED as u8), - }, - ); - - let drive_query = DriveQuery { - contract: &contract_fetch_info.contract, - document_type, - internal_clauses: InternalClauses { - primary_key_in_clause: None, - primary_key_equal_clause: None, - in_clause: None, - range_clause: None, - equal_clauses: where_clauses, - }, - offset: None, - limit: Some(1), - order_by: IndexMap::new(), - start_at: None, - start_at_included: false, - block_time_ms: None, - }; - - let outcome = self.query_documents( - drive_query, - None, - false, - transaction, - Some(platform_version.protocol_version), - )?; - - let document = outcome.documents_owned().pop().ok_or(Error::Drive( - DriveError::CorruptedDriveState("document was not found by transactionId".to_string()), - ))?; - - Ok(document) - } -} - -#[cfg(test)] -mod tests { - use dpp::prelude::Identifier; - use dpp::system_data_contracts::withdrawals_contract; - use dpp::tests::fixtures::get_withdrawal_document_fixture; - - use crate::tests::helpers::setup::setup_drive_with_initial_state_structure; - use crate::tests::helpers::setup::{setup_document, setup_system_data_contract}; - - mod fetch_withdrawal_documents_by_status { - use super::*; - use dpp::data_contract::accessors::v0::DataContractV0Getters; - use dpp::identity::core_script::CoreScript; - use dpp::platform_value::platform_value; - use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; - use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; - use dpp::version::PlatformVersion; - use dpp::withdrawal::Pooling; - - #[test] - fn test_return_list_of_documents() { - let drive = setup_drive_with_initial_state_structure(); - - let transaction = drive.grove.start_transaction(); - - let platform_version = PlatformVersion::latest(); - - let data_contract = - load_system_data_contract(SystemDataContract::Withdrawals, platform_version) - .expect("to load system data contract"); - - setup_system_data_contract(&drive, &data_contract, Some(&transaction)); - - let documents = drive - .fetch_withdrawal_documents_by_status( - withdrawals_contract::WithdrawalStatus::QUEUED.into(), - Some(&transaction), - platform_version, - ) - .expect("to fetch documents by status"); - - assert_eq!(documents.len(), 0); - - let owner_id = Identifier::new([1u8; 32]); - - let document = get_withdrawal_document_fixture( - &data_contract, - owner_id, - platform_value!({ - "amount": 1000u64, - "coreFeePerByte": 1u32, - "pooling": Pooling::Never as u8, - "outputScript": CoreScript::from_bytes((0..23).collect::>()), - "status": withdrawals_contract::WithdrawalStatus::QUEUED as u8, - "transactionIndex": 1u64, - }), - None, - platform_version.protocol_version, - ) - .expect("expected withdrawal document"); - - let document_type = data_contract - .document_type_for_name(withdrawal::NAME) - .expect("expected to get document type"); - - setup_document( - &drive, - &document, - &data_contract, - document_type, - Some(&transaction), - ); - - let document = get_withdrawal_document_fixture( - &data_contract, - owner_id, - platform_value!({ - "amount": 1000u64, - "coreFeePerByte": 1u32, - "pooling": Pooling::Never as u8, - "outputScript": CoreScript::from_bytes((0..23).collect::>()), - "status": withdrawals_contract::WithdrawalStatus::POOLED, - "transactionIndex": 2u64, - }), - None, - platform_version.protocol_version, - ) - .expect("expected withdrawal document"); - - setup_document( - &drive, - &document, - &data_contract, - document_type, - Some(&transaction), - ); - - let documents = drive - .fetch_withdrawal_documents_by_status( - withdrawals_contract::WithdrawalStatus::QUEUED.into(), - Some(&transaction), - platform_version, - ) - .expect("to fetch documents by status"); - - assert_eq!(documents.len(), 1); - - let documents = drive - .fetch_withdrawal_documents_by_status( - withdrawals_contract::WithdrawalStatus::POOLED.into(), - Some(&transaction), - platform_version, - ) - .expect("to fetch documents by status"); - - assert_eq!(documents.len(), 1); - } - } - - mod find_document_by_transaction_id { - use dpp::data_contract::accessors::v0::DataContractV0Getters; - use dpp::document::DocumentV0Getters; - use dpp::identity::core_script::CoreScript; - use dpp::platform_value::{platform_value, Bytes32}; - use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; - use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract}; - use dpp::version::PlatformVersion; - use dpp::withdrawal::Pooling; - - use super::*; - - #[test] - fn test_find_document_by_transaction_id() { - let drive = setup_drive_with_initial_state_structure(); - - let transaction = drive.grove.start_transaction(); - - let platform_version = PlatformVersion::latest(); - - let data_contract = - load_system_data_contract(SystemDataContract::Withdrawals, platform_version) - .expect("to load system data contract"); - - setup_system_data_contract(&drive, &data_contract, Some(&transaction)); - - let owner_id = Identifier::new([1u8; 32]); - - let document = get_withdrawal_document_fixture( - &data_contract, - owner_id, - platform_value!({ - "amount": 1000u64, - "coreFeePerByte": 1u32, - "pooling": Pooling::Never as u8, - "outputScript": CoreScript::from_bytes((0..23).collect::>()), - "status": withdrawals_contract::WithdrawalStatus::POOLED as u8, - "transactionIndex": 1u64, - "transactionId": Bytes32::default(), - }), - None, - platform_version.protocol_version, - ) - .expect("expected to get withdrawal document"); - - let document_type = data_contract - .document_type_for_name(withdrawal::NAME) - .expect("expected to get document type"); - - setup_document( - &drive, - &document, - &data_contract, - document_type, - Some(&transaction), - ); - - let found_document = drive - .find_withdrawal_document_by_transaction_id( - Bytes32::default().as_slice(), - Some(&transaction), - platform_version, - ) - .expect("to find document by it's transaction id"); - - assert_eq!(found_document.id().to_vec(), document.id().to_vec()); - } - } -} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/mod.rs index f5285349513..57f26a5b5ef 100644 --- a/packages/rs-drive/src/drive/identity/withdrawals/mod.rs +++ b/packages/rs-drive/src/drive/identity/withdrawals/mod.rs @@ -1,11 +1,12 @@ -/// Functions related to updating of a withdrawal status -pub mod documents; +/// Functions related to withdrawal documents +pub mod document; + /// Functions and constants related to GroveDB paths pub mod paths; -/// Functions related to withdrawal queue -pub mod queue; -/// Functions related to transaction index counter -pub mod transaction_index; +/// Functions related to withdrawal transactions +pub mod transaction; -/// Simple type alias for withdrawal transaction with it's id -pub type WithdrawalTransactionIdAndBytes = (Vec, Vec); +/// Simple type alias for withdrawal transaction with it's index +pub type WithdrawalTransactionIndexAndBytes = (WithdrawalTransactionIndex, Vec); +/// Transaction index type +pub type WithdrawalTransactionIndex = u64; diff --git a/packages/rs-drive/src/drive/identity/withdrawals/paths.rs b/packages/rs-drive/src/drive/identity/withdrawals/paths.rs index b4e53314a97..6f73afe5d87 100644 --- a/packages/rs-drive/src/drive/identity/withdrawals/paths.rs +++ b/packages/rs-drive/src/drive/identity/withdrawals/paths.rs @@ -3,29 +3,22 @@ use grovedb::Element; use crate::drive::batch::grovedb_op_batch::GroveDbOpBatchV0Methods; use crate::drive::{batch::GroveDbOpBatch, RootTree}; -/// constant id for transaction counter -pub const WITHDRAWAL_TRANSACTIONS_COUNTER_ID: [u8; 1] = [0]; +/// constant key for transaction counter +pub const WITHDRAWAL_TRANSACTIONS_NEXT_INDEX_KEY: [u8; 1] = [0]; /// constant id for subtree containing transactions queue -pub const WITHDRAWAL_TRANSACTIONS_QUEUE_ID: [u8; 1] = [1]; -/// constant id for subtree containing expired transaction ids -pub const WITHDRAWAL_TRANSACTIONS_EXPIRED_IDS: [u8; 1] = [2]; +pub const WITHDRAWAL_TRANSACTIONS_QUEUE_KEY: [u8; 1] = [1]; /// Add operations for creating initial withdrawal state structure pub fn add_initial_withdrawal_state_structure_operations(batch: &mut GroveDbOpBatch) { batch.add_insert( vec![vec![RootTree::WithdrawalTransactions as u8]], - WITHDRAWAL_TRANSACTIONS_COUNTER_ID.to_vec(), + WITHDRAWAL_TRANSACTIONS_NEXT_INDEX_KEY.to_vec(), Element::Item(0u64.to_be_bytes().to_vec(), None), ); batch.add_insert_empty_tree( vec![vec![RootTree::WithdrawalTransactions as u8]], - WITHDRAWAL_TRANSACTIONS_QUEUE_ID.to_vec(), - ); - - batch.add_insert_empty_tree( - vec![vec![RootTree::WithdrawalTransactions as u8]], - WITHDRAWAL_TRANSACTIONS_EXPIRED_IDS.to_vec(), + WITHDRAWAL_TRANSACTIONS_QUEUE_KEY.to_vec(), ); } @@ -43,7 +36,7 @@ pub fn get_withdrawal_root_path() -> [&'static [u8]; 1] { pub fn get_withdrawal_transactions_queue_path_vec() -> Vec> { vec![ vec![RootTree::WithdrawalTransactions as u8], - WITHDRAWAL_TRANSACTIONS_QUEUE_ID.to_vec(), + WITHDRAWAL_TRANSACTIONS_QUEUE_KEY.to_vec(), ] } @@ -51,22 +44,6 @@ pub fn get_withdrawal_transactions_queue_path_vec() -> Vec> { pub fn get_withdrawal_transactions_queue_path() -> [&'static [u8]; 2] { [ Into::<&[u8; 1]>::into(RootTree::WithdrawalTransactions), - &WITHDRAWAL_TRANSACTIONS_QUEUE_ID, - ] -} - -/// Helper function to get expired ids path as Vec -pub fn get_withdrawal_transactions_expired_ids_path_vec() -> Vec> { - vec![ - vec![RootTree::WithdrawalTransactions as u8], - WITHDRAWAL_TRANSACTIONS_EXPIRED_IDS.to_vec(), - ] -} - -/// Helper function to get expired ids path as [u8] -pub fn get_withdrawal_transactions_expired_ids_path() -> [&'static [u8]; 2] { - [ - Into::<&[u8; 1]>::into(RootTree::WithdrawalTransactions), - &WITHDRAWAL_TRANSACTIONS_EXPIRED_IDS, + &WITHDRAWAL_TRANSACTIONS_QUEUE_KEY, ] } diff --git a/packages/rs-drive/src/drive/identity/withdrawals/queue.rs b/packages/rs-drive/src/drive/identity/withdrawals/queue.rs deleted file mode 100644 index 17c00a5c9be..00000000000 --- a/packages/rs-drive/src/drive/identity/withdrawals/queue.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::ops::RangeFull; - -use grovedb::{ - query_result_type::QueryResultType, Element, PathQuery, Query, QueryItem, SizedQuery, - TransactionArg, -}; - -use crate::drive::identity::withdrawals::WithdrawalTransactionIdAndBytes; -use crate::{ - drive::{ - batch::{drive_op_batch::WithdrawalOperationType, DriveOperation}, - Drive, - }, - error::{drive::DriveError, Error}, -}; - -use super::paths::get_withdrawal_transactions_queue_path_vec; - -impl Drive { - /// Add insert operations for withdrawal transactions to the batch - pub fn add_enqueue_withdrawal_transaction_operations<'a>( - &self, - withdrawals: &'a [WithdrawalTransactionIdAndBytes], - drive_operation_types: &mut Vec>, - ) { - if !withdrawals.is_empty() { - drive_operation_types.push(DriveOperation::WithdrawalOperation( - WithdrawalOperationType::InsertTransactions { - withdrawal_transactions: withdrawals, - }, - )); - } - } - - /// Get specified amount of withdrawal transactions from the DB - pub fn dequeue_withdrawal_transactions( - &self, - max_amount: u16, - transaction: TransactionArg, - drive_operation_types: &mut Vec, - ) -> Result, Error> { - let mut query = Query::new(); - - query.insert_item(QueryItem::RangeFull(RangeFull)); - - let path_query = PathQuery { - path: get_withdrawal_transactions_queue_path_vec(), - query: SizedQuery { - query, - limit: Some(max_amount), - offset: None, - }, - }; - - let result_items = self - .grove - .query_raw( - &path_query, - transaction.is_some(), - true, - QueryResultType::QueryKeyElementPairResultType, - transaction, - ) - .unwrap() - .map_err(Error::GroveDB)? - .0 - .to_key_elements(); - - let withdrawals = result_items - .into_iter() - .map(|(id, element)| match element { - Element::Item(bytes, _) => Ok((id, bytes)), - _ => Err(Error::Drive(DriveError::CorruptedWithdrawalNotItem( - "withdrawal is not an item", - ))), - }) - .collect::, Vec)>, Error>>()?; - - if !withdrawals.is_empty() { - for (id, _) in withdrawals.iter() { - drive_operation_types.push(DriveOperation::WithdrawalOperation( - WithdrawalOperationType::DeleteWithdrawalTransaction { id: id.clone() }, - )); - } - } - - Ok(withdrawals) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - drive::batch::DriveOperation, - tests::helpers::setup::setup_drive_with_initial_state_structure, - }; - use dpp::block::block_info::BlockInfo; - use dpp::block::epoch::Epoch; - use dpp::version::PlatformVersion; - - #[test] - fn test_enqueue_and_dequeue() { - let drive = setup_drive_with_initial_state_structure(); - - let platform_version = PlatformVersion::latest(); - - let transaction = drive.grove.start_transaction(); - - let withdrawals: Vec<(Vec, Vec)> = (0..17) - .map(|i: u8| (i.to_be_bytes().to_vec(), vec![i; 32])) - .collect(); - - let block_info = BlockInfo { - time_ms: 1, - height: 1, - core_height: 1, - epoch: Epoch::new(1).unwrap(), - }; - - let mut drive_operations: Vec = vec![]; - - drive.add_enqueue_withdrawal_transaction_operations(&withdrawals, &mut drive_operations); - - drive - .apply_drive_operations( - drive_operations, - true, - &block_info, - Some(&transaction), - platform_version, - ) - .expect("to apply batch"); - - let mut drive_operations: Vec = vec![]; - - let withdrawals = drive - .dequeue_withdrawal_transactions(16, Some(&transaction), &mut drive_operations) - .expect("to dequeue withdrawals"); - - drive - .apply_drive_operations( - drive_operations, - true, - &block_info, - Some(&transaction), - platform_version, - ) - .expect("to apply batch"); - - assert_eq!(withdrawals.len(), 16); - - let mut drive_operations: Vec = vec![]; - - let withdrawals = drive - .dequeue_withdrawal_transactions(16, Some(&transaction), &mut drive_operations) - .expect("to dequeue withdrawals"); - - drive - .apply_drive_operations( - drive_operations, - true, - &block_info, - Some(&transaction), - platform_version, - ) - .expect("to apply batch"); - - assert_eq!(withdrawals.len(), 1); - - let mut drive_operations: Vec = vec![]; - - drive - .dequeue_withdrawal_transactions(16, Some(&transaction), &mut drive_operations) - .expect("to dequeue withdrawals"); - - assert_eq!(drive_operations.len(), 0); - } -} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/add_update_next_withdrawal_transaction_index_operation/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/add_update_next_withdrawal_transaction_index_operation/mod.rs new file mode 100644 index 00000000000..9390683d27b --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/add_update_next_withdrawal_transaction_index_operation/mod.rs @@ -0,0 +1,42 @@ +mod v0; + +use crate::drive::batch::DriveOperation; +use crate::drive::identity::withdrawals::WithdrawalTransactionIndex; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Add next transaction index increment operation to the batch + pub fn add_update_next_withdrawal_transaction_index_operation( + &self, + index: WithdrawalTransactionIndex, + drive_operation_types: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + match platform_version + .drive + .methods + .identity + .withdrawals + .transaction + .index + .add_update_next_withdrawal_transaction_index_operation + { + 0 => { + self.add_update_next_withdrawal_transaction_index_operation_v0( + index, + drive_operation_types, + ); + + Ok(()) + } + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "add_update_next_withdrawal_transaction_index_operation".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/add_update_next_withdrawal_transaction_index_operation/v0/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/add_update_next_withdrawal_transaction_index_operation/v0/mod.rs new file mode 100644 index 00000000000..eb0bb01f20a --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/add_update_next_withdrawal_transaction_index_operation/v0/mod.rs @@ -0,0 +1,16 @@ +use crate::drive::batch::drive_op_batch::WithdrawalOperationType; +use crate::drive::batch::DriveOperation; +use crate::drive::identity::withdrawals::WithdrawalTransactionIndex; +use crate::drive::Drive; + +impl Drive { + pub(super) fn add_update_next_withdrawal_transaction_index_operation_v0( + &self, + index: WithdrawalTransactionIndex, + drive_operation_types: &mut Vec, + ) { + drive_operation_types.push(DriveOperation::WithdrawalOperation( + WithdrawalOperationType::UpdateIndexCounter { index }, + )); + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/fetch_next_withdrawal_transaction_index/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/fetch_next_withdrawal_transaction_index/mod.rs new file mode 100644 index 00000000000..50a84c82f39 --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/fetch_next_withdrawal_transaction_index/mod.rs @@ -0,0 +1,34 @@ +use crate::drive::identity::withdrawals::WithdrawalTransactionIndex; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +mod v0; + +impl Drive { + /// Fetches next withdrawal transaction index + pub fn fetch_next_withdrawal_transaction_index( + &self, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .drive + .methods + .identity + .withdrawals + .transaction + .index + .fetch_next_withdrawal_transaction_index + { + 0 => self.fetch_next_withdrawal_transaction_index_v0(transaction), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_next_withdrawal_transaction_index".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/fetch_next_withdrawal_transaction_index/v0/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/fetch_next_withdrawal_transaction_index/v0/mod.rs new file mode 100644 index 00000000000..56a9f149593 --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/fetch_next_withdrawal_transaction_index/v0/mod.rs @@ -0,0 +1,40 @@ +use crate::drive::identity::withdrawals::paths::WITHDRAWAL_TRANSACTIONS_NEXT_INDEX_KEY; +use crate::drive::identity::withdrawals::WithdrawalTransactionIndex; +use crate::drive::{Drive, RootTree}; +use crate::error::drive::DriveError; +use crate::error::Error; +use grovedb::{Element, TransactionArg}; + +impl Drive { + pub(super) fn fetch_next_withdrawal_transaction_index_v0( + &self, + transaction: TransactionArg, + ) -> Result { + let element = self + .grove + .get( + &[Into::<&[u8; 1]>::into(RootTree::WithdrawalTransactions).as_slice()], + &WITHDRAWAL_TRANSACTIONS_NEXT_INDEX_KEY, + transaction, + ) + .unwrap() + .map_err(Error::GroveDB)?; + + let Element::Item(counter_bytes, _) = element else { + return Err(Error::Drive( + DriveError::CorruptedWithdrawalTransactionsCounterNotItem( + "withdrawal transactions counter must be an item", + ), + )); + }; + + let counter = + WithdrawalTransactionIndex::from_be_bytes(counter_bytes.try_into().map_err(|_| { + DriveError::CorruptedWithdrawalTransactionsCounterInvalidLength( + "withdrawal transactions counter must be an u64", + ) + })?); + + Ok(counter) + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/mod.rs new file mode 100644 index 00000000000..d1882c72d79 --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/index/mod.rs @@ -0,0 +1,72 @@ +/// This module dedicated for a versioned add_update_next_withdrawal_transaction_index_operation +pub mod add_update_next_withdrawal_transaction_index_operation; +/// This module dedicated for a versioned fetch_next_withdrawal_transaction_index +pub mod fetch_next_withdrawal_transaction_index; + +#[cfg(test)] +mod tests { + use dpp::block::block_info::BlockInfo; + use dpp::block::epoch::Epoch; + use dpp::version::PlatformVersion; + + use crate::tests::helpers::setup::setup_drive_with_initial_state_structure; + + #[test] + fn test_next_withdrawal_transaction_index() { + let drive = setup_drive_with_initial_state_structure(); + + let platform_version = PlatformVersion::latest(); + + let transaction = drive.grove.start_transaction(); + + let block_info = BlockInfo { + time_ms: 1, + height: 1, + core_height: 1, + epoch: Epoch::new(1).unwrap(), + }; + + let mut batch = vec![]; + + let counter: u64 = 42; + + drive + .add_update_next_withdrawal_transaction_index_operation( + counter, + &mut batch, + platform_version, + ) + .expect("to add update next withdrawal transaction index operation"); + + drive + .apply_drive_operations( + batch, + true, + &block_info, + Some(&transaction), + platform_version, + ) + .expect("to apply drive ops"); + + let stored_counter = drive + .fetch_next_withdrawal_transaction_index(Some(&transaction), platform_version) + .expect("to withdraw counter"); + + assert_eq!(stored_counter, counter); + } + + #[test] + fn test_initial_withdrawal_transaction_index() { + let drive = setup_drive_with_initial_state_structure(); + + let platform_version = PlatformVersion::latest(); + + let transaction = drive.grove.start_transaction(); + + let stored_counter = drive + .fetch_next_withdrawal_transaction_index(Some(&transaction), platform_version) + .expect("to withdraw counter"); + + assert_eq!(stored_counter, 0); + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/mod.rs new file mode 100644 index 00000000000..03b9ea47d8f --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/mod.rs @@ -0,0 +1,5 @@ +/// Functions related to withdrawal queue +pub mod queue; + +/// Functions related to transaction index counter +pub mod index; diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/mod.rs new file mode 100644 index 00000000000..a48f4f45cef --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/mod.rs @@ -0,0 +1,42 @@ +use crate::drive::batch::DriveOperation; +use crate::drive::identity::withdrawals::WithdrawalTransactionIndexAndBytes; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use platform_version::version::PlatformVersion; + +mod v0; + +impl Drive { + /// Add insert operations for withdrawal transactions to the batch + pub fn add_enqueue_untied_withdrawal_transaction_operations( + &self, + withdrawal_transactions: Vec, + drive_operation_types: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + match platform_version + .drive + .methods + .identity + .withdrawals + .transaction + .queue + .add_enqueue_untied_withdrawal_transaction_operations + { + 0 => { + self.add_enqueue_untied_withdrawal_transaction_operations_v0( + withdrawal_transactions, + drive_operation_types, + ); + + Ok(()) + } + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "add_enqueue_untied_withdrawal_transaction_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/v0/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/v0/mod.rs new file mode 100644 index 00000000000..fb25f044908 --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/v0/mod.rs @@ -0,0 +1,20 @@ +use crate::drive::batch::drive_op_batch::WithdrawalOperationType; +use crate::drive::batch::DriveOperation; +use crate::drive::identity::withdrawals::WithdrawalTransactionIndexAndBytes; +use crate::drive::Drive; + +impl Drive { + pub(super) fn add_enqueue_untied_withdrawal_transaction_operations_v0( + &self, + withdrawal_transactions: Vec, + drive_operation_types: &mut Vec, + ) { + if !withdrawal_transactions.is_empty() { + drive_operation_types.push(DriveOperation::WithdrawalOperation( + WithdrawalOperationType::InsertTransactions { + withdrawal_transactions, + }, + )); + } + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/dequeue_untied_withdrawal_transactions/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/dequeue_untied_withdrawal_transactions/mod.rs new file mode 100644 index 00000000000..2aa39d18a15 --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/dequeue_untied_withdrawal_transactions/mod.rs @@ -0,0 +1,41 @@ +mod v0; + +use crate::drive::batch::DriveOperation; +use crate::drive::identity::withdrawals::WithdrawalTransactionIndexAndBytes; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Get specified amount of withdrawal transactions from the DB + pub fn dequeue_untied_withdrawal_transactions( + &self, + limit: u16, + transaction: TransactionArg, + drive_operation_types: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .identity + .withdrawals + .transaction + .queue + .dequeue_untied_withdrawal_transactions + { + 0 => self.dequeue_untied_withdrawal_transactions_v0( + limit, + transaction, + drive_operation_types, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "dequeue_untied_withdrawal_transactions".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/dequeue_untied_withdrawal_transactions/v0/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/dequeue_untied_withdrawal_transactions/v0/mod.rs new file mode 100644 index 00000000000..6de788887d6 --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/dequeue_untied_withdrawal_transactions/v0/mod.rs @@ -0,0 +1,80 @@ +use crate::drive::batch::drive_op_batch::WithdrawalOperationType; +use crate::drive::batch::DriveOperation; +use crate::drive::identity::withdrawals::paths::get_withdrawal_transactions_queue_path_vec; +use crate::drive::identity::withdrawals::{ + WithdrawalTransactionIndex, WithdrawalTransactionIndexAndBytes, +}; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::query::{Query, QueryItem}; +use grovedb::query_result_type::QueryResultType; +use grovedb::{Element, PathQuery, SizedQuery, TransactionArg}; +use std::convert::TryInto; +use std::ops::RangeFull; + +impl Drive { + pub(super) fn dequeue_untied_withdrawal_transactions_v0( + &self, + limit: u16, + transaction: TransactionArg, + drive_operation_types: &mut Vec, + ) -> Result, Error> { + let mut query = Query::new(); + + query.insert_item(QueryItem::RangeFull(RangeFull)); + + let path_query = PathQuery { + path: get_withdrawal_transactions_queue_path_vec(), + query: SizedQuery { + query, + limit: Some(limit), + offset: None, + }, + }; + + let result_items = self + .grove + .query_raw( + &path_query, + transaction.is_some(), + true, + QueryResultType::QueryKeyElementPairResultType, + transaction, + ) + .unwrap() + .map_err(Error::GroveDB)? + .0 + .to_key_elements(); + + let withdrawal_transactions = result_items + .into_iter() + .map(|(index_bytes, element)| match element { + Element::Item(bytes, _) => { + let index = WithdrawalTransactionIndex::from_be_bytes( + index_bytes.try_into().map_err(|_| { + Error::Drive(DriveError::CorruptedSerialization(String::from( + "withdrawal index must be u64", + ))) + })?, + ); + + Ok((index, bytes)) + } + _ => Err(Error::Drive(DriveError::CorruptedWithdrawalNotItem( + "withdrawal is not an item", + ))), + }) + .collect::, Error>>()?; + + if !withdrawal_transactions.is_empty() { + for (index, _) in withdrawal_transactions.iter() { + drive_operation_types.push(DriveOperation::WithdrawalOperation( + WithdrawalOperationType::DeleteWithdrawalTransaction { index: *index }, + )); + } + } + + Ok(withdrawal_transactions) + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/mod.rs new file mode 100644 index 00000000000..b404dc90883 --- /dev/null +++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/mod.rs @@ -0,0 +1,117 @@ +/// This module dedicated for a versioned add_enqueue_untied_withdrawal_transaction_operations +pub mod add_enqueue_untied_withdrawal_transaction_operations; +/// This module dedicated for a versioned dequeue_untied_withdrawal_transactions +pub mod dequeue_untied_withdrawal_transactions; + +#[cfg(test)] +mod tests { + use crate::drive::identity::withdrawals::{ + WithdrawalTransactionIndex, WithdrawalTransactionIndexAndBytes, + }; + use crate::{ + drive::batch::DriveOperation, + tests::helpers::setup::setup_drive_with_initial_state_structure, + }; + use dpp::block::block_info::BlockInfo; + use dpp::block::epoch::Epoch; + use dpp::version::PlatformVersion; + + #[test] + fn test_enqueue_and_dequeue() { + let drive = setup_drive_with_initial_state_structure(); + + let platform_version = PlatformVersion::latest(); + + let transaction = drive.grove.start_transaction(); + + let withdrawals: Vec = (0..17) + .map(|i: u8| (i as WithdrawalTransactionIndex, vec![i; 32])) + .collect(); + + let block_info = BlockInfo { + time_ms: 1, + height: 1, + core_height: 1, + epoch: Epoch::new(1).unwrap(), + }; + + let mut drive_operations: Vec = vec![]; + + drive + .add_enqueue_untied_withdrawal_transaction_operations( + withdrawals, + &mut drive_operations, + platform_version, + ) + .expect("to add enqueue untied withdrawal transaction operations"); + + drive + .apply_drive_operations( + drive_operations, + true, + &block_info, + Some(&transaction), + platform_version, + ) + .expect("to apply batch"); + + let mut drive_operations: Vec = vec![]; + + let withdrawals = drive + .dequeue_untied_withdrawal_transactions( + 16, + Some(&transaction), + &mut drive_operations, + platform_version, + ) + .expect("to dequeue withdrawals"); + + drive + .apply_drive_operations( + drive_operations, + true, + &block_info, + Some(&transaction), + platform_version, + ) + .expect("to apply batch"); + + assert_eq!(withdrawals.len(), 16); + + let mut drive_operations: Vec = vec![]; + + let withdrawals = drive + .dequeue_untied_withdrawal_transactions( + 16, + Some(&transaction), + &mut drive_operations, + platform_version, + ) + .expect("to dequeue withdrawals"); + + drive + .apply_drive_operations( + drive_operations, + true, + &block_info, + Some(&transaction), + platform_version, + ) + .expect("to apply batch"); + + assert_eq!(withdrawals.len(), 1); + + let mut drive_operations: Vec = vec![]; + + drive + .dequeue_untied_withdrawal_transactions( + 16, + Some(&transaction), + &mut drive_operations, + platform_version, + ) + .expect("to dequeue withdrawals"); + + assert_eq!(drive_operations.len(), 0); + } +} diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction_index.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction_index.rs deleted file mode 100644 index f87bc092649..00000000000 --- a/packages/rs-drive/src/drive/identity/withdrawals/transaction_index.rs +++ /dev/null @@ -1,239 +0,0 @@ -use grovedb::{ - query_result_type::{QueryResultElement, QueryResultType}, - Element, PathQuery, Query, SizedQuery, TransactionArg, -}; - -use crate::{ - drive::{ - batch::{drive_op_batch::WithdrawalOperationType, DriveOperation}, - Drive, RootTree, - }, - error::{drive::DriveError, Error}, -}; - -use super::paths::{ - get_withdrawal_transactions_expired_ids_path_vec, WITHDRAWAL_TRANSACTIONS_COUNTER_ID, -}; - -impl Drive { - /// Get and remove latest withdrawal index in a queue - pub fn fetch_and_remove_latest_withdrawal_transaction_index_operations( - &self, - drive_operation_types: &mut Vec, - transaction: TransactionArg, - ) -> Result { - let mut inner_query = Query::new(); - - inner_query.insert_all(); - - let expired_index_query = PathQuery::new( - get_withdrawal_transactions_expired_ids_path_vec(), - SizedQuery::new(inner_query, Some(1), None), - ); - - let (expired_index_elements, _) = self - .grove - .query_raw( - &expired_index_query, - transaction.is_some(), - true, - QueryResultType::QueryKeyElementPairResultType, - transaction, - ) - .unwrap()?; - - if !expired_index_elements.is_empty() { - let expired_index_element_pair = expired_index_elements.elements.get(0).unwrap(); - - if let QueryResultElement::KeyElementPairResultItem((key, _)) = - expired_index_element_pair - { - drive_operation_types.push(DriveOperation::WithdrawalOperation( - WithdrawalOperationType::DeleteExpiredIndex { key: key.clone() }, - )); - - let index = u64::from_be_bytes(key.clone().try_into().map_err(|_| { - Error::Drive(DriveError::CorruptedCodeExecution( - "Transaction index has wrong length", - )) - })?); - - return Ok(index); - } - } - - let result = self - .grove - .get( - &[Into::<&[u8; 1]>::into(RootTree::WithdrawalTransactions).as_slice()], - &WITHDRAWAL_TRANSACTIONS_COUNTER_ID, - transaction, - ) - .unwrap() - .map_err(Error::GroveDB); - - if let Err(Error::GroveDB(grovedb::Error::PathKeyNotFound(_))) = &result { - return Ok(0); - } - - let element = result?; - - if let Element::Item(counter_bytes, _) = element { - let counter = u64::from_be_bytes(counter_bytes.try_into().map_err(|_| { - DriveError::CorruptedWithdrawalTransactionsCounterInvalidLength( - "withdrawal transactions counter must be an u64", - ) - })?); - - Ok(counter) - } else { - Err(Error::Drive( - DriveError::CorruptedWithdrawalTransactionsCounterNotItem( - "withdrawal transactions counter must be an item", - ), - )) - } - } - - /// Add counter update operations to the batch - pub fn add_update_withdrawal_index_counter_operation( - &self, - value: u64, - drive_operation_types: &mut Vec, - ) { - drive_operation_types.push(DriveOperation::WithdrawalOperation( - WithdrawalOperationType::UpdateIndexCounter { index: value }, - )); - } - - /// Add insert expired counter operations - pub fn add_insert_expired_index_operation( - &self, - transaction_index: u64, - drive_operation_types: &mut Vec, - ) { - drive_operation_types.push(DriveOperation::WithdrawalOperation( - WithdrawalOperationType::InsertExpiredIndex { - index: transaction_index, - }, - )); - } -} - -#[cfg(test)] -mod tests { - use dpp::block::block_info::BlockInfo; - use dpp::block::epoch::Epoch; - use dpp::version::PlatformVersion; - use grovedb::Element; - - use crate::{ - drive::identity::withdrawals::paths::get_withdrawal_transactions_expired_ids_path, - tests::helpers::setup::setup_drive_with_initial_state_structure, - }; - - #[test] - fn test_withdrawal_transaction_counter() { - let drive = setup_drive_with_initial_state_structure(); - - let platform_version = PlatformVersion::latest(); - - let transaction = drive.grove.start_transaction(); - - let block_info = BlockInfo { - time_ms: 1, - height: 1, - core_height: 1, - epoch: Epoch::new(1).unwrap(), - }; - - let mut batch = vec![]; - - let counter: u64 = 42; - - drive.add_update_withdrawal_index_counter_operation(counter, &mut batch); - - drive - .apply_drive_operations( - batch, - true, - &block_info, - Some(&transaction), - platform_version, - ) - .expect("to apply drive ops"); - - let mut batch = vec![]; - - let stored_counter = drive - .fetch_and_remove_latest_withdrawal_transaction_index_operations( - &mut batch, - Some(&transaction), - ) - .expect("to withdraw counter"); - - drive - .apply_drive_operations( - batch, - true, - &block_info, - Some(&transaction), - platform_version, - ) - .expect("to apply drive ops"); - - assert_eq!(stored_counter, counter); - } - - #[test] - fn test_returns_0_if_empty() { - let drive = setup_drive_with_initial_state_structure(); - - let transaction = drive.grove.start_transaction(); - - let mut batch = vec![]; - - let stored_counter = drive - .fetch_and_remove_latest_withdrawal_transaction_index_operations( - &mut batch, - Some(&transaction), - ) - .expect("to withdraw counter"); - - assert_eq!(stored_counter, 0); - } - - #[test] - fn test_should_return_expired_index_if_any() { - let drive = setup_drive_with_initial_state_structure(); - - let transaction = drive.grove.start_transaction(); - - let bytes = 42u64.to_be_bytes(); - - let path = get_withdrawal_transactions_expired_ids_path(); - - drive - .grove - .insert( - &path, - &bytes, - Element::Item(bytes.to_vec(), None), - None, - Some(&transaction), - ) - .unwrap() - .expect("to update index counter"); - - let mut batch = vec![]; - - let stored_counter = drive - .fetch_and_remove_latest_withdrawal_transaction_index_operations( - &mut batch, - Some(&transaction), - ) - .expect("to withdraw counter"); - - assert_eq!(stored_counter, 42); - } -} diff --git a/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/mod.rs b/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/mod.rs index 4706950d116..a3ebd139139 100644 --- a/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/mod.rs +++ b/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/mod.rs @@ -32,6 +32,13 @@ impl IdentityCreditWithdrawalTransitionAction { } } + /// Amount + pub fn amount(&self) -> u64 { + match self { + IdentityCreditWithdrawalTransitionAction::V0(transition) => transition.amount, + } + } + /// Recipient Id pub fn prepared_withdrawal_document(&self) -> &Document { match self { diff --git a/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/mod.rs b/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/mod.rs index e41f127ebc2..5f76a38e215 100644 --- a/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/mod.rs +++ b/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/mod.rs @@ -16,4 +16,6 @@ pub struct IdentityCreditWithdrawalTransitionActionV0 { pub revision: Revision, /// prepared withdrawal document pub prepared_withdrawal_document: Document, + /// amount + pub amount: u64, } diff --git a/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/transformer.rs index 9cebee40643..9a6e8f92af1 100644 --- a/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/transformer.rs @@ -12,16 +12,21 @@ impl IdentityCreditWithdrawalTransitionActionV0 { identity_credit_withdrawal: &IdentityCreditWithdrawalTransitionV0, creation_time_ms: u64, ) -> Self { + let mut entropy = Vec::new(); + entropy.extend_from_slice(&identity_credit_withdrawal.revision.to_be_bytes()); + entropy.extend_from_slice(identity_credit_withdrawal.output_script.as_bytes()); + let document_id = Document::generate_document_id_v0( &withdrawals_contract::ID, &identity_credit_withdrawal.identity_id, withdrawal::NAME, - identity_credit_withdrawal.output_script.as_bytes(), + &entropy, ); let document_data = platform_value!({ withdrawal::properties::AMOUNT: identity_credit_withdrawal.amount, withdrawal::properties::CORE_FEE_PER_BYTE: identity_credit_withdrawal.core_fee_per_byte, + // TODO(withdrawals): replace with actual value from state transition once pooling is done withdrawal::properties::POOLING: Pooling::Never, withdrawal::properties::OUTPUT_SCRIPT: identity_credit_withdrawal.output_script.as_bytes(), withdrawal::properties::STATUS: withdrawals_contract::WithdrawalStatus::QUEUED, @@ -41,6 +46,7 @@ impl IdentityCreditWithdrawalTransitionActionV0 { identity_id: identity_credit_withdrawal.identity_id, revision: identity_credit_withdrawal.revision, prepared_withdrawal_document: withdrawal_document, + amount: identity_credit_withdrawal.amount, } } } diff --git a/packages/rs-platform-version/src/version/drive_abci_versions.rs b/packages/rs-platform-version/src/version/drive_abci_versions.rs index 62fa30ba2c9..cede88f48cc 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions.rs @@ -64,9 +64,8 @@ pub struct DriveAbciMethodVersions { pub core_chain_lock: DriveAbciCoreChainLockMethodVersionsAndConstants, pub fee_pool_inwards_distribution: DriveAbciFeePoolInwardsDistributionMethodVersions, pub fee_pool_outwards_distribution: DriveAbciFeePoolOutwardsDistributionMethodVersions, - pub identity_credit_withdrawal: DriveAbciIdentityCreditWithdrawalMethodVersions, + pub withdrawals: DriveAbciIdentityCreditWithdrawalMethodVersions, pub state_transition_processing: DriveAbciStateTransitionProcessingMethodVersions, - pub withdrawals: DriveAbciWithdrawalsMethodVersions, pub epoch: DriveAbciEpochMethodVersions, pub block_start: DriveAbciBlockStartMethodVersions, pub block_end: DriveAbciBlockEndMethodVersions, @@ -248,11 +247,12 @@ pub struct DriveAbciBlockEndMethodVersions { #[derive(Clone, Debug, Default)] pub struct DriveAbciIdentityCreditWithdrawalMethodVersions { - pub build_withdrawal_transactions_from_documents: FeatureVersion, - pub fetch_and_prepare_unsigned_withdrawal_transactions: FeatureVersion, + pub build_untied_withdrawal_transactions_from_documents: FeatureVersion, + pub dequeue_and_build_unsigned_withdrawal_transactions: FeatureVersion, pub fetch_transactions_block_inclusion_status: FeatureVersion, pub pool_withdrawals_into_transactions_queue: FeatureVersion, - pub update_broadcasted_withdrawal_transaction_statuses: FeatureVersion, + pub update_broadcasted_withdrawal_statuses: FeatureVersion, + pub append_signatures_and_broadcast_withdrawal_transactions: FeatureVersion, } #[derive(Clone, Debug, Default)] @@ -266,8 +266,3 @@ pub struct DriveAbciStateTransitionProcessingMethodVersions { pub process_raw_state_transitions: FeatureVersion, pub validate_fees_of_event: FeatureVersion, } - -#[derive(Clone, Debug, Default)] -pub struct DriveAbciWithdrawalsMethodVersions { - pub check_withdrawals: FeatureVersion, -} diff --git a/packages/rs-platform-version/src/version/drive_versions.rs b/packages/rs-platform-version/src/version/drive_versions.rs index 7afa099da93..afb7bcd623c 100644 --- a/packages/rs-platform-version/src/version/drive_versions.rs +++ b/packages/rs-platform-version/src/version/drive_versions.rs @@ -416,6 +416,37 @@ pub struct DriveIdentityMethodVersions { pub insert: DriveIdentityInsertMethodVersions, pub contract_info: DriveIdentityContractInfoMethodVersions, pub cost_estimation: DriveIdentityCostEstimationMethodVersions, + pub withdrawals: DriveIdentityWithdrawalMethodVersions, +} + +#[derive(Clone, Debug, Default)] +pub struct DriveIdentityWithdrawalMethodVersions { + pub document: DriveIdentityWithdrawalDocumentMethodVersions, + pub transaction: DriveIdentityWithdrawalTransactionMethodVersions, +} + +#[derive(Clone, Debug, Default)] +pub struct DriveIdentityWithdrawalDocumentMethodVersions { + pub fetch_oldest_withdrawal_documents_by_status: FeatureVersion, + pub find_up_to_100_withdrawal_documents_by_status_and_transaction_indices: FeatureVersion, +} + +#[derive(Clone, Debug, Default)] +pub struct DriveIdentityWithdrawalTransactionMethodVersions { + pub index: DriveIdentityWithdrawalTransactionIndexMethodVersions, + pub queue: DriveIdentityWithdrawalTransactionQueueMethodVersions, +} + +#[derive(Clone, Debug, Default)] +pub struct DriveIdentityWithdrawalTransactionIndexMethodVersions { + pub fetch_next_withdrawal_transaction_index: FeatureVersion, + pub add_update_next_withdrawal_transaction_index_operation: FeatureVersion, +} + +#[derive(Clone, Debug, Default)] +pub struct DriveIdentityWithdrawalTransactionQueueMethodVersions { + pub add_enqueue_untied_withdrawal_transaction_operations: FeatureVersion, + pub dequeue_untied_withdrawal_transactions: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/mocks/v2_test.rs b/packages/rs-platform-version/src/version/mocks/v2_test.rs index a9fb7d79d40..840ba8582a6 100644 --- a/packages/rs-platform-version/src/version/mocks/v2_test.rs +++ b/packages/rs-platform-version/src/version/mocks/v2_test.rs @@ -27,7 +27,7 @@ use crate::version::drive_abci_versions::{ DriveAbciStateTransitionProcessingMethodVersions, DriveAbciStateTransitionValidationVersion, DriveAbciStateTransitionValidationVersions, DriveAbciStructureVersions, DriveAbciValidationDataTriggerAndBindingVersions, DriveAbciValidationDataTriggerVersions, - DriveAbciValidationVersions, DriveAbciVersion, DriveAbciWithdrawalsMethodVersions, + DriveAbciValidationVersions, DriveAbciVersion, }; use crate::version::drive_versions::{ DriveAssetLockMethodVersions, DriveBalancesMethodVersions, DriveBatchOperationsMethodVersion, @@ -52,14 +52,18 @@ use crate::version::drive_versions::{ DriveIdentityKeysInsertMethodVersions, DriveIdentityKeysMethodVersions, DriveIdentityKeysProveMethodVersions, DriveIdentityMethodVersions, DriveIdentityProveMethodVersions, DriveIdentityUpdateMethodVersions, - DriveInitializationMethodVersions, DriveMethodVersions, DriveOperationsMethodVersion, - DrivePlatformStateMethodVersions, DrivePlatformSystemMethodVersions, - DriveProtocolUpgradeVersions, DriveProveMethodVersions, DriveStateTransitionMethodVersions, - DriveStateTransitionOperationMethodVersions, DriveStructureVersion, - DriveSystemEstimationCostsMethodVersions, DriveSystemProtocolVersionMethodVersions, - DriveVerifyContractMethodVersions, DriveVerifyDocumentMethodVersions, - DriveVerifyIdentityMethodVersions, DriveVerifyMethodVersions, - DriveVerifySingleDocumentMethodVersions, DriveVerifySystemMethodVersions, DriveVersion, + DriveIdentityWithdrawalDocumentMethodVersions, DriveIdentityWithdrawalMethodVersions, + DriveIdentityWithdrawalTransactionIndexMethodVersions, + DriveIdentityWithdrawalTransactionMethodVersions, + DriveIdentityWithdrawalTransactionQueueMethodVersions, DriveInitializationMethodVersions, + DriveMethodVersions, DriveOperationsMethodVersion, DrivePlatformStateMethodVersions, + DrivePlatformSystemMethodVersions, DriveProtocolUpgradeVersions, DriveProveMethodVersions, + DriveStateTransitionMethodVersions, DriveStateTransitionOperationMethodVersions, + DriveStructureVersion, DriveSystemEstimationCostsMethodVersions, + DriveSystemProtocolVersionMethodVersions, DriveVerifyContractMethodVersions, + DriveVerifyDocumentMethodVersions, DriveVerifyIdentityMethodVersions, + DriveVerifyMethodVersions, DriveVerifySingleDocumentMethodVersions, + DriveVerifySystemMethodVersions, DriveVersion, }; use crate::version::mocks::TEST_BYTES; use crate::version::protocol_version::{FeatureVersionBounds, PlatformVersion}; @@ -361,6 +365,22 @@ pub(crate) const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { for_root_key_reference_tree: 0, for_update_revision: 0, }, + withdrawals: DriveIdentityWithdrawalMethodVersions { + document: DriveIdentityWithdrawalDocumentMethodVersions { + fetch_oldest_withdrawal_documents_by_status: 0, + find_up_to_100_withdrawal_documents_by_status_and_transaction_indices: 0, + }, + transaction: DriveIdentityWithdrawalTransactionMethodVersions { + index: DriveIdentityWithdrawalTransactionIndexMethodVersions { + fetch_next_withdrawal_transaction_index: 0, + add_update_next_withdrawal_transaction_index_operation: 0, + }, + queue: DriveIdentityWithdrawalTransactionQueueMethodVersions { + add_enqueue_untied_withdrawal_transaction_operations: 0, + dequeue_untied_withdrawal_transactions: 0, + }, + }, + }, }, platform_system: DrivePlatformSystemMethodVersions { protocol_version: DriveSystemProtocolVersionMethodVersions { @@ -521,21 +541,19 @@ pub(crate) const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { find_oldest_epoch_needing_payment: 0, fetch_reward_shares_list_for_masternode: 0, }, - identity_credit_withdrawal: DriveAbciIdentityCreditWithdrawalMethodVersions { - build_withdrawal_transactions_from_documents: 0, - fetch_and_prepare_unsigned_withdrawal_transactions: 0, + withdrawals: DriveAbciIdentityCreditWithdrawalMethodVersions { + build_untied_withdrawal_transactions_from_documents: 0, + dequeue_and_build_unsigned_withdrawal_transactions: 0, fetch_transactions_block_inclusion_status: 0, pool_withdrawals_into_transactions_queue: 0, - update_broadcasted_withdrawal_transaction_statuses: 0, + update_broadcasted_withdrawal_statuses: 0, + append_signatures_and_broadcast_withdrawal_transactions: 0, }, state_transition_processing: DriveAbciStateTransitionProcessingMethodVersions { execute_event: 0, process_raw_state_transitions: 0, validate_fees_of_event: 0, }, - withdrawals: DriveAbciWithdrawalsMethodVersions { - check_withdrawals: 0, - }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, get_genesis_time: 0, diff --git a/packages/rs-platform-version/src/version/mocks/v3_test.rs b/packages/rs-platform-version/src/version/mocks/v3_test.rs index 432e417dbfa..3e47bae6dc5 100644 --- a/packages/rs-platform-version/src/version/mocks/v3_test.rs +++ b/packages/rs-platform-version/src/version/mocks/v3_test.rs @@ -27,7 +27,7 @@ use crate::version::drive_abci_versions::{ DriveAbciStateTransitionProcessingMethodVersions, DriveAbciStateTransitionValidationVersion, DriveAbciStateTransitionValidationVersions, DriveAbciStructureVersions, DriveAbciValidationDataTriggerAndBindingVersions, DriveAbciValidationDataTriggerVersions, - DriveAbciValidationVersions, DriveAbciVersion, DriveAbciWithdrawalsMethodVersions, + DriveAbciValidationVersions, DriveAbciVersion, }; use crate::version::drive_versions::{ DriveAssetLockMethodVersions, DriveBalancesMethodVersions, DriveBatchOperationsMethodVersion, @@ -52,14 +52,18 @@ use crate::version::drive_versions::{ DriveIdentityKeysInsertMethodVersions, DriveIdentityKeysMethodVersions, DriveIdentityKeysProveMethodVersions, DriveIdentityMethodVersions, DriveIdentityProveMethodVersions, DriveIdentityUpdateMethodVersions, - DriveInitializationMethodVersions, DriveMethodVersions, DriveOperationsMethodVersion, - DrivePlatformStateMethodVersions, DrivePlatformSystemMethodVersions, - DriveProtocolUpgradeVersions, DriveProveMethodVersions, DriveStateTransitionMethodVersions, - DriveStateTransitionOperationMethodVersions, DriveStructureVersion, - DriveSystemEstimationCostsMethodVersions, DriveSystemProtocolVersionMethodVersions, - DriveVerifyContractMethodVersions, DriveVerifyDocumentMethodVersions, - DriveVerifyIdentityMethodVersions, DriveVerifyMethodVersions, - DriveVerifySingleDocumentMethodVersions, DriveVerifySystemMethodVersions, DriveVersion, + DriveIdentityWithdrawalDocumentMethodVersions, DriveIdentityWithdrawalMethodVersions, + DriveIdentityWithdrawalTransactionIndexMethodVersions, + DriveIdentityWithdrawalTransactionMethodVersions, + DriveIdentityWithdrawalTransactionQueueMethodVersions, DriveInitializationMethodVersions, + DriveMethodVersions, DriveOperationsMethodVersion, DrivePlatformStateMethodVersions, + DrivePlatformSystemMethodVersions, DriveProtocolUpgradeVersions, DriveProveMethodVersions, + DriveStateTransitionMethodVersions, DriveStateTransitionOperationMethodVersions, + DriveStructureVersion, DriveSystemEstimationCostsMethodVersions, + DriveSystemProtocolVersionMethodVersions, DriveVerifyContractMethodVersions, + DriveVerifyDocumentMethodVersions, DriveVerifyIdentityMethodVersions, + DriveVerifyMethodVersions, DriveVerifySingleDocumentMethodVersions, + DriveVerifySystemMethodVersions, DriveVersion, }; use crate::version::mocks::TEST_BYTES; use crate::version::protocol_version::{FeatureVersionBounds, PlatformVersion}; @@ -369,6 +373,22 @@ pub(crate) const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion { for_root_key_reference_tree: 0, for_update_revision: 0, }, + withdrawals: DriveIdentityWithdrawalMethodVersions { + document: DriveIdentityWithdrawalDocumentMethodVersions { + fetch_oldest_withdrawal_documents_by_status: 0, + find_up_to_100_withdrawal_documents_by_status_and_transaction_indices: 0, + }, + transaction: DriveIdentityWithdrawalTransactionMethodVersions { + index: DriveIdentityWithdrawalTransactionIndexMethodVersions { + fetch_next_withdrawal_transaction_index: 0, + add_update_next_withdrawal_transaction_index_operation: 0, + }, + queue: DriveIdentityWithdrawalTransactionQueueMethodVersions { + add_enqueue_untied_withdrawal_transaction_operations: 0, + dequeue_untied_withdrawal_transactions: 0, + }, + }, + }, }, platform_system: DrivePlatformSystemMethodVersions { protocol_version: DriveSystemProtocolVersionMethodVersions { @@ -521,21 +541,19 @@ pub(crate) const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion { find_oldest_epoch_needing_payment: 0, fetch_reward_shares_list_for_masternode: 0, }, - identity_credit_withdrawal: DriveAbciIdentityCreditWithdrawalMethodVersions { - build_withdrawal_transactions_from_documents: 0, - fetch_and_prepare_unsigned_withdrawal_transactions: 0, + withdrawals: DriveAbciIdentityCreditWithdrawalMethodVersions { + build_untied_withdrawal_transactions_from_documents: 0, + dequeue_and_build_unsigned_withdrawal_transactions: 0, fetch_transactions_block_inclusion_status: 0, pool_withdrawals_into_transactions_queue: 0, - update_broadcasted_withdrawal_transaction_statuses: 0, + update_broadcasted_withdrawal_statuses: 0, + append_signatures_and_broadcast_withdrawal_transactions: 0, }, state_transition_processing: DriveAbciStateTransitionProcessingMethodVersions { execute_event: 0, process_raw_state_transitions: 0, validate_fees_of_event: 0, }, - withdrawals: DriveAbciWithdrawalsMethodVersions { - check_withdrawals: 0, - }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, get_genesis_time: 0, diff --git a/packages/rs-platform-version/src/version/v1.rs b/packages/rs-platform-version/src/version/v1.rs index 758332ee62c..ca2ba271701 100644 --- a/packages/rs-platform-version/src/version/v1.rs +++ b/packages/rs-platform-version/src/version/v1.rs @@ -27,7 +27,7 @@ use crate::version::drive_abci_versions::{ DriveAbciStateTransitionProcessingMethodVersions, DriveAbciStateTransitionValidationVersion, DriveAbciStateTransitionValidationVersions, DriveAbciStructureVersions, DriveAbciValidationDataTriggerAndBindingVersions, DriveAbciValidationDataTriggerVersions, - DriveAbciValidationVersions, DriveAbciVersion, DriveAbciWithdrawalsMethodVersions, + DriveAbciValidationVersions, DriveAbciVersion, }; use crate::version::drive_versions::{ DriveAssetLockMethodVersions, DriveBalancesMethodVersions, DriveBatchOperationsMethodVersion, @@ -52,14 +52,18 @@ use crate::version::drive_versions::{ DriveIdentityKeysInsertMethodVersions, DriveIdentityKeysMethodVersions, DriveIdentityKeysProveMethodVersions, DriveIdentityMethodVersions, DriveIdentityProveMethodVersions, DriveIdentityUpdateMethodVersions, - DriveInitializationMethodVersions, DriveMethodVersions, DriveOperationsMethodVersion, - DrivePlatformStateMethodVersions, DrivePlatformSystemMethodVersions, - DriveProtocolUpgradeVersions, DriveProveMethodVersions, DriveStateTransitionMethodVersions, - DriveStateTransitionOperationMethodVersions, DriveStructureVersion, - DriveSystemEstimationCostsMethodVersions, DriveSystemProtocolVersionMethodVersions, - DriveVerifyContractMethodVersions, DriveVerifyDocumentMethodVersions, - DriveVerifyIdentityMethodVersions, DriveVerifyMethodVersions, - DriveVerifySingleDocumentMethodVersions, DriveVerifySystemMethodVersions, DriveVersion, + DriveIdentityWithdrawalDocumentMethodVersions, DriveIdentityWithdrawalMethodVersions, + DriveIdentityWithdrawalTransactionIndexMethodVersions, + DriveIdentityWithdrawalTransactionMethodVersions, + DriveIdentityWithdrawalTransactionQueueMethodVersions, DriveInitializationMethodVersions, + DriveMethodVersions, DriveOperationsMethodVersion, DrivePlatformStateMethodVersions, + DrivePlatformSystemMethodVersions, DriveProtocolUpgradeVersions, DriveProveMethodVersions, + DriveStateTransitionMethodVersions, DriveStateTransitionOperationMethodVersions, + DriveStructureVersion, DriveSystemEstimationCostsMethodVersions, + DriveSystemProtocolVersionMethodVersions, DriveVerifyContractMethodVersions, + DriveVerifyDocumentMethodVersions, DriveVerifyIdentityMethodVersions, + DriveVerifyMethodVersions, DriveVerifySingleDocumentMethodVersions, + DriveVerifySystemMethodVersions, DriveVersion, }; use crate::version::protocol_version::{FeatureVersionBounds, PlatformVersion}; use crate::version::{AbciStructureVersion, PlatformArchitectureVersion}; @@ -358,6 +362,22 @@ pub(super) const PLATFORM_V1: PlatformVersion = PlatformVersion { for_root_key_reference_tree: 0, for_update_revision: 0, }, + withdrawals: DriveIdentityWithdrawalMethodVersions { + document: DriveIdentityWithdrawalDocumentMethodVersions { + fetch_oldest_withdrawal_documents_by_status: 0, + find_up_to_100_withdrawal_documents_by_status_and_transaction_indices: 0, + }, + transaction: DriveIdentityWithdrawalTransactionMethodVersions { + index: DriveIdentityWithdrawalTransactionIndexMethodVersions { + fetch_next_withdrawal_transaction_index: 0, + add_update_next_withdrawal_transaction_index_operation: 0, + }, + queue: DriveIdentityWithdrawalTransactionQueueMethodVersions { + add_enqueue_untied_withdrawal_transaction_operations: 0, + dequeue_untied_withdrawal_transactions: 0, + }, + }, + }, }, platform_system: DrivePlatformSystemMethodVersions { protocol_version: DriveSystemProtocolVersionMethodVersions { @@ -518,21 +538,19 @@ pub(super) const PLATFORM_V1: PlatformVersion = PlatformVersion { find_oldest_epoch_needing_payment: 0, fetch_reward_shares_list_for_masternode: 0, }, - identity_credit_withdrawal: DriveAbciIdentityCreditWithdrawalMethodVersions { - build_withdrawal_transactions_from_documents: 0, - fetch_and_prepare_unsigned_withdrawal_transactions: 0, + withdrawals: DriveAbciIdentityCreditWithdrawalMethodVersions { + build_untied_withdrawal_transactions_from_documents: 0, + dequeue_and_build_unsigned_withdrawal_transactions: 0, fetch_transactions_block_inclusion_status: 0, pool_withdrawals_into_transactions_queue: 0, - update_broadcasted_withdrawal_transaction_statuses: 0, + update_broadcasted_withdrawal_statuses: 0, + append_signatures_and_broadcast_withdrawal_transactions: 0, }, state_transition_processing: DriveAbciStateTransitionProcessingMethodVersions { execute_event: 0, process_raw_state_transitions: 0, validate_fees_of_event: 0, }, - withdrawals: DriveAbciWithdrawalsMethodVersions { - check_withdrawals: 0, - }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, get_genesis_time: 0, diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index 0712383f300..401889f5928 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -33,7 +33,7 @@ envy = { version = "0.4.2", optional = true } futures = { version = "0.3.28" } derive_more = { version = "0.99.16" } # dashcore-rpc is only needed for core rpc; TODO remove once we have correct core rpc impl -dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore-rpc", branch = "master" } +dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore-rpc", tag = "v0.15.2" } [dev-dependencies] rs-dapi-client = { path = "../rs-dapi-client", features = ["mocks"] } diff --git a/packages/simple-signer/Cargo.toml b/packages/simple-signer/Cargo.toml index 66abcd42f99..d1828c744f4 100644 --- a/packages/simple-signer/Cargo.toml +++ b/packages/simple-signer/Cargo.toml @@ -9,5 +9,5 @@ rust-version = "1.73" [dependencies] anyhow = { version = "1.0.70" } bincode = { version = "2.0.0-rc.3", features = ["serde"] } -dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore-rpc", branch = "master" } +dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore-rpc", tag = "v0.15.2" } dpp = { path = "../rs-dpp", features = ["abci"] } diff --git a/packages/strategy-tests/src/transitions.rs b/packages/strategy-tests/src/transitions.rs index 3c783e2fd11..db7821ff0bc 100644 --- a/packages/strategy-tests/src/transitions.rs +++ b/packages/strategy-tests/src/transitions.rs @@ -21,7 +21,9 @@ use dpp::state_transition::identity_create_transition::methods::IdentityCreateTr use dpp::state_transition::identity_create_transition::IdentityCreateTransition; use dpp::state_transition::identity_credit_transfer_transition::v0::IdentityCreditTransferTransitionV0; -use dpp::state_transition::identity_credit_withdrawal_transition::v0::IdentityCreditWithdrawalTransitionV0; +use dpp::state_transition::identity_credit_withdrawal_transition::v0::{ + IdentityCreditWithdrawalTransitionV0, MIN_CORE_FEE_PER_BYTE, +}; use dpp::state_transition::identity_topup_transition::methods::IdentityTopUpTransitionMethodsV0; use dpp::state_transition::identity_topup_transition::IdentityTopUpTransition; @@ -459,7 +461,7 @@ pub fn create_identity_withdrawal_transition( let mut withdrawal: StateTransition = IdentityCreditWithdrawalTransitionV0 { identity_id: identity.id(), amount: 100000000, // 0.001 Dash - core_fee_per_byte: 1, + core_fee_per_byte: MIN_CORE_FEE_PER_BYTE, pooling: Pooling::Never, output_script: CoreScript::random_p2sh(rng), revision: identity.revision(), diff --git a/packages/wallet-lib/package.json b/packages/wallet-lib/package.json index 4795d046767..9823cd92944 100644 --- a/packages/wallet-lib/package.json +++ b/packages/wallet-lib/package.json @@ -46,7 +46,7 @@ "homepage": "https://github.com/dashevo/wallet-lib#readme", "dependencies": { "@dashevo/dapi-client": "workspace:*", - "@dashevo/dashcore-lib": "~0.21.0", + "@dashevo/dashcore-lib": "~0.21.1", "@dashevo/grpc-common": "workspace:*", "@dashevo/wasm-dpp": "workspace:*", "@yarnpkg/pnpify": "^4.0.0-rc.42", diff --git a/packages/wallet-lib/src/types/Storage/Storage.d.ts b/packages/wallet-lib/src/types/Storage/Storage.d.ts index 58894833008..7e10ad2acab 100644 --- a/packages/wallet-lib/src/types/Storage/Storage.d.ts +++ b/packages/wallet-lib/src/types/Storage/Storage.d.ts @@ -1,9 +1,12 @@ import { WalletStore } from '../WalletStore/WalletStore' +import {ChainStore} from "../ChainStore/ChainStore"; export declare class Storage { constructor(options?: Storage.Options); getWalletStore(walletId: string): WalletStore + getChainStore(network: string): ChainStore + getDefaultChainStore(): ChainStore } export declare namespace Storage { diff --git a/packages/wasm-dpp/lib/test/fixtures/getIdentityCreditWithdrawalTransitionFixture.js b/packages/wasm-dpp/lib/test/fixtures/getIdentityCreditWithdrawalTransitionFixture.js new file mode 100644 index 00000000000..97c73146bbf --- /dev/null +++ b/packages/wasm-dpp/lib/test/fixtures/getIdentityCreditWithdrawalTransitionFixture.js @@ -0,0 +1,20 @@ +const { Script, PrivateKey } = require('@dashevo/dashcore-lib'); +const { IdentityCreditWithdrawalTransition } = require('../../..'); +const generateRandomIdentifier = require('../utils/generateRandomIdentifier'); + +module.exports = function getIdentityCreditWithdrawalTransitionFixture() { + const privateKey = new PrivateKey('cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY'); + const address = privateKey.toAddress(); + + const stateTransition = new IdentityCreditWithdrawalTransition(1); + stateTransition.setIdentityId(generateRandomIdentifier()); + // eslint-disable-next-line + stateTransition.setAmount(BigInt(1000)); + stateTransition.setCoreFeePerByte(1000); + stateTransition.setPooling(0); + stateTransition.setOutputScript(Script.buildPublicKeyHashOut(address).toBuffer()); + // eslint-disable-next-line + stateTransition.setRevision(BigInt(1)); + + return stateTransition; +}; diff --git a/packages/wasm-dpp/lib/test/utils/generateRandomIdentifier.js b/packages/wasm-dpp/lib/test/utils/generateRandomIdentifier.js new file mode 100644 index 00000000000..18f67540a53 --- /dev/null +++ b/packages/wasm-dpp/lib/test/utils/generateRandomIdentifier.js @@ -0,0 +1,13 @@ +const crypto = require('crypto'); +const { Identifier } = require('../../..'); + +/** + * Generate random identity ID + * + * @return {Identifier} + */ +function generateRandomIdentifier() { + return new Identifier(crypto.randomBytes(32)); +} + +module.exports = generateRandomIdentifier; diff --git a/packages/wasm-dpp/package.json b/packages/wasm-dpp/package.json index 7dd92cab9cc..f3cadfde288 100644 --- a/packages/wasm-dpp/package.json +++ b/packages/wasm-dpp/package.json @@ -43,7 +43,7 @@ "@babel/cli": "^7.23.0", "@babel/core": "^7.23.3", "@babel/preset-env": "^7.23.3", - "@dashevo/dashcore-lib": "~0.21.0", + "@dashevo/dashcore-lib": "~0.21.1", "@dashevo/dpns-contract": "workspace:*", "@types/bs58": "^4.0.1", "@types/node": "^14.6.0", diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index 4dde4941064..1d958e7b95b 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -53,6 +53,9 @@ use dpp::consensus::signature::SignatureError; // use dpp::consensus::state::data_trigger::data_trigger_error::DataTriggerError; use dpp::consensus::state::state_error::StateError; +use dpp::consensus::state::data_trigger::DataTriggerError::{ + DataTriggerConditionError, DataTriggerExecutionError, DataTriggerInvalidResultError, +}; use wasm_bindgen::{JsError, JsValue}; use crate::errors::consensus::basic::data_contract::{ @@ -73,9 +76,9 @@ use crate::errors::consensus::signature::{ BasicBLSErrorWasm, BasicECDSAErrorWasm, IdentityNotFoundErrorWasm, SignatureShouldNotBePresentErrorWasm, }; -// use crate::errors::consensus::state::data_contract::data_trigger::{ -// DataTriggerConditionErrorWasm, DataTriggerExecutionErrorWasm, -// }; +use crate::errors::consensus::state::data_contract::data_trigger::{ + DataTriggerConditionErrorWasm, DataTriggerExecutionErrorWasm, DataTriggerInvalidResultErrorWasm, +}; use crate::errors::consensus::state::data_contract::{ DataContractAlreadyPresentErrorWasm, DataContractConfigUpdateErrorWasm, DataContractIsReadonlyErrorWasm, @@ -183,20 +186,11 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { StateError::MissingIdentityPublicKeyIdsError(e) => { MissingIdentityPublicKeyIdsErrorWasm::from(e).into() } - // TODO(versioning): restore - // StateError::DataTriggerError(data_trigger_error) => match data_trigger_error.deref() { - // DataTriggerError::DataTriggerConditionError(e) => { - // DataTriggerConditionErrorWasm::from(e).into() - // } - // DataTriggerError::DataTriggerExecutionError(e) => { - // DataTriggerExecutionErrorWasm::from(e).into() - // } - // DataTriggerError::DataTriggerInvalidResultError(e) => { - // DataTriggerInvalidResultErrorWasm::from(e).into() - // } - // }, - // TODO(versioning): restore - // StateError::DataTriggerActionError(_) => JsError::new("Data Trigger action error").into(), + StateError::DataTriggerError(data_trigger_error) => match data_trigger_error { + DataTriggerConditionError(e) => DataTriggerConditionErrorWasm::from(e).into(), + DataTriggerExecutionError(e) => DataTriggerExecutionErrorWasm::from(e).into(), + DataTriggerInvalidResultError(e) => DataTriggerInvalidResultErrorWasm::from(e).into(), + }, StateError::IdentityAlreadyExistsError(e) => { let wasm_error: IdentityAlreadyExistsErrorWasm = e.into(); wasm_error.into() diff --git a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_condition_error.rs b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_condition_error.rs index 626e126e798..d38e84fdb3f 100644 --- a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_condition_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_condition_error.rs @@ -4,7 +4,8 @@ use dpp::consensus::codes::ErrorWithCode; use dpp::consensus::state::data_trigger::data_trigger_condition_error::DataTriggerConditionError; use dpp::consensus::ConsensusError; use dpp::identifier::Identifier; -use dpp::serialization::PlatformSerializable; +use dpp::serialization::PlatformSerializableWithPlatformVersion; +use dpp::version::PlatformVersion; use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name=DataTriggerConditionError)] @@ -48,7 +49,7 @@ impl DataTriggerConditionErrorWasm { #[wasm_bindgen(js_name=serialize)] pub fn serialize(&self) -> Result { let bytes = ConsensusError::from(self.inner.clone()) - .serialize_to_bytes() + .serialize_to_bytes_with_platform_version(PlatformVersion::first()) .map_err(JsError::from)?; Ok(Buffer::from_bytes(bytes.as_slice())) diff --git a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_execution_error.rs b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_execution_error.rs index f3ff1b4b767..b4a928db3c7 100644 --- a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_execution_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_execution_error.rs @@ -3,7 +3,8 @@ use dpp::consensus::codes::ErrorWithCode; use dpp::consensus::state::data_trigger::data_trigger_execution_error::DataTriggerExecutionError; use dpp::consensus::ConsensusError; use dpp::identifier::Identifier; -use dpp::serialization::PlatformSerializable; +use dpp::serialization::PlatformSerializableWithPlatformVersion; +use dpp::version::PlatformVersion; use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name=DataTriggerExecutionError)] @@ -47,7 +48,7 @@ impl DataTriggerExecutionErrorWasm { #[wasm_bindgen(js_name=serialize)] pub fn serialize(&self) -> Result { let bytes = ConsensusError::from(self.inner.clone()) - .serialize_to_bytes() + .serialize_to_bytes_with_platform_version(PlatformVersion::first()) .map_err(JsError::from)?; Ok(Buffer::from_bytes(bytes.as_slice())) diff --git a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_invalid_result_error.rs b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_invalid_result_error.rs index 909c57b637a..ea7dc0d5e71 100644 --- a/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_invalid_result_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/state/data_contract/data_trigger/data_trigger_invalid_result_error.rs @@ -4,7 +4,8 @@ use dpp::consensus::codes::ErrorWithCode; use dpp::consensus::state::data_trigger::data_trigger_invalid_result_error::DataTriggerInvalidResultError; use dpp::consensus::ConsensusError; use dpp::identifier::Identifier; -use dpp::serialization::PlatformSerializable; +use dpp::serialization::PlatformSerializableWithPlatformVersion; +use dpp::version::PlatformVersion; use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name=DataTriggerInvalidResultError)] @@ -43,7 +44,7 @@ impl DataTriggerInvalidResultErrorWasm { #[wasm_bindgen(js_name=serialize)] pub fn serialize(&self) -> Result { let bytes = ConsensusError::from(self.inner.clone()) - .serialize_to_bytes() + .serialize_to_bytes_with_platform_version(PlatformVersion::first()) .map_err(JsError::from)?; Ok(Buffer::from_bytes(bytes.as_slice())) diff --git a/packages/wasm-dpp/src/errors/consensus/state/data_contract/mod.rs b/packages/wasm-dpp/src/errors/consensus/state/data_contract/mod.rs index 78ef7460acb..ac3a318055e 100644 --- a/packages/wasm-dpp/src/errors/consensus/state/data_contract/mod.rs +++ b/packages/wasm-dpp/src/errors/consensus/state/data_contract/mod.rs @@ -2,8 +2,7 @@ mod data_contract_already_present_error; mod data_contract_config_update_error; mod data_contract_is_readonly_error; -// TODO(versioning): remove -// pub mod data_trigger; +pub mod data_trigger; pub use data_contract_already_present_error::*; pub use data_contract_config_update_error::*; diff --git a/packages/wasm-dpp/src/identity/identity_facade.rs b/packages/wasm-dpp/src/identity/identity_facade.rs index 0ab0e8cb514..752d0640dee 100644 --- a/packages/wasm-dpp/src/identity/identity_facade.rs +++ b/packages/wasm-dpp/src/identity/identity_facade.rs @@ -12,7 +12,8 @@ use crate::identity::errors::InvalidIdentityError; use crate::identity::state_transition::{ create_asset_lock_proof_from_wasm_instance, ChainAssetLockProofWasm, IdentityCreateTransitionWasm, IdentityCreditTransferTransitionWasm, - IdentityTopUpTransitionWasm, IdentityUpdateTransitionWasm, InstantAssetLockProofWasm, + IdentityCreditWithdrawalTransitionWasm, IdentityTopUpTransitionWasm, + IdentityUpdateTransitionWasm, InstantAssetLockProofWasm, }; use crate::utils::Inner; @@ -22,6 +23,9 @@ use crate::with_js_error; use dpp::dashcore::{consensus, InstantLock, Transaction}; use crate::identity::IdentityWasm; +use dpp::identity::core_script::CoreScript; +use dpp::prelude::Revision; +use dpp::withdrawal::Pooling; use dpp::NonConsensusError; use serde::Deserialize; @@ -195,6 +199,36 @@ impl IdentityFacadeWasm { .with_js_error() } + #[wasm_bindgen(js_name=createIdentityCreditWithdrawalTransition)] + pub fn create_identity_credit_withdrawal_transition( + &self, + identity_id: &IdentifierWrapper, + amount: u64, + core_fee_per_byte: u32, + pooling: u8, + output_script: Vec, + revision: u64, + ) -> Result { + let pooling = match pooling { + 0 => Pooling::Never, + 1 => Pooling::IfAvailable, + 2 => Pooling::Standard, + _ => return Err(JsError::new("Invalid pooling value").into()), + }; + + self.0 + .create_identity_credit_withdrawal_transition( + identity_id.to_owned().into(), + amount, + core_fee_per_byte, + pooling, + CoreScript::from_bytes(output_script), + revision as Revision, + ) + .map(Into::into) + .with_js_error() + } + #[wasm_bindgen(js_name=createIdentityCreditTransferTransition)] pub fn create_identity_credit_transfer_transition( &self, diff --git a/packages/wasm-dpp/src/identity/identity_factory.rs b/packages/wasm-dpp/src/identity/identity_factory.rs index ee46f175620..384cd38226c 100644 --- a/packages/wasm-dpp/src/identity/identity_factory.rs +++ b/packages/wasm-dpp/src/identity/identity_factory.rs @@ -4,9 +4,11 @@ use crate::identifier::IdentifierWrapper; use crate::identity::errors::InvalidIdentityError; use crate::identity::identity::IdentityWasm; -use crate::identity::state_transition::ChainAssetLockProofWasm; use crate::identity::state_transition::IdentityCreditTransferTransitionWasm; use crate::identity::state_transition::InstantAssetLockProofWasm; +use crate::identity::state_transition::{ + ChainAssetLockProofWasm, IdentityCreditWithdrawalTransitionWasm, +}; use crate::{ identity::state_transition::create_asset_lock_proof_from_wasm_instance, @@ -16,7 +18,7 @@ use crate::{ }; use dpp::dashcore::{consensus, InstantLock, Transaction}; -use dpp::prelude::Identity; +use dpp::prelude::{Identity, Revision}; use serde::Deserialize; use std::convert::TryInto; @@ -24,8 +26,10 @@ use std::convert::TryInto; use crate::utils::{Inner, WithJsError}; use dpp::identity::identity_factory::IdentityFactory; +use dpp::identity::core_script::CoreScript; +use dpp::withdrawal::Pooling; use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsValue; +use wasm_bindgen::{JsError, JsValue}; #[wasm_bindgen(js_name=IdentityFactory)] pub struct IdentityFactoryWasm(IdentityFactory); @@ -197,6 +201,36 @@ impl IdentityFactoryWasm { .with_js_error() } + #[wasm_bindgen(js_name=createIdentityCreditWithdrawalTransition)] + pub fn create_identity_credit_withdrawal_transition( + &self, + identity_id: &IdentifierWrapper, + amount: u64, + core_fee_per_byte: u32, + pooling: u8, + output_script: Vec, + revision: u64, + ) -> Result { + let pooling = match pooling { + 0 => Pooling::Never, + 1 => Pooling::IfAvailable, + 2 => Pooling::Standard, + _ => return Err(JsError::new("Invalid pooling value").into()), + }; + + self.0 + .create_identity_credit_withdrawal_transition( + identity_id.to_owned().into(), + amount, + core_fee_per_byte, + pooling, + CoreScript::from_bytes(output_script), + revision as Revision, + ) + .map(Into::into) + .with_js_error() + } + #[wasm_bindgen(js_name=createIdentityUpdateTransition)] pub fn create_identity_update_transition( &self, diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/mod.rs b/packages/wasm-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/mod.rs new file mode 100644 index 00000000000..ea05af1cf5f --- /dev/null +++ b/packages/wasm-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/mod.rs @@ -0,0 +1,3 @@ +pub use transition::*; +mod to_object; +pub mod transition; diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/to_object.rs b/packages/wasm-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/to_object.rs new file mode 100644 index 00000000000..b94fd8a8d26 --- /dev/null +++ b/packages/wasm-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/to_object.rs @@ -0,0 +1,58 @@ +use dpp::identity::KeyID; + +use dpp::identity::core_script::CoreScript; +use dpp::prelude::Revision; +use dpp::state_transition::identity_credit_withdrawal_transition::accessors::IdentityCreditWithdrawalTransitionAccessorsV0; +use dpp::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition; +use dpp::state_transition::StateTransitionIdentitySigned; +use dpp::withdrawal::Pooling; +use dpp::{identifier::Identifier, state_transition::StateTransitionLike}; +use serde::Deserialize; +use std::default::Default; + +#[derive(Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct ToObjectOptions { + pub skip_signature: Option, +} + +#[derive(Default)] +pub struct ToObject { + pub transition_type: u8, + pub identity_id: Identifier, + pub amount: u64, + pub core_fee_per_byte: u32, + pub pooling: Pooling, + pub output_script: CoreScript, + pub revision: Revision, + pub signature: Option>, + pub signature_public_key_id: Option, +} + +pub fn to_object_struct( + transition: &IdentityCreditWithdrawalTransition, + options: ToObjectOptions, +) -> ToObject { + let mut to_object = ToObject { + transition_type: transition.state_transition_type() as u8, + identity_id: transition.identity_id(), + amount: transition.amount(), + core_fee_per_byte: transition.core_fee_per_byte(), + pooling: transition.pooling(), + output_script: transition.output_script(), + revision: transition.revision(), + ..ToObject::default() + }; + + if !options.skip_signature.unwrap_or(false) { + let signature = Some(transition.signature().to_vec()); + if let Some(signature) = &signature { + if !signature.is_empty() { + to_object.signature_public_key_id = Some(transition.signature_public_key_id()) + } + } + to_object.signature = signature; + } + + to_object +} diff --git a/packages/wasm-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/transition.rs b/packages/wasm-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/transition.rs new file mode 100644 index 00000000000..b823f39cda4 --- /dev/null +++ b/packages/wasm-dpp/src/identity/state_transition/identity_credit_withdrawal_transition/transition.rs @@ -0,0 +1,418 @@ +use crate::utils::WithJsError; +use std::convert::TryInto; +use std::default::Default; + +use dpp::version::PlatformVersion; +use wasm_bindgen::prelude::*; + +use crate::bls_adapter::{BlsAdapter, JsBlsAdapter}; +use crate::errors::from_dpp_err; +use crate::identifier::IdentifierWrapper; +use crate::identity::IdentityPublicKeyWasm; +use crate::{buffer::Buffer, with_js_error}; +use dpp::identifier::Identifier; +use dpp::identity::core_script::CoreScript; +use dpp::identity::KeyType; +use dpp::platform_value; +use dpp::platform_value::string_encoding::Encoding; +use dpp::platform_value::{string_encoding, BinaryData}; +use dpp::serialization::{PlatformSerializable, ValueConvertible}; +use dpp::state_transition::identity_credit_withdrawal_transition::accessors::IdentityCreditWithdrawalTransitionAccessorsV0; +use dpp::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition; +use dpp::state_transition::StateTransitionLike; +use dpp::state_transition::{StateTransition, StateTransitionIdentitySigned}; +use dpp::withdrawal::Pooling; + +#[wasm_bindgen(js_name=IdentityCreditWithdrawalTransition)] +#[derive(Clone)] +pub struct IdentityCreditWithdrawalTransitionWasm(IdentityCreditWithdrawalTransition); + +impl From for IdentityCreditWithdrawalTransitionWasm { + fn from(v: IdentityCreditWithdrawalTransition) -> Self { + IdentityCreditWithdrawalTransitionWasm(v) + } +} + +impl From for IdentityCreditWithdrawalTransition { + fn from(v: IdentityCreditWithdrawalTransitionWasm) -> Self { + v.0 + } +} + +#[wasm_bindgen(js_class = IdentityCreditWithdrawalTransition)] +impl IdentityCreditWithdrawalTransitionWasm { + #[wasm_bindgen(constructor)] + pub fn new(platform_version: u32) -> Result { + let platform_version = + &PlatformVersion::get(platform_version).map_err(|e| JsValue::from(e.to_string()))?; + + IdentityCreditWithdrawalTransition::default_versioned(&platform_version) + .map(Into::into) + .map_err(from_dpp_err) + } + + #[wasm_bindgen(js_name=getType)] + pub fn get_type(&self) -> u8 { + self.0.state_transition_type() as u8 + } + + #[wasm_bindgen(getter, js_name=identityId)] + pub fn identity_id(&self) -> IdentifierWrapper { + self.get_identity_id() + } + + #[wasm_bindgen(getter, js_name=amount)] + pub fn amount(&self) -> u64 { + self.0.amount() + } + + #[wasm_bindgen(js_name=getIdentityId)] + pub fn get_identity_id(&self) -> IdentifierWrapper { + self.0.identity_id().into() + } + + #[wasm_bindgen(js_name=setIdentityId)] + pub fn set_identity_id(&mut self, identity_id: &IdentifierWrapper) { + self.0.set_identity_id(identity_id.into()); + } + + #[wasm_bindgen(js_name=getAmount)] + pub fn get_amount(&self) -> u64 { + self.0.amount() as u64 + } + + #[wasm_bindgen(js_name=setAmount)] + pub fn set_amount(&mut self, amount: u64) { + self.0.set_amount(amount); + } + + #[wasm_bindgen(js_name=getCoreFeePerByte)] + pub fn get_core_fee_per_byte(&self) -> u32 { + self.0.core_fee_per_byte() as u32 + } + + #[wasm_bindgen(js_name=setCoreFeePerByte)] + pub fn set_core_fee_per_byte(&mut self, core_fee_per_byte: u32) { + self.0.set_core_fee_per_byte(core_fee_per_byte as u32); + } + + #[wasm_bindgen(js_name=getPooling)] + pub fn get_pooling(&self) -> u8 { + self.0.pooling() as u8 + } + + #[wasm_bindgen(js_name=setPooling)] + pub fn set_pooling(&mut self, pooling: u8) -> Result<(), JsError> { + match pooling { + 0 => self.0.set_pooling(Pooling::Never), + 1 => self.0.set_pooling(Pooling::IfAvailable), + 2 => self.0.set_pooling(Pooling::Standard), + _ => return Err(JsError::new("Invalid pooling value")), + } + + Ok(()) + } + + #[wasm_bindgen(js_name=getOutputScript)] + pub fn get_output_script(&self) -> Buffer { + Buffer::from_bytes(self.0.output_script().as_bytes()) + } + + #[wasm_bindgen(js_name=setOutputScript)] + pub fn set_output_script(&mut self, output_script: Vec) { + self.0 + .set_output_script(CoreScript::from_bytes(output_script)); + } + + #[wasm_bindgen(js_name=getRevision)] + pub fn get_revision(&self) -> u64 { + self.0.revision() as u64 + } + + #[wasm_bindgen(js_name=setRevision)] + pub fn set_revision(&mut self, revision: u64) { + self.0.set_revision(revision); + } + + #[wasm_bindgen(js_name=toObject)] + pub fn to_object(&self, options: JsValue) -> Result { + let opts: super::to_object::ToObjectOptions = if options.is_object() { + with_js_error!(serde_wasm_bindgen::from_value(options))? + } else { + Default::default() + }; + + let object = super::to_object::to_object_struct(&self.0, opts); + let js_object = js_sys::Object::new(); + + js_sys::Reflect::set( + &js_object, + &"type".to_owned().into(), + &object.transition_type.into(), + )?; + + let version = match self.0 { + IdentityCreditWithdrawalTransition::V0(_) => "0", + }; + + js_sys::Reflect::set(&js_object, &"$version".to_owned().into(), &version.into())?; + + if let Some(signature) = object.signature { + let signature_value: JsValue = if signature.is_empty() { + JsValue::undefined() + } else { + Buffer::from_bytes(signature.as_slice()).into() + }; + + js_sys::Reflect::set(&js_object, &"signature".to_owned().into(), &signature_value)?; + + if let Some(signature_public_key_id) = object.signature_public_key_id { + js_sys::Reflect::set( + &js_object, + &"signaturePublicKeyId".to_owned().into(), + &JsValue::from(signature_public_key_id), + )?; + } else { + js_sys::Reflect::set( + &js_object, + &"signaturePublicKeyId".to_owned().into(), + &JsValue::undefined(), + )?; + } + } + + js_sys::Reflect::set( + &js_object, + &"identityId".to_owned().into(), + &Buffer::from_bytes(object.identity_id.to_buffer().as_slice()), + )?; + + js_sys::Reflect::set( + &js_object, + &"amount".to_owned().into(), + &JsValue::from(object.amount), + )?; + + js_sys::Reflect::set( + &js_object, + &"coreFeePerByte".to_owned().into(), + &JsValue::from_f64(object.core_fee_per_byte as f64), + )?; + + js_sys::Reflect::set( + &js_object, + &"pooling".to_owned().into(), + &JsValue::from_f64((object.pooling as u8) as f64), + )?; + + let output_script = Buffer::from_bytes(object.output_script.as_bytes()); + js_sys::Reflect::set( + &js_object, + &"outputScript".to_owned().into(), + &output_script.into(), + )?; + + js_sys::Reflect::set( + &js_object, + &"revision".to_owned().into(), + &JsValue::from(object.revision), + )?; + + Ok(js_object.into()) + } + + #[wasm_bindgen(js_name=toBuffer)] + pub fn to_buffer(&self) -> Result { + let bytes = PlatformSerializable::serialize_to_bytes( + &StateTransition::IdentityCreditWithdrawal(self.0.clone()), + ) + .with_js_error()?; + Ok(Buffer::from_bytes(&bytes)) + } + + #[wasm_bindgen(js_name=toJSON)] + pub fn to_json(&self) -> Result { + let object = super::to_object::to_object_struct(&self.0, Default::default()); + let js_object = js_sys::Object::new(); + + js_sys::Reflect::set( + &js_object, + &"type".to_owned().into(), + &object.transition_type.into(), + )?; + + let version = match self.0 { + IdentityCreditWithdrawalTransition::V0(_) => "0", + }; + + js_sys::Reflect::set(&js_object, &"$version".to_owned().into(), &version.into())?; + + if let Some(signature) = object.signature { + let signature_value: JsValue = if signature.is_empty() { + JsValue::undefined() + } else { + string_encoding::encode(signature.as_slice(), Encoding::Base64).into() + }; + + js_sys::Reflect::set(&js_object, &"signature".to_owned().into(), &signature_value)?; + + if let Some(signature_public_key_id) = object.signature_public_key_id { + js_sys::Reflect::set( + &js_object, + &"signaturePublicKeyId".to_owned().into(), + &signature_public_key_id.into(), + )?; + } else { + js_sys::Reflect::set( + &js_object, + &"signaturePublicKeyId".to_owned().into(), + &JsValue::undefined(), + )?; + } + } + + let identity_id = object.identity_id.to_string(Encoding::Base58); + + js_sys::Reflect::set( + &js_object, + &"identityId".to_owned().into(), + &identity_id.into(), + )?; + + js_sys::Reflect::set( + &js_object, + &"amount".to_owned().into(), + &JsValue::from(&format!("{}", object.amount)), + )?; + + js_sys::Reflect::set( + &js_object, + &"coreFeePerByte".to_owned().into(), + &JsValue::from_f64(object.core_fee_per_byte as f64), + )?; + + js_sys::Reflect::set( + &js_object, + &"pooling".to_owned().into(), + &JsValue::from_f64((object.pooling as u8) as f64), + )?; + + let output_script = platform_value::string_encoding::encode( + object.output_script.as_bytes(), + Encoding::Base64, + ); + + js_sys::Reflect::set( + &js_object, + &"outputScript".to_owned().into(), + &output_script.into(), + )?; + + js_sys::Reflect::set( + &js_object, + &"revision".to_owned().into(), + &JsValue::from(&format!("{}", object.revision)), + )?; + + Ok(js_object.into()) + } + + #[wasm_bindgen(js_name=getModifiedDataIds)] + pub fn modified_data_ids(&self) -> Vec { + let ids = self.0.modified_data_ids(); + + ids.into_iter() + .map(|id| >::from(id).into()) + .collect() + } + + #[wasm_bindgen(js_name=isDataContractStateTransition)] + pub fn is_data_contract_state_transition(&self) -> bool { + self.0.is_data_contract_state_transition() + } + + #[wasm_bindgen(js_name=isDocumentStateTransition)] + pub fn is_document_state_transition(&self) -> bool { + self.0.is_document_state_transition() + } + + #[wasm_bindgen(js_name=isIdentityStateTransition)] + pub fn is_identity_state_transition(&self) -> bool { + self.0.is_identity_state_transition() + } + + #[wasm_bindgen(js_name=signByPrivateKey)] + pub fn sign_by_private_key( + &mut self, + private_key: Vec, + key_type: u8, + bls: Option, + ) -> Result<(), JsValue> { + let key_type = key_type + .try_into() + .map_err(|e: anyhow::Error| e.to_string())?; + + if bls.is_none() && key_type == KeyType::BLS12_381 { + return Err(JsError::new( + format!("BLS adapter is required for BLS key type '{}'", key_type).as_str(), + ) + .into()); + } + + let bls_adapter = if let Some(adapter) = bls { + BlsAdapter(adapter) + } else { + BlsAdapter(JsValue::undefined().into()) + }; + + // TODO: not the best approach because it involves cloning the transition + // Probably it worth to return `sign_by_private_key` per state transition + let mut wrapper = StateTransition::IdentityCreditWithdrawal(self.0.clone()); + wrapper + .sign_by_private_key(private_key.as_slice(), key_type, &bls_adapter) + .with_js_error()?; + + self.0.set_signature(wrapper.signature().to_owned()); + + Ok(()) + } + + #[wasm_bindgen(js_name=getSignature)] + pub fn get_signature(&self) -> Buffer { + Buffer::from_bytes(self.0.signature().as_slice()) + } + + #[wasm_bindgen(js_name=setSignature)] + pub fn set_signature(&mut self, signature: Option>) { + self.0 + .set_signature(BinaryData::new(signature.unwrap_or(vec![]))) + } + + #[wasm_bindgen] + pub fn sign( + &mut self, + identity_public_key: &IdentityPublicKeyWasm, + private_key: Vec, + bls: JsBlsAdapter, + ) -> Result<(), JsValue> { + let bls_adapter = BlsAdapter(bls); + + // TODO: come up with a better way to set signature to the binding. + let mut state_transition = StateTransition::IdentityCreditWithdrawal(self.0.clone()); + state_transition + .sign( + &identity_public_key.to_owned().into(), + &private_key, + &bls_adapter, + ) + .with_js_error()?; + + let signature = state_transition.signature().to_owned(); + let signature_public_key_id = state_transition.signature_public_key_id().unwrap_or(0); + + self.0.set_signature(signature); + self.0.set_signature_public_key_id(signature_public_key_id); + + Ok(()) + } +} diff --git a/packages/wasm-dpp/src/identity/state_transition/mod.rs b/packages/wasm-dpp/src/identity/state_transition/mod.rs index d724e9743ca..27e8ae4113a 100644 --- a/packages/wasm-dpp/src/identity/state_transition/mod.rs +++ b/packages/wasm-dpp/src/identity/state_transition/mod.rs @@ -1,6 +1,7 @@ pub use asset_lock_proof::*; pub use identity_create_transition::*; pub use identity_credit_transfer_transition::*; +pub use identity_credit_withdrawal_transition::*; pub use identity_topup_transition::*; pub use identity_update_transition::*; // pub use validate_public_key_signatures::*; @@ -8,6 +9,7 @@ pub use identity_update_transition::*; mod asset_lock_proof; mod identity_create_transition; mod identity_credit_transfer_transition; +mod identity_credit_withdrawal_transition; pub(crate) mod identity_public_key_transitions; mod identity_topup_transition; mod identity_update_transition; diff --git a/packages/wasm-dpp/src/state_transition/state_transition_factory.rs b/packages/wasm-dpp/src/state_transition/state_transition_factory.rs index 73abb163483..63d53bf4dd5 100644 --- a/packages/wasm-dpp/src/state_transition/state_transition_factory.rs +++ b/packages/wasm-dpp/src/state_transition/state_transition_factory.rs @@ -3,7 +3,8 @@ use crate::document_batch_transition::DocumentsBatchTransitionWasm; use crate::errors::from_dpp_err; use crate::identity::state_transition::{ IdentityCreateTransitionWasm, IdentityCreditTransferTransitionWasm, - IdentityTopUpTransitionWasm, IdentityUpdateTransitionWasm, + IdentityCreditWithdrawalTransitionWasm, IdentityTopUpTransitionWasm, + IdentityUpdateTransitionWasm, }; use crate::state_transition::errors::invalid_state_transition_error::InvalidStateTransitionErrorWasm; use dpp::state_transition::errors::StateTransitionError; @@ -51,6 +52,9 @@ impl StateTransitionFactoryWasm { StateTransition::IdentityCreditTransfer(st) => { Ok(IdentityCreditTransferTransitionWasm::from(st).into()) } + StateTransition::IdentityCreditWithdrawal(st) => { + Ok(IdentityCreditWithdrawalTransitionWasm::from(st).into()) + } StateTransition::DocumentsBatch(st) => { Ok(DocumentsBatchTransitionWasm::from(st).into()) } diff --git a/packages/wasm-dpp/test/integration/identity/IdentityFacade.spec.js b/packages/wasm-dpp/test/integration/identity/IdentityFacade.spec.js index f3fa71fbbd1..fb1db631347 100644 --- a/packages/wasm-dpp/test/integration/identity/IdentityFacade.spec.js +++ b/packages/wasm-dpp/test/integration/identity/IdentityFacade.spec.js @@ -5,10 +5,12 @@ const getInstantAssetLockProofFixture = require('../../../lib/test/fixtures/getI const getChainAssetLockProofFixture = require('../../../lib/test/fixtures/getChainAssetLockProofFixture'); const { + default: loadWasmDpp, Identity, InstantAssetLockProof, ChainAssetLockProof, IdentityUpdateTransition, IdentityCreateTransition, IdentityTopUpTransition, IdentityPublicKeyWithWitness, DashPlatformProtocol, ValidationResult, } = require('../../..'); +const getIdentityCreditWithdrawalTransitionFixture = require('../../../lib/test/fixtures/getIdentityCreditWithdrawalTransitionFixture'); describe('IdentityFacade', () => { let dpp; @@ -17,6 +19,8 @@ describe('IdentityFacade', () => { let instantAssetLockProof; let chainAssetLockProof; + before(loadWasmDpp); + beforeEach(async () => { dpp = new DashPlatformProtocol( { generate: () => crypto.randomBytes(32) }, @@ -189,4 +193,24 @@ describe('IdentityFacade', () => { expect(stateTransition.getPublicKeysDisabledAt()).to.equal(undefined); }); }); + + describe('createIdentityCreditWithdrawalTransition', () => { + it('should create IdentityCreditWithdrawalTransition', () => { + const stateTransitionFixture = getIdentityCreditWithdrawalTransitionFixture(); + const stateTransition = dpp.identity + .createIdentityCreditWithdrawalTransition( + stateTransitionFixture.getIdentityId(), + stateTransitionFixture.getAmount(), + stateTransitionFixture.getCoreFeePerByte(), + stateTransitionFixture.getPooling(), + stateTransitionFixture.getOutputScript(), + stateTransitionFixture.getRevision(), + ); + + expect(stateTransition.toObject()) + .to.deep.equal( + stateTransitionFixture.toObject(), + ); + }); + }); }); diff --git a/packages/wasm-dpp/test/unit/identity/IdentityFactory.spec.js b/packages/wasm-dpp/test/unit/identity/IdentityFactory.spec.js index 240088e4412..cc431e205aa 100644 --- a/packages/wasm-dpp/test/unit/identity/IdentityFactory.spec.js +++ b/packages/wasm-dpp/test/unit/identity/IdentityFactory.spec.js @@ -1,6 +1,8 @@ const getInstantAssetLockProofFixture = require('../../../lib/test/fixtures/getInstantAssetLockProofFixture'); const getIdentityFixture = require('../../../lib/test/fixtures/getIdentityFixture'); const getChainAssetLockProofFixture = require('../../../lib/test/fixtures/getChainAssetLockProofFixture'); +const getIdentityCreditWithdrawalTransitionFixture = require('../../../lib/test/fixtures/getIdentityCreditWithdrawalTransitionFixture'); + const { Identity, IdentityFactory, InstantAssetLockProof, ChainAssetLockProof, IdentityUpdateTransition, @@ -249,4 +251,24 @@ describe('IdentityFactory', () => { expect(stateTransition.getPublicKeysDisabledAt()).to.deep.equal(new Date()); }); }); + + describe('createIdentityCreditWithdrawalTransition', () => { + it('should create IdentityCreditWithdrawalTransition', () => { + const stateTransitionFixture = getIdentityCreditWithdrawalTransitionFixture(); + const stateTransition = factory + .createIdentityCreditWithdrawalTransition( + stateTransitionFixture.getIdentityId(), + stateTransitionFixture.getAmount(), + stateTransitionFixture.getCoreFeePerByte(), + stateTransitionFixture.getPooling(), + stateTransitionFixture.getOutputScript(), + stateTransitionFixture.getRevision(), + ); + + expect(stateTransition.toObject()) + .to.deep.equal( + stateTransitionFixture.toObject(), + ); + }); + }); }); diff --git a/packages/wasm-dpp/test/unit/identity/stateTransition/IdentityCreditWithdrawalTransition/IdentityCreditWithdrawalTransition.spec.js b/packages/wasm-dpp/test/unit/identity/stateTransition/IdentityCreditWithdrawalTransition/IdentityCreditWithdrawalTransition.spec.js new file mode 100644 index 00000000000..76e6a8d32f0 --- /dev/null +++ b/packages/wasm-dpp/test/unit/identity/stateTransition/IdentityCreditWithdrawalTransition/IdentityCreditWithdrawalTransition.spec.js @@ -0,0 +1,155 @@ +const getIdentityCreditWithdrawalTransitionFixture = require('../../../../../lib/test/fixtures/getIdentityCreditWithdrawalTransitionFixture'); + +const { + default: loadWasmDpp, + Identifier, StateTransitionTypes, +} = require('../../../../..'); + +describe('IdentityCreditWithdrawalTransition', () => { + let rawStateTransition; + let stateTransition; + + before(loadWasmDpp); + + beforeEach(async () => { + stateTransition = await getIdentityCreditWithdrawalTransitionFixture(); + rawStateTransition = stateTransition.toObject(); + }); + + describe('#getType', () => { + it('should return IDENTITY_CREDIT_WITHDRAWAL type', () => { + expect(stateTransition.getType()).to.equal(StateTransitionTypes.IdentityCreditWithdrawal); + }); + }); + + describe('#getIdentityId', () => { + it('should return identity id', () => { + expect(stateTransition.getIdentityId().toBuffer()).to.deep.equal( + rawStateTransition.identityId, + ); + }); + }); + + describe('#getAmount', () => { + it('should return amount', () => { + expect(stateTransition.getAmount()).to.be.equal( + rawStateTransition.amount, + ); + }); + }); + + describe('#getCoreFeePerByte', () => { + it('should return core fee per byte', () => { + expect(stateTransition.getCoreFeePerByte()).to.be.equal( + rawStateTransition.coreFeePerByte, + ); + }); + }); + + describe('#getPooling', () => { + it('should return pooling', () => { + expect(stateTransition.getPooling()).to.be.equal( + rawStateTransition.pooling, + ); + }); + }); + + describe('#getOutputScript', () => { + it('should return output script', () => { + expect(stateTransition.getOutputScript()).to.be.deep.equal( + rawStateTransition.outputScript, + ); + }); + }); + + describe('#getRevision', () => { + it('should return revision', () => { + expect(stateTransition.getRevision()).to.be.equal( + rawStateTransition.revision, + ); + }); + }); + + describe('#toObject', () => { + it('should return raw state transition', () => { + rawStateTransition = stateTransition.toObject(); + + expect(rawStateTransition).to.deep.equal({ + $version: '0', + type: StateTransitionTypes.IdentityCreditWithdrawal, + identityId: stateTransition.getIdentityId().toBuffer(), + amount: stateTransition.getAmount(), + coreFeePerByte: stateTransition.getCoreFeePerByte(), + pooling: stateTransition.getPooling(), + outputScript: stateTransition.getOutputScript(), + revision: stateTransition.getRevision(), + signature: undefined, + signaturePublicKeyId: undefined, + }); + }); + + it('should return raw state transition without signature', () => { + rawStateTransition = stateTransition.toObject({ skipSignature: true }); + + expect(rawStateTransition).to.deep.equal({ + $version: '0', + type: StateTransitionTypes.IdentityCreditWithdrawal, + identityId: stateTransition.getIdentityId().toBuffer(), + amount: stateTransition.getAmount(), + coreFeePerByte: stateTransition.getCoreFeePerByte(), + pooling: stateTransition.getPooling(), + outputScript: stateTransition.getOutputScript(), + revision: stateTransition.getRevision(), + }); + }); + }); + + describe('#toJSON', () => { + it('should return JSON representation of state transition', () => { + const jsonStateTransition = stateTransition.toJSON(); + + expect(jsonStateTransition).to.deep.equal({ + $version: '0', + type: StateTransitionTypes.IdentityCreditWithdrawal, + identityId: stateTransition.getIdentityId().toString(), + amount: stateTransition.getAmount().toString(), + coreFeePerByte: stateTransition.getCoreFeePerByte(), + pooling: stateTransition.getPooling(), + outputScript: stateTransition.getOutputScript().toString('base64'), + revision: stateTransition.getRevision().toString(), + signature: undefined, + signaturePublicKeyId: undefined, + }); + }); + }); + + describe('#getModifiedDataIds', () => { + it('should return ids of topped up identity', () => { + const result = stateTransition.getModifiedDataIds(); + + expect(result.length).to.be.equal(1); + const [identityId] = result; + + expect(identityId).to.be.an.instanceOf(Identifier); + expect(identityId.toBuffer()).to.be.deep.equal(rawStateTransition.identityId); + }); + }); + + describe('#isDataContractStateTransition', () => { + it('should return false', () => { + expect(stateTransition.isDataContractStateTransition()).to.be.false(); + }); + }); + + describe('#isDocumentStateTransition', () => { + it('should return false', () => { + expect(stateTransition.isDocumentStateTransition()).to.be.false(); + }); + }); + + describe('#isIdentityStateTransition', () => { + it('should return true', () => { + expect(stateTransition.isIdentityStateTransition()).to.be.true(); + }); + }); +}); diff --git a/packages/withdrawals-contract/schema/v1/withdrawals-documents.json b/packages/withdrawals-contract/schema/v1/withdrawals-documents.json index b51b2a8360e..bff066ac4f0 100644 --- a/packages/withdrawals-contract/schema/v1/withdrawals-documents.json +++ b/packages/withdrawals-contract/schema/v1/withdrawals-documents.json @@ -58,19 +58,17 @@ "status": "asc" }, { - "transactionId": "asc" + "transactionIndex": "asc" } ], "unique": false } ], "properties": { - "transactionId": { - "description": "Hash of asset unlock transaction. Populated with untied transaction hash on “pooled” stage and with signed transaction on “broadcasted” stage", - "type": "array", - "byteArray": true, - "minItems": 32, - "maxItems": 32, + "transactionIndex": { + "type": "integer", + "description": "Sequential index of asset unlock (withdrawal) transaction. Populated when a withdrawal pooled into withdrawal transaction", + "minimum": 1, "position": 0 }, "transactionSignHeight": { @@ -79,24 +77,18 @@ "minimum": 1, "position": 1 }, - "transactionIndex": { - "type": "integer", - "description": "Sequential index used to create withdrawal transaction", - "minimum": 1, - "position": 2 - }, "amount": { "type": "integer", "description": "The amount to be withdrawn", "minimum": 1000, - "position": 3 + "position": 2 }, "coreFeePerByte": { "type": "integer", "description": "This is the fee that you are willing to spend for this transaction in Duffs/Byte", "minimum": 1, "maximum": 4294967295, - "position": 4 + "position": 3 }, "pooling": { "type": "integer", @@ -106,14 +98,14 @@ 1, 2 ], - "position": 5 + "position": 4 }, "outputScript": { "type": "array", "byteArray": true, "minItems": 23, "maxItems": 25, - "position": 6 + "position": 5 }, "status": { "type": "integer", @@ -124,7 +116,8 @@ 3, 4 ], - "position": 7 + "description": "0 - Pending, 1 - Signed, 2 - Broadcasted, 3 - Complete, 4 - Expired", + "position": 6 } }, "additionalProperties": false, diff --git a/packages/withdrawals-contract/src/v1/mod.rs b/packages/withdrawals-contract/src/v1/mod.rs index df86928bb1e..826ec9a40ff 100644 --- a/packages/withdrawals-contract/src/v1/mod.rs +++ b/packages/withdrawals-contract/src/v1/mod.rs @@ -6,7 +6,6 @@ pub mod document_types { pub const NAME: &str = "withdrawal"; pub mod properties { - pub const TRANSACTION_ID: &str = "transactionId"; pub const TRANSACTION_SIGN_HEIGHT: &str = "transactionSignHeight"; pub const TRANSACTION_INDEX: &str = "transactionIndex"; pub const AMOUNT: &str = "amount"; diff --git a/packages/withdrawals-contract/test/unit/withdrawalsContract.spec.js b/packages/withdrawals-contract/test/unit/withdrawalsContract.spec.js index 7f1d638a0cd..70e573318b6 100644 --- a/packages/withdrawals-contract/test/unit/withdrawalsContract.spec.js +++ b/packages/withdrawals-contract/test/unit/withdrawalsContract.spec.js @@ -42,7 +42,6 @@ describe('Withdrawals contract', () => { beforeEach(() => { rawWithdrawalDocument = { - transactionId: Buffer.alloc(32, 1), transactionIndex: 42, amount: 1000, coreFeePerByte: 1, @@ -52,63 +51,6 @@ describe('Withdrawals contract', () => { }; }); - it('should have at least five properties', () => { - rawWithdrawalDocument = {}; - - const document = dpp.document.create(dataContract, identityId, 'withdrawal', rawWithdrawalDocument); - const validationResult = document.validate(dpp.protocolVersion); - const error = expectJsonSchemaError(validationResult, 5); - - expect(error.keyword).to.equal('required'); - expect(error.params.missingProperty).to.equal('amount'); - }); - - it('should not have additional properties', async () => { - rawWithdrawalDocument.someOtherProperty = 42; - - const document = dpp.document.create(dataContract, identityId, 'withdrawal', rawWithdrawalDocument); - const validationResult = document.validate(dpp.protocolVersion); - const error = expectJsonSchemaError(validationResult); - - expect(error.keyword).to.equal('additionalProperties'); - expect(error.params.additionalProperties).to.deep.equal(['someOtherProperty']); - }); - - describe('transactionId', () => { - it('should be byte array', () => { - rawWithdrawalDocument.transactionId = 1; - - const document = dpp.document.create(dataContract, identityId, 'withdrawal', rawWithdrawalDocument); - const validationResult = document.validate(dpp.protocolVersion); - const error = expectJsonSchemaError(validationResult); - - expect(error.keyword).to.equal('type'); - expect(error.params.type).to.equal('array'); - }); - - it('should be not less then 32 bytes long', () => { - rawWithdrawalDocument.transactionId = [0]; - - const document = dpp.document.create(dataContract, identityId, 'withdrawal', rawWithdrawalDocument); - const validationResult = document.validate(dpp.protocolVersion); - const error = expectJsonSchemaError(validationResult); - - expect(error.keyword).to.equal('minItems'); - expect(error.params.minItems).to.equal(32); - }); - - it('should be not more then 32 bytes long', () => { - rawWithdrawalDocument.transactionId = Buffer.alloc(33); - - const document = dpp.document.create(dataContract, identityId, 'withdrawal', rawWithdrawalDocument); - const validationResult = document.validate(dpp.protocolVersion); - const error = expectJsonSchemaError(validationResult); - - expect(error.keyword).to.equal('maxItems'); - expect(error.params.maxItems).to.equal(32); - }); - }); - describe('amount', () => { it('should be present', async () => { delete rawWithdrawalDocument.amount; diff --git a/yarn.lock b/yarn.lock index 3cc59e34fb4..2b6846d32b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1592,7 +1592,7 @@ __metadata: version: 0.0.0-use.local resolution: "@dashevo/bench-suite@workspace:packages/bench-suite" dependencies: - "@dashevo/dashcore-lib": "npm:~0.21.0" + "@dashevo/dashcore-lib": "npm:~0.21.1" "@dashevo/dpns-contract": "workspace:*" "@dashevo/wallet-lib": "workspace:*" "@dashevo/wasm-dpp": "workspace:*" @@ -1625,7 +1625,7 @@ __metadata: "@babel/core": "npm:^7.23.3" "@dashevo/dapi-grpc": "workspace:*" "@dashevo/dash-spv": "workspace:*" - "@dashevo/dashcore-lib": "npm:~0.21.0" + "@dashevo/dashcore-lib": "npm:~0.21.1" "@dashevo/grpc-common": "workspace:*" "@dashevo/wasm-dpp": "workspace:*" assert-browserify: "npm:^2.0.0" @@ -1705,7 +1705,7 @@ __metadata: "@dashevo/bls": "npm:~1.2.9" "@dashevo/dapi-client": "workspace:*" "@dashevo/dapi-grpc": "workspace:*" - "@dashevo/dashcore-lib": "npm:~0.21.0" + "@dashevo/dashcore-lib": "npm:~0.21.1" "@dashevo/dashd-rpc": "npm:^18.2.0" "@dashevo/dp-services-ctl": "github:dashevo/js-dp-services-ctl#v0.19-dev" "@dashevo/grpc-common": "workspace:*" @@ -1754,7 +1754,7 @@ __metadata: dependencies: "@dashevo/dark-gravity-wave": "npm:^1.1.1" "@dashevo/dash-util": "npm:^2.0.3" - "@dashevo/dashcore-lib": "npm:~0.21.0" + "@dashevo/dashcore-lib": "npm:~0.21.1" chai: "npm:^4.3.10" eslint: "npm:^8.53.0" eslint-config-airbnb-base: "npm:^15.0.0" @@ -1778,9 +1778,9 @@ __metadata: languageName: node linkType: hard -"@dashevo/dashcore-lib@npm:~0.21.0": - version: 0.21.0 - resolution: "@dashevo/dashcore-lib@npm:0.21.0" +"@dashevo/dashcore-lib@npm:~0.21.1": + version: 0.21.1 + resolution: "@dashevo/dashcore-lib@npm:0.21.1" dependencies: "@dashevo/bls": "npm:~1.2.9" "@dashevo/x11-hash-js": "npm:^1.0.2" @@ -1794,7 +1794,7 @@ __metadata: ripemd160: "npm:^2.0.2" tsd: "npm:^0.28.1" unorm: "npm:^1.6.0" - checksum: fb4419623f0e98e9c32e8f567ad1eb7f8f68c9e89c1f0216362771bf08cbf5e7115ea3c6eea722f6992d4460de7c6c5b1abca5c1c1790a8f5a1987baef78cbbb + checksum: 68502e40788cba834e3ac2199998b2b47965d1b900a838899cab1085e20bd10c7c5595d9667416eaac7174859f18da718ab13a9846eb6e77cc2b234c991a6e7f languageName: node linkType: hard @@ -1934,13 +1934,14 @@ __metadata: resolution: "@dashevo/platform-test-suite@workspace:packages/platform-test-suite" dependencies: "@dashevo/dapi-client": "workspace:*" - "@dashevo/dashcore-lib": "npm:~0.21.0" + "@dashevo/dashcore-lib": "npm:~0.21.1" "@dashevo/dpns-contract": "workspace:*" "@dashevo/feature-flags-contract": "workspace:*" "@dashevo/grpc-common": "workspace:*" "@dashevo/masternode-reward-shares-contract": "workspace:*" "@dashevo/wallet-lib": "workspace:*" "@dashevo/wasm-dpp": "workspace:*" + "@dashevo/withdrawals-contract": "workspace:*" assert: "npm:^2.0.0" assert-browserify: "npm:^2.0.0" browserify-zlib: "npm:^0.2.0" @@ -2036,7 +2037,7 @@ __metadata: dependencies: "@dashevo/dapi-client": "workspace:*" "@dashevo/dash-spv": "workspace:*" - "@dashevo/dashcore-lib": "npm:~0.21.0" + "@dashevo/dashcore-lib": "npm:~0.21.1" "@dashevo/grpc-common": "workspace:*" "@dashevo/wasm-dpp": "workspace:*" "@yarnpkg/pnpify": "npm:^4.0.0-rc.42" @@ -2096,7 +2097,7 @@ __metadata: "@babel/core": "npm:^7.23.3" "@babel/preset-env": "npm:^7.23.3" "@dashevo/bls": "npm:~1.2.9" - "@dashevo/dashcore-lib": "npm:~0.21.0" + "@dashevo/dashcore-lib": "npm:~0.21.1" "@dashevo/dpns-contract": "workspace:*" "@types/bs58": "npm:^4.0.1" "@types/node": "npm:^14.6.0" @@ -6482,13 +6483,14 @@ __metadata: "@dashevo/bls": "npm:~1.2.9" "@dashevo/dapi-client": "workspace:*" "@dashevo/dapi-grpc": "workspace:*" - "@dashevo/dashcore-lib": "npm:~0.21.0" + "@dashevo/dashcore-lib": "npm:~0.21.1" "@dashevo/dashpay-contract": "workspace:*" "@dashevo/dpns-contract": "workspace:*" "@dashevo/grpc-common": "workspace:*" "@dashevo/masternode-reward-shares-contract": "workspace:*" "@dashevo/wallet-lib": "workspace:*" "@dashevo/wasm-dpp": "workspace:*" + "@dashevo/withdrawals-contract": "workspace:*" "@types/chai": "npm:^4.2.12" "@types/dirty-chai": "npm:^2.0.2" "@types/mocha": "npm:^8.0.3" @@ -6558,7 +6560,7 @@ __metadata: "@babel/eslint-parser": "npm:^7.23.3" "@dashevo/bls": "npm:~1.2.9" "@dashevo/dapi-client": "workspace:*" - "@dashevo/dashcore-lib": "npm:~0.21.0" + "@dashevo/dashcore-lib": "npm:~0.21.1" "@dashevo/dashd-rpc": "npm:^18.2.0" "@dashevo/dashpay-contract": "workspace:*" "@dashevo/docker-compose": "npm:^0.24.4"