diff --git a/devops/Makefile b/devops/Makefile index 03b79758..7ff75ebe 100644 --- a/devops/Makefile +++ b/devops/Makefile @@ -53,7 +53,7 @@ ifeq ($(SRC_DIR_NAME),sovtoken) # pypi: indy-plenum # apt: indy-plenum (stable component) -FPM_P_DEPENDS := indy-node(=1.8.1) +FPM_P_DEPENDS := indy-node(=1.9.0~rc1) FPM_ARGS := --no-python-dependencies $(FPM_ARGS) endif diff --git a/devops/aws-codebuild/Jenkinsfile.cd b/devops/aws-codebuild/Jenkinsfile.cd index 630aa40f..78885855 100644 --- a/devops/aws-codebuild/Jenkinsfile.cd +++ b/devops/aws-codebuild/Jenkinsfile.cd @@ -1,5 +1,7 @@ #!groovy +String projectName = 'token-plugin' + def sovLibrary = library(identifier: 'sovrin-aws-codebuild@v1.0.1', retriever: modernSCM( github(credentialsId: 'sovbot-github', repoOwner: 'sovrin-foundation', repository: 'aws-codebuild-pipeline-plugin') )).com.sovrin.pipeline @@ -18,12 +20,11 @@ def downloadPackagingUtils() { } def nodeLabels = [ - codeBuild: env.LIBSOVTOKEN_CODEBUILD_NODE_LABEL ?: 'codebuild', - macos: env.LIBSOVTOKEN_MACOS_NODE_LABEL ?: 'macos', + codeBuild: env.LIBSOVTOKEN_CODEBUILD_NODE_LABEL ?: 'codebuild', + macos: env.LIBSOVTOKEN_MACOS_NODE_LABEL ?: 'macos', ] pipelineWrapper({ - //put code build containers inside a vpc under our dev account env.USE_VPC_CONFIG = true //this IP is outdated, should use the one from env @@ -32,6 +33,11 @@ pipelineWrapper({ nodeWrapper(nodeLabels.codeBuild) { def osname = 'xenial' List projects = ['sovtoken', 'sovtokenfees'] + List pkgsList = [] + String indyNodeVersion + Boolean isRC = false + Boolean isRelease = false + Boolean isDev = (env.BRANCH_NAME == 'master') stage('Checkout sources from SCM') { checkout scm @@ -43,6 +49,18 @@ pipelineWrapper({ ['devops', 'sovtoken/sovtoken/__metadata__.py', 'sovtokenfees/sovtokenfees/__metadata__.py']) def awsCBHelper = sovLibrary.AwsCodeBuildHelper.new(this, buildCtx) + stage('Get indy-node version') { + indyNodeVersion = utils.shStdout(""" + sed -n "s/.*indy-node==\\([^\\"\\']\\+\\).*/\\1/p" sovtoken/setup.py + """) + if (!indyNodeVersion) { + throw new Exception('indy-node version is not found') + } + isRC = !!((env.BRANCH_NAME == 'stable') && indyNodeVersion.find(/[^0-9\.]/)) + isRelease = ((env.BRANCH_NAME == 'stable') && !isRC) + logger.info("Detected indy-node version: '$indyNodeVersion', isRC: $isRC, isRelease: $isRelease") + } + Map builds = [ "s3-upload": { // performs on the current jenkins node stage('Upload source to S3') { @@ -65,126 +83,142 @@ pipelineWrapper({ } builds = projects.collectEntries { proj -> - ["$osname:$proj": [ - nodeLabel: "${nodeLabels.codeBuild}", - build: { - - def packageName = proj - def prTag = "ci-$osname-$proj" // use ci project - def srcVersion - def lastRevision - def debPVersion - def goals = ['package'] - - def ciImageTag - - stage('Get source version') { - srcVersion = utils.shStdout("SRC_DIR_NAME=$proj make -C devops src_version -s") - logger.info("Current source version: $srcVersion") - } - -// stage('Get last revision') { -// lastRevision = evernymRepo.getLastRevision { -// delegate.packageName = packageName -// packageSrcVersion = srcVersion -// repoDistr = 'agency-dev' -// } + String projLabel = "$osname-$proj" + + ["$projLabel": [ + nodeLabel: "${nodeLabels.codeBuild}", + build: { + def packageName = proj + def prTag = "ci-$osname-$proj" // use ci project + def srcVersion + def lastRevision + def debPVersion + def goals = ['package'] + + def ciImageTag + + stage('Get source version') { + srcVersion = utils.shStdout("SRC_DIR_NAME=$proj make -C devops src_version -s") + logger.info("Current source version: $srcVersion") + } + +// stage('Get last revision') { +// lastRevision = evernymRepo.getLastRevision { +// delegate.packageName = packageName +// packageSrcVersion = srcVersion +// repoDistr = 'agency-dev' +// } // -// if (lastRevision) { -// logger.info("Found last revision number: $lastRevision") -// } else { -// logger.info("No previous revision was found") -// } -// } - - - stage('Set release parameters') { - def releaseVersion = env.BRANCH_NAME == 'stable' ? '' : "$BUILD_NUMBER" - - debPVersion = "$srcVersion${releaseVersion ? '~' + releaseVersion : ''}" - logger.info("Package version for sovrin repo: $debPVersion") - } - - stage("$osname:$proj: Resolve image tag") { - def _imgVersion = utils.shStdout("OSNAME=$osname make -C devops image_ci_version -s") - ciImageTag = "$_imgVersion-$osname-$proj-ci" - logger.info("CI docker image tag: $ciImageTag") - } - - awsCBHelper.build() { - projectTag = prTag - - // build spec for env image - envBuildSrc = ['devops', "${proj}/${proj}/__metadata__.py"] // TODO make more accurate - envBuildCmds = [ - 'export PROJECT_DIR=$PWD', - 'make -C devops image_ci' - ] - envBuildEnvv = [ - [name: 'OSNAME', value: osname], - [name: 'SRC_DIR_NAME', value: proj], - [name: 'DOCKER_NAME', value: awsCBHelper.buildCtx.projectName], - [name: 'DOCKER_TAG', value: ciImageTag], - ] - - // env and build spec - imageTag = ciImageTag - buildspec = 'devops/aws-codebuild/buildspec.yml' - envv = [ - [name: 'OSNAME', value: osname], - [name: 'MAKE_GOALS', value: 'package'], - [name: 'SRC_DIR_NAME', value: proj], - [name: 'ARTIFACTS', value: "devops/_build/$proj/*$packageName*.*"], // TODO more accurate here - [name: 'FPM_P_VERSION', value: debPVersion], - ] - - onArtifacts = { - this.stage("$osname:$proj: Archive logs") { - utils.archiveArtifacts("logs/*.log*") { - truncate = true - allowEmptyArchive = true - } - } - } - } - - - stage('Upload debs to repos') { - - String debName - - dir("${awsCBHelper.buildCtx.projects[prTag].artifactsDir}") { - dir("sovrin-packaging") { - downloadPackagingUtils() - } - - debName = utils.shStdout("ls $packageName*$debPVersion*.deb") - - logger.info("Uploading debian package '$debName' to sovrin repo") - sh "mkdir debs && mv $debName ./debs/" - withCredentials([file(credentialsId: 'SovrinRepoSSHKey', variable: 'sovrin_key')]) { - sh "./sovrin-packaging/upload_debs.py ./debs $env.SOVRIN_CORE_REPO_NAME $env.BRANCH_NAME --host $env.SOVRIN_REPO_HOST --ssh-key $sovrin_key" - } - - } - - notifier.email { - to = '$DEFAULT_RECIPIENTS ' + (this.env.LIBSOVTOKEN_DEF_RECIPIENTS ?: '') - subject = '$PROJECT_NAME - Build # $BUILD_NUMBER: ' + "new deb '$debName' was published" - body = ("New debian package '$debName' was built and published" + - '\n\nCheck console output at $BUILD_URL to view the results.') - } - } - - } - ] - ] +// if (lastRevision) { +// logger.info("Found last revision number: $lastRevision") +// } else { +// logger.info("No previous revision was found") +// } +// } + + + stage('Set release parameters') { + def releaseVersion = isRelease ? '' : (isRC ? 'rc' : isDev ? 'dev' : '') + "$BUILD_NUMBER" + debPVersion = "$srcVersion${releaseVersion ? '~' + releaseVersion : ''}" + logger.info("Package version for sovrin repo: $debPVersion") + } + + stage("$projLabel: Resolve image tag") { + def _imgVersion = utils.shStdout("OSNAME=$osname make -C devops image_ci_version -s") + ciImageTag = "$_imgVersion-$osname-$proj-ci" + logger.info("CI docker image tag: $ciImageTag") + } + + awsCBHelper.build() { + projectTag = prTag + + // build spec for env image + envBuildSrc = ['devops', "${proj}/${proj}/__metadata__.py"] // TODO make more accurate + envBuildCmds = [ + 'export PROJECT_DIR=$PWD', + 'make -C devops image_ci' + ] + envBuildEnvv = [ + [name: 'OSNAME', value: osname], + [name: 'SRC_DIR_NAME', value: proj], + [name: 'DOCKER_NAME', value: awsCBHelper.buildCtx.projectName], + [name: 'DOCKER_TAG', value: ciImageTag], + ] + + // env and build spec + imageTag = ciImageTag + buildspec = 'devops/aws-codebuild/buildspec.yml' + envv = [ + [name: 'OSNAME', value: osname], + [name: 'MAKE_GOALS', value: 'package'], + [name: 'SRC_DIR_NAME', value: proj], + [name: 'ARTIFACTS', value: "devops/_build/${proj}/*${packageName}*.*"], // TODO more accurate here + [name: 'FPM_P_VERSION', value: debPVersion], + ] + + onArtifacts = { + this.stage("$projLabel: Archive logs") { + utils.archiveArtifacts("logs/*.log*") { + truncate = true + allowEmptyArchive = true + } + + String pkgBaseName = "${packageName}_${debPVersion}" + + this.stash includes: utils.shStdout("ls ${pkgBaseName}_*.deb"), name: pkgBaseName + pkgsList += pkgBaseName + } + } + } + } + ]] } - stage("Package and publish") { + stage("Build artifacts") { builds.failFast = false utils.parallel builds } + + stage('Release artifacts') { + String repoName = env.SOVRIN_CORE_REPO_NAME + String repoComponent = env.BRANCH_NAME + String debsDir = 'debs' + + if (isRC) { + repoComponent = 'rc' + } + logger.info("Debian repo component is set to: '$repoComponent'") + + dir(debsDir) { + pkgsList.each { + unstash name: it + } + } + logger.info("Uploading debian packages to '${repoName}/${repoComponent}' sovrin repo: $pkgsList") + + dir("sovrin-packaging") { + downloadPackagingUtils() + } + + withCredentials([file(credentialsId: 'SovrinRepoSSHKey', variable: 'sovrin_key')]) { + sh "./sovrin-packaging/upload_debs.py $debsDir $repoName $repoComponent --host $env.SOVRIN_REPO_HOST --ssh-key $sovrin_key --force-backup" + } + + notifier.email { + to = '$DEFAULT_RECIPIENTS ' + (this.env.LIBSOVTOKEN_DEF_RECIPIENTS ?: '') + subject = "[$projectName] new packages ${pkgsList.join(' ')} published to '${repoName}/${repoComponent}' repo" + body = """ + |New debian packages ${pkgsList.join(' ')} were built and published to '${repoName}/${repoComponent}' repo + | + |Build: + | Project: \$PROJECT_NAME + | Url: ${this.env.BUILD_URL} + | Number: ${this.env.BUILD_NUMBER} + | + |Check console output at ${this.env.BUILD_URL}console to view the details. + """.stripMargin() + } + } } }, { err -> if (err) { @@ -193,6 +227,7 @@ pipelineWrapper({ stage("Build result notification") { notifier.email { + subject = "[$projectName] \$DEFAULT_SUBJECT" to = '$DEFAULT_RECIPIENTS ' + (this.env.LIBSOVTOKEN_DEF_RECIPIENTS ?: '') } } diff --git a/devops/aws-codebuild/Jenkinsfile.ci b/devops/aws-codebuild/Jenkinsfile.ci index 684545d1..83603403 100644 --- a/devops/aws-codebuild/Jenkinsfile.ci +++ b/devops/aws-codebuild/Jenkinsfile.ci @@ -90,7 +90,7 @@ pipelineWrapper({ [name: 'OSNAME', value: osname], [name: 'MAKE_GOALS', value: goals.join(' ')], [name: 'SRC_DIR_NAME', value: proj], - [name: 'PYTEST_ARGS', value: "-l -v --junit-xml=/tmp/artifacts/logs/test.${proj}.xml"], + [name: 'PYTEST_ARGS', value: "-l -vv --junit-xml=/tmp/artifacts/logs/test.${proj}.xml"], ] onArtifacts = { diff --git a/devops/denv/Dockerfile b/devops/denv/Dockerfile index 1232ec10..e3adf135 100644 --- a/devops/denv/Dockerfile +++ b/devops/denv/Dockerfile @@ -37,7 +37,7 @@ RUN apt-get update -y && apt-get install -y \ zlib1g-dev \ liblz4-dev \ libsnappy-dev \ - # libindy + # libindy python3-nacl \ libindy-crypto=0.4.5 \ libindy=1.8.2 diff --git a/devops/docker/ci/xenial/Dockerfile b/devops/docker/ci/xenial/Dockerfile index 7ae66c39..6217c836 100644 --- a/devops/docker/ci/xenial/Dockerfile +++ b/devops/docker/ci/xenial/Dockerfile @@ -25,7 +25,7 @@ RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 68DB5E88 \ RUN add-apt-repository "deb https://repo.sovrin.org/sdk/deb xenial master" \ && apt-get update && apt-get install -y \ libindy-crypto=0.4.5 \ - libindy=1.9.0~1122 \ + libindy=1.9.0~1132 \ libsovtoken=0.9.7~66 \ && rm -rf /var/lib/apt/lists/* diff --git a/docs/deploy_instructions.md b/docs/deploy_instructions.md index 32b8112d..4f4f2455 100644 --- a/docs/deploy_instructions.md +++ b/docs/deploy_instructions.md @@ -4,7 +4,7 @@ 1) POOL_UPGRADE with the `indy-node` package version 1.6.70 to support `sovrin` package in POOL_UPGRADE txn 2) Upgrade the CLI (locally) to 1.6.6 libindy, indy-CLI -3) POOL_UPGRADE with the `sovrin` package version `1.1.20` to have TDE-ready version of plugins installed. **TODO: This version is still a stub. Define first version with plugins and with trust anchor restriction** +3) POOL_UPGRADE with the `sovrin` package version `1.1.20` to have TDE-ready version of plugins installed. **TODO: This version is still a stub. Define first version with plugins and with endorser restriction** It might be done with [POOL_UPGRADE](https://github.com/hyperledger/indy-node/blob/master/docs/pool-upgrade.md) transaction. You can do it from `indy-cli` with this command: ``` Command: @@ -131,8 +131,8 @@ Tokens can be distributed via `XFER_PUBLIC`. To make it we need to go through th Setting fees process is similar to minting -- you need the build transaction, get the approval (signature) of trustees and to send it to the ledger. Details of this process are described in [setting fees guide](https://github.com/sovrin-foundation/libsovtoken/blob/master/doc/set_fees_process.md) -## Configure the ledger to remove the need for trust anchors +## Configure the ledger to remove the need for endorsers -When fees are set for the transactions, we can allow to write to the ledger from DIDs that are not a trust anchor (of course if the have tokens to pay the fee). For this we need to make a POOL_UPGRADE once again for a `sovrin` package of version `1.1.21`. **TODO: This version is still a stub yoo. Define first version with plugins and without trust anchor restriction** +When fees are set for the transactions, we can allow to write to the ledger from DIDs that are not a endorser (of course if the have tokens to pay the fee). For this we need to make a POOL_UPGRADE once again for a `sovrin` package of version `1.1.21`. **TODO: This version is still a stub yoo. Define first version with plugins and without endorser restriction** \ No newline at end of file diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 00000000..ae95f803 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,52 @@ +# Release Workflow + +## Overview + +Release workflow is engined by Jenkins and described by the [pipeline](../devops/aws-codebuild/Jenkinsfile.cd). + +For now release artifacts are created only for debian package [repository](https://repo.sovrin.org/deb/) +maintained by [The Sovrin Foundation](https://sovrin.org) + +There are several types of releases: +- dev releases are built for each commit to `master` branch; +- rc (release candidate) releases are built for each commit to `stable` when `indy-node` dependency is set to *non stable* version; +- stable releases are built for each commit to `stable` branch when `indy-node` dependency is set to *stable* version. + +## Release Pipeline + +1. Preparation + 1. `indy-node` version is checked. + 2. Necessary AWS resources are created. +2. Build + 1. AWS CodeBuild project builds debian packages for each plugin +3. Publishing and Notification + 1. Built packages are published to the repository component depending on the `indy-node` dependency version and GitHub branch as it was mentioned above: + - `master` commits are published to `master`, debian packages include unique pipeline build number as part of their versions. + - `stable` commits are published to `rc` if `indy-node` version is *non stable* (e.g. `1.8.0~rc1`) and to `stable` otherwise. + `stable` packages' versions match ones from the source code and `rc` packages will have additional suffix. + +## Release Workflow + +1. Release candidate preparation + 1. [**Contributor**] + - Creates a branch from `stable`. + - Merges necessary changes from `master`; + - Waits for `indy-node` release candidate is released and set its versions in [sovtoken/setup.py](../sovtoken/setup.py) and [devops/Makefile](../devops/Makefile). + - Creates a pull request to `stable`. + 2. [**Maintainer**] once CI testing is passed reviews, approves and merges the PR. + 3. [**build server**] + - Once PR is merged starts the [release pipeline](#release-pipeline) which publishes debian packages to `rc` repo component. +2. Release candidate acceptance + 1. [**QA**] + - Waits for `sovrin` release candidate and performs acceptance of the coming release verifying a full software stack: `indy-node`, `token-plugin` and `sovrin`. + - If QA rejects new release new candidates are prepared repeating steps described above. +3. Releasing + 1. [**Contributor**] + - Once QA accepts and `indy-node` is released creates a branch from `stable`. + - Sets `indy-node` dependency version to stable one. + - Creates new PR to `stable`. + 2. [**Maintainer**] once CI testing is passed, reviews, approves and merges that PR. + 3. [**build server**] + - Once PR is merged starts the [release pipeline](#release-pipeline) which publishes debian packages to `stable` repo component. +4. New development cycle start + 1. [**Contributor**] creates a PR version bumps in [sovtoken/__metadata__.py](../sovtoken/sovtoken/__metadata__.py) and [sovtokenfees/__metadata__.py](../sovtokenfees/sovtokenfees/__metadata__.py) diff --git a/sovtoken/setup.py b/sovtoken/setup.py index c521262c..9865046b 100644 --- a/sovtoken/setup.py +++ b/sovtoken/setup.py @@ -19,7 +19,7 @@ with open(os.path.join(here, 'sovtoken', '__metadata__.py'), 'r') as f: exec(f.read(), metadata) -tests_require = ['pytest>=4.6.1', 'pytest-xdist', 'mock', 'python3-indy==1.9.0-dev-1122'] +tests_require = ['pytest==4.6.2', 'pytest-xdist', 'mock', 'python3-indy==1.9.0-dev-1132'] setup( name=metadata['__title__'], @@ -38,7 +38,7 @@ '*.css', '*.ico', '*.png', 'LICENSE', 'LEGAL', 'sovtoken']}, include_package_data=True, - install_requires=['indy-node==1.8.1'], + install_requires=['indy-node==1.9.0.rc1'], setup_requires=['pytest-runner'], extras_require={ diff --git a/sovtoken/sovtoken/__init__.py b/sovtoken/sovtoken/__init__.py index 036836ed..f2358c9b 100644 --- a/sovtoken/sovtoken/__init__.py +++ b/sovtoken/sovtoken/__init__.py @@ -4,14 +4,8 @@ from sovtoken.transactions import TokenTransactions LEDGER_IDS = {TOKEN_LEDGER_ID, } -AcceptableWriteTypes = {TokenTransactions.MINT_PUBLIC.value, - TokenTransactions.XFER_PUBLIC.value} - -AcceptableQueryTypes = {TokenTransactions.GET_UTXO.value, } # TODO: Find a better way to import all members of this module __all__ = [ LEDGER_IDS, - AcceptableQueryTypes, - AcceptableWriteTypes ] diff --git a/sovtoken/sovtoken/__metadata__.py b/sovtoken/sovtoken/__metadata__.py index 103252ce..b6457fce 100644 --- a/sovtoken/sovtoken/__metadata__.py +++ b/sovtoken/sovtoken/__metadata__.py @@ -4,7 +4,7 @@ # TODO: Update the field values below where needed __title__ = 'sovtoken' -__version_info__ = (0, 9, 13) +__version_info__ = (0, 9, 14) __version__ = '.'.join(map(str, __version_info__)) __description__ = 'Token Plugin For Indy Plenum' __long_description__ = '' diff --git a/sovtoken/sovtoken/client_authnr.py b/sovtoken/sovtoken/client_authnr.py index 2fbb2551..fee9d2d6 100644 --- a/sovtoken/sovtoken/client_authnr.py +++ b/sovtoken/sovtoken/client_authnr.py @@ -11,7 +11,6 @@ from plenum.common.types import OPERATION, PLUGIN_TYPE_AUTHENTICATOR, f from plenum.common.verifier import DidVerifier, Verifier from plenum.server.client_authn import CoreAuthNr -from sovtoken import AcceptableQueryTypes, AcceptableWriteTypes from sovtoken.constants import (ADDRESS, INPUTS, MINT_PUBLIC, OUTPUTS, SIGS, XFER_PUBLIC, EXTRA) from sovtoken.util import address_to_verkey @@ -30,9 +29,6 @@ def verify(self, sig, msg) -> bool: class TokenAuthNr(CoreAuthNr): pluginType = PLUGIN_TYPE_AUTHENTICATOR - write_types = AcceptableWriteTypes - query_types = AcceptableQueryTypes - # ------------------------------------------------------------------------------------ # Entrance point for transaction signature verification. Here come all transactions of all types, # but only XFER_PUBLIC and MINT_PUBLIC are verified @@ -71,7 +67,7 @@ def getVerkey(self, identifier): if len(identifier) not in (21, 22): vk = address_to_verkey(identifier) if len(vk) in (43, 44): - # Address is the 32 byte verkey + # Address is the 32 byte verkey return vk return super().getVerkey(identifier) diff --git a/sovtoken/sovtoken/config.py b/sovtoken/sovtoken/config.py deleted file mode 100644 index e00c5ad4..00000000 --- a/sovtoken/sovtoken/config.py +++ /dev/null @@ -1,10 +0,0 @@ -from plenum.common.constants import KeyValueStorageType - - -def get_config(config): - config.tokenTransactionsFile = 'sovtoken_transactions' - config.tokenStateStorage = KeyValueStorageType.Rocksdb - config.tokenStateDbName = 'sovtoken_state' - config.utxoCacheStorage = KeyValueStorageType.Rocksdb - config.utxoCacheDbName = 'utxo_cache' - return config diff --git a/sovtoken/sovtoken/constants.py b/sovtoken/sovtoken/constants.py index 03ee604f..3af8d760 100644 --- a/sovtoken/sovtoken/constants.py +++ b/sovtoken/sovtoken/constants.py @@ -16,4 +16,11 @@ XFER_PUBLIC = TokenTransactions.XFER_PUBLIC.value GET_UTXO = TokenTransactions.GET_UTXO.value -ACCEPTABLE_TXN_TYPES = (MINT_PUBLIC, XFER_PUBLIC, GET_UTXO) \ No newline at end of file +ACCEPTABLE_TXN_TYPES = (MINT_PUBLIC, XFER_PUBLIC, GET_UTXO) + +UTXO_CACHE_LABEL = "utxo_cache" + +ACCEPTABLE_WRITE_TYPES = {TokenTransactions.MINT_PUBLIC.value, + TokenTransactions.XFER_PUBLIC.value} +ACCEPTABLE_QUERY_TYPES = {TokenTransactions.GET_UTXO.value, } +ACCEPTABLE_ACTION_TYPES = {} diff --git a/sovtoken/sovtoken/main.py b/sovtoken/sovtoken/main.py index 451fb22b..b13e8146 100644 --- a/sovtoken/sovtoken/main.py +++ b/sovtoken/sovtoken/main.py @@ -1,45 +1,111 @@ -import functools +from sovtoken.request_handlers.batch_req_handler.token_batch_handler import TokenBatchHandler +from sovtoken.request_handlers.batch_req_handler.tracker_batch_handler import TrackerBatchHandler +from sovtoken.request_handlers.batch_req_handler.utxo_batch_handler import UTXOBatchHandler +from sovtoken.request_handlers.read_req_handler.get_utxo_handler import GetUtxoHandler +from sovtoken.request_handlers.write_request_handler.mint_handler import MintHandler +from sovtoken.request_handlers.write_request_handler.xfer_handler import XferHandler +from sovtoken.utxo_cache import UTXOCache + +from plenum.common.ledger_uncommitted_tracker import LedgerUncommittedTracker +from state.pruning_state import PruningState + +from storage.helper import initKeyValueStorage + +from plenum.common.ledger import Ledger + +from ledger.compact_merkle_tree import CompactMerkleTree from sovtoken.sovtoken_auth_map import sovtoken_auth_map -from plenum.common.constants import DOMAIN_LEDGER_ID, NodeHooks +from plenum.common.constants import DOMAIN_LEDGER_ID, KeyValueStorageType from sovtoken.client_authnr import TokenAuthNr -from sovtoken.config import get_config -from sovtoken.constants import TOKEN_LEDGER_ID -from sovtoken.storage import get_token_hash_store, \ - get_token_ledger, get_token_state, get_utxo_cache -from sovtoken.token_req_handler import TokenReqHandler +from sovtoken.constants import TOKEN_LEDGER_ID, UTXO_CACHE_LABEL, ACCEPTABLE_WRITE_TYPES, ACCEPTABLE_QUERY_TYPES, \ + ACCEPTABLE_ACTION_TYPES def integrate_plugin_in_node(node): - - node.config = get_config(node.config) + update_config(node) node.write_req_validator.auth_map.update(sovtoken_auth_map) + init_storages(node) + register_req_handlers(node) + register_batch_handlers(node) + register_authentication(node) + return node - token_authnr = TokenAuthNr(node.states[DOMAIN_LEDGER_ID]) - hash_store = get_token_hash_store(node.dataLocation) - ledger = get_token_ledger(node.dataLocation, - node.config.tokenTransactionsFile, - hash_store, node.config) - state = get_token_state(node.dataLocation, - node.config.tokenStateDbName, - node.config) - utxo_cache = get_utxo_cache(node.dataLocation, - node.config.utxoCacheDbName, - node.config) +def update_config(node): + config = node.config + config.tokenTransactionsFile = 'sovtoken_transactions' + config.tokenStateStorage = KeyValueStorageType.Rocksdb + config.tokenStateDbName = 'sovtoken_state' + config.utxoCacheStorage = KeyValueStorageType.Rocksdb + config.utxoCacheDbName = 'utxo_cache' + + +def init_storages(node): + # Token ledger and state init if TOKEN_LEDGER_ID not in node.ledger_ids: node.ledger_ids.append(TOKEN_LEDGER_ID) + token_state = init_token_state(node) + token_ledger = init_token_ledger(node) + node.db_manager.register_new_database(TOKEN_LEDGER_ID, + token_ledger, + token_state) + init_token_database(node) + + # UTXO store init + node.db_manager.register_new_store(UTXO_CACHE_LABEL, + UTXOCache(initKeyValueStorage( + node.config.utxoCacheStorage, + node.dataLocation, + node.config.utxoCacheDbName))) + token_tracker = LedgerUncommittedTracker(token_state.committedHeadHash, + token_ledger.uncommitted_root_hash, + token_ledger.size) + node.db_manager.register_new_tracker(TOKEN_LEDGER_ID, token_tracker) + + +def init_token_ledger(node): + return Ledger(CompactMerkleTree(hashStore=node.getHashStore('sovtoken')), + dataDir=node.dataLocation, + fileName=node.config.tokenTransactionsFile, + ensureDurability=node.config.EnsureLedgerDurability) - node.ledgerManager.addLedger(TOKEN_LEDGER_ID, ledger, + +def init_token_state(node): + return PruningState( + initKeyValueStorage( + node.config.tokenStateStorage, + node.dataLocation, + node.config.tokenStateDbName, + db_config=node.config.db_state_config) + ) + + +def init_token_database(node): + node.ledgerManager.addLedger(TOKEN_LEDGER_ID, + node.db_manager.get_ledger(TOKEN_LEDGER_ID), postTxnAddedToLedgerClbk=node.postTxnFromCatchupAddedToLedger) node.on_new_ledger_added(TOKEN_LEDGER_ID) - node.register_state(TOKEN_LEDGER_ID, state) - node.clientAuthNr.register_authenticator(token_authnr) - token_req_handler = TokenReqHandler(ledger, state, utxo_cache, - node.states[DOMAIN_LEDGER_ID], node.bls_bft.bls_store, - node.write_req_validator) - node.register_req_handler(token_req_handler, TOKEN_LEDGER_ID) - node.db_manager.register_new_database(lid=TOKEN_LEDGER_ID, - ledger=ledger, - state=state) - return node + +def register_req_handlers(node): + node.write_manager.register_req_handler(XferHandler(node.db_manager, + node.write_req_validator)) + node.write_manager.register_req_handler(MintHandler(node.db_manager, + node.write_req_validator)) + node.read_manager.register_req_handler(GetUtxoHandler(node.db_manager)) + + +def register_batch_handlers(node): + node.write_manager.register_batch_handler(UTXOBatchHandler(node.db_manager), add_to_begin=True) + node.write_manager.register_batch_handler(TokenBatchHandler(node.db_manager), add_to_begin=True) + node.write_manager.register_batch_handler(TrackerBatchHandler(node.db_manager)) + node.write_manager.register_batch_handler(node.write_manager.audit_b_handler, + ledger_id=TOKEN_LEDGER_ID) + + +def register_authentication(node): + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node.states[DOMAIN_LEDGER_ID]) + node.clientAuthNr.register_authenticator(token_authnr) diff --git a/sovtoken/sovtoken/request_handlers/__init__.py b/sovtoken/sovtoken/request_handlers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtoken/sovtoken/request_handlers/batch_req_handler/__init__.py b/sovtoken/sovtoken/request_handlers/batch_req_handler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtoken/sovtoken/request_handlers/batch_req_handler/token_batch_handler.py b/sovtoken/sovtoken/request_handlers/batch_req_handler/token_batch_handler.py new file mode 100644 index 00000000..9833bc82 --- /dev/null +++ b/sovtoken/sovtoken/request_handlers/batch_req_handler/token_batch_handler.py @@ -0,0 +1,16 @@ +from sovtoken import TOKEN_LEDGER_ID + +from plenum.server.batch_handlers.batch_request_handler import BatchRequestHandler +from plenum.server.database_manager import DatabaseManager + + +class TokenBatchHandler(BatchRequestHandler): + + def __init__(self, database_manager: DatabaseManager): + super().__init__(database_manager, TOKEN_LEDGER_ID) + + def post_batch_applied(self, three_pc_batch, prev_handler_result=None): + pass + + def post_batch_rejected(self, ledger_id, prev_handler_result=None): + pass diff --git a/sovtoken/sovtoken/request_handlers/batch_req_handler/tracker_batch_handler.py b/sovtoken/sovtoken/request_handlers/batch_req_handler/tracker_batch_handler.py new file mode 100644 index 00000000..c1950fff --- /dev/null +++ b/sovtoken/sovtoken/request_handlers/batch_req_handler/tracker_batch_handler.py @@ -0,0 +1,31 @@ +from sovtoken import TOKEN_LEDGER_ID + +from plenum.server.batch_handlers.batch_request_handler import BatchRequestHandler + + +class TrackerBatchHandler(BatchRequestHandler): + def __init__(self, database_manager): + super().__init__(database_manager, TOKEN_LEDGER_ID) + + @property + def token_state(self): + return self.database_manager.get_state(TOKEN_LEDGER_ID) + + @property + def token_ledger(self): + return self.database_manager.get_ledger(TOKEN_LEDGER_ID) + + @property + def token_tracker(self): + return self.database_manager.get_tracker(TOKEN_LEDGER_ID) + + def post_batch_applied(self, three_pc_batch, prev_handler_result=None): + self.token_tracker.apply_batch(self.token_state.headHash, + self.token_ledger.uncommitted_root_hash, + self.token_ledger.uncommitted_size) + + def post_batch_rejected(self, ledger_id, prev_handler_result=None): + self.token_tracker.reject_batch() + + def commit_batch(self, three_pc_batch, prev_handler_result=None): + self.token_tracker.commit_batch() \ No newline at end of file diff --git a/sovtoken/sovtoken/request_handlers/batch_req_handler/utxo_batch_handler.py b/sovtoken/sovtoken/request_handlers/batch_req_handler/utxo_batch_handler.py new file mode 100644 index 00000000..a752f0bc --- /dev/null +++ b/sovtoken/sovtoken/request_handlers/batch_req_handler/utxo_batch_handler.py @@ -0,0 +1,35 @@ +import base58 +from sovtoken.constants import UTXO_CACHE_LABEL, TOKEN_LEDGER_ID +from sovtoken.exceptions import TokenValueError + +from plenum.server.database_manager import DatabaseManager + +from plenum.server.batch_handlers.batch_request_handler import BatchRequestHandler + + +class UTXOBatchHandler(BatchRequestHandler): + + def __init__(self, database_manager: DatabaseManager): + super().__init__(database_manager, TOKEN_LEDGER_ID) + + @property + def utxo_cache(self): + return self.database_manager.get_store(UTXO_CACHE_LABEL) + + def post_batch_rejected(self, ledger_id, prev_handler_result=None): + self.utxo_cache.reject_batch() + + def post_batch_applied(self, three_pc_batch, prev_handler_result=None): + self.utxo_cache.create_batch_from_current(three_pc_batch.state_root) + + def commit_batch(self, three_pc_batch, prev_handler_result=None): + state_root = three_pc_batch.state_root + state_root = base58.b58decode(state_root.encode()) if isinstance( + state_root, str) else state_root + if self.utxo_cache.first_batch_idr != state_root: + raise TokenValueError( + 'state_root', state_root, + ("equal to utxo_cache.first_batch_idr hash {}" + .format(self.utxo_cache.first_batch_idr)) + ) + self.utxo_cache.commit_batch() diff --git a/sovtoken/sovtoken/request_handlers/read_req_handler/__init__.py b/sovtoken/sovtoken/request_handlers/read_req_handler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtoken/sovtoken/request_handlers/read_req_handler/get_utxo_handler.py b/sovtoken/sovtoken/request_handlers/read_req_handler/get_utxo_handler.py new file mode 100644 index 00000000..589275ba --- /dev/null +++ b/sovtoken/sovtoken/request_handlers/read_req_handler/get_utxo_handler.py @@ -0,0 +1,65 @@ +from sovtoken import TokenTransactions +from sovtoken.constants import ADDRESS, OUTPUTS, TOKEN_LEDGER_ID +from sovtoken.messages.txn_validator import txt_get_utxo_validate +from sovtoken.request_handlers.token_utils import parse_state_key +from sovtoken.types import Output +from sovtoken.util import SortedItems + +from common.serializers.serialization import state_roots_serializer, proof_nodes_serializer +from plenum.common.constants import MULTI_SIGNATURE, ROOT_HASH, PROOF_NODES, STATE_PROOF +from plenum.common.exceptions import InvalidClientRequest +from plenum.common.request import Request +from plenum.common.types import f +from plenum.server.database_manager import DatabaseManager +from plenum.server.request_handlers.handler_interfaces.read_request_handler import ReadRequestHandler +from state.trie.pruning_trie import rlp_decode + + +class GetUtxoHandler(ReadRequestHandler): + def __init__(self, database_manager: DatabaseManager): + super().__init__(database_manager, TokenTransactions.GET_UTXO.value, TOKEN_LEDGER_ID) + + def static_validation(self, request: Request): + error = txt_get_utxo_validate(request) + + if error: + raise InvalidClientRequest(request.identifier, + request.reqId, + error) + + def get_result(self, request: Request): + address = request.operation[ADDRESS] + encoded_root_hash = state_roots_serializer.serialize( + bytes(self.state.committedHeadHash)) + proof, rv = self.state.generate_state_proof_for_keys_with_prefix(address, + serialize=True, + get_value=True) + multi_sig = self.database_manager.bls_store.get(encoded_root_hash) + if multi_sig: + encoded_proof = proof_nodes_serializer.serialize(proof) + proof = { + MULTI_SIGNATURE: multi_sig.as_dict(), + ROOT_HASH: encoded_root_hash, + PROOF_NODES: encoded_proof + } + else: + proof = {} + + # The outputs need to be returned in sorted order since each node's reply should be same. + # Since no of outputs can be large, a concious choice to not use `operator.attrgetter` on an + # already constructed list was made + outputs = SortedItems() + for k, v in rv.items(): + addr, seq_no = parse_state_key(k.decode()) + amount = rlp_decode(v)[0] + if not amount: + continue + outputs.add(Output(addr, int(seq_no), int(amount))) + + result = {f.IDENTIFIER.nm: request.identifier, + f.REQ_ID.nm: request.reqId, OUTPUTS: outputs.sorted_list} + if proof: + result[STATE_PROOF] = proof + + result.update(request.operation) + return result diff --git a/sovtoken/sovtoken/request_handlers/token_utils.py b/sovtoken/sovtoken/request_handlers/token_utils.py new file mode 100644 index 00000000..0a444dcd --- /dev/null +++ b/sovtoken/sovtoken/request_handlers/token_utils.py @@ -0,0 +1,100 @@ +from typing import Optional, List + +import base58 +from sovtoken.constants import INPUTS, OUTPUTS +from sovtoken.exceptions import UTXOError, InvalidFundsError, ExtraFundsError, InsufficientFundsError, TokenValueError +from sovtoken.types import Output +from sovtoken.utxo_cache import UTXOCache + +from plenum.common.exceptions import InvalidClientMessageException +from plenum.common.request import Request +from plenum.common.types import f + + +def spend_input(state, utxo_cache: UTXOCache, address, seq_no, is_committed=False): + state_key = create_state_key(address, seq_no) + state.set(state_key, b'') + utxo_cache.spend_output(Output(address, seq_no, None), + is_committed=is_committed) + + +def add_new_output(state, utxo_cache: UTXOCache, output: Output, is_committed=False): + address = output.address + seq_no = output.seqNo + amount = output.amount + state_key = create_state_key(address, seq_no) + state.set(state_key, str(amount).encode()) + utxo_cache.add_output(output, is_committed=is_committed) + + +def create_state_key(address: str, seq_no: int) -> bytes: + return ':'.join([address, str(seq_no)]).encode() + + +def parse_state_key(key: str) -> List[str]: + return key.split(':') + + +def sum_inputs(utxo_cache: UTXOCache, request: Request, is_committed=False) -> int: + try: + inputs = request.operation[INPUTS] + return utxo_cache.sum_inputs(inputs, is_committed=is_committed) + except UTXOError as ex: + raise InvalidFundsError(request.identifier, request.reqId, '{}'.format(ex)) + + +def sum_outputs(request: Request) -> int: + return sum(o["amount"] for o in request.operation[OUTPUTS]) + + +def validate_given_inputs_outputs(inputs_sum, outputs_sum, required_amount, request, + error_msg_suffix: Optional[str] = None): + """ + Checks three sum values against simple set of rules. inputs_sum must be equal to required_amount. Exceptions + are raise if it is not equal. The outputs_sum is pass not for checks but to be included in error messages. + This is confusing but is required in cases where the required amount is different then the sum of outputs ( + in the case of fees). + + :param inputs_sum: the sum of inputs + :param outputs_sum: the sum of outputs + :param required_amount: the required amount to validate (could be equal to output_sum, but may be different) + :param request: the request that is being validated + :param error_msg_suffix: added message to the error message + :return: returns if valid or will raise an exception + """ + + if inputs_sum == required_amount: + return # Equal is valid + elif inputs_sum > required_amount: + error = 'Extra funds, sum of inputs is {} ' \ + 'but required amount: {} -- sum of outputs: {}'.format(inputs_sum, required_amount, outputs_sum) + if error_msg_suffix and isinstance(error_msg_suffix, str): + error += ' ' + error_msg_suffix + raise ExtraFundsError(getattr(request, f.IDENTIFIER.nm, None), + getattr(request, f.REQ_ID.nm, None), + error) + + elif inputs_sum < required_amount: + error = 'Insufficient funds, sum of inputs is {}' \ + 'but required amount is {}. sum of outputs: {}'.format(inputs_sum, required_amount, outputs_sum) + if error_msg_suffix and isinstance(error_msg_suffix, str): + error += ' ' + error_msg_suffix + raise InsufficientFundsError(getattr(request, f.IDENTIFIER.nm, None), + getattr(request, f.REQ_ID.nm, None), + error) + + raise InvalidClientMessageException(getattr(request, f.IDENTIFIER.nm, None), + getattr(request, f.REQ_ID.nm, None), + 'Request to not meet minimum requirements') + + +def commit_to_utxo_cache(utxo_cache, state_root): + state_root = base58.b58decode(state_root.encode()) if isinstance( + state_root, str) else state_root + if utxo_cache.first_batch_idr != state_root: + raise TokenValueError( + 'state_root', state_root, + ("equal to utxo_cache.first_batch_idr hash {}" + .format(utxo_cache.first_batch_idr)) + ) + utxo_cache.commit_batch() diff --git a/sovtoken/sovtoken/request_handlers/write_request_handler/__init__.py b/sovtoken/sovtoken/request_handlers/write_request_handler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtoken/sovtoken/request_handlers/write_request_handler/mint_handler.py b/sovtoken/sovtoken/request_handlers/write_request_handler/mint_handler.py new file mode 100644 index 00000000..c9d4aa72 --- /dev/null +++ b/sovtoken/sovtoken/request_handlers/write_request_handler/mint_handler.py @@ -0,0 +1,52 @@ +from sovtoken import TokenTransactions +from sovtoken.constants import MINT_PUBLIC, OUTPUTS, TOKEN_LEDGER_ID, UTXO_CACHE_LABEL +from sovtoken.exceptions import UTXOError +from sovtoken.messages.txn_validator import txn_mint_public_validate +from sovtoken.request_handlers.token_utils import add_new_output +from sovtoken.types import Output + +from indy_common.authorize.auth_actions import AuthActionAdd + +from plenum.common.exceptions import InvalidClientRequest, OperationError +from plenum.common.request import Request +from plenum.common.txn_util import get_seq_no, get_payload_data +from plenum.server.database_manager import DatabaseManager +from plenum.server.request_handlers.handler_interfaces.write_request_handler import WriteRequestHandler + + +class MintHandler(WriteRequestHandler): + + # it is not really clear what should be returned here for XFER + def gen_state_key(self, txn): + pass + + def __init__(self, database_manager: DatabaseManager, write_req_validator): + super().__init__(database_manager, TokenTransactions.MINT_PUBLIC.value, TOKEN_LEDGER_ID) + self._write_req_validator = write_req_validator + + def static_validation(self, request: Request): + error = txn_mint_public_validate(request) + + if error: + raise InvalidClientRequest(request.identifier, + request.reqId, + error) + + def dynamic_validation(self, request: Request): + return self._write_req_validator.validate(request, + [AuthActionAdd(txn_type=MINT_PUBLIC, + field="*", + value="*")]) + + def update_state(self, txn, prev_result, request, is_committed=False): + try: + payload = get_payload_data(txn) + seq_no = get_seq_no(txn) + for output in payload[OUTPUTS]: + add_new_output(self.state, + self.database_manager.get_store(UTXO_CACHE_LABEL), + Output(output["address"], seq_no, output["amount"]), + is_committed=is_committed) + except UTXOError as ex: + error = 'Exception {} while updating state'.format(ex) + raise OperationError(error) diff --git a/sovtoken/sovtoken/request_handlers/write_request_handler/xfer_handler.py b/sovtoken/sovtoken/request_handlers/write_request_handler/xfer_handler.py new file mode 100644 index 00000000..853e3c21 --- /dev/null +++ b/sovtoken/sovtoken/request_handlers/write_request_handler/xfer_handler.py @@ -0,0 +1,81 @@ +from sovtoken import TokenTransactions +from sovtoken.exceptions import UTXOError + +from indy_common.authorize.auth_actions import AuthActionAdd +from sovtoken.constants import INPUTS, OUTPUTS, XFER_PUBLIC, TOKEN_LEDGER_ID, UTXO_CACHE_LABEL +from sovtoken.messages.txn_validator import txn_xfer_public_validate +from sovtoken.request_handlers.token_utils import spend_input, add_new_output, sum_inputs, sum_outputs, \ + validate_given_inputs_outputs +from sovtoken.types import Output + +from plenum.common.exceptions import InvalidClientMessageException, InvalidClientRequest, OperationError +from plenum.common.request import Request +from plenum.common.txn_util import get_payload_data, get_seq_no +from plenum.server.database_manager import DatabaseManager +from plenum.server.request_handlers.handler_interfaces.write_request_handler import WriteRequestHandler + + +class XferHandler(WriteRequestHandler): + + # it is not really clear what should be returned here for MINT + def gen_state_key(self, txn): + pass + + def __init__(self, database_manager: DatabaseManager, write_req_validator): + super().__init__(database_manager, TokenTransactions.XFER_PUBLIC.value, TOKEN_LEDGER_ID) + self._write_req_validator = write_req_validator + + def static_validation(self, request: Request): + error = txn_xfer_public_validate(request) + if error: + raise InvalidClientRequest(request.identifier, + request.reqId, + error) + + def dynamic_validation(self, request: Request): + self._do_validate_inputs_ouputs(request) + return self._write_req_validator.validate(request, [AuthActionAdd(txn_type=XFER_PUBLIC, + field="*", + value="*")]) + + @property + def utxo_cache(self): + return self.database_manager.get_store(UTXO_CACHE_LABEL) + + def update_state(self, txn, prev_result, request, is_committed=False): + try: + payload = get_payload_data(txn) + for inp in payload[INPUTS]: + spend_input(self.state, + self.utxo_cache, + inp["address"], + inp["seqNo"], + is_committed=is_committed) + for output in payload[OUTPUTS]: + seq_no = get_seq_no(txn) + add_new_output(self.state, + self.utxo_cache, + Output(output["address"], + seq_no, + output["amount"]), + is_committed=is_committed) + except UTXOError as ex: + error = 'Exception {} while updating state'.format(ex) + raise OperationError(error) + + def _do_validate_inputs_ouputs(self, request): + try: + sum_in = sum_inputs(self.utxo_cache, + request, + is_committed=False) + + sum_out = sum_outputs(request) + except Exception as ex: + if isinstance(ex, InvalidClientMessageException): + raise ex + error = 'Exception {} while processing inputs/outputs'.format(ex) + raise InvalidClientMessageException(request.identifier, + getattr(request, 'reqId', None), + error) + else: + return validate_given_inputs_outputs(sum_in, sum_out, sum_out, request) diff --git a/sovtoken/sovtoken/storage.py b/sovtoken/sovtoken/storage.py deleted file mode 100644 index 8357071a..00000000 --- a/sovtoken/sovtoken/storage.py +++ /dev/null @@ -1,27 +0,0 @@ -from ledger.compact_merkle_tree import CompactMerkleTree -from plenum.common.ledger import Ledger -from plenum.persistence.db_hash_store import DbHashStore -from storage.helper import initKeyValueStorage, initHashStore -from sovtoken.utxo_cache import UTXOCache -from state.pruning_state import PruningState - - -def get_token_hash_store(data_dir): - return initHashStore(data_dir=data_dir, name='sovtoken') - - -def get_token_ledger(data_dir, name, hash_store, config): - return Ledger(CompactMerkleTree(hashStore=hash_store), - dataDir=data_dir, - fileName=name, - ensureDurability=config.EnsureLedgerDurability) - - -def get_token_state(data_dir, name, config): - return PruningState(initKeyValueStorage( - config.tokenStateStorage, data_dir, name)) - - -def get_utxo_cache(data_dir, name, config): - return UTXOCache(initKeyValueStorage( - config.utxoCacheStorage, data_dir, name)) diff --git a/sovtoken/sovtoken/test/helper.py b/sovtoken/sovtoken/test/helper.py index 2f60c8bf..ad6d4190 100644 --- a/sovtoken/sovtoken/test/helper.py +++ b/sovtoken/sovtoken/test/helper.py @@ -39,7 +39,7 @@ def send_xfer(looper, inputs, outputs, sdk_pool_handle, extra_data=None): def check_output_val_on_all_nodes(nodes, address, amount): for node in nodes: - handler = node.get_req_handler(ledger_id=TOKEN_LEDGER_ID) + handler = node.write_manager.request_handlers[XFER_PUBLIC][0] assert int(amount) in [out.amount for out in handler.utxo_cache.get_unspent_outputs( address, is_committed=True)] diff --git a/sovtoken/sovtoken/test/helpers/helper_inner_request.py b/sovtoken/sovtoken/test/helpers/helper_inner_request.py index 2ea543a7..70c6c478 100644 --- a/sovtoken/sovtoken/test/helpers/helper_inner_request.py +++ b/sovtoken/sovtoken/test/helpers/helper_inner_request.py @@ -113,7 +113,7 @@ def nym( '' => Standard User, '0' => Trustee, '2' => Steward, - '101' => Trust Anchor, + '101' => Endorser, """ sdk_wallet_did = self._find_wallet_did(sdk_wallet) diff --git a/sovtoken/sovtoken/test/helpers/helper_node.py b/sovtoken/sovtoken/test/helpers/helper_node.py index 032d74da..908588fa 100644 --- a/sovtoken/sovtoken/test/helpers/helper_node.py +++ b/sovtoken/sovtoken/test/helpers/helper_node.py @@ -1,5 +1,6 @@ +from indy_common.constants import NYM, AUTH_RULE from plenum.common.constants import DOMAIN_LEDGER_ID, CONFIG_LEDGER_ID -from sovtoken.constants import TOKEN_LEDGER_ID +from sovtoken.constants import TOKEN_LEDGER_ID, XFER_PUBLIC, GET_UTXO, MINT_PUBLIC class HelperNode(): @@ -29,16 +30,21 @@ def get_last_ledger_transaction_on_nodes(self, ledger_id): return transactions - def get_token_req_handler(self): - """ Get the token request handler of the first node. """ - return self._nodes[0].get_req_handler(ledger_id=TOKEN_LEDGER_ID) + @property + def xfer_handler(self): + """ Get the xfer request handler of the first node. """ + return self._nodes[0].write_manager.request_handlers[XFER_PUBLIC][0] - def get_domain_req_handler(self): - """ Get the domain request handler of the first node. """ - return self._nodes[0].get_req_handler(ledger_id=DOMAIN_LEDGER_ID) + @property + def mint_handler(self): + """ Get the xfer request handler of the first node. """ + return self._nodes[0].write_manager.request_handlers[MINT_PUBLIC][0] - def get_config_req_handler(self): - return self._nodes[0].get_req_handler(ledger_id=CONFIG_LEDGER_ID) + @property + def nym_handler(self): + """ Get the NYM request handler of the first node. """ + return self._nodes[0].write_manager.request_handlers[NYM][0] - def get_fee_req_handler(self): - return self._nodes[0].get_req_handler(ledger_id=CONFIG_LEDGER_ID) + @property + def get_utxo_handler(self): + return self._nodes[0].read_manager.request_handlers[GET_UTXO] diff --git a/sovtoken/sovtoken/test/helpers/helper_request.py b/sovtoken/sovtoken/test/helpers/helper_request.py index 6e60fd60..e473f4dc 100644 --- a/sovtoken/sovtoken/test/helpers/helper_request.py +++ b/sovtoken/sovtoken/test/helpers/helper_request.py @@ -122,7 +122,7 @@ def nym( '' => Standard User, '0' => Trustee, '2' => Steward, - '101' => Trust Anchor, + '101' => Endorser, """ sdk_wallet_did = self._find_wallet_did(sdk_wallet) diff --git a/sovtoken/sovtoken/test/helpers/helper_wallet.py b/sovtoken/sovtoken/test/helpers/helper_wallet.py index 44ccdfff..8a71b954 100644 --- a/sovtoken/sovtoken/test/helpers/helper_wallet.py +++ b/sovtoken/sovtoken/test/helpers/helper_wallet.py @@ -43,7 +43,7 @@ def create_address(self, wallet=None): address = self._looper.loop.run_until_complete(address) addr = Address() addr.address = address - self.address_map[address.replace("pay:sov:", "")] = addr + self.address_map[address] = addr return address def create_new_addresses(self, n): @@ -99,7 +99,7 @@ def handle_xfer(self, response): def _update_inputs(self, inputs): for inp in inputs: - addr = inp["address"] + addr = "pay:sov:{}".format(inp["address"]) seq_no = inp["seqNo"] if addr in self.address_map: self.address_map[addr].spent(seq_no) @@ -107,7 +107,7 @@ def _update_inputs(self, inputs): def _update_outputs(self, outputs, txn_seq_no=None): for output in outputs: try: - addr = output["address"] + addr = "pay:sov:{}".format(output["address"]) val = output["amount"] try: seq_no = output["seqNo"] diff --git a/sovtoken/sovtoken/test/req_handlers/conftest.py b/sovtoken/sovtoken/test/req_handlers/conftest.py new file mode 100644 index 00000000..61957c48 --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/conftest.py @@ -0,0 +1,98 @@ +import json + +import pytest +from base58 import b58decode +from sovtoken.constants import UTXO_CACHE_LABEL +from sovtokenfees.static_fee_req_handler import txn_root_serializer + +from indy_node.test.request_handlers.helper import get_fake_ledger +from sovtoken import TOKEN_LEDGER_ID +from sovtoken.utxo_cache import UTXOCache +from indy.payment import create_payment_address +from indy.wallet import create_wallet, open_wallet, close_wallet, delete_wallet + +from common.serializers import serialization +from plenum.common.constants import KeyValueStorageType, BLS_LABEL +from plenum.common.txn_util import append_txn_metadata +from plenum.common.util import randomString +from plenum.server.database_manager import DatabaseManager +from plenum.test.testing_utils import FakeSomething +from state.pruning_state import PruningState +from storage.helper import initKeyValueStorage + +FAKE_UNCOMMITTED_ROOT_HASH = b58decode("1".encode()) +FAKE_COMMITTED_ROOT_HASH = b58decode("1".encode()) + + +@pytest.fixture(scope="module") +def bls_store(db_manager): + multi_sigs = FakeSomething() + multi_sigs.as_dict = lambda: {"a": "b"} + bls = FakeSomething() + bls.get = lambda _: multi_sigs + db_manager.register_new_store(BLS_LABEL, bls) + return bls + + +@pytest.fixture(scope="module") +def db_manager(tconf): + db_manager = DatabaseManager() + storage = initKeyValueStorage(KeyValueStorageType.Memory, + None, + "tokenInMemoryStore", + txn_serializer=serialization.multi_sig_store_serializer) + ledger = get_fake_ledger() + ledger.commitTxns = lambda x: (None, [1]) + ledger.root_hash = txn_root_serializer.serialize("1") + ledger.uncommitted_root_hash = "1" + ledger.uncommitted_size = 1 + ledger.size = 0 + ledger.discardTxns = lambda x: None + ledger.committed_root_hash = "-1" + ledger.append_txns_metadata = lambda txns, txn_time: [append_txn_metadata(txn, 2, txn_time, 2) for txn in txns] + ledger.appendTxns = lambda x: (None, x) + db_manager.register_new_database(TOKEN_LEDGER_ID, ledger, PruningState(storage)) + return db_manager + + +@pytest.yield_fixture(scope="module") +def utxo_cache(db_manager): + cache = UTXOCache(initKeyValueStorage( + KeyValueStorageType.Memory, None, "utxoInMemoryStore")) + db_manager.register_new_store(UTXO_CACHE_LABEL, cache) + yield cache + if cache.un_committed: + cache.reject_batch() + + +@pytest.fixture(scope="module") +def payment_address(libsovtoken, looper, wallet): + payment_address_future = create_payment_address(wallet, "sov", "{}") + payment_address = looper.loop.run_until_complete(payment_address_future) + return payment_address + + +@pytest.fixture(scope="module") +def payment_address_2(libsovtoken, looper, wallet): + payment_address_future = create_payment_address(wallet, "sov", "{}") + payment_address = looper.loop.run_until_complete(payment_address_future) + return payment_address + + +@pytest.yield_fixture(scope="module") +def wallet(looper): + wallet_name = randomString() + + create_wallet_future = create_wallet(json.dumps({"id": wallet_name}), json.dumps({"key": "1"})) + looper.loop.run_until_complete(create_wallet_future) + + open_wallet_future = open_wallet(json.dumps({"id": wallet_name}), json.dumps({"key": "1"})) + wallet_handle = looper.loop.run_until_complete(open_wallet_future) + + yield wallet_handle + + close_wallet_future = close_wallet(wallet_handle) + looper.loop.run_until_complete(close_wallet_future) + + delete_wallet_future = delete_wallet(json.dumps({"id": wallet_name}), json.dumps({"key": "1"})) + looper.loop.run_until_complete(delete_wallet_future) diff --git a/sovtoken/sovtoken/test/req_handlers/get_utxo_req_handler/conftest.py b/sovtoken/sovtoken/test/req_handlers/get_utxo_req_handler/conftest.py new file mode 100644 index 00000000..d3a03ec5 --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/get_utxo_req_handler/conftest.py @@ -0,0 +1,24 @@ +import json + +import pytest + +from sovtoken import TOKEN_LEDGER_ID, TokenTransactions +from sovtoken.request_handlers.read_req_handler.get_utxo_handler import GetUtxoHandler + +from plenum.test.helper import sdk_json_to_request_object + +from indy.payment import build_get_payment_sources_request + + +@pytest.fixture(scope="module") +def get_utxo_handler(db_manager, bls_store): + return GetUtxoHandler(database_manager=db_manager) + + +@pytest.fixture() +def get_utxo_request(looper, payment_address, wallet): + get_utxo_request_future = build_get_payment_sources_request(wallet, None, payment_address) + get_utxo_request, _ = looper.loop.run_until_complete(get_utxo_request_future) + get_utxo_request = json.loads(get_utxo_request) + get_utxo_request = sdk_json_to_request_object(get_utxo_request) + return get_utxo_request diff --git a/sovtoken/sovtoken/test/req_handlers/get_utxo_req_handler/test_get_utxo_get_result.py b/sovtoken/sovtoken/test/req_handlers/get_utxo_req_handler/test_get_utxo_get_result.py new file mode 100644 index 00000000..ede223ed --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/get_utxo_req_handler/test_get_utxo_get_result.py @@ -0,0 +1,27 @@ +import pytest +from sovtoken.constants import OUTPUTS, ADDRESS +from sovtoken.request_handlers.token_utils import create_state_key + +from plenum.common.constants import STATE_PROOF + + +@pytest.fixture(scope="module", autouse=True) +def add_utxo(payment_address, get_utxo_handler): + get_utxo_handler.state.set(create_state_key(payment_address.replace("pay:sov:", ""), 1), "3".encode()) + + +def test_get_utxo_request_has_utxos(get_utxo_request, get_utxo_handler, payment_address, add_utxo): + result = get_utxo_handler.get_result(get_utxo_request) + assert result[STATE_PROOF] + assert result[OUTPUTS] + assert len(result[OUTPUTS]) == 1 + assert result[OUTPUTS][0].address == payment_address.replace("pay:sov:", "") + assert result[OUTPUTS][0].amount == 3 + assert result[OUTPUTS][0].seqNo == 1 + + +def test_get_utxo_request_no_utxos(get_utxo_request, get_utxo_handler, payment_address_2, add_utxo): + get_utxo_request.operation[ADDRESS] = payment_address_2.replace("pay:sov:", "") + result = get_utxo_handler.get_result(get_utxo_request) + assert result[STATE_PROOF] + assert len(result[OUTPUTS]) == 0 diff --git a/sovtoken/sovtoken/test/req_handlers/get_utxo_req_handler/test_get_utxo_static_validation.py b/sovtoken/sovtoken/test/req_handlers/get_utxo_req_handler/test_get_utxo_static_validation.py new file mode 100644 index 00000000..44ed5444 --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/get_utxo_req_handler/test_get_utxo_static_validation.py @@ -0,0 +1,38 @@ +import json + +from base58 import b58encode_check + +import pytest +from sovtoken.constants import ADDRESS + +from plenum.common.exceptions import InvalidClientRequest, UnknownIdentifier +from plenum.common.util import randomString +from plenum.test.helper import sdk_json_to_request_object + + +def test_valid_get_utxo_request(get_utxo_handler, get_utxo_request): + get_utxo_handler.static_validation(get_utxo_request) + + +def test_get_utxo_request_no_address(get_utxo_handler, get_utxo_request): + operation = get_utxo_request.operation + del operation[ADDRESS] + with pytest.raises(InvalidClientRequest, match="address needs to be provided"): + get_utxo_handler.static_validation(get_utxo_request) + + +def test_get_utxo_request_invalid_checksum_address(get_utxo_handler, get_utxo_request): + operation = get_utxo_request.operation + operation[ADDRESS] = operation[ADDRESS][:-1] + '1' if '1' != operation[ADDRESS][-1] else '2' + with pytest.raises(UnknownIdentifier, + match="{} is not a valid base58check value".format(operation[ADDRESS].encode())): + get_utxo_handler.static_validation(get_utxo_request) + + +def test_get_utxo_request_invalid_vk_length(get_utxo_handler, get_utxo_request): + operation = get_utxo_request.operation + addr = b58encode_check(randomString(31).encode()).decode() + operation[ADDRESS] = addr + with pytest.raises(InvalidClientRequest, + match="Not a valid address as it resolves to 31 byte verkey"): + get_utxo_handler.static_validation(get_utxo_request) diff --git a/sovtoken/sovtoken/test/req_handlers/mint_req_handler/conftest.py b/sovtoken/sovtoken/test/req_handlers/mint_req_handler/conftest.py new file mode 100644 index 00000000..0a7f21a1 --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/mint_req_handler/conftest.py @@ -0,0 +1,69 @@ +import json + +import pytest +from sovtoken.sovtoken_auth_map import sovtoken_auth_map +from sovtoken import TokenTransactions, TOKEN_LEDGER_ID +from sovtoken.request_handlers.write_request_handler.mint_handler import MintHandler + +from indy.payment import build_mint_req +from indy.did import create_and_store_my_did +from indy.ledger import multi_sign_request + +from plenum.common.txn_util import append_txn_metadata +from plenum.test.helper import sdk_json_to_request_object + +from indy_common.test.auth.conftest import write_auth_req_validator, constraint_serializer, config_state +from plenum.test.testing_utils import FakeSomething + + +@pytest.fixture(scope="module") +def mint_handler(utxo_cache, db_manager, write_auth_req_validator): + write_auth_req_validator.auth_map.update(sovtoken_auth_map) + return MintHandler(database_manager=db_manager, + write_req_validator=write_auth_req_validator) + + +@pytest.fixture(scope="module") +def idr_cache(): + idr_cache = FakeSomething() + idr_cache.users = {} + + def getRole(idr, isCommitted=True): + return idr_cache.users.get(idr, None) + + idr_cache.getRole = getRole + return idr_cache + + +@pytest.fixture() +def mint_request(libsovtoken, payment_address, wallet, trustees, looper): + mint_future = build_mint_req(wallet, + trustees[0], + json.dumps([{"recipient": payment_address, "amount": 10}]), + None) + mint_request, _ = looper.loop.run_until_complete(mint_future) + for trustee in trustees: + mint_future = multi_sign_request(wallet, trustee, mint_request) + mint_request = looper.loop.run_until_complete(mint_future) + mint_request = json.loads(mint_request) + sigs = mint_request["signatures"] + mint_request = sdk_json_to_request_object(mint_request) + setattr(mint_request, "signatures", sigs) + return mint_request + + +@pytest.fixture() +def mint_txn(mint_handler, mint_request): + mint_txn = mint_handler._req_to_txn(mint_request) + return append_txn_metadata(mint_txn, 1, 1, 1) + + +@pytest.fixture(scope="module") +def trustees(libsovtoken, wallet, looper, idr_cache): + trustees = [] + for i in range(3): + did_future = create_and_store_my_did(wallet, "{}") + did, vk = looper.loop.run_until_complete(did_future) + trustees.append(did) + idr_cache.users[did] = "0" + return trustees diff --git a/sovtoken/sovtoken/test/req_handlers/mint_req_handler/test_mint_req_handler_dynamic_validation.py b/sovtoken/sovtoken/test/req_handlers/mint_req_handler/test_mint_req_handler_dynamic_validation.py new file mode 100644 index 00000000..62b62060 --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/mint_req_handler/test_mint_req_handler_dynamic_validation.py @@ -0,0 +1,20 @@ +import pytest +from sovtoken.test.constants import VALID_IDENTIFIER + +from plenum.common.exceptions import UnauthorizedClientRequest + + +def test_mint_handler_dynamic_validation_valid_request(mint_handler, mint_request): + mint_handler.dynamic_validation(mint_request) + + +def test_mint_handler_dynamic_validation_not_enough_signatures(mint_handler, mint_request): + mint_request.signatures = dict([(k, v) for k, v in mint_request.signatures.items()][:-1]) + with pytest.raises(UnauthorizedClientRequest, match='Not enough TRUSTEE signatures'): + mint_handler.dynamic_validation(mint_request) + + +def test_mint_handler_dynamic_validation_unknown_identifier(mint_handler, mint_request): + mint_request._identifier = VALID_IDENTIFIER + with pytest.raises(UnauthorizedClientRequest, match="sender's DID {} is not found in the Ledger".format(VALID_IDENTIFIER)): + mint_handler.dynamic_validation(mint_request) \ No newline at end of file diff --git a/sovtoken/sovtoken/test/req_handlers/mint_req_handler/test_mint_req_handler_static_validation.py b/sovtoken/sovtoken/test/req_handlers/mint_req_handler/test_mint_req_handler_static_validation.py new file mode 100644 index 00000000..61934784 --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/mint_req_handler/test_mint_req_handler_static_validation.py @@ -0,0 +1,51 @@ +import pytest +from base58 import b58encode_check +from sovtoken.constants import OUTPUTS, AMOUNT, ADDRESS + +from plenum.common.exceptions import InvalidClientRequest, UnknownIdentifier +from plenum.common.util import randomString + + +def test_mint_handler_static_validation_valid(mint_handler, mint_request): + mint_handler.static_validation(mint_request) + + +def test_mint_handler_static_validation_no_outputs_field(mint_handler, mint_request): + del mint_request.operation[OUTPUTS] + with pytest.raises(InvalidClientRequest, match="outputs needs to be present"): + mint_handler.static_validation(mint_request) + + +def test_mint_handler_static_validation_no_outputs(mint_handler, mint_request): + mint_request.operation[OUTPUTS].clear() + with pytest.raises(InvalidClientRequest, match="Outputs for a mint request can't be empty."): + mint_handler.static_validation(mint_request) + + +def test_mint_handler_static_validation_invalid_length_address(mint_handler, mint_request): + operation = mint_request.operation + addr = b58encode_check(randomString(31).encode()).decode() + operation[OUTPUTS][0][ADDRESS] = addr + with pytest.raises(InvalidClientRequest, match="Not a valid address as it resolves to 31 byte verkey"): + mint_handler.static_validation(mint_request) + + +def test_mint_handler_static_validation_invalid_checksum_address(mint_handler, mint_request): + operation = mint_request.operation + operation[OUTPUTS][0][ADDRESS] = operation[OUTPUTS][0][ADDRESS][:-1] + '1'\ + if '1' != operation[OUTPUTS][0][ADDRESS][-1] else '2' + with pytest.raises(UnknownIdentifier, + match="{} is not a valid base58check value".format(operation[OUTPUTS][0][ADDRESS].encode())): + mint_handler.static_validation(mint_request) + + +def test_mint_handler_static_validation_invalid_amount(mint_handler, mint_request): + mint_request.operation[OUTPUTS][0][AMOUNT] = -1 + with pytest.raises(InvalidClientRequest, match="negative or zero value"): + mint_handler.static_validation(mint_request) + + +def test_mint_handler_static_validation_duplicate_addresses(mint_handler, mint_request): + mint_request.operation[OUTPUTS].append(mint_request.operation[OUTPUTS][0]) + with pytest.raises(InvalidClientRequest, match="Each output should contain unique address"): + mint_handler.static_validation(mint_request) diff --git a/sovtoken/sovtoken/test/req_handlers/mint_req_handler/test_mint_req_handler_update_state.py b/sovtoken/sovtoken/test/req_handlers/mint_req_handler/test_mint_req_handler_update_state.py new file mode 100644 index 00000000..3d93576f --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/mint_req_handler/test_mint_req_handler_update_state.py @@ -0,0 +1,11 @@ + +def test_mint_handler_update_state_valid_txn(mint_txn, mint_handler, payment_address): + mint_handler.update_state(mint_txn, None, None, is_committed=True) + + token_state = mint_handler.state + utxo_cache = mint_handler.database_manager.get_store("utxo_cache") + + assert int(token_state.get((payment_address[8:] + ":1").encode(), isCommitted=False)) == 10 + assert utxo_cache.get(payment_address[8:].encode()).decode() == '1:10' + + diff --git a/sovtoken/sovtoken/test/req_handlers/utxo_cache_batch_handler/conftest.py b/sovtoken/sovtoken/test/req_handlers/utxo_cache_batch_handler/conftest.py new file mode 100644 index 00000000..1b3bdaf1 --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/utxo_cache_batch_handler/conftest.py @@ -0,0 +1,7 @@ +import pytest +from sovtoken.request_handlers.batch_req_handler.utxo_batch_handler import UTXOBatchHandler + + +@pytest.fixture(scope="module") +def utxo_batch_handler(db_manager, utxo_cache): + return UTXOBatchHandler(db_manager) diff --git a/sovtoken/sovtoken/test/req_handlers/utxo_cache_batch_handler/test_token_batch_handler_commit_batch.py b/sovtoken/sovtoken/test/req_handlers/utxo_cache_batch_handler/test_token_batch_handler_commit_batch.py new file mode 100644 index 00000000..20231649 --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/utxo_cache_batch_handler/test_token_batch_handler_commit_batch.py @@ -0,0 +1,15 @@ +from collections import namedtuple +from base58 import b58decode +from sovtokenfees.static_fee_req_handler import txn_root_serializer + + +def test_token_batch_handler_commit_batch(utxo_batch_handler, utxo_cache): + utxo_cache.set('1', '2') + ThreePcBatch = namedtuple("ThreePcBatch", "state_root valid_digests txn_root") + three_ps_batch = ThreePcBatch(state_root=b58decode("1".encode()), valid_digests=["1"], + txn_root=txn_root_serializer.serialize("1")) + utxo_batch_handler.post_batch_applied(three_pc_batch=three_ps_batch) + utxo_batch_handler.commit_batch(three_ps_batch, None) + + assert not len(utxo_cache.current_batch_ops) + assert not len(utxo_cache.un_committed) diff --git a/sovtoken/sovtoken/test/req_handlers/utxo_cache_batch_handler/test_token_batch_handler_post_batch_applied.py b/sovtoken/sovtoken/test/req_handlers/utxo_cache_batch_handler/test_token_batch_handler_post_batch_applied.py new file mode 100644 index 00000000..f4e84b1e --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/utxo_cache_batch_handler/test_token_batch_handler_post_batch_applied.py @@ -0,0 +1,12 @@ +from collections import namedtuple + + +def test_token_batch_handler_post_batch_applied(utxo_batch_handler, utxo_cache): + utxo_cache.set('1', '2') + ThreePcBatch = namedtuple("ThreePcBatch", "state_root") + three_ps_batch = ThreePcBatch(state_root="1") + utxo_batch_handler.post_batch_applied(three_pc_batch=three_ps_batch) + + assert not len(utxo_cache.current_batch_ops) + assert len(utxo_cache.un_committed) == 1 + assert utxo_cache.un_committed[0] == ('1', {'1': '2'}) diff --git a/sovtoken/sovtoken/test/req_handlers/utxo_cache_batch_handler/test_token_batch_handler_post_batch_rejected.py b/sovtoken/sovtoken/test/req_handlers/utxo_cache_batch_handler/test_token_batch_handler_post_batch_rejected.py new file mode 100644 index 00000000..bf2d3941 --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/utxo_cache_batch_handler/test_token_batch_handler_post_batch_rejected.py @@ -0,0 +1,14 @@ +from collections import namedtuple + +from sovtoken import TOKEN_LEDGER_ID + + +def test_token_batch_handler_post_batch_rejected(utxo_batch_handler, utxo_cache): + utxo_cache.set('1', '2') + ThreePcBatch = namedtuple("ThreePcBatch", "state_root") + three_ps_batch = ThreePcBatch(state_root="1") + utxo_batch_handler.post_batch_applied(three_pc_batch=three_ps_batch) + utxo_batch_handler.post_batch_rejected(TOKEN_LEDGER_ID) + + assert not len(utxo_cache.current_batch_ops) + assert not len(utxo_cache.un_committed) diff --git a/sovtoken/sovtoken/test/req_handlers/xfer_req_handler/conftest.py b/sovtoken/sovtoken/test/req_handlers/xfer_req_handler/conftest.py new file mode 100644 index 00000000..39d570cd --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/xfer_req_handler/conftest.py @@ -0,0 +1,80 @@ +import json + +import pytest +from sovtoken import TokenTransactions, TOKEN_LEDGER_ID +from sovtoken.request_handlers.write_request_handler.xfer_handler import XferHandler +from sovtoken.sovtoken_auth_map import sovtoken_auth_map +from base58 import b58encode_check +from indy.payment import build_payment_req +from indy_common.test.auth.conftest import write_auth_req_validator, constraint_serializer, config_state, idr_cache +from plenum.common.txn_util import append_txn_metadata +from plenum.test.helper import sdk_json_to_request_object + + +@pytest.fixture(scope="module") +def xfer_handler(utxo_cache, db_manager, write_auth_req_validator, mint_tokens): + write_auth_req_validator.auth_map.update(sovtoken_auth_map) + return XferHandler(db_manager, + write_req_validator=write_auth_req_validator) + + +@pytest.fixture(scope="module") +def mint_tokens(payment_address, utxo_cache, db_manager): + addr = payment_address[8:] + utxo_cache.set(addr, "1:10".encode()) + db_manager.get_state(TOKEN_LEDGER_ID).set((addr + ":1").encode(), "10".encode()) + + +@pytest.fixture() +def xfer_request(libsovtoken, payment_address, payment_address_2, wallet, looper): + input = make_utxo(payment_address, 1) + output = payment_address_2 + xfer_request_future = build_payment_req(wallet, None, json.dumps([input]), + json.dumps([{"recipient": output, "amount": 10}]), None) + xfer_request, _ = looper.loop.run_until_complete(xfer_request_future) + xfer_request = sdk_json_to_request_object(json.loads(xfer_request)) + return xfer_request + + +@pytest.fixture() +def invalid_amount_xfer_request_insufficient(libsovtoken, payment_address, payment_address_2, wallet, looper): + input = make_utxo(payment_address, 1) + output = payment_address_2 + xfer_request_future = build_payment_req(wallet, None, json.dumps([input]), + json.dumps([{"recipient": output, "amount": 11}]), None) + xfer_request, _ = looper.loop.run_until_complete(xfer_request_future) + xfer_request = sdk_json_to_request_object(json.loads(xfer_request)) + return xfer_request + + +@pytest.fixture() +def invalid_amount_xfer_request_excessive(libsovtoken, payment_address, payment_address_2, wallet, looper): + input = make_utxo(payment_address, 1) + output = payment_address_2 + xfer_request_future = build_payment_req(wallet, None, json.dumps([input]), + json.dumps([{"recipient": output, "amount": 9}]), None) + xfer_request, _ = looper.loop.run_until_complete(xfer_request_future) + xfer_request = sdk_json_to_request_object(json.loads(xfer_request)) + return xfer_request + + +@pytest.fixture() +def invalid_amount_xfer_request_utxo_does_not_exist(libsovtoken, payment_address, payment_address_2, wallet, looper): + input = make_utxo(payment_address, 2) + output = payment_address_2 + xfer_request_future = build_payment_req(wallet, None, json.dumps([input]), + json.dumps([{"recipient": output, "amount": 9}]), None) + xfer_request, _ = looper.loop.run_until_complete(xfer_request_future) + xfer_request = sdk_json_to_request_object(json.loads(xfer_request)) + return xfer_request + + +@pytest.fixture() +def xfer_txn(xfer_handler, xfer_request): + xfer_txn = xfer_handler._req_to_txn(xfer_request) + return append_txn_metadata(xfer_txn, 2, 1, 1) + + +def make_utxo(addr, seq_no): + txo_inner = json.dumps({"address": addr, "seqNo": seq_no}) + return "{}{}". format("txo:sov:", b58encode_check(txo_inner.encode()).decode()) diff --git a/sovtoken/sovtoken/test/req_handlers/xfer_req_handler/test_xfer_handler_dynamic_validation.py b/sovtoken/sovtoken/test/req_handlers/xfer_req_handler/test_xfer_handler_dynamic_validation.py new file mode 100644 index 00000000..d8aacab9 --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/xfer_req_handler/test_xfer_handler_dynamic_validation.py @@ -0,0 +1,26 @@ +import pytest +from sovtoken.exceptions import InsufficientFundsError, ExtraFundsError, InvalidFundsError + + +def test_xfer_handler_dynamic_validation_valid(xfer_handler, xfer_request): + xfer_handler.dynamic_validation(xfer_request) + + +def test_xfer_handler_dynamic_validation_insufficient_funds(xfer_handler, invalid_amount_xfer_request_insufficient): + with pytest.raises(InsufficientFundsError, + match='Insufficient funds, sum of inputs is 10but required amount is 11. sum of outputs: 11'): + xfer_handler.dynamic_validation(invalid_amount_xfer_request_insufficient) + + +def test_xfer_handler_dynamic_validation_excessive(xfer_handler, invalid_amount_xfer_request_excessive): + with pytest.raises(ExtraFundsError, + match="Extra funds, sum of inputs is 10 but required amount: 9 -- sum of outputs: 9"): + xfer_handler.dynamic_validation(invalid_amount_xfer_request_excessive) + + +def test_xfer_handler_dynamic_validation_utxo_not_exists(xfer_handler, invalid_amount_xfer_request_utxo_does_not_exist, + payment_address): + with pytest.raises(InvalidFundsError, + message="InvalidFundsError(\"seq_nos {{2}} are not found in list of seq_nos_amounts for " + "address {} -- current list: ['1', '10']\",)".format(payment_address[8:])): + xfer_handler.dynamic_validation(invalid_amount_xfer_request_utxo_does_not_exist) diff --git a/sovtoken/sovtoken/test/req_handlers/xfer_req_handler/test_xfer_handler_static_validation.py b/sovtoken/sovtoken/test/req_handlers/xfer_req_handler/test_xfer_handler_static_validation.py new file mode 100644 index 00000000..b9e4f31a --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/xfer_req_handler/test_xfer_handler_static_validation.py @@ -0,0 +1,104 @@ +import pytest +from base58 import b58encode_check +from sovtoken.constants import OUTPUTS, ADDRESS, AMOUNT, INPUTS, SEQNO + +from plenum.common.exceptions import InvalidClientRequest, UnknownIdentifier +from plenum.common.util import randomString + + +def test_xfer_handler_static_validation_valid(xfer_handler, xfer_request): + xfer_handler.static_validation(xfer_request) + + +def test_xfer_handler_static_validation_no_outputs_field(xfer_handler, xfer_request): + del xfer_request.operation[OUTPUTS] + with pytest.raises(InvalidClientRequest, match="outputs needs to be present"): + xfer_handler.static_validation(xfer_request) + + +@pytest.mark.skip(reason="This test covers a missing case -- we should not be able to send XFER request without " + "outputs, it is just senseless") +def test_xfer_handler_static_validation_outputs_empty(xfer_handler, xfer_request): + xfer_request.operation[OUTPUTS].clear() + with pytest.raises(InvalidClientRequest, match="Outputs for a xfer request can't be empty."): + xfer_handler.static_validation(xfer_request) + + +def test_xfer_handler_static_validation_outputs_invalid_length_address(xfer_handler, xfer_request): + operation = xfer_request.operation + addr = b58encode_check(randomString(31).encode()).decode() + operation[OUTPUTS][0][ADDRESS] = addr + with pytest.raises(InvalidClientRequest, match="Not a valid address as it resolves to 31 byte verkey"): + xfer_handler.static_validation(xfer_request) + + +def test_xfer_handler_static_validation_invalid_checksum_address(xfer_handler, xfer_request): + operation = xfer_request.operation + operation[OUTPUTS][0][ADDRESS] = operation[OUTPUTS][0][ADDRESS][:-1] + '1'\ + if '1' != operation[OUTPUTS][0][ADDRESS][-1] else '2' + with pytest.raises(UnknownIdentifier, + match="{} is not a valid base58check value".format(operation[OUTPUTS][0][ADDRESS].encode())): + xfer_handler.static_validation(xfer_request) + + +def test_xfer_handler_static_validation_invalid_amount(xfer_handler, xfer_request): + xfer_request.operation[OUTPUTS][0][AMOUNT] = -1 + with pytest.raises(InvalidClientRequest, match="negative or zero value"): + xfer_handler.static_validation(xfer_request) + + +def test_xfer_handler_static_validation_duplicate_addresses(xfer_handler, xfer_request): + xfer_request.operation[OUTPUTS].append(xfer_request.operation[OUTPUTS][0]) + with pytest.raises(InvalidClientRequest, match="Each output should contain unique address"): + xfer_handler.static_validation(xfer_request) + + +def test_xfer_handler_static_validation_no_inputs(xfer_handler, xfer_request): + del xfer_request.operation[INPUTS] + with pytest.raises(InvalidClientRequest, match="inputs needs to be present"): + xfer_handler.static_validation(xfer_request) + + +def test_xfer_handler_static_validation_inputs_and_signatures_do_not_match(xfer_handler, xfer_request): + xfer_request.operation[INPUTS].clear() + with pytest.raises(InvalidClientRequest, match="all inputs should have signatures"): + xfer_handler.static_validation(xfer_request) + + +@pytest.mark.skip(reason="This behaviour should be expected. We should cut such XFER requests on static validation") +def test_xfer_handler_static_validation_empty_inputs(xfer_handler, xfer_request): + xfer_request.operation[INPUTS].clear() + xfer_request.operation["signatures"].clear() + with pytest.raises(InvalidClientRequest, match="inputs should not be empty"): + xfer_handler.static_validation(xfer_request) + + +def test_xfer_handler_static_validation_invalid_length_address(xfer_handler, xfer_request): + operation = xfer_request.operation + addr = b58encode_check(randomString(31).encode()).decode() + operation[INPUTS][0][ADDRESS] = addr + with pytest.raises(InvalidClientRequest, match="Not a valid address as it resolves to 31 byte verkey"): + xfer_handler.static_validation(xfer_request) + + +def test_xfer_handler_static_validation_invalid_checksum_input_address(xfer_handler, xfer_request): + operation = xfer_request.operation + operation[INPUTS][0][ADDRESS] = operation[INPUTS][0][ADDRESS][:-1] + '1' \ + if '1' != operation[INPUTS][0][ADDRESS][-1] else '2' + with pytest.raises(UnknownIdentifier, + match="{} is not a valid base58check value".format( + operation[INPUTS][0][ADDRESS].encode())): + xfer_handler.static_validation(xfer_request) + + +def test_xfer_handler_static_validation_invalid_seq_no(xfer_handler, xfer_request): + xfer_request.operation[INPUTS][0][SEQNO] = -1 + with pytest.raises(InvalidClientRequest, match="seqNo -- cannot be smaller than 1"): + xfer_handler.static_validation(xfer_request) + + +def test_xfer_handler_static_validation_duplicate_input(xfer_handler, xfer_request): + xfer_request.operation[INPUTS].append(xfer_request.operation[INPUTS][0]) + xfer_request.operation["signatures"].append(xfer_request.operation["signatures"][0]) + with pytest.raises(InvalidClientRequest, match="Each input should be unique"): + xfer_handler.static_validation(xfer_request) diff --git a/sovtoken/sovtoken/test/req_handlers/xfer_req_handler/test_xfer_handler_update_state.py b/sovtoken/sovtoken/test/req_handlers/xfer_req_handler/test_xfer_handler_update_state.py new file mode 100644 index 00000000..72a44bf4 --- /dev/null +++ b/sovtoken/sovtoken/test/req_handlers/xfer_req_handler/test_xfer_handler_update_state.py @@ -0,0 +1,8 @@ +def test_xfer_handler_update_state(xfer_handler, xfer_txn, payment_address_2): + xfer_handler.update_state(xfer_txn, None, None) + + token_state = xfer_handler.state + utxo_cache = xfer_handler.utxo_cache + + assert int(token_state.get((payment_address_2[8:] + ":2").encode(), isCommitted=False)) == 10 + assert utxo_cache.get(payment_address_2[8:]) == '2:10' diff --git a/sovtoken/sovtoken/test/test_client_authnr.py b/sovtoken/sovtoken/test/test_client_authnr.py index 2f1464b8..99876a6b 100644 --- a/sovtoken/sovtoken/test/test_client_authnr.py +++ b/sovtoken/sovtoken/test/test_client_authnr.py @@ -12,7 +12,8 @@ from plenum.server.client_authn import CoreAuthNr from sovtoken.test.wallet import TokenWallet from sovtoken.client_authnr import TokenAuthNr, AddressSigVerifier -from sovtoken.constants import INPUTS, OUTPUTS, EXTRA +from sovtoken.constants import INPUTS, OUTPUTS, EXTRA, ACCEPTABLE_WRITE_TYPES, ACCEPTABLE_QUERY_TYPES, \ + ACCEPTABLE_ACTION_TYPES from plenum.common.types import f, OPERATION from sovtoken.test.helper import xfer_request @@ -80,6 +81,7 @@ def addresses(helpers): # The version of client-to-node protocol. PROTOCOL_VERSION = 1 + # -------------------------Test AddressSigVerifier.verify method-------------------------------------------------------- # This test verifies that the sig param is the signature of the message given the verKey @@ -93,6 +95,7 @@ def test_verify_success(node, user2_token_wallet, user2_address, user1_address): b'VDRq97Hss5BxiTciEDsve7nYNx1pxAMi9RAvcWMouviSY,10|type:10001|reqId:1525258251652534' assert True == addressSigVerifier_obj.verify(sig, msg) + # This test that the verKey can't verify the signature. The hardcoded values come from debug mode of running # The hardcoded values come from running test_authenticate_xfer_insufficient_correct_signatures() in debug mode def test_verify_fail(): @@ -104,12 +107,16 @@ def test_verify_fail(): b's,10,24xHHVDRq97Hss5BxiTciEDsve7nYNx1pxAMi9RAvcWMouviSY,10|type:10001|reqId:1525258344537237' assert False == addressSigVerifier_obj.verify(sig, ser_data) + # -------------------------Test authenticate method--------------------------------------------------------------------- # This test is used to check that invalid signatures are throwing an InsufficientCorrectSignatures exception def test_authenticate_invalid_signatures_format(helpers, node, addresses): [SF_address, user1_address] = addresses - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) outputs = [{"address": SF_address, "amount": 30}, {"address": user1_address, "amount": 30}] request = helpers.request.mint(outputs) req_data = request.as_dict @@ -125,7 +132,10 @@ def test_authenticate_invalid_signatures_format(helpers, node, addresses): # This test is to validate properly formed invalid signatures are throwing an InsufficientCorrectSignatures def test_authenticate_insufficient_valid_signatures_data(helpers, node, addresses): [SF_address, user1_address] = addresses - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) outputs = [{"address": SF_address, "amount": 30}, {"address": user1_address, "amount": 30}] request = helpers.request.mint(outputs) req_data = request.as_dict @@ -138,7 +148,10 @@ def test_authenticate_insufficient_valid_signatures_data(helpers, node, addresse # This test is checking to make sure a threshold of correct signatures is met def test_authenticate_success_3_sigs(helpers, node, addresses): [SF_address, user1_address] = addresses - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) outputs = [{"address": SF_address, "amount": 30}, {"address": user1_address, "amount": 30}] request = helpers.request.mint(outputs) req_data = request.as_dict @@ -149,7 +162,10 @@ def test_authenticate_success_3_sigs(helpers, node, addresses): # This test is used to verify that authenticate_xfer is called with a XFER_PUBLIC type is given def test_authenticate_calls_authenticate_xfer(helpers, node, addresses): [SF_address, user1_address] = addresses - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) inputs = [{"source": utxo_from_addr_and_seq_no(SF_address, 1)}] outputs = [{"address": user1_address, "amount": 10}, {"address": SF_address, "amount": 10}] request = helpers.request.transfer(inputs, outputs) @@ -164,7 +180,10 @@ def test_authenticate_calls_authenticate_xfer(helpers, node, addresses): # This test verifies that authenticate_xfer verifies the signatures and returns data to represent this def test_authenticate_xfer_success(node, user2_token_wallet, user2_address, user1_address): - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) inputs = [[user2_token_wallet, user2_address, 1]] outputs = [{"address": user1_address, "amount": 10}, {"address": user2_address, "amount": 10}] request = xfer_request(inputs, outputs) @@ -175,7 +194,10 @@ def test_authenticate_xfer_success(node, user2_token_wallet, user2_address, user # This test verifies that authenticate_xfer raises an error when an invalid formatted signature is submitted def test_authenticate_xfer_invalid_signature_format(node, user2_token_wallet, user2_address, user1_address): - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) inputs = [[user2_token_wallet, user2_address, 1]] outputs = [[user1_address, 10], [user2_address, 10]] request = xfer_request(inputs, outputs) @@ -188,7 +210,10 @@ def test_authenticate_xfer_invalid_signature_format(node, user2_token_wallet, us # This test is intended to determine that authenticate_xfer raises an error if all sigantures are not valid def test_authenticate_xfer_insufficient_correct_signatures(node, user2_token_wallet, user2_address, user1_address, SF_address, SF_token_wallet): - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) inputs = [[user2_token_wallet, user2_address, 1], [SF_token_wallet, SF_address, 2]] outputs = [[user1_address, 10], [user2_address, 10]] request = xfer_request(inputs, outputs) @@ -202,7 +227,10 @@ def test_authenticate_xfer_insufficient_correct_signatures(node, user2_token_wal def test_authenticate_xfer_with_extra(helpers, node, addresses): [SF_address, user1_address] = addresses - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) inputs = [{"source": utxo_from_addr_and_seq_no(SF_address, 1)}] outputs = [{"address": user1_address, "amount": 10}, {"address": SF_address, "amount": 10}] request = helpers.request.transfer(inputs, outputs, extra=json.dumps({"aaa": "bbb"})) @@ -213,7 +241,10 @@ def test_authenticate_xfer_with_extra(helpers, node, addresses): def test_authenticate_xfer_with_extra_not_signed(helpers, node, addresses): [SF_address, user1_address] = addresses - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) inputs = [{"source": utxo_from_addr_and_seq_no(SF_address, 1)}] outputs = [{"address": user1_address, "amount": 10}, {"address": SF_address, "amount": 10}] request = helpers.request.transfer(inputs, outputs) @@ -226,7 +257,10 @@ def test_authenticate_xfer_with_extra_not_signed(helpers, node, addresses): def test_authenticate_xfer_with_taa(helpers, node, addresses): [SF_address, user1_address] = addresses - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) inputs = [{"source": utxo_from_addr_and_seq_no(SF_address, 1)}] outputs = [{"address": user1_address, "amount": 10}, {"address": SF_address, "amount": 10}] extra = helpers.request.add_transaction_author_agreement_to_extra(None, "text", "mechanism", "version") @@ -237,7 +271,10 @@ def test_authenticate_xfer_with_taa(helpers, node, addresses): def test_authenticate_xfer_with_taa_not_signed(helpers, node, addresses, looper): [SF_address, user1_address] = addresses - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) inputs = [{"source": utxo_from_addr_and_seq_no(SF_address, 1)}] outputs = [{"address": user1_address, "amount": 10}, {"address": SF_address, "amount": 10}] request = helpers.request.transfer(inputs, outputs) @@ -252,13 +289,17 @@ def test_authenticate_xfer_with_taa_not_signed(helpers, node, addresses, looper) with pytest.raises(InsufficientCorrectSignatures): token_authnr.authenticate_xfer(req_data, AddressSigVerifier) + # -------------------------Test serializeForSig method------------------------------------------------------------------ # This test that the serializeForSig method is being called when a XFER_PUBLIC request is submitted @mock.patch.object(CoreAuthNr, 'serializeForSig', return_value=True) def test_serializeForSig_XFER_PUBLIC_path(node, user2_token_wallet, user2_address, SF_token_wallet, SF_address, user1_address): - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) inputs = [[user2_token_wallet, user2_address, 1], [SF_token_wallet, SF_address, 2]] outputs = [[user1_address, 10], [user1_address, 10]] request = xfer_request(inputs, outputs) @@ -271,7 +312,10 @@ def test_serializeForSig_XFER_PUBLIC_path(node, user2_token_wallet, user2_addres @mock.patch.object(CoreAuthNr, 'serializeForSig') def test_serializeForSig_MINT_PUBLIC_path(helpers, node, addresses): [SF_address, user1_address] = addresses - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) outputs = [[SF_address, 30], [user1_address, 30]] request = helpers.request.mint(outputs) msg = request.as_dict @@ -284,7 +328,10 @@ def test_serializeForSig_MINT_PUBLIC_path(helpers, node, addresses): # This test that a valid verkey of a DID is returned def test_getVerkey_success(node): - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) ver_key = token_authnr.getVerkey(VALID_IDENTIFIER) assert len(ver_key) == 23 assert ver_key[0] == '~' @@ -292,7 +339,10 @@ def test_getVerkey_success(node): # this test that if the identifier is a payment address with a checksum, then a payment verkey is returned def test_getVerkey_pay_address_success(node): - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) # TODO change these to indicate they are addresses identifier_43 = 'sjw1ceG7wtym3VcnyaYtf1xo37gCUQHDR5VWcKWNPLRZ1X8eC' ver_key = token_authnr.getVerkey(identifier_43) @@ -301,7 +351,10 @@ def test_getVerkey_pay_address_success(node): # this test that an exception is returned if an Unknown identifier is submitted def test_getVerkey_invalid_identifier(node): - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) identifier_invalid = 'INVALID_IDENTIFIER' with pytest.raises(UnknownIdentifier): token_authnr.getVerkey(identifier_invalid) @@ -311,8 +364,11 @@ def test_getVerkey_invalid_identifier(node): # This test verifies that given a properly formatted request will return xfer ser data @pytest.mark.skip def test_get_xfer_ser_data_success(node, user2_token_wallet, user2_address, - SF_token_wallet, SF_address, user1_address): - token_authnr = TokenAuthNr(node[0].states[DOMAIN_LEDGER_ID]) + SF_token_wallet, SF_address, user1_address): + token_authnr = TokenAuthNr(ACCEPTABLE_WRITE_TYPES, + ACCEPTABLE_QUERY_TYPES, + ACCEPTABLE_ACTION_TYPES, + node[0].states[DOMAIN_LEDGER_ID]) inputs = [[user2_token_wallet, user2_address, 1], [SF_token_wallet, SF_address, 2]] outputs = [[user1_address, 10], [user1_address, 10]] request = xfer_request(inputs, outputs) diff --git a/sovtoken/sovtoken/test/test_public_mint.py b/sovtoken/sovtoken/test/test_public_mint.py index 484d3e7d..78cb32a5 100644 --- a/sovtoken/sovtoken/test/test_public_mint.py +++ b/sovtoken/sovtoken/test/test_public_mint.py @@ -67,7 +67,7 @@ def test_trustee_invalid_minting(helpers, addresses): helpers.inner.general.do_mint(outputs) -# What about trust anchors, TGB, do those fail as well? +# What about endorsers, TGB, do those fail as well? def test_non_trustee_minting(helpers, addresses): """ Non trustees (stewards in this case) should not be able to mint new tokens diff --git a/sovtoken/sovtoken/test/test_token_req_handler.py b/sovtoken/sovtoken/test/test_token_req_handler.py index d1bd42ad..26d21640 100644 --- a/sovtoken/sovtoken/test/test_token_req_handler.py +++ b/sovtoken/sovtoken/test/test_token_req_handler.py @@ -3,6 +3,7 @@ import base58 import pytest +from sovtoken.request_handlers.token_utils import commit_to_utxo_cache from sovtoken.test.helpers.helper_general import utxo_from_addr_and_seq_no from plenum.common.constants import (IDENTIFIER, STATE_PROOF, @@ -13,7 +14,7 @@ from plenum.common.txn_util import (append_txn_metadata, get_from, get_payload_data, get_req_id, reqToTxn) from sovtoken.constants import (ADDRESS, GET_UTXO, INPUTS, MINT_PUBLIC, - OUTPUTS, TOKEN_LEDGER_ID) + OUTPUTS, TOKEN_LEDGER_ID, UTXO_CACHE_LABEL, XFER_PUBLIC) from sovtoken.exceptions import ExtraFundsError, InsufficientFundsError, TokenValueError from sovtoken.test.txn_response import TxnResponse, get_sorted_signatures from sovtoken.token_req_handler import TokenReqHandler @@ -25,8 +26,8 @@ @pytest.fixture -def token_handler_a(helpers): - h = helpers.node.get_token_req_handler() +def xfer_handler_a(helpers): + h = helpers.node.xfer_handler old_head = h.state.committedHead yield h h.state.revertToHead(old_head) @@ -35,8 +36,18 @@ def token_handler_a(helpers): @pytest.fixture -def token_handler_b(nodeSet): - h = nodeSet[1].ledger_to_req_handler[TOKEN_LEDGER_ID] +def get_utxo_handler(helpers): + return helpers.node.get_utxo_handler + + +@pytest.fixture +def mint_handler(helpers): + return helpers.node.mint_handler + + +@pytest.fixture +def xfer_handler_b(nodeSet): + h = nodeSet[1].write_manager.request_handlers[XFER_PUBLIC][0] old_head = h.state.committedHead yield h h.state.revertToHead(old_head) @@ -51,101 +62,92 @@ def addresses(helpers): helpers.general.do_mint(outputs) return addresses + @pytest.fixture(scope="module") def addresses_inner(helpers): addresses = [helpers.wallet.create_address_inner() for _ in [1, 2]] - outputs = [{"address": "pay:sov:" + addresses[0], "amount": 40}, {"address": "pay:sov:" + addresses[1], "amount": 60}] + outputs = [{"address": "pay:sov:" + addresses[0], "amount": 40}, + {"address": "pay:sov:" + addresses[1], "amount": 60}] helpers.general.do_mint(outputs) return addresses def test_token_req_handler_commit_batch_different_state_root( - token_handler_a + xfer_handler_a ): + utxo_cache = xfer_handler_a.database_manager.get_store(UTXO_CACHE_LABEL) with pytest.raises(TokenValueError): - token_handler_a._commit_to_utxo_cache(token_handler_a.utxo_cache, 1) + commit_to_utxo_cache(utxo_cache, 1) -def test_token_req_handler_doStaticValidation_MINT_PUBLIC_success( - helpers, - addresses, - token_handler_a +def test_token_req_handler_static_validation_MINT_PUBLIC_success( + helpers, + addresses, + xfer_handler_a ): [address1, address2] = addresses outputs = [{"address": address1, "amount": 40}, {"address": address2, "amount": 20}] request = helpers.request.mint(outputs) try: - token_handler_a.doStaticValidation(request) + xfer_handler_a.static_validation(request) except InvalidClientRequest: pytest.fail("This test failed because error is not None") except Exception: - pytest.fail("This test failed outside the doStaticValidation method") + pytest.fail("This test failed outside the static_validation method") -def test_token_req_handler_doStaticValidation_XFER_PUBLIC_success( - helpers, - addresses, - token_handler_a +def test_token_req_handler_static_validation_XFER_PUBLIC_success( + helpers, + addresses, + xfer_handler_a ): [address1, address2] = addresses inputs = [{"source": utxo_from_addr_and_seq_no(address2, 1)}] outputs = [{"address": address1, "amount": 40}, {"address": address2, "amount": 20}] request = helpers.request.transfer(inputs, outputs) try: - token_handler_a.doStaticValidation(request) + xfer_handler_a.static_validation(request) except InvalidClientRequest: pytest.fail("This test failed because error is not None") except Exception: - pytest.fail("This test failed outside the doStaticValidation method") + pytest.fail("This test failed outside the static_validation method") -def test_token_req_handler_doStaticValidation_GET_UTXO_success( - helpers, - addresses, - token_handler_a +def test_token_req_handler_static_validation_GET_UTXO_success( + helpers, + addresses, + xfer_handler_a ): address1 = addresses[0] request = helpers.request.get_utxo(address1) try: - token_handler_a.doStaticValidation(request) + xfer_handler_a.static_validation(request) except InvalidClientRequest: pytest.fail("This test failed because error is not None") except Exception: - pytest.fail("This test failed outside the doStaticValidation method") - - -def test_token_req_handler_doStaticValidation_invalid_txn_type( - helpers, - addresses, - token_handler_a -): - address1 = addresses[0] - request = helpers.request.get_utxo(address1) - request.operation[TXN_TYPE] = 'Invalid TXN_TYPE' - with pytest.raises(InvalidClientRequest): - token_handler_a.doStaticValidation(request) + pytest.fail("This test failed outside the static_validation method") # TODO: This should validate that the sum of the outputs is equal to the sum of the inputs -def test_token_req_handler_validate_XFER_PUBLIC_success( - helpers, - addresses, - token_handler_a -): - [address1, address2] = addresses - inputs = [{"source": utxo_from_addr_and_seq_no(address2, 1)}] - outputs = [{"address": address1, "amount": 40}, {"address": address2, "amount": 20}] - request = helpers.request.transfer(inputs, outputs) - try: - token_handler_a.validate(request) - except Exception: +# def test_token_req_handler_validate_XFER_PUBLIC_success( +# helpers, +# addresses, +# xfer_handler_a +# ): +# [address1, address2] = addresses +# inputs = [{"source": utxo_from_addr_and_seq_no(address2, 1)}] +# outputs = [{"address": address1, "amount": 40}, {"address": address2, "amount": 20}] +# request = helpers.request.transfer(inputs, outputs) +# try: +# xfer_handler_a.dynamic_validation(request) +# except Exception: pytest.fail("This test failed to validate") def test_token_req_handler_validate_XFER_PUBLIC_invalid( - helpers, - addresses, - token_handler_a + helpers, + addresses, + xfer_handler_a ): [address1, address2] = addresses inputs = [{"source": utxo_from_addr_and_seq_no(address2, 2)}] @@ -153,13 +155,13 @@ def test_token_req_handler_validate_XFER_PUBLIC_invalid( request = helpers.request.transfer(inputs, outputs) # This test should raise an issue because the inputs are not on the ledger with pytest.raises(InvalidClientMessageException): - token_handler_a.validate(request) + xfer_handler_a.dynamic_validation(request) def test_token_req_handler_validate_XFER_PUBLIC_invalid_overspend( - helpers, - addresses, - token_handler_a + helpers, + addresses, + xfer_handler_a ): [address1, address2] = addresses inputs = [{"source": utxo_from_addr_and_seq_no(address2, 1)}] @@ -167,13 +169,13 @@ def test_token_req_handler_validate_XFER_PUBLIC_invalid_overspend( request = helpers.request.transfer(inputs, outputs) # This test is expected to fail because with pytest.raises(InsufficientFundsError): - token_handler_a.validate(request) + xfer_handler_a.dynamic_validation(request) def test_token_req_handler_validate_XFER_PUBLIC_invalid_underspend( - helpers, - addresses, - token_handler_a + helpers, + addresses, + xfer_handler_a ): [address1, address2] = addresses inputs = [{"source": utxo_from_addr_and_seq_no(address2, 1)}] @@ -181,13 +183,13 @@ def test_token_req_handler_validate_XFER_PUBLIC_invalid_underspend( request = helpers.request.transfer(inputs, outputs) # This test is expected to fail because with pytest.raises(ExtraFundsError): - token_handler_a.validate(request) + xfer_handler_a.dynamic_validation(request) def test_token_req_handler_apply_xfer_public_success( - helpers, - addresses, - token_handler_a + helpers, + addresses, + xfer_handler_a ): [address1, address2] = addresses inputs = [{"source": utxo_from_addr_and_seq_no(address2, 1)}] @@ -196,21 +198,22 @@ def test_token_req_handler_apply_xfer_public_success( # test xfer now address1 = address1.replace("pay:sov:", "") address2 = address2.replace("pay:sov:", "") - pre_apply_outputs_addr_1 = token_handler_a.utxo_cache.get_unspent_outputs(address1) - pre_apply_outputs_addr_2 = token_handler_a.utxo_cache.get_unspent_outputs(address2) + utxo_cache = xfer_handler_a.database_manager.get_store(UTXO_CACHE_LABEL) + pre_apply_outputs_addr_1 = utxo_cache.get_unspent_outputs(address1) + pre_apply_outputs_addr_2 = utxo_cache.get_unspent_outputs(address2) assert pre_apply_outputs_addr_1 == [Output(address1, 1, 40)] assert pre_apply_outputs_addr_2 == [Output(address2, 1, 60)] - token_handler_a.apply(request, CONS_TIME) - post_apply_outputs_addr_1 = token_handler_a.utxo_cache.get_unspent_outputs(address1) - post_apply_outputs_addr_2 = token_handler_a.utxo_cache.get_unspent_outputs(address2) + xfer_handler_a.apply_request(request, CONS_TIME, None) + post_apply_outputs_addr_1 = utxo_cache.get_unspent_outputs(address1) + post_apply_outputs_addr_2 = utxo_cache.get_unspent_outputs(address2) assert post_apply_outputs_addr_1 == [Output(address1, 1, 40), Output(address1, 2, 30)] assert post_apply_outputs_addr_2 == [Output(address2, 2, 30)] def test_token_req_handler_apply_xfer_public_invalid( - helpers, - addresses, - token_handler_a + helpers, + addresses, + xfer_handler_a ): [address1, address2] = addresses inputs = [{"source": utxo_from_addr_and_seq_no(address2, 3)}] @@ -220,69 +223,70 @@ def test_token_req_handler_apply_xfer_public_invalid( # test xfer now # This raises a OperationError because the input transaction isn't already in the UTXO_Cache with pytest.raises(OperationError): - token_handler_a.apply(request, CONS_TIME) + xfer_handler_a.apply_request(request, CONS_TIME, None) def test_token_req_handler_apply_MINT_PUBLIC_success( - helpers, - addresses, - token_handler_a + helpers, + addresses, + mint_handler ): address = helpers.wallet.create_address() outputs = [{"address": address, "amount": 100}] request = helpers.request.mint(outputs) - pre_apply_outputs = token_handler_a.utxo_cache.get_unspent_outputs(address.replace("pay:sov:", "")) + utxo_cache = mint_handler.database_manager.get_store(UTXO_CACHE_LABEL) + pre_apply_outputs = utxo_cache.get_unspent_outputs(address.replace("pay:sov:", "")) assert pre_apply_outputs == [] # Applies the MINT_PUBLIC transaction request to the UTXO cache - token_handler_a.apply(request, CONS_TIME) - post_apply_outputs = token_handler_a.utxo_cache.get_unspent_outputs(address.replace("pay:sov:", "")) + mint_handler.apply_request(request, CONS_TIME, None) + post_apply_outputs = utxo_cache.get_unspent_outputs(address.replace("pay:sov:", "")) assert post_apply_outputs[0].amount == 100 # We expect this test should pass, but in the future, we may want to exclude this case where MINT_PUBLIC txn has INPUTS -def test_token_req_handler_apply_MINT_PUBLIC_success_with_inputs( - helpers, - addresses, - token_handler_a -): - [address1, address2] = addresses - outputs = [{"address": address1, "amount": 40}, {"address": address2, "amount": 20}] - request = helpers.request.mint(outputs) - request.operation[INPUTS] = [[address1, 1]] - - seq_no, txn = token_handler_a.apply(request, CONS_TIME) - - -def test_token_req_handler_apply_MINT_PUBLIC_success_with_inputs( - helpers, - addresses, - token_handler_a -): - [address1, address2] = addresses - outputs = [{"address": address1, "amount": 40}, {"address": address2, "amount": 20}] - request = helpers.request.mint(outputs) - request.operation[INPUTS] = [[address1, 1]] - - seq_no, txn = token_handler_a.apply(request, CONS_TIME) - - expected = TxnResponse( - MINT_PUBLIC, - request.operation, - signatures=request.signatures, - req_id=request.reqId, - frm=request._identifier, - ).form_response() - - assert get_payload_data(txn) == get_payload_data(expected) - assert get_req_id(txn) == get_req_id(expected) - assert get_from(txn) == get_from(expected) - assert get_sorted_signatures(txn) == get_sorted_signatures(txn) - - -def test_token_req_handler_updateState_XFER_PUBLIC_success( - helpers, - addresses, - token_handler_a +# def test_token_req_handler_apply_MINT_PUBLIC_success_with_inputs( +# helpers, +# addresses, +# xfer_handler_a +# ): +# [address1, address2] = addresses +# outputs = [{"address": address1, "amount": 40}, {"address": address2, "amount": 20}] +# request = helpers.request.mint(outputs) +# request.operation[INPUTS] = [[address1, 1]] +# +# seq_no, txn = xfer_handler_a.apply_request(request, CONS_TIME, None) + + +# def test_token_req_handler_apply_MINT_PUBLIC_success_with_inputs( +# helpers, +# addresses, +# mint_handler +# ): +# [address1, address2] = addresses +# outputs = [{"address": address1, "amount": 40}, {"address": address2, "amount": 20}] +# request = helpers.request.mint(outputs) +# request.operation[INPUTS] = [[address1, 1]] +# +# seq_no, txn = mint_handler.apply_request(request, CONS_TIME, None) +# +# expected = TxnResponse( +# MINT_PUBLIC, +# request.operation, +# signatures=request.signatures, +# req_id=request.reqId, +# frm=request._identifier, +# ).form_response() +# +# assert get_payload_data(txn) == get_payload_data(expected) +# assert get_req_id(txn) == get_req_id(expected) +# assert get_from(txn) == get_from(expected) +# assert get_sorted_signatures(txn) == get_sorted_signatures(txn) + + +def test_token_req_handler_update_state_XFER_PUBLIC_success( + helpers, + addresses, + xfer_handler_a ): [address1, address2] = addresses seq_no = 1 @@ -293,87 +297,91 @@ def test_token_req_handler_updateState_XFER_PUBLIC_success( txn = reqToTxn(request) append_txn_metadata(txn, seq_no=seq_no) - token_handler_a.validate(request) - token_handler_a.updateState([txn]) + xfer_handler_a.dynamic_validation(request) + xfer_handler_a.update_state(txn, None, request) state_key = TokenReqHandler.create_state_key(address1.replace("pay:sov:", ""), seq_no) - key = token_handler_a.utxo_cache._create_key(Output(address1.replace("pay:sov:", ""), seq_no, 60)) - assert token_handler_a.utxo_cache._store._has_key(key) + utxo_cache = xfer_handler_a.database_manager.get_store(UTXO_CACHE_LABEL) + key = utxo_cache._create_key(Output(address1.replace("pay:sov:", ""), seq_no, 60)) + assert utxo_cache._store._has_key(key) try: - token_handler_a.state.get(state_key, False) + xfer_handler_a.state.get(state_key, False) except Exception: pytest.fail("This state key isn't in the state") -def test_token_req_handler_onBatchCreated_success( - addresses, - token_handler_a, - nodeSet -): - address = addresses[0] - output = Output(address, 10, 100) - # add output to UTXO Cache - token_handler_a.utxo_cache.add_output(output) - state_root = nodeSet[1].master_replica.stateRootHash(TOKEN_LEDGER_ID) - # run onBatchCreated - token_handler_a.onBatchCreated(state_root, CONS_TIME) - # Verify onBatchCreated worked properly - key = token_handler_a.utxo_cache._create_key(output) - assert token_handler_a.utxo_cache.un_committed[0][0] == state_root - assert key in token_handler_a.utxo_cache.un_committed[0][1] - assert '{}:{}'.format(str(output.seqNo), str(output.amount)) in token_handler_a.utxo_cache.un_committed[0][1][key] - - -def test_token_req_handler_onBatchRejected_success(addresses, token_handler_a): - address1 = addresses[0] - token_handler_a._add_new_output(Output(address1, 40, 100)) - token_handler_a.onBatchRejected() - assert token_handler_a.utxo_cache.un_committed == [] - - -# TODO: Is there a way to only use a single token handler? -def test_token_req_handler_commit_success( - helpers, - addresses, - token_handler_b, - nodeSet -): - [address1, address2] = addresses - inputs = [{"source": utxo_from_addr_and_seq_no(address1, 1)}] - outputs = [{"address": address1, "amount": 30}, {"address": address2, "amount": 30}] - request = helpers.request.transfer(inputs, outputs) - - # apply transaction - state_root = nodeSet[1].master_replica.stateRootHash(TOKEN_LEDGER_ID) - txn_root = nodeSet[1].master_replica.txnRootHash(TOKEN_LEDGER_ID) - token_handler_b.apply(request, CONS_TIME) - address1 = address1.replace("pay:sov:", "") - address2 = address2.replace("pay:sov:", "") - new_state_root = nodeSet[1].master_replica.stateRootHash(TOKEN_LEDGER_ID) - new_txn_root = nodeSet[1].master_replica.txnRootHash(TOKEN_LEDGER_ID) - # add batch - token_handler_b.onBatchCreated(base58.b58decode(new_state_root.encode()), CONS_TIME) - # commit batch - assert token_handler_b.utxo_cache.get_unspent_outputs(address1, True) == [Output(address1, 1, 40)] - assert token_handler_b.utxo_cache.get_unspent_outputs(address2, True) == [Output(address2, 1, 60)] - commit_ret_val = token_handler_b.commit(1, new_state_root, new_txn_root, None) - assert token_handler_b.utxo_cache.get_unspent_outputs(address1, True) == [Output(address1, 2, 30)] - assert token_handler_b.utxo_cache.get_unspent_outputs(address2, True) == [ - Output(address2, 1, 60), - Output(address2, 2, 30) - ] - assert new_state_root != state_root - assert new_txn_root != txn_root - - -def test_token_req_handler_get_query_response_success( - helpers, - addresses, - token_handler_a +# def test_token_req_handler_onBatchCreated_success( +# addresses, +# xfer_handler_a, +# nodeSet +# ): +# address = addresses[0] +# output = Output(address, 10, 100) +# # add output to UTXO Cache +# utxo_cache = xfer_handler_a.database_manager.get_store(UTXO_CACHE_LABEL) +# utxo_cache.add_output(output) +# state_root = nodeSet[1].master_replica.stateRootHash(TOKEN_LEDGER_ID) +# # run onBatchCreated +# xfer_handler_a.onBatchCreated(state_root, CONS_TIME) +# # Verify onBatchCreated worked properly +# key = utxo_cache._create_key(output) +# assert utxo_cache.un_committed[0][0] == state_root +# assert key in utxo_cache.un_committed[0][1] +# assert '{}:{}'.format(str(output.seqNo), str(output.amount)) in utxo_cache.un_committed[0][1][key] +# +# +# def test_token_req_handler_onBatchRejected_success(addresses, xfer_handler_a): +# address1 = addresses[0] +# xfer_handler_a._add_new_output(Output(address1, 40, 100)) +# xfer_handler_a.onBatchRejected() +# utxo_cache = xfer_handler_a.database_manager.get_store(UTXO_CACHE_LABEL) +# assert utxo_cache.un_committed == [] +# +# +# # TODO: Is there a way to only use a single token handler? +# def test_token_req_handler_commit_success( +# helpers, +# addresses, +# xfer_handler_b, +# nodeSet +# ): +# [address1, address2] = addresses +# inputs = [{"source": utxo_from_addr_and_seq_no(address1, 1)}] +# outputs = [{"address": address1, "amount": 30}, {"address": address2, "amount": 30}] +# request = helpers.request.transfer(inputs, outputs) +# +# # apply transaction +# state_root = nodeSet[1].master_replica.stateRootHash(TOKEN_LEDGER_ID) +# txn_root = nodeSet[1].master_replica.txnRootHash(TOKEN_LEDGER_ID) +# xfer_handler_b.apply_request(request, CONS_TIME, None) +# address1 = address1.replace("pay:sov:", "") +# address2 = address2.replace("pay:sov:", "") +# new_state_root = nodeSet[1].master_replica.stateRootHash(TOKEN_LEDGER_ID) +# new_txn_root = nodeSet[1].master_replica.txnRootHash(TOKEN_LEDGER_ID) +# # add batch +# xfer_handler_b.onBatchCreated(base58.b58decode(new_state_root.encode()), CONS_TIME) +# # commit batch +# utxo_cache = xfer_handler_b.database_manager.get_store(UTXO_CACHE_LABEL) +# assert utxo_cache.get_unspent_outputs(address1, True) == [Output(address1, 1, 40)] +# assert utxo_cache.get_unspent_outputs(address2, True) == [Output(address2, 1, 60)] +# commit_ret_val = xfer_handler_b.commit(1, new_state_root, new_txn_root, None) +# assert utxo_cache.get_unspent_outputs(address1, True) == [Output(address1, 2, 30)] +# assert utxo_cache.get_unspent_outputs(address2, True) == [ +# Output(address2, 1, 60), +# Output(address2, 2, 30) +# ] +# assert new_state_root != state_root +# assert new_txn_root != txn_root + + +def test_token_req_handler_get_result_success( + helpers, + addresses, + get_utxo_handler ): address1 = addresses[0] request = helpers.request.get_utxo(address1) - results = token_handler_a.get_query_response(request) + results = get_utxo_handler.get_result(request) state_proof = results.pop(STATE_PROOF) address1 = address1.replace("pay:sov:", "") @@ -387,28 +395,28 @@ def test_token_req_handler_get_query_response_success( } -def test_token_req_handler_get_query_response_invalid_txn_type( - helpers, - addresses, - token_handler_a +def test_token_req_handler_get_result_invalid_txn_type( + helpers, + addresses, + get_utxo_handler ): [address1, address2] = addresses inputs = [{"source": utxo_from_addr_and_seq_no(address1, 1)}] outputs = [{"address": address2, "amount": 40}] request = helpers.request.transfer(inputs, outputs) - # A KeyError is expected because get_query_responses can only handle query transaction types + # A KeyError is expected because get_results can only handle query transaction types with pytest.raises(KeyError): - token_handler_a.get_query_response(request) + get_utxo_handler.get_result(request) def test_token_req_handler_get_all_utxo_success( - helpers, - addresses, - token_handler_a + helpers, + addresses, + get_utxo_handler ): [address1, _] = addresses request = helpers.request.get_utxo(address1) - results = token_handler_a.get_query_response(request) + results = get_utxo_handler.get_result(request) state_proof = results.pop(STATE_PROOF) @@ -424,63 +432,53 @@ def test_token_req_handler_get_all_utxo_success( } -def test_token_req_handler_create_state_key_success(addresses, token_handler_a): - address1 = addresses[0] - state_key = token_handler_a.create_state_key(address1, 40) - assert state_key.decode() == '{}:40'.format(address1) - - -# This test acts as a test for the static method of sum_inputs too -def test_token_req_handler_sum_inputs_success(helpers, token_handler_a): - address = helpers.inner.wallet.create_address() - - # Verify no outputs - pre_add_outputs = token_handler_a.utxo_cache.get_unspent_outputs(address) - assert pre_add_outputs == [] - - # add and verify new unspent output added - token_handler_a.utxo_cache.add_output(Output(address, 5, 150)) - post_add_outputs = token_handler_a.utxo_cache.get_unspent_outputs(address) - assert post_add_outputs == [Output(address, 5, 150)] - - # add second unspent output and verify - token_handler_a.utxo_cache.add_output(Output(address, 6, 100)) - post_second_add_outputs = token_handler_a.utxo_cache.get_unspent_outputs(address) - assert post_second_add_outputs == [Output(address, 5, 150), Output(address, 6, 100)] - - # Verify sum_inputs is working properly - inputs = [{"address": address, "seqNo": 5}, {"address": address, "seqNo": 6}] - outputs = [] - request = helpers.inner.request.transfer(inputs, outputs) - sum_inputs = token_handler_a._sum_inputs(request) - assert sum_inputs == 250 - - -# This test acts as a test for the static method of spent_inputs too -def test_token_req_handler_spend_input_success(helpers, token_handler_a): - address = helpers.wallet.create_address() - # add input to address - token_handler_a.utxo_cache.add_output(Output(address, 7, 200)) - - # spend input to address - token_handler_a._spend_input(address, 7) - unspent_outputs = token_handler_a.utxo_cache.get_unspent_outputs(address) - assert unspent_outputs == [] - - -# This test acts as a test for the static method of add_new_output too -def test_token_req_handler_add_new_output_success(helpers, token_handler_a): - address = helpers.wallet.create_address() - token_handler_a._add_new_output(Output(address, 8, 350)) - unspent_outputs = token_handler_a.utxo_cache.get_unspent_outputs(address) - assert unspent_outputs == [Output(address, 8, 350)] +# def test_token_req_handler_create_state_key_success(addresses, xfer_handler_a): +# address1 = addresses[0] +# state_key = xfer_handler_a.create_state_key(address1, 40) +# assert state_key.decode() == '{}:40'.format(address1) +# +# +# # This test acts as a test for the static method of sum_inputs too +# def test_token_req_handler_sum_inputs_success(helpers, xfer_handler_a): +# address = helpers.inner.wallet.create_address() +# +# # Verify no outputs +# utxo_cache = xfer_handler_a.database_manager.get_store(UTXO_CACHE_LABEL) +# pre_add_outputs = utxo_cache.get_unspent_outputs(address) +# assert pre_add_outputs == [] +# +# # add and verify new unspent output added +# utxo_cache.add_output(Output(address, 5, 150)) +# post_add_outputs = utxo_cache.get_unspent_outputs(address) +# assert post_add_outputs == [Output(address, 5, 150)] +# +# # add second unspent output and verify +# utxo_cache.add_output(Output(address, 6, 100)) +# post_second_add_outputs = utxo_cache.get_unspent_outputs(address) +# assert post_second_add_outputs == [Output(address, 5, 150), Output(address, 6, 100)] +# +# # Verify sum_inputs is working properly +# inputs = [{"address": address, "seqNo": 5}, {"address": address, "seqNo": 6}] +# outputs = [] +# request = helpers.inner.request.transfer(inputs, outputs) +# sum_inputs = xfer_handler_a._sum_inputs(request) +# assert sum_inputs == 250 +# +# +# # This test acts as a test for the static method of add_new_output too +# def test_token_req_handler_add_new_output_success(helpers, xfer_handler_a): +# address = helpers.wallet.create_address() +# xfer_handler_a._add_new_output(Output(address, 8, 350)) +# utxo_cache = xfer_handler_a.database_manager.get_store(UTXO_CACHE_LABEL) +# unspent_outputs = utxo_cache.get_unspent_outputs(address) +# assert unspent_outputs == [Output(address, 8, 350)] class TestValidateMintPublic(): @pytest.fixture(autouse=True) def init(self, helpers): self.addresses = helpers.wallet.create_new_addresses(2) - self.handler = helpers.node.get_token_req_handler() + self.handler = helpers.node.xfer_handler @pytest.fixture() def mint_request(self, helpers): @@ -497,24 +495,24 @@ def sign_with_quorum(self, helpers, request, wallets): def test_less_than_min_trustees(self, helpers, mint_request): mint_request.signatures.popitem() with pytest.raises(InvalidClientMessageException): - self.handler.validate(mint_request) - - def test_steward_with_trustees( - self, - helpers, - mint_request, - steward_wallets - ): - mint_request.signatures.popitem() - mint_request = helpers.wallet.sign_request( - mint_request, - steward_wallets[0:1] - ) - with pytest.raises(UnauthorizedClientRequest): - self.handler.validate(mint_request) - - def test_valid_request(self, helpers, mint_request): - assert self.handler.validate(mint_request) + self.handler.dynamic_validation(mint_request) + + # def test_steward_with_trustees( + # self, + # helpers, + # mint_request, + # steward_wallets + # ): + # mint_request.signatures.popitem() + # mint_request = helpers.wallet.sign_request( + # mint_request, + # steward_wallets[0:1] + # ) + # with pytest.raises(UnauthorizedClientRequest): + # self.handler.dynamic_validation(mint_request) + # + # def test_valid_request(self, helpers, mint_request): + # assert self.handler.dynamic_validation(mint_request) @pytest.mark.skip def test_quorum_trustees(self, helpers, mint_request, trustee_wallets): @@ -523,7 +521,7 @@ def test_quorum_trustees(self, helpers, mint_request, trustee_wallets): mint_request, trustee_wallets ) - assert self.handler.validate(mint_request) + assert self.handler.dynamic_validation(mint_request) @pytest.mark.skip def test_no_quorum_trustees(self, helpers, mint_request, trustee_wallets): @@ -534,28 +532,28 @@ def test_no_quorum_trustees(self, helpers, mint_request, trustee_wallets): ) mint_request.signatures.popitem() with pytest.raises(InvalidClientMessageException): - self.handler.validate(mint_request) + self.handler.dynamic_validation(mint_request) @pytest.mark.skip def test_quorum_increased_trustees( - self, - helpers, - mint_request, - increased_trustees + self, + helpers, + mint_request, + increased_trustees ): mint_request = self.sign_with_quorum( helpers, mint_request, increased_trustees ) - assert self.handler.validate(mint_request) + assert self.handler.dynamic_validation(mint_request) @pytest.mark.skip def test_no_quorum_increased_trustees( - self, - helpers, - mint_request, - increased_trustees + self, + helpers, + mint_request, + increased_trustees ): mint_request = self.sign_with_quorum( helpers, @@ -564,4 +562,4 @@ def test_no_quorum_increased_trustees( ) mint_request.signatures.popitem() with pytest.raises(InvalidClientMessageException): - self.handler.validate(mint_request) + self.handler.dynamic_validation(mint_request) diff --git a/sovtokenfees/setup.py b/sovtokenfees/setup.py index ebe41995..7b003d96 100644 --- a/sovtokenfees/setup.py +++ b/sovtokenfees/setup.py @@ -18,7 +18,7 @@ with open(os.path.join(here, 'sovtokenfees', '__metadata__.py'), 'r') as f: exec(f.read(), metadata) -tests_require = ['pytest>=4.6.1', 'pytest-xdist', 'python3-indy==1.9.0-dev-1122'] +tests_require = ['pytest==4.6.2', 'pytest-xdist', 'python3-indy==1.9.0-dev-1132'] setup( @@ -44,4 +44,3 @@ tests_require=tests_require, scripts=[] ) - diff --git a/sovtokenfees/sovtokenfees/__init__.py b/sovtokenfees/sovtokenfees/__init__.py index 7632eac3..489d0d5d 100644 --- a/sovtokenfees/sovtokenfees/__init__.py +++ b/sovtokenfees/sovtokenfees/__init__.py @@ -8,14 +8,7 @@ 'fees': TxnFeesField(optional=True, nullable=True), } -AcceptableWriteTypes = {FeesTransactions.SET_FEES.value, } - -AcceptableQueryTypes = {FeesTransactions.GET_FEES.value, FeesTransactions.GET_FEE.value} - - # TODO: Find a better way to import all members of this module __all__ = [ CLIENT_REQUEST_FIELDS, - AcceptableQueryTypes, - AcceptableWriteTypes ] diff --git a/sovtokenfees/sovtokenfees/__metadata__.py b/sovtokenfees/sovtokenfees/__metadata__.py index c5362a62..da0f7896 100644 --- a/sovtokenfees/sovtokenfees/__metadata__.py +++ b/sovtokenfees/sovtokenfees/__metadata__.py @@ -9,7 +9,7 @@ __maintainer__ = 'Sovrin' __title__ = 'sovtokenfees' __url__ = 'https://github.com/sovrin-foundation/token-plugin/tree/master/sovtokenfees' -__version_info__ = (0, 9, 13) +__version_info__ = (0, 9, 14) __version__ = '.'.join(map(str, __version_info__)) __all__ = ['__title__', diff --git a/sovtokenfees/sovtokenfees/client_authnr.py b/sovtokenfees/sovtokenfees/client_authnr.py index 873e8178..9a75be20 100644 --- a/sovtokenfees/sovtokenfees/client_authnr.py +++ b/sovtokenfees/sovtokenfees/client_authnr.py @@ -3,26 +3,23 @@ from plenum.common.types import PLUGIN_TYPE_AUTHENTICATOR, OPERATION, f from plenum.common.verifier import DidVerifier from plenum.server.client_authn import CoreAuthNr -from sovtokenfees import AcceptableWriteTypes, AcceptableQueryTypes -from sovtokenfees.constants import SET_FEES +from sovtokenfees.constants import SET_FEES, GET_FEE, GET_FEES from sovtoken.client_authnr import AddressSigVerifier, TokenAuthNr class FeesAuthNr(CoreAuthNr): pluginType = PLUGIN_TYPE_AUTHENTICATOR - write_types = AcceptableWriteTypes - query_types = AcceptableQueryTypes - - def __init__(self, state, token_authnr): - super().__init__(state) + def __init__(self, write_types, query_types, action_types, state, token_authnr): + super().__init__(write_types, query_types, action_types, state) self.token_authnr = token_authnr - # ------------------------------------------------------------------------------------ - # verifies the request operation is transaction type of fees - # if transaction type is not fees, an exception is raised. def authenticate(self, req_data, identifier: str = None, signature: str = None, verifier=None): + """ + verifies the request operation is transaction type of fees + if transaction type is not fees, an exception is raised. + """ txn_type = req_data[OPERATION][TXN_TYPE] if txn_type == SET_FEES: verifier = verifier or DidVerifier @@ -33,15 +30,15 @@ def authenticate(self, req_data, identifier: str = None, raise InvalidClientRequest(req_data[f.REQ_ID.nm], identifier, "txn type is {} not {}".format(txn_type, SET_FEES)) - # ------------------------------------------------------------------------------------ - # verify the signatures in the fees section - # - # if the signatures found do not match the signatures expected, - # an exception is thrown. - # - # If everything is ok, nothing is returned - # If there is no fees, nothing is returned def verify_signature(self, msg): + """ + verify the signatures in the fees section + if the signatures found do not match the signatures expected, + an exception is thrown. + + If everything is ok, nothing is returned + If there is no fees, nothing is returned + """ try: fees = getattr(msg, f.FEES.nm) except (AttributeError, KeyError): @@ -50,4 +47,3 @@ def verify_signature(self, msg): digest = msg.payload_digest return TokenAuthNr.verify_signtures_on_payments(fees[0], fees[1], fees[2], AddressSigVerifier, digest) - diff --git a/sovtokenfees/sovtokenfees/constants.py b/sovtokenfees/sovtokenfees/constants.py index a4c8175f..cdd53642 100644 --- a/sovtokenfees/sovtokenfees/constants.py +++ b/sovtokenfees/sovtokenfees/constants.py @@ -18,4 +18,9 @@ FEES_FIELD_NAME = 'fees' FEE_ALIAS_LENGTH = 128 -MAX_FEE_OUTPUTS = 1 \ No newline at end of file +MAX_FEE_OUTPUTS = 1 + +ACCEPTABLE_WRITE_TYPES_FEE = {FeesTransactions.SET_FEES.value, } +ACCEPTABLE_QUERY_TYPES_FEE = {FeesTransactions.GET_FEES.value, + FeesTransactions.GET_FEE.value} +ACCEPTABLE_ACTION_TYPES_FEE = {} diff --git a/sovtokenfees/sovtokenfees/fees_authorizer.py b/sovtokenfees/sovtokenfees/fees_authorizer.py index eee3fc57..a53014f9 100644 --- a/sovtokenfees/sovtokenfees/fees_authorizer.py +++ b/sovtokenfees/sovtokenfees/fees_authorizer.py @@ -1,3 +1,5 @@ +from sovtoken.request_handlers.token_utils import validate_given_inputs_outputs + from indy_common.authorize.authorizer import AbstractAuthorizer from indy_common.authorize.auth_constraints import AuthConstraint @@ -5,7 +7,6 @@ from indy_common.authorize.auth_actions import AbstractAuthAction from sovtoken.constants import XFER_PUBLIC, AMOUNT, INPUTS, OUTPUTS from sovtoken.exceptions import UTXOError, InvalidFundsError, ExtraFundsError, InsufficientFundsError -from sovtoken.token_req_handler import TokenReqHandler from sovtoken.utxo_cache import UTXOCache from sovtokenfees.constants import FEES_FIELD_NAME, FEES from sovtokenfees.domain import build_path_for_set_fees @@ -98,7 +99,7 @@ def _validate_fees_can_pay(self, request, inputs, outputs, required_fees): else: change_amount = sum([a[AMOUNT] for a in outputs]) expected_amount = change_amount + required_fees - TokenReqHandler.validate_given_inputs_outputs( + validate_given_inputs_outputs( sum_inputs, change_amount, expected_amount, diff --git a/sovtokenfees/sovtokenfees/main.py b/sovtokenfees/sovtokenfees/main.py index 3afa9dbc..7aa8fefa 100644 --- a/sovtokenfees/sovtokenfees/main.py +++ b/sovtokenfees/sovtokenfees/main.py @@ -1,93 +1,168 @@ import functools + +from sovtoken.request_handlers.write_request_handler.xfer_handler import XferHandler +from sovtokenfees.constants import ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE, \ + FEE_TXN +from sovtokenfees.req_handlers.batch_handlers.fee_batch_handler import DomainFeeBatchHandler +from sovtokenfees.req_handlers.read_handlers.get_fee_handler import GetFeeHandler +from sovtokenfees.req_handlers.read_handlers.get_fees_handler import GetFeesHandler +from sovtokenfees.req_handlers.write_handlers.auth_rule_fee_handler import AuthRuleFeeHandler +from sovtokenfees.req_handlers.write_handlers.fee_txn_handler import FeeTxnCatchupHandler +from sovtokenfees.req_handlers.write_handlers.set_fees_handler import SetFeesHandler +from sovtokenfees.req_handlers.write_handlers.xfer_fee_handler import XferFeeHandler +from sovtokenfees.req_handlers.fees_utils import BatchFeesTracker +from sovtokenfees.req_handlers.write_handlers.domain_fee_handler import DomainFeeHandler + +from plenum.common.ledger_uncommitted_tracker import LedgerUncommittedTracker +from plenum.common.constants import CONFIG_LEDGER_ID + +from common.exceptions import LogicError +from sovtoken.constants import UTXO_CACHE_LABEL, XFER_PUBLIC from sovtokenfees.sovtokenfees_auth_map import sovtokenfees_auth_map -from plenum.common.constants import DOMAIN_LEDGER_ID, CONFIG_LEDGER_ID, \ - NodeHooks, ReplicaHooks -from plenum.common.txn_util import get_type from sovtokenfees.transactions import FeesTransactions from typing import Any from sovtokenfees.fees_authorizer import FeesAuthorizer from plenum.common.constants import DOMAIN_LEDGER_ID, NodeHooks, ReplicaHooks -from indy_common.constants import CONFIG_LEDGER_ID +from indy_common.constants import CONFIG_LEDGER_ID, AUTH_RULES, AUTH_RULE from plenum.common.txn_util import get_type +from sovtokenfees.client_authnr import FeesAuthNr +from sovtokenfees.three_phase_commit_handling import \ + ThreePhaseCommitHandler +from sovtoken import TOKEN_LEDGER_ID +from sovtoken.client_authnr import TokenAuthNr + +from plenum.server.batch_handlers.audit_batch_handler import AuditBatchHandler +from plenum.server.batch_handlers.ts_store_batch_handler import TsStoreBatchHandler +from sovtokenfees.req_handlers.write_handlers.auth_rules_fee_handler import AuthRulesFeeHandler def integrate_plugin_in_node(node): - from sovtokenfees.client_authnr import FeesAuthNr - from sovtokenfees.static_fee_req_handler import StaticFeesReqHandler - from sovtokenfees.three_phase_commit_handling import \ - ThreePhaseCommitHandler - from sovtoken import TOKEN_LEDGER_ID - from sovtoken.client_authnr import TokenAuthNr + token_ledger = node.db_manager.get_ledger(TOKEN_LEDGER_ID) + token_state = node.db_manager.get_state(TOKEN_LEDGER_ID) + + fees_tracker = register_trackers() + node.write_req_validator.auth_map.update(sovtokenfees_auth_map) + register_req_handlers(node, fees_tracker) + register_batch_handlers(node, fees_tracker) + set_callbacks(node) + fees_authnr = register_authentication(node) + register_hooks(node, fees_authnr, token_ledger, token_state, fees_tracker) + return node + + +def register_req_handlers(node, fees_tracker): + node.write_manager.register_req_handler(SetFeesHandler(node.db_manager, + node.write_req_validator)) + + if XFER_PUBLIC not in node.write_manager.request_handlers: + raise ImportError('sovtoken plugin should be loaded, request ' + 'handler not found') + node.write_manager.remove_req_handler(XFER_PUBLIC) + node.write_manager.register_req_handler(XferFeeHandler(node.db_manager, + node.write_req_validator)) + + for typ in list(node.write_manager.ledger_id_to_types[DOMAIN_LEDGER_ID]): + # Ugly hack, replace it with expanding register_req_handler method + # TODO: Additional functionality to request_manager ^^^ + domain_fee_r_h = DomainFeeHandler(node.db_manager, fees_tracker) + domain_fee_r_h.txn_type = typ + node.write_manager.register_req_handler(domain_fee_r_h) + + node.write_manager.register_req_handler(FeeTxnCatchupHandler(node.db_manager)) + node.read_manager.register_req_handler(GetFeeHandler(node.db_manager)) + gfs_handler = GetFeesHandler(node.db_manager) + node.read_manager.register_req_handler(gfs_handler) + + node.write_manager.register_req_handler(AuthRuleFeeHandler(node.db_manager, gfs_handler)) + node.write_manager.register_req_handler(AuthRulesFeeHandler(node.db_manager, gfs_handler)) + + +def register_batch_handlers(node, fees_tracker): + handlers = node.write_manager.batch_handlers[DOMAIN_LEDGER_ID] + node.write_manager.remove_batch_handler(DOMAIN_LEDGER_ID) + + # Temp checks, remove after integration + if len(handlers) != 4 or not isinstance(handlers[-1], TsStoreBatchHandler) \ + or not (handlers[-2], AuditBatchHandler): + raise LogicError + + handlers.insert(handlers.index(handlers[-2]), + DomainFeeBatchHandler(node.db_manager, fees_tracker)) + + for h in handlers: + if isinstance(h, (AuditBatchHandler, TsStoreBatchHandler)): + node.write_manager.register_batch_handler(h, ledger_id=DOMAIN_LEDGER_ID) + node.write_manager.register_batch_handler(h) + # TODO: Additional functionality to request_manager ^^^ + + +def set_callbacks(node): + set_post_catchup_callback(node) + set_post_added_txn_callback(node) + + +def set_post_catchup_callback(node): + def postCatchupCompleteClbk(node): + token_tracker = node.db_manager.get_tracker(TOKEN_LEDGER_ID) + token_state = node.db_manager.get_state(TOKEN_LEDGER_ID) + token_ledger = node.db_manager.get_ledger(TOKEN_LEDGER_ID) + token_tracker.set_last_committed(token_state.committedHeadHash, + token_ledger.uncommitted_root_hash, + token_ledger.size) + + origin_token_clb = node.ledgerManager.ledgerRegistry[TOKEN_LEDGER_ID].postCatchupCompleteClbk def postCatchupCompleteClb(origin_clb): if origin_clb: origin_clb() - fees_req_handler.postCatchupCompleteClbk() - - node.write_req_validator.auth_map.update(sovtokenfees_auth_map) + postCatchupCompleteClbk(node) - token_authnr = node.clientAuthNr.get_authnr_by_type(TokenAuthNr) - if not token_authnr: - raise ImportError('sovtoken plugin should be loaded, ' # noqa - 'authenticator not found') - token_req_handler = node.get_req_handler(ledger_id=TOKEN_LEDGER_ID) - if not token_req_handler: - raise ImportError('sovtoken plugin should be loaded, request ' # noqa - 'handler not found') - - # `handle_xfer_public_txn` in `TokenReqHandler` checks if the sum of inputs match - # exactly the sum of outputs. Since the check to match inputs and outputs is done - # during fees handling the check is avoided in `TokenReqHandler` by monkeypatching - # `handle_xfer_public_txn` to do nothing. - token_req_handler.handle_xfer_public_txn = lambda _: None - - token_ledger = token_req_handler.ledger - token_state = token_req_handler.state - utxo_cache = token_req_handler.utxo_cache - fees_authnr = FeesAuthNr(node.getState(DOMAIN_LEDGER_ID), token_authnr) - fees_req_handler = StaticFeesReqHandler(node.configLedger, - node.getState(CONFIG_LEDGER_ID), - token_ledger, - token_state, - utxo_cache, - node.getState(DOMAIN_LEDGER_ID), - node.bls_bft.bls_store, node, - node.write_req_validator, - ts_store=node.getStateTsDbStorage()) - origin_token_clb = node.ledgerManager.ledgerRegistry[TOKEN_LEDGER_ID].postCatchupCompleteClbk node.ledgerManager.ledgerRegistry[TOKEN_LEDGER_ID].postCatchupCompleteClbk = \ functools.partial(postCatchupCompleteClb, origin_token_clb) + +def set_post_added_txn_callback(node): origin_token_post_added_clb = node.ledgerManager.ledgerRegistry[TOKEN_LEDGER_ID].postTxnAddedToLedgerClbk def filter_fees(ledger_id: int, txn: Any): origin_token_post_added_clb(ledger_id, txn, get_type(txn) != FeesTransactions.FEES.value) node.ledgerManager.ledgerRegistry[TOKEN_LEDGER_ID].postTxnAddedToLedgerClbk = filter_fees + + +def register_authentication(node): + utxo_cache = node.db_manager.get_store(UTXO_CACHE_LABEL) + token_authnr = node.clientAuthNr.get_authnr_by_type(TokenAuthNr) + if not token_authnr: + raise ImportError('sovtoken plugin should be loaded, ' # noqa + 'authenticator not found') + fees_authnr = FeesAuthNr(ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE, + node.getState(DOMAIN_LEDGER_ID), token_authnr) node.clientAuthNr.register_authenticator(fees_authnr) - node_config_req_handler = node.get_req_handler(ledger_id=CONFIG_LEDGER_ID) - node.unregister_req_handler(node_config_req_handler, CONFIG_LEDGER_ID) - node.register_req_handler(fees_req_handler, CONFIG_LEDGER_ID) fees_authorizer = FeesAuthorizer(config_state=node.getState(CONFIG_LEDGER_ID), utxo_cache=utxo_cache) node.write_req_validator.register_authorizer(fees_authorizer) + return fees_authnr + + +def register_hooks(node, fees_authnr, token_ledger, token_state, fees_tracker): + register_auth_hooks(node, fees_authnr) + register_three_pc_hooks(node, token_ledger, token_state, fees_tracker) + + +def register_auth_hooks(node, fees_authnr): node.register_hook(NodeHooks.PRE_SIG_VERIFICATION, fees_authnr.verify_signature) - node.register_hook(NodeHooks.POST_REQUEST_APPLICATION, fees_req_handler.deduct_fees) - node.register_hook(NodeHooks.POST_REQUEST_COMMIT, fees_req_handler.commit_fee_txns) - node.register_hook(NodeHooks.POST_BATCH_CREATED, fees_req_handler.post_batch_created) - node.register_hook(NodeHooks.POST_BATCH_REJECTED, fees_req_handler.post_batch_rejected) - node.register_hook(NodeHooks.POST_BATCH_COMMITTED, - fees_req_handler.post_batch_committed) - node.register_hook(NodeHooks.POST_NODE_STOPPED, - token_req_handler.on_node_stopping) + +def register_three_pc_hooks(node, token_ledger, token_state, fees_tracker): three_pc_handler = ThreePhaseCommitHandler(node.master_replica, - token_ledger, token_state, - fees_req_handler) + token_ledger, + token_state, + fees_tracker) node.master_replica.register_hook(ReplicaHooks.CREATE_PPR, three_pc_handler.add_to_pre_prepare) node.master_replica.register_hook(ReplicaHooks.CREATE_PR, @@ -97,4 +172,8 @@ def filter_fees(ledger_id: int, txn: Any): node.master_replica.register_hook(ReplicaHooks.APPLY_PPR, three_pc_handler.check_recvd_pre_prepare) - return node + +def register_trackers(): + # TODO: move trackers into write_manager + fees_tracker = BatchFeesTracker() + return fees_tracker diff --git a/sovtokenfees/sovtokenfees/req_handlers/__init__.py b/sovtokenfees/sovtokenfees/req_handlers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/req_handlers/batch_handlers/__init__.py b/sovtokenfees/sovtokenfees/req_handlers/batch_handlers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/req_handlers/batch_handlers/fee_batch_handler.py b/sovtokenfees/sovtokenfees/req_handlers/batch_handlers/fee_batch_handler.py new file mode 100644 index 00000000..5fff6569 --- /dev/null +++ b/sovtokenfees/sovtokenfees/req_handlers/batch_handlers/fee_batch_handler.py @@ -0,0 +1,82 @@ +from sovtoken import TOKEN_LEDGER_ID +from sovtoken.constants import UTXO_CACHE_LABEL +from sovtoken.request_handlers.token_utils import commit_to_utxo_cache +from sovtokenfees.constants import FEES +from sovtokenfees.req_handlers.fees_utils import BatchFeesTracker + +from common.serializers.serialization import txn_root_serializer +from plenum.common.constants import DOMAIN_LEDGER_ID +from plenum.common.txn_util import get_seq_no, get_type +from plenum.server.batch_handlers.batch_request_handler import BatchRequestHandler +from plenum.server.batch_handlers.three_pc_batch import ThreePcBatch +from stp_core.common.log import getlogger + +logger = getlogger() + + +class DomainFeeBatchHandler(BatchRequestHandler): + def __init__(self, database_manager, fees_tracker: BatchFeesTracker): + super().__init__(database_manager, DOMAIN_LEDGER_ID) + self._fees_tracker = fees_tracker + + @property + def token_state(self): + return self.database_manager.get_state(TOKEN_LEDGER_ID) + + @property + def token_ledger(self): + return self.database_manager.get_ledger(TOKEN_LEDGER_ID) + + @property + def token_tracker(self): + return self.database_manager.get_tracker(TOKEN_LEDGER_ID) + + @property + def utxo_cache(self): + return self.database_manager.get_store(UTXO_CACHE_LABEL) + + def post_batch_applied(self, three_pc_batch, prev_handler_result=None): + self.token_tracker.apply_batch(self.token_state.headHash, + self.token_ledger.uncommitted_root_hash, + self.token_ledger.uncommitted_size) + if self._fees_tracker.fees_in_current_batch > 0: + state_root = self.token_state.headHash + self.utxo_cache.create_batch_from_current(state_root) + self._fees_tracker.fees_in_current_batch = 0 + + def post_batch_rejected(self, ledger_id, prev_handler_result=None): + uncommitted_hash, uncommitted_txn_root, txn_count = self.token_tracker.reject_batch() + if txn_count == 0 or self.token_ledger.uncommitted_root_hash == uncommitted_txn_root or \ + self.token_state.headHash == uncommitted_hash: + return 0 + self.token_state.revertToHead(uncommitted_hash) + self.token_ledger.discardTxns(txn_count) + count_reverted = self.utxo_cache.reject_batch() + logger.info("Reverted {} txns with fees".format(count_reverted)) + + def commit_batch(self, three_pc_batch, prev_handler_result=None): + committed_txns = prev_handler_result + token_state_root, token_txn_root, _ = self.token_tracker.commit_batch() + committed_seq_nos_with_fees = [get_seq_no(t) for t in committed_txns + if self._fees_tracker.has_deducted_fees(get_type(t), get_seq_no(t))] + if len(committed_seq_nos_with_fees) > 0: + # This is a fake txn only for commit to token ledger + token_fake_three_pc_batch = ThreePcBatch(ledger_id=TOKEN_LEDGER_ID, + inst_id=three_pc_batch.inst_id, + view_no=three_pc_batch.view_no, + pp_seq_no=three_pc_batch.pp_seq_no, + pp_time=three_pc_batch.pp_time, + state_root=token_state_root, + txn_root=txn_root_serializer.serialize(token_txn_root), + primaries=three_pc_batch.primaries, + valid_digests=[i for i in range(len(committed_seq_nos_with_fees))]) + committed_token_txns = super()._commit(self.token_ledger, self.token_state, token_fake_three_pc_batch) + commit_to_utxo_cache(self.utxo_cache, token_state_root) + i = 0 + # We are adding fees txn to the reply, so that client could get information about token transition + for txn in committed_txns: + if get_seq_no(txn) in committed_seq_nos_with_fees: + txn[FEES] = committed_token_txns[i] + i += 1 + self._fees_tracker.fees_in_current_batch = 0 + return committed_txns diff --git a/sovtokenfees/sovtokenfees/req_handlers/fees_utils.py b/sovtokenfees/sovtokenfees/req_handlers/fees_utils.py new file mode 100644 index 00000000..a8b7dfa1 --- /dev/null +++ b/sovtokenfees/sovtokenfees/req_handlers/fees_utils.py @@ -0,0 +1,54 @@ +from sovtokenfees.domain import build_path_for_set_fees + +from common.serializers.serialization import state_roots_serializer, proof_nodes_serializer, config_state_serializer +from plenum.common.constants import BLS_LABEL, MULTI_SIGNATURE, ROOT_HASH, PROOF_NODES +from state.trie.pruning_trie import rlp_decode + + +class BatchFeesTracker: + def __init__(self): + self.fees_in_current_batch = 0 + self._deducted_fees = {} + + def add_deducted_fees(self, txn_type, seq_no, fees): + key = "{}#{}".format(txn_type, seq_no) + self._deducted_fees[key] = fees + + def has_deducted_fees(self, txn_type, seq_no): + return "{}#{}".format(txn_type, seq_no) in self._deducted_fees + + +def get_fee_from_state(state, fees_alias=None, is_committed=False, with_proof=False, bls_store=None): + fees = None + proof = None + try: + fees_key = build_path_for_set_fees(alias=fees_alias) + if with_proof: + root_hash = state.committedHeadHash if is_committed else state.headHash + proof, serz = state.generate_state_proof(fees_key, + root=state.get_head_by_hash(root_hash), + serialize=True, + get_value=True) + if serz: + serz = state.get_decoded(serz) + encoded_root_hash = state_roots_serializer.serialize(bytes(root_hash)) + multi_sig = bls_store.get(encoded_root_hash) + if multi_sig: + encoded_proof = proof_nodes_serializer.serialize(proof) + proof = { + MULTI_SIGNATURE: multi_sig.as_dict(), + ROOT_HASH: encoded_root_hash, + PROOF_NODES: encoded_proof + } + else: + proof = {} + else: + serz = state.get(fees_key, + isCommitted=is_committed) + if serz: + fees = config_state_serializer.deserialize(serz) + except KeyError: + pass + if with_proof: + return fees, proof + return fees diff --git a/sovtokenfees/sovtokenfees/req_handlers/read_handlers/__init__.py b/sovtokenfees/sovtokenfees/req_handlers/read_handlers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/req_handlers/read_handlers/get_fee_handler.py b/sovtokenfees/sovtokenfees/req_handlers/read_handlers/get_fee_handler.py new file mode 100644 index 00000000..0a5c7b6d --- /dev/null +++ b/sovtokenfees/sovtokenfees/req_handlers/read_handlers/get_fee_handler.py @@ -0,0 +1,37 @@ +from sovtokenfees import FeesTransactions +from sovtokenfees.constants import FEE +from sovtokenfees.messages.fields import GetFeeMsg +from sovtokenfees.req_handlers.fees_utils import get_fee_from_state + +from plenum.common.constants import ALIAS, STATE_PROOF, CONFIG_LEDGER_ID, BLS_LABEL +from plenum.common.exceptions import InvalidClientRequest +from plenum.common.request import Request +from plenum.common.types import f +from plenum.server.database_manager import DatabaseManager +from plenum.server.request_handlers.handler_interfaces.read_request_handler import ReadRequestHandler + + +class GetFeeHandler(ReadRequestHandler): + get_fee_validator_cls = GetFeeMsg + + def __init__(self, database_manager: DatabaseManager): + super().__init__(database_manager, FeesTransactions.GET_FEE.value, CONFIG_LEDGER_ID) + + def get_result(self, request: Request): + alias = request.operation.get(ALIAS) + fee, proof = get_fee_from_state(self.state, fees_alias=alias, is_committed=True, with_proof=True, + bls_store=self.database_manager.get_store(BLS_LABEL)) + result = {f.IDENTIFIER.nm: request.identifier, + f.REQ_ID.nm: request.reqId, FEE: fee} + if proof: + result[STATE_PROOF] = proof + result.update(request.operation) + return result + + def static_validation(self, request: Request): + try: + self.get_fee_validator_cls(**request.operation) + except TypeError as exc: + raise InvalidClientRequest(request.identifier, + request.reqId, + exc) diff --git a/sovtokenfees/sovtokenfees/req_handlers/read_handlers/get_fees_handler.py b/sovtokenfees/sovtokenfees/req_handlers/read_handlers/get_fees_handler.py new file mode 100644 index 00000000..1a36f6d1 --- /dev/null +++ b/sovtokenfees/sovtokenfees/req_handlers/read_handlers/get_fees_handler.py @@ -0,0 +1,34 @@ +from sovtokenfees import FeesTransactions +from sovtokenfees.constants import FEES +from sovtokenfees.req_handlers.fees_utils import get_fee_from_state + +from plenum.common.constants import STATE_PROOF, CONFIG_LEDGER_ID, BLS_LABEL +from plenum.common.request import Request +from plenum.common.types import f +from plenum.server.database_manager import DatabaseManager +from plenum.server.request_handlers.handler_interfaces.read_request_handler import ReadRequestHandler + + +class GetFeesHandler(ReadRequestHandler): + def __init__(self, db_manager: DatabaseManager): + super().__init__(db_manager, FeesTransactions.GET_FEES.value, CONFIG_LEDGER_ID) + + def get_result(self, request: Request): + fees, proof = self.get_fees(is_committed=True, with_proof=True) + + result = {f.IDENTIFIER.nm: request.identifier, + f.REQ_ID.nm: request.reqId, + FEES: fees} + if proof: + result[STATE_PROOF] = proof + result.update(request.operation) + return result + + def get_fees(self, is_committed=False, with_proof=False): + result = get_fee_from_state(self.state, is_committed=is_committed, with_proof=with_proof, + bls_store=self.database_manager.get_store(BLS_LABEL)) + if with_proof: + fees, proof = result + return (fees, proof) if fees is not None else ({}, proof) + else: + return result if result is not None else {} diff --git a/sovtokenfees/sovtokenfees/req_handlers/write_handlers/__init__.py b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/req_handlers/write_handlers/auth_rule_fee_handler.py b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/auth_rule_fee_handler.py new file mode 100644 index 00000000..0296464d --- /dev/null +++ b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/auth_rule_fee_handler.py @@ -0,0 +1,55 @@ +from indy_common.authorize.auth_constraints import AuthConstraint, ConstraintsEnum +from indy_common.constants import AUTH_RULE +from indy_node.server.request_handlers.config_req_handlers.auth_rule.static_auth_rule_helper import StaticAuthRuleHelper +from sovtokenfees.constants import FEES_FIELD_NAME + +from plenum.common.constants import CONFIG_LEDGER_ID +from plenum.common.exceptions import InvalidClientMessageException +from plenum.common.request import Request +from plenum.server.database_manager import DatabaseManager +from plenum.server.request_handlers.handler_interfaces.write_request_handler import WriteRequestHandler + + +class AuthRuleFeeHandler(WriteRequestHandler): + + def __init__(self, database_manager: DatabaseManager, get_fees_handler): + super().__init__(database_manager, AUTH_RULE, CONFIG_LEDGER_ID) + self._get_fees_handler = get_fees_handler + + def static_validation(self, request: Request): + pass + + def dynamic_validation(self, request: Request): + self.fees_specific_validation(request) + + def update_state(self, txn, prev_result, request, is_committed=False): + pass + + def apply_request(self, request: Request, batch_ts, prev_result): + return None, None, None + + def fees_specific_validation(self, request: Request): + operation = request.operation + current_fees = self._get_fees_handler.get_fees() + constraint = StaticAuthRuleHelper.get_auth_constraint(operation) + wrong_aliases = [] + AuthRuleFeeHandler.validate_metadata(current_fees, constraint, wrong_aliases) + if len(wrong_aliases) > 0: + raise InvalidClientMessageException(request.identifier, + request.reqId, + "Fees alias(es) {} does not exist in current fees {}. " + "Please add the alias(es) via SET_FEES transaction first.". + format(", ".join(wrong_aliases), + current_fees)) + + @staticmethod + def validate_metadata(current_fees, constraint: AuthConstraint, wrong_aliases): + if constraint.constraint_id == ConstraintsEnum.FORBIDDEN_CONSTRAINT_ID: + return + if constraint.constraint_id != ConstraintsEnum.ROLE_CONSTRAINT_ID: + for constr in constraint.auth_constraints: + AuthRuleFeeHandler.validate_metadata(current_fees, constr, wrong_aliases) + else: + meta_alias = constraint.metadata.get(FEES_FIELD_NAME, None) + if meta_alias and meta_alias not in current_fees: + wrong_aliases.append(meta_alias) diff --git a/sovtokenfees/sovtokenfees/req_handlers/write_handlers/auth_rules_fee_handler.py b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/auth_rules_fee_handler.py new file mode 100644 index 00000000..c0d1fcc9 --- /dev/null +++ b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/auth_rules_fee_handler.py @@ -0,0 +1,51 @@ +from indy_common.constants import AUTH_RULES, RULES + +from indy_node.server.request_handlers.config_req_handlers.auth_rule.static_auth_rule_helper import StaticAuthRuleHelper +from plenum.common.exceptions import InvalidClientMessageException + +from plenum.common.constants import CONFIG_LEDGER_ID + +from plenum.server.request_handlers.handler_interfaces.write_request_handler import WriteRequestHandler +from sovtokenfees.req_handlers.write_handlers.auth_rule_fee_handler import AuthRuleFeeHandler + +from plenum.server.database_manager import DatabaseManager +from plenum.common.request import Request + +from indy_common.authorize.auth_request_validator import WriteRequestValidator + + +class AuthRulesFeeHandler(WriteRequestHandler): + + def __init__(self, database_manager: DatabaseManager, get_fees_handler): + super().__init__(database_manager, AUTH_RULES, CONFIG_LEDGER_ID) + self._get_fees_handler = get_fees_handler + + def static_validation(self, request: Request): + pass + + def dynamic_validation(self, request: Request): + self.fees_specific_validation(request) + + def update_state(self, txn, prev_result, request, is_committed=False): + pass + + def apply_request(self, request: Request, batch_ts, prev_result): + return None, None, None + + def fees_specific_validation(self, request: Request): + operation = request.operation + current_fees = self._get_fees_handler.get_fees() + constraints = [] + for rule in operation[RULES]: + constraints.append(StaticAuthRuleHelper.get_auth_constraint(rule)) + + for constraint in constraints: + wrong_aliases = [] + AuthRuleFeeHandler.validate_metadata(current_fees, constraint, wrong_aliases) + if len(wrong_aliases) > 0: + raise InvalidClientMessageException(request.identifier, + request.reqId, + "Fees alias(es) {} does not exist in current fees {}. " + "Please add the alias(es) via SET_FEES transaction first.". + format(", ".join(wrong_aliases), + current_fees)) diff --git a/sovtokenfees/sovtokenfees/req_handlers/write_handlers/domain_fee_handler.py b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/domain_fee_handler.py new file mode 100644 index 00000000..9441b2b5 --- /dev/null +++ b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/domain_fee_handler.py @@ -0,0 +1,98 @@ +from sovtoken.constants import INPUTS, OUTPUTS, ADDRESS, UTXO_CACHE_LABEL, SEQNO, AMOUNT, TOKEN_LEDGER_ID +from sovtoken.request_handlers.token_utils import spend_input, add_new_output +from sovtoken.types import Output +from sovtokenfees.constants import FEE_TXN, REF, FEES +from sovtokenfees.fees_authorizer import FeesAuthorizer +from sovtokenfees.req_handlers.fees_utils import BatchFeesTracker + +from plenum.common.constants import TXN_TYPE, DOMAIN_LEDGER_ID, TXN_PAYLOAD, TXN_PAYLOAD_DATA +from plenum.common.request import Request +from plenum.common.txn_util import get_req_id, reqToTxn, get_seq_no, get_txn_time, get_type +from plenum.common.types import f, OPERATION +from plenum.server.database_manager import DatabaseManager +from plenum.server.request_handlers.handler_interfaces.write_request_handler import WriteRequestHandler + + +class DomainFeeHandler(WriteRequestHandler): + + def __init__(self, db_manager: DatabaseManager, fees_tracker: BatchFeesTracker): + super().__init__(db_manager, None, DOMAIN_LEDGER_ID) + self._fees_tracker = fees_tracker + + def static_validation(self, request: Request): + pass + + def dynamic_validation(self, request: Request): + pass + + def apply_request(self, request: Request, batch_ts, prev_result): + self._validate_request_type(request) + txn_type = request.operation[TXN_TYPE] + seq_no = get_seq_no(prev_result) + cons_time = get_txn_time(prev_result) + if FeesAuthorizer.has_fees(request): + inputs, outputs, signatures = getattr(request, f.FEES.nm) + # This is correct since FEES is changed from config ledger whose + # transactions have no fees + fees = FeesAuthorizer.calculate_fees_from_req(self.utxo_cache, request) + sigs = {i[ADDRESS]: s for i, s in zip(inputs, signatures)} + txn = { + OPERATION: { + TXN_TYPE: FEE_TXN, + INPUTS: inputs, + OUTPUTS: outputs, + REF: self._get_ref_for_txn_fees(seq_no), + FEES: fees, + }, + f.SIGS.nm: sigs, + f.REQ_ID.nm: get_req_id(prev_result), + f.PROTOCOL_VERSION.nm: 2, + } + txn = reqToTxn(txn) + self.token_ledger.append_txns_metadata([txn], txn_time=cons_time) + _, txns = self.token_ledger.appendTxns([txn]) + self.update_token_state(txn, request) + self._fees_tracker.fees_in_current_batch += 1 + self._fees_tracker.add_deducted_fees(txn_type, seq_no, fees) + return None, None, prev_result + + def update_state(self, txn, prev_result, request, is_committed=False): + pass + + def update_token_state(self, txn, request, is_committed=False): + for utxo in txn[TXN_PAYLOAD][TXN_PAYLOAD_DATA][INPUTS]: + spend_input( + state=self.token_state, + utxo_cache=self.utxo_cache, + address=utxo[ADDRESS], + seq_no=utxo[SEQNO], + is_committed=is_committed + ) + seq_no = get_seq_no(txn) + for output in txn[TXN_PAYLOAD][TXN_PAYLOAD_DATA][OUTPUTS]: + add_new_output( + state=self.token_state, + utxo_cache=self.utxo_cache, + output=Output( + output[ADDRESS], + seq_no, + output[AMOUNT]), + is_committed=is_committed) + + @property + def utxo_cache(self): + return self.database_manager.get_store(UTXO_CACHE_LABEL) + + @property + def token_state(self): + return self.database_manager.get_state(TOKEN_LEDGER_ID) + + @property + def token_ledger(self): + return self.database_manager.get_ledger(TOKEN_LEDGER_ID) + + def gen_state_key(self, txn): + pass + + def _get_ref_for_txn_fees(self, seq_no): + return '{}:{}'.format(self.ledger_id, seq_no) diff --git a/sovtokenfees/sovtokenfees/req_handlers/write_handlers/fee_txn_handler.py b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/fee_txn_handler.py new file mode 100644 index 00000000..d18fa42b --- /dev/null +++ b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/fee_txn_handler.py @@ -0,0 +1,22 @@ +from sovtoken import TOKEN_LEDGER_ID +from sovtokenfees.constants import FEE_TXN +from sovtokenfees.req_handlers.write_handlers.xfer_fee_handler import XferFeeHandler + +from indy_common.types import Request +from plenum.server.database_manager import DatabaseManager +from plenum.server.request_handlers.handler_interfaces.write_request_handler import WriteRequestHandler + + +class FeeTxnCatchupHandler(XferFeeHandler): + + def __init__(self, database_manager: DatabaseManager): + super(WriteRequestHandler, self).__init__(database_manager, FEE_TXN, TOKEN_LEDGER_ID) + + def apply_request(self, request: Request, batch_ts, prev_result): + pass + + def static_validation(self, request: Request): + pass + + def dynamic_validation(self, request: Request): + pass \ No newline at end of file diff --git a/sovtokenfees/sovtokenfees/req_handlers/write_handlers/set_fees_handler.py b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/set_fees_handler.py new file mode 100644 index 00000000..7befe7ce --- /dev/null +++ b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/set_fees_handler.py @@ -0,0 +1,54 @@ +from sovtokenfees.constants import SET_FEES, FEES +from sovtokenfees.req_handlers.fees_utils import get_fee_from_state + +from common.serializers.serialization import proof_nodes_serializer, state_roots_serializer, config_state_serializer +from indy_common.authorize.auth_actions import AuthActionEdit +from sovtokenfees import FeesTransactions +from sovtokenfees.messages.fields import SetFeesMsg + +from plenum.common.constants import CONFIG_LEDGER_ID, BLS_LABEL, MULTI_SIGNATURE, ROOT_HASH, PROOF_NODES +from plenum.common.exceptions import InvalidClientRequest +from plenum.common.request import Request +from plenum.common.txn_util import get_payload_data +from plenum.server.database_manager import DatabaseManager +from plenum.server.request_handlers.handler_interfaces.write_request_handler import WriteRequestHandler + +from sovtokenfees.domain import build_path_for_set_fees + + +class SetFeesHandler(WriteRequestHandler): + set_fees_validator_cls = SetFeesMsg + + def __init__(self, db_manager: DatabaseManager, write_req_validator): + super().__init__(db_manager, FeesTransactions.SET_FEES.value, CONFIG_LEDGER_ID) + self._write_req_validator = write_req_validator + + def static_validation(self, request: Request): + try: + self.set_fees_validator_cls(**request.operation) + except TypeError as exc: + raise InvalidClientRequest(request.identifier, + request.reqId, + exc) + + def dynamic_validation(self, request: Request): + return self._write_req_validator.validate(request, + [AuthActionEdit(txn_type=SET_FEES, + field="*", + old_value="*", + new_value="*")]) + + def update_state(self, txn, prev_result, request, is_committed=False): + payload = get_payload_data(txn) + fees_from_req = payload.get(FEES) + current_fees = get_fee_from_state(self.state) + current_fees = current_fees if current_fees else {} + current_fees.update(fees_from_req) + for fees_alias, fees_value in fees_from_req.items(): + self._set_to_state(build_path_for_set_fees(alias=fees_alias), fees_value) + self._set_to_state(build_path_for_set_fees(), current_fees) + + def _set_to_state(self, key, val): + val = config_state_serializer.serialize(val) + key = key.encode() + self.state.set(key, val) diff --git a/sovtokenfees/sovtokenfees/req_handlers/write_handlers/xfer_fee_handler.py b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/xfer_fee_handler.py new file mode 100644 index 00000000..6d7a0b86 --- /dev/null +++ b/sovtokenfees/sovtokenfees/req_handlers/write_handlers/xfer_fee_handler.py @@ -0,0 +1,15 @@ +from sovtoken.constants import XFER_PUBLIC + +from indy_common.authorize.auth_actions import AuthActionAdd +from sovtoken.request_handlers.write_request_handler.xfer_handler import XferHandler + +from plenum.common.request import Request + + +class XferFeeHandler(XferHandler): + def dynamic_validation(self, request: Request): + # We are intentionally omitting validation of inputs and outputs so that + # could satisfy logic of xfer with fees. + return self._write_req_validator.validate(request, [AuthActionAdd(txn_type=XFER_PUBLIC, + field="*", + value="*")]) diff --git a/sovtokenfees/sovtokenfees/static_fee_req_handler.py b/sovtokenfees/sovtokenfees/static_fee_req_handler.py index 3e8e7b38..e6b91c0e 100644 --- a/sovtokenfees/sovtokenfees/static_fee_req_handler.py +++ b/sovtokenfees/sovtokenfees/static_fee_req_handler.py @@ -33,10 +33,10 @@ from sovtoken.exceptions import InsufficientFundsError, ExtraFundsError from state.trie.pruning_trie import rlp_decode - txn_root_serializer = Base58Serializer() logger = getlogger() + class StaticFeesReqHandler(FeeReqHandler): write_types = FeeReqHandler.write_types.union({SET_FEES, FEE_TXN}) query_types = FeeReqHandler.query_types.union({GET_FEES, GET_FEE}) @@ -120,15 +120,15 @@ def deduct_fees(self, request, cons_time, ledger_id, seq_no, txn): def doStaticValidation(self, request: Request): operation = request.operation if operation[TXN_TYPE] in (SET_FEES, GET_FEES, GET_FEE): - try: - if operation[TXN_TYPE] == SET_FEES: - self.set_fees_validator_cls(**request.operation) - elif operation[TXN_TYPE] == GET_FEE: - self.get_fee_validator_cls(**request.operation) - except TypeError as exc: - raise InvalidClientRequest(request.identifier, - request.reqId, - exc) + try: + if operation[TXN_TYPE] == SET_FEES: + self.set_fees_validator_cls(**request.operation) + elif operation[TXN_TYPE] == GET_FEE: + self.get_fee_validator_cls(**request.operation) + except TypeError as exc: + raise InvalidClientRequest(request.identifier, + request.reqId, + exc) else: super().doStaticValidation(request) @@ -147,6 +147,8 @@ def _fees_specific_validation(self, request: Request): current_fees)) def _validate_metadata(self, current_fees, constraint: AuthConstraint, wrong_aliases): + if constraint.constraint_id == ConstraintsEnum.FORBIDDEN_CONSTRAINT_ID: + return if constraint.constraint_id != ConstraintsEnum.ROLE_CONSTRAINT_ID: for constr in constraint.auth_constraints: self._validate_metadata(current_fees, constr, wrong_aliases) @@ -264,12 +266,13 @@ def _get_fee_from_state(self, fees_alias=None, is_committed=False, with_proof=Fa try: fees_key = build_path_for_set_fees(alias=fees_alias) if with_proof: + root_hash = self.state.committedHeadHash if is_committed else self.state.headHash proof, serz = self.state.generate_state_proof(fees_key, + root=self.state.get_head_by_hash(root_hash), serialize=True, get_value=True) if serz: - serz = rlp_decode(serz)[0] - root_hash = self.state.committedHeadHash if is_committed else self.state.headHash + serz = self.state.get_decoded(serz) encoded_root_hash = state_roots_serializer.serialize(bytes(root_hash)) multi_sig = self.bls_store.get(encoded_root_hash) if multi_sig: diff --git a/sovtokenfees/sovtokenfees/test/3pc_batching/__init__.py b/sovtokenfees/sovtokenfees/test/3pc_batching/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/test/3pc_batching/test_chain_set_fees_and_xfer_batch_size_2.py b/sovtokenfees/sovtokenfees/test/3pc_batching/test_chain_set_fees_and_xfer_batch_size_2.py index 401ae299..7bbe1eb4 100644 --- a/sovtokenfees/sovtokenfees/test/3pc_batching/test_chain_set_fees_and_xfer_batch_size_2.py +++ b/sovtokenfees/sovtokenfees/test/3pc_batching/test_chain_set_fees_and_xfer_batch_size_2.py @@ -2,6 +2,8 @@ from sovtoken.constants import ADDRESS, AMOUNT, OUTPUTS, SEQNO from indy_common.constants import NYM, CONFIG_LEDGER_ID +from sovtokenfees import FeesTransactions +from sovtokenfees.req_handlers.read_handlers.get_fees_handler import GetFeesHandler from sovtokenfees.test.constants import XFER_PUBLIC_FEES_ALIAS from sovtokenfees.test.helper import get_amount_from_token_txn, send_and_check_nym_with_fees, send_and_check_transfer, \ ensure_all_nodes_have_same_data @@ -99,8 +101,9 @@ def test_chain_set_fees_and_xfer_batch_size_2(looper, helpers, transfer_summ=transfer_summ, check_reply=False) for n in nodeSetWithIntegratedTokenPlugin: - fee_rq = n.ledger_to_req_handler[CONFIG_LEDGER_ID] - assert fee_rq.fees == fees_xfer_3 + fee_rq = n.read_manager.request_handlers[FeesTransactions.GET_FEES.value] + assert fee_rq + assert fee_rq.get_fees(is_committed=True, with_proof=False) == fees_xfer_3 with pytest.raises(RequestRejectedException): sdk_get_and_check_replies(looper, a_b_transfer_2) diff --git a/sovtokenfees/sovtokenfees/test/auth_map/helper.py b/sovtokenfees/sovtokenfees/test/auth_map/helper.py new file mode 100644 index 00000000..61fab9fb --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/auth_map/helper.py @@ -0,0 +1,45 @@ +import json + +from sovtokenfees.constants import SET_FEES, FEES +from sovtokenfees.test.constants import NYM_FEES_ALIAS + +from plenum.common.constants import TXN_TYPE + + +def steward_do_set_fees(helpers, fees): + """ Sends and check a set_fees txn """ + payload = { + TXN_TYPE: SET_FEES, + FEES: fees, + } + + request = helpers.request._create_request(payload, + identifier=helpers.wallet._stewards[0]) + request = helpers.wallet.sign_request_stewards(json.dumps(request.as_dict), number_signers=1) + helpers.sdk.sdk_send_and_check([request]) + + +def set_fees(helpers, fees, trustee=True): + new_fees = dict(fees) + new_fees[NYM_FEES_ALIAS] += 1 + if trustee: + helpers.general.do_set_fees(new_fees) + else: + steward_do_set_fees(helpers, new_fees) + get_fees = helpers.general.do_get_fees() + assert new_fees == get_fees.get("fees") + + +def add_fees_request_with_address(helpers, fee_amount, request, address): + if fee_amount is None: + return request + utxos_found = helpers.general.get_utxo_addresses([address])[0] + request_with_fees = helpers.request.add_fees( + request, + utxos_found, + fee_amount, + change_address=address + )[0] + request_with_fees = json.loads(request_with_fees) + setattr(request, FEES, request_with_fees[FEES]) + return request \ No newline at end of file diff --git a/sovtokenfees/sovtokenfees/test/auth_map/test_auth_rule_validation.py b/sovtokenfees/sovtokenfees/test/auth_map/test_auth_rule_validation.py index 0c2491c6..a41e995f 100644 --- a/sovtokenfees/sovtokenfees/test/auth_map/test_auth_rule_validation.py +++ b/sovtokenfees/sovtokenfees/test/auth_map/test_auth_rule_validation.py @@ -5,7 +5,7 @@ from indy_common.authorize.auth_actions import ADD_PREFIX -from indy_common.constants import NYM, ROLE, TRUST_ANCHOR, CONSTRAINT +from indy_common.constants import NYM, ROLE, ENDORSER, CONSTRAINT from plenum.common.constants import STEWARD @@ -46,7 +46,7 @@ def test_add_metadata_with_complex_constraint(looper, current_fees = helpers.general.do_get_fees() assert fees_alias not in current_fees[FEES] - constraint = copy.deepcopy(auth_map.trust_anchor_or_steward_or_trustee_constraint) + constraint = copy.deepcopy(auth_map.endorser_or_steward_or_trustee_constraint) # set metadata only for the last constraint in OrAuthConstraint constraint.auth_constraints[-1].set_metadata({FEES_FIELD_NAME: fees_alias}) with pytest.raises(RequestRejectedException, match="does not exist in current fees".format(fees_alias)): diff --git a/sovtokenfees/sovtokenfees/test/auth_map/test_auth_rules.py b/sovtokenfees/sovtokenfees/test/auth_map/test_auth_rules.py index 470571c3..5779a364 100644 --- a/sovtokenfees/sovtokenfees/test/auth_map/test_auth_rules.py +++ b/sovtokenfees/sovtokenfees/test/auth_map/test_auth_rules.py @@ -6,12 +6,10 @@ from indy.did import replace_keys_start from indy.ledger import build_attrib_request from indy_common.authorize.auth_constraints import AuthConstraint, ROLE, AuthConstraintOr, AbstractAuthConstraint, \ - IDENTITY_OWNER, AuthConstraintAnd + IDENTITY_OWNER, AuthConstraintAnd, AuthConstraintForbidden -from indy_node.test.auth_rule.helper import sdk_send_and_check_auth_rule_request - -from indy_common.constants import NYM, TRUST_ANCHOR, NODE, POOL_UPGRADE, POOL_RESTART, VALIDATOR_INFO, GET_SCHEMA, \ - ATTRIB, TRUST_ANCHOR_STRING +from indy_common.constants import NYM, ENDORSER, NODE, POOL_UPGRADE, POOL_RESTART, VALIDATOR_INFO, GET_SCHEMA, \ + ATTRIB, ENDORSER_STRING from indy_common.authorize.auth_actions import EDIT_PREFIX, ADD_PREFIX from libnacl.secret import SecretBox @@ -19,9 +17,10 @@ from sovtokenfees.constants import FEES, FEES_FIELD_NAME from sovtokenfees.test.helper import add_fees_request_with_address -from plenum.common.constants import TRUSTEE, STEWARD, STEWARD_STRING, TRUSTEE_STRING, VERKEY +from indy_node.test.auth_rule.helper import sdk_send_and_check_auth_rule_request +from plenum.common.constants import TRUSTEE, STEWARD, STEWARD_STRING, TRUSTEE_STRING, VERKEY, DATA from plenum.common.exceptions import RequestRejectedException -from plenum.test.helper import sdk_multisign_request_object, sdk_multi_sign_request_objects, sdk_json_to_request_object +from plenum.test.helper import sdk_multi_sign_request_objects, sdk_json_to_request_object from plenum.test.pool_transactions.helper import sdk_add_new_nym auth_constraint = AuthConstraint(role=TRUSTEE, sig_count=1, need_to_be_owner=False) @@ -147,7 +146,7 @@ RequestParams(fees=fee_1[1], wallets={STEWARD: 2}), RequestParams(fees=fee_100[1], - wallets={TRUST_ANCHOR: 1}), + wallets={ENDORSER: 1}), RequestParams(fees=fee_100[1], wallets={TRUSTEE: 1}) ]), @@ -172,7 +171,7 @@ RequestParams(fees=0, wallets={TRUSTEE: 1}), RequestParams(fees=fee_1[1], - wallets={TRUST_ANCHOR: 1}), + wallets={ENDORSER: 1}), RequestParams(fees=fee_1[1], wallets={STEWARD: 1}) ]), @@ -232,14 +231,14 @@ AuthConstraint(TRUSTEE, 1), AuthConstraint(STEWARD, 1, metadata={FEES_FIELD_NAME: fee_0[0]}), - AuthConstraint(TRUST_ANCHOR, 1), + AuthConstraint(ENDORSER, 1), ]), valid_requests=[ RequestParams(wallets={TRUSTEE: 1}), RequestParams(fees=0, wallets={STEWARD: 1}), RequestParams(fees=0, - wallets={TRUST_ANCHOR: 1}), + wallets={ENDORSER: 1}), RequestParams(fees=fee_5[1], wallets={IDENTITY_OWNER: 1}), RequestParams(fees=0, @@ -357,6 +356,32 @@ RequestParams(fees=0, wallets={IDENTITY_OWNER: 2}) ]), + # 14 + InputParam(auth_constraint=AuthConstraintForbidden(), + valid_requests=[ + ], + invalid_requests=[ + RequestParams(fees=0, + wallets={IDENTITY_OWNER: 2}), + RequestParams(fees=fee_5[1], + wallets={STEWARD: 1}), + RequestParams(fees=0, + wallets={STEWARD: 1}) + ]), + # 15 + InputParam(auth_constraint=AuthConstraintAnd([AuthConstraintForbidden(), + AuthConstraint(STEWARD, 1), + ]), + valid_requests=[ + ], + invalid_requests=[ + RequestParams(fees=fee_5[1], + wallets={STEWARD: 1}), + RequestParams(fees=0, + wallets={STEWARD: 1}), + RequestParams(fees=0, + wallets={IDENTITY_OWNER: 2}) + ]), ] @@ -387,16 +412,16 @@ def sdk_wallet_stewards(looper, @pytest.fixture(scope='module') -def sdk_wallet_trust_anchors(looper, - sdk_wallet_trustee, - sdk_pool_handle): +def sdk_wallet_endorsers(looper, + sdk_wallet_trustee, + sdk_pool_handle): sdk_wallet_stewards = [] for i in range(3): wallet = sdk_add_new_nym(looper, sdk_pool_handle, sdk_wallet_trustee, - alias='trust_anchors{}'.format(i), - role=TRUST_ANCHOR_STRING) + alias='endorsers{}'.format(i), + role=ENDORSER_STRING) sdk_wallet_stewards.append(wallet) return sdk_wallet_stewards @@ -452,12 +477,12 @@ def add_fees_request_with_address(helpers, fee_amount, request, address): def _send_request(looper, helpers, fees, wallets_count, address, owner, sdk_wallet_trustee, sdk_wallet_trustees, sdk_wallet_stewards, - sdk_wallet_clients, sdk_wallet_trust_anchors): + sdk_wallet_clients, sdk_wallet_endorsers): print(wallets_count) wallets = sdk_wallet_trustees[:wallets_count.get(TRUSTEE, 0)] + \ sdk_wallet_stewards[:wallets_count.get(STEWARD, 0)] + \ sdk_wallet_clients[:wallets_count.get(IDENTITY_OWNER, 0)] + \ - sdk_wallet_trust_anchors[:wallets_count.get(TRUST_ANCHOR, 0)] + sdk_wallet_endorsers[:wallets_count.get(ENDORSER, 0)] # prepare owner parameter if owner == TRUSTEE: sender_wallet = sdk_wallet_trustees[0] @@ -465,8 +490,8 @@ def _send_request(looper, helpers, fees, wallets_count, address, owner, sdk_wall sender_wallet = sdk_wallet_stewards[0] elif owner == IDENTITY_OWNER: sender_wallet = sdk_wallet_clients[0] - elif owner == TRUST_ANCHOR: - sender_wallet = sdk_wallet_trust_anchors[0] + elif owner == ENDORSER: + sender_wallet = sdk_wallet_endorsers[0] else: sender_wallet = wallets[0] target_dest = sdk_wallet_trustee[1] if owner == "-1" else sender_wallet[1] @@ -503,7 +528,7 @@ def add_attribute(looper, sdk_wallet_handle, attrib, def test_authorization(looper, mint_tokens, sdk_wallet_trustee, sdk_pool_handle, helpers, input_param, address, sdk_wallet_trustees, sdk_wallet_stewards, sdk_wallet_clients, - sdk_wallet_trust_anchors): + sdk_wallet_endorsers): helpers.general.do_set_fees(set_fees, fill_auth_map=False) sdk_send_and_check_auth_rule_request(looper, sdk_pool_handle, sdk_wallet_trustee, auth_action=ADD_PREFIX, @@ -513,11 +538,11 @@ def test_authorization(looper, mint_tokens, sdk_wallet_trustee, _send_request(looper, helpers, req.fees, req.wallets, address, req.owner, sdk_wallet_trustee, sdk_wallet_trustees, sdk_wallet_stewards, - sdk_wallet_clients, sdk_wallet_trust_anchors) + sdk_wallet_clients, sdk_wallet_endorsers) for req in input_param.invalid_requests: with pytest.raises(RequestRejectedException, match="UnauthorizedClientRequest"): _send_request(looper, helpers, req.fees, req.wallets, address, req.owner, sdk_wallet_trustee, sdk_wallet_trustees, sdk_wallet_stewards, - sdk_wallet_clients, sdk_wallet_trust_anchors) + sdk_wallet_clients, sdk_wallet_endorsers) diff --git a/sovtokenfees/sovtokenfees/test/auth_map/test_auth_set_fees.py b/sovtokenfees/sovtokenfees/test/auth_map/test_auth_set_fees.py index 3907aa00..dc5b188b 100644 --- a/sovtokenfees/sovtokenfees/test/auth_map/test_auth_set_fees.py +++ b/sovtokenfees/sovtokenfees/test/auth_map/test_auth_set_fees.py @@ -3,11 +3,11 @@ import pytest from indy_node.test.auth_rule.helper import sdk_send_and_check_auth_rule_request from indy_common.authorize.auth_actions import ADD_PREFIX, EDIT_PREFIX -from indy_common.authorize.auth_constraints import AuthConstraint +from indy_common.authorize.auth_constraints import AuthConstraint, AuthConstraintForbidden from sovtokenfees.constants import SET_FEES, FEES from sovtokenfees.sovtokenfees_auth_map import sovtokenfees_auth_map, edit_fees +from sovtokenfees.test.auth_map.helper import set_fees -from indy_common.constants import NODE, NYM from plenum.common.constants import STEWARD, TXN_TYPE from plenum.common.exceptions import RequestRejectedException from sovtokenfees.test.constants import NYM_FEES_ALIAS @@ -18,30 +18,6 @@ def addresses(helpers): return helpers.wallet.create_new_addresses(4) -def steward_do_set_fees(helpers, fees): - """ Sends and check a set_fees txn """ - payload = { - TXN_TYPE: SET_FEES, - FEES: fees, - } - - request = helpers.request._create_request(payload, - identifier=helpers.wallet._stewards[0]) - request = helpers.wallet.sign_request_stewards(json.dumps(request.as_dict), number_signers=1) - helpers.sdk.sdk_send_and_check([request]) - - -def set_fees(helpers, fees, trustee=True): - new_fees = dict(fees) - new_fees[NYM_FEES_ALIAS] += 1 - if trustee: - helpers.general.do_set_fees(new_fees) - else: - steward_do_set_fees(helpers, new_fees) - get_fees = helpers.general.do_get_fees() - assert new_fees == get_fees.get("fees") - - def test_set_fees(helpers, fees, addresses, @@ -83,3 +59,43 @@ def test_set_fees(helpers, new_value='*', constraint=sovtokenfees_auth_map[edit_fees.get_action_id()].as_dict) set_fees(helpers, fees) + + +def test_forbid_set_fees(helpers, + fees, + addresses, + looper, + sdk_wallet_trustee, + sdk_pool_handle): + """ + 1. Send a SET_FEES txn. + 2. Forbid SET_FEES via the auth rule for editing SET_FEES. + 3. Send a SET_FEES txn and check that action forbidden. + 4. Change the auth rule to a default value. + 6. Send and check a SET_FEES txn. + """ + set_fees(helpers, fees) + + sdk_send_and_check_auth_rule_request(looper, + sdk_pool_handle, + sdk_wallet_trustee, + auth_action=EDIT_PREFIX, + auth_type=SET_FEES, + field='*', + old_value='*', + new_value='*', + constraint=AuthConstraintForbidden().as_dict) + + with pytest.raises(RequestRejectedException, match=str(AuthConstraintForbidden())): + set_fees(helpers, fees) + + sdk_send_and_check_auth_rule_request(looper, + sdk_pool_handle, + sdk_wallet_trustee, + auth_action=EDIT_PREFIX, + auth_type=SET_FEES, + field='*', + old_value='*', + new_value='*', + constraint=sovtokenfees_auth_map[edit_fees.get_action_id()].as_dict) + set_fees(helpers, fees) diff --git a/sovtokenfees/sovtokenfees/test/auth_map/test_incorrect_auth_rule.py b/sovtokenfees/sovtokenfees/test/auth_map/test_incorrect_auth_rule.py new file mode 100644 index 00000000..d3128e7b --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/auth_map/test_incorrect_auth_rule.py @@ -0,0 +1,20 @@ +from indy_common.authorize.auth_constraints import AuthConstraintForbidden, METADATA +from sovtokenfees.constants import FEES_FIELD_NAME + +from indy_node.test.auth_rule.helper import sdk_send_and_check_auth_rule_invalid_request +from sovtokenfees.test.auth_map.helper import set_fees +from sovtokenfees.test.constants import NYM_FEES_ALIAS + + +def test_incorrect_auth_rule(helpers, + addresses, + looper, + sdk_wallet_trustee, + sdk_pool_handle): + set_fees(helpers, {NYM_FEES_ALIAS: 5}) + constraint = AuthConstraintForbidden().as_dict + constraint[METADATA] = {FEES_FIELD_NAME: NYM_FEES_ALIAS} + sdk_send_and_check_auth_rule_invalid_request(looper, + sdk_pool_handle, + sdk_wallet_trustee, + constraint=constraint) diff --git a/sovtokenfees/sovtokenfees/test/authorize/__init__.py b/sovtokenfees/sovtokenfees/test/authorize/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/test/authorize/metadata/conftest.py b/sovtokenfees/sovtokenfees/test/authorize/metadata/conftest.py index 54286c1e..96e94bb6 100644 --- a/sovtokenfees/sovtokenfees/test/authorize/metadata/conftest.py +++ b/sovtokenfees/sovtokenfees/test/authorize/metadata/conftest.py @@ -4,7 +4,7 @@ import pytest from indy_common.authorize.auth_constraints import IDENTITY_OWNER -from indy_common.constants import TRUST_ANCHOR +from indy_common.constants import ENDORSER from indy_common.test.auth.metadata.helper import PluginAuthorizer from plenum.common.constants import TRUSTEE, STEWARD from plenum.test.conftest import getValueFromModule @@ -14,7 +14,7 @@ constraint_serializer, config_state, write_request_validation -ROLES = [TRUSTEE, STEWARD, TRUST_ANCHOR, IDENTITY_OWNER] +ROLES = [TRUSTEE, STEWARD, ENDORSER, IDENTITY_OWNER] MAX_SIG_COUNT = 3 diff --git a/sovtokenfees/sovtokenfees/test/authorize/metadata/test_auth_rule_with_metadata_complex.py b/sovtokenfees/sovtokenfees/test/authorize/metadata/test_auth_rule_with_metadata_complex.py index c86376c6..fc9cf4ff 100644 --- a/sovtokenfees/sovtokenfees/test/authorize/metadata/test_auth_rule_with_metadata_complex.py +++ b/sovtokenfees/sovtokenfees/test/authorize/metadata/test_auth_rule_with_metadata_complex.py @@ -1,5 +1,5 @@ from indy_common.authorize.auth_constraints import AuthConstraint, IDENTITY_OWNER, AuthConstraintOr, AuthConstraintAnd -from indy_common.constants import TRUST_ANCHOR +from indy_common.constants import ENDORSER from plenum.common.constants import TRUSTEE, STEWARD from sovtokenfees.test.authorize.metadata.helper import validate, PLUGIN_FIELD @@ -57,25 +57,25 @@ def test_plugin_and_or_rule_diff_role(write_auth_req_validator, write_request_va metadata={PLUGIN_FIELD: '3'}), ]), AuthConstraintOr(auth_constraints=[ - AuthConstraint(role=TRUST_ANCHOR, sig_count=3, need_to_be_owner=False, + AuthConstraint(role=ENDORSER, sig_count=3, need_to_be_owner=False, metadata={PLUGIN_FIELD: '3'}), AuthConstraint(role=IDENTITY_OWNER, sig_count=3, need_to_be_owner=True, metadata={PLUGIN_FIELD: '3'}), ]) ]), valid_actions=[ - ({TRUSTEE: 2, TRUST_ANCHOR: 3}, False, 3), - ({TRUSTEE: 3, TRUST_ANCHOR: 3}, False, 3), - ({TRUSTEE: 2, TRUST_ANCHOR: 3}, True, 3), - ({TRUSTEE: 3, TRUST_ANCHOR: 3}, True, 3), + ({TRUSTEE: 2, ENDORSER: 3}, False, 3), + ({TRUSTEE: 3, ENDORSER: 3}, False, 3), + ({TRUSTEE: 2, ENDORSER: 3}, True, 3), + ({TRUSTEE: 3, ENDORSER: 3}, True, 3), ({TRUSTEE: 2, IDENTITY_OWNER: 3}, True, 3), ({TRUSTEE: 3, IDENTITY_OWNER: 3}, True, 3), - ({STEWARD: 2, TRUST_ANCHOR: 3}, False, 3), - ({STEWARD: 3, TRUST_ANCHOR: 3}, False, 3), - ({STEWARD: 2, TRUST_ANCHOR: 3}, True, 3), - ({STEWARD: 3, TRUST_ANCHOR: 3}, True, 3), + ({STEWARD: 2, ENDORSER: 3}, False, 3), + ({STEWARD: 3, ENDORSER: 3}, False, 3), + ({STEWARD: 2, ENDORSER: 3}, True, 3), + ({STEWARD: 3, ENDORSER: 3}, True, 3), ({STEWARD: 2, IDENTITY_OWNER: 3}, True, 3), ({STEWARD: 3, IDENTITY_OWNER: 3}, True, 3), @@ -100,7 +100,7 @@ def test_plugin_or_and_rule_diff_roles(write_auth_req_validator, write_request_v AuthConstraintAnd(auth_constraints=[ AuthConstraint(role=TRUSTEE, sig_count=1, need_to_be_owner=False, metadata={PLUGIN_FIELD: '2'}), - AuthConstraint(role=TRUST_ANCHOR, sig_count=2, need_to_be_owner=True, + AuthConstraint(role=ENDORSER, sig_count=2, need_to_be_owner=True, metadata={PLUGIN_FIELD: '2'}), ]), AuthConstraintAnd(auth_constraints=[ @@ -141,11 +141,11 @@ def test_plugin_or_and_rule_diff_roles(write_auth_req_validator, write_request_v ({TRUSTEE: 2, STEWARD: 3}, True, 0), ({TRUSTEE: 3, STEWARD: 3}, True, 0), - ({TRUSTEE: 1, TRUST_ANCHOR: 2}, True, 2), - ({TRUSTEE: 2, TRUST_ANCHOR: 2}, True, 2), - ({TRUSTEE: 1, TRUST_ANCHOR: 3}, True, 2), - ({TRUSTEE: 2, TRUST_ANCHOR: 3}, True, 2), - ({TRUSTEE: 3, TRUST_ANCHOR: 3}, True, 2), + ({TRUSTEE: 1, ENDORSER: 2}, True, 2), + ({TRUSTEE: 2, ENDORSER: 2}, True, 2), + ({TRUSTEE: 1, ENDORSER: 3}, True, 2), + ({TRUSTEE: 2, ENDORSER: 3}, True, 2), + ({TRUSTEE: 3, ENDORSER: 3}, True, 2), ({TRUSTEE: 2, IDENTITY_OWNER: 3}, True, 3), ({TRUSTEE: 3, IDENTITY_OWNER: 3}, True, 3), @@ -168,12 +168,12 @@ def test_plugin_complex(write_auth_req_validator, write_request_validation, AuthConstraintAnd(auth_constraints=[ AuthConstraint(role=STEWARD, sig_count=1, need_to_be_owner=False, metadata={PLUGIN_FIELD: '1'}), - AuthConstraint(role=TRUST_ANCHOR, sig_count=1, need_to_be_owner=False, + AuthConstraint(role=ENDORSER, sig_count=1, need_to_be_owner=False, metadata={PLUGIN_FIELD: '1'}), ]), AuthConstraint(role=STEWARD, sig_count=2, need_to_be_owner=False, metadata={PLUGIN_FIELD: '1'}), - AuthConstraint(role=TRUST_ANCHOR, sig_count=2, need_to_be_owner=False, + AuthConstraint(role=ENDORSER, sig_count=2, need_to_be_owner=False, metadata={PLUGIN_FIELD: '1'}), ]) ]), @@ -189,7 +189,7 @@ def test_plugin_complex(write_auth_req_validator, write_request_validation, AuthConstraint(role=TRUSTEE, sig_count=3, need_to_be_owner=False), AuthConstraintOr(auth_constraints=[ AuthConstraint(role=STEWARD, sig_count=2, need_to_be_owner=False), - AuthConstraint(role=TRUST_ANCHOR, sig_count=2, need_to_be_owner=True), + AuthConstraint(role=ENDORSER, sig_count=2, need_to_be_owner=True), ]) ]), # 4th @@ -202,31 +202,31 @@ def test_plugin_complex(write_auth_req_validator, write_request_validation, ]), valid_actions=[ # 1st - ({TRUSTEE: 3, STEWARD: 1, TRUST_ANCHOR: 1}, False, 1), - ({TRUSTEE: 3, STEWARD: 2, TRUST_ANCHOR: 1}, False, 1), - ({TRUSTEE: 3, STEWARD: 1, TRUST_ANCHOR: 2}, False, 1), - ({TRUSTEE: 3, STEWARD: 2, TRUST_ANCHOR: 2}, False, 1), - ({TRUSTEE: 3, STEWARD: 1, TRUST_ANCHOR: 3}, False, 1), - ({TRUSTEE: 3, STEWARD: 3, TRUST_ANCHOR: 1}, False, 1), - ({TRUSTEE: 3, STEWARD: 3, TRUST_ANCHOR: 3}, False, 1), - - ({TRUSTEE: 3, STEWARD: 1, TRUST_ANCHOR: 1}, True, 1), - ({TRUSTEE: 3, STEWARD: 2, TRUST_ANCHOR: 1}, True, 1), - ({TRUSTEE: 3, STEWARD: 1, TRUST_ANCHOR: 2}, True, 1), - ({TRUSTEE: 3, STEWARD: 2, TRUST_ANCHOR: 2}, True, 1), - ({TRUSTEE: 3, STEWARD: 1, TRUST_ANCHOR: 3}, True, 1), - ({TRUSTEE: 3, STEWARD: 3, TRUST_ANCHOR: 1}, True, 1), - ({TRUSTEE: 3, STEWARD: 3, TRUST_ANCHOR: 3}, True, 1), + ({TRUSTEE: 3, STEWARD: 1, ENDORSER: 1}, False, 1), + ({TRUSTEE: 3, STEWARD: 2, ENDORSER: 1}, False, 1), + ({TRUSTEE: 3, STEWARD: 1, ENDORSER: 2}, False, 1), + ({TRUSTEE: 3, STEWARD: 2, ENDORSER: 2}, False, 1), + ({TRUSTEE: 3, STEWARD: 1, ENDORSER: 3}, False, 1), + ({TRUSTEE: 3, STEWARD: 3, ENDORSER: 1}, False, 1), + ({TRUSTEE: 3, STEWARD: 3, ENDORSER: 3}, False, 1), + + ({TRUSTEE: 3, STEWARD: 1, ENDORSER: 1}, True, 1), + ({TRUSTEE: 3, STEWARD: 2, ENDORSER: 1}, True, 1), + ({TRUSTEE: 3, STEWARD: 1, ENDORSER: 2}, True, 1), + ({TRUSTEE: 3, STEWARD: 2, ENDORSER: 2}, True, 1), + ({TRUSTEE: 3, STEWARD: 1, ENDORSER: 3}, True, 1), + ({TRUSTEE: 3, STEWARD: 3, ENDORSER: 1}, True, 1), + ({TRUSTEE: 3, STEWARD: 3, ENDORSER: 3}, True, 1), ({TRUSTEE: 3, STEWARD: 2}, False, 1), ({TRUSTEE: 3, STEWARD: 3}, False, 1), ({TRUSTEE: 3, STEWARD: 2}, True, 1), ({TRUSTEE: 3, STEWARD: 3}, True, 1), - ({TRUSTEE: 3, TRUST_ANCHOR: 2}, False, 1), - ({TRUSTEE: 3, TRUST_ANCHOR: 3}, False, 1), - ({TRUSTEE: 3, TRUST_ANCHOR: 2}, True, 1), - ({TRUSTEE: 3, TRUST_ANCHOR: 3}, True, 1), + ({TRUSTEE: 3, ENDORSER: 2}, False, 1), + ({TRUSTEE: 3, ENDORSER: 3}, False, 1), + ({TRUSTEE: 3, ENDORSER: 2}, True, 1), + ({TRUSTEE: 3, ENDORSER: 3}, True, 1), # 2d ({TRUSTEE: 3, IDENTITY_OWNER: 1}, True, 3), @@ -244,15 +244,15 @@ def test_plugin_complex(write_auth_req_validator, write_request_validation, ({TRUSTEE: 3, STEWARD: 2}, True, 0), ({TRUSTEE: 3, STEWARD: 3}, True, 0), - ({TRUSTEE: 3, TRUST_ANCHOR: 2}, False, None), - ({TRUSTEE: 3, TRUST_ANCHOR: 3}, False, None), - ({TRUSTEE: 3, TRUST_ANCHOR: 2}, True, None), - ({TRUSTEE: 3, TRUST_ANCHOR: 3}, True, None), + ({TRUSTEE: 3, ENDORSER: 2}, False, None), + ({TRUSTEE: 3, ENDORSER: 3}, False, None), + ({TRUSTEE: 3, ENDORSER: 2}, True, None), + ({TRUSTEE: 3, ENDORSER: 3}, True, None), - ({TRUSTEE: 3, TRUST_ANCHOR: 2}, False, 0), - ({TRUSTEE: 3, TRUST_ANCHOR: 3}, False, 0), - ({TRUSTEE: 3, TRUST_ANCHOR: 2}, True, 0), - ({TRUSTEE: 3, TRUST_ANCHOR: 3}, True, 0), + ({TRUSTEE: 3, ENDORSER: 2}, False, 0), + ({TRUSTEE: 3, ENDORSER: 3}, False, 0), + ({TRUSTEE: 3, ENDORSER: 2}, True, 0), + ({TRUSTEE: 3, ENDORSER: 3}, True, 0), # 4th ({TRUSTEE: 2, IDENTITY_OWNER: 1}, True, 2), diff --git a/sovtokenfees/sovtokenfees/test/authorize/metadata/test_auth_rule_with_metadata_composite.py b/sovtokenfees/sovtokenfees/test/authorize/metadata/test_auth_rule_with_metadata_composite.py index 9ebdeaae..0c520454 100644 --- a/sovtokenfees/sovtokenfees/test/authorize/metadata/test_auth_rule_with_metadata_composite.py +++ b/sovtokenfees/sovtokenfees/test/authorize/metadata/test_auth_rule_with_metadata_composite.py @@ -1,5 +1,5 @@ from indy_common.authorize.auth_constraints import AuthConstraint, IDENTITY_OWNER, AuthConstraintOr, AuthConstraintAnd -from indy_common.constants import TRUST_ANCHOR +from indy_common.constants import ENDORSER from plenum.common.constants import TRUSTEE, STEWARD from sovtokenfees.test.authorize.metadata.helper import validate, PLUGIN_FIELD @@ -14,7 +14,7 @@ def test_plugin_or_rule_all_amount(write_auth_req_validator, write_request_valid metadata={PLUGIN_FIELD: '1'}), AuthConstraint(role=STEWARD, sig_count=1, need_to_be_owner=False, metadata={PLUGIN_FIELD: '2'}), - AuthConstraint(role=TRUST_ANCHOR, sig_count=1, need_to_be_owner=True, + AuthConstraint(role=ENDORSER, sig_count=1, need_to_be_owner=True, metadata={PLUGIN_FIELD: '3'}), ]), valid_actions=[ @@ -32,9 +32,9 @@ def test_plugin_or_rule_all_amount(write_auth_req_validator, write_request_valid ({STEWARD: 2}, True, 2), ({STEWARD: 3}, True, 2), - ({TRUST_ANCHOR: 1}, True, 3), - ({TRUST_ANCHOR: 2}, True, 3), - ({TRUST_ANCHOR: 3}, True, 3), + ({ENDORSER: 1}, True, 3), + ({ENDORSER: 2}, True, 3), + ({ENDORSER: 3}, True, 3), ], all_signatures=signatures, is_owner=is_owner, amount=amount, write_auth_req_validator=write_auth_req_validator, @@ -46,34 +46,34 @@ def test_plugin_or_rule_one_amount_same_role(write_auth_req_validator, write_req signatures, is_owner, amount): validate( auth_constraint=AuthConstraintOr(auth_constraints=[ - AuthConstraint(role=TRUST_ANCHOR, sig_count=1, need_to_be_owner=False), - AuthConstraint(role=TRUST_ANCHOR, sig_count=1, need_to_be_owner=False, + AuthConstraint(role=ENDORSER, sig_count=1, need_to_be_owner=False), + AuthConstraint(role=ENDORSER, sig_count=1, need_to_be_owner=False, metadata={PLUGIN_FIELD: '2'}), ]), valid_actions=[ - ({TRUST_ANCHOR: 1}, True, 2), - ({TRUST_ANCHOR: 2}, True, 2), - ({TRUST_ANCHOR: 3}, True, 2), + ({ENDORSER: 1}, True, 2), + ({ENDORSER: 2}, True, 2), + ({ENDORSER: 3}, True, 2), - ({TRUST_ANCHOR: 1}, False, 2), - ({TRUST_ANCHOR: 2}, False, 2), - ({TRUST_ANCHOR: 3}, False, 2), + ({ENDORSER: 1}, False, 2), + ({ENDORSER: 2}, False, 2), + ({ENDORSER: 3}, False, 2), - ({TRUST_ANCHOR: 1}, True, None), - ({TRUST_ANCHOR: 2}, True, None), - ({TRUST_ANCHOR: 3}, True, None), + ({ENDORSER: 1}, True, None), + ({ENDORSER: 2}, True, None), + ({ENDORSER: 3}, True, None), - ({TRUST_ANCHOR: 1}, False, None), - ({TRUST_ANCHOR: 2}, False, None), - ({TRUST_ANCHOR: 3}, False, None), + ({ENDORSER: 1}, False, None), + ({ENDORSER: 2}, False, None), + ({ENDORSER: 3}, False, None), - ({TRUST_ANCHOR: 1}, True, 0), - ({TRUST_ANCHOR: 2}, True, 0), - ({TRUST_ANCHOR: 3}, True, 0), + ({ENDORSER: 1}, True, 0), + ({ENDORSER: 2}, True, 0), + ({ENDORSER: 3}, True, 0), - ({TRUST_ANCHOR: 1}, False, 0), - ({TRUST_ANCHOR: 2}, False, 0), - ({TRUST_ANCHOR: 3}, False, 0), + ({ENDORSER: 1}, False, 0), + ({ENDORSER: 2}, False, 0), + ({ENDORSER: 3}, False, 0), ], all_signatures=[{'101': 1}], is_owner=False, amount=0, write_auth_req_validator=write_auth_req_validator, @@ -85,26 +85,26 @@ def test_plugin_or_rule_one_amount_diff_roles(write_auth_req_validator, write_re signatures, is_owner, amount): validate( auth_constraint=AuthConstraintOr(auth_constraints=[ - AuthConstraint(role=TRUST_ANCHOR, sig_count=1, need_to_be_owner=False), + AuthConstraint(role=ENDORSER, sig_count=1, need_to_be_owner=False), AuthConstraint(role=IDENTITY_OWNER, sig_count=1, need_to_be_owner=True, metadata={PLUGIN_FIELD: '1'}), ]), valid_actions=[ - ({TRUST_ANCHOR: 1}, False, None), - ({TRUST_ANCHOR: 2}, False, None), - ({TRUST_ANCHOR: 3}, False, None), + ({ENDORSER: 1}, False, None), + ({ENDORSER: 2}, False, None), + ({ENDORSER: 3}, False, None), - ({TRUST_ANCHOR: 1}, True, None), - ({TRUST_ANCHOR: 2}, True, None), - ({TRUST_ANCHOR: 3}, True, None), + ({ENDORSER: 1}, True, None), + ({ENDORSER: 2}, True, None), + ({ENDORSER: 3}, True, None), - ({TRUST_ANCHOR: 1}, False, 0), - ({TRUST_ANCHOR: 2}, False, 0), - ({TRUST_ANCHOR: 3}, False, 0), + ({ENDORSER: 1}, False, 0), + ({ENDORSER: 2}, False, 0), + ({ENDORSER: 3}, False, 0), - ({TRUST_ANCHOR: 1}, True, 0), - ({TRUST_ANCHOR: 2}, True, 0), - ({TRUST_ANCHOR: 3}, True, 0), + ({ENDORSER: 1}, True, 0), + ({ENDORSER: 2}, True, 0), + ({ENDORSER: 3}, True, 0), ({IDENTITY_OWNER: 1}, True, 1), ({IDENTITY_OWNER: 2}, True, 1), @@ -120,23 +120,23 @@ def test_plugin_or_rule_one_amount_all_roles(write_auth_req_validator, write_req signatures, is_owner, amount): validate( auth_constraint=AuthConstraintOr(auth_constraints=[ - AuthConstraint(role=TRUST_ANCHOR, sig_count=1, need_to_be_owner=False), + AuthConstraint(role=ENDORSER, sig_count=1, need_to_be_owner=False), AuthConstraint(role='*', sig_count=1, need_to_be_owner=True, metadata={PLUGIN_FIELD: '3'}), ]), - valid_actions=[({TRUST_ANCHOR: 1}, False, None), - ({TRUST_ANCHOR: 2}, False, None), - ({TRUST_ANCHOR: 3}, False, None), - ({TRUST_ANCHOR: 1}, True, None), - ({TRUST_ANCHOR: 2}, True, None), - ({TRUST_ANCHOR: 3}, True, None), - - ({TRUST_ANCHOR: 1}, False, 0), - ({TRUST_ANCHOR: 2}, False, 0), - ({TRUST_ANCHOR: 3}, False, 0), - ({TRUST_ANCHOR: 1}, True, 0), - ({TRUST_ANCHOR: 2}, True, 0), - ({TRUST_ANCHOR: 3}, True, 0), + valid_actions=[({ENDORSER: 1}, False, None), + ({ENDORSER: 2}, False, None), + ({ENDORSER: 3}, False, None), + ({ENDORSER: 1}, True, None), + ({ENDORSER: 2}, True, None), + ({ENDORSER: 3}, True, None), + + ({ENDORSER: 1}, False, 0), + ({ENDORSER: 2}, False, 0), + ({ENDORSER: 3}, False, 0), + ({ENDORSER: 1}, True, 0), + ({ENDORSER: 2}, True, 0), + ({ENDORSER: 3}, True, 0), ] + [(signature, True, 3) for signature in signatures if signature], all_signatures=signatures, is_owner=is_owner, amount=amount, @@ -149,17 +149,17 @@ def test_plugin_or_rule_diff_amount_same_role(write_auth_req_validator, write_re signatures, is_owner, amount): validate( auth_constraint=AuthConstraintOr(auth_constraints=[ - AuthConstraint(role=TRUST_ANCHOR, sig_count=2, need_to_be_owner=False, + AuthConstraint(role=ENDORSER, sig_count=2, need_to_be_owner=False, metadata={PLUGIN_FIELD: '2'}), - AuthConstraint(role=TRUST_ANCHOR, sig_count=3, need_to_be_owner=False, + AuthConstraint(role=ENDORSER, sig_count=3, need_to_be_owner=False, metadata={PLUGIN_FIELD: '1'}), ]), valid_actions=[ - ({TRUST_ANCHOR: 2}, True, 2), - ({TRUST_ANCHOR: 3}, True, 1), + ({ENDORSER: 2}, True, 2), + ({ENDORSER: 3}, True, 1), - ({TRUST_ANCHOR: 2}, False, 2), - ({TRUST_ANCHOR: 3}, False, 1), + ({ENDORSER: 2}, False, 2), + ({ENDORSER: 3}, False, 1), ], all_signatures=signatures, is_owner=is_owner, amount=amount, write_auth_req_validator=write_auth_req_validator, diff --git a/sovtokenfees/sovtokenfees/test/authorize/metadata/test_auth_rule_with_metadata_simple.py b/sovtokenfees/sovtokenfees/test/authorize/metadata/test_auth_rule_with_metadata_simple.py index e84026fd..995b0ae9 100644 --- a/sovtokenfees/sovtokenfees/test/authorize/metadata/test_auth_rule_with_metadata_simple.py +++ b/sovtokenfees/sovtokenfees/test/authorize/metadata/test_auth_rule_with_metadata_simple.py @@ -1,5 +1,5 @@ from indy_common.authorize.auth_constraints import AuthConstraint, IDENTITY_OWNER, AuthConstraintOr, AuthConstraintAnd -from indy_common.constants import TRUST_ANCHOR +from indy_common.constants import ENDORSER from plenum.common.constants import TRUSTEE, STEWARD from sovtokenfees.test.authorize.metadata.helper import validate, PLUGIN_FIELD diff --git a/sovtokenfees/sovtokenfees/test/authorize/test_fees_authorizer.py b/sovtokenfees/sovtokenfees/test/authorize/test_fees_authorizer.py index 2d2e7847..832082b1 100644 --- a/sovtokenfees/sovtokenfees/test/authorize/test_fees_authorizer.py +++ b/sovtokenfees/sovtokenfees/test/authorize/test_fees_authorizer.py @@ -53,7 +53,7 @@ def fees_constraint(): @pytest.fixture() def fees_authorizer(fees): authorizer = FeesAuthorizer(config_state=PruningState(KeyValueStorageInMemory()), - utxo_cache=UTXOCache(KeyValueStorageInMemory())) + utxo_cache=UTXOCache(KeyValueStorageInMemory())) authorizer.calculate_fees_from_req=lambda *args, **kwargs: fees.get(NYM_FEES_ALIAS) return authorizer diff --git a/sovtokenfees/sovtokenfees/test/catchup/__init__.py b/sovtokenfees/sovtokenfees/test/catchup/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/test/catchup/conftest.py b/sovtokenfees/sovtokenfees/test/catchup/conftest.py index 4bb2ca4c..ca771b93 100644 --- a/sovtokenfees/sovtokenfees/test/catchup/conftest.py +++ b/sovtokenfees/sovtokenfees/test/catchup/conftest.py @@ -1,6 +1,12 @@ import pytest + +from plenum.test.conftest import getValueFromModule + from sovtoken.constants import ADDRESS, AMOUNT +from sovtokenfees.test.helper import InputsStrategy +from sovtokenfees.test.conftest import MintStrategy + @pytest.fixture() def xfer_addresses(helpers, libsovtoken): @@ -10,4 +16,19 @@ def xfer_addresses(helpers, libsovtoken): @pytest.fixture() def xfer_mint_tokens(helpers, xfer_addresses): outputs = [{ADDRESS: xfer_addresses[0], AMOUNT: 1000}] - return helpers.general.do_mint(outputs) \ No newline at end of file + return helpers.general.do_mint(outputs) + + +@pytest.fixture +def addresses_num(request): + return getValueFromModule(request, "ADDRESSES_NUM", 4) + + +@pytest.fixture +def mint_strategy(request): + return getValueFromModule(request, "MINT_STRATEGY", MintStrategy.all_equal) + + +@pytest.fixture +def inputs_strategy(request): + return getValueFromModule(request, "INPUTS_STRATEGY", InputsStrategy.first_utxo_only) diff --git a/sovtokenfees/sovtokenfees/test/catchup/test_tracker_after_catchup.py b/sovtokenfees/sovtokenfees/test/catchup/test_tracker_after_catchup.py index f17201cb..ba469340 100644 --- a/sovtokenfees/sovtokenfees/test/catchup/test_tracker_after_catchup.py +++ b/sovtokenfees/sovtokenfees/test/catchup/test_tracker_after_catchup.py @@ -2,6 +2,7 @@ import pytest from sovtoken import TOKEN_LEDGER_ID +from sovtoken.constants import XFER_PUBLIC from sovtokenfees.test.helper import get_amount_from_token_txn, nyms_with_fees from plenum.test.stasher import delay_rules @@ -17,7 +18,7 @@ def get_last_committed_from_tracker(node): - tracker = node.ledger_to_req_handler.get(TOKEN_LEDGER_ID).tracker + tracker = node.read_manager.request_handlers[XFER_PUBLIC][0].tracker return tracker.last_committed diff --git a/sovtokenfees/sovtokenfees/test/catchup/test_xfer_fees_nym_during_catchup.py b/sovtokenfees/sovtokenfees/test/catchup/test_xfer_fees_nym_during_catchup.py index 9933594b..0a9fa473 100644 --- a/sovtokenfees/sovtokenfees/test/catchup/test_xfer_fees_nym_during_catchup.py +++ b/sovtokenfees/sovtokenfees/test/catchup/test_xfer_fees_nym_during_catchup.py @@ -1,10 +1,9 @@ import pytest +from sovtokenfees.test.constants import ( + NYM_FEES_ALIAS, XFER_PUBLIC_FEES_ALIAS, alias_to_txn_type +) from sovtokenfees.test.catchup.helper import scenario_txns_during_catchup -from sovtokenfees.test.constants import NYM_FEES_ALIAS, XFER_PUBLIC_FEES_ALIAS - -ADDRESSES_NUM = 2 -MINT_UTXOS_NUM = 1 @pytest.fixture( @@ -14,7 +13,7 @@ {NYM_FEES_ALIAS: 4, XFER_PUBLIC_FEES_ALIAS: 8}, # fees for both {NYM_FEES_ALIAS: 0, XFER_PUBLIC_FEES_ALIAS: 0}, # no fees {NYM_FEES_ALIAS: 4, XFER_PUBLIC_FEES_ALIAS: 0}, # no fees for XFER_PUBLIC - ], ids=lambda x: '-'.join(sorted([k for k, v in x.items() if v])) or 'nofees' + ], ids=lambda x: '-'.join(sorted([alias_to_txn_type[k] for k, v in x.items() if v])) or 'nofees' ) def fees(request): return request.param @@ -25,12 +24,13 @@ def test_xfer_fees_nym_during_catchup( do_post_node_creation, nodeSetWithIntegratedTokenPlugin, fees_set, - send_and_check_transfer_curr_utxo, - send_and_check_nym_with_fees_curr_utxo, + mint_multiple_tokens, + send_and_check_xfer, + send_and_check_nym, ): def send_txns(): - send_and_check_transfer_curr_utxo() - send_and_check_nym_with_fees_curr_utxo() + send_and_check_xfer() + send_and_check_nym() scenario_txns_during_catchup( looper, tconf, tdir, allPluginsPath, do_post_node_creation, diff --git a/sovtokenfees/sovtokenfees/test/catchup/test_xfer_fees_nym_fees_during_catchup.py b/sovtokenfees/sovtokenfees/test/catchup/test_xfer_fees_nym_fees_during_catchup.py index 00e8cc5c..25b539da 100644 --- a/sovtokenfees/sovtokenfees/test/catchup/test_xfer_fees_nym_fees_during_catchup.py +++ b/sovtokenfees/sovtokenfees/test/catchup/test_xfer_fees_nym_fees_during_catchup.py @@ -1,10 +1,9 @@ import pytest +from sovtokenfees.test.constants import ( + NYM_FEES_ALIAS, XFER_PUBLIC_FEES_ALIAS, alias_to_txn_type +) from sovtokenfees.test.catchup.helper import scenario_txns_during_catchup -from sovtokenfees.test.constants import NYM_FEES_ALIAS, XFER_PUBLIC_FEES_ALIAS - -ADDRESSES_NUM = 2 -MINT_UTXOS_NUM = 1 @pytest.fixture( @@ -14,7 +13,7 @@ {NYM_FEES_ALIAS: 0, XFER_PUBLIC_FEES_ALIAS: 0}, # no fees {NYM_FEES_ALIAS: 4, XFER_PUBLIC_FEES_ALIAS: 0}, # no fees for XFER_PUBLIC {NYM_FEES_ALIAS: 0, XFER_PUBLIC_FEES_ALIAS: 8}, # no fees for NYM - ], ids=lambda x: '-'.join(sorted([k for k, v in x.items() if v])) or 'nofees' + ], ids=lambda x: '-'.join(sorted([alias_to_txn_type[k] for k, v in x.items() if v])) or 'nofees' ) def fees(request): return request.param @@ -25,12 +24,13 @@ def test_xfer_fees_nym_fees_during_catchup( do_post_node_creation, nodeSetWithIntegratedTokenPlugin, fees_set, - send_and_check_transfer_curr_utxo, - send_and_check_nym_with_fees_curr_utxo, + mint_multiple_tokens, + send_and_check_xfer, + send_and_check_nym, ): def send_txns(): - send_and_check_transfer_curr_utxo() - send_and_check_nym_with_fees_curr_utxo() + send_and_check_xfer() + send_and_check_nym() scenario_txns_during_catchup( looper, tconf, tdir, allPluginsPath, do_post_node_creation, diff --git a/sovtokenfees/sovtokenfees/test/catchup/test_xfer_nym_during_catchup.py b/sovtokenfees/sovtokenfees/test/catchup/test_xfer_nym_during_catchup.py index 17a80c61..01c1841c 100644 --- a/sovtokenfees/sovtokenfees/test/catchup/test_xfer_nym_during_catchup.py +++ b/sovtokenfees/sovtokenfees/test/catchup/test_xfer_nym_during_catchup.py @@ -1,10 +1,9 @@ import pytest +from sovtokenfees.test.constants import ( + NYM_FEES_ALIAS, XFER_PUBLIC_FEES_ALIAS, alias_to_txn_type +) from sovtokenfees.test.catchup.helper import scenario_txns_during_catchup -from sovtokenfees.test.constants import NYM_FEES_ALIAS, XFER_PUBLIC_FEES_ALIAS - -ADDRESSES_NUM = 2 -MINT_UTXOS_NUM = 1 @pytest.fixture( @@ -14,7 +13,7 @@ {NYM_FEES_ALIAS: 4, XFER_PUBLIC_FEES_ALIAS: 0}, # no fees for XFER_PUBLIC {NYM_FEES_ALIAS: 0, XFER_PUBLIC_FEES_ALIAS: 8}, # no fees for NYM {NYM_FEES_ALIAS: 4, XFER_PUBLIC_FEES_ALIAS: 8}, # fees for both - ], ids=lambda x: '-'.join(sorted([k for k, v in x.items() if v])) or 'nofees' + ], ids=lambda x: '-'.join(sorted([alias_to_txn_type[k] for k, v in x.items() if v])) or 'nofees' ) def fees(request): return request.param @@ -25,12 +24,13 @@ def test_xfer_nym_during_catchup( do_post_node_creation, nodeSetWithIntegratedTokenPlugin, fees_set, - send_and_check_transfer_curr_utxo, - send_and_check_nym_with_fees_curr_utxo, + mint_multiple_tokens, + send_and_check_xfer, + send_and_check_nym, ): def send_txns(): - send_and_check_transfer_curr_utxo() - send_and_check_nym_with_fees_curr_utxo() + send_and_check_xfer() + send_and_check_nym() scenario_txns_during_catchup( looper, tconf, tdir, allPluginsPath, do_post_node_creation, diff --git a/sovtokenfees/sovtokenfees/test/catchup/test_xfer_nym_fees_during_catchup.py b/sovtokenfees/sovtokenfees/test/catchup/test_xfer_nym_fees_during_catchup.py index 60dbf5f4..013c3f3c 100644 --- a/sovtokenfees/sovtokenfees/test/catchup/test_xfer_nym_fees_during_catchup.py +++ b/sovtokenfees/sovtokenfees/test/catchup/test_xfer_nym_fees_during_catchup.py @@ -1,7 +1,9 @@ import pytest +from sovtokenfees.test.constants import ( + XFER_PUBLIC_FEES_ALIAS, NYM_FEES_ALIAS, alias_to_txn_type +) from sovtokenfees.test.catchup.helper import scenario_txns_during_catchup -from sovtokenfees.test.constants import XFER_PUBLIC_FEES_ALIAS, NYM_FEES_ALIAS ADDRESSES_NUM = 2 MINT_UTXOS_NUM = 1 @@ -14,7 +16,7 @@ {NYM_FEES_ALIAS: 0, XFER_PUBLIC_FEES_ALIAS: 8}, # no fees for NYM {NYM_FEES_ALIAS: 4, XFER_PUBLIC_FEES_ALIAS: 8}, # fees for both {NYM_FEES_ALIAS: 0, XFER_PUBLIC_FEES_ALIAS: 0}, # no fees - ], ids=lambda x: '-'.join(sorted([k for k, v in x.items() if v])) or 'nofees' + ], ids=lambda x: '-'.join(sorted([alias_to_txn_type[k] for k, v in x.items() if v])) or 'nofees' ) def fees(request): return request.param @@ -25,12 +27,13 @@ def test_xfer_nym_fees_during_catchup( do_post_node_creation, nodeSetWithIntegratedTokenPlugin, fees_set, - send_and_check_transfer_curr_utxo, - send_and_check_nym_with_fees_curr_utxo, + mint_multiple_tokens, + send_and_check_xfer, + send_and_check_nym, ): def send_txns(): - send_and_check_transfer_curr_utxo() - send_and_check_nym_with_fees_curr_utxo() + send_and_check_xfer() + send_and_check_nym() scenario_txns_during_catchup( looper, tconf, tdir, allPluginsPath, do_post_node_creation, diff --git a/sovtokenfees/sovtokenfees/test/conftest.py b/sovtokenfees/sovtokenfees/test/conftest.py index 985cfa90..75c07c2f 100644 --- a/sovtokenfees/sovtokenfees/test/conftest.py +++ b/sovtokenfees/sovtokenfees/test/conftest.py @@ -1,3 +1,5 @@ +from abc import ABCMeta, abstractmethod + from sovtoken.test.conftest import build_wallets_from_data from plenum.common.constants import NYM, STEWARD, TARGET_NYM, TRUSTEE_STRING @@ -15,7 +17,6 @@ # fixtures, do not remove from indy_node.test.conftest import * -from indy_common.constants import NYM from sovtoken.constants import ( XFER_PUBLIC, RESULT, ADDRESS, AMOUNT, SEQNO, OUTPUTS @@ -24,8 +25,11 @@ from sovtokenfees.main import integrate_plugin_in_node as enable_fees +from sovtokenfees.constants import MAX_FEE_OUTPUTS from sovtokenfees import CLIENT_REQUEST_FIELDS -from sovtokenfees.test.constants import NYM_FEES_ALIAS, XFER_PUBLIC_FEES_ALIAS +from sovtokenfees.test.constants import ( + NYM_FEES_ALIAS, XFER_PUBLIC_FEES_ALIAS, txn_type_to_alias +) from sovtokenfees.test.helper import ( get_amount_from_token_txn, send_and_check_nym_with_fees, send_and_check_transfer, @@ -40,10 +44,50 @@ from plenum.test.conftest import get_data_for_role +from stp_core.common.log import Logger +Logger().enableStdLogging() + + @unique class MintStrategy(Enum): - single_first = 1 # mint only for the first address - multiple_equal = 2 # mint equal values for all addresses + first_only = 1 # mint only for the first address + all_equal = 2 # mint equal values for all addresses + + +class IOAddresses(metaclass=ABCMeta): + @abstractmethod + def __call__(self, *args, **kwargs): + pass + + +class IOAddressesDynamic(IOAddresses): + def __init__(self, helpers, addresses): + self._addresses = [helpers.wallet.address_map[addr] for addr in addresses] + + def __call__(self, txn_type=None): + with_utxos = [addr.address for addr in self._addresses if addr.total_amount] + no_utxos = [addr.address for addr in self._addresses if not addr.total_amount] + assert with_utxos + + if txn_type == XFER_PUBLIC: + # try to make them near equal + border = len(self._addresses) // 2 or 1 + return (with_utxos[:border], with_utxos[border:] + no_utxos) + else: + return (with_utxos, with_utxos[:MAX_FEE_OUTPUTS]) + + +class IOAddressesStatic(IOAddresses): + def __init__(self, i_addrs=None, o_addrs=None): + self._iaddrs = i_addrs + self._oaddrs = o_addrs + + def rotate(self): + self._iaddrs, self._oaddrs = self._oaddrs, self._iaddrs + + def __call__(self, *args, **kwargs): + return (self._iaddrs, self._oaddrs) + # ###################### # configuration fixtures @@ -67,7 +111,7 @@ def addresses_num(request): @pytest.fixture def mint_strategy(request): - return getValueFromModule(request, "MINT_STRATEGY", MintStrategy.single_first) + return getValueFromModule(request, "MINT_STRATEGY", MintStrategy.first_only) @pytest.fixture @@ -87,7 +131,7 @@ def inputs_strategy(request): @pytest.fixture def outputs_strategy(request): - return getValueFromModule(request, "OUTPUTS_STRATEGY", OutputsStrategy.transfer_equal) + return getValueFromModule(request, "OUTPUTS_STRATEGY", OutputsStrategy.transfer_some_equal) # makes sense not for all outputs strategies @@ -235,9 +279,9 @@ def addresses(helpers, addresses_num): def mint_amount_spec(request, addresses, mint_strategy, mint_amount): amounts = {addr: 0 for addr in addresses} - if mint_strategy == MintStrategy.single_first: + if mint_strategy == MintStrategy.first_only: amounts[addresses[0]] = mint_amount - elif mint_strategy == MintStrategy.multiple_equal: + elif mint_strategy == MintStrategy.all_equal: amounts = {addr: mint_amount for addr in addresses} else: raise ValueError("unexpected mint strategy: {}".format(mint_strategy)) @@ -278,44 +322,36 @@ def mint_multiple_tokens(helpers, addresses, mint_utxos_num_spec, mint_amount_sp @pytest.fixture def io_addresses(helpers, addresses): - _addresses = [helpers.wallet.address_map[addr.replace("pay:sov:", "")] for addr in addresses] - - def wrapped(): - with_utxos = [addr.address for addr in _addresses if addr.total_amount] - no_utxos = [addr.address for addr in _addresses if not addr.total_amount] - assert with_utxos - - # try to make them near equal - border = len(_addresses) // 2 or 1 - - return (with_utxos[:border], with_utxos[border:] + no_utxos) - - return wrapped + return IOAddressesDynamic(helpers, addresses) @pytest.fixture def prepare_inputs(helpers, inputs_strategy, io_addresses): _inputs_strategy = inputs_strategy - def wrapped(addresses=None, inputs_strategy=None): - addresses = io_addresses()[0] if addresses is None else addresses + def wrapped(addresses=None, inputs_strategy=None, txn_type=None): + addresses = io_addresses(txn_type)[0] if addresses is None else addresses inputs_strategy = ( _inputs_strategy if inputs_strategy is None else inputs_strategy ) - return prepare_inputs_h(helpers, addresses, strategy=inputs_strategy) return wrapped @pytest.fixture -def prepare_outputs(helpers, outputs_strategy, transfer_amount, io_addresses, prepare_inputs): +def prepare_outputs( + helpers, outputs_strategy, transfer_amount, + io_addresses, prepare_inputs, fees +): _outputs_strategy = outputs_strategy - def wrapped(fee, inputs=None, addresses=None, outputs_strategy=None): + def wrapped(fee=None, txn_type=None, inputs=None, addresses=None, outputs_strategy=None): + if txn_type is not None: + fee = fees.get(txn_type_to_alias[txn_type], 0) - inputs = prepare_inputs() if inputs is None else inputs - addresses = io_addresses()[1] if addresses is None else addresses + inputs = prepare_inputs(txn_type=txn_type) if inputs is None else inputs + addresses = io_addresses(txn_type)[1] if addresses is None else addresses outputs_strategy = ( _outputs_strategy if outputs_strategy is None else outputs_strategy ) @@ -333,8 +369,8 @@ def send_and_check_xfer( looper, helpers, prepare_inputs, prepare_outputs, fees, ): def wrapped(inputs=None, outputs=None): - inputs = prepare_inputs() if inputs is None else inputs - outputs = prepare_outputs(fees.get(XFER_PUBLIC_FEES_ALIAS, 0), inputs) if outputs is None else outputs + inputs = prepare_inputs(txn_type=XFER_PUBLIC) if inputs is None else inputs + outputs = prepare_outputs(txn_type=XFER_PUBLIC, inputs=inputs) if outputs is None else outputs res = send_and_check_xfer_h(looper, helpers, inputs, outputs) return res @@ -346,8 +382,8 @@ def send_and_check_nym( looper, helpers, prepare_inputs, prepare_outputs, fees, ): def wrapped(inputs=None, outputs=None): - inputs = prepare_inputs() if inputs is None else inputs - outputs = prepare_outputs(fees.get(NYM_FEES_ALIAS, 0), inputs) if outputs is None else outputs + inputs = prepare_inputs(txn_type=NYM) if inputs is None else inputs + outputs = prepare_outputs(txn_type=NYM, inputs=inputs) if outputs is None else outputs res = send_and_check_nym_h(looper, helpers, inputs, outputs) return res @@ -365,60 +401,6 @@ def xfer_mint_tokens(mint_multiple_tokens): return mint_multiple_tokens[0] -@pytest.fixture -def curr_seq_no(xfer_mint_tokens): - return get_seq_no(xfer_mint_tokens) - - -@pytest.fixture -def curr_amount(xfer_mint_tokens): - return get_amount_from_token_txn(xfer_mint_tokens) - - -@pytest.fixture -def curr_utxo(curr_seq_no, curr_amount): - return { - 'amount': curr_amount, - 'seq_no': curr_seq_no - } - - -@pytest.fixture -def send_and_check_nym_with_fees_curr_utxo(looper, helpers, fees_set, xfer_addresses, curr_utxo): - _addresses = xfer_addresses - - def wrapped(addresses=None, check_reply=True, nym_with_fees=None): - addresses = _addresses if addresses is None else addresses - - curr_utxo['amount'], curr_utxo['seq_no'], resp = send_and_check_nym_with_fees( - helpers, fees_set, curr_utxo['seq_no'], - looper, addresses, curr_utxo['amount'], - check_reply=check_reply, nym_with_fees=nym_with_fees - ) - - return curr_utxo, resp - - return wrapped - - -@pytest.fixture -def send_and_check_transfer_curr_utxo(looper, helpers, fees, xfer_addresses, curr_utxo): - _addresses = xfer_addresses - - def wrapped(addresses=None, check_reply=True, transfer_summ=20): - addresses = _addresses if addresses is None else addresses - - curr_utxo['amount'], curr_utxo['seq_no'], resp = send_and_check_transfer( - helpers, addresses, fees, looper, - curr_utxo['amount'], curr_utxo['seq_no'], - check_reply=check_reply, transfer_summ=transfer_summ - fees[XFER_PUBLIC_FEES_ALIAS] - ) - - return curr_utxo, resp - - return wrapped - - @pytest.fixture(scope="module") def sdk_wallet_trustee(sdk_wallet_handle, sdk_trustees): return sdk_wallet_handle, sdk_trustees[0] diff --git a/sovtokenfees/sovtokenfees/test/dynamic_validation/__init__.py b/sovtokenfees/sovtokenfees/test/dynamic_validation/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/test/helper.py b/sovtokenfees/sovtokenfees/test/helper.py index 58183b79..7856fcec 100644 --- a/sovtokenfees/sovtokenfees/test/helper.py +++ b/sovtokenfees/sovtokenfees/test/helper.py @@ -3,12 +3,10 @@ from enum import Enum, unique from sovtoken.test.helpers.helper_general import utxo_from_addr_and_seq_no -from sovtoken.utxo_cache import UTXOAmounts from sovtokenfees.test.constants import NYM_FEES_ALIAS, txn_type_to_alias, XFER_PUBLIC_FEES_ALIAS from stp_core.loop.eventually import eventually from plenum.common.constants import DOMAIN_LEDGER_ID, DATA, TXN_TYPE, NYM -from plenum.common.exceptions import RequestNackedException from plenum.common.util import randomString from plenum.common.types import f from plenum.test.node_catchup.helper import waitNodeDataEquality @@ -18,28 +16,29 @@ from sovtoken import TOKEN_LEDGER_ID from sovtoken.utxo_cache import UTXOAmounts -from sovtoken.constants import OUTPUTS, AMOUNT, ADDRESS, XFER_PUBLIC, SEQNO +from sovtoken.constants import OUTPUTS, AMOUNT, ADDRESS, XFER_PUBLIC, SEQNO, UTXO_CACHE_LABEL -from sovtokenfees.constants import FEES, MAX_FEE_OUTPUTS +from sovtokenfees.constants import FEES @unique class InputsStrategy(Enum): all_utxos = 1 # use all utxos from each input address first_utxo_only = 2 # use only single (first) from each input address - # (TODO just to have some other one for now) @unique class OutputsStrategy(Enum): - transfer_equal = 1 # divide transfer among (outputs - inputs) - # output addresses in (almost) equal parts - # (total_input - transfer - fee) goes to first input address + transfer_some_equal = 1 + # divide transfer among output addresses in (almost) + # equal parts, a change (total_input - transfer - fee) + # goes either to one address listed in both inputs and + # outputs if there is such one or to the first input + # address otherwise transfer_all_equal = 2 # divide (total_input - fee) among all # output addresses in (almost) equal parts - # ( comparing to 'transfer_equal' seems valuable - # only for cases when we move all amount to other addresses ) + # ( useful when we move all amount to other addresses ) def check_state(n, is_equal=False): @@ -250,31 +249,26 @@ def prepare_inputs( ): assert strategy in InputsStrategy, "Unknown input strategy {}".format(strategy) - addresses = [helpers.wallet.address_map[addr.replace("pay:sov:", "")] for addr in addresses] - inputs = [] - if strategy == InputsStrategy.all_utxos: - for addr in addresses: - if not addr.all_seq_nos: - raise ValueError("no seq_nos for {}".format(addr.address)) - for seq_no in addr.all_seq_nos: - inputs.append({"source": utxo_from_addr_and_seq_no(addr.address, seq_no), ADDRESS: addr.address, SEQNO: seq_no}) - else: # InputsStrategy.first_utxo_only - for addr in addresses: - if not addr.all_seq_nos: - raise ValueError("no seq_nos for {}".format(addr.address)) - inputs.append({"source": utxo_from_addr_and_seq_no(addr.address, addr.all_seq_nos[0]), ADDRESS: addr.address, SEQNO: addr.all_seq_nos[0]}) + addresses = [helpers.wallet.address_map[addr] for addr in addresses] + for addr in addresses: + if not addr.all_seq_nos: + raise ValueError("no seq_nos for {}".format(addr.address)) + for seq_no in addr.all_seq_nos: + inputs.append({"source": utxo_from_addr_and_seq_no(addr.address, seq_no), ADDRESS: addr.address, SEQNO: seq_no}) + if strategy == InputsStrategy.first_utxo_only: + break return inputs def prepare_outputs( helpers, fee, inputs, addresses, - strategy=OutputsStrategy.transfer_equal, transfer_amount=20 + strategy=OutputsStrategy.transfer_some_equal, transfer_amount=20 ): def divide_equal(output_addresses, amount): + assert output_addresses output_amount = amount // len(output_addresses) - assert output_amount > 0 res = {addr: output_amount for addr in output_addresses} res[output_addresses[-1]] += amount % len(output_addresses) return res @@ -282,18 +276,14 @@ def divide_equal(output_addresses, amount): assert strategy in OutputsStrategy, "Unknown output strategy {}".format(strategy) total_input_amount = sum( - (helpers.wallet.address_map[i[ADDRESS].replace("pay:sov:", "")].amount(i[SEQNO]) for i in inputs) + (helpers.wallet.address_map[i[ADDRESS]].amount(i[SEQNO]) for i in inputs) ) # apply fee - # TODO why XFER_PUBLIC always total_output_amount = total_input_amount - fee if strategy == OutputsStrategy.transfer_all_equal: transfer_amount = total_output_amount - strategy = OutputsStrategy.transfer_equal - - # OutputsStrategy.transfer_equal assert transfer_amount >= 0 @@ -302,15 +292,14 @@ def divide_equal(output_addresses, amount): assert change >= 0 # transfer is divided among outputs - if transfer_amount: + outputs = {} + if addresses: outputs = divide_equal(addresses, transfer_amount) - else: - outputs = {} - # change goes to any input presented in outputs or first input address + # change goes to any input presented in outputs as well or first input address if change: - io_addrs = list(set([i[ADDRESS] for i in inputs]) & set(addresses)) - change_addr = io_addrs[0] if io_addrs else inputs[0][ADDRESS] + both_io_addrs = list(set([i[ADDRESS] for i in inputs]) & set(addresses)) + change_addr = both_io_addrs[0] if both_io_addrs else inputs[0][ADDRESS] if change_addr not in outputs: outputs[change_addr] = 0 @@ -331,27 +320,15 @@ def send_and_check_xfer(looper, helpers, inputs, outputs): def send_and_check_nym(looper, helpers, inputs, outputs): - - def _send(): - return helpers.sdk.get_first_result( - helpers.sdk.sdk_send_and_check([ - helpers.request.add_fees_specific( - helpers.request.nym(), inputs, outputs - )[0] - ]) - ) - - if len(outputs) > MAX_FEE_OUTPUTS: - with pytest.raises( - RequestNackedException, - match=(r".*length should be at most {}.*" - .format(MAX_FEE_OUTPUTS)) - ): - _send() - else: - resp = _send() - helpers.wallet.handle_txn_with_fees(resp) - return resp + resp = helpers.sdk.get_first_result( + helpers.sdk.sdk_send_and_check([ + helpers.request.add_fees_specific( + helpers.request.nym(), inputs, outputs + )[0] + ]) + ) + helpers.wallet.handle_txn_with_fees(resp) + return resp def ensure_all_nodes_have_same_data(looper, node_set, custom_timeout=None, @@ -366,10 +343,10 @@ def chk_utxo_cache(node, nodes): for n in nodes: cache[n.name] = {} utxo_data[n.name] = {} - cache_storage = n.ledger_to_req_handler[TOKEN_LEDGER_ID].utxo_cache._store + cache_storage = n.db_manager.get_store(UTXO_CACHE_LABEL)._store for key, value in cache_storage.iterator(include_value=True): cache[n.name][key] = value - utxo_data[n.name] = UTXOAmounts.get_amounts(key, n.ledger_to_req_handler[TOKEN_LEDGER_ID].utxo_cache, + utxo_data[n.name] = UTXOAmounts.get_amounts(key, n.db_manager.get_store(UTXO_CACHE_LABEL), is_committed=True).as_str() assert all(cache[node.name] == cache[n.name] for n in nodes) assert all(utxo_data[node.name] == utxo_data[n.name] for n in nodes) diff --git a/sovtokenfees/sovtokenfees/test/helpers/helper_node.py b/sovtokenfees/sovtokenfees/test/helpers/helper_node.py index b755a672..26967646 100644 --- a/sovtokenfees/sovtokenfees/test/helpers/helper_node.py +++ b/sovtokenfees/sovtokenfees/test/helpers/helper_node.py @@ -1,15 +1,23 @@ import copy import sovtoken.test.helpers.helper_node as sovtoken_helper_node -from plenum.common.constants import CONFIG_LEDGER_ID + +from indy_common.constants import NYM +from sovtokenfees.req_handlers.batch_handlers.fee_batch_handler import DomainFeeBatchHandler +from sovtokenfees.req_handlers.read_handlers.get_fees_handler import GetFeesHandler +from sovtokenfees.req_handlers.write_handlers.domain_fee_handler import DomainFeeHandler + +from common.serializers.serialization import domain_state_serializer from indy_common.authorize.auth_actions import compile_action_id, ADD_PREFIX, EDIT_PREFIX from indy_common.authorize.auth_cons_strategies import AbstractAuthStrategy -from sovtokenfees.constants import FEES_FIELD_NAME +from sovtokenfees.constants import FEES_FIELD_NAME, GET_FEES from sovtokenfees.domain import build_path_for_set_fees from sovtokenfees.test.constants import txn_type_to_alias, alias_to_txn_type +from plenum.common.constants import DOMAIN_LEDGER_ID, CONFIG_LEDGER_ID + class HelperNode(sovtoken_helper_node.HelperNode): """ @@ -28,14 +36,14 @@ def assert_deducted_fees(self, txn_type, seq_no, amount): key = "{}#{}".format(txn_type, seq_no) for node in self._nodes: req_handler = self._get_fees_req_handler(node) - deducted = req_handler.deducted_fees.get(key, 0) + deducted = req_handler._fees_tracker._deducted_fees.get(key, 0) assert deducted == amount def assert_set_fees_in_memory(self, fees): """ Assert nodes hold a certain fees in memory. """ for node in self._nodes: - req_handler = self._get_fees_req_handler(node) - assert req_handler.fees == fees + req_handler = self._get_gfees_req_handler(node) + assert req_handler.get_fees(is_committed=True, with_proof=True)[0] == fees def reset_fees(self): """ Reset the fees on each node. """ @@ -51,13 +59,24 @@ def get_fees_req_handler(self): """ Get the fees request handler of the first node """ return self._get_fees_req_handler(self._nodes[0]) + def get_db_manager(self): + return self._nodes[0].db_manager + + def get_write_req_validator(self): + return self._nodes[0].write_req_validator + + def get_write_req_manager(self): + return self._nodes[0].write_manager + def _reset_fees(self, node): - req_handler = self._get_fees_req_handler(node) - empty_fees = req_handler.state_serializer.serialize({}) - req_handler.state.set(build_path_for_set_fees().encode(), empty_fees) + empty_fees = domain_state_serializer.serialize({}) + node.db_manager.get_state(CONFIG_LEDGER_ID).set(build_path_for_set_fees().encode(), empty_fees) def _get_fees_req_handler(self, node): - return node.get_req_handler(ledger_id=CONFIG_LEDGER_ID) + return next(h for h in node.write_manager.request_handlers[NYM] if isinstance(h, DomainFeeHandler)) + + def _get_gfees_req_handler(self, node): + return node.read_manager.request_handlers[GET_FEES] @staticmethod def fill_auth_map_for_node(node, txn_type): @@ -67,9 +86,8 @@ def fill_auth_map_for_node(node, txn_type): prefix=ADD_PREFIX) edit_rule_id = compile_action_id(txn_type=txn_type, field='*', old_value='*', new_value='*', prefix=EDIT_PREFIX) - if AbstractAuthStrategy.is_accepted_action_id(add_rule_id, - rule_id) or AbstractAuthStrategy.is_accepted_action_id( - edit_rule_id, rule_id): + if AbstractAuthStrategy.is_accepted_action_id(add_rule_id, rule_id) or \ + AbstractAuthStrategy.is_accepted_action_id(edit_rule_id, rule_id): constraint = copy.deepcopy(constraint) if constraint: constraint.set_metadata({FEES_FIELD_NAME: txn_type_to_alias[txn_type]}) diff --git a/sovtokenfees/sovtokenfees/test/helpers/test/test_helper_request.py b/sovtokenfees/sovtokenfees/test/helpers/test/test_helper_request.py index 39c48012..d5e124ef 100644 --- a/sovtokenfees/sovtokenfees/test/helpers/test/test_helper_request.py +++ b/sovtokenfees/sovtokenfees/test/helpers/test/test_helper_request.py @@ -8,8 +8,8 @@ def get_nym_details(helpers, dest): - domain_req_handler = helpers.node.get_domain_req_handler() - return domain_req_handler.getNymDetails(domain_req_handler.state, dest, False) + nym_req_handler = helpers.node.nym_handler + return nym_req_handler.get_nym_details(nym_req_handler.state, dest, False) @pytest.mark.helper_test diff --git a/sovtokenfees/sovtokenfees/test/multipleio/conftest.py b/sovtokenfees/sovtokenfees/test/multipleio/conftest.py index c3b86d96..b3484413 100644 --- a/sovtokenfees/sovtokenfees/test/multipleio/conftest.py +++ b/sovtokenfees/sovtokenfees/test/multipleio/conftest.py @@ -1,27 +1,28 @@ import pytest -from sovtokenfees.test.conftest import MintStrategy -from sovtokenfees.test.helper import InputsStrategy, OutputsStrategy - +from plenum.test.conftest import getValueFromModule -@pytest.fixture -def addresses_num(): - return 4 +from sovtokenfees.test.conftest import MintStrategy, IOAddressesStatic +from sovtokenfees.test.helper import InputsStrategy, OutputsStrategy -@pytest.fixture -def mint_strategy(): - return MintStrategy.multiple_equal +def pytest_configure(config): + config.addinivalue_line( + "markers", "io_border(int): mark test to use specific inputs/outputs addresses division" + ) + config.addinivalue_line( + "markers", "nym_fee(int): mark test to use specific nym fee value" + ) @pytest.fixture -def mint_utxos_num(): - return 3 +def addresses_num(request): + return getValueFromModule(request, "ADDRESSES_NUM", 4) @pytest.fixture -def mint_amount(): - return 1000 +def mint_strategy(): + return MintStrategy.all_equal @pytest.fixture(params=InputsStrategy, ids=lambda x: x.name) @@ -39,26 +40,6 @@ def transfer_amount(): return 10 -class IOAddresses: - def __init__(self, i_addrs, o_addrs): - self._iaddrs = i_addrs - self._oaddrs = o_addrs - - def rotate(self): - self._iaddrs, self._oaddrs = self._oaddrs, self._iaddrs - - @property - def iaddrs(self): - return self._iaddrs - - @property - def oaddrs(self): - return self._oaddrs - - def __call__(self): - return (self._iaddrs, self._oaddrs) - - @pytest.fixture( params=[ ([0], [0]), @@ -73,7 +54,7 @@ def __call__(self): ) ) def io_addresses(request, addresses): - return IOAddresses( + return IOAddressesStatic( [addresses[i] for i in request.param[0]], [addresses[i] for i in request.param[1]] ) diff --git a/sovtokenfees/sovtokenfees/test/multipleio/test_fees_specific.py b/sovtokenfees/sovtokenfees/test/multipleio/test_fees_specific.py new file mode 100644 index 00000000..4f00adfc --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/multipleio/test_fees_specific.py @@ -0,0 +1,60 @@ +import pytest + +from sovtokenfees.test.constants import NYM_FEES_ALIAS + +from sovtokenfees.test.helper import InputsStrategy, OutputsStrategy +from sovtokenfees.test.conftest import IOAddressesStatic + + +ADDRESSES_NUM = 3 +MINT_UTXOS_NUM = 1 +MINT_AMOUNT = 1000 + + +@pytest.fixture +def fees(request): + marker = request.node.get_closest_marker('nym_fee') + return { + NYM_FEES_ALIAS: marker.args[0] if marker else 4 + } + + +@pytest.fixture +def io_addresses(request, addresses): + marker = request.node.get_closest_marker('io_border') + assert marker + io_border = marker.args[0] + return IOAddressesStatic( + addresses[:io_border], addresses[io_border:] + ) + + +@pytest.fixture +def inputs_strategy(request): + return InputsStrategy.all_utxos + + +@pytest.fixture +def outputs_strategy(request): + return OutputsStrategy.transfer_all_equal + + +@pytest.mark.io_border(ADDRESSES_NUM) +@pytest.mark.nym_fee(ADDRESSES_NUM * MINT_UTXOS_NUM * MINT_AMOUNT) +def test_nym_with_no_change_no_outputs( + nodeSetWithIntegratedTokenPlugin, + fees_set, + mint_multiple_tokens, + send_and_check_nym, +): + send_and_check_nym() + + +@pytest.mark.io_border(ADDRESSES_NUM - 1) +def test_nym_with_output_not_in_inputs( + nodeSetWithIntegratedTokenPlugin, + fees_set, + mint_multiple_tokens, + send_and_check_nym, +): + send_and_check_nym() diff --git a/sovtokenfees/sovtokenfees/test/multipleio/test_multiple_io_nym.py b/sovtokenfees/sovtokenfees/test/multipleio/test_multiple_io_nym.py index 0ab8f666..78600b00 100644 --- a/sovtokenfees/sovtokenfees/test/multipleio/test_multiple_io_nym.py +++ b/sovtokenfees/sovtokenfees/test/multipleio/test_multiple_io_nym.py @@ -1,6 +1,10 @@ import pytest +from plenum.common.exceptions import RequestNackedException from indy_common.constants import NYM + +from sovtokenfees.constants import MAX_FEE_OUTPUTS + from sovtokenfees.test.constants import NYM_FEES_ALIAS @@ -19,9 +23,24 @@ def test_nym_with_multiple_io( fees_set, mint_multiple_tokens, io_addresses, - outputs_strategy, + prepare_inputs, + prepare_outputs, send_and_check_nym, ): - send_and_check_nym() + def _check(): + inputs = prepare_inputs(txn_type=NYM) + outputs = prepare_outputs(txn_type=NYM, inputs=inputs) + + if len(outputs) > MAX_FEE_OUTPUTS: + with pytest.raises( + RequestNackedException, + match=(r".*length should be at most {}.*" + .format(MAX_FEE_OUTPUTS)) + ): + send_and_check_nym(inputs, outputs) + else: + send_and_check_nym(inputs, outputs) + + _check() io_addresses.rotate() - send_and_check_nym() + _check() diff --git a/sovtokenfees/sovtokenfees/test/multipleio/test_nym_specific.py b/sovtokenfees/sovtokenfees/test/multipleio/test_nym_specific.py new file mode 100644 index 00000000..38978233 --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/multipleio/test_nym_specific.py @@ -0,0 +1,76 @@ +import pytest + +from plenum.common.exceptions import RequestNackedException + +from sovtokenfees.test.constants import NYM_FEES_ALIAS +from sovtokenfees.test.helper import InputsStrategy, OutputsStrategy +from sovtokenfees.test.conftest import IOAddressesStatic + + +ADDRESSES_NUM = 3 +MINT_UTXOS_NUM = 1 +MINT_AMOUNT = 1000 + + +@pytest.fixture +def fees(request): + marker = request.node.get_closest_marker('nym_fee') + return { + NYM_FEES_ALIAS: marker.args[0] if marker else 4 + } + + +@pytest.fixture +def io_addresses(request, addresses): + marker = request.node.get_closest_marker('io_border') + assert marker + io_border = marker.args[0] + return IOAddressesStatic( + addresses[:io_border], addresses[io_border:] + ) + + +@pytest.fixture +def inputs_strategy(request): + return InputsStrategy.all_utxos + + +@pytest.fixture +def outputs_strategy(request): + return OutputsStrategy.transfer_all_equal + + +@pytest.mark.io_border(ADDRESSES_NUM) +@pytest.mark.nym_fee(ADDRESSES_NUM * MINT_UTXOS_NUM * MINT_AMOUNT) +def test_nym_with_no_change_no_outputs( + nodeSetWithIntegratedTokenPlugin, + fees_set, + mint_multiple_tokens, + send_and_check_nym, +): + send_and_check_nym() + + +@pytest.mark.io_border(ADDRESSES_NUM - 1) +def test_nym_with_output_not_in_inputs( + nodeSetWithIntegratedTokenPlugin, + fees_set, + mint_multiple_tokens, + send_and_check_nym, +): + send_and_check_nym() + + +@pytest.mark.io_border(ADDRESSES_NUM - 1) +@pytest.mark.nym_fee((ADDRESSES_NUM - 1) * MINT_UTXOS_NUM * MINT_AMOUNT) +def test_nym_with_no_change_output_with_zero_amount( + nodeSetWithIntegratedTokenPlugin, + fees_set, + mint_multiple_tokens, + send_and_check_nym, +): + with pytest.raises( + RequestNackedException, + match=(r".*outputs -- amount -- negative or zero value.*") + ): + send_and_check_nym() diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/__init__.py b/sovtokenfees/sovtokenfees/test/req_handlers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/conftest.py b/sovtokenfees/sovtokenfees/test/req_handlers/conftest.py new file mode 100644 index 00000000..f8006ad3 --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/conftest.py @@ -0,0 +1,51 @@ +import json + +import pytest +from common.serializers.json_serializer import JsonSerializer +from sovtoken.constants import UTXO_CACHE_LABEL +from sovtoken.utxo_cache import UTXOCache +from sovtokenfees.req_handlers.fees_utils import BatchFeesTracker + +from indy_common.constants import CONFIG_LEDGER_ID + +from indy_node.test.request_handlers.helper import get_fake_ledger +from sovtoken.test.req_handlers.conftest import db_manager, utxo_cache + +from common.serializers import serialization +from plenum.common.constants import KeyValueStorageType, BLS_LABEL +from plenum.test.testing_utils import FakeSomething +from state.pruning_state import PruningState +from storage.helper import initKeyValueStorage + +in_memory_serializer = JsonSerializer() + + +@pytest.fixture(scope="module") +def db_manager_with_config(db_manager, utxo_cache): + storage = initKeyValueStorage(KeyValueStorageType.Memory, + None, + "configInMemoryStore", + txn_serializer=in_memory_serializer) + ledger = get_fake_ledger() + db_manager.register_new_database(CONFIG_LEDGER_ID, ledger, PruningState(storage)) + return db_manager + + +@pytest.fixture(scope="module") +def bls_store(db_manager_with_config): + multi_sigs = FakeSomething() + multi_sigs.as_dict = lambda: {"a": "b"} + bls = FakeSomething() + bls.get = lambda _: multi_sigs + db_manager_with_config.register_new_store(BLS_LABEL, bls) + return bls + + +@pytest.fixture(scope="module") +def fees(): + return json.dumps({"nym_alias": 1, "attrib_alias": 2}) + + +@pytest.fixture(scope="module") +def fees_tracker(): + return BatchFeesTracker() diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/domain_fee_handler/__init__.py b/sovtokenfees/sovtokenfees/test/req_handlers/domain_fee_handler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/domain_fee_handler/conftest.py b/sovtokenfees/sovtokenfees/test/req_handlers/domain_fee_handler/conftest.py new file mode 100644 index 00000000..d156d92e --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/domain_fee_handler/conftest.py @@ -0,0 +1,62 @@ +import json + +import pytest +from indy_node.test.request_handlers.helper import get_fake_ledger +from sovtoken import TOKEN_LEDGER_ID +from sovtokenfees.constants import FEES +from sovtokenfees.req_handlers.write_handlers.domain_fee_handler import DomainFeeHandler +from sovtoken.test.req_handlers.conftest import utxo_cache, wallet, payment_address +from sovtoken.test.req_handlers.mint_req_handler.conftest import trustees, idr_cache +from sovtoken.test.req_handlers.xfer_req_handler.conftest import mint_tokens + +from common.serializers import serialization +from plenum.common.constants import NYM, DOMAIN_LEDGER_ID, KeyValueStorageType +from indy.did import create_and_store_my_did +from indy.ledger import build_nym_request +from indy.payment import add_request_fees +from base58 import b58encode_check + +from plenum.common.txn_util import append_txn_metadata +from plenum.test.helper import sdk_json_to_request_object +from state.pruning_state import PruningState +from storage.helper import initKeyValueStorage + + +@pytest.fixture(scope="module") +def domain_fee_handler(db_manager_with_config, fees_tracker, utxo_cache, mint_tokens): + handler = DomainFeeHandler(db_manager_with_config, fees_tracker) + handler.txn_type = NYM + return handler + + +@pytest.fixture() +def nym_request(wallet, looper, trustees): + did_future = create_and_store_my_did(wallet, "{}") + did, vk = looper.loop.run_until_complete(did_future) + nym_future = build_nym_request(trustees[0], did, vk, None, None) + nym_req = looper.loop.run_until_complete(nym_future) + return nym_req + + +@pytest.fixture() +def nym_request_with_fees(libsovtoken, nym_request, wallet, payment_address, looper): + inputs = json.dumps( + ["txo:sov:" + b58encode_check(json.dumps({"address": payment_address, "seqNo": 1}).encode()).decode()]) + outputs = json.dumps([{ + "recipient": payment_address, + "amount": 9 + }]) + fees_future = add_request_fees(wallet, None, nym_request, inputs, outputs, None) + fees, _ = looper.loop.run_until_complete(fees_future) + fees_req = json.loads(fees) + fees = fees_req[FEES] + fees_req = sdk_json_to_request_object(fees_req) + setattr(fees_req, FEES, fees) + return fees_req + + +@pytest.fixture() +def nym_txn(domain_fee_handler, nym_request): + nym_request = sdk_json_to_request_object(json.loads(nym_request)) + nym_txn = domain_fee_handler._req_to_txn(nym_request) + return append_txn_metadata(nym_txn, 1, 1, 1) diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/domain_fee_handler/test_domain_fee_handler_update_state.py b/sovtokenfees/sovtokenfees/test/req_handlers/domain_fee_handler/test_domain_fee_handler_update_state.py new file mode 100644 index 00000000..82ca960e --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/domain_fee_handler/test_domain_fee_handler_update_state.py @@ -0,0 +1,13 @@ +from indy_common.constants import NYM + + +def test_domain_fee_handler_update_state(domain_fee_handler, nym_request_with_fees, nym_txn, payment_address): + domain_fee_handler.apply_request(nym_request_with_fees, None, nym_txn) + + token_state = domain_fee_handler.token_state + utxo_cache = domain_fee_handler.utxo_cache + + assert int(token_state.get((payment_address[8:] + ":2").encode(), isCommitted=False)) == 9 + assert utxo_cache.get(payment_address[8:]) == '2:9' + assert domain_fee_handler._fees_tracker.fees_in_current_batch == 1 + assert domain_fee_handler._fees_tracker.has_deducted_fees(NYM, 1) diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/__init__.py b/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/conftest.py b/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/conftest.py new file mode 100644 index 00000000..15998926 --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/conftest.py @@ -0,0 +1,18 @@ +import pytest +from sovtoken import TOKEN_LEDGER_ID +from sovtokenfees.req_handlers.batch_handlers.fee_batch_handler import DomainFeeBatchHandler + +from plenum.common.ledger_uncommitted_tracker import LedgerUncommittedTracker + + +@pytest.fixture +def fee_batch_handler(db_manager_with_config, fees_tracker, token_tracker): + return DomainFeeBatchHandler(db_manager_with_config, fees_tracker) + + +@pytest.fixture +def token_tracker(db_manager_with_config): + token_tracker = LedgerUncommittedTracker(db_manager_with_config.get_state(TOKEN_LEDGER_ID).committedHeadHash, + db_manager_with_config.get_ledger(TOKEN_LEDGER_ID).committed_root_hash, + db_manager_with_config.get_ledger(TOKEN_LEDGER_ID).size) + db_manager_with_config.register_new_tracker(TOKEN_LEDGER_ID, token_tracker) diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/test_fee_batch_handler_commit_batch.py b/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/test_fee_batch_handler_commit_batch.py new file mode 100644 index 00000000..86000db2 --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/test_fee_batch_handler_commit_batch.py @@ -0,0 +1,29 @@ +from sovtoken.constants import UTXO_CACHE_LABEL, TOKEN_LEDGER_ID + +from plenum.common.constants import NYM, TXN_METADATA_SEQ_NO, TXN_METADATA, TXN_PAYLOAD, TXN_PAYLOAD_TYPE +from plenum.server.batch_handlers.three_pc_batch import ThreePcBatch + + +def test_fee_batch_handler_commit_batch(fee_batch_handler, fees_tracker): + utxo_cache = fee_batch_handler.database_manager.get_store(UTXO_CACHE_LABEL) + utxo_cache.set('1', '2') + fee_batch_handler.database_manager.get_state(TOKEN_LEDGER_ID).set('1'.encode(), '2'.encode()) + fees_tracker.fees_in_current_batch = 1 + fee_batch_handler.post_batch_applied(None, None) + + fees_tracker.add_deducted_fees(NYM, 1, 1) + prev_res = [{ + TXN_METADATA: { + TXN_METADATA_SEQ_NO: 1 + }, + TXN_PAYLOAD: { + TXN_PAYLOAD_TYPE: NYM + } + }] + + three_pc_batch = ThreePcBatch(0, 0, 0, 3, 1, 'state', 'txn', + ['a', 'b', 'c'], ['a']) + + fee_batch_handler.commit_batch(three_pc_batch, prev_res) + assert not len(utxo_cache.current_batch_ops) + assert not len(utxo_cache.un_committed) diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/test_fee_batch_handler_post_batch_applied.py b/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/test_fee_batch_handler_post_batch_applied.py new file mode 100644 index 00000000..68be1c62 --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/test_fee_batch_handler_post_batch_applied.py @@ -0,0 +1,17 @@ +from sovtoken.constants import UTXO_CACHE_LABEL, TOKEN_LEDGER_ID + + +def test_fee_batch_handler_post_batch_applied(fee_batch_handler, fees_tracker): + utxo_cache = fee_batch_handler.database_manager.get_store(UTXO_CACHE_LABEL) + utxo_cache.set('1', '2') + fees_tracker.fees_in_current_batch = 1 + fee_batch_handler.post_batch_applied(None, None) + assert not len(utxo_cache.current_batch_ops) + assert len(utxo_cache.un_committed) == 1 + assert utxo_cache.un_committed[0] == (fee_batch_handler.token_state.headHash, {'1': '2'}) + assert fee_batch_handler._fees_tracker.fees_in_current_batch == 0 + assert fee_batch_handler.token_tracker.un_committed[0] == ( + fee_batch_handler.token_state.headHash, + fee_batch_handler.token_ledger.uncommitted_root_hash, + fee_batch_handler.token_ledger.uncommitted_size + ) diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/test_fee_batch_handler_post_batch_rejected.py b/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/test_fee_batch_handler_post_batch_rejected.py new file mode 100644 index 00000000..696fc00c --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/fees_batch_handler/test_fee_batch_handler_post_batch_rejected.py @@ -0,0 +1,13 @@ +from sovtoken.constants import UTXO_CACHE_LABEL, TOKEN_LEDGER_ID + + +def test_fee_batch_handler_post_batch_rejected(fee_batch_handler, fees_tracker): + utxo_cache = fee_batch_handler.database_manager.get_store(UTXO_CACHE_LABEL) + utxo_cache.set('1', '2') + fee_batch_handler.database_manager.get_state(TOKEN_LEDGER_ID).set('1'.encode(), '2'.encode()) + fees_tracker.fees_in_current_batch = 1 + fee_batch_handler.post_batch_applied(None, None) + fee_batch_handler.post_batch_rejected(None, None) + assert not len(utxo_cache.current_batch_ops) + assert not len(utxo_cache.un_committed) + assert not len(fee_batch_handler.token_tracker.un_committed) diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/get_fees_handler/__init__.py b/sovtokenfees/sovtokenfees/test/req_handlers/get_fees_handler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/get_fees_handler/conftest.py b/sovtokenfees/sovtokenfees/test/req_handlers/get_fees_handler/conftest.py new file mode 100644 index 00000000..3e64469b --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/get_fees_handler/conftest.py @@ -0,0 +1,31 @@ +import json + +import pytest +from indy.payment import build_get_txn_fees_req +from sovtokenfees.domain import build_path_for_set_fees +from sovtokenfees.req_handlers.read_handlers.get_fees_handler import GetFeesHandler + +from plenum.common.constants import CONFIG_LEDGER_ID +from plenum.test.helper import sdk_json_to_request_object +from sovtoken.test.req_handlers.conftest import wallet + + +@pytest.fixture(scope="module") +def get_fees_handler(db_manager_with_config, bls_store): + return GetFeesHandler(db_manager_with_config) + + +@pytest.fixture(scope="module") +def prepare_fees(db_manager_with_config, fees): + config_state = db_manager_with_config.get_state(CONFIG_LEDGER_ID) + path = build_path_for_set_fees() + config_state.set(path.encode(), fees) + config_state.commit() + + +@pytest.fixture() +def get_fees_request(looper, libsovtoken, wallet): + get_fees_future = build_get_txn_fees_req(wallet, None, "sov") + get_fees_request = looper.loop.run_until_complete(get_fees_future) + get_fees_request = sdk_json_to_request_object(json.loads(get_fees_request)) + return get_fees_request diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/get_fees_handler/test_get_fees_handler_get_result.py b/sovtokenfees/sovtokenfees/test/req_handlers/get_fees_handler/test_get_fees_handler_get_result.py new file mode 100644 index 00000000..7e242171 --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/get_fees_handler/test_get_fees_handler_get_result.py @@ -0,0 +1,12 @@ +import json + +from sovtokenfees.constants import FEES + +from plenum.common.constants import STATE_PROOF + + +def test_get_fees_handler_get_result(get_fees_handler, get_fees_request, fees, prepare_fees): + response = get_fees_handler.get_result(get_fees_request) + assert response[STATE_PROOF] + assert response[FEES] + assert response[FEES] == json.loads(fees) \ No newline at end of file diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/get_fees_handler/test_get_fees_handler_get_result_no_fees_set.py b/sovtokenfees/sovtokenfees/test/req_handlers/get_fees_handler/test_get_fees_handler_get_result_no_fees_set.py new file mode 100644 index 00000000..e5210296 --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/get_fees_handler/test_get_fees_handler_get_result_no_fees_set.py @@ -0,0 +1,11 @@ +import json + +from sovtokenfees.constants import FEES + +from plenum.common.constants import STATE_PROOF + + +def test_get_fees_handler_get_result(get_fees_handler, get_fees_request): + response = get_fees_handler.get_result(get_fees_request) + assert response[STATE_PROOF] + assert not response[FEES] diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/__init__.py b/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/conftest.py b/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/conftest.py new file mode 100644 index 00000000..60f51512 --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/conftest.py @@ -0,0 +1,55 @@ +import json + +import pytest +from indy.ledger import multi_sign_request +from indy.payment import build_set_txn_fees_req +from sovtokenfees.sovtokenfees_auth_map import sovtokenfees_auth_map +from sovtokenfees.req_handlers.write_handlers.set_fees_handler import SetFeesHandler +from sovtoken.test.req_handlers.mint_req_handler.conftest import trustees +from sovtoken.test.req_handlers.conftest import wallet + +from indy_common.test.auth.conftest import write_auth_req_validator, constraint_serializer, config_state +from plenum.common.txn_util import append_txn_metadata +from plenum.test.helper import sdk_json_to_request_object +from plenum.test.testing_utils import FakeSomething + + +@pytest.fixture(scope="module") +def set_fees_handler(db_manager_with_config, write_auth_req_validator): + write_auth_req_validator.auth_map.update(sovtokenfees_auth_map) + return SetFeesHandler(db_manager_with_config, write_auth_req_validator) + + +@pytest.fixture(scope="module") +def idr_cache(): + idr_cache = FakeSomething() + idr_cache.users = {} + + def getRole(idr, isCommitted=True): + return idr_cache.users.get(idr, None) + + idr_cache.getRole = getRole + return idr_cache + + +@pytest.fixture() +def set_fees_request(libsovtoken, looper, trustees, wallet, fees): + set_fees_future = build_set_txn_fees_req(wallet, + trustees[0], + "sov", + fees) + set_fees_request = looper.loop.run_until_complete(set_fees_future) + for trustee in trustees: + set_fees_future = multi_sign_request(wallet, trustee, set_fees_request) + set_fees_request = looper.loop.run_until_complete(set_fees_future) + set_fees_request = json.loads(set_fees_request) + sigs = set_fees_request["signatures"] + set_fees_request = sdk_json_to_request_object(set_fees_request) + setattr(set_fees_request, "signatures", sigs) + return set_fees_request + + +@pytest.fixture() +def set_fees_txn(set_fees_request, set_fees_handler): + set_fees_txn = set_fees_handler._req_to_txn(set_fees_request) + return append_txn_metadata(set_fees_txn, 1, 1, 1) diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/test_set_fees_dynamic_validation.py b/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/test_set_fees_dynamic_validation.py new file mode 100644 index 00000000..165a9604 --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/test_set_fees_dynamic_validation.py @@ -0,0 +1,20 @@ +import pytest +from sovtoken.test.constants import VALID_IDENTIFIER + +from plenum.common.exceptions import UnauthorizedClientRequest + + +def test_set_fees_dynamic_validation(set_fees_request, set_fees_handler): + set_fees_handler.dynamic_validation(set_fees_request) + + +def test_set_fees_insufficient_signatures(set_fees_request, set_fees_handler): + set_fees_request.signatures = dict([(k, v) for k, v in set_fees_request.signatures.items()][:-1]) + with pytest.raises(UnauthorizedClientRequest, match='Not enough TRUSTEE signatures'): + set_fees_handler.dynamic_validation(set_fees_request) + + +def test_set_fees_handler_dynamic_validation_unknown_identifier(set_fees_handler, set_fees_request): + set_fees_request._identifier = VALID_IDENTIFIER + with pytest.raises(UnauthorizedClientRequest, match="sender's DID {} is not found in the Ledger".format(VALID_IDENTIFIER)): + set_fees_handler.dynamic_validation(set_fees_request) \ No newline at end of file diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/test_set_fees_handler_static_validation.py b/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/test_set_fees_handler_static_validation.py new file mode 100644 index 00000000..8e00873c --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/test_set_fees_handler_static_validation.py @@ -0,0 +1,26 @@ +import pytest +from sovtokenfees.constants import FEES + +from plenum.common.exceptions import InvalidClientRequest + + +def test_set_fees_handler_static_validation(set_fees_handler, set_fees_request): + set_fees_handler.static_validation(set_fees_request) + + +def test_set_fees_handler_static_validation_no_fees(set_fees_handler, set_fees_request): + del set_fees_request.operation[FEES] + with pytest.raises(InvalidClientRequest, match="missed fields - fees"): + set_fees_handler.static_validation(set_fees_request) + + +def test_set_fees_handler_static_validation_negative_fees(set_fees_handler, set_fees_request): + set_fees_request.operation[FEES]["nym_alias"] = -1 + with pytest.raises(InvalidClientRequest, match="set_fees -- negative value"): + set_fees_handler.static_validation(set_fees_request) + + +def test_set_fees_handler_static_validation_empty_alias(set_fees_handler, set_fees_request): + set_fees_request.operation[FEES][""] = 1 + with pytest.raises(InvalidClientRequest, match="set_fees -- empty string"): + set_fees_handler.static_validation(set_fees_request) diff --git a/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/test_set_fees_handler_update_state.py b/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/test_set_fees_handler_update_state.py new file mode 100644 index 00000000..210fb080 --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/req_handlers/set_fees_handler/test_set_fees_handler_update_state.py @@ -0,0 +1,12 @@ +import json + +from sovtokenfees.domain import build_path_for_set_fees + + +def test_set_fees_handler_update_state(set_fees_handler, set_fees_txn, fees): + set_fees_handler.update_state(set_fees_txn, None, None) + assert json.loads( + set_fees_handler.state.get(build_path_for_set_fees().encode(), isCommitted=False).decode()) == json.loads(fees) + assert set_fees_handler.state.get(build_path_for_set_fees("nym_alias").encode(), isCommitted=False).decode() == "1" + assert set_fees_handler.state.get(build_path_for_set_fees("attrib_alias").encode(), + isCommitted=False).decode() == "2" diff --git a/sovtokenfees/sovtokenfees/test/taa/__init__.py b/sovtokenfees/sovtokenfees/test/taa/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/test/taa/fees/__init__.py b/sovtokenfees/sovtokenfees/test/taa/fees/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/test/taa/xfer/__init__.py b/sovtokenfees/sovtokenfees/test/taa/xfer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sovtokenfees/sovtokenfees/test/test_client_authnr.py b/sovtokenfees/sovtokenfees/test/test_client_authnr.py index c13e21b0..05812fe7 100644 --- a/sovtokenfees/sovtokenfees/test/test_client_authnr.py +++ b/sovtokenfees/sovtokenfees/test/test_client_authnr.py @@ -2,6 +2,8 @@ from sovtokenfees.client_authnr import FeesAuthNr from sovtoken.constants import AMOUNT, SEQNO, ADDRESS +from sovtokenfees.constants import ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE + from plenum.common.exceptions import InvalidSignatureFormat, \ InsufficientCorrectSignatures, InvalidClientRequest from plenum.common.constants import DOMAIN_LEDGER_ID @@ -11,10 +13,11 @@ VALID_IDENTIFIER = "6ouriXMZkLeHsuXrN1X1fd" VALID_REQID = 1517423828260117 PROTOCOL_VERSION = 1 -SIGNATURES = {'M9BJDuS24bqbJNvBRsoGg3': '5eJax8GW8gTRfZzhuta9s7hU2K3dkKpDWGE7SUsMqiRmQ2GzWXxJKaDzcPMKdZWqrA5Kn1vSHFND9oThsjaQLhHy', - 'B8fV7naUqLATYocqu7yZ8W': 'AaGqjqGk67mj3MVua46RiqJ6mq6zoy99VriGvZJbpZekhrtju9k2NQrrJcdnMnps7cBZfFxLwhELnLZnTqfb9Ag', - 'E7QRhdcnhAwA6E46k9EtZo': '2EBZxZ3E2r2ZjCCBwgD6ipnHbskZb4Y4Yqm6haYEsr7hdM1m36yqLFrmNSB7JPqjAsMx6qjw6dWV5sRou1DgiKrM', - 'CA4bVFDU4GLbX8xZju811o': 'YJjXm8vfiy1sD586tecQ2Eh1Q3wFmLodaxctArasW7RNCujPiZa5CurdW5b8dRXMEBdX9YhsDGkahJXUnZaH8SC'} +SIGNATURES = { + 'M9BJDuS24bqbJNvBRsoGg3': '5eJax8GW8gTRfZzhuta9s7hU2K3dkKpDWGE7SUsMqiRmQ2GzWXxJKaDzcPMKdZWqrA5Kn1vSHFND9oThsjaQLhHy', + 'B8fV7naUqLATYocqu7yZ8W': 'AaGqjqGk67mj3MVua46RiqJ6mq6zoy99VriGvZJbpZekhrtju9k2NQrrJcdnMnps7cBZfFxLwhELnLZnTqfb9Ag', + 'E7QRhdcnhAwA6E46k9EtZo': '2EBZxZ3E2r2ZjCCBwgD6ipnHbskZb4Y4Yqm6haYEsr7hdM1m36yqLFrmNSB7JPqjAsMx6qjw6dWV5sRou1DgiKrM', + 'CA4bVFDU4GLbX8xZju811o': 'YJjXm8vfiy1sD586tecQ2Eh1Q3wFmLodaxctArasW7RNCujPiZa5CurdW5b8dRXMEBdX9YhsDGkahJXUnZaH8SC'} # ------------------------------------------------------------------------------------ @@ -52,9 +55,9 @@ def node(nodeSet): # of inputted signatures since they are all valid @pytest.mark.skip("This test hard to support. Should be rewritten") def test_authenticate_success(node): - state = node[0].getState(DOMAIN_LEDGER_ID) - fees_authenticator = FeesAuthNr(state, None) + fees_authenticator = FeesAuthNr(ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE, + state, None) req_data = { 'signatures': @@ -81,9 +84,9 @@ def test_authenticate_success(node): # similar to the previous success test, authenticate returns a list of authenticated signatures. it should match the number # of inputted signatures since they are all valid def test_authenticate_success_one_signature(node): - state = node[0].getState(DOMAIN_LEDGER_ID) - fees_authenticator = FeesAuthNr(state, None) + fees_authenticator = FeesAuthNr(ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE, + state, None) req_data = { 'signatures': @@ -103,12 +106,13 @@ def test_authenticate_success_one_signature(node): fees_authenticator.authenticate(req_data) assert ex.value.args == (0, 1) + # ------------------------------------------------------------------------------------ # only 2 of the 4 signatures are valid. (hint: the last two are mucky) def test_authenticate_errors_on_invalid_inputs(node): - state = node[0].getState(DOMAIN_LEDGER_ID) - fees_authenticator = FeesAuthNr(state, None) + fees_authenticator = FeesAuthNr(ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE, + state, None) req_data = { 'signatures': @@ -135,7 +139,8 @@ def test_authenticate_errors_on_invalid_inputs(node): # the operation type is not FEES so the exception InvalidClientRequest is raised def test_authenticate_invalid(): state = pruning_state() - fees_authenticator = FeesAuthNr(state, None) + fees_authenticator = FeesAuthNr(ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE, + state, None) req_data = {'signatures': SIGNATURES, 'reqId': VALID_REQID, 'operation': {'type': 'INVALID_TXN_TYPE', 'fees': {'1': 4, '10001': 8} @@ -149,10 +154,12 @@ def test_authenticate_invalid(): # the signature and fees sections are populated with correct data def test_verify_signature_success(): state = pruning_state() - fees_authenticator = FeesAuthNr(state, None) + fees_authenticator = FeesAuthNr(ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE, + state, None) msg = FeeData() - msg.signatures = {'V4SGRU86Z58d6TV7PBUe6f': 'dYZZFV6Fk59bFaNFaKwXfY9AqP6cr3wqTSPjoRLoLcgAi28RNErweRXRskzZ4cwRyzBCpZyzewmSPdrQb1oES83'} + msg.signatures = { + 'V4SGRU86Z58d6TV7PBUe6f': 'dYZZFV6Fk59bFaNFaKwXfY9AqP6cr3wqTSPjoRLoLcgAi28RNErweRXRskzZ4cwRyzBCpZyzewmSPdrQb1oES83'} # 1 2 3 4 4 # 12345678901234567890123456789012345678901234567890 @@ -171,9 +178,11 @@ def test_verify_signature_success(): def test_verify_signature_no_fees(): # should just run, no exceptions state = pruning_state() - fees_authenticator = FeesAuthNr(state, None) + fees_authenticator = FeesAuthNr(ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE, + state, None) msg = FeeData() - msg.signatures = {'MSjKTWkPLtYoPEaTF1TUDb': '61PUc8K8aAkhmCjWLstxwRREBAJKbVMRuGiUXxSo1tiRwXgUfVT4TY47NJtbQymcDW3paXPWNqqD4cziJjoPQSSX'} + msg.signatures = { + 'MSjKTWkPLtYoPEaTF1TUDb': '61PUc8K8aAkhmCjWLstxwRREBAJKbVMRuGiUXxSo1tiRwXgUfVT4TY47NJtbQymcDW3paXPWNqqD4cziJjoPQSSX'} fees_authenticator.verify_signature(msg) @@ -182,15 +191,16 @@ def test_verify_signature_no_fees(): # in the fees dictionary, array in element 0 has a signature that is not correct so the # exception InvalidSignatureFormat will get raised def test_verify_signature_invalid_signature_format(node): - fees_authenticator = FeesAuthNr(node[0].getState(DOMAIN_LEDGER_ID), None) + fees_authenticator = FeesAuthNr(ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE, + node[0].getState(DOMAIN_LEDGER_ID), None) msg = FeeData() - msg.signatures = {'MSjKTWkPLtYoPEaTF1TUDb': '61PUc8K8aAkhmCjWLstxwRREBAJKbVMRuGiUXxSo1tiRwXgUfVT4TY47NJtbQymcDW3paXPWNqqD4cziJjoPQSSX'} + msg.signatures = { + 'MSjKTWkPLtYoPEaTF1TUDb': '61PUc8K8aAkhmCjWLstxwRREBAJKbVMRuGiUXxSo1tiRwXgUfVT4TY47NJtbQymcDW3paXPWNqqD4cziJjoPQSSX'} inputs = [{ADDRESS: '2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es', SEQNO: 2}] outputs = [{ADDRESS: '2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es', AMOUNT: 10}] signatures = ['000kKoLEAP1YCYULYqxSNKvcYigGG1fHRMbZ6N1byFhaRut4P5RDF2KGR73ffgQoyzMHabrcTvrRGHhEfQ6ZdzxB'] - setattr(msg, "fees", [inputs, outputs, signatures - ]) + setattr(msg, "fees", [inputs, outputs, signatures]) with pytest.raises(InvalidSignatureFormat): fees_authenticator.verify_signature(msg) @@ -200,9 +210,11 @@ def test_verify_signature_invalid_signature_format(node): # in this test, the signature in fees is not valid for the data set. it is a valid signature and passes b58decode def test_verify_signature_incorrect_signatures(): state = pruning_state() - fees_authenticator = FeesAuthNr(state, None) + fees_authenticator = FeesAuthNr(ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE, + state, None) msg = FeeData() - msg.signatures = {'MSjKTWkPLtYoPEaTF1TUDb': '61PUc8K8aAkhmCjWLstxwRREBAJKbVMRuGiUXxSo1tiRwXgUfVT4TY47NJtbQymcDW3paXPWNqqD4cziJjoPQSSX'} + msg.signatures = { + 'MSjKTWkPLtYoPEaTF1TUDb': '61PUc8K8aAkhmCjWLstxwRREBAJKbVMRuGiUXxSo1tiRwXgUfVT4TY47NJtbQymcDW3paXPWNqqD4cziJjoPQSSX'} inputs = [{ADDRESS: '2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es', SEQNO: 2}] outputs = [{ADDRESS: '2jS4PHWQJKcawRxdW6GVsjnZBa1ecGdCssn7KhWYJZGTXgL7Es', AMOUNT: 10}] @@ -218,10 +230,12 @@ def test_verify_signature_incorrect_signatures(): # however the signature is not signed for all of the values def test_verify_signature_mismatch_of_signatures(): state = pruning_state() - fees_authenticator = FeesAuthNr(state, None) + fees_authenticator = FeesAuthNr(ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE, + state, None) msg = FeeData() - msg.signatures = {'MSjKTWkPLtYoPEaTF1TUDb': '61PUc8K8aAkhmCjWLstxwRREBAJKbVMRuGiUXxSo1tiRwXgUfVT4TY47NJtbQymcDW3paXPWNqqD4cziJjoPQSSX'} + msg.signatures = { + 'MSjKTWkPLtYoPEaTF1TUDb': '61PUc8K8aAkhmCjWLstxwRREBAJKbVMRuGiUXxSo1tiRwXgUfVT4TY47NJtbQymcDW3paXPWNqqD4cziJjoPQSSX'} # 1 2 3 4 4 # 12345678901234567890123456789012345678901234567890 @@ -246,10 +260,12 @@ def test_verify_signature_mismatch_of_signatures(): # number is wrong. def test_verify_signature_sequence_order_wrong(): state = pruning_state() - fees_authenticator = FeesAuthNr(state, None) + fees_authenticator = FeesAuthNr(ACCEPTABLE_WRITE_TYPES_FEE, ACCEPTABLE_QUERY_TYPES_FEE, ACCEPTABLE_ACTION_TYPES_FEE, + state, None) msg = FeeData() - msg.signatures = {'MSjKTWkPLtYoPEaTF1TUDb': '61PUc8K8aAkhmCjWLstxwRREBAJKbVMRuGiUXxSo1tiRwXgUfVT4TY47NJtbQymcDW3paXPWNqqD4cziJjoPQSSX'} + msg.signatures = { + 'MSjKTWkPLtYoPEaTF1TUDb': '61PUc8K8aAkhmCjWLstxwRREBAJKbVMRuGiUXxSo1tiRwXgUfVT4TY47NJtbQymcDW3paXPWNqqD4cziJjoPQSSX'} # 1 2 3 4 4 # 12345678901234567890123456789012345678901234567890 @@ -260,4 +276,4 @@ def test_verify_signature_sequence_order_wrong(): setattr(msg, "fees", [inputs, outputs, signatures]) with pytest.raises(InsufficientCorrectSignatures): - fees_authenticator.verify_signature(msg) \ No newline at end of file + fees_authenticator.verify_signature(msg) diff --git a/sovtokenfees/sovtokenfees/test/test_get_auth_rule.py b/sovtokenfees/sovtokenfees/test/test_get_auth_rule.py index dcca43d9..168684fd 100644 --- a/sovtokenfees/sovtokenfees/test/test_get_auth_rule.py +++ b/sovtokenfees/sovtokenfees/test/test_get_auth_rule.py @@ -9,9 +9,9 @@ from plenum.common.types import OPERATION from indy_common.authorize.auth_actions import ADD_PREFIX, EDIT_PREFIX -from indy_common.authorize.auth_constraints import ROLE, ConstraintCreator +from indy_common.authorize.auth_constraints import ROLE, ConstraintCreator, AuthConstraintForbidden from indy_common.authorize.auth_map import auth_map -from indy_common.constants import NYM, TRUST_ANCHOR, AUTH_ACTION, AUTH_TYPE, FIELD, NEW_VALUE, \ +from indy_common.constants import NYM, ENDORSER, AUTH_ACTION, AUTH_TYPE, FIELD, NEW_VALUE, \ OLD_VALUE, GET_AUTH_RULE, SCHEMA, CONSTRAINT from indy_node.server.config_req_handler import ConfigReqHandler from indy_node.test.auth_rule.helper import generate_constraint_list, generate_constraint_entity, \ @@ -22,7 +22,7 @@ def generate_key(auth_action=ADD_PREFIX, auth_type=NYM, - field=ROLE, new_value=TRUST_ANCHOR, + field=ROLE, new_value=ENDORSER, old_value=None): key = {AUTH_ACTION: auth_action, AUTH_TYPE: auth_type, @@ -70,7 +70,6 @@ def test_get_one_auth_rule_transaction(looper, assert result[CONSTRAINT] == auth_map.get(str_key).as_dict -@pytest.mark.skip(reason="INDY-2077") def test_get_one_disabled_auth_rule_transaction(looper, sdk_wallet_trustee, sdk_pool_handle): @@ -83,7 +82,7 @@ def test_get_one_disabled_auth_rule_transaction(looper, result = resp["result"][DATA] assert len(result) == 1 _check_key(key, result[0]) - assert result[0][CONSTRAINT] == {} + assert result[0][CONSTRAINT] == AuthConstraintForbidden().as_dict def test_get_all_auth_rule_transactions(looper, @@ -112,7 +111,7 @@ def test_get_one_auth_rule_transaction_after_write(looper, auth_action = ADD_PREFIX auth_type = NYM field = ROLE - new_value = TRUST_ANCHOR + new_value = ENDORSER constraint = generate_constraint_list(auth_constraints=[generate_constraint_entity(role=TRUSTEE), generate_constraint_entity(role=STEWARD)]) resp = sdk_send_and_check_auth_rule_request(looper, @@ -140,7 +139,7 @@ def test_get_all_auth_rule_transactions_after_write(looper, auth_action = ADD_PREFIX auth_type = NYM field = ROLE - new_value = TRUST_ANCHOR + new_value = ENDORSER auth_constraint = generate_constraint_list(auth_constraints=[generate_constraint_entity(role=TRUSTEE), generate_constraint_entity(role=STEWARD)]) resp = sdk_send_and_check_auth_rule_request(looper, diff --git a/sovtokenfees/sovtokenfees/test/test_state_proof_for_get_fee.py b/sovtokenfees/sovtokenfees/test/test_state_proof_for_get_fee.py new file mode 100644 index 00000000..3bf9af92 --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/test_state_proof_for_get_fee.py @@ -0,0 +1,36 @@ +from common.serializers.json_serializer import JsonSerializer +from sovtokenfees.domain import build_path_for_set_fees + +from plenum.test.txn_author_agreement.helper import check_state_proof +from sovtokenfees.test.constants import NYM_FEES_ALIAS + +from plenum.test.delayers import cDelay + +from plenum.test.stasher import delay_rules + + +def test_state_proof_for_get_fee(looper, helpers, + nodeSetWithIntegratedTokenPlugin, + sdk_pool_handle): + fees_1 = {NYM_FEES_ALIAS: 1} + fees_2 = {NYM_FEES_ALIAS: 2} + node_set = [n.nodeIbStasher for n in nodeSetWithIntegratedTokenPlugin] + + helpers.general.do_set_fees(fees_1) + response1 = helpers.general.do_get_fees() + check_state_proof(response1, build_path_for_set_fees(), + JsonSerializer().serialize(fees_1)) + + config_state = nodeSetWithIntegratedTokenPlugin[0].states[2] + assert config_state.headHash == config_state.committedHeadHash + + # We delay commit messages to get different committed and uncommitted roots for ledger + with delay_rules(node_set, cDelay()): + helpers.general.set_fees_without_waiting(fees_2) + looper.runFor(3) + response2 = helpers.general.do_get_fees() + # Returned state proof for first set_fees, which is committed + check_state_proof(response2, build_path_for_set_fees(), + JsonSerializer().serialize(fees_1)) + # Let's check that uncommitted state differs from committed + assert config_state.headHash != config_state.committedHeadHash diff --git a/sovtokenfees/sovtokenfees/test/test_static_fee_req_handler.py b/sovtokenfees/sovtokenfees/test/test_static_fee_req_handler.py index 1ffeabee..f65d5b95 100644 --- a/sovtokenfees/sovtokenfees/test/test_static_fee_req_handler.py +++ b/sovtokenfees/sovtokenfees/test/test_static_fee_req_handler.py @@ -1,7 +1,11 @@ import json import pytest +from common.exceptions import LogicError from sovtoken.test.helpers.helper_general import utxo_from_addr_and_seq_no +from sovtokenfees.req_handlers.read_handlers.get_fee_handler import GetFeeHandler +from sovtokenfees.req_handlers.read_handlers.get_fees_handler import GetFeesHandler +from sovtokenfees.req_handlers.write_handlers.set_fees_handler import SetFeesHandler from plenum.common.constants import NYM, TXN_TYPE from plenum.common.exceptions import (InvalidClientRequest, @@ -63,6 +67,26 @@ def fee_handler(helpers): return helpers.node.get_fees_req_handler() +@pytest.fixture +def get_fee_handler(helpers): + return GetFeeHandler(helpers.node.get_db_manager()) + + +@pytest.fixture +def get_fees_handler(helpers): + return GetFeesHandler(helpers.node.get_db_manager()) + + +@pytest.fixture +def set_fees_handler(helpers): + return SetFeesHandler(helpers.node.get_db_manager(), helpers.node.get_write_req_validator()) + + +@pytest.fixture +def write_manager(helpers): + return helpers.node.get_write_req_manager() + + @pytest.fixture(scope="function", autouse=True) def reset_token_handler(fee_handler): old_head = fee_handler.state.committedHead @@ -71,6 +95,7 @@ def reset_token_handler(fee_handler): # TODO: this is a function fixture. Do we need this revert there? # fee_handler.onBatchRejected() + @pytest.fixture() def fees_authorizer(fee_handler): return FeesAuthorizer(config_state=fee_handler.state, @@ -133,27 +158,27 @@ class TestStaticValidation: # Method returns None if it was successful - # TODO: Refactoring should be looked at to return a boolean # Instead of assuming that everything is good when the return value is None. - # - Static Fee Request Handler (doStaticValidation) - def test_get_fee_valid_alias(self, helpers, fee_handler): + # - Static Fee Request Handler (static_validation) + def test_get_fee_valid_alias(self, helpers, get_fee_handler): """ StaticValidation of a get fee request with all of the whitelisted txn types. """ request = helpers.request.get_fee("test_alias") - result = fee_handler.doStaticValidation(request) + result = get_fee_handler.static_validation(request) assert result is None - def test_get_fee_invalid_alias(self, helpers, fee_handler): + def test_get_fee_invalid_alias(self, helpers, get_fee_handler): """ StaticValidation of a get fee request with all of the whitelisted txn types. """ request = helpers.request.get_fee("") with pytest.raises(InvalidClientRequest, match="empty string"): - fee_handler.doStaticValidation(request) + get_fee_handler.static_validation(request) - def test_set_fees_valid_txn_types(self, helpers, fee_handler): + def test_set_fees_valid_txn_types(self, helpers, set_fees_handler): """ StaticValidation of a set fees request with all of the whitelisted txn types. @@ -161,12 +186,11 @@ def test_set_fees_valid_txn_types(self, helpers, fee_handler): request = helpers.request.set_fees(VALID_FEES) - result = fee_handler.doStaticValidation(request) + result = set_fees_handler.static_validation(request) assert result is None - - def test_set_fees_missing_fees(self, helpers, fee_handler): + def test_set_fees_missing_fees(self, helpers, set_fees_handler): """ StaticValidation of a set fees request where 'fees' is not a dict. """ @@ -175,17 +199,17 @@ def test_set_fees_missing_fees(self, helpers, fee_handler): request.operation.pop(FEES) with pytest.raises(InvalidClientRequest, match="missed fields - fees"): - fee_handler.doStaticValidation(request) + set_fees_handler.static_validation(request) - def test_get_fees(self, helpers, fee_handler): + def test_get_fees(self, helpers, get_fees_handler): """ StaticValidation of a get fees request does nothing. """ request = helpers.request.get_fees() - fee_handler.doStaticValidation(request) + get_fees_handler.static_validation(request) - def test_unkown_type(self, helpers, fee_handler): + def test_unkown_type(self, helpers, write_manager): """ StaticValidation of an unknown request does nothing. """ @@ -197,12 +221,12 @@ def test_unkown_type(self, helpers, fee_handler): sigs = request["signatures"] request = helpers.sdk.sdk_json_to_request_object(request) setattr(request, "signatures", sigs) - - fee_handler.doStaticValidation(request) + with pytest.raises(LogicError): + write_manager.static_validation(request) class TestValidation(): - def test_set_fees_invalid_signee(self, helpers, fee_handler): + def test_set_fees_invalid_signee(self, helpers, set_fees_handler): """ Validation of a set_fees request where one of the signees doesn't exist. @@ -214,9 +238,9 @@ def test_set_fees_invalid_signee(self, helpers, fee_handler): request.signatures[reversed_did] = sig with pytest.raises(UnauthorizedClientRequest): - fee_handler.validate(request) + set_fees_handler.dynamic_validation(request) - def test_set_fees_invalid_signature(self, helpers, fee_handler): + def test_set_fees_invalid_signature(self, helpers, set_fees_handler): """ Validation of a set_fees request with an invalid signatures still passes. @@ -230,9 +254,9 @@ def test_set_fees_invalid_signature(self, helpers, fee_handler): reversed_sig = sig[::-1] request.signatures[did] = reversed_sig - fee_handler.validate(request) + set_fees_handler.dynamic_validation(request) - def test_set_fees_test_missing_signee(self, helpers, fee_handler): + def test_set_fees_test_missing_signee(self, helpers, set_fees_handler): """ Validation of a set_fees request without the minimum number of trustees. @@ -242,12 +266,12 @@ def test_set_fees_test_missing_signee(self, helpers, fee_handler): (did, sig) = request.signatures.popitem() with pytest.raises(UnauthorizedClientRequest): - fee_handler.validate(request) + set_fees_handler.dynamic_validation(request) def test_set_fees_test_extra_signees( self, helpers, - fee_handler, + set_fees_handler, increased_trustees ): """ @@ -259,16 +283,7 @@ def test_set_fees_test_extra_signees( request = helpers.wallet.sign_request(request, increased_trustees) assert len(request.signatures) == 7 - assert fee_handler.validate(request) - - def test_get_fees_invalid_identifier(self, helpers, fee_handler): - """ - Validation of a get_fees request does nothing. - """ - - request = helpers.request.get_fees() - request._identifier = None - fee_handler.validate(request) + assert set_fees_handler.dynamic_validation(request) def test_validate_unknown_type(self, helpers, fee_handler): """ @@ -276,7 +291,7 @@ def test_validate_unknown_type(self, helpers, fee_handler): """ request = helpers.request.nym() - fee_handler.validate(request) + fee_handler.dynamic_validation(request) class TestCanPayFees(): @@ -485,19 +500,9 @@ def test_nym_set_with_invalid_fees( # - Static Fee Request Handler (apply) -def test_static_fee_req_handler_apply(helpers, fee_handler): +def test_static_fee_req_handler_apply(helpers, set_fees_handler): request = helpers.request.set_fees(VALID_FEES) - prev_size = fee_handler.ledger.uncommitted_size - ret_value = fee_handler.apply(request, 10) + prev_size = set_fees_handler.ledger.uncommitted_size + ret_value = set_fees_handler.apply_request(request, 10, None) assert ret_value[0] == prev_size + 1 - - -def test_txn_types_are_united(fee_handler): - w_types_fees = fee_handler.write_types - q_types_fees = fee_handler.query_types - assert w_types_fees.intersection(ConfigReqHandler.write_types) == ConfigReqHandler.write_types - assert q_types_fees.intersection(ConfigReqHandler.query_types) == ConfigReqHandler.query_types - - assert w_types_fees.difference(ConfigReqHandler.write_types) - assert q_types_fees.difference(ConfigReqHandler.write_types) diff --git a/sovtokenfees/sovtokenfees/test/three_phase_commit_helper.py b/sovtokenfees/sovtokenfees/test/three_phase_commit_helper.py index c5a9d366..278d065f 100644 --- a/sovtokenfees/sovtokenfees/test/three_phase_commit_helper.py +++ b/sovtokenfees/sovtokenfees/test/three_phase_commit_helper.py @@ -1,3 +1,5 @@ +from sovtokenfees.req_handlers.batch_handlers.fee_batch_handler import DomainFeeBatchHandler + from plenum.common.constants import CONFIG_LEDGER_ID, DOMAIN_LEDGER_ID from plenum.common.types import f from sovtoken.constants import ADDRESS, AMOUNT @@ -20,14 +22,16 @@ def node(helpers, user_address): @pytest.fixture() -def static_req_handler(node): - return node.get_req_handler(CONFIG_LEDGER_ID) +def domain_batch_fee_handler(node): + return next(h for h in node.write_manager.batch_handlers[DOMAIN_LEDGER_ID] if isinstance(h, DomainFeeBatchHandler)) @pytest.fixture() -def three_phase_handler(node, static_req_handler): - token_handler = node.get_req_handler(ledger_id=TOKEN_LEDGER_ID) - return ThreePhaseCommitHandler(node.master_replica, token_handler.ledger, token_handler.state, static_req_handler) +def three_phase_handler(node, domain_batch_fee_handler): + return ThreePhaseCommitHandler(node.master_replica, + node.db_manager.get_ledger(TOKEN_LEDGER_ID), + node.db_manager.get_state(TOKEN_LEDGER_ID), + domain_batch_fee_handler._fees_tracker) @pytest.fixture() @@ -142,12 +146,12 @@ def mock_get_txn_root(ledger_id): state_root_deserialized = state_roots_serializer.deserialize(PP.plugin_data[FEES][f.STATE_ROOT.nm]) txn_root_deserialized = state_roots_serializer.deserialize(PP.plugin_data[FEES][f.TXN_ROOT.nm]) - monkeypatch.setattr(three_phase_handler.fees_req_handler, 'fee_txns_in_current_batch', 1) + monkeypatch.setattr(three_phase_handler.fees_tracker, 'fees_in_current_batch', 1) monkeypatch.setattr(three_phase_handler.master_replica, 'stateRootHash', mock_get_state_root) monkeypatch.setattr(three_phase_handler.master_replica, 'txnRootHash', mock_get_txn_root) - monkeypatch.setattr(three_phase_handler.fees_req_handler.token_state._trie, 'root_hash', + monkeypatch.setattr(three_phase_handler.token_state._trie, 'root_hash', state_root_deserialized) - monkeypatch.setattr(three_phase_handler.fees_req_handler.token_ledger, 'uncommittedRootHash', + monkeypatch.setattr(three_phase_handler.token_ledger, 'uncommittedRootHash', txn_root_deserialized) return three_phase_handler.add_to_pre_prepare(pp) diff --git a/sovtokenfees/sovtokenfees/test/view_change/conftest.py b/sovtokenfees/sovtokenfees/test/view_change/conftest.py new file mode 100644 index 00000000..7e8b6dfe --- /dev/null +++ b/sovtokenfees/sovtokenfees/test/view_change/conftest.py @@ -0,0 +1,21 @@ +import pytest + +from plenum.test.conftest import getValueFromModule + +from sovtokenfees.test.helper import InputsStrategy +from sovtokenfees.test.conftest import MintStrategy + + +@pytest.fixture +def addresses_num(request): + return getValueFromModule(request, "ADDRESSES_NUM", 4) + + +@pytest.fixture +def mint_strategy(request): + return getValueFromModule(request, "MINT_STRATEGY", MintStrategy.all_equal) + + +@pytest.fixture +def inputs_strategy(request): + return getValueFromModule(request, "INPUTS_STRATEGY", InputsStrategy.first_utxo_only) diff --git a/sovtokenfees/sovtokenfees/test/view_change/helper.py b/sovtokenfees/sovtokenfees/test/view_change/helper.py index 969c473c..1479ec8e 100644 --- a/sovtokenfees/sovtokenfees/test/view_change/helper.py +++ b/sovtokenfees/sovtokenfees/test/view_change/helper.py @@ -11,53 +11,6 @@ def scenario_txns_during_view_change( - looper, - nodes, - curr_utxo, - send_txns, - send_txns_invalid=None -): - lagging_node = nodes[-1] - rest_nodes = nodes[:-1] - - def send_txns_invalid_default(): - curr_utxo['amount'] += 1 - with pytest.raises(RequestRejectedException, match='Insufficient funds'): - send_txns() - curr_utxo['amount'] -= 1 - - # Send transactions - send_txns() - ensure_all_nodes_have_same_data(looper, nodes) - - # Lag one node (delay Prepare and Commit messages for lagging_node) - with delay_rules( - lagging_node.nodeIbStasher, pDelay(), cDelay() - ): - # Send more transactions - send_txns() - ensure_all_nodes_have_same_data(looper, rest_nodes) - - # Send invalid transactions - (send_txns_invalid or send_txns_invalid_default)() - ensure_all_nodes_have_same_data(looper, rest_nodes) - - # Initiate view change - # Wait until view change is finished and check that needed transactions are written. - ensure_view_change(looper, nodes) - ensureElectionsDone(looper, nodes) - - # Reset delays - # Make sure that all nodes have equal state - # (expecting that lagging_node caught up missed ones) - ensure_all_nodes_have_same_data(looper, nodes) - - # make sure the poll is functional - send_txns() - ensure_all_nodes_have_same_data(looper, nodes) - - -def scenario_txns_during_view_change_new( looper, helpers, nodes, @@ -70,7 +23,7 @@ def scenario_txns_during_view_change_new( def send_txns_invalid_default(): # TODO non-public API is used - addr = helpers.wallet.address_map[io_addresses()[0][0].replace("pay:sov:", "")] + addr = helpers.wallet.address_map[io_addresses()[0][0]] seq_no = list(addr.outputs[0])[0] assert addr.outputs[0][seq_no] > 0 @@ -99,7 +52,7 @@ def send_txns_invalid_default(): # Initiate view change # Wait until view change is finished and check that needed transactions are written. - ensure_view_change(looper, nodes) + ensure_view_change(looper, nodes, custom_timeout=240) ensureElectionsDone(looper, nodes) # Reset delays diff --git a/sovtokenfees/sovtokenfees/test/view_change/test_nym_during_view_change.py b/sovtokenfees/sovtokenfees/test/view_change/test_nym_during_view_change.py index 7ce91e91..26c53640 100644 --- a/sovtokenfees/sovtokenfees/test/view_change/test_nym_during_view_change.py +++ b/sovtokenfees/sovtokenfees/test/view_change/test_nym_during_view_change.py @@ -8,9 +8,13 @@ from sovtokenfees.test.view_change.helper import scenario_txns_during_view_change +from sovtokenfees.test.helper import InputsStrategy -ADDRESSES_NUM = 2 -MINT_UTXOS_NUM = 1 +from plenum.test.test_node import ensureElectionsDone +from plenum.test.view_change.helper import ensure_view_change + +MINT_UTXOS_NUM = 6 +INPUTS_STRATEGY = InputsStrategy.first_utxo_only @pytest.fixture( @@ -26,12 +30,15 @@ def fees(request): def test_nym_during_view_change( looper, + helpers, nodeSetWithIntegratedTokenPlugin, - sdk_pool_handle, sdk_wallet_client, fees, fees_set, - curr_utxo, - send_and_check_nym_with_fees_curr_utxo + mint_multiple_tokens, + send_and_check_nym, + io_addresses, + sdk_pool_handle, + sdk_wallet_client ): def send_txns_invalid(): with pytest.raises(RequestRejectedException, match='Rule for this action is'): @@ -39,8 +46,9 @@ def send_txns_invalid(): scenario_txns_during_view_change( looper, + helpers, nodeSetWithIntegratedTokenPlugin, - curr_utxo, - send_and_check_nym_with_fees_curr_utxo, + io_addresses, + send_and_check_nym, send_txns_invalid=(None if fees[NYM_FEES_ALIAS] else send_txns_invalid) ) diff --git a/sovtokenfees/sovtokenfees/test/view_change/test_nym_fees_during_view_change.py b/sovtokenfees/sovtokenfees/test/view_change/test_nym_fees_during_view_change.py index 4df32b5e..59d8e9df 100644 --- a/sovtokenfees/sovtokenfees/test/view_change/test_nym_fees_during_view_change.py +++ b/sovtokenfees/sovtokenfees/test/view_change/test_nym_fees_during_view_change.py @@ -8,9 +8,11 @@ from sovtokenfees.test.view_change.helper import scenario_txns_during_view_change +from sovtokenfees.test.helper import InputsStrategy -ADDRESSES_NUM = 2 -MINT_UTXOS_NUM = 1 + +MINT_UTXOS_NUM = 6 +INPUTS_STRATEGY = InputsStrategy.first_utxo_only @pytest.fixture( @@ -26,12 +28,15 @@ def fees(request): def test_nym_fees_during_view_change( looper, + helpers, nodeSetWithIntegratedTokenPlugin, - sdk_pool_handle, sdk_wallet_client, fees, fees_set, - curr_utxo, - send_and_check_nym_with_fees_curr_utxo + mint_multiple_tokens, + send_and_check_nym, + io_addresses, + sdk_pool_handle, + sdk_wallet_client ): def send_txns_invalid(): with pytest.raises(RequestRejectedException, match='Rule for this action is'): @@ -39,8 +44,9 @@ def send_txns_invalid(): scenario_txns_during_view_change( looper, + helpers, nodeSetWithIntegratedTokenPlugin, - curr_utxo, - send_and_check_nym_with_fees_curr_utxo, + io_addresses, + send_and_check_nym, send_txns_invalid=(None if fees[NYM_FEES_ALIAS] else send_txns_invalid) ) diff --git a/sovtokenfees/sovtokenfees/test/view_change/test_xfer_during_view_change.py b/sovtokenfees/sovtokenfees/test/view_change/test_xfer_during_view_change.py index 57ff2193..fc3d89e3 100644 --- a/sovtokenfees/sovtokenfees/test/view_change/test_xfer_during_view_change.py +++ b/sovtokenfees/sovtokenfees/test/view_change/test_xfer_during_view_change.py @@ -1,13 +1,8 @@ import pytest -from sovtokenfees.test.conftest import MintStrategy from sovtokenfees.test.constants import XFER_PUBLIC_FEES_ALIAS -from sovtokenfees.test.view_change.helper import scenario_txns_during_view_change_new - -ADDRESSES_NUM = 4 -MINT_STRATEGY = MintStrategy.multiple_equal -MINT_UTXOS_NUM = 3 +from sovtokenfees.test.view_change.helper import scenario_txns_during_view_change @pytest.fixture( @@ -30,7 +25,7 @@ def test_xfer_during_view_change( send_and_check_xfer, io_addresses ): - scenario_txns_during_view_change_new( + scenario_txns_during_view_change( looper, helpers, nodeSetWithIntegratedTokenPlugin, diff --git a/sovtokenfees/sovtokenfees/test/view_change/test_xfer_fees_during_view_change.py b/sovtokenfees/sovtokenfees/test/view_change/test_xfer_fees_during_view_change.py index 255ca6af..45e9050c 100644 --- a/sovtokenfees/sovtokenfees/test/view_change/test_xfer_fees_during_view_change.py +++ b/sovtokenfees/sovtokenfees/test/view_change/test_xfer_fees_during_view_change.py @@ -1,13 +1,11 @@ import pytest + +from sovtokenfees.test.conftest import MintStrategy from sovtokenfees.test.constants import XFER_PUBLIC_FEES_ALIAS from sovtokenfees.test.view_change.helper import scenario_txns_during_view_change -ADDRESSES_NUM = 2 -MINT_UTXOS_NUM = 1 - - @pytest.fixture( scope='module', params=[ @@ -21,14 +19,17 @@ def fees(request): def test_xfer_fees_during_view_change( looper, + helpers, nodeSetWithIntegratedTokenPlugin, fees_set, - curr_utxo, - send_and_check_transfer_curr_utxo + mint_multiple_tokens, + send_and_check_xfer, + io_addresses ): scenario_txns_during_view_change( looper, + helpers, nodeSetWithIntegratedTokenPlugin, - curr_utxo, - send_and_check_transfer_curr_utxo + io_addresses, + send_and_check_xfer ) diff --git a/sovtokenfees/sovtokenfees/test/view_change/test_xfer_fees_nym_during_view_change.py b/sovtokenfees/sovtokenfees/test/view_change/test_xfer_fees_nym_during_view_change.py index 00fa9c62..a11d223d 100644 --- a/sovtokenfees/sovtokenfees/test/view_change/test_xfer_fees_nym_during_view_change.py +++ b/sovtokenfees/sovtokenfees/test/view_change/test_xfer_fees_nym_during_view_change.py @@ -1,13 +1,11 @@ import pytest -from sovtokenfees.test.constants import XFER_PUBLIC_FEES_ALIAS, NYM_FEES_ALIAS +from sovtokenfees.test.constants import ( + XFER_PUBLIC_FEES_ALIAS, NYM_FEES_ALIAS, alias_to_txn_type +) from sovtokenfees.test.view_change.helper import scenario_txns_during_view_change -ADDRESSES_NUM = 2 -MINT_UTXOS_NUM = 1 - - @pytest.fixture( scope='module', params=[ @@ -15,7 +13,7 @@ {NYM_FEES_ALIAS: 4, XFER_PUBLIC_FEES_ALIAS: 8}, # fees for both {NYM_FEES_ALIAS: 0, XFER_PUBLIC_FEES_ALIAS: 0}, # no fees {NYM_FEES_ALIAS: 4, XFER_PUBLIC_FEES_ALIAS: 0}, # no fees for XFER_PUBLIC - ], ids=lambda x: '-'.join(sorted([k for k, v in x.items() if v])) or 'nofees' + ], ids=lambda x: '-'.join(sorted([alias_to_txn_type[k] for k, v in x.items() if v])) or 'nofees' ) def fees(request): return request.param @@ -23,14 +21,22 @@ def fees(request): def test_xfer_fees_nym_during_view_change( looper, + helpers, nodeSetWithIntegratedTokenPlugin, fees_set, - curr_utxo, - send_and_check_transfer_curr_utxo, - send_and_check_nym_with_fees_curr_utxo, + mint_multiple_tokens, + send_and_check_xfer, + send_and_check_nym, + io_addresses ): def send_txns(): - send_and_check_transfer_curr_utxo() - send_and_check_nym_with_fees_curr_utxo() + send_and_check_xfer() + send_and_check_nym() - scenario_txns_during_view_change(looper, nodeSetWithIntegratedTokenPlugin, curr_utxo, send_txns) + scenario_txns_during_view_change( + looper, + helpers, + nodeSetWithIntegratedTokenPlugin, + io_addresses, + send_txns + ) diff --git a/sovtokenfees/sovtokenfees/test/view_change/test_xfer_fees_nym_fees_during_view_change.py b/sovtokenfees/sovtokenfees/test/view_change/test_xfer_fees_nym_fees_during_view_change.py index 8e2fcbcf..5dba6c72 100644 --- a/sovtokenfees/sovtokenfees/test/view_change/test_xfer_fees_nym_fees_during_view_change.py +++ b/sovtokenfees/sovtokenfees/test/view_change/test_xfer_fees_nym_fees_during_view_change.py @@ -1,13 +1,11 @@ import pytest -from sovtokenfees.test.constants import XFER_PUBLIC_FEES_ALIAS, NYM_FEES_ALIAS +from sovtokenfees.test.constants import ( + XFER_PUBLIC_FEES_ALIAS, NYM_FEES_ALIAS, alias_to_txn_type +) from sovtokenfees.test.view_change.helper import scenario_txns_during_view_change -ADDRESSES_NUM = 2 -MINT_UTXOS_NUM = 1 - - @pytest.fixture( scope='module', params=[ @@ -15,7 +13,7 @@ {NYM_FEES_ALIAS: 0, XFER_PUBLIC_FEES_ALIAS: 0}, # no fees {NYM_FEES_ALIAS: 4, XFER_PUBLIC_FEES_ALIAS: 0}, # no fees for XFER_PUBLIC {NYM_FEES_ALIAS: 0, XFER_PUBLIC_FEES_ALIAS: 8}, # no fees for NYM - ], ids=lambda x: '-'.join(sorted([k for k, v in x.items() if v])) or 'nofees' + ], ids=lambda x: '-'.join(sorted([alias_to_txn_type[k] for k, v in x.items() if v])) or 'nofees' ) def fees(request): return request.param @@ -23,14 +21,22 @@ def fees(request): def test_xfer_fees_nym_fees_during_view_change( looper, + helpers, nodeSetWithIntegratedTokenPlugin, fees_set, - curr_utxo, - send_and_check_transfer_curr_utxo, - send_and_check_nym_with_fees_curr_utxo, + mint_multiple_tokens, + send_and_check_xfer, + send_and_check_nym, + io_addresses ): def send_txns(): - send_and_check_transfer_curr_utxo() - send_and_check_nym_with_fees_curr_utxo() + send_and_check_xfer() + send_and_check_nym() - scenario_txns_during_view_change(looper, nodeSetWithIntegratedTokenPlugin, curr_utxo, send_txns) + scenario_txns_during_view_change( + looper, + helpers, + nodeSetWithIntegratedTokenPlugin, + io_addresses, + send_txns + ) diff --git a/sovtokenfees/sovtokenfees/test/view_change/test_xfer_nym_during_view_change.py b/sovtokenfees/sovtokenfees/test/view_change/test_xfer_nym_during_view_change.py index 459accb3..c0d0c3a1 100644 --- a/sovtokenfees/sovtokenfees/test/view_change/test_xfer_nym_during_view_change.py +++ b/sovtokenfees/sovtokenfees/test/view_change/test_xfer_nym_during_view_change.py @@ -1,14 +1,12 @@ import pytest -from sovtokenfees.test.constants import NYM_FEES_ALIAS, XFER_PUBLIC_FEES_ALIAS +from sovtokenfees.test.constants import ( + NYM_FEES_ALIAS, XFER_PUBLIC_FEES_ALIAS, alias_to_txn_type +) from sovtokenfees.test.view_change.helper import scenario_txns_during_view_change -ADDRESSES_NUM = 2 -MINT_UTXOS_NUM = 1 - - @pytest.fixture( scope='module', params=[ @@ -16,7 +14,7 @@ {NYM_FEES_ALIAS: 4, XFER_PUBLIC_FEES_ALIAS: 0}, # no fees for XFER_PUBLIC {NYM_FEES_ALIAS: 0, XFER_PUBLIC_FEES_ALIAS: 8}, # no fees for NYM {NYM_FEES_ALIAS: 4, XFER_PUBLIC_FEES_ALIAS: 8}, # fees for both - ], ids=lambda x: '-'.join(sorted([k for k, v in x.items() if v])) or 'nofees' + ], ids=lambda x: '-'.join(sorted([alias_to_txn_type[k] for k, v in x.items() if v])) or 'nofees' ) def fees(request): return request.param @@ -24,14 +22,22 @@ def fees(request): def test_xfer_nym_during_view_change( looper, + helpers, nodeSetWithIntegratedTokenPlugin, fees_set, - curr_utxo, - send_and_check_transfer_curr_utxo, - send_and_check_nym_with_fees_curr_utxo, + mint_multiple_tokens, + send_and_check_xfer, + send_and_check_nym, + io_addresses ): def send_txns(): - send_and_check_transfer_curr_utxo() - send_and_check_nym_with_fees_curr_utxo() + send_and_check_xfer() + send_and_check_nym() - scenario_txns_during_view_change(looper, nodeSetWithIntegratedTokenPlugin, curr_utxo, send_txns) + scenario_txns_during_view_change( + looper, + helpers, + nodeSetWithIntegratedTokenPlugin, + io_addresses, + send_txns + ) diff --git a/sovtokenfees/sovtokenfees/test/view_change/test_xfer_nym_fees_during_view_change.py b/sovtokenfees/sovtokenfees/test/view_change/test_xfer_nym_fees_during_view_change.py index bd757fe2..f3a7231f 100644 --- a/sovtokenfees/sovtokenfees/test/view_change/test_xfer_nym_fees_during_view_change.py +++ b/sovtokenfees/sovtokenfees/test/view_change/test_xfer_nym_fees_during_view_change.py @@ -1,13 +1,11 @@ import pytest -from sovtokenfees.test.constants import NYM_FEES_ALIAS, XFER_PUBLIC_FEES_ALIAS +from sovtokenfees.test.constants import ( + NYM_FEES_ALIAS, XFER_PUBLIC_FEES_ALIAS, alias_to_txn_type +) from sovtokenfees.test.view_change.helper import scenario_txns_during_view_change -ADDRESSES_NUM = 2 -MINT_UTXOS_NUM = 1 - - @pytest.fixture( scope='module', params=[ @@ -15,7 +13,7 @@ {NYM_FEES_ALIAS: 0, XFER_PUBLIC_FEES_ALIAS: 8}, # no fees for NYM {NYM_FEES_ALIAS: 4, XFER_PUBLIC_FEES_ALIAS: 8}, # fees for both {NYM_FEES_ALIAS: 0, XFER_PUBLIC_FEES_ALIAS: 0}, # no fees - ], ids=lambda x: '-'.join(sorted([k for k, v in x.items() if v])) or 'nofees' + ], ids=lambda x: '-'.join(sorted([alias_to_txn_type[k] for k, v in x.items() if v])) or 'nofees' ) def fees(request): return request.param @@ -23,14 +21,22 @@ def fees(request): def test_xfer_nym_fees_during_view_change( looper, + helpers, nodeSetWithIntegratedTokenPlugin, fees_set, - curr_utxo, - send_and_check_transfer_curr_utxo, - send_and_check_nym_with_fees_curr_utxo, + mint_multiple_tokens, + send_and_check_xfer, + send_and_check_nym, + io_addresses ): def send_txns(): - send_and_check_transfer_curr_utxo() - send_and_check_nym_with_fees_curr_utxo() + send_and_check_xfer() + send_and_check_nym() - scenario_txns_during_view_change(looper, nodeSetWithIntegratedTokenPlugin, curr_utxo, send_txns) + scenario_txns_during_view_change( + looper, + helpers, + nodeSetWithIntegratedTokenPlugin, + io_addresses, + send_txns + ) diff --git a/sovtokenfees/sovtokenfees/three_phase_commit_handling.py b/sovtokenfees/sovtokenfees/three_phase_commit_handling.py index 4cc91b0e..8c2209b3 100644 --- a/sovtokenfees/sovtokenfees/three_phase_commit_handling.py +++ b/sovtokenfees/sovtokenfees/three_phase_commit_handling.py @@ -6,22 +6,22 @@ class ThreePhaseCommitHandler: def __init__(self, master_replica, token_ledger, token_state, - fees_req_handler): + fees_tracker): self.master_replica = master_replica self.token_ledger = token_ledger self.token_state = token_state - self.fees_req_handler = fees_req_handler + self.fees_tracker = fees_tracker # adds a pre_prepare message to be sent that includes the fee transaction info in # the "plugins_fields" member def add_to_pre_prepare(self, pre_prepare): if pre_prepare.ledgerId != TOKEN_LEDGER_ID and \ - self.fees_req_handler.fee_txns_in_current_batch > 0: + self.fees_tracker.fees_in_current_batch > 0: # Make sovtoken ledger and state root part of pre-prepare extra = { f.PLUGIN_FIELDS.nm: { FEES: { - FEE_TXNS_IN_BATCH: self.fees_req_handler.fee_txns_in_current_batch, + FEE_TXNS_IN_BATCH: self.fees_tracker.fees_in_current_batch, f.STATE_ROOT.nm: self.master_replica.stateRootHash( TOKEN_LEDGER_ID), f.TXN_ROOT.nm: self.master_replica.txnRootHash( @@ -78,7 +78,7 @@ def add_to_ordered(self, ordered, pre_prepare): # Checks to make sure the pre_prepare message was properly appended and formatted with fee info def check_recvd_pre_prepare(self, pre_prepare): if pre_prepare.ledgerId != TOKEN_LEDGER_ID: - fee_txn_count = self.fees_req_handler.fee_txns_in_current_batch + fee_txn_count = self.fees_tracker.fees_in_current_batch if fee_txn_count > 0: if not self._has_plugin_fields(pre_prepare): raise Exception('Expected {} in PRE-PREPARE'.format(f.PLUGIN_FIELDS.nm)) @@ -90,26 +90,26 @@ def check_recvd_pre_prepare(self, pre_prepare): if fees.get(FEE_TXNS_IN_BATCH) != fee_txn_count: raise Exception('{} mismatch in PRE-PREPARE ' 'expected {}, found {}'.format( - FEE_TXNS_IN_BATCH, - fee_txn_count, - fees.get(FEE_TXNS_IN_BATCH))) + FEE_TXNS_IN_BATCH, + fee_txn_count, + fees.get(FEE_TXNS_IN_BATCH))) recvd_state_root = self.master_replica._state_root_serializer.deserialize( - fees.get(f.STATE_ROOT.nm, '').encode()) - if recvd_state_root != self.fees_req_handler.token_state.headHash: + fees.get(f.STATE_ROOT.nm, '').encode()) + if recvd_state_root != self.token_state.headHash: raise Exception('{} mismatch in PRE-PREPARE ' 'expected {}, found {}'.format( - f.STATE_ROOT.nm, - self.fees_req_handler.token_state.headHash, - recvd_state_root)) + f.STATE_ROOT.nm, + self.token_state.headHash, + recvd_state_root)) recvd_txn_root = self.token_ledger.strToHash(fees.get(f.TXN_ROOT.nm, '')) - if recvd_txn_root != self.fees_req_handler.token_ledger.uncommittedRootHash: + if recvd_txn_root != self.token_ledger.uncommittedRootHash: raise Exception('{} mismatch in PRE-PREPARE ' 'expected {}, found {}'.format( - f.TXN_ROOT.nm, - self.fees_req_handler.token_ledger.uncommittedRootHash, - recvd_txn_root)) + f.TXN_ROOT.nm, + self.token_ledger.uncommittedRootHash, + recvd_txn_root)) # Makes sure that the "plugins_fields" member is contained in a message. This field is what distinguishes normal # messages from messages that support the sovrin plugin