diff --git a/.jenkinsci/bindings.groovy b/.jenkinsci/bindings.groovy index ae49bc57db..c5f3896f45 100644 --- a/.jenkinsci/bindings.groovy +++ b/.jenkinsci/bindings.groovy @@ -1,6 +1,6 @@ #!/usr/bin/env groovy -def doJavaBindings(os, buildType=Release) { +def doJavaBindings(os, packageName, buildType=Release) { def currentPath = sh(script: "pwd", returnStdout: true).trim() def commit = env.GIT_COMMIT def artifactsPath = sprintf('%1$s/java-bindings-%2$s-%3$s-%4$s-%5$s.zip', @@ -20,12 +20,15 @@ def doJavaBindings(os, buildType=Release) { -Bbuild \ -DCMAKE_BUILD_TYPE=$buildType \ -DSWIG_JAVA=ON \ + -DSWIG_JAVA_PKG="$packageName" \ ${cmakeOptions} """ def parallelismParam = (os == 'windows') ? '' : "-j${params.PARALLELISM}" sh "cmake --build build --target irohajava -- ${parallelismParam}" // TODO 29.05.18 @bakhtin Java tests never finishes on Windows Server 2016. IR-1380 - sh "zip -j $artifactsPath build/bindings/*.java build/bindings/*.dll build/bindings/libirohajava.so" + sh "pushd build/bindings; \ + zip -r $artifactsPath *.dll *.lib *.manifest *.exp libirohajava.so \$(echo ${packageName} | cut -d '.' -f1); \ + popd" if (os == 'windows') { sh "cp $artifactsPath /tmp/${env.GIT_COMMIT}/bindings-artifact" } @@ -66,27 +69,25 @@ def doPythonBindings(os, buildType=Release) { sh "cd build; ctest -R python --output-on-failure" if (os == 'linux') { sh """ - protoc --proto_path=schema \ - --python_out=build/bindings \ - block.proto primitive.proto commands.proto queries.proto responses.proto endpoint.proto + protoc --proto_path=shared_model/schema \ + --python_out=build/bindings shared_model/schema/*.proto """ sh """ - ${env.PBVersion} -m grpc_tools.protoc --proto_path=schema --python_out=build/bindings \ - --grpc_python_out=build/bindings endpoint.proto yac.proto ordering.proto loader.proto + ${env.PBVersion} -m grpc_tools.protoc --proto_path=shared_model/schema --python_out=build/bindings \ + --grpc_python_out=build/bindings shared_model/schema/endpoint.proto """ } else if (os == 'windows') { sh """ - protoc --proto_path=schema \ + protoc --proto_path=shared_model/schema \ --proto_path=/c/Users/Administrator/Downloads/vcpkg-master/vcpkg-master/buildtrees/protobuf/src/protobuf-3.5.1-win32/include \ - --python_out=build/bindings \ - block.proto primitive.proto commands.proto queries.proto responses.proto endpoint.proto + --python_out=build/bindings shared_model/schema/*.proto """ sh """ ${env.PBVersion} -m grpc_tools.protoc \ --proto_path=/c/Users/Administrator/Downloads/vcpkg-master/vcpkg-master/buildtrees/protobuf/src/protobuf-3.5.1-win32/include \ - --proto_path=schema --python_out=build/bindings --grpc_python_out=build/bindings \ - endpoint.proto yac.proto ordering.proto loader.proto + --proto_path=shared_model/schema --python_out=build/bindings --grpc_python_out=build/bindings \ + shared_model/schema/endpoint.proto """ } sh """ diff --git a/.jenkinsci/debug-build.groovy b/.jenkinsci/debug-build.groovy index 47087c07eb..b210b893dd 100644 --- a/.jenkinsci/debug-build.groovy +++ b/.jenkinsci/debug-build.groovy @@ -17,14 +17,16 @@ def doDebugBuild(coverageEnabled=false) { if (env.NODE_NAME.contains('arm7')) { parallelism = 1 } + sh "docker network create ${env.IROHA_NETWORK}" def iC = dPullOrBuild.dockerPullOrUpdate("${platform}-develop-build", "${env.GIT_RAW_BASE_URL}/${env.GIT_COMMIT}/docker/develop/Dockerfile", "${env.GIT_RAW_BASE_URL}/${previousCommit}/docker/develop/Dockerfile", "${env.GIT_RAW_BASE_URL}/develop/docker/develop/Dockerfile", ['PARALLELISM': parallelism]) - - if (GIT_LOCAL_BRANCH == 'develop' && manifest.manifestSupportEnabled()) { + // push Docker image in case the current branch is develop, + // or it is a commit into PR which base branch is develop (usually develop -> master) + if ((GIT_LOCAL_BRANCH == 'develop' || CHANGE_BRANCH_LOCAL == 'develop') && manifest.manifestSupportEnabled()) { manifest.manifestCreate("${DOCKER_REGISTRY_BASENAME}:develop-build", ["${DOCKER_REGISTRY_BASENAME}:x86_64-develop-build", "${DOCKER_REGISTRY_BASENAME}:armv7l-develop-build", @@ -42,6 +44,7 @@ def doDebugBuild(coverageEnabled=false) { manifest.manifestPush("${DOCKER_REGISTRY_BASENAME}:develop-build", login, password) } } + docker.image('postgres:9.5').withRun("" + " -e POSTGRES_USER=${env.IROHA_POSTGRES_USER}" + " -e POSTGRES_PASSWORD=${env.IROHA_POSTGRES_PASSWORD}" diff --git a/.jenkinsci/docker-pull-or-build.groovy b/.jenkinsci/docker-pull-or-build.groovy index d699ac40bb..26660c33bc 100644 --- a/.jenkinsci/docker-pull-or-build.groovy +++ b/.jenkinsci/docker-pull-or-build.groovy @@ -38,7 +38,7 @@ def dockerPullOrUpdate(imageName, currentDockerfileURL, previousDockerfileURL, r def testExitCode = sh(script: "docker pull ${DOCKER_REGISTRY_BASENAME}:${imageName}", returnStatus: true) if (testExitCode != 0) { // image does not (yet) exist on Dockerhub. Build it - iC = docker.build("${DOCKER_REGISTRY_BASENAME}:${commit}-${BUILD_NUMBER}", "$buildOptions --no-cache -f /tmp/${env.GIT_COMMIT}/f1 /tmp/${env.GIT_COMMIT}") + iC = docker.build("${DOCKER_REGISTRY_BASENAME}:${commit}-${BUILD_NUMBER}", "$buildOptions --no-cache -f /tmp/${env.GIT_COMMIT}/f1 /tmp/${env.GIT_COMMIT}") } else { // no difference found compared to both previous and reference Dockerfile @@ -46,7 +46,7 @@ def dockerPullOrUpdate(imageName, currentDockerfileURL, previousDockerfileURL, r } } } - if (GIT_LOCAL_BRANCH ==~ /develop|master/) { + if (GIT_LOCAL_BRANCH ==~ /develop|master/ || CHANGE_BRANCH_LOCAL == 'develop') { docker.withRegistry('https://registry.hub.docker.com', 'docker-hub-credentials') { iC.push(imageName) } diff --git a/.jenkinsci/doxygen.groovy b/.jenkinsci/doxygen.groovy index 7389b55c07..65d330f013 100644 --- a/.jenkinsci/doxygen.groovy +++ b/.jenkinsci/doxygen.groovy @@ -1,11 +1,20 @@ #!/usr/bin/env groovy def doDoxygen() { - - sh """ - doxygen Doxyfile - #rsync docs/doxygen - """ + if (env.GIT_LOCAL_BRANCH in ["master","develop"] || env.CHANGE_BRANCH_LOCAL == 'develop') { + def branch = env.CHANGE_BRANCH_LOCAL == 'develop' ? env.CHANGE_BRANCH_LOCAL : env.GIT_LOCAL_BRANCH + sh "doxygen Doxyfile" + sshagent(['jenkins-artifact']) { + sh "ssh-agent" + sh """ + rsync \ + -e 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' \ + -rzcv --delete \ + docs/doxygen/html/* \ + ubuntu@docs.iroha.tech:/var/nexus-efs/doxygen/${branch}/ + """ + } + } } return this diff --git a/.jenkinsci/release-build.groovy b/.jenkinsci/release-build.groovy index d24d6cfb31..e9675dba54 100644 --- a/.jenkinsci/release-build.groovy +++ b/.jenkinsci/release-build.groovy @@ -56,7 +56,10 @@ def doReleaseBuild() { sh "mv /tmp/${GIT_COMMIT}-${BUILD_NUMBER}/iroha.deb /tmp/${env.GIT_COMMIT}" sh "chmod +x /tmp/${env.GIT_COMMIT}/entrypoint.sh" iCRelease = docker.build("${DOCKER_REGISTRY_BASENAME}:${GIT_COMMIT}-${BUILD_NUMBER}-release", "--no-cache -f /tmp/${env.GIT_COMMIT}/Dockerfile /tmp/${env.GIT_COMMIT}") - if (env.GIT_LOCAL_BRANCH == 'develop') { + + // push Docker image in case the current branch is develop, + // or it is a commit into PR which base branch is develop (usually develop -> master) + if (GIT_LOCAL_BRANCH == 'develop' || CHANGE_BRANCH_LOCAL == 'develop') { iCRelease.push("${platform}-develop") if (manifest.manifestSupportEnabled()) { manifest.manifestCreate("${DOCKER_REGISTRY_BASENAME}:develop", @@ -77,7 +80,7 @@ def doReleaseBuild() { } } } - else if (env.GIT_LOCAL_BRANCH == 'master') { + else if (GIT_LOCAL_BRANCH == 'master') { iCRelease.push("${platform}-latest") if (manifest.manifestSupportEnabled()) { manifest.manifestCreate("${DOCKER_REGISTRY_BASENAME}:latest", @@ -98,6 +101,7 @@ def doReleaseBuild() { } } } + sh "docker rmi ${iCRelease.id}" } return this diff --git a/CMakeLists.txt b/CMakeLists.txt index d3ae2d5202..95f04dd5d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + cmake_minimum_required(VERSION 3.5.1) find_program(CCACHE_PROGRAM ccache) @@ -9,8 +12,8 @@ endif() PROJECT(iroha C CXX) SET(CMAKE_POSITION_INDEPENDENT_CODE TRUE) -SET(CMAKE_CXX_FLAGS "-std=c++1y -Wall") -SET(CMAKE_CXX_FLAGS_RELEASE "-O3") +SET(CMAKE_CXX_FLAGS "-std=c++14 -Wall -fdiagnostics-color=always") +SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wno-error=deprecated-declarations") SET(CMAKE_CXX_FLAGS_DEBUG "-g -Wextra -Wno-unused-parameter -Wno-deprecated-declarations -O0") SET(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1) SET(CMAKE_INSTALL_RPATH "../lib") @@ -100,16 +103,22 @@ message(STATUS "-DSUPPORT_PYTHON2=${SUPPORT_PYTHON2}") message(STATUS "-DSWIG_CSHARP=${SWIG_CSHARP}") message(STATUS "-DSWIG_NODE=${SWIG_NODE}") -SET(IROHA_SCHEMA_DIR "${PROJECT_SOURCE_DIR}/schema") +set(IROHA_SCHEMA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/schema") +set(SM_SCHEMA_DIR "${PROJECT_SOURCE_DIR}/shared_model/schema") +set(SCHEMA_OUT_DIR ${CMAKE_BINARY_DIR}/schema) include_directories( ${PROJECT_SOURCE_DIR}/irohad ${PROJECT_SOURCE_DIR}/shared_model ${PROJECT_SOURCE_DIR}/libs - ${IROHA_SCHEMA_DIR} ) SET(IROHA_ROOT_PROJECT ON) +# Boost uses RTTI to perform some actions (such as type erasure). +# This is slow. This flag forces boost to use other methods, +# which are generally faster +add_definitions(-DBOOST_NO_RTTI) + include(FeatureSummary) include(cmake/functions.cmake) include(cmake/dependencies.cmake) @@ -131,9 +140,7 @@ if(TESTING) endif() if (FUZZING) - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - add_subdirectory(fuzz) - else() - message(Fuzzing with compilers other than clang does not supported yet) + if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + message(Fuzzing with compilers other than clang is not supported yet) endif() endif() diff --git a/Doxyfile b/Doxyfile index 03d7940060..bfa2075c9e 100644 --- a/Doxyfile +++ b/Doxyfile @@ -51,7 +51,7 @@ PROJECT_BRIEF = "Iroha - A simple, decentralized ledger http://iroha.te # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = ./docs/ +PROJECT_LOGO = ./docs/image_assets/iroha_logo_doxygen.png # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is diff --git a/Jenkinsfile b/Jenkinsfile index 2fd2412580..61e7001f11 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,6 +9,7 @@ properties([parameters([ choice(choices: 'Debug\nRelease', description: 'Iroha build type', name: 'build_type'), booleanParam(defaultValue: false, description: 'Build Java bindings', name: 'JavaBindings'), choice(choices: 'Release\nDebug', description: 'Java bindings build type', name: 'JBBuildType'), + string(defaultValue: 'jp.co.soramitsu.iroha', description: 'Java bindings package name', name: 'JBPackageName'), booleanParam(defaultValue: false, description: 'Build Python bindings', name: 'PythonBindings'), choice(choices: 'Release\nDebug', description: 'Python bindings build type', name: 'PBBuildType'), choice(choices: 'python3\npython2', description: 'Python bindings version', name: 'PBVersion'), @@ -16,7 +17,7 @@ properties([parameters([ choice(choices: '26\n25\n24\n23\n22\n21\n20\n19\n18\n17\n16\n15\n14', description: 'Android Bindings ABI Version', name: 'ABABIVersion'), choice(choices: 'Release\nDebug', description: 'Android bindings build type', name: 'ABBuildType'), choice(choices: 'arm64-v8a\narmeabi-v7a\narmeabi\nx86_64\nx86', description: 'Android bindings platform', name: 'ABPlatform'), - booleanParam(defaultValue: false, description: 'Build docs', name: 'Doxygen'), + booleanParam(defaultValue: true, description: 'Build docs', name: 'Doxygen'), string(defaultValue: '4', description: 'How much parallelism should we exploit. "4" is optimal for machines with modest amount of memory and at least 4 cores', name: 'PARALLELISM')])]) @@ -34,6 +35,7 @@ pipeline { IROHA_POSTGRES_USER = "pguser${GIT_COMMIT}" IROHA_POSTGRES_PASSWORD = "${GIT_COMMIT}" IROHA_POSTGRES_PORT = 5432 + CHANGE_BRANCH_LOCAL = '' } options { @@ -47,7 +49,13 @@ pipeline { agent { label 'master' } steps { script { - if (GIT_LOCAL_BRANCH != "develop") { + // need this for develop->master PR cases + // CHANGE_BRANCH is not defined if this is a branch build + try { + CHANGE_BRANCH_LOCAL = env.CHANGE_BRANCH + } + catch(MissingPropertyException e) { } + if (GIT_LOCAL_BRANCH != "develop" && CHANGE_BRANCH_LOCAL != "develop") { def builds = load ".jenkinsci/cancel-builds-same-job.groovy" builds.cancelSameJobBuilds() } @@ -363,23 +371,32 @@ pipeline { stage('Build docs') { when { beforeAgent true - allOf { - expression { return params.Doxygen } - expression { GIT_LOCAL_BRANCH ==~ /(master|develop)/ } - } + expression { return params.Doxygen } } // build docs on any vacant node. Prefer `x86_64` over // others as nodes are more powerful - agent { label 'x86_64 || arm' } + agent { label 'x86_64' } steps { script { def doxygen = load ".jenkinsci/doxygen.groovy" - docker.image("${env.DOCKER_IMAGE}").inside { - def scmVars = checkout scm + def dPullOrBuild = load ".jenkinsci/docker-pull-or-build.groovy" + def platform = sh(script: 'uname -m', returnStdout: true).trim() + def iC = dPullOrBuild.dockerPullOrUpdate( + "$platform-develop-build", + "${env.GIT_RAW_BASE_URL}/${env.GIT_COMMIT}/docker/develop/Dockerfile", + "${env.GIT_RAW_BASE_URL}/${env.GIT_PREVIOUS_COMMIT}/docker/develop/Dockerfile", + "${env.GIT_RAW_BASE_URL}/develop/docker/develop/Dockerfile", + ['PARALLELISM': params.PARALLELISM]) + iC.inside() { doxygen.doDoxygen() } } } + post { + cleanup { + cleanWs() + } + } } stage('Build bindings') { when { @@ -410,7 +427,7 @@ pipeline { ['PARALLELISM': params.PARALLELISM]) if (params.JavaBindings) { iC.inside("-v /tmp/${env.GIT_COMMIT}/bindings-artifact:/tmp/bindings-artifact") { - bindings.doJavaBindings('linux', params.JBBuildType) + bindings.doJavaBindings('linux', params.JBPackageName, params.JBBuildType) } } if (params.PythonBindings) { @@ -469,7 +486,7 @@ pipeline { script { def bindings = load ".jenkinsci/bindings.groovy" if (params.JavaBindings) { - bindings.doJavaBindings('windows', params.JBBuildType) + bindings.doJavaBindings('windows', params.JBPackageName, params.JBBuildType) } if (params.PythonBindings) { bindings.doPythonBindings('windows', params.PBBuildType) diff --git a/README.md b/README.md index 43e94c3697..709fd1a46a 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,8 @@ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/960/badge)](https://bestpractices.coreinfrastructure.org/projects/960) -[![codecov](https://codecov.io/gh/hyperledger/iroha/branch/master/graph/badge.svg)](https://codecov.io/gh/hyperledger/iroha) [![Snap Status](https://build.snapcraft.io/badge/hyperledger/iroha.svg)](https://build.snapcraft.io/user/hyperledger/iroha) -[![Build Status](https://jenkins.soramitsu.co.jp/buildStatus/icon?job=iroha/iroha-hyperledger/master)](https://jenkins.soramitsu.co.jp/job/iroha/iroha-hyperledger/master) +[![Build Status](https://jenkins.soramitsu.co.jp/buildStatus/icon?job=iroha/iroha-hyperledger/master)](https://jenkins.soramitsu.co.jp/job/iroha/job/iroha-hyperledger/job/master/) [![Throughput Graph](https://graphs.waffle.io/hyperledger/iroha/throughput.svg)](https://waffle.io/hyperledger/iroha/metrics/throughput) Blockchain platform Hyperledger Iroha is designed for simple creation and management of assets. This is a distributed ledger of transactions. @@ -25,7 +24,7 @@ Among the non-functional requirements can be noted a high degree of network faul ## Documentation -Our documentation is hosted at ReadTheDocs service here: [http://iroha.readthedocs.io](http://iroha.readthedocs.io/en/). +Our documentation is hosted at ReadTheDocs service here: [http://iroha.readthedocs.io](http://iroha.readthedocs.io). We have documentation in several languages available and you are welcome to contribute on [POEditor website](https://poeditor.com/join/project/SFpZw7o33o)! ### How to explore Iroha really fast? @@ -42,7 +41,7 @@ Yes, in [Java](http://iroha.readthedocs.io/en/latest/guides/libraries/java.html) ### Are there any example applications? -[Android point app](https://github.com/soramitsu/iroha-demo-android) and [JavaScript wallet](https://github.com/soramitsu/iroha-wallet-js). +[Android point app](https://github.com/hyperledger/iroha-android/tree/master/iroha-android-sample) and [JavaScript wallet](https://github.com/soramitsu/iroha-wallet-js). ## Need help? diff --git a/clean.sh b/clean.sh index 59e214debe..59cff7cc96 100755 --- a/clean.sh +++ b/clean.sh @@ -1,5 +1,4 @@ #!/bin/bash -rm schema/*.{cc,h} rm -rf external rm -rf build rm -rf cmake-build-debug diff --git a/cmake/Modules/Finded25519.cmake b/cmake/Modules/Finded25519.cmake index 57f5b7b054..e82fc30aca 100644 --- a/cmake/Modules/Finded25519.cmake +++ b/cmake/Modules/Finded25519.cmake @@ -12,24 +12,10 @@ find_package_handle_standard_args(ed25519 DEFAULT_MSG ) set(URL https://github.com/hyperledger/iroha-ed25519) -if (MSVC) - # trunk/1.2 with windows-specific changes - set(VERSION 31bb9b50e01b21ea2c21d33929e20934be4665b4) -else() - set(VERSION e7188b8393dbe5ac54378610d53630bd4a180038) -endif() +set(VERSION f42953c631fae93011612f6b1ee33f1f88c3f8af) set_target_description(ed25519 "Digital signature algorithm" ${URL} ${VERSION}) if (NOT ed25519_FOUND) - if (NOT WIN32) - find_package(Git REQUIRED) - set(PATCH_RANDOM ${GIT_EXECUTABLE} apply ${PROJECT_SOURCE_DIR}) - if (NOT IROHA_ROOT_PROJECT) - set(PATCH_RANDOM ${PATCH_RANDOM}/..) - endif () - set(PATCH_RANDOM ${PATCH_RANDOM}/patch/close.patch || true) - endif () - externalproject_add(hyperledger_ed25519 GIT_REPOSITORY ${URL} GIT_TAG ${VERSION} diff --git a/cmake/Modules/Findgrpc.cmake b/cmake/Modules/Findgrpc.cmake index 2dde985c0f..a9fd9923e3 100644 --- a/cmake/Modules/Findgrpc.cmake +++ b/cmake/Modules/Findgrpc.cmake @@ -32,6 +32,12 @@ set(URL https://github.com/grpc/grpc) set(VERSION bd44e485f69d70ca4095cea92decd98de3892aa6) # Release 1.11.0 set_target_description(grpc "Remote Procedure Call library" ${URL} ${VERSION}) +if (NOT protobuf_FOUND) + set(PROTO_DEP -DgRPC_PROTOBUF_PACKAGE_TYPE=CONFIG -DProtobuf_DIR=${EP_PREFIX}/src/google_protobuf-build/lib/cmake/protobuf) +else () + set(PROTO_DEP -DgRPC_PROTOBUF_PACKAGE_TYPE=MODULE) +endif () + if (NOT grpc_FOUND) find_package(Git REQUIRED) externalproject_add(grpc_grpc @@ -39,8 +45,7 @@ if (NOT grpc_FOUND) GIT_TAG ${VERSION} CMAKE_ARGS -DgRPC_PROTOBUF_PROVIDER=package - -DgRPC_PROTOBUF_PACKAGE_TYPE=CONFIG - -DProtobuf_DIR=${EP_PREFIX}/src/google_protobuf-build/lib/cmake/protobuf + ${PROTO_DEP} -DgRPC_ZLIB_PROVIDER=package -DBUILD_SHARED_LIBS=ON BUILD_BYPRODUCTS diff --git a/cmake/Modules/Findgtest.cmake b/cmake/Modules/Findgtest.cmake index 22e6cd6b63..daa6037cc1 100644 --- a/cmake/Modules/Findgtest.cmake +++ b/cmake/Modules/Findgtest.cmake @@ -1,5 +1,7 @@ -add_library(gtest UNKNOWN IMPORTED) -add_library(gmock UNKNOWN IMPORTED) +add_library(gtest::gtest UNKNOWN IMPORTED) +add_library(gtest::main UNKNOWN IMPORTED) +add_library(gmock::gmock UNKNOWN IMPORTED) +add_library(gmock::main UNKNOWN IMPORTED) find_path(gtest_INCLUDE_DIR gtest/gtest.h) mark_as_advanced(gtest_INCLUDE_DIR) @@ -30,8 +32,8 @@ find_package_handle_standard_args(gtest DEFAULT_MSG set(URL https://github.com/google/googletest) set(VERSION ec44c6c1675c25b9827aacd08c02433cccde7780) -set_target_description(gtest "Unit testing library" ${URL} ${VERSION}) -set_target_description(gmock "Mocking library" ${URL} ${VERSION}) +set_target_description(gtest::gtest "Unit testing library" ${URL} ${VERSION}) +set_target_description(gmock::gmock "Mocking library" ${URL} ${VERSION}) if (NOT gtest_FOUND) ExternalProject_Add(google_test @@ -62,18 +64,30 @@ if (NOT gtest_FOUND) file(MAKE_DIRECTORY ${gtest_INCLUDE_DIR}) file(MAKE_DIRECTORY ${gmock_INCLUDE_DIR}) - add_dependencies(gtest google_test) - add_dependencies(gmock google_test) + add_dependencies(gtest::gtest google_test) + add_dependencies(gtest::main google_test) + add_dependencies(gmock::gmock google_test) + add_dependencies(gmock::main google_test) endif () -set_target_properties(gtest PROPERTIES +set_target_properties(gtest::gtest PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${gtest_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "pthread;${gtest_MAIN_LIBRARY}" + INTERFACE_LINK_LIBRARIES Threads::Threads IMPORTED_LOCATION ${gtest_LIBRARY} ) +set_target_properties(gtest::main PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${gtest_INCLUDE_DIR} + INTERFACE_LINK_LIBRARIES gtest::gtest + IMPORTED_LOCATION ${gtest_MAIN_LIBRARY} + ) -set_target_properties(gmock PROPERTIES +set_target_properties(gmock::gmock PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${gmock_INCLUDE_DIR} - INTERFACE_LINK_LIBRARIES "pthread;${gmock_MAIN_LIBRARY}" + INTERFACE_LINK_LIBRARIES Threads::Threads IMPORTED_LOCATION ${gmock_LIBRARY} ) +set_target_properties(gmock::main PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${gmock_INCLUDE_DIR} + INTERFACE_LINK_LIBRARIES gmock::gmock + IMPORTED_LOCATION ${gmock_MAIN_LIBRARY} + ) diff --git a/cmake/Modules/Findpq.cmake b/cmake/Modules/Findpq.cmake index 1457ca2357..2a018af746 100644 --- a/cmake/Modules/Findpq.cmake +++ b/cmake/Modules/Findpq.cmake @@ -20,12 +20,10 @@ find_package_handle_standard_args(pq DEFAULT_MSG pg_config_EXECUTABLE ) - set(URL https://git.postgresql.org/git/postgresql.git) set(VERSION 029386ccbddd0a33d481b94e511f5219b03e6636) set_target_description(pq "C postgres client library" ${URL} ${VERSION}) - if (NOT pq_FOUND) externalproject_add(postgres_postgres GIT_REPOSITORY ${URL} @@ -52,6 +50,9 @@ endif () get_filename_component(pq_LIBRARY_DIR ${pq_LIBRARY} DIRECTORY) mark_as_advanced(pq_LIBRARY_DIR) +get_filename_component(pg_config_EXECUTABLE_DIR ${pg_config_EXECUTABLE} DIRECTORY) +mark_as_advanced(pg_config_EXECUTABLE_DIR) + set_target_properties(pq PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${pq_INCLUDE_DIR};${postgres_INCLUDE_DIR}" IMPORTED_LOCATION ${pq_LIBRARY} diff --git a/cmake/Modules/Findpqxx.cmake b/cmake/Modules/Findpqxx.cmake deleted file mode 100644 index 66d61df98b..0000000000 --- a/cmake/Modules/Findpqxx.cmake +++ /dev/null @@ -1,46 +0,0 @@ -add_library(pqxx UNKNOWN IMPORTED) - -find_path(pqxx_INCLUDE_DIR pqxx/pqxx) -mark_as_advanced(pqxx_INCLUDE_DIR) - -find_library(pqxx_LIBRARY pqxx) -mark_as_advanced(pqxx_LIBRARY) - -find_package_handle_standard_args(pqxx DEFAULT_MSG - pqxx_INCLUDE_DIR - pqxx_LIBRARY - ) - -set(URL https://github.com/jtv/libpqxx.git) -set(VERSION c4d4f4b4e6ecaf85de770a030f3a1cdc1b3a79ae) # 6.1.0 -set_target_description(pqxx "C++ bindings for postgres client library" ${URL} ${VERSION}) - -if (NOT pqxx_FOUND) - externalproject_add(jtv_libpqxx - GIT_REPOSITORY ${URL} - GIT_TAG ${VERSION} - CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env CXXFLAGS=${CMAKE_CXX_FLAGS} CPPFLAGS=-I${postgres_INCLUDE_DIR} PG_CONFIG=${pg_config_EXECUTABLE} ./configure --disable-documentation --with-postgres-include=${pq_INCLUDE_DIR} --with-postgres-lib=${pq_INCLUDE_DIR} - BUILD_IN_SOURCE 1 - BUILD_COMMAND ${MAKE} - BUILD_BYPRODUCTS ${EP_PREFIX}/src/jtv_libpqxx/src/.libs/libpqxx.a - ${EP_PREFIX}/src/jtv_libpqxx/src/libpqxx.la - ${EP_PREFIX}/src/jtv_libpqxx/src/.libs/libpqxx.la - ${EP_PREFIX}/src/jtv_libpqxx/src/.libs/libpqxx.lai - INSTALL_COMMAND "" # remove install step - TEST_COMMAND "" # remove test step - UPDATE_COMMAND "" # remove update step - ) - externalproject_get_property(jtv_libpqxx source_dir) - set(pqxx_INCLUDE_DIR ${source_dir}/include) - set(pqxx_LIBRARY ${source_dir}/src/.libs/libpqxx.a) - file(MAKE_DIRECTORY ${pqxx_INCLUDE_DIR}) - - add_dependencies(jtv_libpqxx pq) - add_dependencies(pqxx jtv_libpqxx) -endif () - -set_target_properties(pqxx PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${pqxx_INCLUDE_DIR} - IMPORTED_LOCATION ${pqxx_LIBRARY} - INTERFACE_LINK_LIBRARIES "pq" - ) diff --git a/cmake/Modules/Findprotobuf-mutator.cmake b/cmake/Modules/Findprotobuf-mutator.cmake new file mode 100644 index 0000000000..6650d821ef --- /dev/null +++ b/cmake/Modules/Findprotobuf-mutator.cmake @@ -0,0 +1,36 @@ +add_library(protobuf-mutator UNKNOWN IMPORTED) + +set(URL https://github.com/google/libprotobuf-mutator.git) +set(VERSION c9a1e56750a4eef6ffca95f41f79f06979056e01) +set(protomutator_LIB ${CMAKE_STATIC_LIBRARY_PREFIX}protobuf-mutator${CMAKE_STATIC_LIBRARY_SUFFIX}) +set(libfuzzer_LIB ${CMAKE_STATIC_LIBRARY_PREFIX}protobuf-mutator-libfuzzer${CMAKE_STATIC_LIBRARY_SUFFIX}) + +externalproject_add(google_protobuf-mutator + GIT_REPOSITORY ${URL} + GIT_TAG ${VERSION} + PATCH_COMMAND patch -p1 < ${PROJECT_SOURCE_DIR}/patch/libprotobuf-mutator.patch || true + CMAKE_ARGS -G${CMAKE_GENERATOR} -DTESTING=OFF + BUILD_BYPRODUCTS ${EP_PREFIX}/src/google_protobuf-mutator-build/src/${protomutator_LIB} + ${EP_PREFIX}/src/google_protobuf-mutator-build/src/libfuzzer/${libfuzzer_LIB} + INSTALL_COMMAND "" + TEST_COMMAND "" # remove test step + UPDATE_COMMAND "" # remove update step + ) +externalproject_get_property(google_protobuf-mutator source_dir binary_dir) +set(protobuf_mutator_INCLUDE_DIR ${source_dir}/src) +set(protobuf_mutator_LIBRARY ${binary_dir}/src/${protomutator_LIB}) +file(MAKE_DIRECTORY ${protobuf_mutator_INCLUDE_DIR}) +include_directories(${source_dir}) +link_directories(${binary_dir}) + +add_dependencies(protobuf-mutator google_protobuf-mutator) + +set_target_properties(protobuf-mutator PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${protobuf_mutator_INCLUDE_DIR} + INTERFACE_LINK_LIBRARIES ${protobuf_mutator_LIBRARY} + IMPORTED_LOCATION ${binary_dir}/src/libfuzzer/${libfuzzer_LIB} + ) + +if(ENABLE_LIBS_PACKAGING) + add_install_step_for_lib(${protobuf_mutator_LIBRARY}) +endif() diff --git a/cmake/Modules/Findsoci.cmake b/cmake/Modules/Findsoci.cmake new file mode 100644 index 0000000000..4b5c891c51 --- /dev/null +++ b/cmake/Modules/Findsoci.cmake @@ -0,0 +1,91 @@ +set(_SOCI_REQUIRED_VARS SOCI_INCLUDE_DIR SOCI_LIBRARY SOCI_postgresql_PLUGIN) + +add_library(SOCI::core UNKNOWN IMPORTED) +add_library(SOCI::postgresql UNKNOWN IMPORTED) + +find_path( + SOCI_INCLUDE_DIR soci.h + PATH_SUFFIXES "" "soci" + DOC "Soci (http://soci.sourceforge.net) include directory") +mark_as_advanced(SOCI_INCLUDE_DIR) +get_filename_component(_SOCI_INCLUDE_PARENT_DIR ${SOCI_INCLUDE_DIR} DIRECTORY) +set(SOCI_INCLUDE_DIRS ${SOCI_INCLUDE_DIR} ${_SOCI_INCLUDE_PARENT_DIR}) +mark_as_advanced(SOCI_INCLUDE_DIRS) + +find_library( + SOCI_LIBRARY + NAMES soci_core + HINTS ${SOCI_INCLUDE_DIR}/.. + PATH_SUFFIXES lib${LIB_SUFFIX}) +mark_as_advanced(SOCI_LIBRARY) + +find_library( + SOCI_postgresql_PLUGIN + NAMES soci_postgresql + HINTS ${SOCI_INCLUDE_DIR}/.. + PATH_SUFFIXES lib${LIB_SUFFIX}) +mark_as_advanced(SOCI_postgresql_PLUGIN) + +get_filename_component(SOCI_LIBRARY_DIR ${SOCI_LIBRARY} PATH) +mark_as_advanced(SOCI_LIBRARY_DIR) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(soci DEFAULT_MSG ${_SOCI_REQUIRED_VARS}) + +set(URL https://github.com/SOCI/soci) +set(VERSION 111b50af8c3876ea392367640b4bd83b4f903ab8) # 3.2.3 +set_target_description(soci "The C++ Database Access Library" ${URL} ${VERSION}) + +if (NOT soci_FOUND) + externalproject_add(soci_soci + GIT_REPOSITORY ${URL} + GIT_TAG ${VERSION} + CONFIGURE_COMMAND ${CMAKE_COMMAND} + -G${CMAKE_GENERATOR} + -H${EP_PREFIX}/src/soci_soci/src + -B${EP_PREFIX}/src/soci_soci-build + -DCMAKE_INCLUDE_PATH=${pq_INCLUDE_DIR} + -DCMAKE_LIBRARY_PATH=${pq_INCLUDE_DIR} + -DCMAKE_PROGRAM_PATH=${pg_config_EXECUTABLE_DIR} + -DCMAKE_CXX_FLAGS=-I${postgres_INCLUDE_DIR} + -DCMAKE_INSTALL_PREFIX=${EP_PREFIX} + -DWITH_BOOST=ON + -DWITH_DB2=OFF + -DWITH_FIREBIRD=OFF + -DWITH_MYSQL=OFF + -DWITH_ODBC=OFF + -DWITH_ORACLE=OFF + -DWITH_POSTGRESQL=ON + -DWITH_SQLITE3=OFF + BUILD_BYPRODUCTS ${EP_PREFIX}/src/soci_soci-build/lib/${CMAKE_STATIC_LIBRARY_PREFIX}soci_core${CMAKE_STATIC_LIBRARY_SUFFIX} + ${EP_PREFIX}/src/soci_soci-build/lib/${CMAKE_STATIC_LIBRARY_PREFIX}soci_postgresql${CMAKE_STATIC_LIBRARY_SUFFIX} + TEST_COMMAND "" # remove test step + UPDATE_COMMAND "" # remove update step + ) + externalproject_get_property(soci_soci binary_dir) + set(SOCI_INCLUDE_DIRS ${EP_PREFIX}/include ${EP_PREFIX}/include/soci) + set(SOCI_LIBRARY ${binary_dir}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}soci_core${CMAKE_STATIC_LIBRARY_SUFFIX}) + set(SOCI_postgresql_PLUGIN ${binary_dir}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}soci_postgresql${CMAKE_STATIC_LIBRARY_SUFFIX}) + file(MAKE_DIRECTORY ${EP_PREFIX}/include/soci) + + add_dependencies(soci_soci pq) + add_dependencies(SOCI::core soci_soci) + add_dependencies(SOCI::postgresql soci_soci) +endif () + +set_target_properties(SOCI::core PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${SOCI_INCLUDE_DIRS}" + IMPORTED_LOCATION "${SOCI_LIBRARY}" + INTERFACE_LINK_LIBRARIES dl + ) + +set_target_properties(SOCI::postgresql PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${SOCI_INCLUDE_DIRS}" + IMPORTED_LOCATION "${SOCI_postgresql_PLUGIN}" + INTERFACE_LINK_LIBRARIES pq + ) + +if(ENABLE_LIBS_PACKAGING) + add_install_step_for_lib(${SOCI_LIBRARY}) + add_install_step_for_lib(${SOCI_postgresql_PLUGIN}) +endif() diff --git a/cmake/Modules/Findtbb.cmake b/cmake/Modules/Findtbb.cmake index e5ffba9380..2a55b1aa52 100644 --- a/cmake/Modules/Findtbb.cmake +++ b/cmake/Modules/Findtbb.cmake @@ -48,3 +48,10 @@ set_target_properties(tbb PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${tbb_INCLUDE_DIR} IMPORTED_LOCATION ${tbb_LIBRARY} ) + +if (NOT TBB_USE_GLIBCXX_VERSION AND UNIX AND NOT APPLE AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + # https://www.threadingbuildingblocks.org/docs/help/reference/appendices/known_issues/linux_os.html + string(REPLACE "." "0" TBB_USE_GLIBCXX_VERSION ${CMAKE_CXX_COMPILER_VERSION}) + set_target_properties(tbb PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "TBB_USE_GLIBCXX_VERSION=${TBB_USE_GLIBCXX_VERSION}") +endif() diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake index 69ac283fe9..cc869ff288 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -44,10 +44,10 @@ find_package(rapidjson) ########################## find_package(pq) -##########################a -# pqxx # ########################## -find_package(pqxx) +# SOCI # +########################## +find_package(soci) ################################ # gflags # @@ -71,6 +71,7 @@ find_package(Boost 1.65.0 REQUIRED COMPONENTS filesystem system + thread ) add_library(boost INTERFACE IMPORTED) set_target_properties(boost PROPERTIES diff --git a/cmake/functions.cmake b/cmake/functions.cmake index cc2e442bce..93f93523a4 100644 --- a/cmake/functions.cmake +++ b/cmake/functions.cmake @@ -25,7 +25,7 @@ function(addtest test_name SOURCES) set(test_xml_output --gtest_output=xml:${REPORT_DIR}/xunit-${test_name}.xml) endif () add_executable(${test_name} ${SOURCES}) - target_link_libraries(${test_name} gtest gmock) + target_link_libraries(${test_name} gtest::main gmock::main) target_include_directories(${test_name} PUBLIC ${PROJECT_SOURCE_DIR}/test) # fetch directory after test in source dir call @@ -71,28 +71,30 @@ function(compile_proto_to_cpp PROTO) set(GEN_ARGS ${protobuf_INCLUDE_DIR}) endif() add_custom_command( - OUTPUT ${IROHA_SCHEMA_DIR}/${GEN_PB_HEADER} ${IROHA_SCHEMA_DIR}/${GEN_PB} + OUTPUT ${SCHEMA_OUT_DIR}/${GEN_PB_HEADER} ${SCHEMA_OUT_DIR}/${GEN_PB} COMMAND ${GEN_COMMAND} - ARGS -I${GEN_ARGS} -I. --cpp_out=${IROHA_SCHEMA_DIR} ${PROTO} - DEPENDS protoc ${IROHA_SCHEMA_DIR}/${PROTO} - WORKING_DIRECTORY ${IROHA_SCHEMA_DIR} + ARGS -I${GEN_ARGS} -I${CMAKE_CURRENT_SOURCE_DIR} ${ARGN} --cpp_out=${SCHEMA_OUT_DIR} ${PROTO} + DEPENDS protoc ${SCHEMA_PATH}/${PROTO} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) endfunction() - -function(compile_proto_to_grpc_cpp PROTO) - compile_proto_to_cpp(${PROTO}) +function(compile_proto_only_grpc_to_cpp PROTO) string(REGEX REPLACE "\\.proto$" ".grpc.pb.h" GEN_GRPC_PB_HEADER ${PROTO}) string(REGEX REPLACE "\\.proto$" ".grpc.pb.cc" GEN_GRPC_PB ${PROTO}) add_custom_command( - OUTPUT ${IROHA_SCHEMA_DIR}/${GEN_GRPC_PB_HEADER} ${IROHA_SCHEMA_DIR}/${GEN_GRPC_PB} + OUTPUT ${SCHEMA_OUT_DIR}/${GEN_GRPC_PB_HEADER} ${SCHEMA_OUT_DIR}/${GEN_GRPC_PB} COMMAND ${CMAKE_COMMAND} -E env LD_LIBRARY_PATH=${protobuf_LIBRARY_DIR}:$ENV{LD_LIBRARY_PATH} "${protoc_EXECUTABLE}" - ARGS -I${protobuf_INCLUDE_DIR} -I. --grpc_out=${IROHA_SCHEMA_DIR} --plugin=protoc-gen-grpc="${grpc_CPP_PLUGIN}" ${PROTO} - DEPENDS grpc_cpp_plugin ${IROHA_SCHEMA_DIR}/${PROTO} - WORKING_DIRECTORY ${IROHA_SCHEMA_DIR} + ARGS -I${protobuf_INCLUDE_DIR} -I${CMAKE_CURRENT_SOURCE_DIR} ${ARGN} --grpc_out=${SCHEMA_OUT_DIR} --plugin=protoc-gen-grpc="${grpc_CPP_PLUGIN}" ${PROTO} + DEPENDS grpc_cpp_plugin ${SCHEMA_PATH}/${PROTO} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) endfunction() +function(compile_proto_to_grpc_cpp PROTO) + compile_proto_to_cpp(${PROTO} "${ARGN}") + compile_proto_only_grpc_to_cpp(${PROTO} "${ARGN}") +endfunction() function(compile_proto_to_python PROTO) string(REGEX REPLACE "\\.proto$" "_pb2.py" PY_PB ${PROTO}) @@ -106,8 +108,8 @@ function(compile_proto_to_python PROTO) add_custom_command( OUTPUT ${SWIG_BUILD_DIR}/${PY_PB} COMMAND ${GEN_COMMAND} - ARGS -I${GEN_ARGS} -I. --python_out=${SWIG_BUILD_DIR} ${PROTO} - DEPENDS protoc ${IROHA_SCHEMA_DIR}/${PROTO} + ARGS -I${GEN_ARGS} -I${SM_SCHEMA_DIR} --python_out=${SWIG_BUILD_DIR} ${PROTO} + DEPENDS protoc ${SM_SCHEMA_DIR}/${PROTO} WORKING_DIRECTORY ${IROHA_SCHEMA_DIR} ) endfunction() diff --git a/docker/android/Dockerfile b/docker/android/Dockerfile index eb70b69ddb..6f4782ffaa 100644 --- a/docker/android/Dockerfile +++ b/docker/android/Dockerfile @@ -46,7 +46,7 @@ RUN set -ex; \ # ed25519 RUN set -e; \ git clone git://github.com/hyperledger/iroha-ed25519; \ - (cd ./iroha-ed25519 ; git checkout e7188b8393dbe5ac54378610d53630bd4a180038); \ + (cd ./iroha-ed25519 ; git checkout f42953c631fae93011612f6b1ee33f1f88c3f8af); \ cmake -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=$VERSION -DCMAKE_ANDROID_ARCH_ABI=$PLATFORM -DANDROID_NDK=$NDK_PATH -DCMAKE_ANDROID_STL_TYPE=c++_static -DCMAKE_INSTALL_PREFIX=$DEPS_DIR -DTESTING=OFF -DCMAKE_BUILD_TYPE=$BUILD_TYPE_A -DBUILD=STATIC -H./iroha-ed25519 -B./iroha-ed25519/build; \ VERBOSE=1 cmake --build ./iroha-ed25519/build --target install -- -j$PARALLELISM; \ mv "$DEPS_DIR"/lib/static/libed25519.a "$DEPS_DIR"/lib; rmdir "$DEPS_DIR"/lib/static/ diff --git a/docker/dependencies/Dockerfile b/docker/dependencies/Dockerfile index 346371aaac..0cd8972b58 100644 --- a/docker/dependencies/Dockerfile +++ b/docker/dependencies/Dockerfile @@ -30,7 +30,7 @@ RUN set -e; \ # SWIG dependencies libpcre3-dev autoconf bison \ # other - wget curl file gdb ccache \ + wget curl file gdb gdbserver ccache \ gcovr cppcheck doxygen graphviz graphviz-dev unzip zip; \ apt-get -y clean @@ -49,7 +49,7 @@ RUN set -e; \ git clone https://github.com/boostorg/boost /tmp/boost; \ (cd /tmp/boost ; git checkout 436ad1dfcfc7e0246141beddd11c8a4e9c10b146); \ (cd /tmp/boost ; git submodule update --init --recursive); \ - (cd /tmp/boost ; /tmp/boost/bootstrap.sh --with-libraries=system,filesystem); \ + (cd /tmp/boost ; /tmp/boost/bootstrap.sh --with-libraries=system,filesystem,thread); \ (cd /tmp/boost ; /tmp/boost/b2 headers); \ (cd /tmp/boost ; /tmp/boost/b2 cxxflags="-std=c++14" -j ${PARALLELISM} install --prefix=/opt/dependencies/boost); \ ldconfig; \ @@ -187,18 +187,27 @@ RUN set -e; \ # remove rm -rf /tmp/postgresql -# install pqxx +# install soci 3.2.3 RUN set -e; \ - export PATH="/opt/dependencies/libpq/bin:$PATH"; \ - git clone https://github.com/jtv/libpqxx /tmp/libpqxx; \ - (cd /tmp/libpqxx ; git checkout 5b17abce5ac2b1a2f8278718405b7ade8bb30ae9); \ - curl -L -o /tmp/libpqxx/config/config.guess 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=6b2374c79506ee82a8b440f6d1ca293e2e2e2463'; \ - curl -L -o /tmp/libpqxx/config/config.sub 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=6b2374c79506ee82a8b440f6d1ca293e2e2e2463'; \ - (cd /tmp/libpqxx ; /tmp/libpqxx/configure --disable-documentation --with-pic --prefix=/opt/dependencies/libpqxx); \ - make -j${PARALLELISM} -C /tmp/libpqxx; \ - make -C /tmp/libpqxx install; \ + git clone https://github.com/SOCI/soci /tmp/soci; \ + (cd /tmp/soci ; git checkout 111b50af8c3876ea392367640b4bd83b4f903ab8); \ + cmake \ + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ + -DWITH_BOOST=ON \ + -DWITH_DB2=OFF \ + -DWITH_FIREBIRD=OFF \ + -DWITH_MYSQL=OFF \ + -DWITH_ODBC=OFF \ + -DWITH_ORACLE=OFF \ + -DWITH_POSTGRESQL=ON \ + -DWITH_SQLITE3=OFF \ + -H/tmp/soci/src \ + -B/tmp/soci/build \ + -DCMAKE_PREFIX_PATH="/opt/dependencies/libpq" \ + -DCMAKE_INSTALL_PREFIX=/opt/dependencies/soci; \ + cmake --build /tmp/soci/build --target install; \ ldconfig; \ - rm -rf /tmp/libpqxx + rm -rf /tmp/soci # install tbb RUN set -e; \ @@ -225,7 +234,7 @@ RUN set -e; \ # install ed25519 RUN set -e; \ git clone git://github.com/hyperledger/iroha-ed25519.git /tmp/ed25519; \ - (cd /tmp/ed25519 ; git checkout e7188b8393dbe5ac54378610d53630bd4a180038); \ + (cd /tmp/ed25519 ; git checkout f42953c631fae93011612f6b1ee33f1f88c3f8af); \ cmake \ -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ -DTESTING=OFF \ diff --git a/docker/develop/Dockerfile b/docker/develop/Dockerfile index 1834483fed..7a2dd169b2 100644 --- a/docker/develop/Dockerfile +++ b/docker/develop/Dockerfile @@ -30,8 +30,8 @@ RUN set -e; \ # SWIG dependencies libpcre3-dev autoconf bison \ # other - wget curl file gdb ccache \ - gcovr cppcheck doxygen graphviz graphviz-dev unzip zip; \ + wget curl file gdb gdbserver ccache \ + gcovr cppcheck doxygen rsync graphviz graphviz-dev unzip zip; \ apt-get -y clean # install cmake 3.10.2 @@ -49,7 +49,7 @@ RUN set -e; \ git clone https://github.com/boostorg/boost /tmp/boost; \ (cd /tmp/boost ; git checkout 436ad1dfcfc7e0246141beddd11c8a4e9c10b146); \ (cd /tmp/boost ; git submodule update --init --recursive); \ - (cd /tmp/boost ; /tmp/boost/bootstrap.sh --with-libraries=system,filesystem); \ + (cd /tmp/boost ; /tmp/boost/bootstrap.sh --with-libraries=system,filesystem,thread); \ (cd /tmp/boost ; /tmp/boost/b2 headers); \ (cd /tmp/boost ; /tmp/boost/b2 cxxflags="-std=c++14" -j ${PARALLELISM} install); \ ldconfig; \ @@ -177,17 +177,25 @@ RUN set -e; \ # remove rm -rf /tmp/postgresql -# install pqxx +# install soci 3.2.3 RUN set -e; \ - git clone https://github.com/jtv/libpqxx /tmp/libpqxx; \ - (cd /tmp/libpqxx ; git checkout 5b17abce5ac2b1a2f8278718405b7ade8bb30ae9); \ - curl -L -o /tmp/libpqxx/config/config.guess 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=6b2374c79506ee82a8b440f6d1ca293e2e2e2463'; \ - curl -L -o /tmp/libpqxx/config/config.sub 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=6b2374c79506ee82a8b440f6d1ca293e2e2e2463'; \ - (cd /tmp/libpqxx ; /tmp/libpqxx/configure --disable-documentation --with-pic); \ - make -j${PARALLELISM} -C /tmp/libpqxx; \ - make -C /tmp/libpqxx install; \ + git clone https://github.com/SOCI/soci /tmp/soci; \ + (cd /tmp/soci ; git checkout 111b50af8c3876ea392367640b4bd83b4f903ab8); \ + cmake \ + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ + -DWITH_BOOST=ON \ + -DWITH_DB2=OFF \ + -DWITH_FIREBIRD=OFF \ + -DWITH_MYSQL=OFF \ + -DWITH_ODBC=OFF \ + -DWITH_ORACLE=OFF \ + -DWITH_POSTGRESQL=ON \ + -DWITH_SQLITE3=OFF \ + -H/tmp/soci/src \ + -B/tmp/soci/build; \ + cmake --build /tmp/soci/build --target install; \ ldconfig; \ - rm -rf /tmp/libpqxx + rm -rf /tmp/soci # install tbb RUN set -e; \ @@ -213,7 +221,7 @@ RUN set -e; \ # install ed25519 RUN set -e; \ git clone git://github.com/hyperledger/iroha-ed25519.git /tmp/ed25519; \ - (cd /tmp/ed25519 ; git checkout e7188b8393dbe5ac54378610d53630bd4a180038); \ + (cd /tmp/ed25519 ; git checkout f42953c631fae93011612f6b1ee33f1f88c3f8af); \ cmake \ -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ -DTESTING=OFF \ diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 95803e4084..504636b84b 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -24,6 +24,8 @@ services: working_dir: /opt/iroha cap_add: - SYS_PTRACE + security_opt: + - seccomp:unconfined postgres: image: postgres:9.5 diff --git a/docs/README.md b/docs/README.md index a57bff90f7..566a9e0e76 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,6 +5,7 @@ The purpose of this documentation is to convey design and architecture aspects o ## Principles * modify only the contents of `image_assets` and `source` folders + * do `make permissions` in case of any change inside `source/permissions` folder * if new section/file is added — it should be placed in the list of contents * if any new principle is added — it should be discussed in GitHub issues first, as an improvement proposal * reference images or assets using GitHub download link or an external service, since our localized docs can't use relative path to image assets or other resources @@ -22,6 +23,7 @@ The purpose of this documentation is to convey design and architecture aspects o ### Build steps 1. `cd docs/source` +1. `make permissions` 1. `make html` After that, the documentation is generated in html format in `_build` folder. You can check another formats on Sphinx website or just by typing `make`. In order to get familiar with the syntax of theme used in the project please go to their [demo website](https://sphinx-rtd-theme.readthedocs.io/en/latest/demo/demo.html) diff --git a/docs/image_assets/iroha_logo_doxygen.png b/docs/image_assets/iroha_logo_doxygen.png new file mode 100644 index 0000000000..d59f0eedd0 Binary files /dev/null and b/docs/image_assets/iroha_logo_doxygen.png differ diff --git a/docs/permissions_compiler/compiler.py b/docs/permissions_compiler/compiler.py new file mode 100644 index 0000000000..63cebb9510 --- /dev/null +++ b/docs/permissions_compiler/compiler.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 + +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import csv +import rst +import glossary +import consts +import os.path +import os + +perm_type = category = perm = "" + +result = ['.. DON\'T MODIFY THE CONTENTS MANUALLY.', + ' THIS IS AUTOGENERATED FILE.', + ' All the changes will be lost in case of manual modification.', + ' For modification change the files in docs/source/permissions.' + ' Then do "make permissions" before "make html".', ''] +result.extend(rst.header("Permissions", 0)) +glossary_links = glossary.titles_to_links(glossary.read_titles()) + +with open('permissions/introduction.txt') as intro: + intro_lines = intro.readlines() + for line in intro_lines: + result.append(line.strip()) + +result.append('') + +with open('permissions/matrix.csv', newline='') as csvfile: + reader = csv.DictReader(csvfile) + for row in reader: + grantable = False + + if row['Type'] != perm_type: + perm_type = row['Type'] + result.extend( + rst.header("{}-related permissions".format(row['Type']), 1)) + + if row['Category'] != category: + category = row['Category'] + result.extend(rst.header(category, 2)) + + if row['Permission'] != perm: + perm = row['Permission'] + result.extend(rst.header(perm, 3)) + + if row['Grantable'].strip() == 'TRUE': + grantable = True + hint = rst.hint('This is a grantable permission.') + result.extend(hint) + + descr_lines = row['Description'].split('\n') + descr_lines = list(map(lambda x: x.strip(), descr_lines)) + descr_lines.append('') + + if row['Additional Information'].strip(): + ainfo = row['Additional Information'].split('\n') + ainfo = list(map(lambda x: x.strip(), ainfo)) + ainfo.append('') + descr_lines.extend(ainfo) + + links_dict = dict(glossary_links) + descr_lines_linkified = [] + for line in descr_lines: + tokens = line.split(' ') + tokens_linkified = [] + skip = False + for token in tokens: + if skip: + tokens_linkified.append(token) + if '`' in token: + if not skip: + tokens_linkified.append(token) + if token.count('`') % 2 == 1: + skip = not skip + continue + tokens_linkified.append(rst.linkify(token, links_dict, pop=True)) + descr_lines_linkified.append(' '.join(tokens_linkified)) + + result.extend(descr_lines_linkified) + + if row['Note'].strip(): + result.extend(rst.note(row['Note'])) + + if row['Related Command'].strip(): + rc = row['Related Command'].split('\n') + rc = map(lambda x: x.strip(), rc) + rc = filter(lambda x: len(x) > 0, rc) + rc = list(rc) + links = [] + related = 'Related API method' + ('s' if len(rc) > 1 else '') + for link in rc: + try: + links.append(rst.reference(link)) + except Exception: + if (row['Related Command'].strip().lower().startswith('tbd')): + links.append('To be done') + else: + print(row['Related Command']) + raise + result.append('| {}: {}'.format(related, ', '.join(links))) + + result.extend(rst.alias(perm, grantable)) + + if row['Example'].strip(): + result.extend(rst.example(row['Example'])) + + result.extend(rst.excerpt(perm)) + result.extend(rst.header('Supplementary Sources', 1)) + commons_path = [os.path.pardir] * 2 + ['example', 'python', 'permissions', 'commons.py'] + result.extend(rst.listing(commons_path, 'commons.py')) + consts_path = [os.path.pardir, 'permissions_compiler', 'consts.py'] + result.extend(rst.listing(consts_path, 'consts.py')) + +with open('maintenance/permissions.rst', 'w') as output: + content = "\n".join(result) + output.write(content) + output.flush() + +print('done') diff --git a/docs/permissions_compiler/consts.py b/docs/permissions_compiler/consts.py new file mode 100644 index 0000000000..bbe998eb20 --- /dev/null +++ b/docs/permissions_compiler/consts.py @@ -0,0 +1,58 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +grantable = { + 'can_add_my_signatory': 'kAddMySignatory', + 'can_remove_my_signatory': 'kRemoveMySignatory', + 'can_set_my_account_detail': 'kSetMyAccountDetail', + 'can_set_my_quorum': 'kSetMyQuorum', + 'can_transfer_my_assets': 'kTransferMyAssets' +} + +role = { + 'can_add_asset_qty': 'kAddAssetQty', + 'can_add_peer': 'kAddPeer', + 'can_add_signatory': 'kAddSignatory', + 'can_append_role': 'kAppendRole', + 'can_create_account': 'kCreateAccount', + 'can_create_asset': 'kCreateAsset', + 'can_create_domain': 'kCreateDomain', + 'can_create_role': 'kCreateRole', + 'can_detach_role': 'kDetachRole', + 'can_get_all_acc_ast': 'kGetAllAccAst', + 'can_get_all_acc_ast_txs': 'kGetAllAccAstTxs', + 'can_get_all_acc_detail': 'kGetAllAccDetail', + 'can_get_all_acc_txs': 'kGetAllAccTxs', + 'can_get_all_accounts': 'kGetAllAccounts', + 'can_get_all_signatories': 'kGetAllSignatories', + 'can_get_all_txs': 'kGetAllTxs', + 'can_get_blocks': 'kGetBlocks', + 'can_get_domain_acc_ast': 'kGetDomainAccAst', + 'can_get_domain_acc_ast_txs': 'kGetDomainAccAstTxs', + 'can_get_domain_acc_detail': 'kGetDomainAccDetail', + 'can_get_domain_acc_txs': 'kGetDomainAccTxs', + 'can_get_domain_accounts': 'kGetDomainAccounts', + 'can_get_domain_signatories': 'kGetDomainSignatories', + 'can_get_my_acc_ast': 'kGetMyAccAst', + 'can_get_my_acc_ast_txs': 'kGetMyAccAstTxs', + 'can_get_my_acc_detail': 'kGetMyAccDetail', + 'can_get_my_acc_txs': 'kGetMyAccTxs', + 'can_get_my_account': 'kGetMyAccount', + 'can_get_my_signatories': 'kGetMySignatories', + 'can_get_my_txs': 'kGetMyTxs', + 'can_get_roles': 'kGetRoles', + 'can_grant_can_add_my_signatory': 'kAddMySignatory', + 'can_grant_can_remove_my_signatory': 'kRemoveMySignatory', + 'can_grant_can_set_my_account_detail': 'kSetMyAccountDetail', + 'can_grant_can_set_my_quorum': 'kSetMyQuorum', + 'can_grant_can_transfer_my_assets': 'kTransferMyAssets', + 'can_read_assets': 'kReadAssets', + 'can_receive': 'kReceive', + 'can_remove_signatory': 'kRemoveSignatory', + 'can_set_detail': 'kSetDetail', + 'can_set_quorum': 'kSetQuorum', + 'can_subtract_asset_qty': 'kSubtractAssetQty', + 'can_transfer': 'kTransfer' +} diff --git a/docs/permissions_compiler/glossary.py b/docs/permissions_compiler/glossary.py new file mode 100644 index 0000000000..7bfdce243b --- /dev/null +++ b/docs/permissions_compiler/glossary.py @@ -0,0 +1,27 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +def read_titles(level_char='='): + headings = [] + with open('core_concepts/glossary.rst') as gfile: + lines = gfile.readlines() + prevline = '' + prevlen = 0 + for line in lines: + line = line.strip() + if len(line) == prevlen and line == (level_char * prevlen) and line: + headings.append(prevline) + prevlen = len(line) + prevline = line + return headings + + +def titles_to_links(titles): + d = {} + for title in titles: + title = title.strip().lower() + anchor = '../core_concepts/glossary.html#' + title.replace(' ', '-') + d[title] = anchor + return d diff --git a/docs/permissions_compiler/rst.py b/docs/permissions_compiler/rst.py new file mode 100644 index 0000000000..ebcfaceba9 --- /dev/null +++ b/docs/permissions_compiler/rst.py @@ -0,0 +1,186 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import consts +import os.path + +levels = ['*', '=', '-', '^', '"'] + + +def header_char(level): + level = int(level) % len(levels) + return levels[level] + + +def header_overline(level): + level = int(level) % len(levels) + return level == 0 + + +def header(title, level): + title = title.strip() + decoration = header_char(level) * len(title) + result = [] + if header_overline(level): + result.append(decoration) + result.append(title) + result.append(decoration) + result.append("") + return result + + +def hint(text): + text = text.strip() + return ['.. Hint:: {}'.format(text), ''] + + +def note(text): + text = text.strip() + return ['.. Note:: {}'.format(text), ''] + + +def reference(link): + if (not '#' in link) or (not '/api' in link): + raise Exception('Badly formed input link') + link = link.strip() + title = link.split('#')[1].replace('-', ' ').title() + path = ''.join(link.split("/api")[1:]) + path = '../api{}'.format(path) + link = '`{} <{}>`__'.format(title, path) + return link + + +def linkify(term, dictionary, pop=False): + if not term: + return term + clean = term.strip().lower() + before = after = '' + if clean[0] in ['"', "'", '(']: + before = clean[0] + clean = clean[1:] + term = term[1:] + if clean[-1] in ['.', ',', '!', '?', ':', ';', '"', "'", ')']: + after = clean[-1] + clean = clean[:-1] + term = term[:-1] + result = before + term + after + found = False + if clean in dictionary: + found = True + result = '{}`{} <{}>`__{}'.format(before, term, dictionary[clean], after) + if pop: + dictionary.pop(clean, None) + if not found and clean.endswith('s'): + clean_singular = clean[:-1] + if clean_singular in dictionary: + result = '{}`{} <{}>`__{}'.format(before, term, dictionary[clean_singular], after) + if pop: + dictionary.pop(clean_singular, None) + return result + + +def alias(permission, grantable): + """ + + :param permission: string permsssion name + :param grantable: boolean True if grantable + :return: list of strings + """ + langs = {'Python': '_', 'Java': '.'} + lines = [] + perm = permission.lower().strip() + for lang, delimiter in sorted(langs.items()): + lines.append('| Usage in {} bindings: ``{}{}{}``'.format( + lang, + 'Grantable' if grantable else 'Role', + delimiter, + consts.grantable[perm] if grantable else consts.role[perm] + )) + lines.append('|') + lines.append('') + return lines + + +def listing(compile_time_path, caption='', lines_range=None, lang='python'): + """ + Generates listing lines to include + :param compile_time_path: list of os.path primitives + :param lines_range: tuple of two ints + :return: rst lines + """ + path = os.path.join(*compile_time_path) + if not os.path.isfile(path): + print('File not found: {} (compile time path)'.format(path)) + return [] + + docs_time_path = [os.path.pardir] + list(compile_time_path) + path = os.path.join(*docs_time_path) + result = [ + '.. literalinclude:: {}'.format(path), + ' :language: {}'.format(lang), + ' :linenos:' + ] + if caption: + result.append(' :caption: {}'.format(caption)) + if lines_range: + result.append(' :lines: {}-{}'.format(lines_range[0], lines_range[1])) + result.append('') + return result + + +def excerpt_boundaries(path): + """ + + :param path: path to python example + :return: tuple with two numbers + """ + lines = [] + with open(path) as source: + lines = source.readlines() + begin = 1 + end = len(lines) + for index, line in enumerate(lines): + if begin == 1 and ('commons.user' in line or 'admin' in line): + begin = index + 1 + break + prints = spaces = False + for index, line in reversed(list(enumerate(lines))): + if index < end: + if not prints and line.startswith('print'): + prints = True + elif prints and not line.strip(): + spaces = True + elif prints and spaces and line.strip(): + end = index + 1 + break + + return (begin, end) + + +def excerpt(permission): + """ + Renders source file listing + :param permission: name of permission to list, used as a part of filename + :return: rst lines + """ + compile_time_path = [os.path.pardir, os.path.pardir, 'example', 'python', 'permissions', '{}.py'.format(permission)] + path = os.path.join(*compile_time_path) + result = [] + if not os.path.isfile(path): + print(path) + return result + window = excerpt_boundaries(path) + result.extend(listing(compile_time_path, lines_range=window)) + return result + + +def example(text): + """Renders example description contents""" + result = ['**Example**', ''] + lines = text.split('\n') + for line in lines: + result.append('| {}'.format(line)) + result.extend(['|', '']) + return result diff --git a/docs/source/Makefile b/docs/source/Makefile index e4e5e26c1f..1bc166d403 100644 --- a/docs/source/Makefile +++ b/docs/source/Makefile @@ -13,6 +13,10 @@ help: .PHONY: help Makefile +.PHONY: permissions +permissions: + python3 ../permissions_compiler/compiler.py + # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile diff --git a/docs/source/api/commands.rst b/docs/source/api/commands.rst index 4ac57860ef..b09e356226 100644 --- a/docs/source/api/commands.rst +++ b/docs/source/api/commands.rst @@ -19,9 +19,8 @@ Schema .. code-block:: proto message AddAssetQuantity { - string account_id = 1; - string asset_id = 2; - Amount amount = 3; + string asset_id = 1; + Amount amount = 2; } message uint256 { @@ -48,7 +47,6 @@ Structure :header: "Field", "Description", "Constraint", "Example" :widths: 15, 30, 20, 15 - "Account ID", "account id in which to add asset", "@", "alex@morgan" "Asset ID", "id of the asset", "#", "usd#morgan" "Amount", "positive amount of the asset to add", "> 0", "200.02" @@ -58,7 +56,6 @@ Validation 1. Asset and account should exist 2. Added quantity precision should be equal to asset precision 3. Creator of a transaction should have a role which has permissions for issuing assets -4. Creator of a transaction adds account quantity to his/her account only Add peer -------- @@ -557,10 +554,9 @@ Schema .. code-block:: proto - message AddAssetQuantity { - string account_id = 1; - string asset_id = 2; - Amount amount = 3; + message SubtractAssetQuantity { + string asset_id = 1; + Amount amount = 2; } message uint256 { @@ -586,7 +582,6 @@ Structure :header: "Field", "Description", "Constraint", "Example" :widths: 15, 30, 20, 15 - "Account ID", "account id from which to subtract asset", "already existent", "makoto@soramitsu" "Asset ID", "id of the asset", "#", "usd#morgan" "Amount", "positive amount of the asset to subtract", "> 0", "200" @@ -596,7 +591,6 @@ Validation 1. Asset and account should exist 2. Added quantity precision should be equal to asset precision 3. Creator of the transaction should have a role which has permissions for subtraction of assets -4. Creator of transaction subtracts account quantity in his/her account only Transfer asset -------------- diff --git a/docs/source/api/queries.rst b/docs/source/api/queries.rst index e68f145aba..92db08badd 100644 --- a/docs/source/api/queries.rst +++ b/docs/source/api/queries.rst @@ -159,6 +159,44 @@ Response Structure "Transactions", "an array of transactions", "Committed transactions", "{tx1, tx2…}" +Get Pending Transactions +^^^^^^^^^^^^^^^^^^^^^^^^ + +Purpose +------- + +GetPendingTransactions is used for retrieving a list of pending (not fully signed) `multisignature transactions <../core_concepts/glossary.html#multisignature-transactions>`_ +or `batches of transactions <../core_concepts/glossary.html#batch-of-transactions>`__ issued by account of query creator. + +Request Schema +-------------- + +.. code-block:: proto + + message GetPendingTransactions { + } + +Response Schema +--------------- + +.. code-block:: proto + + message TransactionsResponse { + repeated Transaction transactions = 1; + } + +Response Structure +------------------ + +The response contains a list of `pending transactions <../core_concepts/glossary.html#pending-transactions>`_. + +.. csv-table:: + :header: "Field", "Description", "Constraint", "Example" + :widths: 15, 30, 20, 15 + + "Transactions", "an array of pending transactions", "Pending transactions", "{tx1, tx2…}" + + Get Account Transactions ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -300,6 +338,157 @@ Response Structure "Account ID", "account which has this balance", "@", "makoto@soramitsu" "Balance", "balance of the asset", "Not less than 0", "200.20" +Get Account Detail +^^^^^^^^^^^^^^^^^^ + +Purpose +------- + +To get details of the account, `GetAccountDetail` query can be used. Account details are key-value pairs, splitted into writers categories. Writers are accounts, which added the corresponding account detail. Example of such structure is: + +.. code-block:: json + + { + "account@a_domain": { + "age": 18, + "hobbies": "crypto" + }, + "account@b_domain": { + "age": 20, + "sports": "basketball" + } + } + +Here, one can see four account details - "age", "hobbies" and "sports" - added by two writers - "account@a_domain" and "account@b_domain". All of these details, obviously, are about the same account. + +Request Schema +-------------- + +.. code-block:: proto + + message GetAccountDetail { + oneof opt_account_id { + string account_id = 1; + } + oneof opt_key { + string key = 2; + } + oneof opt_writer { + string writer = 3; + } + } + +.. note:: + Pay attention, that all fields are optional. Reasons will be described later. + +Request Structure +----------------- + +.. csv-table:: + :header: "Field", "Description", "Constraint", "Example" + :widths: 15, 30, 20, 15 + + "Account ID", "account id to get details from", "@", "account@domain" + "Key", "key, under which to get details", "string", "age" + "Writer", "account id of writer", "@", "account@domain" + +Response Schema +--------------- + +.. code-block:: proto + + message AccountDetailResponse { + string detail = 1; + } + +Response Structure +------------------ + +.. csv-table:: + :header: "Field", "Description", "Constraint", "Example" + :widths: 15, 30, 20, 15 + + "Detail", "key-value pairs with account details", "JSON", "see below" + +Usage Examples +-------------- + +Let's again consider the example of details from the beginning and see, how different variants of `GetAccountDetail` queries will change the resulting response. + +.. code-block:: json + + { + "account@a_domain": { + "age": 18, + "hobbies": "crypto" + }, + "account@b_domain": { + "age": 20, + "sports": "basketball" + } + } + +**account_id is not set** + +If account_id is not set - other fields can be empty or not - it will automatically be substituted with query creator's account, which will lead to one of the next cases. + +**only account_id is set** + +In this case, all details about that account are going to be returned, leading to the following response: + +.. code-block:: json + + { + "account@a_domain": { + "age": 18, + "hobbies": "crypto" + }, + "account@b_domain": { + "age": 20, + "sports": "basketball" + } + } + +**account_id and key are set** + +Here, details added by all writers under the key are going to be returned. For example, if we asked for the key "age", that's the response we would get: + +.. code-block:: json + + { + "account@a_domain": { + "age": 18 + }, + "account@b_domain": { + "age": 20 + } + } + +**account_id and writer are set** + +Now, the response will contain all details about this account, added by one specific writer. For example, if we asked for writer "account@b_domain", we would get: + +.. code-block:: json + + { + "account@b_domain": { + "age": 20, + "sports": "basketball" + } + } + +**account_id, key and writer are set** + +Lastly, if all three field are set, result will contain details, added the specific writer and under the specific key, for example, if we asked for key "age" and writer "account@a_domain", we would get: + +.. code-block:: json + + { + "account@a_domain": { + "age": 18 + } + } + Get Asset Info ^^^^^^^^^^^^^^ diff --git a/docs/source/core_concepts/glossary.rst b/docs/source/core_concepts/glossary.rst index d90e888914..99b1bd3d9e 100644 --- a/docs/source/core_concepts/glossary.rst +++ b/docs/source/core_concepts/glossary.rst @@ -145,6 +145,8 @@ A named rule that gives the privilege to perform a command. Permission **cannot** be granted to an `account <#account>`__ directly, instead, an account has roles, which are the collection of permissions. +`List of Iroha permissions <../maintenance/permissions.html>`_. + Grantable Permission -------------------- @@ -167,16 +169,6 @@ Verified Proposal A set of transactions that have been passed `stateless <#stateless-validation>`__ and `stateful <#stateful-validation>`__ validation, but were not committed yet. -Role -==== - -A named abstraction that holds a set of `permissions <#permission>`__. - -Simulator -========= - -See `Verified Proposal Creator <#verified-proposal-creator>`__. - Query ===== @@ -192,6 +184,22 @@ of signatures required to consider a transaction signed. The default value is 1. Each account can link additional public keys and increase own quorum number. +Role +==== + +A named abstraction that holds a set of `permissions <#permission>`__. + +Signatory +========= + +Represents an entity that can confirm multisignature transactions for some `account <#account>`__. +It can be attached to account via `AddSignatory <../api/commands.html#add-signatory>`__ and detached via `RemoveSignatory <../api/commands.html#remove-signatory>`__. + +Simulator +========= + +See `Verified Proposal Creator <#verified-proposal-creator>`__. + Synchronizer ============ @@ -215,7 +223,7 @@ An ordered set of `commands <#command>`__, which is applied to the ledger atomic Any nonvalid command within a transaction leads to rejection of the whole transaction during the validation process. -Transaction structure +Transaction Structure --------------------- **Payload** stores all transaction fields, except signatures: @@ -223,12 +231,20 @@ Transaction structure - Time of creation (unix time, in milliseconds) - Account ID of transaction creator (username@domain) - Quorum field (indicates required number of signatures) - - Repeated commands which are described in details in commands section <../api/commands.html>`__ + - Repeated commands which are described in details in `commands section <../api/commands.html>`__ + - Batch meta information (optional part). See `Batch of Transactions`_ for details **Signatures** contain one or many signatures (ed25519 public key + signature) -Transaction statuses +Reduced Transaction Hash +^^^^^^^^^^^^^^^^^^^^^^^^ + +Reduced hash is calculated over transaction payload excluding batch meta information. +Used in `Batch of Transactions`_. + + +Transaction Statuses -------------------- Hyperledger Iroha supports both push and pull interaction mode with a client. @@ -239,7 +255,7 @@ In any of these modes, the set of transaction statuses is the same: .. image:: ./../../image_assets/tx_status.png -Transaction status set +Transaction Status Set ^^^^^^^^^^^^^^^^^^^^^^ - NOT_RECEIVED: requested peer does not have this transaction. @@ -250,6 +266,50 @@ Transaction status set - STATEFUL_VALIDATION_SUCCESS: the transaction has successfully passed stateful validation. - COMMITTED: the transaction is the part of a block, which gained enough votes and is in the block store at the moment. +Pending Transactions +^^^^^^^^^^^^^^^^^^^^ + +Any transaction that has lesser signatures at the moment than `quorum`_ of transaction creator account is considered as pending. +Pending transaction will be submitted for `stateful validation`_ as soon as `multisignature <#multisignature-transactions>`__ mechanism will collect required amount of signatures for quorum. + +Transaction that already has quorum of signatures can also be considered as pending in cases +when the transaction is a part of `batch of transactions`_ and there is a not fully signed transaction. + +Batch of Transactions +===================== + +*The feature is to be released.* + +Transactions batch is a feature that allows sending several transactions to Iroha at once preserving their order. + +Each transaction within a batch includes batch meta information. +Batch meta contains batch type identifier (atomic or ordered) and a list of `reduced hashes <#reduced-transaction-hash>`_ +of all transactions within a batch. +The order of hashes prescribes transactions sequence. + +Batch can contain transactions created by different accounts. +Any transaction within a batch can require single or `multiple <#multisignature-transactions>`__ signatures (depends on quorum set for an account of transaction creator). +At least one transaction inside a batch should have at least one signature to let the batch pass `stateful validation`_. + +Atomic Batch +------------ + +All the transactions within an atomic batch should pass `stateful validation`_ for the batch to be applied to a ledger. + +Ordered Batch +------------- + +Ordered batch preserves only the sequence of transactions applying to a ledger. +All the transactions that able to pass stateful validation within a batch will be applied to a ledger. +Validation failure of one transaction would NOT directly imply the failure of the whole batch. + +Multisignature Transactions +=========================== + +A transaction which has the `quorum`_ greater than one is considered as multisignature (also called mst). +To achieve `stateful validity <#stateful-validation>`__ the confirmation is required by the `signatories <#signatory>`__ of the creator account. +These participants need to send the same transaction with their signature. + Validator ========= diff --git a/docs/source/guides/build.rst b/docs/source/guides/build.rst index 2a46387808..a49dfb50d7 100644 --- a/docs/source/guides/build.rst +++ b/docs/source/guides/build.rst @@ -116,7 +116,7 @@ to install all dependencies with Homebrew. .. code-block:: shell xcode-select --install - brew install cmake boost postgres grpc autoconf automake libtool golang libpqxx + brew install cmake boost postgres grpc autoconf automake libtool golang soci .. hint:: To install the Homebrew itself please run diff --git a/docs/source/guides/libraries/python.rst b/docs/source/guides/libraries/python.rst index 7a01427835..685183fbd2 100644 --- a/docs/source/guides/libraries/python.rst +++ b/docs/source/guides/libraries/python.rst @@ -74,8 +74,8 @@ Building Protobuf Files echo "schema" >> iroha-schema/.git/info/sparse-checkout git -C iroha-schema pull schema develop cd iroha-schema - protoc --proto_path=schema --python_out=. block.proto primitive.proto commands.proto queries.proto responses.proto endpoint.proto - python -m grpc_tools.protoc --proto_path=schema --python_out=. --grpc_python_out=. endpoint.proto yac.proto ordering.proto loader.proto + protoc --proto_path=shared_model/schema --python_out=. shared_model/schema/*.proto + python -m grpc_tools.protoc --proto_path=shared_model/schema --python_out=. --grpc_python_out=. shared_model/schema/endpoint.proto Protobuf files can be found in **iroha-schema** folder ('\*_pb2\*.py' files) @@ -242,7 +242,7 @@ Create asset quantity: tx = tx_builder.creatorAccountId(creator) \ .createdTime(current_time) \ - .addAssetQuantity("admin@test", "coin#domain", "1000.2").build() + .addAssetQuantity("coin#domain", "1000.2").build() send_tx(tx, key_pair) print_status(tx) diff --git a/docs/source/maintenance/permissions.rst b/docs/source/maintenance/permissions.rst index c85d3b421a..7805e98200 100644 --- a/docs/source/maintenance/permissions.rst +++ b/docs/source/maintenance/permissions.rst @@ -1,3 +1,8 @@ +.. DON'T MODIFY THE CONTENTS MANUALLY. + THIS IS AUTOGENERATED FILE. + All the changes will be lost in case of manual modification. + For modification change the files in docs/source/permissions. Then do "make permissions" before "make html". + *********** Permissions *********** @@ -13,6 +18,8 @@ This might be done at the initial step of system deployment — in genesis block or later when Iroha network is up and running, roles can be changed (if there is a role that can do that :) This section will help you to understand permissions and give you an idea of how to create roles including certain permissions. +Each permission is provided with an example written in Python that demonstrates the way of transaction or query creation, +which require specific permission. Every example uses *commons.py* module, which listing is available at `Supplementary Sources`_ section. Command-related permissions =========================== @@ -25,16 +32,44 @@ can_create_account Allows creating new `accounts <../core_concepts/glossary.html#account>`__. -Related API method: `Create Account <../api/commands.html#create-account>`__ +| Related API method: `Create Account <../api/commands.html#create-account>`__ +| Usage in Java bindings: ``Role.kCreateAccount`` +| Usage in Python bindings: ``Role_kCreateAccount`` +| + +**Example** + +| Admin creates domain "test" that contains only can_create_account permission and Alice account in that domain. Alice can create Bob account. +| + +.. literalinclude:: ../../../example/python/permissions/can_create_account.py + :language: python + :linenos: + :lines: 9-42 can_set_detail ^^^^^^^^^^^^^^ Allows setting `account <../core_concepts/glossary.html#account>`__ detail. -.. Note:: Due to a known `issue `__, permission does not take an effect. +The `permission <../core_concepts/glossary.html#permission>`__ allows setting details to other accounts. Another way to set detail without can_set_detail permission is to grant `can_set_my_account_detail`_ permission to someone. In order to grant, `transaction <../core_concepts/glossary.html#transaction>`__ creator should have `can_grant_can_set_my_account_detail`_ permission. + +.. Note:: Transaction creator can always set detail for own account even without that permission. -Related API method: `Set Account Detail <../api/commands.html#set-account-detail>`__ +| Related API method: `Set Account Detail <../api/commands.html#set-account-detail>`__ +| Usage in Java bindings: ``Role.kSetDetail`` +| Usage in Python bindings: ``Role_kSetDetail`` +| + +**Example** + +| Admin creates domain "test" that contains only can_set_detail permission and Alice account in that domain. Alice can set detail for Admin account. +| + +.. literalinclude:: ../../../example/python/permissions/can_set_detail.py + :language: python + :linenos: + :lines: 9-39 can_set_my_account_detail ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -43,9 +78,22 @@ can_set_my_account_detail `Permission <../core_concepts/glossary.html#permission>`__ that allows a specified `account <../core_concepts/glossary.html#account>`__ to set details for the another specified account. -See the example (to be done) for the usage details. +.. Note:: To grant the permission an account should already have a role with `can_grant_can_set_my_account_detail`_ permission. + +| Related API method: `Set Account Detail <../api/commands.html#set-account-detail>`__ +| Usage in Java bindings: ``Grantable.kSetMyAccountDetail`` +| Usage in Python bindings: ``Grantable_kSetMyAccountDetail`` +| -Related API method: `Set Account Detail <../api/commands.html#set-account-detail>`__ +**Example** + +| Admin creates domain "test" that contains only can_grant_can_set_my_account_detail permission and two accounts for Alice and Bob in that domain. Alice grants to Bob can_set_my_account_detail permission. Bob can set detail for Alice account. +| + +.. literalinclude:: ../../../example/python/permissions/can_set_my_account_detail.py + :language: python + :linenos: + :lines: 9-54 Asset ----- @@ -55,14 +103,40 @@ can_create_asset Allows creating new `assets <../core_concepts/glossary.html#asset>`__. -Related API method: `Create Asset <../api/commands.html#create-asset>`__ +| Related API method: `Create Asset <../api/commands.html#create-asset>`__ +| Usage in Java bindings: ``Role.kCreateAsset`` +| Usage in Python bindings: ``Role_kCreateAsset`` +| + +**Example** + +| Admin creates domain "test" that contains only can_create_asset permission and Alice account in that domain. Alice can create new assets. +| + +.. literalinclude:: ../../../example/python/permissions/can_create_asset.py + :language: python + :linenos: + :lines: 9-39 can_receive ^^^^^^^^^^^ Allows `account <../core_concepts/glossary.html#account>`__ receive `assets <../core_concepts/glossary.html#asset>`__. -Related API method: `Transfer Asset <../api/commands.html#transfer-asset>`__ +| Related API method: `Transfer Asset <../api/commands.html#transfer-asset>`__ +| Usage in Java bindings: ``Role.kReceive`` +| Usage in Python bindings: ``Role_kReceive`` +| + +**Example** + +| Admin creates domain "test" that contains can_receive and can_transfer permissions and two accounts for Alice and Bob. Admin creates "coin" asset, adds some quantity of it and transfers the asset to Alice. Alice can transfer assets to Bob (Alice has can_transfer permission and Bob has can_receive permission). +| + +.. literalinclude:: ../../../example/python/permissions/can_receive.py + :language: python + :linenos: + :lines: 9-47 can_transfer ^^^^^^^^^^^^ @@ -73,7 +147,15 @@ You can transfer an asset from one `domain <../core_concepts/glossary.html#domai .. Note:: Destination account should have `can_receive`_ permission. -Related API method: `Transfer Asset <../api/commands.html#transfer-asset>`__ +| Related API method: `Transfer Asset <../api/commands.html#transfer-asset>`__ +| Usage in Java bindings: ``Role.kTransfer`` +| Usage in Python bindings: ``Role_kTransfer`` +| + +.. literalinclude:: ../../../example/python/permissions/can_transfer.py + :language: python + :linenos: + :lines: 1-11 can_transfer_my_assets ^^^^^^^^^^^^^^^^^^^^^^ @@ -84,7 +166,20 @@ can_transfer_my_assets See the example (to be done) for the usage details. -Related API method: `Transfer Asset <../api/commands.html#transfer-asset>`__ +| Related API method: `Transfer Asset <../api/commands.html#transfer-asset>`__ +| Usage in Java bindings: ``Grantable.kTransferMyAssets`` +| Usage in Python bindings: ``Grantable_kTransferMyAssets`` +| + +**Example** + +| Admin creates domain "test" that contains can_grant_can_transfer_my_assets, can_receive, can_transfer permissions and two accounts for Alice and Bob in that domain. Admin issues some amount of "coin" asset and transfers it to Alice. Alice grants to Bob can_transfer_my_assets permission. Bob can transfer Alice's assets to any account that has can_receive permission, for example, to Admin. +| + +.. literalinclude:: ../../../example/python/permissions/can_transfer_my_assets.py + :language: python + :linenos: + :lines: 9-59 Asset Quantity -------------- @@ -96,7 +191,20 @@ Allows issuing `assets <../core_concepts/glossary.html#asset>`__. The corresponding `command <../core_concepts/glossary.html#command>`__ can be executed only for an `account <../core_concepts/glossary.html#account>`__ of `transaction <../core_concepts/glossary.html#transaction>`__ creator and only if that account has a `role <../core_concepts/glossary.html#role>`__ with the `permission <../core_concepts/glossary.html#permission>`__. -Related API method: `Add Asset Quantity <../api/commands.html#add-asset-quantity>`__ +| Related API method: `Add Asset Quantity <../api/commands.html#add-asset-quantity>`__ +| Usage in Java bindings: ``Role.kAddAssetQty`` +| Usage in Python bindings: ``Role_kAddAssetQty`` +| + +**Example** + +| Admin creates domain "test" that contains only can_add_asset_qty permission and Alice account in that domain. Admin craetes "coin" asset. Alice can add to own account any amount of any asset (e.g. "coin" asset). +| + +.. literalinclude:: ../../../example/python/permissions/can_add_asset_qty.py + :language: python + :linenos: + :lines: 9-40 can_subtract_asset_qty ^^^^^^^^^^^^^^^^^^^^^^ @@ -105,7 +213,20 @@ Allows burning `assets <../core_concepts/glossary.html#asset>`__. The corresponding `command <../core_concepts/glossary.html#command>`__ can be executed only for an `account <../core_concepts/glossary.html#account>`__ of `transaction <../core_concepts/glossary.html#transaction>`__ creator and only if that account has a `role <../core_concepts/glossary.html#role>`__ with the `permission <../core_concepts/glossary.html#permission>`__. -Related API method: `Subtract Asset Quantity <../api/commands.html#subtract-asset-quantity>`__ +| Related API method: `Subtract Asset Quantity <../api/commands.html#subtract-asset-quantity>`__ +| Usage in Java bindings: ``Role.kSubtractAssetQty`` +| Usage in Python bindings: ``Role_kSubtractAssetQty`` +| + +**Example** + +| Admin creates domain "test" that contains only can_subtract_asset_qty permission and Alice account in that domain. Admin issues some amount of "coin" asset and transfers some amount of "coin" asset to Alice. Alice can burn any amount of "coin" assets. +| + +.. literalinclude:: ../../../example/python/permissions/can_subtract_asset_qty.py + :language: python + :linenos: + :lines: 9-42 Domain ------ @@ -115,7 +236,20 @@ can_create_domain Allows creating new `domains <../core_concepts/glossary.html#domain>`__ within the system. -Related API method: `Create Domain <../api/commands.html#create-domain>`__ +| Related API method: `Create Domain <../api/commands.html#create-domain>`__ +| Usage in Java bindings: ``Role.kCreateDomain`` +| Usage in Python bindings: ``Role_kCreateDomain`` +| + +**Example** + +| Admin creates domain that contains only can_create_domain permission and Alice account in that domain. Alice can create new domains. +| + +.. literalinclude:: ../../../example/python/permissions/can_create_domain.py + :language: python + :linenos: + :lines: 9-40 Grant ----- @@ -125,35 +259,100 @@ can_grant_can_add_my_signatory Allows `role <../core_concepts/glossary.html#role>`__ owners grant `can_add_my_signatory`_ `permission <../core_concepts/glossary.html#permission>`__. -Related API method: `Grant Permission <../api/commands.html#grant-permission>`__ +| Related API method: `Grant Permission <../api/commands.html#grant-permission>`__ +| Usage in Java bindings: ``Role.kAddMySignatory`` +| Usage in Python bindings: ``Role_kAddMySignatory`` +| + +**Example** + +| Admin creates domain that contains only can_grant_can_add_my_signatory permission and two accounts for Alice and Bob in that domain. Alice can grant to Bob can_add_my_signatory permission. +| + +.. literalinclude:: ../../../example/python/permissions/can_grant_can_add_my_signatory.py + :language: python + :linenos: + :lines: 9-41 can_grant_can_remove_my_signatory ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Allows `role <../core_concepts/glossary.html#role>`__ owners grant `can_remove_my_signatory`_ `permission <../core_concepts/glossary.html#permission>`__. -Related API method: `Grant Permission <../api/commands.html#grant-permission>`__ +| Related API method: `Grant Permission <../api/commands.html#grant-permission>`__ +| Usage in Java bindings: ``Role.kRemoveMySignatory`` +| Usage in Python bindings: ``Role_kRemoveMySignatory`` +| + +**Example** + +| Admin creates domain that contains only can_grant_can_remove_my_signatory permission and two accounts for Alice and Bob in that domain. Alice can grant to Bob can_remove_my_signatory permission. +| + +.. literalinclude:: ../../../example/python/permissions/can_grant_can_remove_my_signatory.py + :language: python + :linenos: + :lines: 9-41 can_grant_can_set_my_account_detail ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Allows `role <../core_concepts/glossary.html#role>`__ owners grant `can_set_my_account_detail`_ `permission <../core_concepts/glossary.html#permission>`__. -Related API method: `Grant Permission <../api/commands.html#grant-permission>`__ +| Related API method: `Grant Permission <../api/commands.html#grant-permission>`__ +| Usage in Java bindings: ``Role.kSetMyAccountDetail`` +| Usage in Python bindings: ``Role_kSetMyAccountDetail`` +| + +**Example** + +| Admin creates domain that contains only can_grant_can_set_my_account_detail permission and two accounts for Alice and Bob in that domain. Alice can grant to Bob can_set_my_account_detail permission. +| + +.. literalinclude:: ../../../example/python/permissions/can_grant_can_set_my_account_detail.py + :language: python + :linenos: + :lines: 9-41 can_grant_can_set_my_quorum ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Allows `role <../core_concepts/glossary.html#role>`__ owners grant `can_set_my_quorum`_ `permission <../core_concepts/glossary.html#permission>`__. -Related API method: `Grant Permission <../api/commands.html#grant-permission>`__ +| Related API method: `Grant Permission <../api/commands.html#grant-permission>`__ +| Usage in Java bindings: ``Role.kSetMyQuorum`` +| Usage in Python bindings: ``Role_kSetMyQuorum`` +| + +**Example** + +| Admin creates domain that contains only can_grant_can_set_my_quorum permission and two accounts for Alice and Bob in that domain. Alice can grant to Bob can_set_my_quorum permission. +| + +.. literalinclude:: ../../../example/python/permissions/can_grant_can_set_my_quorum.py + :language: python + :linenos: + :lines: 9-41 can_grant_can_transfer_my_assets ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Allows `role <../core_concepts/glossary.html#role>`__ owners grant `can_transfer_my_assets`_ `permission <../core_concepts/glossary.html#permission>`__. -Related API method: `Grant Permission <../api/commands.html#grant-permission>`__ +| Related API method: `Grant Permission <../api/commands.html#grant-permission>`__ +| Usage in Java bindings: ``Role.kTransferMyAssets`` +| Usage in Python bindings: ``Role_kTransferMyAssets`` +| + +**Example** + +| Admin creates domain that contains only can_grant_can_transfer_my_assets permission and two accounts for Alice and Bob in that domain. Alice can grant to Bob can_transfer_my_assets permission. +| + +.. literalinclude:: ../../../example/python/permissions/can_grant_can_transfer_my_assets.py + :language: python + :linenos: + :lines: 9-48 Peer ---- @@ -165,7 +364,20 @@ Allows adding `peers <../core_concepts/glossary.html#peer>`__ to the network. A new peer will be a valid participant in the next `consensus <../core_concepts/glossary.html#consensus>`__ round after an agreement on `transaction <../core_concepts/glossary.html#transaction>`__ containing "addPeer" `command <../core_concepts/glossary.html#command>`__. -Related API method: `Add Peer <../api/commands.html#add-peer>`__ +| Related API method: `Add Peer <../api/commands.html#add-peer>`__ +| Usage in Java bindings: ``Role.kAddPeer`` +| Usage in Python bindings: ``Role_kAddPeer`` +| + +**Example** + +| Admin creates domain that contains only can_add_peer permission and Alice account in that domain. Alice can add new peers. +| + +.. literalinclude:: ../../../example/python/permissions/can_add_peer.py + :language: python + :linenos: + :lines: 9-40 Role ---- @@ -175,21 +387,67 @@ can_append_role Allows appending `roles <../core_concepts/glossary.html#role>`__ to another `account <../core_concepts/glossary.html#account>`__. -Related API method: `Append Role <../api/commands.html#append-role>`__ +You can append only that role that has lesser or the same set of privileges as `transaction <../core_concepts/glossary.html#transaction>`__ creator. + +| Related API method: `Append Role <../api/commands.html#append-role>`__ +| Usage in Java bindings: ``Role.kAppendRole`` +| Usage in Python bindings: ``Role_kAppendRole`` +| + +**Example** + +| Admin creates domian that contains can_append_role and can_add_peer permissions and two accounts for Alice and Bob in that domain. Admin creates the second role that contains only can_add_peer permission. Alice can append role to Bob. +| + +.. literalinclude:: ../../../example/python/permissions/can_append_role.py + :language: python + :linenos: + :lines: 9-48 can_create_role ^^^^^^^^^^^^^^^ Allows creating a new `role <../core_concepts/glossary.html#role>`__ within a system. -Related API method: `Create Role <../api/commands.html#create-role>`__ +Possible set of `permissions <../core_concepts/glossary.html#permission>`__ for a new role is limited to those permissions that `transaction <../core_concepts/glossary.html#transaction>`__ creator has. + + +| Related API method: `Create Role <../api/commands.html#create-role>`__ +| Usage in Java bindings: ``Role.kCreateRole`` +| Usage in Python bindings: ``Role_kCreateRole`` +| + +**Example** + +| Admin creates domain that contains only can_create_role permission and Alice account in that domain. Alice can create new roles. +| + +.. literalinclude:: ../../../example/python/permissions/can_create_role.py + :language: python + :linenos: + :lines: 9-44 can_detach_role ^^^^^^^^^^^^^^^ Allows revoking a `role <../core_concepts/glossary.html#role>`__ from a user. -Related API method: `Detach Role <../api/commands.html#detach-role>`__ +.. Note:: Due to a known issue the permission allows to detach any role without limitations https://soramitsu.atlassian.net/browse/IR-1468 + +| Related API method: `Detach Role <../api/commands.html#detach-role>`__ +| Usage in Java bindings: ``Role.kDetachRole`` +| Usage in Python bindings: ``Role_kDetachRole`` +| + +**Example** + +| Admin creates domain that contains only can_detach_role permission and creates Alice account in that domain. Admin has two roles test_role and admin_role. Alice can detach test_role from Admin account. +| + +.. literalinclude:: ../../../example/python/permissions/can_detach_role.py + :language: python + :linenos: + :lines: 9-39 Signatory --------- @@ -201,7 +459,20 @@ can_add_my_signatory `Permission <../core_concepts/glossary.html#permission>`__ that allows a specified `account <../core_concepts/glossary.html#account>`__ to add an extra public key to the another specified account. -Related API method: `Add Signatory <../api/commands.html#add-signatory>`__ +| Related API method: `Add Signatory <../api/commands.html#add-signatory>`__ +| Usage in Java bindings: ``Grantable.kAddMySignatory`` +| Usage in Python bindings: ``Grantable_kAddMySignatory`` +| + +**Example** + +| Admin creates domain that contains only can_grant_can_add_my_signatory permission and two accounts for Alice and Bob in that domain. Alice can grant to Bob can_add_my_signatory permission. Bob can add an extra key to Alice account. +| + +.. literalinclude:: ../../../example/python/permissions/can_add_my_signatory.py + :language: python + :linenos: + :lines: 9-53 can_add_signatory ^^^^^^^^^^^^^^^^^ @@ -210,7 +481,20 @@ Allows linking additional public keys to `account <../core_concepts/glossary.htm The corresponding `command <../core_concepts/glossary.html#command>`__ can be executed only for an account of `transaction <../core_concepts/glossary.html#transaction>`__ creator and only if that account has a `role <../core_concepts/glossary.html#role>`__ with the `permission <../core_concepts/glossary.html#permission>`__. -Related API method: `Add Signatory <../api/commands.html#add-signatory>`__ +| Related API method: `Add Signatory <../api/commands.html#add-signatory>`__ +| Usage in Java bindings: ``Role.kAddSignatory`` +| Usage in Python bindings: ``Role_kAddSignatory`` +| + +**Example** + +| Admin creates domain that contains only can_add_signatory permission and Alice account in that domain. Alice can add to own account additional keys. +| + +.. literalinclude:: ../../../example/python/permissions/can_add_signatory.py + :language: python + :linenos: + :lines: 9-40 can_remove_my_signatory ^^^^^^^^^^^^^^^^^^^^^^^ @@ -221,7 +505,20 @@ can_remove_my_signatory See the example (to be done) for the usage details. -Related API method: `Remove Signatory <../api/commands.html#remove-signatory>`__ +| Related API method: `Remove Signatory <../api/commands.html#remove-signatory>`__ +| Usage in Java bindings: ``Grantable.kRemoveMySignatory`` +| Usage in Python bindings: ``Grantable_kRemoveMySignatory`` +| + +**Example** + +| Admin creates domain that contains can_add_signatory and can_grant_can_remove_my_signatory permissions and two accounts for Alice and Bob. Alice grants can_remove_my_signatory permission to Bob and adds additional key to own account. Bob can remove one of Alice's keys. +| + +.. literalinclude:: ../../../example/python/permissions/can_remove_my_signatory.py + :language: python + :linenos: + :lines: 9-57 can_remove_signatory ^^^^^^^^^^^^^^^^^^^^ @@ -230,7 +527,20 @@ Allows unlinking additional public keys from an `account <../core_concepts/gloss The corresponding `command <../core_concepts/glossary.html#command>`__ can be executed only for an account of `transaction <../core_concepts/glossary.html#transaction>`__ creator and only if that account has a `role <../core_concepts/glossary.html#role>`__ with the `permission <../core_concepts/glossary.html#permission>`__. -Related API method: `Remove Signatory <../api/commands.html#remove-signatory>`__ +| Related API method: `Remove Signatory <../api/commands.html#remove-signatory>`__ +| Usage in Java bindings: ``Role.kRemoveSignatory`` +| Usage in Python bindings: ``Role_kRemoveSignatory`` +| + +**Example** + +| Admin creates domian that contains can_remove_signatory permission and Alice account in that domain. Admin adds an extra key to Alice account. Alice can remove one of the keys. +| + +.. literalinclude:: ../../../example/python/permissions/can_remove_signatory.py + :language: python + :linenos: + :lines: 9-41 can_set_my_quorum ^^^^^^^^^^^^^^^^^ @@ -239,9 +549,22 @@ can_set_my_quorum `Permission <../core_concepts/glossary.html#permission>`__ that allows a specified `account <../core_concepts/glossary.html#account>`__ to set `quorum <../core_concepts/glossary.html#quorum>`__ for the another specified account. -See the example (to be done) for the usage details. +Account should have greater or equal amount of keys than quorum. + +| Related API method: `Set Account Quorum <../api/commands.html#set-account-quorum>`__ +| Usage in Java bindings: ``Grantable.kSetMyQuorum`` +| Usage in Python bindings: ``Grantable_kSetMyQuorum`` +| + +**Example** -Related API method: `Set Account Quorum <../api/commands.html#set-account-quorum>`__ +| Admin creates domain that contains can_grant_can_set_my_quorum and can_add_signatory permissions and create two accounts for Alice and Bob in that domain. Alice grants to Bob can_set_my_qourum permission and adds an extra key to account. Bob can set quorum for Alice. +| + +.. literalinclude:: ../../../example/python/permissions/can_set_my_quorum.py + :language: python + :linenos: + :lines: 9-57 can_set_quorum ^^^^^^^^^^^^^^ @@ -250,7 +573,20 @@ Allows setting `quorum <../core_concepts/glossary.html#quorum>`__. At least the same number (or more) of public keys should be already linked to an `account <../core_concepts/glossary.html#account>`__. -Related API method: `Set Account Quorum <../api/commands.html#set-account-quorum>`__ +| Related API method: `Set Account Quorum <../api/commands.html#set-account-quorum>`__ +| Usage in Java bindings: ``Role.kSetQuorum`` +| Usage in Python bindings: ``Role_kSetQuorum`` +| + +**Example** + +| Admin creates domain that contains only can_set_quorum permission and creates Alice account in that domain. Admin adds an extra key for Alice account. Alice can set quorum equals two. +| + +.. literalinclude:: ../../../example/python/permissions/can_set_quorum.py + :language: python + :linenos: + :lines: 9-42 Query-related permissions ========================= @@ -263,7 +599,20 @@ can_get_all_acc_detail Allows getting all the details set to any `account <../core_concepts/glossary.html#account>`__ within the system. -Related API method: To be done +| Related API method: To be done +| Usage in Java bindings: ``Role.kGetAllAccDetail`` +| Usage in Python bindings: ``Role_kGetAllAccDetail`` +| + +**Example** + +| Admin creates Alice account in a diffrerent domain that has only can_get_all_acc_detail permission. Alice can access details set to Admin account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_all_acc_detail.py + :language: python + :linenos: + :lines: 9-40 can_get_all_accounts ^^^^^^^^^^^^^^^^^^^^ @@ -274,14 +623,40 @@ With this `permission <../core_concepts/glossary.html#permission>`__, `query <.. All the details (set by the account owner or owners of other accounts) will be returned. -Related API method: `Get Account <../api/queries.html#get-account>`__ +| Related API method: `Get Account <../api/queries.html#get-account>`__ +| Usage in Java bindings: ``Role.kGetAllAccounts`` +| Usage in Python bindings: ``Role_kGetAllAccounts`` +| + +**Example** + +| Admin creates Alice account in a different domain that has only can_get_all_accounts permission. Alice can access account information of Admin. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_all_accounts.py + :language: python + :linenos: + :lines: 9-40 can_get_domain_acc_detail ^^^^^^^^^^^^^^^^^^^^^^^^^ Allows getting all the details set to any `account <../core_concepts/glossary.html#account>`__ within the same `domain <../core_concepts/glossary.html#domain>`__ as a domain of `query <../core_concepts/glossary.html#query>`__ creator account. -Related API method: To be done +| Related API method: To be done +| Usage in Java bindings: ``Role.kGetDomainAccDetail`` +| Usage in Python bindings: ``Role_kGetDomainAccDetail`` +| + +**Example** + +| Admin creates Alice account in the same domain that has only can_get_domain_acc_detail permission. Alice can get details set to Admin account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_domain_acc_detail.py + :language: python + :linenos: + :lines: 9-39 can_get_domain_accounts ^^^^^^^^^^^^^^^^^^^^^^^ @@ -292,14 +667,40 @@ With this `permission <../core_concepts/glossary.html#permission>`__, `query <.. All the details (set by the account owner or owners of other accounts) will be returned. -Related API method: `Get Account <../api/queries.html#get-account>`__ +| Related API method: `Get Account <../api/queries.html#get-account>`__ +| Usage in Java bindings: ``Role.kGetDomainAccounts`` +| Usage in Python bindings: ``Role_kGetDomainAccounts`` +| + +**Example** + +| Admin creates Alice account in the same domain that has only can_get_domain_accounts. Alice can access account information of Admin. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_domain_accounts.py + :language: python + :linenos: + :lines: 9-39 can_get_my_acc_detail ^^^^^^^^^^^^^^^^^^^^^ Allows getting all the details set to the `account <../core_concepts/glossary.html#account>`__ of `query <../core_concepts/glossary.html#query>`__ creator. -Related API method: To be done +| Related API method: To be done +| Usage in Java bindings: ``Role.kGetMyAccDetail`` +| Usage in Python bindings: ``Role_kGetMyAccDetail`` +| + +**Example** + +| Admin creates Alice account in the domain that has only can_get_my_acc_detail permission. Alice can get details set to own account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_my_acc_detail.py + :language: python + :linenos: + :lines: 9-39 can_get_my_account ^^^^^^^^^^^^^^^^^^ @@ -310,7 +711,20 @@ With this `permission <../core_concepts/glossary.html#permission>`__, `query <.. All the details (set by the account owner or owners of other accounts) will be returned. -Related API method: `Get Account <../api/queries.html#get-account>`__ +| Related API method: `Get Account <../api/queries.html#get-account>`__ +| Usage in Java bindings: ``Role.kGetMyAccount`` +| Usage in Python bindings: ``Role_kGetMyAccount`` +| + +**Example** + +| Admin creates Alice account in the domain that has only can_get_my_account permission. Alice can access own account information. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_my_account.py + :language: python + :linenos: + :lines: 9-39 Account Asset ------------- @@ -318,23 +732,68 @@ Account Asset can_get_all_acc_ast ^^^^^^^^^^^^^^^^^^^ -Allows getting a balance of specified `asset <../core_concepts/glossary.html#asset>`__ on any `account <../core_concepts/glossary.html#account>`__ within the system. +Allows getting a balance of `assets <../core_concepts/glossary.html#asset>`__ on any `account <../core_concepts/glossary.html#account>`__ within the system. + +`Query <../core_concepts/glossary.html#query>`__ response will contain information about all the assets that ever been assigned to an account. + +| Related API method: `Get Account Assets <../api/queries.html#get-account-assets>`__ +| Usage in Java bindings: ``Role.kGetAllAccAst`` +| Usage in Python bindings: ``Role_kGetAllAccAst`` +| + +**Example** -Related API method: `Get Account Assets <../api/queries.html#get-account-assets>`__ +| Admin creates Alice account in a different domain that has only can_get_all_acc_ast permission. Alice can access assets balance on Admin account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_all_acc_ast.py + :language: python + :linenos: + :lines: 9-40 can_get_domain_acc_ast ^^^^^^^^^^^^^^^^^^^^^^ Allows getting a balance of specified `asset <../core_concepts/glossary.html#asset>`__ on any `account <../core_concepts/glossary.html#account>`__ within the same `domain <../core_concepts/glossary.html#domain>`__ as a domain of `query <../core_concepts/glossary.html#query>`__ creator account. -Related API method: `Get Account Assets <../api/queries.html#get-account-assets>`__ +Query response will contain information about all the assets that ever been assigned to an account. + +| Related API method: `Get Account Assets <../api/queries.html#get-account-assets>`__ +| Usage in Java bindings: ``Role.kGetDomainAccAst`` +| Usage in Python bindings: ``Role_kGetDomainAccAst`` +| + +**Example** + +| Admin creates Alice account in the same domain that has only can_get_domain_acc_ast permission. Alice can access assets balance on Admin account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_domain_acc_ast.py + :language: python + :linenos: + :lines: 9-39 can_get_my_acc_ast ^^^^^^^^^^^^^^^^^^ Allows getting a balance of specified `asset <../core_concepts/glossary.html#asset>`__ on `account <../core_concepts/glossary.html#account>`__ of `query <../core_concepts/glossary.html#query>`__ creator. -Related API method: `Get Account Assets <../api/queries.html#get-account-assets>`__ +Query response will contain information about all the assets that ever been assigned to an account. + +| Related API method: `Get Account Assets <../api/queries.html#get-account-assets>`__ +| Usage in Java bindings: ``Role.kGetMyAccAst`` +| Usage in Python bindings: ``Role_kGetMyAccAst`` +| + +**Example** + +| Admin creates Alice account in a domain that has only can_get_my_acc_ast permission. Alice can access assets balance on own account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_my_acc_ast.py + :language: python + :linenos: + :lines: 9-39 Account Asset Transaction ------------------------- @@ -344,27 +803,66 @@ can_get_all_acc_ast_txs Allows getting `transactions <../core_concepts/glossary.html#transaction>`__ associated with a specified `asset <../core_concepts/glossary.html#asset>`__ and any `account <../core_concepts/glossary.html#account>`__ within the system. -.. Note:: Incoming asset transfers will also appear in the command output. +.. Note:: Incoming asset transfers will also appear in the query response. + +| Related API method: `Get Account Asset Transactions <../api/queries.html#get-account-asset-transactions>`__ +| Usage in Java bindings: ``Role.kGetAllAccAstTxs`` +| Usage in Python bindings: ``Role_kGetAllAccAstTxs`` +| -Related API method: `Get Account Asset Transactions <../api/queries.html#get-account-asset-transactions>`__ +**Example** + +| Admin creates Alice account in a different domain that has can_get_all_acc_ast_txs, can_receive and can_transfer permissions. Admin issues some amount of coins and transfers them to Alice. Alice can query all transactions related to coins and Admin account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_all_acc_ast_txs.py + :language: python + :linenos: + :lines: 9-47 can_get_domain_acc_ast_txs ^^^^^^^^^^^^^^^^^^^^^^^^^^ Allows getting `transactions <../core_concepts/glossary.html#transaction>`__ associated with a specified `asset <../core_concepts/glossary.html#asset>`__ and an `account <../core_concepts/glossary.html#account>`__ from the same `domain <../core_concepts/glossary.html#domain>`__ as `query <../core_concepts/glossary.html#query>`__ creator. -.. Note:: Incoming asset transfers will also appear in the command output. +.. Note:: Incoming asset transfers will also appear in the query response. + +| Related API method: `Get Account Asset Transactions <../api/queries.html#get-account-asset-transactions>`__ +| Usage in Java bindings: ``Role.kGetDomainAccAstTxs`` +| Usage in Python bindings: ``Role_kGetDomainAccAstTxs`` +| + +**Example** -Related API method: `Get Account Asset Transactions <../api/queries.html#get-account-asset-transactions>`__ +| Admin creates Alice in the same domain that has only can_get_domain_acc_ast_txs permission. Admin issues some amount of coins and transfers them to Alice. Alice can query all transactions related to coins and Admin account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_domain_acc_ast_txs.py + :language: python + :linenos: + :lines: 9-42 can_get_my_acc_ast_txs ^^^^^^^^^^^^^^^^^^^^^^ Allows getting `transactions <../core_concepts/glossary.html#transaction>`__ associated with the `account <../core_concepts/glossary.html#account>`__ of `query <../core_concepts/glossary.html#query>`__ creator and specified `asset <../core_concepts/glossary.html#asset>`__. -.. Note:: Incoming asset transfers will also appear in the command output. +.. Note:: Incoming asset transfers will also appear in the query response. + +| Related API method: `Get Account Asset Transactions <../api/queries.html#get-account-asset-transactions>`__ +| Usage in Java bindings: ``Role.kGetMyAccAstTxs`` +| Usage in Python bindings: ``Role_kGetMyAccAstTxs`` +| + +**Example** -Related API method: `Get Account Asset Transactions <../api/queries.html#get-account-asset-transactions>`__ +| Admin creates Alice account in a domain that has only can_get_my_acc_ast_txs permission. Admin issues some amount of coins and transfers them to Alice. Alice can query all transactions related to coins and own account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_my_acc_ast_txs.py + :language: python + :linenos: + :lines: 9-42 Account Transaction ------------------- @@ -376,7 +874,20 @@ Allows getting all `transactions <../core_concepts/glossary.html#transaction>`__ .. Note:: Incoming asset transfer inside a transaction would NOT lead to an appearance of the transaction in the command output. -Related API method: `Get Account Asset Transactions <../api/queries.html#get-account-asset-transactions>`__ +| Related API method: `Get Account Transactions <../api/queries.html#get-account-transactions>`__ +| Usage in Java bindings: ``Role.kGetAllAccTxs`` +| Usage in Python bindings: ``Role_kGetAllAccTxs`` +| + +**Example** + +| Admin creates Alice account in a different domain that has only can_get_all_acc_txs permiison. Alice can request all the transactions issues by Admin. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_all_acc_txs.py + :language: python + :linenos: + :lines: 9-40 can_get_domain_acc_txs ^^^^^^^^^^^^^^^^^^^^^^ @@ -385,7 +896,20 @@ Allows getting all `transactions <../core_concepts/glossary.html#transaction>`__ .. Note:: Incoming asset transfer inside a transaction would NOT lead to an appearance of the transaction in the command output. -Related API method: `Get Account Asset Transactions <../api/queries.html#get-account-asset-transactions>`__ +| Related API method: `Get Account Transactions <../api/queries.html#get-account-transactions>`__ +| Usage in Java bindings: ``Role.kGetDomainAccTxs`` +| Usage in Python bindings: ``Role_kGetDomainAccTxs`` +| + +**Example** + +| Admin creates Alice account in the same domain that has only can_get_domain_acc_txs permission. Alice can request all the transactions issued by Admin. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_domain_acc_txs.py + :language: python + :linenos: + :lines: 9-39 can_get_my_acc_txs ^^^^^^^^^^^^^^^^^^ @@ -394,7 +918,20 @@ Allows getting all `transactions <../core_concepts/glossary.html#transaction>`__ .. Note:: Incoming asset transfer inside a transaction would NOT lead to an appearance of the transaction in the command output. -Related API method: `Get Account Asset Transactions <../api/queries.html#get-account-asset-transactions>`__ +| Related API method: `Get Account Transactions <../api/queries.html#get-account-transactions>`__ +| Usage in Java bindings: ``Role.kGetMyAccTxs`` +| Usage in Python bindings: ``Role_kGetMyAccTxs`` +| + +**Example** + +| Admin creates Alice account in a domain that has only can_get_my_acc_txs permission. Alice can get all transactions issued by own account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_my_acc_txs.py + :language: python + :linenos: + :lines: 9-39 Asset ----- @@ -404,7 +941,20 @@ can_read_assets Allows getting information about `asset <../core_concepts/glossary.html#asset>`__ precision. -Related API method: `Get Asset Info <../api/queries.html#get-asset-info>`__ +| Related API method: `Get Asset Info <../api/queries.html#get-asset-info>`__ +| Usage in Java bindings: ``Role.kReadAssets`` +| Usage in Python bindings: ``Role_kReadAssets`` +| + +**Example** + +| Admin creates Alice account in a domain that has can_read_assets permissions. Alice can query information about any asset. +| + +.. literalinclude:: ../../../example/python/permissions/can_read_assets.py + :language: python + :linenos: + :lines: 9-40 Block Stream ------------ @@ -412,7 +962,11 @@ Block Stream can_get_blocks ^^^^^^^^^^^^^^ -Not implemented now. Allows subscription to the stream of accepted `blocks <../core_concepts/glossary.html#block>`__. +Allows subscription to the stream of accepted `blocks <../core_concepts/glossary.html#block>`__. + +| Usage in Java bindings: ``Role.kGetBlocks`` +| Usage in Python bindings: ``Role_kGetBlocks`` +| Role ---- @@ -423,7 +977,20 @@ can_get_roles Allows getting a list of `roles <../core_concepts/glossary.html#role>`__ within the system. Allows getting a list of `permissions <../core_concepts/glossary.html#permission>`__ associated with a role. -Related API methods: `Get Roles <../api/queries.html#get-roles>`__, `Get Role Permissions <../api/queries.html#get-role-permissions>`__ +| Related API methods: `Get Roles <../api/queries.html#get-roles>`__, `Get Role Permissions <../api/queries.html#get-role-permissions>`__ +| Usage in Java bindings: ``Role.kGetRoles`` +| Usage in Python bindings: ``Role_kGetRoles`` +| + +**Example** + +| Admin creates Alice account in a domain that has can_get_roles permission. Alice can query list of all existing roles. Alice can query list of permissions contained in any role. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_roles.py + :language: python + :linenos: + :lines: 9-52 Signatory --------- @@ -433,21 +1000,60 @@ can_get_all_signatories Allows getting a list of public keys linked to an `account <../core_concepts/glossary.html#account>`__ within the system. -Related API method: `Get Signatories <../api/queries.html#get-signatories>`__ +| Related API method: `Get Signatories <../api/queries.html#get-signatories>`__ +| Usage in Java bindings: ``Role.kGetAllSignatories`` +| Usage in Python bindings: ``Role_kGetAllSignatories`` +| + +**Example** + +| Admin creates Alice account in a different domain that has only can_get_all_signatories permission. Alice can query a list of public keys related to Admin account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_all_signatories.py + :language: python + :linenos: + :lines: 9-40 can_get_domain_signatories ^^^^^^^^^^^^^^^^^^^^^^^^^^ Allows getting a list of public keys of any `account <../core_concepts/glossary.html#account>`__ within the same `domain <../core_concepts/glossary.html#domain>`__ as the domain of `query <../core_concepts/glossary.html#query>`__ creator account. -Related API method: `Get Signatories <../api/queries.html#get-signatories>`__ +| Related API method: `Get Signatories <../api/queries.html#get-signatories>`__ +| Usage in Java bindings: ``Role.kGetDomainSignatories`` +| Usage in Python bindings: ``Role_kGetDomainSignatories`` +| + +**Example** + +| Admin creates Alice account in the same domain that has only can_get_domain_signatories permission. Alice can query a list of public keys related to Admin account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_domain_signatories.py + :language: python + :linenos: + :lines: 9-39 can_get_my_signatories ^^^^^^^^^^^^^^^^^^^^^^ Allows getting a list of public keys of `query <../core_concepts/glossary.html#query>`__ creator `account <../core_concepts/glossary.html#account>`__. -Related API method: `Get Signatories <../api/queries.html#get-signatories>`__ +| Related API method: `Get Signatories <../api/queries.html#get-signatories>`__ +| Usage in Java bindings: ``Role.kGetMySignatories`` +| Usage in Python bindings: ``Role_kGetMySignatories`` +| + +**Example** + +| Admin creates Alice account in a domain that has only can_get_my_signatories permission. Alice can query a list of public keys related to own account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_my_signatories.py + :language: python + :linenos: + :lines: 9-39 Transaction ----------- @@ -457,11 +1063,50 @@ can_get_all_txs Allows getting any `transaction <../core_concepts/glossary.html#transaction>`__ by hash. -Related API method: `Get Transactions <../api/queries.html#get-transactions>`__ +| Related API method: `Get Transactions <../api/queries.html#get-transactions>`__ +| Usage in Java bindings: ``Role.kGetAllTxs`` +| Usage in Python bindings: ``Role_kGetAllTxs`` +| + +**Example** + +| Admin issues several transactions and creates Alice account in a different domain that has only can_get_all_txs permission. Alice (knowing transactions hashes) can query transactions issued by Admin Account. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_all_txs.py + :language: python + :linenos: + :lines: 9-72 can_get_my_txs ^^^^^^^^^^^^^^ Allows getting `transaction <../core_concepts/glossary.html#transaction>`__ (that was issued by `query <../core_concepts/glossary.html#query>`__ creator) by hash. -Related API method: `Get Transactions <../api/queries.html#get-transactions>`__ +| Related API method: `Get Transactions <../api/queries.html#get-transactions>`__ +| Usage in Java bindings: ``Role.kGetMyTxs`` +| Usage in Python bindings: ``Role_kGetMyTxs`` +| + +**Example** + +| Admin creates Alice account in a different domain. Alice (knowing transactions hashes) issues several transactions. Alice can query own transactions. +| + +.. literalinclude:: ../../../example/python/permissions/can_get_my_txs.py + :language: python + :linenos: + :lines: 9-76 + +Supplementary Sources +===================== + +.. literalinclude:: ../../../example/python/permissions/commons.py + :language: python + :linenos: + :caption: commons.py + +.. literalinclude:: ../../permissions_compiler/consts.py + :language: python + :linenos: + :caption: consts.py diff --git a/docs/source/permissions/introduction.txt b/docs/source/permissions/introduction.txt new file mode 100644 index 0000000000..a12e0c7ad7 --- /dev/null +++ b/docs/source/permissions/introduction.txt @@ -0,0 +1,13 @@ +Hyperledger Iroha uses a role-based access control system to limit actions of its users. +This system greatly helps to implement use cases involving user groups having different access levels — +ranging from the weak users, who can't even receive asset transfer to the super-users. +The beauty of our permission system is that you don't have to have a super-user +in your Iroha setup or use all the possible permissions: you can create segregated and lightweight roles. + +Maintenance of the system involves setting up roles and permissions, that are included in the roles. +This might be done at the initial step of system deployment — in genesis block, +or later when Iroha network is up and running, roles can be changed (if there is a role that can do that :) + +This section will help you to understand permissions and give you an idea of how to create roles including certain permissions. +Each permission is provided with an example written in Python that demonstrates the way of transaction or query creation, +which require specific permission. Every example uses *commons.py* module, which listing is available at `Supplementary Sources`_ section. diff --git a/docs/source/permissions/matrix.csv b/docs/source/permissions/matrix.csv new file mode 100644 index 0000000000..00638a4de6 --- /dev/null +++ b/docs/source/permissions/matrix.csv @@ -0,0 +1,59 @@ +Type,Category,Permission,Grantable,Only for tx creator,Description,Additional Information,Note,Related Command,Example +Command,Account,can_create_account,FALSE,FALSE,Allows creating new accounts.,,,http://iroha.readthedocs.io/en/latest/api/commands.html#create-account,"Admin creates domain ""test"" that contains only can_create_account permission and Alice account in that domain. Alice can create Bob account." +Command,Account,can_set_detail,FALSE,TRUE,Allows setting account detail.,"The permission allows setting details to other accounts. Another way to set detail without can_set_detail permission is to grant `can_set_my_account_detail`_ permission to someone. In order to grant, transaction creator should have `can_grant_can_set_my_account_detail`_ permission.",Transaction creator can always set detail for own account even without that permission.,http://iroha.readthedocs.io/en/latest/api/commands.html#set-account-detail,"Admin creates domain ""test"" that contains only can_set_detail permission and Alice account in that domain. Alice can set detail for Admin account." +Command,Account,can_set_my_account_detail,TRUE,,Permission that allows a specified account to set details for the another specified account., ,To grant the permission an account should already have a role with `can_grant_can_set_my_account_detail`_ permission.,http://iroha.readthedocs.io/en/latest/api/commands.html#set-account-detail,"Admin creates domain ""test"" that contains only can_grant_can_set_my_account_detail permission and two accounts for Alice and Bob in that domain. Alice grants to Bob can_set_my_account_detail permission. Bob can set detail for Alice account." +Command,Asset,can_create_asset,FALSE,FALSE,Allows creating new assets.,,,http://iroha.readthedocs.io/en/latest/api/commands.html#create-asset,"Admin creates domain ""test"" that contains only can_create_asset permission and Alice account in that domain. Alice can create new assets." +Command,Asset,can_receive,FALSE,,Allows account receive assets.,,,http://iroha.readthedocs.io/en/latest/api/commands.html#transfer-asset,"Admin creates domain ""test"" that contains can_receive and can_transfer permissions and two accounts for Alice and Bob. Admin creates ""coin"" asset, adds some quantity of it and transfers the asset to Alice. Alice can transfer assets to Bob (Alice has can_transfer permission and Bob has can_receive permission)." +Command,Asset,can_transfer,FALSE,FALSE,Allows sending assets from an account of transaction creator.,"You can transfer an asset from one domain to another, even if the other domain does not have an asset with the same name.",Destination account should have `can_receive`_ permission.,http://iroha.readthedocs.io/en/latest/api/commands.html#transfer-asset, +Command,Asset,can_transfer_my_assets,TRUE,,Permission that allows a specified account to transfer assets of another specified account.,See the example (to be done) for the usage details., ,http://iroha.readthedocs.io/en/latest/api/commands.html#transfer-asset,"Admin creates domain ""test"" that contains can_grant_can_transfer_my_assets, can_receive, can_transfer permissions and two accounts for Alice and Bob in that domain. Admin issues some amount of ""coin"" asset and transfers it to Alice. Alice grants to Bob can_transfer_my_assets permission. Bob can transfer Alice's assets to any account that has can_receive permission, for example, to Admin." +Command,Asset Quantity,can_add_asset_qty,FALSE,TRUE,Allows issuing assets.,The corresponding command can be executed only for an account of transaction creator and only if that account has a role with the permission.,,http://iroha.readthedocs.io/en/latest/api/commands.html#add-asset-quantity,"Admin creates domain ""test"" that contains only can_add_asset_qty permission and Alice account in that domain. Admin craetes ""coin"" asset. Alice can add to own account any amount of any asset (e.g. ""coin"" asset)." +Command,Asset Quantity,can_subtract_asset_qty,FALSE,TRUE,Allows burning assets.,The corresponding command can be executed only for an account of transaction creator and only if that account has a role with the permission.,,http://iroha.readthedocs.io/en/latest/api/commands.html#subtract-asset-quantity,"Admin creates domain ""test"" that contains only can_subtract_asset_qty permission and Alice account in that domain. Admin issues some amount of ""coin"" asset and transfers some amount of ""coin"" asset to Alice. Alice can burn any amount of ""coin"" assets." +Command,Domain,can_create_domain,FALSE,,Allows creating new domains within the system.,,,http://iroha.readthedocs.io/en/latest/api/commands.html#create-domain,Admin creates domain that contains only can_create_domain permission and Alice account in that domain. Alice can create new domains. +Command,Grant,can_grant_can_add_my_signatory,FALSE,,Allows role owners grant `can_add_my_signatory`_ permission.,,,http://iroha.readthedocs.io/en/latest/api/commands.html#grant-permission,Admin creates domain that contains only can_grant_can_add_my_signatory permission and two accounts for Alice and Bob in that domain. Alice can grant to Bob can_add_my_signatory permission. +Command,Grant,can_grant_can_remove_my_signatory,FALSE,,Allows role owners grant `can_remove_my_signatory`_ permission.,,,http://iroha.readthedocs.io/en/latest/api/commands.html#grant-permission,Admin creates domain that contains only can_grant_can_remove_my_signatory permission and two accounts for Alice and Bob in that domain. Alice can grant to Bob can_remove_my_signatory permission. +Command,Grant,can_grant_can_set_my_account_detail,FALSE,,Allows role owners grant `can_set_my_account_detail`_ permission.,,,http://iroha.readthedocs.io/en/latest/api/commands.html#grant-permission,Admin creates domain that contains only can_grant_can_set_my_account_detail permission and two accounts for Alice and Bob in that domain. Alice can grant to Bob can_set_my_account_detail permission. +Command,Grant,can_grant_can_set_my_quorum,FALSE,,Allows role owners grant `can_set_my_quorum`_ permission.,,,http://iroha.readthedocs.io/en/latest/api/commands.html#grant-permission,Admin creates domain that contains only can_grant_can_set_my_quorum permission and two accounts for Alice and Bob in that domain. Alice can grant to Bob can_set_my_quorum permission. +Command,Grant,can_grant_can_transfer_my_assets,FALSE,,Allows role owners grant `can_transfer_my_assets`_ permission.,,,http://iroha.readthedocs.io/en/latest/api/commands.html#grant-permission,Admin creates domain that contains only can_grant_can_transfer_my_assets permission and two accounts for Alice and Bob in that domain. Alice can grant to Bob can_transfer_my_assets permission. +Command,Peer,can_add_peer,FALSE,,Allows adding peers to the network.,"A new peer will be a valid participant in the next consensus round after an agreement on transaction containing ""addPeer"" command.",,http://iroha.readthedocs.io/en/latest/api/commands.html#add-peer,Admin creates domain that contains only can_add_peer permission and Alice account in that domain. Alice can add new peers. +Command,Role,can_append_role,FALSE,,Allows appending roles to another account.,You can append only that role that has lesser or the same set of privileges as transaction creator.,,http://iroha.readthedocs.io/en/latest/api/commands.html#append-role,Admin creates domian that contains can_append_role and can_add_peer permissions and two accounts for Alice and Bob in that domain. Admin creates the second role that contains only can_add_peer permission. Alice can append role to Bob. +Command,Role,can_create_role,FALSE,,Allows creating a new role within a system.,"Possible set of permissions for a new role is limited to those permissions that transaction creator has. +",,http://iroha.readthedocs.io/en/latest/api/commands.html#create-role,Admin creates domain that contains only can_create_role permission and Alice account in that domain. Alice can create new roles. +Command,Role,can_detach_role,FALSE,,Allows revoking a role from a user.,,Due to a known issue the permission allows to detach any role without limitations https://soramitsu.atlassian.net/browse/IR-1468,http://iroha.readthedocs.io/en/latest/api/commands.html#detach-role,Admin creates domain that contains only can_detach_role permission and creates Alice account in that domain. Admin has two roles test_role and admin_role. Alice can detach test_role from Admin account. +Command,Signatory,can_add_my_signatory,TRUE,,Permission that allows a specified account to add an extra public key to the another specified account.,,,http://iroha.readthedocs.io/en/latest/api/commands.html#add-signatory,Admin creates domain that contains only can_grant_can_add_my_signatory permission and two accounts for Alice and Bob in that domain. Alice can grant to Bob can_add_my_signatory permission. Bob can add an extra key to Alice account. +Command,Signatory,can_add_signatory,FALSE,FALSE,Allows linking additional public keys to account.,The corresponding command can be executed only for an account of transaction creator and only if that account has a role with the permission.,,http://iroha.readthedocs.io/en/latest/api/commands.html#add-signatory,Admin creates domain that contains only can_add_signatory permission and Alice account in that domain. Alice can add to own account additional keys. +Command,Signatory,can_remove_my_signatory,TRUE,,Permission that allows a specified account remove public key from the another specified account.,See the example (to be done) for the usage details.,,http://iroha.readthedocs.io/en/latest/api/commands.html#remove-signatory,Admin creates domain that contains can_add_signatory and can_grant_can_remove_my_signatory permissions and two accounts for Alice and Bob. Alice grants can_remove_my_signatory permission to Bob and adds additional key to own account. Bob can remove one of Alice's keys. +Command,Signatory,can_remove_signatory,FALSE,FALSE,Allows unlinking additional public keys from an account.,The corresponding command can be executed only for an account of transaction creator and only if that account has a role with the permission.,,http://iroha.readthedocs.io/en/latest/api/commands.html#remove-signatory,Admin creates domian that contains can_remove_signatory permission and Alice account in that domain. Admin adds an extra key to Alice account. Alice can remove one of the keys. +Command,Signatory,can_set_my_quorum,TRUE,,Permission that allows a specified account to set quorum for the another specified account.,Account should have greater or equal amount of keys than quorum. ,,http://iroha.readthedocs.io/en/latest/api/commands.html#set-account-quorum,Admin creates domain that contains can_grant_can_set_my_quorum and can_add_signatory permissions and create two accounts for Alice and Bob in that domain. Alice grants to Bob can_set_my_qourum permission and adds an extra key to account. Bob can set quorum for Alice. +Command,Signatory,can_set_quorum,FALSE,FALSE,Allows setting quorum.,At least the same number (or more) of public keys should be already linked to an account.,,http://iroha.readthedocs.io/en/latest/api/commands.html#set-account-quorum,Admin creates domain that contains only can_set_quorum permission and creates Alice account in that domain. Admin adds an extra key for Alice account. Alice can set quorum equals two. +Query,Account,can_get_all_acc_detail,FALSE,,Allows getting all the details set to any account within the system.,,,tbd GetAccountDetail,Admin creates Alice account in a diffrerent domain that has only can_get_all_acc_detail permission. Alice can access details set to Admin account. +Query,Account,can_get_all_accounts,FALSE,,"Allows getting account information: quorum and all the details related to the account. + +With this permission, query creator can get information about any account within a system.",All the details (set by the account owner or owners of other accounts) will be returned.,,http://iroha.readthedocs.io/en/latest/api/queries.html#get-account,Admin creates Alice account in a different domain that has only can_get_all_accounts permission. Alice can access account information of Admin. +Query,Account,can_get_domain_acc_detail,FALSE,,Allows getting all the details set to any account within the same domain as a domain of query creator account.,,,tbd GetAccountDetail,Admin creates Alice account in the same domain that has only can_get_domain_acc_detail permission. Alice can get details set to Admin account. +Query,Account,can_get_domain_accounts,FALSE,,"Allows getting account information: quorum and all the details related to the account. + +With this permission, query creator can get information only about accounts from the same domain.",All the details (set by the account owner or owners of other accounts) will be returned.,,http://iroha.readthedocs.io/en/latest/api/queries.html#get-account,Admin creates Alice account in the same domain that has only can_get_domain_accounts. Alice can access account information of Admin. +Query,Account,can_get_my_acc_detail,FALSE,,Allows getting all the details set to the account of query creator.,,,tbd GetAccountDetail,Admin creates Alice account in the domain that has only can_get_my_acc_detail permission. Alice can get details set to own account. +Query,Account,can_get_my_account,FALSE,,"Allows getting account information: quorum and all the details related to the account. + +With this permission, query creator can get information only about own account.",All the details (set by the account owner or owners of other accounts) will be returned.,,http://iroha.readthedocs.io/en/latest/api/queries.html#get-account,Admin creates Alice account in the domain that has only can_get_my_account permission. Alice can access own account information. +Query,Account Asset,can_get_all_acc_ast,FALSE,,Allows getting a balance of assets on any account within the system.,Query response will contain information about all the assets that ever been assigned to an account.,,http://iroha.readthedocs.io/en/latest/api/queries.html#get-account-assets,Admin creates Alice account in a different domain that has only can_get_all_acc_ast permission. Alice can access assets balance on Admin account. +Query,Account Asset,can_get_domain_acc_ast,FALSE,,Allows getting a balance of specified asset on any account within the same domain as a domain of query creator account.,Query response will contain information about all the assets that ever been assigned to an account.,,http://iroha.readthedocs.io/en/latest/api/queries.html#get-account-assets,Admin creates Alice account in the same domain that has only can_get_domain_acc_ast permission. Alice can access assets balance on Admin account. +Query,Account Asset,can_get_my_acc_ast,FALSE,,Allows getting a balance of specified asset on account of query creator.,Query response will contain information about all the assets that ever been assigned to an account.,,http://iroha.readthedocs.io/en/latest/api/queries.html#get-account-assets,Admin creates Alice account in a domain that has only can_get_my_acc_ast permission. Alice can access assets balance on own account. +Query,Account Asset Transaction,can_get_all_acc_ast_txs,FALSE,,Allows getting transactions associated with a specified asset and any account within the system.,,Incoming asset transfers will also appear in the query response.,http://iroha.readthedocs.io/en/latest/api/queries.html#get-account-asset-transactions,"Admin creates Alice account in a different domain that has can_get_all_acc_ast_txs, can_receive and can_transfer permissions. Admin issues some amount of coins and transfers them to Alice. Alice can query all transactions related to coins and Admin account." +Query,Account Asset Transaction,can_get_domain_acc_ast_txs,FALSE,,Allows getting transactions associated with a specified asset and an account from the same domain as query creator.,,Incoming asset transfers will also appear in the query response.,http://iroha.readthedocs.io/en/latest/api/queries.html#get-account-asset-transactions,Admin creates Alice in the same domain that has only can_get_domain_acc_ast_txs permission. Admin issues some amount of coins and transfers them to Alice. Alice can query all transactions related to coins and Admin account. +Query,Account Asset Transaction,can_get_my_acc_ast_txs,FALSE,,Allows getting transactions associated with the account of query creator and specified asset.,,Incoming asset transfers will also appear in the query response.,http://iroha.readthedocs.io/en/latest/api/queries.html#get-account-asset-transactions,Admin creates Alice account in a domain that has only can_get_my_acc_ast_txs permission. Admin issues some amount of coins and transfers them to Alice. Alice can query all transactions related to coins and own account. +Query,Account Transaction,can_get_all_acc_txs,FALSE,,Allows getting all transactions issued by any account within the system.,,Incoming asset transfer inside a transaction would NOT lead to an appearance of the transaction in the command output.,http://iroha.readthedocs.io/en/latest/api/queries.html#get-account-transactions,Admin creates Alice account in a different domain that has only can_get_all_acc_txs permiison. Alice can request all the transactions issues by Admin. +Query,Account Transaction,can_get_domain_acc_txs,FALSE,,Allows getting all transactions issued by any account from the same domain as query creator.,,Incoming asset transfer inside a transaction would NOT lead to an appearance of the transaction in the command output.,http://iroha.readthedocs.io/en/latest/api/queries.html#get-account-transactions,Admin creates Alice account in the same domain that has only can_get_domain_acc_txs permission. Alice can request all the transactions issued by Admin. +Query,Account Transaction,can_get_my_acc_txs,FALSE,,Allows getting all transactions issued by an account of query creator.,,Incoming asset transfer inside a transaction would NOT lead to an appearance of the transaction in the command output.,http://iroha.readthedocs.io/en/latest/api/queries.html#get-account-transactions,Admin creates Alice account in a domain that has only can_get_my_acc_txs permission. Alice can get all transactions issued by own account. +Query,Asset,can_read_assets,FALSE,,Allows getting information about asset precision.,,,http://iroha.readthedocs.io/en/latest/api/queries.html#get-asset-info,Admin creates Alice account in a domain that has can_read_assets permissions. Alice can query information about any asset. +Query,Block Stream,can_get_blocks,FALSE,,Allows subscription to the stream of accepted blocks.,,,, +Query,Role,can_get_roles,FALSE,,"Allows getting a list of roles within the system. +Allows getting a list of permissions associated with a role.",,,"http://iroha.readthedocs.io/en/latest/api/queries.html#get-roles + +http://iroha.readthedocs.io/en/latest/api/queries.html#get-role-permissions",Admin creates Alice account in a domain that has can_get_roles permission. Alice can query list of all existing roles. Alice can query list of permissions contained in any role. +Query,Signatory,can_get_all_signatories,FALSE,,Allows getting a list of public keys linked to an account within the system.,,,http://iroha.readthedocs.io/en/latest/api/queries.html#get-signatories,Admin creates Alice account in a different domain that has only can_get_all_signatories permission. Alice can query a list of public keys related to Admin account. +Query,Signatory,can_get_domain_signatories,FALSE,,Allows getting a list of public keys of any account within the same domain as the domain of query creator account.,,,http://iroha.readthedocs.io/en/latest/api/queries.html#get-signatories,Admin creates Alice account in the same domain that has only can_get_domain_signatories permission. Alice can query a list of public keys related to Admin account. +Query,Signatory,can_get_my_signatories,FALSE,,Allows getting a list of public keys of query creator account.,,,http://iroha.readthedocs.io/en/latest/api/queries.html#get-signatories,Admin creates Alice account in a domain that has only can_get_my_signatories permission. Alice can query a list of public keys related to own account. +Query,Transaction,can_get_all_txs,FALSE,,Allows getting any transaction by hash.,,,http://iroha.readthedocs.io/en/latest/api/queries.html#get-transactions,Admin issues several transactions and creates Alice account in a different domain that has only can_get_all_txs permission. Alice (knowing transactions hashes) can query transactions issued by Admin Account. +Query,Transaction,can_get_my_txs,FALSE,,Allows getting transaction (that was issued by query creator) by hash.,,,http://iroha.readthedocs.io/en/latest/api/queries.html#get-transactions,Admin creates Alice account in a different domain. Alice (knowing transactions hashes) issues several transactions. Alice can query own transactions. diff --git a/docs/source/use_cases/index.rst b/docs/source/use_cases/index.rst index bc273be590..dbebce0152 100644 --- a/docs/source/use_cases/index.rst +++ b/docs/source/use_cases/index.rst @@ -81,6 +81,22 @@ For example, ``gangreen`` is a registered farmer ``tomato`` asset creator, he se We simplified asset creation to just a single command ``CreateAsset`` without the need to create complex smart contracts. One the major advantages of Hyperledger Iroha is in its ease, that allows developers to focus on the provided value of their applications. +Fund Management +--------------- + +With the support of multisignature transactions it is possible to maintain a fund by many managers. In that scheme investment can only be made after the confirmation of the quorum participants. + +Example +^^^^^^^ + +The fund assets should be held at one account. +Its signatories should be fund managers, who are dealing with investments and portfolio distributions. +That can be added via ``AddSignatory`` command. +All of the assets should be held within one account, which signatories represent the fund managers. +Thus the concrete exchanges can be performed with the multisignature transaction so that everyone will decide on a particular financial decision. +The one may confirm a deal by sending the original transaction and one of managers' signature. +Iroha will maintain the transaction sending so that the deal will not be completed until it receives the required number of confirmation, which is parametrized with the transaction quorum parameter. + Related Research ---------------- diff --git a/example/admin@test.priv b/example/admin@test.priv index 87ff52af36..d858ce796c 100644 --- a/example/admin@test.priv +++ b/example/admin@test.priv @@ -1 +1 @@ -0f0ce16d2afbb8eca23c7d8c2724f0c257a800ee2bbd54688cec6b898e3f7e33 \ No newline at end of file +f101537e319568c765b2cc89698325604991dca57b9716b58016b253506cab70 \ No newline at end of file diff --git a/example/admin@test.pub b/example/admin@test.pub index 1dfda5428f..49c3bef482 100644 --- a/example/admin@test.pub +++ b/example/admin@test.pub @@ -1 +1 @@ -889f6b881e331be21487db77dcf32c5f8d3d5e8066e78d2feac4239fe91d416f \ No newline at end of file +313a07e6384776ed95447710d15e59148473ccfc052a681317a72a69f2a49910 \ No newline at end of file diff --git a/example/genesis.block b/example/genesis.block index bb30df3c22..2f10b25aca 100644 --- a/example/genesis.block +++ b/example/genesis.block @@ -3,113 +3,116 @@ "transactions":[ { "payload":{ - "commands":[ - { - "addPeer":{ - "peer":{ - "address":"localhost:10001", - "peerKey":"0E2icbV/5jQmrh3Jf2lSEEA3QR/PTztzncIX9F5fyZs=" + "reducedPayload":{ + "commands":[ + { + "addPeer":{ + "peer":{ + "address":"0.0.0.0:10001", + "peerKey":"vd1YQE0TFeDrJ5AsXXyOsGAsFiOPAFdz30BrwZEwiSk=" + } + } + }, + { + "createRole":{ + "roleName":"admin", + "permissions":[ + "can_add_peer", + "can_add_signatory", + "can_create_account", + "can_create_domain", + "can_get_all_acc_ast", + "can_get_all_acc_ast_txs", + "can_get_all_acc_detail", + "can_get_all_acc_txs", + "can_get_all_accounts", + "can_get_all_signatories", + "can_get_all_txs", + "can_get_blocks", + "can_get_roles", + "can_read_assets", + "can_remove_signatory", + "can_set_quorum" + ] + } + }, + { + "createRole":{ + "roleName":"user", + "permissions":[ + "can_add_signatory", + "can_get_my_acc_ast", + "can_get_my_acc_ast_txs", + "can_get_my_acc_detail", + "can_get_my_acc_txs", + "can_get_my_account", + "can_get_my_signatories", + "can_get_my_txs", + "can_grant_can_add_my_signatory", + "can_grant_can_remove_my_signatory", + "can_grant_can_set_my_account_detail", + "can_grant_can_set_my_quorum", + "can_grant_can_transfer_my_assets", + "can_receive", + "can_remove_signatory", + "can_set_quorum", + "can_transfer" + ] + } + }, + { + "createRole":{ + "roleName":"money_creator", + "permissions":[ + "can_add_asset_qty", + "can_create_asset", + "can_receive", + "can_transfer" + ] + } + }, + { + "createDomain":{ + "domainId":"test", + "defaultRole":"user" + } + }, + { + "createAsset":{ + "assetName":"coin", + "domainId":"test", + "precision":2 + } + }, + { + "createAccount":{ + "accountName":"admin", + "domainId":"test", + "mainPubkey":"MToH5jhHdu2VRHcQ0V5ZFIRzzPwFKmgTF6cqafKkmRA=" + } + }, + { + "createAccount":{ + "accountName":"test", + "domainId":"test", + "mainPubkey":"cW/lBfafGFEaGwg5Faqf9z7zbmaIGZ85WXUNs4uPS/w=" + } + }, + { + "appendRole":{ + "accountId":"admin@test", + "roleName":"admin" + } + }, + { + "appendRole":{ + "accountId":"admin@test", + "roleName":"money_creator" } } - }, - { - "createRole":{ - "roleName":"admin", - "permissions":[ - "can_add_peer", - "can_add_signatory", - "can_create_account", - "can_create_domain", - "can_get_all_acc_ast", - "can_get_all_acc_ast_txs", - "can_get_all_acc_detail", - "can_get_all_acc_txs", - "can_get_all_accounts", - "can_get_all_signatories", - "can_get_all_txs", - "can_get_roles", - "can_read_assets", - "can_remove_signatory", - "can_set_quorum", - "can_get_blocks" - ] - } - }, - { - "createRole":{ - "roleName":"user", - "permissions":[ - "can_add_signatory", - "can_get_my_acc_ast", - "can_get_my_acc_ast_txs", - "can_get_my_acc_detail", - "can_get_my_acc_txs", - "can_get_my_account", - "can_get_my_signatories", - "can_get_my_txs", - "can_grant_can_add_my_signatory", - "can_grant_can_remove_my_signatory", - "can_grant_can_set_my_account_detail", - "can_grant_can_set_my_quorum", - "can_grant_can_transfer_my_assets", - "can_receive", - "can_remove_signatory", - "can_set_quorum", - "can_transfer" - ] - } - }, - { - "createRole":{ - "roleName":"money_creator", - "permissions":[ - "can_add_asset_qty", - "can_create_asset", - "can_receive", - "can_transfer" - ] - } - }, - { - "createDomain":{ - "domainId":"test", - "defaultRole":"user" - } - }, - { - "createAsset":{ - "assetName":"coin", - "domainId":"test", - "precision":2 - } - }, - { - "createAccount":{ - "accountName":"admin", - "domainId":"test", - "mainPubkey":"iJ9riB4zG+IUh9t33PMsX409XoBm540v6sQjn+kdQW8=" - } - }, - { - "createAccount":{ - "accountName":"test", - "domainId":"test", - "mainPubkey":"3MfszkSPLhkKSBrsfKIe5S7aVG5mC0gg9JdtATIVcJc=" - } - }, - { - "appendRole":{ - "accountId":"admin@test", - "roleName":"admin" - } - }, - { - "appendRole":{ - "accountId":"admin@test", - "roleName":"money_creator" - } - } - ] + ], + "quorum":1 + } } } ], diff --git a/example/java/TransactionExample.java b/example/java/TransactionExample.java index cab06f8d88..e66107e7d9 100644 --- a/example/java/TransactionExample.java +++ b/example/java/TransactionExample.java @@ -1,5 +1,5 @@ import iroha.protocol.Commands; -import iroha.protocol.BlockOuterClass; +import iroha.protocol.TransactionOuterClass; import iroha.protocol.Endpoint; import iroha.protocol.Queries; import iroha.protocol.Queries.Query; @@ -12,9 +12,9 @@ import iroha.protocol.Endpoint.TxStatus; import iroha.protocol.Endpoint.TxStatusRequest; import iroha.protocol.Endpoint.ToriiResponse; -import iroha.protocol.Responses.QueryResponse; -import iroha.protocol.Responses.AssetResponse; -import iroha.protocol.Responses.Asset; +import iroha.protocol.QryResponses.QueryResponse; +import iroha.protocol.QryResponses.AssetResponse; +import iroha.protocol.QryResponses.Asset; import com.google.protobuf.InvalidProtocolBufferException; import io.grpc.ManagedChannel; @@ -84,9 +84,9 @@ public static void main(String[] args) { byte bs[] = toByteArray(txblob); // create proto object - BlockOuterClass.Transaction protoTx = null; + TransactionOuterClass.Transaction protoTx = null; try { - protoTx = BlockOuterClass.Transaction.parseFrom(bs); + protoTx = TransactionOuterClass.Transaction.parseFrom(bs); } catch (InvalidProtocolBufferException e) { System.err.println("Exception while converting byte array to protobuf:" + e.getMessage()); System.exit(1); diff --git a/example/java/build.gradle b/example/java/build.gradle index a80b9ae414..f7d8aad6bc 100644 --- a/example/java/build.gradle +++ b/example/java/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'com.google.protobuf' apply plugin: 'application' def BUILD_DIR = "./build/shared_model/bindings" -def SCHEMA_DIR = "../../schema" +def SCHEMA_DIR = ["../../schema", "../../shared_model/schema"] buildscript { repositories { @@ -38,7 +38,7 @@ sourceSets { srcDirs = ['.'] } proto { - srcDirs = [SCHEMA_DIR] + srcDirs = SCHEMA_DIR } } } diff --git a/example/node0.priv b/example/node0.priv index 1362af9c39..48c73f36e8 100644 --- a/example/node0.priv +++ b/example/node0.priv @@ -1 +1 @@ -41209bd907789fd5a796ac6bdff908bac2f7abcf7a1d0b99a18290f285f6e965 \ No newline at end of file +cc5013e43918bd0e5c4d800416c88bed77892ff077929162bb03ead40a745e88 \ No newline at end of file diff --git a/example/node0.pub b/example/node0.pub index bb7c1c9dcd..35d44a8228 100644 --- a/example/node0.pub +++ b/example/node0.pub @@ -1 +1 @@ -d04da271b57fe63426ae1dc97f6952104037411fcf4f3b739dc217f45e5fc99b \ No newline at end of file +bddd58404d1315e0eb27902c5d7c8eb0602c16238f005773df406bc191308929 \ No newline at end of file diff --git a/example/python/.gitignore b/example/python/.gitignore index dcadf0aed8..568b860e77 100644 --- a/example/python/.gitignore +++ b/example/python/.gitignore @@ -3,5 +3,7 @@ !.gitignore !tx-example.py !blocks-query.py +!permissions/ +!permissions/* !prepare.sh !README.md diff --git a/example/python/permissions/can_add_asset_qty.py b/example/python/permissions/can_add_asset_qty.py new file mode 100644 index 0000000000..afdb8d3521 --- /dev/null +++ b/example/python/permissions/can_add_asset_qty.py @@ -0,0 +1,40 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kAddAssetQty]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .createAsset('coin', 'test', 2) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def add_asset_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .addAssetQuantity('coin#test', '5000.99') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_add_my_signatory.py b/example/python/permissions/can_add_my_signatory.py new file mode 100644 index 0000000000..2b23f0a63d --- /dev/null +++ b/example/python/permissions/can_add_my_signatory.py @@ -0,0 +1,53 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kAddMySignatory]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def grant_can_add_my_signatory_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .grantPermission(bob['id'], iroha.Grantable_kAddMySignatory) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() + + +@commons.hex +def add_signatory_tx(): + extra_key = iroha.ModelCrypto().generateKeypair() + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(bob['id']) \ + .addSignatory(alice['id'], extra_key.publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(bob['key']).finish() diff --git a/example/python/permissions/can_add_peer.py b/example/python/permissions/can_add_peer.py new file mode 100644 index 0000000000..98c238f7c1 --- /dev/null +++ b/example/python/permissions/can_add_peer.py @@ -0,0 +1,40 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kAddPeer]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def add_peer_tx(): + peer_key = iroha.ModelCrypto().generateKeypair() + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .addPeer('192.168.10.10:50541', peer_key.publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_add_signatory.py b/example/python/permissions/can_add_signatory.py new file mode 100644 index 0000000000..1adcff3e9a --- /dev/null +++ b/example/python/permissions/can_add_signatory.py @@ -0,0 +1,40 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kAddSignatory]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def add_signatory_tx(): + extra_key = iroha.ModelCrypto().generateKeypair() + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .addSignatory(alice['id'], extra_key.publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_append_role.py b/example/python/permissions/can_append_role.py new file mode 100644 index 0000000000..0c9bbf778e --- /dev/null +++ b/example/python/permissions/can_append_role.py @@ -0,0 +1,48 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet( + [iroha.Role_kAppendRole, iroha.Role_kAddPeer] + ) + second_role = iroha.RolePermissionSet([iroha.Role_kAddPeer]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createRole('second_role', second_role) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .appendRole(alice['id'], 'second_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def append_role_tx(): + # Note that you can append only that role that has + # lesser or the same set of permissions as transaction creator. + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .appendRole(bob['id'], 'second_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_create_account.py b/example/python/permissions/can_create_account.py new file mode 100644 index 0000000000..bd13821bec --- /dev/null +++ b/example/python/permissions/can_create_account.py @@ -0,0 +1,42 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet( + [iroha.Role_kCreateAccount] + ) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def create_account_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_create_asset.py b/example/python/permissions/can_create_asset.py new file mode 100644 index 0000000000..6d52788dbc --- /dev/null +++ b/example/python/permissions/can_create_asset.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kCreateAsset]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def create_asset_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .createAsset('coin', 'test', 2) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_create_domain.py b/example/python/permissions/can_create_domain.py new file mode 100644 index 0000000000..06d6032edf --- /dev/null +++ b/example/python/permissions/can_create_domain.py @@ -0,0 +1,40 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kCreateDomain]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def create_domain_tx(): + # 'test_role' was created in genesis transaction + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .createDomain('another-domain', 'test_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_create_role.py b/example/python/permissions/can_create_role.py new file mode 100644 index 0000000000..474e4f2a2f --- /dev/null +++ b/example/python/permissions/can_create_role.py @@ -0,0 +1,44 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet( + [iroha.Role_kCreateRole, iroha.Role_kCreateDomain] + ) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def create_role_tx(): + # You can pick only those permissions that + # already belong to account of transaction creator. + role_permissions = iroha.RolePermissionSet([iroha.Role_kCreateDomain]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .createRole('newrole', role_permissions) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_detach_role.py b/example/python/permissions/can_detach_role.py new file mode 100644 index 0000000000..4b5cee6463 --- /dev/null +++ b/example/python/permissions/can_detach_role.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kDetachRole]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def detach_role_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .detachRole(admin['id'], 'test_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_all_acc_ast.py b/example/python/permissions/can_get_all_acc_ast.py new file mode 100644 index 0000000000..96aaa55e8f --- /dev/null +++ b/example/python/permissions/can_get_all_acc_ast.py @@ -0,0 +1,40 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@first') +alice = commons.new_user('alice@second') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetAllAccAst]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('first', 'admin_role') \ + .createDomain('second', 'test_role') \ + .createAccount('admin', 'first', admin['key'].publicKey()) \ + .createAccount('alice', 'second', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_assets_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccountAssets(admin['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_all_acc_ast_txs.py b/example/python/permissions/can_get_all_acc_ast_txs.py new file mode 100644 index 0000000000..af6f3d29e5 --- /dev/null +++ b/example/python/permissions/can_get_all_acc_ast_txs.py @@ -0,0 +1,47 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@first') +alice = commons.new_user('alice@second') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([ + iroha.Role_kGetAllAccAstTxs, + iroha.Role_kReceive, + iroha.Role_kTransfer + ]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('first', 'admin_role') \ + .createDomain('second', 'test_role') \ + .createAccount('admin', 'first', admin['key'].publicKey()) \ + .createAccount('alice', 'second', alice['key'].publicKey()) \ + .createAsset('coin', 'first', 2) \ + .addAssetQuantity('coin#first', '300.00') \ + .transferAsset(admin['id'], alice['id'], 'coin#first', 'top up', '200.00') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_asset_transactions_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccountAssetTransactions(admin['id'], 'coin#first') \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_all_acc_detail.py b/example/python/permissions/can_get_all_acc_detail.py new file mode 100644 index 0000000000..0a62055576 --- /dev/null +++ b/example/python/permissions/can_get_all_acc_detail.py @@ -0,0 +1,40 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@first') +alice = commons.new_user('alice@second') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetAllAccDetail]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('first', 'admin_role') \ + .createDomain('second', 'test_role') \ + .createAccount('admin', 'first', admin['key'].publicKey()) \ + .createAccount('alice', 'second', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_detail_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccountDetail(admin['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_all_acc_txs.py b/example/python/permissions/can_get_all_acc_txs.py new file mode 100644 index 0000000000..18c41042f7 --- /dev/null +++ b/example/python/permissions/can_get_all_acc_txs.py @@ -0,0 +1,40 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@first') +alice = commons.new_user('alice@second') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetAllAccTxs]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('first', 'admin_role') \ + .createDomain('second', 'test_role') \ + .createAccount('admin', 'first', admin['key'].publicKey()) \ + .createAccount('alice', 'second', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_transactions_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccountTransactions(admin['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_all_accounts.py b/example/python/permissions/can_get_all_accounts.py new file mode 100644 index 0000000000..3adeec05a4 --- /dev/null +++ b/example/python/permissions/can_get_all_accounts.py @@ -0,0 +1,40 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@first') +alice = commons.new_user('alice@second') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetAllAccounts]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('first', 'admin_role') \ + .createDomain('second', 'test_role') \ + .createAccount('admin', 'first', admin['key'].publicKey()) \ + .createAccount('alice', 'second', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccount(admin['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_all_signatories.py b/example/python/permissions/can_get_all_signatories.py new file mode 100644 index 0000000000..bf4b3b7c54 --- /dev/null +++ b/example/python/permissions/can_get_all_signatories.py @@ -0,0 +1,40 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@first') +alice = commons.new_user('alice@second') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetAllSignatories]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('first', 'admin_role') \ + .createDomain('second', 'test_role') \ + .createAccount('admin', 'first', admin['key'].publicKey()) \ + .createAccount('alice', 'second', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def signatories_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getSignatories(admin['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_all_txs.py b/example/python/permissions/can_get_all_txs.py new file mode 100644 index 0000000000..b17e76fea9 --- /dev/null +++ b/example/python/permissions/can_get_all_txs.py @@ -0,0 +1,72 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@first') +alice = commons.new_user('alice@second') + +admin_tx1_hash = None +admin_tx2_hash_blob = None + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetAllTxs]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('first', 'admin_role') \ + .createDomain('second', 'test_role') \ + .createAccount('admin', 'first', admin['key'].publicKey()) \ + .createAccount('alice', 'second', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def admin_action_1_tx(): + global admin_tx1_hash + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .createAsset('coin', 'second', 2) \ + .build() + admin_tx1_hash = tx.hash() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def admin_action_2_tx(): + global admin_tx2_hash_blob + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .setAccountDetail(admin['id'], 'hyperledger', 'iroha') \ + .build() + admin_tx2_hash_blob = tx.hash().blob() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def transactions_query(): + hashes = iroha.HashVector() + hashes.append(admin_tx1_hash) + hashes.append(iroha.Hash(iroha.Blob(admin_tx2_hash_blob))) + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getTransactions(hashes) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_domain_acc_ast.py b/example/python/permissions/can_get_domain_acc_ast.py new file mode 100644 index 0000000000..67cddb006c --- /dev/null +++ b/example/python/permissions/can_get_domain_acc_ast.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetDomainAccAst]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_assets_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccountAssets(admin['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_domain_acc_ast_txs.py b/example/python/permissions/can_get_domain_acc_ast_txs.py new file mode 100644 index 0000000000..2feeab8334 --- /dev/null +++ b/example/python/permissions/can_get_domain_acc_ast_txs.py @@ -0,0 +1,42 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetDomainAccAstTxs]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAsset('coin', 'test', 2) \ + .addAssetQuantity('coin#test', '500.69') \ + .transferAsset(admin['id'], alice['id'], 'coin#test', 'top up', '10.00') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_asset_transactions_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccountAssetTransactions(admin['id'], 'coin#test') \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_domain_acc_detail.py b/example/python/permissions/can_get_domain_acc_detail.py new file mode 100644 index 0000000000..b3612a62c2 --- /dev/null +++ b/example/python/permissions/can_get_domain_acc_detail.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetDomainAccDetail]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_detail_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccountDetail(admin['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_domain_acc_txs.py b/example/python/permissions/can_get_domain_acc_txs.py new file mode 100644 index 0000000000..ea67addb14 --- /dev/null +++ b/example/python/permissions/can_get_domain_acc_txs.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetDomainAccTxs]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_transactions_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccountTransactions(admin['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_domain_accounts.py b/example/python/permissions/can_get_domain_accounts.py new file mode 100644 index 0000000000..d043998732 --- /dev/null +++ b/example/python/permissions/can_get_domain_accounts.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetDomainAccounts]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccount(admin['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_domain_signatories.py b/example/python/permissions/can_get_domain_signatories.py new file mode 100644 index 0000000000..ffbbe909dd --- /dev/null +++ b/example/python/permissions/can_get_domain_signatories.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetDomainSignatories]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def signatories_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getSignatories(admin['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_my_acc_ast.py b/example/python/permissions/can_get_my_acc_ast.py new file mode 100644 index 0000000000..bb08774303 --- /dev/null +++ b/example/python/permissions/can_get_my_acc_ast.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetMyAccAst]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_assets_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccountAssets(alice['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_my_acc_ast_txs.py b/example/python/permissions/can_get_my_acc_ast_txs.py new file mode 100644 index 0000000000..c9170f4a35 --- /dev/null +++ b/example/python/permissions/can_get_my_acc_ast_txs.py @@ -0,0 +1,42 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetMyAccAstTxs]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAsset('coin', 'test', 2) \ + .addAssetQuantity('coin#test', '500.69') \ + .transferAsset(admin['id'], alice['id'], 'coin#test', 'top up', '10.00') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_asset_transactions_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccountAssetTransactions(alice['id'], 'coin#test') \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_my_acc_detail.py b/example/python/permissions/can_get_my_acc_detail.py new file mode 100644 index 0000000000..0e24b3e0f0 --- /dev/null +++ b/example/python/permissions/can_get_my_acc_detail.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetMyAccDetail]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_detail_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccountDetail(alice['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_my_acc_txs.py b/example/python/permissions/can_get_my_acc_txs.py new file mode 100644 index 0000000000..b1611fd23d --- /dev/null +++ b/example/python/permissions/can_get_my_acc_txs.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetMyAccTxs]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_transactions_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccountTransactions(alice['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_my_account.py b/example/python/permissions/can_get_my_account.py new file mode 100644 index 0000000000..522099d4df --- /dev/null +++ b/example/python/permissions/can_get_my_account.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetMyAccount]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def account_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAccount(alice['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_my_signatories.py b/example/python/permissions/can_get_my_signatories.py new file mode 100644 index 0000000000..ec8f5b4499 --- /dev/null +++ b/example/python/permissions/can_get_my_signatories.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetMySignatories]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def signatories_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getSignatories(alice['id']) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_my_txs.py b/example/python/permissions/can_get_my_txs.py new file mode 100644 index 0000000000..4c281a96a1 --- /dev/null +++ b/example/python/permissions/can_get_my_txs.py @@ -0,0 +1,76 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@first') +alice = commons.new_user('alice@second') + +alice_tx1_hash = None +alice_tx2_hash_blob = None + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([ + iroha.Role_kGetMyTxs, + iroha.Role_kAddAssetQty, + iroha.Role_kCreateAsset + ]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('first', 'admin_role') \ + .createDomain('second', 'test_role') \ + .createAccount('admin', 'first', admin['key'].publicKey()) \ + .createAccount('alice', 'second', alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def alice_action_1_tx(): + global alice_tx1_hash + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .createAsset('coin', 'first', 2) \ + .build() + alice_tx1_hash = tx.hash() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() + + +@commons.hex +def alice_action_2_tx(): + global alice_tx2_hash_blob + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .addAssetQuantity('coin#first', '600.30') \ + .build() + alice_tx2_hash_blob = tx.hash().blob() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() + + +@commons.hex +def transactions_query(): + hashes = iroha.HashVector() + hashes.append(alice_tx1_hash) + hashes.append(iroha.Hash(iroha.Blob(alice_tx2_hash_blob))) + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getTransactions(hashes) \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_get_roles.py b/example/python/permissions/can_get_roles.py new file mode 100644 index 0000000000..defcc3de61 --- /dev/null +++ b/example/python/permissions/can_get_roles.py @@ -0,0 +1,52 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kGetRoles]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAsset('coin', 'test', 2) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def get_system_roles_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getRoles() \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() + + +@commons.hex +def get_role_permissions_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(2) \ + .creatorAccountId(alice['id']) \ + .getRolePermissions('admin_role') \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_grant_can_add_my_signatory.py b/example/python/permissions/can_grant_can_add_my_signatory.py new file mode 100644 index 0000000000..20db1278f7 --- /dev/null +++ b/example/python/permissions/can_grant_can_add_my_signatory.py @@ -0,0 +1,41 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kAddMySignatory]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def grant_can_add_my_signatory_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .grantPermission(bob['id'], iroha.Grantable_kAddMySignatory) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_grant_can_remove_my_signatory.py b/example/python/permissions/can_grant_can_remove_my_signatory.py new file mode 100644 index 0000000000..1408fe7b33 --- /dev/null +++ b/example/python/permissions/can_grant_can_remove_my_signatory.py @@ -0,0 +1,41 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kRemoveMySignatory]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def grant_can_remove_my_signatory_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .grantPermission(bob['id'], iroha.Grantable_kRemoveMySignatory) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_grant_can_set_my_account_detail.py b/example/python/permissions/can_grant_can_set_my_account_detail.py new file mode 100644 index 0000000000..a3bafc826c --- /dev/null +++ b/example/python/permissions/can_grant_can_set_my_account_detail.py @@ -0,0 +1,41 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kSetMyAccountDetail]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def grant_can_set_my_account_detail_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .grantPermission(bob['id'], iroha.Grantable_kSetMyAccountDetail) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_grant_can_set_my_quorum.py b/example/python/permissions/can_grant_can_set_my_quorum.py new file mode 100644 index 0000000000..47301e193e --- /dev/null +++ b/example/python/permissions/can_grant_can_set_my_quorum.py @@ -0,0 +1,41 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kSetMyQuorum]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def grant_can_set_my_quorum_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .grantPermission(bob['id'], iroha.Grantable_kSetMyQuorum) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_grant_can_transfer_my_assets.py b/example/python/permissions/can_grant_can_transfer_my_assets.py new file mode 100644 index 0000000000..f9b8ab3494 --- /dev/null +++ b/example/python/permissions/can_grant_can_transfer_my_assets.py @@ -0,0 +1,48 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([ + iroha.Role_kTransferMyAssets, + iroha.Role_kReceive, + iroha.Role_kTransfer + ]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .createAsset('coin', 'test', 2) \ + .addAssetQuantity('coin#test', '100.00') \ + .transferAsset(admin['id'], alice['id'], 'coin#test', 'init top up', '90.00') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def grant_can_transfer_my_assets_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .grantPermission(bob['id'], iroha.Grantable_kTransferMyAssets) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_read_assets.py b/example/python/permissions/can_read_assets.py new file mode 100644 index 0000000000..58eeb3d3c8 --- /dev/null +++ b/example/python/permissions/can_read_assets.py @@ -0,0 +1,40 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kReadAssets]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAsset('coin', 'test', 2) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def get_asset_query(): + tx = iroha.ModelQueryBuilder() \ + .createdTime(commons.now()) \ + .queryCounter(1) \ + .creatorAccountId(alice['id']) \ + .getAssetInfo('coin#test') \ + .build() + return iroha.ModelProtoQuery(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_receive.py b/example/python/permissions/can_receive.py new file mode 100644 index 0000000000..0f9552e15f --- /dev/null +++ b/example/python/permissions/can_receive.py @@ -0,0 +1,47 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([ + iroha.Role_kReceive, + iroha.Role_kTransfer + ]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .createAsset('coin', 'test', 2) \ + .addAssetQuantity('coin#test', '100.00') \ + .transferAsset(admin['id'], alice['id'], 'coin#test', 'init top up', '90.00') \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def transfer_asset_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .transferAsset(alice['id'], bob['id'], 'coin#test', 'transfer to Bob', '60.00') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_remove_my_signatory.py b/example/python/permissions/can_remove_my_signatory.py new file mode 100644 index 0000000000..1f17ebd338 --- /dev/null +++ b/example/python/permissions/can_remove_my_signatory.py @@ -0,0 +1,57 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([ + iroha.Role_kRemoveMySignatory, + iroha.Role_kAddSignatory + ]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def grant_can_remove_my_signatory_tx(): + extra_key = iroha.ModelCrypto().generateKeypair() + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .grantPermission(bob['id'], iroha.Grantable_kRemoveMySignatory) \ + .addSignatory(alice['id'], extra_key.publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() + + +@commons.hex +def remove_signatory_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(bob['id']) \ + .removeSignatory(alice['id'], alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(bob['key']).finish() diff --git a/example/python/permissions/can_remove_signatory.py b/example/python/permissions/can_remove_signatory.py new file mode 100644 index 0000000000..03bf922f93 --- /dev/null +++ b/example/python/permissions/can_remove_signatory.py @@ -0,0 +1,41 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kRemoveSignatory]) + extra_key = iroha.ModelCrypto().generateKeypair() + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .addSignatory(alice['id'], extra_key.publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def remove_signatory_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .removeSignatory(alice['id'], alice['key'].publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_set_detail.py b/example/python/permissions/can_set_detail.py new file mode 100644 index 0000000000..577bee5571 --- /dev/null +++ b/example/python/permissions/can_set_detail.py @@ -0,0 +1,39 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kSetDetail]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def set_account_detail_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .setAccountDetail(admin['id'], 'fav_color', 'red') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_set_my_account_detail.py b/example/python/permissions/can_set_my_account_detail.py new file mode 100644 index 0000000000..43477d0a5a --- /dev/null +++ b/example/python/permissions/can_set_my_account_detail.py @@ -0,0 +1,54 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([ + iroha.Role_kSetMyAccountDetail, + ]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def grant_permission_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .grantPermission(bob['id'], iroha.Grantable_kSetMyAccountDetail) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() + + +@commons.hex +def set_detail_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(bob['id']) \ + .setAccountDetail(alice['id'], 'bobs', 'call') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(bob['key']).finish() diff --git a/example/python/permissions/can_set_my_quorum.py b/example/python/permissions/can_set_my_quorum.py new file mode 100644 index 0000000000..4310d8f55f --- /dev/null +++ b/example/python/permissions/can_set_my_quorum.py @@ -0,0 +1,57 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([ + iroha.Role_kSetMyQuorum, + iroha.Role_kAddSignatory + ]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def grant_can_set_my_quorum_tx(): + extra_key = iroha.ModelCrypto().generateKeypair() + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .grantPermission(bob['id'], iroha.Grantable_kSetMyQuorum) \ + .addSignatory(alice['id'], extra_key.publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() + + +@commons.hex +def set_quorum_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(bob['id']) \ + .setAccountQuorum(alice['id'], 2) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(bob['key']).finish() diff --git a/example/python/permissions/can_set_quorum.py b/example/python/permissions/can_set_quorum.py new file mode 100644 index 0000000000..33d0b6235b --- /dev/null +++ b/example/python/permissions/can_set_quorum.py @@ -0,0 +1,42 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kSetQuorum]) + extra_key = iroha.ModelCrypto().generateKeypair() + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .addSignatory(alice['id'], extra_key.publicKey()) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def set_quorum_tx(): + # Quourum cannot be greater than amount of keys linked to an account + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .setAccountQuorum(alice['id'], 2) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_subtract_asset_qty.py b/example/python/permissions/can_subtract_asset_qty.py new file mode 100644 index 0000000000..e57dc4dd0a --- /dev/null +++ b/example/python/permissions/can_subtract_asset_qty.py @@ -0,0 +1,42 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([iroha.Role_kSubtractAssetQty]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .createAsset('coin', 'test', 2) \ + .addAssetQuantity('coin#test', '1000.00') \ + .transferAsset(admin['id'], alice['id'], 'coin#test', 'init top up', '999.99') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def subtract_asset_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .subtractAssetQuantity('coin#test', '999.99') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() diff --git a/example/python/permissions/can_transfer.py b/example/python/permissions/can_transfer.py new file mode 100644 index 0000000000..ddb9d069b9 --- /dev/null +++ b/example/python/permissions/can_transfer.py @@ -0,0 +1,11 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import can_receive + + +# Please see example for can_receive permission. +# By design can_receive and can_transfer permissions +# can be tested only together. diff --git a/example/python/permissions/can_transfer_my_assets.py b/example/python/permissions/can_transfer_my_assets.py new file mode 100644 index 0000000000..547bc6c2a7 --- /dev/null +++ b/example/python/permissions/can_transfer_my_assets.py @@ -0,0 +1,59 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +import commons + +admin = commons.new_user('admin@test') +alice = commons.new_user('alice@test') +bob = commons.new_user('bob@test') + + +@commons.hex +def genesis_tx(): + test_permissions = iroha.RolePermissionSet([ + iroha.Role_kTransferMyAssets, + iroha.Role_kReceive, + iroha.Role_kTransfer + ]) + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(admin['id']) \ + .addPeer('0.0.0.0:50541', admin['key'].publicKey()) \ + .createRole('admin_role', commons.all_permissions()) \ + .createRole('test_role', test_permissions) \ + .createDomain('test', 'test_role') \ + .createAccount('admin', 'test', admin['key'].publicKey()) \ + .createAccount('alice', 'test', alice['key'].publicKey()) \ + .createAccount('bob', 'test', bob['key'].publicKey()) \ + .appendRole(admin['id'], 'admin_role') \ + .createAsset('coin', 'test', 2) \ + .addAssetQuantity('coin#test', '100.00') \ + .transferAsset(admin['id'], alice['id'], 'coin#test', 'init top up', '90.00') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(admin['key']).finish() + + +@commons.hex +def grant_permission_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(alice['id']) \ + .grantPermission(bob['id'], iroha.Grantable_kTransferMyAssets) \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(alice['key']).finish() + + +@commons.hex +def transfer_asset_tx(): + tx = iroha.ModelTransactionBuilder() \ + .createdTime(commons.now()) \ + .creatorAccountId(bob['id']) \ + .transferAsset(alice['id'], admin['id'], 'coin#test', 'transfer from alice to admin done by bob', '60.00') \ + .build() + return iroha.ModelProtoTransaction(tx) \ + .signAndAddSignature(bob['key']).finish() diff --git a/example/python/permissions/commons.py b/example/python/permissions/commons.py new file mode 100644 index 0000000000..dcac6f2517 --- /dev/null +++ b/example/python/permissions/commons.py @@ -0,0 +1,79 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +import iroha +from time import time + + +def now(): + return int(time() * 1000) + + +def all_permissions(): + return iroha.RolePermissionSet([ + iroha.Role_kAppendRole, + iroha.Role_kCreateRole, + iroha.Role_kDetachRole, + iroha.Role_kAddAssetQty, + iroha.Role_kSubtractAssetQty, + iroha.Role_kAddPeer, + iroha.Role_kAddSignatory, + iroha.Role_kRemoveSignatory, + iroha.Role_kSetQuorum, + iroha.Role_kCreateAccount, + iroha.Role_kSetDetail, + iroha.Role_kCreateAsset, + iroha.Role_kTransfer, + iroha.Role_kReceive, + iroha.Role_kCreateDomain, + iroha.Role_kReadAssets, + iroha.Role_kGetRoles, + iroha.Role_kGetMyAccount, + iroha.Role_kGetAllAccounts, + iroha.Role_kGetDomainAccounts, + iroha.Role_kGetMySignatories, + iroha.Role_kGetAllSignatories, + iroha.Role_kGetDomainSignatories, + iroha.Role_kGetMyAccAst, + iroha.Role_kGetAllAccAst, + iroha.Role_kGetDomainAccAst, + iroha.Role_kGetMyAccDetail, + iroha.Role_kGetAllAccDetail, + iroha.Role_kGetDomainAccDetail, + iroha.Role_kGetMyAccTxs, + iroha.Role_kGetAllAccTxs, + iroha.Role_kGetDomainAccTxs, + iroha.Role_kGetMyAccAstTxs, + iroha.Role_kGetAllAccAstTxs, + iroha.Role_kGetDomainAccAstTxs, + iroha.Role_kGetMyTxs, + iroha.Role_kGetAllTxs, + iroha.Role_kSetMyQuorum, + iroha.Role_kAddMySignatory, + iroha.Role_kRemoveMySignatory, + iroha.Role_kTransferMyAssets, + iroha.Role_kSetMyAccountDetail, + iroha.Role_kGetBlocks + ]) + + +def new_user(user_id): + key = iroha.ModelCrypto().generateKeypair() + if user_id.lower().startswith('admin'): + print('K{}'.format(key.privateKey().hex())) + return { + 'id': user_id, + 'key': key + } + + +def hex(generator): + """ + Decorator for transactions' and queries generators. + + Allows preserving the type of binaries for Binary Testing Framework. + """ + prefix = 'T' if generator.__name__.lower().endswith('tx') else 'Q' + print('{}{}'.format(prefix, generator().hex())) diff --git a/example/python/prepare.sh b/example/python/prepare.sh index 12e5da318d..6af9714c0f 100755 --- a/example/python/prepare.sh +++ b/example/python/prepare.sh @@ -9,5 +9,5 @@ cmake -H$IROHA_HOME -Bbuild -DSWIG_PYTHON=ON -DSUPPORT_PYTHON2=ON; cmake --build build/ --target irohapy -- -j"$(getconf _NPROCESSORS_ONLN)" # generate proto files in current dir -protoc --proto_path=../../schema --python_out=. block.proto primitive.proto commands.proto queries.proto responses.proto endpoint.proto -python -m grpc_tools.protoc --proto_path=../../schema --python_out=. --grpc_python_out=. endpoint.proto yac.proto ordering.proto loader.proto +protoc --proto_path=../../shared_model/schema --python_out=. ../../shared_model/schema/*.proto +python -m grpc_tools.protoc --proto_path=../../shared_model/schema --python_out=. --grpc_python_out=. ../../shared_model/schema/endpoint.proto diff --git a/example/python/tx-example.py b/example/python/tx-example.py index cd519c2748..98ec845833 100644 --- a/example/python/tx-example.py +++ b/example/python/tx-example.py @@ -2,7 +2,7 @@ sys.path.insert(0, 'build/shared_model/bindings') import iroha -import block_pb2 +import transaction_pb2 import endpoint_pb2 import endpoint_pb2_grpc import queries_pb2 @@ -82,7 +82,7 @@ def print_status_streaming(tx): def send_tx(tx, key_pair): tx_blob = iroha.ModelProtoTransaction(tx).signAndAddSignature(key_pair).finish().blob() - proto_tx = block_pb2.Transaction() + proto_tx = transaction_pb2.Transaction() if sys.version_info[0] == 2: tmp = ''.join(map(chr, tx_blob)) @@ -135,7 +135,7 @@ def add_coin_to_admin(): """ tx = tx_builder.creatorAccountId(creator) \ .createdTime(current_time) \ - .addAssetQuantity("admin@test", "coin#domain", "1000.00").build() + .addAssetQuantity("coin#domain", "1000.00").build() send_tx(tx, key_pair) print_status_streaming(tx) @@ -169,7 +169,7 @@ def grant_admin_to_add_detail_to_userone(): """ tx = tx_builder.creatorAccountId("userone@domain") \ .createdTime(current_time) \ - .grantPermission(creator, "can_set_my_account_detail") \ + .grantPermission(creator, iroha.Grantable_kSetMyAccountDetail) \ .build() send_tx(tx, user1_kp) diff --git a/example/test@test.priv b/example/test@test.priv index f41898d68e..6605e2840b 100644 --- a/example/test@test.priv +++ b/example/test@test.priv @@ -1 +1 @@ -2e5b37fd881f260323dca4f0776e6e1e969bef7dab14673858f82663e7cd8556 \ No newline at end of file +7e00405ece477bb6dd9b03a78eee4e708afc2f5bcdce399573a5958942f4a390 \ No newline at end of file diff --git a/example/test@test.pub b/example/test@test.pub index 95419850c9..f08d97cac9 100644 --- a/example/test@test.pub +++ b/example/test@test.pub @@ -1 +1 @@ -dcc7ecce448f2e190a481aec7ca21ee52eda546e660b4820f4976d0132157097 \ No newline at end of file +716fe505f69f18511a1b083915aa9ff73ef36e6688199f3959750db38b8f4bfc \ No newline at end of file diff --git a/iroha-cli/impl/query_response_handler.cpp b/iroha-cli/impl/query_response_handler.cpp index d360756d7b..751d434f97 100644 --- a/iroha-cli/impl/query_response_handler.cpp +++ b/iroha-cli/impl/query_response_handler.cpp @@ -153,9 +153,7 @@ namespace iroha_cli { for (auto &acc_asset : acc_assets) { log_->info(prefix.at(kAccountId), acc_asset.account_id()); log_->info(prefix.at(kAssetId), acc_asset.asset_id()); - auto balance = - iroha::model::converters::deserializeAmount(acc_asset.balance()); - log_->info(prefix.at(kAmount), balance.to_string()); + log_->info(prefix.at(kAmount), acc_asset.balance()); } } diff --git a/iroha-cli/interactive/impl/interactive_common_cli.cpp b/iroha-cli/interactive/impl/interactive_common_cli.cpp index 5b5c8bbd49..e2d0df4d47 100644 --- a/iroha-cli/interactive/impl/interactive_common_cli.cpp +++ b/iroha-cli/interactive/impl/interactive_common_cli.cpp @@ -14,8 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include #include +#include "common/types.hpp" #include "interactive/interactive_common_cli.hpp" #include "parser/parser.hpp" @@ -35,14 +37,25 @@ namespace iroha_cli { int default_port) { return { // commonParamsMap - {SAVE_CODE, {"Path to save json file"}}, + {SAVE_CODE, makeParamsDescription({"Path to save json file"})}, {SEND_CODE, - {"Peer address (" + default_ip + ")", - "Peer port (" + std::to_string(default_port) + ")"}} + {ParamData({"Peer address", default_ip}), + ParamData({"Peer port", std::to_string(default_port)})}} // commonParamsMap }; } + ParamsDescription makeParamsDescription( + const std::vector ¶ms) { + return std::accumulate(params.begin(), + params.end(), + ParamsDescription{}, + [](auto &&acc, auto &el) { + acc.push_back(ParamData({el, {}})); + return std::forward(acc); + }); + } + void handleEmptyCommand() { std::cout << "Put not empty command" << std::endl; } @@ -57,16 +70,15 @@ namespace iroha_cli { bool isBackOption(std::string line) { auto command = parser::parseFirstCommand(std::move(line)); - return command - and (*command == "0" or *command == BACK_CODE); + return command and (*command == "0" or *command == BACK_CODE); } void printCommandParameters(std::string &command, - std::vector parameters) { + const ParamsDescription ¶meters) { std::cout << "Run " << command << " with following parameters: " << std::endl; std::for_each(parameters.begin(), parameters.end(), [](auto el) { - std::cout << " " << el << std::endl; + std::cout << " " << el.message << std::endl; }); } @@ -87,12 +99,20 @@ namespace iroha_cli { return line; } + boost::optional promptString(const ParamData ¶m) { + std::string message = param.message; + if (not param.cache.empty()) { + message += " (" + param.cache + ")"; + } + return promptString(message); + } + void printEnd() { std::cout << "--------------------" << std::endl; } boost::optional> parseIrohaPeerParams( - ParamsDescription params, + std::vector params, const std::string &default_ip, int default_port) { const auto &address = params[0].empty() ? default_ip : params[0]; @@ -107,9 +127,8 @@ namespace iroha_cli { } boost::optional> parseParams( - std::string line, std::string command_name, ParamsMap params_map) { - auto params_description = - findInHandlerMap(command_name, std::move(params_map)); + std::string line, std::string command_name, ParamsMap ¶ms_map) { + auto params_description = findInHandlerMap(command_name, params_map); if (not params_description) { // Report no params where found for this command std::cout << "Command params not found" << std::endl; @@ -122,11 +141,18 @@ namespace iroha_cli { std::vector params; std::for_each(params_description.value().begin(), params_description.value().end(), - [¶ms](auto param) { - auto val = promptString(param); - if (val and not val.value().empty()) { - params.push_back(val.value()); - } + [¶ms](auto ¶m) { + using namespace iroha; + promptString(param) | [&](auto &val) { + if (not val.empty()) { + // Update input cache + param.cache = val; + params.push_back(val); + } else if (not param.cache.empty()) { + // Input cache is not empty, use cached value + params.push_back(param.cache); + } + }; }); if (params.size() != params_description.value().size()) { // Wrong params passed diff --git a/iroha-cli/interactive/impl/interactive_query_cli.cpp b/iroha-cli/interactive/impl/interactive_query_cli.cpp index 95fdcaa3e3..dedad966b0 100644 --- a/iroha-cli/interactive/impl/interactive_query_cli.cpp +++ b/iroha-cli/interactive/impl/interactive_query_cli.cpp @@ -58,23 +58,24 @@ namespace iroha_cli { const auto role_id = "Requested role name"; const auto tx_hashes = "Requested tx hashes"; - query_params_descriptions_ = { - {GET_ACC, {acc_id}}, - {GET_ACC_AST, {acc_id, ast_id}}, - {GET_ACC_AST_TX, {acc_id, ast_id}}, - {GET_ACC_TX, {acc_id}}, - {GET_TX, {tx_hashes}}, - {GET_ACC_SIGN, {acc_id}}, - {GET_ROLES, {}}, - {GET_AST_INFO, {ast_id}}, - {GET_ROLE_PERM, {role_id}} - // query_params_descriptions_ + query_params_map_ = { + {GET_ACC, makeParamsDescription({acc_id})}, + {GET_ACC_AST, makeParamsDescription({acc_id, ast_id})}, + {GET_ACC_AST_TX, makeParamsDescription({acc_id, ast_id})}, + {GET_ACC_TX, makeParamsDescription({acc_id})}, + {GET_TX, makeParamsDescription({tx_hashes})}, + {GET_ACC_SIGN, makeParamsDescription({acc_id})}, + {GET_ROLES, makeParamsDescription({})}, + {GET_AST_INFO, makeParamsDescription({ast_id})}, + {GET_ROLE_PERM, makeParamsDescription({role_id})} + // query_params_map_ }; query_handlers_ = { {GET_ACC, &InteractiveQueryCli::parseGetAccount}, {GET_ACC_AST, &InteractiveQueryCli::parseGetAccountAssets}, - {GET_ACC_AST_TX, &InteractiveQueryCli::parseGetAccountAssetTransactions}, + {GET_ACC_AST_TX, + &InteractiveQueryCli::parseGetAccountAssetTransactions}, {GET_ACC_TX, &InteractiveQueryCli::parseGetAccountTransactions}, {GET_TX, &InteractiveQueryCli::parseGetTransactions}, {GET_ACC_SIGN, &InteractiveQueryCli::parseGetSignatories}, @@ -84,8 +85,8 @@ namespace iroha_cli { // query_handlers_ }; - menu_points_ = formMenu( - query_handlers_, query_params_descriptions_, description_map_); + menu_points_ = + formMenu(query_handlers_, query_params_map_, description_map_); // Add "go back" option addBackOption(menu_points_); } @@ -93,12 +94,10 @@ namespace iroha_cli { void InteractiveQueryCli::create_result_menu() { result_handlers_ = {{SAVE_CODE, &InteractiveQueryCli::parseSaveFile}, {SEND_CODE, &InteractiveQueryCli::parseSendToIroha}}; - result_params_descriptions_ = - getCommonParamsMap(default_peer_ip_, default_port_); + result_params_map_ = getCommonParamsMap(default_peer_ip_, default_port_); - result_points_ = formMenu(result_handlers_, - result_params_descriptions_, - getCommonDescriptionMap()); + result_points_ = formMenu( + result_handlers_, result_params_map_, getCommonDescriptionMap()); addBackOption(result_points_); } @@ -159,7 +158,7 @@ namespace iroha_cli { } auto res = handleParse>( - this, line, query_handlers_, query_params_descriptions_); + this, line, query_handlers_, query_params_map_); if (not res) { // Continue parsing return true; @@ -259,8 +258,8 @@ namespace iroha_cli { return true; } - auto res = handleParse( - this, line, result_handlers_, result_params_descriptions_); + auto res = + handleParse(this, line, result_handlers_, result_params_map_); return res.get_value_or(true); } diff --git a/iroha-cli/interactive/impl/interactive_status_cli.cpp b/iroha-cli/interactive/impl/interactive_status_cli.cpp index 9ec0c91771..2c14377bda 100644 --- a/iroha-cli/interactive/impl/interactive_status_cli.cpp +++ b/iroha-cli/interactive/impl/interactive_status_cli.cpp @@ -50,7 +50,8 @@ namespace iroha_cli { descriptionMap_ = {{GET_TX_INFO, "Get status of transaction"}}; const auto tx_id = "Requested tx hash"; - requestParamsDescriptions_ = {{GET_TX_INFO, {tx_id}}}; + requestParamsDescriptions_ = { + {GET_TX_INFO, makeParamsDescription({tx_id})}}; actionHandlers_ = {{GET_TX_INFO, &InteractiveStatusCli::parseGetHash}}; menuPoints_ = formMenu( @@ -76,7 +77,7 @@ namespace iroha_cli { printMenu("Choose action: ", menuPoints_); while (isParsing) { auto line = promptString("> "); - if (not line){ + if (not line) { // line has terminating symbol isParsing = false; break; @@ -139,15 +140,20 @@ namespace iroha_cli { } auto status = iroha::protocol::TxStatus::NOT_RECEIVED; + iroha::protocol::ToriiResponse answer; if (iroha::hexstringToBytestring(txHash_)) { - status = CliClient(address.value().first, address.value().second) + answer = CliClient(address.value().first, address.value().second) .getTxStatus(*iroha::hexstringToBytestring(txHash_)) - .answer.tx_status(); + .answer; + status = answer.tx_status(); } std::string message; try { message = userMessageMap.at(status); + if (not answer.error_message().empty()) { + message += " " + answer.error_message(); + } } catch (const std::out_of_range &e) { message = "A problem detected while retrieving transaction status. Please " diff --git a/iroha-cli/interactive/impl/interactive_transaction_cli.cpp b/iroha-cli/interactive/impl/interactive_transaction_cli.cpp index 02b095a6e1..8ba6be8104 100644 --- a/iroha-cli/interactive/impl/interactive_transaction_cli.cpp +++ b/iroha-cli/interactive/impl/interactive_transaction_cli.cpp @@ -68,8 +68,7 @@ namespace iroha_cli { const auto acc_id = "Account Id"; const auto ast_id = "Asset Id"; const auto dom_id = "Domain Id"; - const auto amount_a = "Amount to add (integer part)"; - const auto amount_b = "Amount to add (precision)"; + const auto amount_str = "Amount to to add, e.g 123.456"; const auto peer_id = "Full address of a peer"; const auto pub_key = "Public Key"; const auto acc_name = "Account Name"; @@ -86,37 +85,38 @@ namespace iroha_cli { const auto can_asset_creator = "Can create/add new assets"; const auto can_roles = "Can create/append roles"; - command_params_descriptions_ = { - {ADD_ASSET_QTY, {acc_id, ast_id, amount_a, amount_b}}, - {ADD_PEER, {peer_id, pub_key}}, - {ADD_SIGN, {acc_id, pub_key}}, - {CREATE_ACC, {acc_name, dom_id, pub_key}}, - {CREATE_DOMAIN, {dom_id, std::string("Default ") + role}}, - {CREATE_ASSET, {ast_name, dom_id, ast_precision}}, - {REMOVE_SIGN, {acc_id, pub_key}}, - {SET_QUO, {acc_id, quorum}}, - {SUB_ASSET_QTY, {}}, + command_params_map_ = { + {ADD_ASSET_QTY, makeParamsDescription({acc_id, ast_id, amount_str})}, + {ADD_PEER, makeParamsDescription({peer_id, pub_key})}, + {ADD_SIGN, makeParamsDescription({acc_id, pub_key})}, + {CREATE_ACC, makeParamsDescription({acc_name, dom_id, pub_key})}, + {CREATE_DOMAIN, + makeParamsDescription({dom_id, std::string("Default ") + role})}, + {CREATE_ASSET, + makeParamsDescription({ast_name, dom_id, ast_precision})}, + {REMOVE_SIGN, makeParamsDescription({acc_id, pub_key})}, + {SET_QUO, makeParamsDescription({acc_id, quorum})}, + {SUB_ASSET_QTY, makeParamsDescription({})}, {TRAN_ASSET, - {std::string("Src") + acc_id, - std::string("Dest") + acc_id, - ast_id, - amount_a, - amount_b}}, + makeParamsDescription({std::string("Src") + acc_id, + std::string("Dest") + acc_id, + ast_id, + amount_str})}, {CREATE_ROLE, - {role, - can_read_self, - can_edit_self, - can_read_all, - can_transfer_receive, - can_asset_creator, - can_create_domain, - can_roles, - can_create_account}}, - {APPEND_ROLE, {acc_id, role}}, - {DETACH_ROLE, {acc_id, role}}, - {GRANT_PERM, {acc_id, perm}}, - {REVOKE_PERM, {acc_id, perm}}, - {SET_ACC_KV, {acc_id, "key", "value"}} + makeParamsDescription({role, + can_read_self, + can_edit_self, + can_read_all, + can_transfer_receive, + can_asset_creator, + can_create_domain, + can_roles, + can_create_account})}, + {APPEND_ROLE, makeParamsDescription({acc_id, role})}, + {DETACH_ROLE, makeParamsDescription({acc_id, role})}, + {GRANT_PERM, makeParamsDescription({acc_id, perm})}, + {REVOKE_PERM, makeParamsDescription({acc_id, perm})}, + {SET_ACC_KV, makeParamsDescription({acc_id, "key", "value"})} // command parameters descriptions }; @@ -141,9 +141,8 @@ namespace iroha_cli { // Command parsers }; - commands_menu_ = formMenu(command_handlers_, - command_params_descriptions_, - commands_description_map_); + commands_menu_ = formMenu( + command_handlers_, command_params_map_, commands_description_map_); // Add "go back" option addBackOption(commands_menu_); } @@ -159,11 +158,10 @@ namespace iroha_cli { result_desciption.insert( {BACK_CODE, "Go back and start a new transaction"}); - result_params_descriptions = - getCommonParamsMap(default_peer_ip_, default_port_); + result_params_map = getCommonParamsMap(default_peer_ip_, default_port_); - result_params_descriptions.insert({ADD_CMD, {}}); - result_params_descriptions.insert({BACK_CODE, {}}); + result_params_map.insert({ADD_CMD, {}}); + result_params_map.insert({BACK_CODE, {}}); result_handlers_ = { {SAVE_CODE, &InteractiveTransactionCli::parseSaveFile}, @@ -173,8 +171,8 @@ namespace iroha_cli { // Parsers for result }; - result_menu_ = formMenu( - result_handlers_, result_params_descriptions, result_desciption); + result_menu_ = + formMenu(result_handlers_, result_params_map, result_desciption); } InteractiveTransactionCli::InteractiveTransactionCli( @@ -227,7 +225,7 @@ namespace iroha_cli { } auto res = handleParse>( - this, line, command_handlers_, command_params_descriptions_); + this, line, command_handlers_, command_params_map_); if (not res) { // Continue parsing @@ -328,22 +326,9 @@ namespace iroha_cli { std::shared_ptr InteractiveTransactionCli::parseAddAssetQuantity( std::vector params) { - auto account_id = params[0]; - auto asset_id = params[1]; - auto val_int = - parser::parseValue(params[2]); - auto precision = parser::parseValue(params[3]); - if (not val_int or not precision) { - std::cout << "Wrong format for amount" << std::endl; - return nullptr; - } - if (precision.value() > 255) { - std::cout << "Too big precision (should be less than 256)" << std::endl; - return nullptr; - } - std::cout << val_int.value() << " " << precision.value() << std::endl; - iroha::Amount amount(val_int.value(), precision.value()); - return generator_.generateAddAssetQuantity(account_id, asset_id, amount); + auto asset_id = params[0]; + auto amount = params[1]; + return generator_.generateAddAssetQuantity(asset_id, amount); } std::shared_ptr @@ -432,14 +417,7 @@ namespace iroha_cli { auto src_account_id = params[0]; auto dest_account_id = params[1]; auto asset_id = params[2]; - auto val_int = - parser::parseValue(params[3]); - auto precision = parser::parseValue(params[4]); - if (not val_int or not precision) { - std::cout << "Wrong format for amount" << std::endl; - return nullptr; - } - iroha::Amount amount(val_int.value(), precision.value()); + auto amount = params[3]; return generator_.generateTransferAsset( src_account_id, dest_account_id, asset_id, amount); } @@ -457,8 +435,8 @@ namespace iroha_cli { bool InteractiveTransactionCli::parseResult(std::string line) { // Find in result handler map - auto res = handleParse( - this, line, result_handlers_, result_params_descriptions); + auto res = + handleParse(this, line, result_handlers_, result_params_map); return res.get_value_or(true); } diff --git a/iroha-cli/interactive/interactive_common_cli.hpp b/iroha-cli/interactive/interactive_common_cli.hpp index 27d3bc89dd..958d1fb9fc 100644 --- a/iroha-cli/interactive/interactive_common_cli.hpp +++ b/iroha-cli/interactive/interactive_common_cli.hpp @@ -19,8 +19,9 @@ #define IROHA_CLI_INTERACTIVE_COMMON_CLI_HPP #include -#include #include +#include +#include #include #include #include @@ -46,8 +47,23 @@ namespace iroha_cli { RESULT }; + /** + * Data structure for parameter data + */ + struct ParamData { + /** + * Message to display when prompting for user input + */ + std::string message; + + /** + * Cached user input for the parameter or the default value + */ + std::string cache; + }; + // Description of parameters - using ParamsDescription = std::vector; + using ParamsDescription = std::vector; // map for command - command description relationship using DescriptionMap = std::unordered_map; // Points in a menu @@ -68,11 +84,22 @@ namespace iroha_cli { /** * Return mapping of Command_name to parameters descriptions - * @return Map with parameters of common commands + * @param default_ip - default hostname or IP to be used when connecting to + * irohad + * @param default_port - default port to be used when connecting to irohad + * @return ParamsMap with parameters of common commands */ ParamsMap getCommonParamsMap(const std::string &default_ip, int default_port); + /** + * Creates parameters descriptions with empty default/cache values + * @param params - parameters as a vector of prompt messages + * @return ParamsDescription with parameter data + */ + ParamsDescription makeParamsDescription( + const std::vector ¶ms); + /** * Handle error with empty command */ @@ -108,7 +135,7 @@ namespace iroha_cli { * @param parameters needed to run the command */ void printCommandParameters(std::string &command, - std::vector parameters); + const ParamsDescription ¶meters); /** * Pretty Print of menu @@ -124,6 +151,13 @@ namespace iroha_cli { */ boost::optional promptString(const std::string &message); + /** + * Construct a prompt and get a string input from user + * @param param Parameter to collect the input for + * @return nullopt if termintaing symbol, else user's input + */ + boost::optional promptString(const ParamData ¶m); + /** * Parse parameters in interactive and shortcuted mode. * Function run interactive mode if in line only the command name is passed. @@ -136,7 +170,7 @@ namespace iroha_cli { * @return vector with needed parameters */ boost::optional> parseParams( - std::string line, std::string command_name, ParamsMap params_map); + std::string line, std::string command_name, ParamsMap ¶ms_map); /** * Add menu point to vector menu @@ -171,8 +205,8 @@ namespace iroha_cli { * @return nullopt if key not found, value if found */ template - boost::optional findInHandlerMap(K command_name, - std::unordered_map params_map) { + boost::optional findInHandlerMap( + K command_name, std::unordered_map ¶ms_map) { auto it = params_map.find(command_name); if (it == params_map.end()) { // Command not found, report error @@ -211,7 +245,7 @@ namespace iroha_cli { C class_pointer, std::string &line, std::unordered_map &parsers_map, - ParamsMap params_map) { + ParamsMap ¶ms_map) { auto raw_command = parser::parseFirstCommand(line); if (not raw_command) { handleEmptyCommand(); diff --git a/iroha-cli/interactive/interactive_query_cli.hpp b/iroha-cli/interactive/interactive_query_cli.hpp index a21003587b..ac1e2963f6 100644 --- a/iroha-cli/interactive/interactive_query_cli.hpp +++ b/iroha-cli/interactive/interactive_query_cli.hpp @@ -95,7 +95,7 @@ namespace iroha_cli { DescriptionMap description_map_; // Parameters descriptions of queries - ParamsMap query_params_descriptions_; + ParamsMap query_params_map_; /** * Parse line for query @@ -127,7 +127,7 @@ namespace iroha_cli { std::unordered_map result_handlers_; // Parameters descriptions of result commands - ParamsMap result_params_descriptions_; + ParamsMap result_params_map_; /** * Parse line for result diff --git a/iroha-cli/interactive/interactive_transaction_cli.hpp b/iroha-cli/interactive/interactive_transaction_cli.hpp index 5e8f2a9597..ee8a61eb75 100644 --- a/iroha-cli/interactive/interactive_transaction_cli.hpp +++ b/iroha-cli/interactive/interactive_transaction_cli.hpp @@ -99,7 +99,7 @@ namespace iroha_cli { std::unordered_map command_handlers_; // Descriptions for commands parameters - ParamsMap command_params_descriptions_; + ParamsMap command_params_map_; // Descriptions for commands DescriptionMap commands_description_map_; @@ -151,7 +151,7 @@ namespace iroha_cli { std::unordered_map result_handlers_; // Description for result command - ParamsMap result_params_descriptions; + ParamsMap result_params_map; /** * Parse line for result diff --git a/iroha-cli/query_response_handler.hpp b/iroha-cli/query_response_handler.hpp index 18eb0b1a17..85f4b11510 100644 --- a/iroha-cli/query_response_handler.hpp +++ b/iroha-cli/query_response_handler.hpp @@ -23,7 +23,7 @@ #include #include -#include "responses.pb.h" +#include "qry_responses.pb.h" namespace spdlog { class logger; diff --git a/irohad/ametsuchi/CMakeLists.txt b/irohad/ametsuchi/CMakeLists.txt index fef18aa6a1..154fe30c3f 100644 --- a/irohad/ametsuchi/CMakeLists.txt +++ b/irohad/ametsuchi/CMakeLists.txt @@ -7,6 +7,7 @@ add_library(ametsuchi impl/postgres_wsv_command.cpp impl/peer_query_wsv.cpp impl/postgres_block_query.cpp + impl/postgres_command_executor.cpp impl/postgres_block_index.cpp impl/postgres_ordering_service_persistent_state.cpp impl/wsv_restorer_impl.cpp @@ -16,11 +17,16 @@ add_library(ametsuchi target_link_libraries(ametsuchi logger rxcpp - pqxx libs_common command_execution query_execution shared_model_interfaces shared_model_proto_backend shared_model_stateless_validation + SOCI::core + SOCI::postgresql + ) + +target_compile_definitions(ametsuchi + PRIVATE SOCI_USE_BOOST HAVE_BOOST ) diff --git a/irohad/ametsuchi/block_query.hpp b/irohad/ametsuchi/block_query.hpp index 87ca9329d3..70e8fe0ba0 100644 --- a/irohad/ametsuchi/block_query.hpp +++ b/irohad/ametsuchi/block_query.hpp @@ -46,7 +46,7 @@ namespace iroha { * @param account_id - account_id (accountName@domainName) * @return observable of Model Transaction */ - virtual rxcpp::observable getAccountTransactions( + virtual std::vector getAccountTransactions( const shared_model::interface::types::AccountIdType &account_id) = 0; /** @@ -55,7 +55,7 @@ namespace iroha { * @param asset_id - asset_id (assetName#domainName) * @return observable of Model Transaction */ - virtual rxcpp::observable getAccountAssetTransactions( + virtual std::vector getAccountAssetTransactions( const shared_model::interface::types::AccountIdType &account_id, const shared_model::interface::types::AssetIdType &asset_id) = 0; @@ -64,7 +64,7 @@ namespace iroha { * @param tx_hashes - transactions' hashes to retrieve * @return observable of Model Transaction */ - virtual rxcpp::observable> getTransactions( + virtual std::vector> getTransactions( const std::vector &tx_hashes) = 0; /** @@ -73,7 +73,7 @@ namespace iroha { * @param count - number of blocks to retrieve * @return observable of Model Block */ - virtual rxcpp::observable getBlocks( + virtual std::vector getBlocks( shared_model::interface::types::HeightType height, uint32_t count) = 0; @@ -82,7 +82,7 @@ namespace iroha { * @param from - starting height * @return observable of Model Block */ - virtual rxcpp::observable getBlocksFrom( + virtual std::vector getBlocksFrom( shared_model::interface::types::HeightType height) = 0; /** @@ -90,7 +90,7 @@ namespace iroha { * @param count - number of blocks to retrieve * @return observable of Model Block */ - virtual rxcpp::observable getTopBlocks(uint32_t count) = 0; + virtual std::vector getTopBlocks(uint32_t count) = 0; /** * Get height of the top block. diff --git a/irohad/ametsuchi/command_executor.hpp b/irohad/ametsuchi/command_executor.hpp new file mode 100644 index 0000000000..6beb339896 --- /dev/null +++ b/irohad/ametsuchi/command_executor.hpp @@ -0,0 +1,114 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_AMETSUCHI_COMMAND_EXECUTOR_HPP +#define IROHA_AMETSUCHI_COMMAND_EXECUTOR_HPP + +#include "common/result.hpp" +#include "interfaces/common_objects/types.hpp" + +namespace shared_model { + namespace interface { + class AddAssetQuantity; + class AddPeer; + class AddSignatory; + class AppendRole; + class CreateAccount; + class CreateAsset; + class CreateDomain; + class CreateRole; + class DetachRole; + class GrantPermission; + class RemoveSignatory; + class RevokePermission; + class SetAccountDetail; + class SetQuorum; + class SubtractAssetQuantity; + class TransferAsset; + } // namespace interface +} // namespace shared_model + +namespace iroha { + namespace ametsuchi { + + /** + * Error for command execution or validation + * Contains command name, as well as an error message + */ + struct CommandError { + std::string command_name; + std::string error_message; + + std::string toString() const; + }; + + /** + * If command is successful, we assume changes are made, + * and do not need anything + * If something goes wrong, Result will contain Error + * with additional information + */ + using CommandResult = expected::Result; + + class CommandExecutor : public boost::static_visitor { + public: + virtual ~CommandExecutor() = default; + + virtual void setCreatorAccountId( + const shared_model::interface::types::AccountIdType + &creator_account_id) = 0; + + virtual CommandResult operator()( + const shared_model::interface::AddAssetQuantity &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::AddPeer &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::AddSignatory &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::AppendRole &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::CreateAccount &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::CreateAsset &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::CreateDomain &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::CreateRole &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::DetachRole &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::GrantPermission &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::RemoveSignatory &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::RevokePermission &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::SetAccountDetail &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::SetQuorum &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::SubtractAssetQuantity &command) = 0; + + virtual CommandResult operator()( + const shared_model::interface::TransferAsset &command) = 0; + }; + } // namespace ametsuchi +} // namespace iroha + +#endif // IROHA_AMETSUCHI_COMMAND_EXECUTOR_HPP diff --git a/irohad/ametsuchi/impl/mutable_storage_impl.cpp b/irohad/ametsuchi/impl/mutable_storage_impl.cpp index 0130d58849..d8016f3a0c 100644 --- a/irohad/ametsuchi/impl/mutable_storage_impl.cpp +++ b/irohad/ametsuchi/impl/mutable_storage_impl.cpp @@ -20,44 +20,47 @@ #include #include "ametsuchi/impl/postgres_block_index.hpp" +#include "ametsuchi/impl/postgres_command_executor.hpp" #include "ametsuchi/impl/postgres_wsv_command.hpp" #include "ametsuchi/impl/postgres_wsv_query.hpp" #include "ametsuchi/wsv_command.hpp" +#include "interfaces/common_objects/common_objects_factory.hpp" #include "model/sha3_hash.hpp" namespace iroha { namespace ametsuchi { MutableStorageImpl::MutableStorageImpl( shared_model::interface::types::HashType top_hash, - std::unique_ptr connection, - std::unique_ptr transaction) + std::unique_ptr sql, + std::shared_ptr factory) : top_hash_(top_hash), - connection_(std::move(connection)), - transaction_(std::move(transaction)), - wsv_(std::make_unique(*transaction_)), - executor_(std::make_unique(*transaction_)), - block_index_(std::make_unique(*transaction_)), + sql_(std::move(sql)), + wsv_(std::make_shared(*sql_, factory)), + executor_(std::make_shared(*sql_)), + block_index_(std::make_unique(*sql_)), + command_executor_(std::make_shared(*sql_)), committed(false), log_(logger::log("MutableStorage")) { - auto query = std::make_shared(*transaction_); - auto command = std::make_shared(*transaction_); - command_executor_ = - std::make_shared(CommandExecutor(query, command)); - transaction_->exec("BEGIN;"); + *sql_ << "BEGIN"; + } + + bool MutableStorageImpl::check( + const shared_model::interface::BlockVariant &block, + MutableStorage::MutableStoragePredicateType + predicate) { + return predicate(block, *wsv_, top_hash_); } bool MutableStorageImpl::apply( const shared_model::interface::Block &block, - std::function + MutableStoragePredicateType function) { auto execute_transaction = [this](auto &transaction) { command_executor_->setCreatorAccountId(transaction.creatorAccountId()); auto execute_command = [this](auto &command) { auto result = boost::apply_visitor(*command_executor_, command.get()); return result.match([](expected::Value &v) { return true; }, - [&](expected::Error &e) { + [&](expected::Error &e) { log_->error(e.error.toString()); return false; }); @@ -67,7 +70,7 @@ namespace iroha { execute_command); }; - transaction_->exec("SAVEPOINT savepoint_;"); + *sql_ << "SAVEPOINT savepoint_"; auto result = function(block, *wsv_, top_hash_) and std::all_of(block.transactions().begin(), block.transactions().end(), @@ -78,16 +81,16 @@ namespace iroha { block_index_->index(block); top_hash_ = block.hash(); - transaction_->exec("RELEASE SAVEPOINT savepoint_;"); + *sql_ << "RELEASE SAVEPOINT savepoint_"; } else { - transaction_->exec("ROLLBACK TO SAVEPOINT savepoint_;"); + *sql_ << "ROLLBACK TO SAVEPOINT savepoint_"; } return result; } MutableStorageImpl::~MutableStorageImpl() { if (not committed) { - transaction_->exec("ROLLBACK;"); + *sql_ << "ROLLBACK"; } } } // namespace ametsuchi diff --git a/irohad/ametsuchi/impl/mutable_storage_impl.hpp b/irohad/ametsuchi/impl/mutable_storage_impl.hpp index dd65aa9f43..0606eb96fc 100644 --- a/irohad/ametsuchi/impl/mutable_storage_impl.hpp +++ b/irohad/ametsuchi/impl/mutable_storage_impl.hpp @@ -18,12 +18,12 @@ #ifndef IROHA_MUTABLE_STORAGE_IMPL_HPP #define IROHA_MUTABLE_STORAGE_IMPL_HPP +#include #include -#include -#include #include "ametsuchi/mutable_storage.hpp" #include "execution/command_executor.hpp" +#include "interfaces/common_objects/common_objects_factory.hpp" #include "logger/logger.hpp" namespace iroha { @@ -37,17 +37,16 @@ namespace iroha { friend class StorageImpl; public: - MutableStorageImpl( - shared_model::interface::types::HashType top_hash, - std::unique_ptr connection, - std::unique_ptr transaction); + MutableStorageImpl(shared_model::interface::types::HashType top_hash, + std::unique_ptr sql, + std::shared_ptr + factory); + bool check(const shared_model::interface::BlockVariant &block, + MutableStoragePredicateType function) override; bool apply( const shared_model::interface::Block &block, - std::function - function) override; + MutableStoragePredicateType function) override; ~MutableStorageImpl() override; @@ -58,10 +57,9 @@ namespace iroha { std::map> block_store_; - std::unique_ptr connection_; - std::unique_ptr transaction_; - std::unique_ptr wsv_; - std::unique_ptr executor_; + std::unique_ptr sql_; + std::shared_ptr wsv_; + std::shared_ptr executor_; std::unique_ptr block_index_; std::shared_ptr command_executor_; diff --git a/irohad/ametsuchi/impl/postgres_block_index.cpp b/irohad/ametsuchi/impl/postgres_block_index.cpp index 593643b619..dd2e333800 100644 --- a/irohad/ametsuchi/impl/postgres_block_index.cpp +++ b/irohad/ametsuchi/impl/postgres_block_index.cpp @@ -31,18 +31,28 @@ namespace iroha { namespace ametsuchi { - PostgresBlockIndex::PostgresBlockIndex(pqxx::nontransaction &transaction) - : transaction_(transaction), - log_(logger::log("PostgresBlockIndex")), - execute_{makeExecuteOptional(transaction_, log_)} {} + bool execute(soci::statement &st) { + st.define_and_bind(); + try { + st.execute(true); + return true; + } catch (const std::exception &e) { + return false; + } + } + + PostgresBlockIndex::PostgresBlockIndex(soci::session &sql) + : sql_(sql), log_(logger::log("PostgresBlockIndex")) {} auto PostgresBlockIndex::indexAccountIdHeight(const std::string &account_id, const std::string &height) { - return this->execute( - "INSERT INTO height_by_account_set(account_id, height) " - "VALUES (" - + transaction_.quote(account_id) + ", " + transaction_.quote(height) - + ");"); + soci::statement st = + (sql_.prepare << "INSERT INTO height_by_account_set(account_id, " + "height) VALUES " + "(:id, :height)", + soci::use(account_id), + soci::use(height)); + return execute(st); } auto PostgresBlockIndex::indexAccountAssets( @@ -68,18 +78,20 @@ namespace iroha { auto ids = {account_id, command.srcAccountId(), command.destAccountId()}; + auto &asset_id = command.assetId(); // flat map accounts to unindexed keys boost::for_each(ids, [&](const auto &id) { - auto res = execute_( - "INSERT INTO index_by_id_height_asset(id, " - "height, asset_id, " - "index) " - "VALUES (" - + transaction_.quote(id) + ", " - + transaction_.quote(height) + ", " - + transaction_.quote(command.assetId()) + ", " - + transaction_.quote(index) + ");"); - status &= res != boost::none; + soci::statement st = + (sql_.prepare + << "INSERT INTO index_by_id_height_asset(id, " + "height, asset_id, " + "index) " + "VALUES (:id, :height, :asset_id, :index)", + soci::use(id), + soci::use(height), + soci::use(asset_id), + soci::use(index)); + status &= execute(st); }); return status; }, @@ -94,26 +106,31 @@ namespace iroha { block.transactions() | boost::adaptors::indexed(0), [&](const auto &tx) { const auto &creator_id = tx.value().creatorAccountId(); - const auto &hash = tx.value().hash().blob(); + const auto &hash = tx.value().hash().hex(); const auto &index = std::to_string(tx.index()); // tx hash -> block where hash is stored - this->execute("INSERT INTO height_by_hash(hash, height) VALUES (" - + transaction_.quote( - pqxx::binarystring(hash.data(), hash.size())) - + ", " + transaction_.quote(height) + ");"); + soci::statement st = + (sql_.prepare << "INSERT INTO height_by_hash(hash, height) " + "VALUES (:hash, " + ":height)", + soci::use(hash), + soci::use(height)); + execute(st); this->indexAccountIdHeight(creator_id, height); // to make index account_id:height -> list of tx indexes // (where tx is placed in the block) - execute_( - "INSERT INTO index_by_creator_height(creator_id, " - "height, index) " - "VALUES (" - + transaction_.quote(creator_id) + ", " - + transaction_.quote(height) + ", " + transaction_.quote(index) - + ");"); + st = + (sql_.prepare + << "INSERT INTO index_by_creator_height(creator_id, " + "height, index) " + "VALUES (:id, :height, :index)", + soci::use(creator_id), + soci::use(height), + soci::use(index)); + execute(st); this->indexAccountAssets( creator_id, height, index, tx.value().commands()); diff --git a/irohad/ametsuchi/impl/postgres_block_index.hpp b/irohad/ametsuchi/impl/postgres_block_index.hpp index 0ca4fe1fba..60a917bc83 100644 --- a/irohad/ametsuchi/impl/postgres_block_index.hpp +++ b/irohad/ametsuchi/impl/postgres_block_index.hpp @@ -19,10 +19,9 @@ #define IROHA_POSTGRES_BLOCK_INDEX_HPP #include -#include #include "ametsuchi/impl/block_index.hpp" -#include "ametsuchi/impl/postgres_wsv_common.hpp" +#include "ametsuchi/impl/soci_utils.hpp" #include "interfaces/transaction.hpp" #include "logger/logger.hpp" @@ -30,7 +29,7 @@ namespace iroha { namespace ametsuchi { class PostgresBlockIndex : public BlockIndex { public: - explicit PostgresBlockIndex(pqxx::nontransaction &transaction); + explicit PostgresBlockIndex(soci::session &sql); void index(const shared_model::interface::Block &block) override; @@ -58,15 +57,8 @@ namespace iroha { const std::string &index, const shared_model::interface::Transaction::CommandsType &commands); - pqxx::nontransaction &transaction_; + soci::session &sql_; logger::Logger log_; - using ExecuteType = decltype(makeExecuteOptional(transaction_, log_)); - ExecuteType execute_; - - // TODO: refactor to return Result when it is introduced IR-775 - bool execute(const std::string &statement) noexcept { - return static_cast(execute_(statement)); - } }; } // namespace ametsuchi } // namespace iroha diff --git a/irohad/ametsuchi/impl/postgres_block_query.cpp b/irohad/ametsuchi/impl/postgres_block_query.cpp index 079aa466ad..fb6682f096 100644 --- a/irohad/ametsuchi/impl/postgres_block_query.cpp +++ b/irohad/ametsuchi/impl/postgres_block_query.cpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include "ametsuchi/impl/postgres_block_query.hpp" @@ -25,59 +13,39 @@ namespace iroha { namespace ametsuchi { - PostgresBlockQuery::PostgresBlockQuery(pqxx::nontransaction &transaction, + PostgresBlockQuery::PostgresBlockQuery(soci::session &sql, KeyValueStorage &file_store) - : block_store_(file_store), - transaction_(transaction), - log_(logger::log("PostgresBlockIndex")), - execute_{makeExecuteOptional(transaction_, log_)} {} - - PostgresBlockQuery::PostgresBlockQuery( - std::unique_ptr connection, - std::unique_ptr transaction, - KeyValueStorage &file_store) - : connection_ptr_(std::move(connection)), - transaction_ptr_(std::move(transaction)), + : sql_(sql), block_store_(file_store), - transaction_(*transaction_ptr_), - log_(logger::log("PostgresBlockIndex")), - execute_{makeExecuteOptional(transaction_, log_)} {} + log_(logger::log("PostgresBlockIndex")) {} - rxcpp::observable PostgresBlockQuery::getBlocks( + std::vector PostgresBlockQuery::getBlocks( shared_model::interface::types::HeightType height, uint32_t count) { shared_model::interface::types::HeightType last_id = block_store_.last_id(); auto to = std::min(last_id, height + count - 1); + std::vector result; if (height > to or count == 0) { - return rxcpp::observable<>::empty(); + return result; } - return rxcpp::observable<>::range(height, to) - .flat_map([this](const auto &i) { - auto block = block_store_.get(i) | [](const auto &bytes) { - return shared_model::converters::protobuf::jsonToModel< - shared_model::proto::Block>(bytesToString(bytes)); - }; - if (not block) { - log_->error("error while converting from JSON"); - } - - return rxcpp::observable<>::create( - [block{std::move(block)}](const auto &s) { - if (block) { - s.on_next(std::make_shared( - block.value())); - } - s.on_completed(); - }); - }); + for (auto i = height; i <= to; i++) { + block_store_.get(i) | [](const auto &bytes) { + return shared_model::converters::protobuf::jsonToModel< + shared_model::proto::Block>(bytesToString(bytes)); + } | [&result](auto &&block) { + result.push_back( + std::make_shared(std::move(block))); + }; + } + return result; } - rxcpp::observable PostgresBlockQuery::getBlocksFrom( + std::vector PostgresBlockQuery::getBlocksFrom( shared_model::interface::types::HeightType height) { return getBlocks(height, block_store_.last_id()); } - rxcpp::observable PostgresBlockQuery::getTopBlocks( + std::vector PostgresBlockQuery::getTopBlocks( uint32_t count) { auto last_id = block_store_.last_id(); count = std::min(count, last_id); @@ -87,43 +55,42 @@ namespace iroha { std::vector PostgresBlockQuery::getBlockIds( const shared_model::interface::types::AccountIdType &account_id) { - return execute_( - "SELECT DISTINCT height FROM height_by_account_set WHERE " - "account_id = " - + transaction_.quote(account_id) + ";") - | [&](const auto &result) - -> std::vector { - return transform( - result, [&](const auto &row) { - return row.at("height") - .template as(); - }); - }; + std::vector result; + soci::indicator ind; + std::string row; + soci::statement st = + (sql_.prepare << "SELECT DISTINCT height FROM height_by_account_set " + "WHERE account_id = :id", + soci::into(row, ind), + soci::use(account_id)); + st.execute(); + + processSoci(st, ind, row, [&result](std::string &r) { + result.push_back(std::stoull(r)); + }); + return result; } boost::optional PostgresBlockQuery::getBlockId(const shared_model::crypto::Hash &hash) { - boost::optional blockId; - return execute_("SELECT height FROM height_by_hash WHERE hash = " - + transaction_.quote(pqxx::binarystring( - hash.blob().data(), hash.blob().size())) - + ";") - | [&](const auto &result) - -> boost::optional< - shared_model::interface::types::HeightType> { - if (result.size() == 0) { - log_->info("No block with transaction {}", hash.toString()); - return boost::none; - } - return result[0] - .at("height") - .template as(); - }; + boost::optional blockId = boost::none; + boost::optional block_str; + auto hash_str = hash.hex(); + + sql_ << "SELECT height FROM height_by_hash WHERE hash = :hash", + soci::into(block_str), soci::use(hash_str); + if (block_str) { + blockId = std::stoull(block_str.get()); + } else { + log_->info("No block with transaction {}", hash.toString()); + } + return blockId; } - std::function PostgresBlockQuery::callback( - const rxcpp::subscriber &subscriber, uint64_t block_id) { - return [this, &subscriber, block_id](pqxx::result &result) { + std::function &result)> + PostgresBlockQuery::callback(std::vector &blocks, + uint64_t block_id) { + return [this, &blocks, block_id](std::vector &result) { auto block = block_store_.get(block_id) | [](const auto &bytes) { return shared_model::converters::protobuf::jsonToModel< shared_model::proto::Block>(bytesToString(bytes)); @@ -135,76 +102,88 @@ namespace iroha { boost::for_each( result | boost::adaptors::transformed([](const auto &x) { - return x.at("index").template as(); + std::istringstream iss(x); + size_t size; + iss >> size; + return size; }), [&](const auto &x) { - subscriber.on_next(PostgresBlockQuery::wTransaction( + blocks.push_back(PostgresBlockQuery::wTransaction( clone(block->transactions()[x]))); }); }; } - rxcpp::observable + std::vector PostgresBlockQuery::getAccountTransactions( const shared_model::interface::types::AccountIdType &account_id) { - return rxcpp::observable<>::create( - [this, account_id](const auto &subscriber) { - auto block_ids = this->getBlockIds(account_id); - if (block_ids.empty()) { - subscriber.on_completed(); - return; - } - - for (const auto &block_id : block_ids) { - execute_( - "SELECT DISTINCT index FROM index_by_creator_height WHERE " - "creator_id = " - + transaction_.quote(account_id) - + " AND height = " + transaction_.quote(block_id) + ";") - | this->callback(subscriber, block_id); - } - subscriber.on_completed(); - }); + std::vector result; + auto block_ids = this->getBlockIds(account_id); + if (block_ids.empty()) { + return result; + } + for (const auto &block_id : block_ids) { + std::vector index; + soci::indicator ind; + std::string row; + soci::statement st = + (sql_.prepare + << "SELECT DISTINCT index FROM index_by_creator_height " + "WHERE creator_id = :id AND height = :height", + soci::into(row, ind), + soci::use(account_id), + soci::use(block_id)); + st.execute(); + + processSoci( + st, ind, row, [&index](std::string &r) { index.push_back(r); }); + this->callback(result, block_id)(index); + } + return result; } - rxcpp::observable + std::vector PostgresBlockQuery::getAccountAssetTransactions( const shared_model::interface::types::AccountIdType &account_id, const shared_model::interface::types::AssetIdType &asset_id) { - return rxcpp::observable<>::create([this, - account_id, - asset_id]( - auto subscriber) { - auto block_ids = this->getBlockIds(account_id); - if (block_ids.empty()) { - subscriber.on_completed(); - return; - } + std::vector result; + auto block_ids = this->getBlockIds(account_id); + if (block_ids.empty()) { + return result; + } - for (const auto &block_id : block_ids) { - execute_( - "SELECT DISTINCT index FROM index_by_id_height_asset WHERE id = " - + transaction_.quote(account_id) - + " AND height = " + transaction_.quote(block_id) - + " AND asset_id = " + transaction_.quote(asset_id) + ";") - | this->callback(subscriber, block_id); - } - subscriber.on_completed(); - }); + for (const auto &block_id : block_ids) { + std::vector index; + soci::indicator ind; + std::string row; + soci::statement st = + (sql_.prepare + << "SELECT DISTINCT index FROM index_by_id_height_asset " + "WHERE id = :id AND height = :height AND asset_id = " + ":asset_id", + soci::into(row, ind), + soci::use(account_id), + soci::use(block_id), + soci::use(asset_id)); + st.execute(); + + processSoci( + st, ind, row, [&index](std::string &r) { index.push_back(r); }); + this->callback(result, block_id)(index); + } + return result; } - rxcpp::observable> + std::vector> PostgresBlockQuery::getTransactions( const std::vector &tx_hashes) { - return rxcpp::observable<>::create>( - [this, tx_hashes](const auto &subscriber) { - std::for_each(tx_hashes.begin(), - tx_hashes.end(), - [that = this, &subscriber](const auto &tx_hash) { - subscriber.on_next(that->getTxByHashSync(tx_hash)); - }); - subscriber.on_completed(); - }); + std::vector> result; + std::for_each(tx_hashes.begin(), + tx_hashes.end(), + [this, &result](const auto &tx_hash) { + result.push_back(this->getTxByHashSync(tx_hash)); + }); + return result; } boost::optional diff --git a/irohad/ametsuchi/impl/postgres_block_query.hpp b/irohad/ametsuchi/impl/postgres_block_query.hpp index 5573f41a55..f6efb53a50 100644 --- a/irohad/ametsuchi/impl/postgres_block_query.hpp +++ b/irohad/ametsuchi/impl/postgres_block_query.hpp @@ -1,31 +1,17 @@ /** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_POSTGRES_FLAT_BLOCK_QUERY_HPP #define IROHA_POSTGRES_FLAT_BLOCK_QUERY_HPP #include -#include -#include #include "ametsuchi/block_query.hpp" #include "ametsuchi/impl/flat_file/flat_file.hpp" +#include "ametsuchi/impl/soci_utils.hpp" #include "logger/logger.hpp" -#include "postgres_wsv_common.hpp" namespace iroha { namespace ametsuchi { @@ -37,34 +23,31 @@ namespace iroha { */ class PostgresBlockQuery : public BlockQuery { public: - PostgresBlockQuery(pqxx::nontransaction &transaction_, - KeyValueStorage &file_store); - PostgresBlockQuery(std::unique_ptr connection, - std::unique_ptr transaction, - KeyValueStorage &file_store); + explicit PostgresBlockQuery(soci::session &sql, + KeyValueStorage &file_store); - rxcpp::observable getAccountTransactions( + std::vector getAccountTransactions( const shared_model::interface::types::AccountIdType &account_id) override; - rxcpp::observable getAccountAssetTransactions( + std::vector getAccountAssetTransactions( const shared_model::interface::types::AccountIdType &account_id, const shared_model::interface::types::AssetIdType &asset_id) override; - rxcpp::observable> getTransactions( + std::vector> getTransactions( const std::vector &tx_hashes) override; boost::optional getTxByHashSync( const shared_model::crypto::Hash &hash) override; - rxcpp::observable getBlocks( + std::vector getBlocks( shared_model::interface::types::HeightType height, uint32_t count) override; - rxcpp::observable getBlocksFrom( + std::vector getBlocksFrom( shared_model::interface::types::HeightType height) override; - rxcpp::observable getTopBlocks(uint32_t count) override; + std::vector getTopBlocks(uint32_t count) override; uint32_t getTopBlockHeight() override; @@ -96,17 +79,13 @@ namespace iroha { * @param block_id * @return */ - std::function callback( - const rxcpp::subscriber &s, uint64_t block_id); + std::function &result)> callback( + std::vector &s, uint64_t block_id); - std::unique_ptr connection_ptr_; - std::unique_ptr transaction_ptr_; + soci::session &sql_; KeyValueStorage &block_store_; - pqxx::nontransaction &transaction_; logger::Logger log_; - using ExecuteType = decltype(makeExecuteOptional(transaction_, log_)); - ExecuteType execute_; }; } // namespace ametsuchi } // namespace iroha diff --git a/irohad/ametsuchi/impl/postgres_command_executor.cpp b/irohad/ametsuchi/impl/postgres_command_executor.cpp new file mode 100644 index 0000000000..f29018d8be --- /dev/null +++ b/irohad/ametsuchi/impl/postgres_command_executor.cpp @@ -0,0 +1,799 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ametsuchi/impl/postgres_command_executor.hpp" + +#include + +#include "ametsuchi/impl/soci_utils.hpp" +#include "backend/protobuf/permissions.hpp" +#include "interfaces/commands/add_asset_quantity.hpp" +#include "interfaces/commands/add_peer.hpp" +#include "interfaces/commands/add_signatory.hpp" +#include "interfaces/commands/append_role.hpp" +#include "interfaces/commands/create_account.hpp" +#include "interfaces/commands/create_asset.hpp" +#include "interfaces/commands/create_domain.hpp" +#include "interfaces/commands/create_role.hpp" +#include "interfaces/commands/detach_role.hpp" +#include "interfaces/commands/grant_permission.hpp" +#include "interfaces/commands/remove_signatory.hpp" +#include "interfaces/commands/revoke_permission.hpp" +#include "interfaces/commands/set_account_detail.hpp" +#include "interfaces/commands/set_quorum.hpp" +#include "interfaces/commands/subtract_asset_quantity.hpp" +#include "interfaces/commands/transfer_asset.hpp" +#include "interfaces/common_objects/types.hpp" + +namespace { + iroha::expected::Error makeCommandError( + const std::string &error_message, + const std::string &command_name) noexcept { + return iroha::expected::makeError( + iroha::ametsuchi::CommandError{command_name, error_message}); + } + + /** + * Transforms soci statement to CommandResult, + * which will have error message generated by error_generator + * appended to error received from given result + * @param result which can be received by calling execute_ + * @param error_generator function which must generate error message + * to be used as a return error. + * Function is passed instead of string to avoid overhead of string + * construction in successful case. + * @return CommandResult with combined error message + * in case of result contains error + */ + template + iroha::ametsuchi::CommandResult makeCommandResult( + soci::statement &st, + const std::string &command_name, + Function &&error_generator) noexcept { + st.define_and_bind(); + try { + st.execute(true); + } catch (std::exception &e) { + return makeCommandError(error_generator() + "\n" + e.what(), + command_name); + } + return {}; + } + + /** + * Transforms soci statement to CommandResult, + * which will have error message generated exception + * Assums that statement query returns 0 in case of success or error code + * @param result which can be received by calling execute_ + * @param error_generator functions which must generate error message + * to be used as a return error. + * Functions are passed instead of string to avoid overhead of string + * construction in successful case. + * @return CommandResult with combined error message + * in case of result contains error + */ + iroha::ametsuchi::CommandResult makeCommandResultByReturnedValue( + soci::statement &st, + const std::string &command_name, + std::vector> &error_generator) noexcept { + uint32_t result; + st.exchange(soci::into(result)); + st.define_and_bind(); + try { + st.execute(true); + if (result != 0) { + return makeCommandError(error_generator[result - 1](), command_name); + } + return {}; + } catch (std::exception &e) { + return makeCommandError(e.what(), command_name); + } + } +} // namespace + +namespace iroha { + namespace ametsuchi { + + std::string CommandError::toString() const { + return (boost::format("%s: %s") % command_name % error_message).str(); + } + + PostgresCommandExecutor::PostgresCommandExecutor(soci::session &sql) + : sql_(sql) {} + + void PostgresCommandExecutor::setCreatorAccountId( + const shared_model::interface::types::AccountIdType + &creator_account_id) { + creator_account_id_ = creator_account_id; + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::AddAssetQuantity &command) { + auto &account_id = creator_account_id_; + auto &asset_id = command.assetId(); + auto amount = command.amount().toStringRepr(); + auto precision = command.amount().precision(); + soci::statement st = sql_.prepare << + // clang-format off + (R"( + WITH has_account AS (SELECT account_id FROM account + WHERE account_id = :account_id LIMIT 1), + has_asset AS (SELECT asset_id FROM asset + WHERE asset_id = :asset_id AND + precision >= :precision LIMIT 1), + amount AS (SELECT amount FROM account_has_asset + WHERE asset_id = :asset_id AND + account_id = :account_id LIMIT 1), + new_value AS (SELECT :new_value::decimal + + (SELECT + CASE WHEN EXISTS + (SELECT amount FROM amount LIMIT 1) THEN + (SELECT amount FROM amount LIMIT 1) + ELSE 0::decimal + END) AS value + ), + inserted AS + ( + INSERT INTO account_has_asset(account_id, asset_id, amount) + ( + SELECT :account_id, :asset_id, value FROM new_value + WHERE EXISTS (SELECT * FROM has_account LIMIT 1) AND + EXISTS (SELECT * FROM has_asset LIMIT 1) AND + EXISTS (SELECT value FROM new_value + WHERE value < 2::decimal ^ (256 - :precision) + LIMIT 1) + ) + ON CONFLICT (account_id, asset_id) DO UPDATE + SET amount = EXCLUDED.amount + RETURNING (1) + ) + SELECT CASE + WHEN EXISTS (SELECT * FROM inserted LIMIT 1) THEN 0 + WHEN NOT EXISTS (SELECT * FROM has_account LIMIT 1) THEN 1 + WHEN NOT EXISTS (SELECT * FROM has_asset LIMIT 1) THEN 2 + WHEN NOT EXISTS (SELECT value FROM new_value + WHERE value < 2::decimal ^ (256 - :precision) + LIMIT 1) THEN 3 + ELSE 4 + END AS result;)"); + // clang-format on + + st.exchange(soci::use(account_id, "account_id")); + st.exchange(soci::use(asset_id, "asset_id")); + st.exchange(soci::use(amount, "new_value")); + st.exchange(soci::use(precision, "precision")); + + std::vector> message_gen = { + [] { return std::string("Account does not exist"); }, + [] { + return std::string("Asset with given precision does not exist"); + }, + [] { return std::string("Summation overflows uint256"); }, + }; + return makeCommandResultByReturnedValue( + st, "AddAssetQuantity", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::AddPeer &command) { + auto &peer = command.peer(); + soci::statement st = sql_.prepare + << "INSERT INTO peer(public_key, address) VALUES (:pk, :address)"; + st.exchange(soci::use(peer.pubkey().hex())); + st.exchange(soci::use(peer.address())); + auto message_gen = [&] { + return (boost::format( + "failed to insert peer, public key: '%s', address: '%s'") + % peer.pubkey().hex() % peer.address()) + .str(); + }; + return makeCommandResult(st, "AddPeer", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::AddSignatory &command) { + auto &account_id = command.accountId(); + auto pubkey = command.pubkey().hex(); + soci::statement st = sql_.prepare << + R"( + WITH insert_signatory AS + ( + INSERT INTO signatory(public_key) VALUES (:pk) + ON CONFLICT DO NOTHING RETURNING (1) + ), + has_signatory AS (SELECT * FROM signatory WHERE public_key = :pk), + insert_account_signatory AS + ( + INSERT INTO account_has_signatory(account_id, public_key) + ( + SELECT :account_id, :pk WHERE EXISTS + (SELECT * FROM insert_signatory) OR + EXISTS (SELECT * FROM has_signatory) + ) + RETURNING (1) + ) + SELECT CASE + WHEN EXISTS (SELECT * FROM insert_account_signatory) THEN 0 + WHEN EXISTS (SELECT * FROM insert_signatory) THEN 1 + ELSE 2 + END AS RESULT;)"; + st.exchange(soci::use(pubkey, "pk")); + st.exchange(soci::use(account_id, "account_id")); + + std::vector> message_gen = { + [&] { + return (boost::format( + "failed to insert account signatory, account id: " + "'%s', signatory hex string: '%s") + % account_id % pubkey) + .str(); + }, + [&] { + return (boost::format("failed to insert signatory, " + "signatory hex string: '%s'") + % pubkey) + .str(); + }, + }; + return makeCommandResultByReturnedValue(st, "AddSignatory", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::AppendRole &command) { + auto &account_id = command.accountId(); + auto &role_name = command.roleName(); + soci::statement st = sql_.prepare + << "INSERT INTO account_has_roles(account_id, role_id) VALUES " + "(:account_id, :role_id)"; + st.exchange(soci::use(account_id)); + st.exchange(soci::use(role_name)); + auto message_gen = [&] { + return (boost::format("failed to insert account role, account: '%s', " + "role name: '%s'") + % account_id % role_name) + .str(); + }; + return makeCommandResult(st, "AppendRole", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::CreateAccount &command) { + auto &account_name = command.accountName(); + auto &domain_id = command.domainId(); + auto &pubkey = command.pubkey().hex(); + std::string account_id = account_name + "@" + domain_id; + soci::statement st = sql_.prepare << + R"( + WITH get_domain_default_role AS (SELECT default_role FROM domain + WHERE domain_id = :domain_id), + insert_signatory AS + ( + INSERT INTO signatory(public_key) + ( + SELECT :pk WHERE EXISTS + (SELECT * FROM get_domain_default_role) + ) ON CONFLICT DO NOTHING RETURNING (1) + ), + has_signatory AS (SELECT * FROM signatory WHERE public_key = :pk), + insert_account AS + ( + INSERT INTO account(account_id, domain_id, quorum, data) + ( + SELECT :account_id, :domain_id, 1, '{}' WHERE (EXISTS + (SELECT * FROM insert_signatory) OR EXISTS + (SELECT * FROM has_signatory) + ) AND EXISTS (SELECT * FROM get_domain_default_role) + ) RETURNING (1) + ), + insert_account_signatory AS + ( + INSERT INTO account_has_signatory(account_id, public_key) + ( + SELECT :account_id, :pk WHERE + EXISTS (SELECT * FROM insert_account) + ) + RETURNING (1) + ), + insert_account_role AS + ( + INSERT INTO account_has_roles(account_id, role_id) + ( + SELECT :account_id, default_role FROM get_domain_default_role + WHERE EXISTS (SELECT * FROM get_domain_default_role) + AND EXISTS (SELECT * FROM insert_account_signatory) + ) RETURNING (1) + ) + SELECT CASE + WHEN EXISTS (SELECT * FROM insert_account_role) THEN 0 + WHEN NOT EXISTS (SELECT * FROM account + WHERE account_id = :account_id) THEN 1 + WHEN NOT EXISTS (SELECT * FROM account_has_signatory + WHERE account_id = :account_id + AND public_key = :pk) THEN 2 + WHEN NOT EXISTS (SELECT * FROM account_has_roles + WHERE account_id = account_id AND role_id = ( + SELECT default_role FROM get_domain_default_role) + ) THEN 3 + ELSE 4 + END AS result +)"; + st.exchange(soci::use(account_id, "account_id")); + st.exchange(soci::use(domain_id, "domain_id")); + st.exchange(soci::use(pubkey, "pk")); + std::vector> message_gen = { + [&] { + return (boost::format("failed to insert account, " + "account id: '%s', " + "domain id: '%s', " + "quorum: '1', " + "json_data: {}") + % account_id % domain_id) + .str(); + }, + [&] { + return (boost::format("failed to insert account signatory, " + "account id: " + "'%s', signatory hex string: '%s") + % account_id % pubkey) + .str(); + }, + [&] { + return (boost::format( + "failed to insert account role, account: '%s' " + "with default domain role name for domain: " + "'%s'") + % account_id % domain_id) + .str(); + }, + }; + return makeCommandResultByReturnedValue(st, "CreateAccount", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::CreateAsset &command) { + auto &domain_id = command.domainId(); + auto asset_id = command.assetName() + "#" + domain_id; + auto precision = command.precision(); + soci::statement st = sql_.prepare + << "INSERT INTO asset(asset_id, domain_id, \"precision\", data) " + "VALUES (:id, :domain_id, :precision, NULL)"; + st.exchange(soci::use(asset_id)); + st.exchange(soci::use(domain_id)); + st.exchange(soci::use(precision)); + auto message_gen = [&] { + return (boost::format("failed to insert asset, asset id: '%s', " + "domain id: '%s', precision: %d") + % asset_id % domain_id % precision) + .str(); + }; + return makeCommandResult(st, "CreateAsset", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::CreateDomain &command) { + auto &domain_id = command.domainId(); + auto &default_role = command.userDefaultRole(); + soci::statement st = sql_.prepare + << "INSERT INTO domain(domain_id, default_role) VALUES (:id, " + ":role)"; + st.exchange(soci::use(domain_id)); + st.exchange(soci::use(default_role)); + auto message_gen = [&] { + return (boost::format("failed to insert domain, domain id: '%s', " + "default role: '%s'") + % domain_id % default_role) + .str(); + }; + return makeCommandResult(st, "CreateDomain", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::CreateRole &command) { + auto &role_id = command.roleName(); + auto &permissions = command.rolePermissions(); + auto perm_str = permissions.toBitstring(); + soci::statement st = sql_.prepare << + R"( + WITH insert_role AS (INSERT INTO role(role_id) + VALUES (:role_id) RETURNING (1)), + insert_role_permissions AS + ( + INSERT INTO role_has_permissions(role_id, permission) + ( + SELECT :role_id, :perms WHERE EXISTS + (SELECT * FROM insert_role) + ) RETURNING (1) + ) + SELECT CASE + WHEN EXISTS (SELECT * FROM insert_role_permissions) THEN 0 + WHEN EXISTS (SELECT * FROM role WHERE role_id = :role_id) THEN 1 + ELSE 2 + END AS result +)"; + st.exchange(soci::use(role_id, "role_id")); + st.exchange(soci::use(perm_str, "perms")); + + std::vector> message_gen = { + [&] { + // TODO(@l4l) 26/06/18 need to be simplified at IR-1479 + const auto &str = + shared_model::proto::permissions::toString(permissions); + const auto perm_debug_str = + std::accumulate(str.begin(), str.end(), std::string()); + return (boost::format("failed to insert role permissions, role " + "id: '%s', permissions: [%s]") + % role_id % perm_debug_str) + .str(); + }, + [&] { + return (boost::format("failed to insert role: '%s'") % role_id) + .str(); + }, + }; + return makeCommandResultByReturnedValue(st, "CreateRole", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::DetachRole &command) { + auto &account_id = command.accountId(); + auto &role_name = command.roleName(); + soci::statement st = sql_.prepare + << "DELETE FROM account_has_roles WHERE account_id=:account_id " + "AND role_id=:role_id"; + st.exchange(soci::use(account_id)); + st.exchange(soci::use(role_name)); + auto message_gen = [&] { + return (boost::format( + "failed to delete account role, account id: '%s', " + "role name: '%s'") + % account_id % role_name) + .str(); + }; + return makeCommandResult(st, "DetachRole", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::GrantPermission &command) { + auto &permittee_account_id = command.accountId(); + auto &account_id = creator_account_id_; + auto permission = command.permissionName(); + const auto perm_str = + shared_model::interface::GrantablePermissionSet({permission}) + .toBitstring(); + soci::statement st = sql_.prepare + << "INSERT INTO account_has_grantable_permissions as " + "has_perm(permittee_account_id, account_id, permission) VALUES " + "(:permittee_account_id, :account_id, :perms) ON CONFLICT " + "(permittee_account_id, account_id) " + // SELECT will end up with a error, if the permission exists + "DO UPDATE SET permission=(SELECT has_perm.permission | :perms " + "WHERE (has_perm.permission & :perms) <> :perms);"; + st.exchange(soci::use(permittee_account_id, "permittee_account_id")); + st.exchange(soci::use(account_id, "account_id")); + st.exchange(soci::use(perm_str, "perms")); + auto message_gen = [&] { + return (boost::format("failed to insert account grantable permission, " + "permittee account id: '%s', " + "account id: '%s', " + "permission: '%s'") + % permittee_account_id + % account_id + // TODO(@l4l) 26/06/18 need to be simplified at IR-1479 + % shared_model::proto::permissions::toString(permission)) + .str(); + }; + + return makeCommandResult(st, "GrantPermission", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::RemoveSignatory &command) { + auto &account_id = command.accountId(); + auto &pubkey = command.pubkey().hex(); + soci::statement st = sql_.prepare << + R"( + WITH delete_account_signatory AS (DELETE FROM account_has_signatory + WHERE account_id = :account_id + AND public_key = :pk RETURNING (1)), + delete_signatory AS + ( + DELETE FROM signatory WHERE public_key = :pk AND + NOT EXISTS (SELECT 1 FROM account_has_signatory + WHERE public_key = :pk) + AND NOT EXISTS (SELECT 1 FROM peer WHERE public_key = :pk) + RETURNING (1) + ) + SELECT CASE + WHEN EXISTS (SELECT * FROM delete_account_signatory) THEN + CASE + WHEN EXISTS (SELECT * FROM delete_signatory) THEN 0 + WHEN EXISTS (SELECT 1 FROM account_has_signatory + WHERE public_key = :pk) THEN 0 + WHEN EXISTS (SELECT 1 FROM peer + WHERE public_key = :pk) THEN 0 + ELSE 2 + END + ELSE 1 + END AS result +)"; + st.exchange(soci::use(account_id, "account_id")); + st.exchange(soci::use(pubkey, "pk")); + std::vector> message_gen = { + [&] { + return (boost::format( + "failed to delete account signatory, account id: " + "'%s', signatory hex string: '%s'") + % account_id % pubkey) + .str(); + }, + [&] { + return (boost::format("failed to delete signatory, " + "signatory hex string: '%s'") + % pubkey) + .str(); + }, + }; + return makeCommandResultByReturnedValue( + st, "RemoveSignatory", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::RevokePermission &command) { + auto &permittee_account_id = command.accountId(); + auto &account_id = creator_account_id_; + auto permission = command.permissionName(); + const auto without_perm_str = + shared_model::interface::GrantablePermissionSet() + .set() + .unset(permission) + .toBitstring(); + const auto perms = shared_model::interface::GrantablePermissionSet() + .set(permission) + .toBitstring(); + soci::statement st = sql_.prepare + << "UPDATE account_has_grantable_permissions as has_perm " + // SELECT will end up with a error, if the permission + // doesn't exists + "SET permission=(SELECT has_perm.permission & :without_perm " + "WHERE has_perm.permission & :perm = :perm AND " + "has_perm.permittee_account_id=:permittee_account_id AND " + "has_perm.account_id=:account_id) WHERE " + "permittee_account_id=:permittee_account_id AND " + "account_id=:account_id"; + st.exchange(soci::use(permittee_account_id, "permittee_account_id")); + st.exchange(soci::use(account_id, "account_id")); + st.exchange(soci::use(without_perm_str, "without_perm")); + st.exchange(soci::use(perms, "perm")); + auto message_gen = [&] { + return (boost::format("failed to delete account grantable permission, " + "permittee account id: '%s', " + "account id: '%s', " + "permission id: '%s'") + % permittee_account_id + % account_id + // TODO(@l4l) 26/06/18 need to be simplified at IR-1479 + % shared_model::proto::permissions::toString(permission)) + .str(); + }; + return makeCommandResult(st, "RevokePermission", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::SetAccountDetail &command) { + auto &account_id = command.accountId(); + auto &key = command.key(); + auto &value = command.value(); + if (creator_account_id_.empty()) { + // When creator is not known, it is genesis block + creator_account_id_ = "genesis"; + } + std::string json = "{" + creator_account_id_ + "}"; + std::string empty_json = "{}"; + std::string filled_json = "{" + creator_account_id_ + ", " + key + "}"; + std::string val = "\"" + value + "\""; + soci::statement st = sql_.prepare + << "UPDATE account SET data = jsonb_set(" + "CASE WHEN data ?:creator_account_id THEN data ELSE " + "jsonb_set(data, :json, :empty_json) END, " + " :filled_json, :val) WHERE account_id=:account_id"; + st.exchange(soci::use(creator_account_id_)); + st.exchange(soci::use(json)); + st.exchange(soci::use(empty_json)); + st.exchange(soci::use(filled_json)); + st.exchange(soci::use(val)); + st.exchange(soci::use(account_id)); + auto message_gen = [&] { + return (boost::format( + "failed to set account key-value, account id: '%s', " + "creator account id: '%s',\n key: '%s', value: '%s'") + % account_id % creator_account_id_ % key % value) + .str(); + }; + return makeCommandResult(st, "SetAccountDetail", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::SetQuorum &command) { + auto &account_id = command.accountId(); + auto quorum = command.newQuorum(); + soci::statement st = sql_.prepare + << "UPDATE account SET quorum=:quorum WHERE account_id=:account_id"; + st.exchange(soci::use(quorum)); + st.exchange(soci::use(account_id)); + auto message_gen = [&] { + return (boost::format( + "failed to update account, account id: '%s', quorum: '%s'") + % account_id % quorum) + .str(); + }; + return makeCommandResult(st, "SetQuorum", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::SubtractAssetQuantity &command) { + auto &account_id = creator_account_id_; + auto &asset_id = command.assetId(); + auto amount = command.amount().toStringRepr(); + uint32_t precision = command.amount().precision(); + soci::statement st = sql_.prepare << + // clang-format off + R"( + WITH has_account AS (SELECT account_id FROM account + WHERE account_id = :account_id LIMIT 1), + has_asset AS (SELECT asset_id FROM asset + WHERE asset_id = :asset_id + AND precision >= :precision LIMIT 1), + amount AS (SELECT amount FROM account_has_asset + WHERE asset_id = :asset_id + AND account_id = :account_id LIMIT 1), + new_value AS (SELECT + (SELECT + CASE WHEN EXISTS + (SELECT amount FROM amount LIMIT 1) + THEN (SELECT amount FROM amount LIMIT 1) + ELSE 0::decimal + END) - :value::decimal AS value + ), + inserted AS + ( + INSERT INTO account_has_asset(account_id, asset_id, amount) + ( + SELECT :account_id, :asset_id, value FROM new_value + WHERE EXISTS (SELECT * FROM has_account LIMIT 1) AND + EXISTS (SELECT * FROM has_asset LIMIT 1) AND + EXISTS (SELECT value FROM new_value WHERE value >= 0 LIMIT 1) + ) + ON CONFLICT (account_id, asset_id) + DO UPDATE SET amount = EXCLUDED.amount + RETURNING (1) + ) + SELECT CASE + WHEN EXISTS (SELECT * FROM inserted LIMIT 1) THEN 0 + WHEN NOT EXISTS (SELECT * FROM has_account LIMIT 1) THEN 1 + WHEN NOT EXISTS (SELECT * FROM has_asset LIMIT 1) THEN 2 + WHEN NOT EXISTS + (SELECT value FROM new_value WHERE value >= 0 LIMIT 1) THEN 3 + ELSE 4 + END AS result;)"; + // clang-format on + st.exchange(soci::use(account_id, "account_id")); + st.exchange(soci::use(asset_id, "asset_id")); + st.exchange(soci::use(amount, "value")); + st.exchange(soci::use(precision, "precision")); + + std::vector> message_gen = { + [&] { return "Account does not exist with given precision"; }, + [&] { return "Asset with given precision does not exist"; }, + [&] { return "Subtracts overdrafts account asset"; }, + }; + return makeCommandResultByReturnedValue( + st, "SubtractAssetQuantity", message_gen); + } + + CommandResult PostgresCommandExecutor::operator()( + const shared_model::interface::TransferAsset &command) { + auto &src_account_id = command.srcAccountId(); + auto &dest_account_id = command.destAccountId(); + auto &asset_id = command.assetId(); + auto amount = command.amount().toStringRepr(); + uint32_t precision = command.amount().precision(); + soci::statement st = sql_.prepare << + // clang-format off + R"( + WITH has_src_account AS (SELECT account_id FROM account + WHERE account_id = :src_account_id LIMIT 1), + has_dest_account AS (SELECT account_id FROM account + WHERE account_id = :dest_account_id + LIMIT 1), + has_asset AS (SELECT asset_id FROM asset + WHERE asset_id = :asset_id AND + precision >= :precision LIMIT 1), + src_amount AS (SELECT amount FROM account_has_asset + WHERE asset_id = :asset_id AND + account_id = :src_account_id LIMIT 1), + dest_amount AS (SELECT amount FROM account_has_asset + WHERE asset_id = :asset_id AND + account_id = :dest_account_id LIMIT 1), + new_src_value AS (SELECT + (SELECT + CASE WHEN EXISTS + (SELECT amount FROM src_amount LIMIT 1) + THEN + (SELECT amount FROM src_amount LIMIT 1) + ELSE 0::decimal + END) - :value::decimal AS value + ), + new_dest_value AS (SELECT + (SELECT :value::decimal + + CASE WHEN EXISTS + (SELECT amount FROM dest_amount LIMIT 1) + THEN + (SELECT amount FROM dest_amount LIMIT 1) + ELSE 0::decimal + END) AS value + ), + insert_src AS + ( + INSERT INTO account_has_asset(account_id, asset_id, amount) + ( + SELECT :src_account_id, :asset_id, value + FROM new_src_value + WHERE EXISTS (SELECT * FROM has_src_account LIMIT 1) AND + EXISTS (SELECT * FROM has_dest_account LIMIT 1) AND + EXISTS (SELECT * FROM has_asset LIMIT 1) AND + EXISTS (SELECT value FROM new_src_value + WHERE value >= 0 LIMIT 1) + ) + ON CONFLICT (account_id, asset_id) + DO UPDATE SET amount = EXCLUDED.amount + RETURNING (1) + ), + insert_dest AS + ( + INSERT INTO account_has_asset(account_id, asset_id, amount) + ( + SELECT :dest_account_id, :asset_id, value + FROM new_dest_value + WHERE EXISTS (SELECT * FROM insert_src) AND + EXISTS (SELECT * FROM has_src_account LIMIT 1) AND + EXISTS (SELECT * FROM has_dest_account LIMIT 1) AND + EXISTS (SELECT * FROM has_asset LIMIT 1) AND + EXISTS (SELECT value FROM new_dest_value + WHERE value < 2::decimal ^ (256 - :precision) + LIMIT 1) + ) + ON CONFLICT (account_id, asset_id) + DO UPDATE SET amount = EXCLUDED.amount + RETURNING (1) + ) + SELECT CASE + WHEN EXISTS (SELECT * FROM insert_dest LIMIT 1) THEN 0 + WHEN NOT EXISTS (SELECT * FROM has_dest_account LIMIT 1) THEN 1 + WHEN NOT EXISTS (SELECT * FROM has_src_account LIMIT 1) THEN 2 + WHEN NOT EXISTS (SELECT * FROM has_asset LIMIT 1) THEN 3 + WHEN NOT EXISTS (SELECT value FROM new_src_value + WHERE value >= 0 LIMIT 1) THEN 4 + WHEN NOT EXISTS (SELECT value FROM new_dest_value + WHERE value < 2::decimal ^ (256 - :precision) + LIMIT 1) THEN 5 + ELSE 6 + END AS result;)"; + // clang-format on + st.exchange(soci::use(src_account_id, "src_account_id")); + st.exchange(soci::use(dest_account_id, "dest_account_id")); + st.exchange(soci::use(asset_id, "asset_id")); + st.exchange(soci::use(amount, "value")); + st.exchange(soci::use(precision, "precision")); + std::vector> message_gen = { + [&] { return "Destination account does not exist"; }, + [&] { return "Source account does not exist"; }, + [&] { return "Asset with given precision does not exist"; }, + [&] { return "Transfer overdrafts source account asset"; }, + [&] { return "Transfer overflows destanation account asset"; }, + }; + return makeCommandResultByReturnedValue(st, "TransferAsset", message_gen); + } + } // namespace ametsuchi +} // namespace iroha diff --git a/irohad/ametsuchi/impl/postgres_command_executor.hpp b/irohad/ametsuchi/impl/postgres_command_executor.hpp new file mode 100644 index 0000000000..4c4441b96d --- /dev/null +++ b/irohad/ametsuchi/impl/postgres_command_executor.hpp @@ -0,0 +1,80 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_POSTGRES_COMMAND_EXECUTOR_HPP +#define IROHA_POSTGRES_COMMAND_EXECUTOR_HPP + +#include "ametsuchi/command_executor.hpp" +#include "ametsuchi/impl/soci_utils.hpp" + +namespace iroha { + namespace ametsuchi { + + class PostgresCommandExecutor : public CommandExecutor { + public: + explicit PostgresCommandExecutor(soci::session &transaction); + + void setCreatorAccountId( + const shared_model::interface::types::AccountIdType + &creator_account_id) override; + + CommandResult operator()( + const shared_model::interface::AddAssetQuantity &command) override; + + CommandResult operator()( + const shared_model::interface::AddPeer &command) override; + + CommandResult operator()( + const shared_model::interface::AddSignatory &command) override; + + CommandResult operator()( + const shared_model::interface::AppendRole &command) override; + + CommandResult operator()( + const shared_model::interface::CreateAccount &command) override; + + CommandResult operator()( + const shared_model::interface::CreateAsset &command) override; + + CommandResult operator()( + const shared_model::interface::CreateDomain &command) override; + + CommandResult operator()( + const shared_model::interface::CreateRole &command) override; + + CommandResult operator()( + const shared_model::interface::DetachRole &command) override; + + CommandResult operator()( + const shared_model::interface::GrantPermission &command) override; + + CommandResult operator()( + const shared_model::interface::RemoveSignatory &command) override; + + CommandResult operator()( + const shared_model::interface::RevokePermission &command) override; + + CommandResult operator()( + const shared_model::interface::SetAccountDetail &command) override; + + CommandResult operator()( + const shared_model::interface::SetQuorum &command) override; + + CommandResult operator()( + const shared_model::interface::SubtractAssetQuantity &command) + override; + + CommandResult operator()( + const shared_model::interface::TransferAsset &command) override; + + private: + soci::session &sql_; + + shared_model::interface::types::AccountIdType creator_account_id_; + }; + } // namespace ametsuchi +} // namespace iroha + +#endif // IROHA_POSTGRES_COMMAND_EXECUTOR_HPP diff --git a/irohad/ametsuchi/impl/postgres_ordering_service_persistent_state.cpp b/irohad/ametsuchi/impl/postgres_ordering_service_persistent_state.cpp index e8a9caf12f..b94fbce747 100644 --- a/irohad/ametsuchi/impl/postgres_ordering_service_persistent_state.cpp +++ b/irohad/ametsuchi/impl/postgres_ordering_service_persistent_state.cpp @@ -16,108 +16,89 @@ */ #include "ametsuchi/impl/postgres_ordering_service_persistent_state.hpp" + #include #include +#include + #include "common/types.hpp" namespace iroha { namespace ametsuchi { + bool PostgresOrderingServicePersistentState::execute_(std::string query) { + try { + *sql_ << query; + } catch (std::exception &e) { + log_->error("Failed to execute query: " + query + + ". Reason: " + e.what()); + return false; + } + return true; + } + expected::Result, std::string> PostgresOrderingServicePersistentState::create( const std::string &postgres_options) { - // create connection - auto postgres_connection = - std::make_unique(postgres_options); + std::unique_ptr sql; try { - postgres_connection->activate(); - } catch (const pqxx::broken_connection &e) { + sql = + std::make_unique(soci::postgresql, postgres_options); + + } catch (std::exception &e) { return expected::makeError( (boost::format("Connection to PostgreSQL broken: %s") % e.what()) .str()); } - - // create transaction - auto postgres_transaction = std::make_unique( - *postgres_connection, "Storage"); expected::Result, std::string> storage; storage = expected::makeValue( std::make_shared( - std::move(postgres_connection), std::move(postgres_transaction))); + std::move(sql))); return storage; } PostgresOrderingServicePersistentState:: PostgresOrderingServicePersistentState( - std::unique_ptr postgres_connection, - std::unique_ptr postgres_transaction) - : postgres_connection_(std::move(postgres_connection)), - postgres_transaction_(std::move(postgres_transaction)), - log_(logger::log("PostgresOrderingServicePersistentState")), - execute_{ametsuchi::makeExecuteResult(*postgres_transaction_)} {} + std::unique_ptr sql) + : sql_(std::move(sql)), + log_(logger::log("PostgresOrderingServicePersistentState")) {} bool PostgresOrderingServicePersistentState::initStorage() { return execute_( - "CREATE TABLE IF NOT EXISTS ordering_service_state (\n" - " proposal_height bigserial\n" - ");\n" - "INSERT INTO ordering_service_state\n" - "VALUES (2); -- expected height (1 is genesis)") - .match([](expected::Value v) -> bool { return true; }, - [&](expected::Error e) -> bool { - log_->error(e.error); - return false; - }); + "CREATE TABLE IF NOT EXISTS ordering_service_state " + "(proposal_height bigserial)") + && execute_("INSERT INTO ordering_service_state VALUES (2)"); } bool PostgresOrderingServicePersistentState::dropStorgage() { log_->info("Drop storage"); - return execute_("DROP TABLE IF EXISTS ordering_service_state;") - .match([](expected::Value v) -> bool { return true; }, - [&](expected::Error e) -> bool { - log_->error(e.error); - return false; - }); + return execute_("DROP TABLE IF EXISTS ordering_service_state"); } bool PostgresOrderingServicePersistentState::saveProposalHeight( size_t height) { log_->info("Save proposal_height in ordering_service_state " + std::to_string(height)); - return execute_( - "DELETE FROM ordering_service_state;\n" - "INSERT INTO ordering_service_state " - "VALUES (" - + postgres_transaction_->quote(height) + ");") - .match([](expected::Value v) -> bool { return true; }, - [&](expected::Error e) -> bool { - log_->error(e.error); - return false; - }); + return execute_("DELETE FROM ordering_service_state") + && execute_("INSERT INTO ordering_service_state VALUES (" + + std::to_string(height) + ")"); } boost::optional PostgresOrderingServicePersistentState::loadProposalHeight() const { boost::optional height; - execute_("SELECT * FROM ordering_service_state;") - .match( - [&](expected::Value result) { - if (result.value.empty()) { - log_->error( - "There is no proposal_height in ordering_service_state. " - "Use default value 2."); - height = 2; - } else { - auto row = result.value.at(0); - height = row.at("proposal_height").as(); - log_->info("Load proposal_height in ordering_service_state " - + std::to_string(height.value())); - } - }, - [&](expected::Error e) { log_->error(e.error); }); + *sql_ << "SELECT * FROM ordering_service_state LIMIT 1", + soci::into(height); + + if (not height) { + log_->error( + "There is no proposal_height in ordering_service_state. " + "Use default value 2."); + height = 2; + } return height; } diff --git a/irohad/ametsuchi/impl/postgres_ordering_service_persistent_state.hpp b/irohad/ametsuchi/impl/postgres_ordering_service_persistent_state.hpp index a6e15f97b7..5c7dc45106 100644 --- a/irohad/ametsuchi/impl/postgres_ordering_service_persistent_state.hpp +++ b/irohad/ametsuchi/impl/postgres_ordering_service_persistent_state.hpp @@ -18,8 +18,7 @@ #ifndef IROHA_POSTGRES_ORDERING_SERVICE_PERSISTENT_STATE_HPP #define IROHA_POSTGRES_ORDERING_SERVICE_PERSISTENT_STATE_HPP -#include -#include "ametsuchi/impl/postgres_wsv_common.hpp" +#include "ametsuchi/impl/soci_utils.hpp" #include "ametsuchi/ordering_service_persistent_state.hpp" #include "common/result.hpp" #include "logger/logger.hpp" @@ -50,8 +49,7 @@ namespace iroha { * @param postgres_transaction postgres transaction object */ explicit PostgresOrderingServicePersistentState( - std::unique_ptr postgres_connection, - std::unique_ptr postgres_transaction); + std::unique_ptr sql); /** * Initialize storage. @@ -81,20 +79,11 @@ namespace iroha { virtual bool resetState(); private: - /** - * Pg connection with direct transaction management - */ - std::unique_ptr postgres_connection_; - - /** - * Pg transaction - */ - std::unique_ptr postgres_transaction_; + std::unique_ptr sql_; logger::Logger log_; - using ExecuteType = decltype(makeExecuteResult(*postgres_transaction_)); - ExecuteType execute_; + bool execute_(std::string query); }; } // namespace ametsuchi } // namespace iroha diff --git a/irohad/ametsuchi/impl/postgres_wsv_command.cpp b/irohad/ametsuchi/impl/postgres_wsv_command.cpp index 0d80986cba..d5b77038af 100644 --- a/irohad/ametsuchi/impl/postgres_wsv_command.cpp +++ b/irohad/ametsuchi/impl/postgres_wsv_command.cpp @@ -17,98 +17,105 @@ #include "ametsuchi/impl/postgres_wsv_command.hpp" +#include + #include + #include "backend/protobuf/permissions.hpp" +#include "interfaces/common_objects/account.hpp" +#include "interfaces/common_objects/account_asset.hpp" +#include "interfaces/common_objects/asset.hpp" +#include "interfaces/common_objects/domain.hpp" +#include "interfaces/common_objects/peer.hpp" namespace iroha { namespace ametsuchi { - PostgresWsvCommand::PostgresWsvCommand(pqxx::nontransaction &transaction) - : transaction_(transaction), - execute_{makeExecuteResult(transaction_)} {} + template + WsvCommandResult execute(soci::statement &st, Function &&error) { + st.define_and_bind(); + try { + st.execute(true); + return {}; + } catch (const std::exception &e) { + return expected::makeError(error()); + } + } + + PostgresWsvCommand::PostgresWsvCommand(soci::session &sql) : sql_(sql) {} WsvCommandResult PostgresWsvCommand::insertRole( const shared_model::interface::types::RoleIdType &role_name) { - auto result = execute_("INSERT INTO role(role_id) VALUES (" - + transaction_.quote(role_name) + ");"); - - auto message_gen = [&] { + soci::statement st = sql_.prepare + << "INSERT INTO role(role_id) VALUES (:role_id)"; + st.exchange(soci::use(role_name)); + auto msg = [&] { return (boost::format("failed to insert role: '%s'") % role_name).str(); }; - - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::insertAccountRole( const shared_model::interface::types::AccountIdType &account_id, const shared_model::interface::types::RoleIdType &role_name) { - auto result = - execute_("INSERT INTO account_has_roles(account_id, role_id) VALUES (" - + transaction_.quote(account_id) + ", " - + transaction_.quote(role_name) + ");"); + soci::statement st = sql_.prepare + << "INSERT INTO account_has_roles(account_id, role_id) VALUES " + "(:account_id, :role_id)"; + st.exchange(soci::use(account_id)); + st.exchange(soci::use(role_name)); - auto message_gen = [&] { + auto msg = [&] { return (boost::format("failed to insert account role, account: '%s', " "role name: '%s'") % account_id % role_name) .str(); }; - - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::deleteAccountRole( const shared_model::interface::types::AccountIdType &account_id, const shared_model::interface::types::RoleIdType &role_name) { - auto result = execute_("DELETE FROM account_has_roles WHERE account_id=" - + transaction_.quote(account_id) + "AND role_id=" - + transaction_.quote(role_name) + ";"); - auto message_gen = [&] { + soci::statement st = sql_.prepare + << "DELETE FROM account_has_roles WHERE account_id=:account_id " + "AND role_id=:role_id"; + st.exchange(soci::use(account_id)); + st.exchange(soci::use(role_name)); + + auto msg = [&] { return (boost::format( "failed to delete account role, account id: '%s', " "role name: '%s'") % account_id % role_name) .str(); }; - - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::insertRolePermissions( const shared_model::interface::types::RoleIdType &role_id, const shared_model::interface::RolePermissionSet &permissions) { - auto entry = [this, &role_id](auto permission) { - return "(" + transaction_.quote(role_id) + ", " - + transaction_.quote(permission) + ")"; - }; - - // generate string with all permissions, - // applying transform_func to each permission - auto generate_perm_string = [&permissions](auto transform_func) { - std::string s; - permissions.iterate([&](auto perm) { - s += transform_func(shared_model::proto::permissions::toString(perm)) - + ','; - }); - if (s.size() > 0) { - // remove last comma - s.resize(s.size() - 1); - } - return s; - }; - - auto result = execute_( - "INSERT INTO role_has_permissions(role_id, permission) VALUES " - + generate_perm_string(entry) + ";"); - - auto message_gen = [&] { + auto perm_str = permissions.toBitstring(); + soci::statement st = sql_.prepare + << "INSERT INTO role_has_permissions(role_id, permission) VALUES " + "(:id, :perm)"; + st.exchange(soci::use(role_id)); + st.exchange(soci::use(perm_str)); + + auto msg = [&] { + const auto &str = + shared_model::proto::permissions::toString(permissions); + std::string perm_debug_str = std::accumulate( + str.begin(), + str.end(), + std::string(), + [](auto acc, const auto &elem) { return acc + " " + elem; }); return (boost::format("failed to insert role permissions, role " "id: '%s', permissions: [%s]") - % role_id % generate_perm_string([](auto a) { return a; })) + % role_id % perm_debug_str) .str(); }; - - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::insertAccountGrantablePermission( @@ -116,27 +123,33 @@ namespace iroha { &permittee_account_id, const shared_model::interface::types::AccountIdType &account_id, shared_model::interface::permissions::Grantable permission) { - auto result = execute_( - "INSERT INTO " - "account_has_grantable_permissions(permittee_account_id, " - "account_id, permission) VALUES (" - + transaction_.quote(permittee_account_id) + ", " - + transaction_.quote(account_id) + ", " - + transaction_.quote( - shared_model::proto::permissions::toString(permission)) - + ");"); - - auto message_gen = [&] { + const auto perm_str = + shared_model::interface::GrantablePermissionSet({permission}) + .toBitstring(); + soci::statement st = sql_.prepare + << "INSERT INTO account_has_grantable_permissions as " + "has_perm(permittee_account_id, account_id, permission) VALUES " + "(:permittee_account_id, :account_id, :perm) ON CONFLICT " + "(permittee_account_id, account_id) DO UPDATE SET " + // SELECT will end up with a error, if the permission exists + "permission=(SELECT has_perm.permission | :perm WHERE " + "(has_perm.permission & :perm) <> :perm);"; + st.exchange(soci::use(permittee_account_id, "permittee_account_id")); + st.exchange(soci::use(account_id, "account_id")); + st.exchange(soci::use(perm_str, "perm")); + + auto msg = [&] { return (boost::format("failed to insert account grantable permission, " "permittee account id: '%s', " "account id: '%s', " "permission: '%s'") - % permittee_account_id % account_id + % permittee_account_id + % account_id + // TODO(@l4l) 26/06/18 need to be simplified at IR-1479 % shared_model::proto::permissions::toString(permission)) .str(); }; - - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::deleteAccountGrantablePermission( @@ -144,16 +157,24 @@ namespace iroha { &permittee_account_id, const shared_model::interface::types::AccountIdType &account_id, shared_model::interface::permissions::Grantable permission) { - auto result = execute_( - "DELETE FROM public.account_has_grantable_permissions WHERE " - "permittee_account_id=" - + transaction_.quote(permittee_account_id) + " AND account_id=" - + transaction_.quote(account_id) + " AND permission=" - + transaction_.quote( - shared_model::proto::permissions::toString(permission)) - + " ;"); - - auto message_gen = [&] { + const auto perm_str = shared_model::interface::GrantablePermissionSet() + .set() + .unset(permission) + .toBitstring(); + soci::statement st = sql_.prepare + << "UPDATE account_has_grantable_permissions as has_perm SET " + // SELECT will end up with a error, if the permission doesn't + // exists + "permission=(SELECT has_perm.permission & :perm WHERE " + "has_perm.permission & :perm = :perm) WHERE " + "permittee_account_id=:permittee_account_id AND " + "account_id=:account_id;"; + + st.exchange(soci::use(permittee_account_id, "permittee_account_id")); + st.exchange(soci::use(account_id, "account_id")); + st.exchange(soci::use(perm_str, "perm")); + + auto msg = [&] { return (boost::format("failed to delete account grantable permission, " "permittee account id: '%s', " "account id: '%s', " @@ -162,21 +183,21 @@ namespace iroha { % shared_model::proto::permissions::toString(permission)) .str(); }; - - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::insertAccount( const shared_model::interface::Account &account) { - auto result = execute_( - "INSERT INTO account(account_id, domain_id, quorum, " - "data) VALUES (" - + transaction_.quote(account.accountId()) + ", " - + transaction_.quote(account.domainId()) + ", " - + transaction_.quote(account.quorum()) + ", " - + transaction_.quote(account.jsonData()) + ");"); - - auto message_gen = [&] { + soci::statement st = sql_.prepare + << "INSERT INTO account(account_id, domain_id, quorum," + "data) VALUES (:id, :domain_id, :quorum, :data)"; + uint32_t quorum = account.quorum(); + st.exchange(soci::use(account.accountId())); + st.exchange(soci::use(account.domainId())); + st.exchange(soci::use(quorum)); + st.exchange(soci::use(account.jsonData())); + + auto msg = [&] { return (boost::format("failed to insert account, " "account id: '%s', " "domain id: '%s', " @@ -186,194 +207,185 @@ namespace iroha { % account.jsonData()) .str(); }; - - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::insertAsset( const shared_model::interface::Asset &asset) { - uint32_t precision = asset.precision(); - auto result = execute_( - "INSERT INTO asset(asset_id, domain_id, \"precision\", data) " - "VALUES (" - + transaction_.quote(asset.assetId()) + ", " - + transaction_.quote(asset.domainId()) + ", " - + transaction_.quote(precision) + ", " + /*asset.data*/ "NULL" - + ");"); - - auto message_gen = [&] { + auto precision = asset.precision(); + soci::statement st = sql_.prepare + << "INSERT INTO asset(asset_id, domain_id, \"precision\", data) " + "VALUES (:id, :domain_id, :precision, NULL)"; + st.exchange(soci::use(asset.assetId())); + st.exchange(soci::use(asset.domainId())); + st.exchange(soci::use(precision)); + + auto msg = [&] { return (boost::format("failed to insert asset, asset id: '%s', " "domain id: '%s', precision: %d") - % asset.assetId() % asset.domainId() % precision) + % asset.assetId() % asset.domainId() % asset.precision()) .str(); }; - - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::upsertAccountAsset( const shared_model::interface::AccountAsset &asset) { - auto result = execute_( - "INSERT INTO account_has_asset(account_id, asset_id, amount) " - "VALUES (" - + transaction_.quote(asset.accountId()) + ", " - + transaction_.quote(asset.assetId()) + ", " - + transaction_.quote(asset.balance().toStringRepr()) - + ") ON CONFLICT (account_id, asset_id) DO UPDATE SET " - "amount = EXCLUDED.amount;"); - - auto message_gen = [&] { + auto balance = asset.balance().toStringRepr(); + soci::statement st = sql_.prepare + << "INSERT INTO account_has_asset(account_id, asset_id, amount) " + "VALUES (:account_id, :asset_id, :amount) ON CONFLICT " + "(account_id, asset_id) DO UPDATE SET " + "amount = EXCLUDED.amount"; + + st.exchange(soci::use(asset.accountId())); + st.exchange(soci::use(asset.assetId())); + st.exchange(soci::use(balance)); + + auto msg = [&] { return (boost::format("failed to upsert account, account id: '%s', " "asset id: '%s', balance: %s") % asset.accountId() % asset.assetId() % asset.balance().toString()) .str(); }; - - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::insertSignatory( const shared_model::interface::types::PubkeyType &signatory) { - auto result = - execute_("INSERT INTO signatory(public_key) VALUES (" - + transaction_.quote(pqxx::binarystring( - signatory.blob().data(), signatory.blob().size())) - + ") ON CONFLICT DO NOTHING;"); - auto message_gen = [&] { + soci::statement st = sql_.prepare + << "INSERT INTO signatory(public_key) VALUES (:pk) ON CONFLICT DO " + "NOTHING;"; + st.exchange(soci::use(signatory.hex())); + + auto msg = [&] { return (boost::format( "failed to insert signatory, signatory hex string: '%s'") % signatory.hex()) .str(); }; - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::insertAccountSignatory( const shared_model::interface::types::AccountIdType &account_id, const shared_model::interface::types::PubkeyType &signatory) { - auto result = execute_( - "INSERT INTO account_has_signatory(account_id, public_key) VALUES (" - + transaction_.quote(account_id) + ", " - + transaction_.quote(pqxx::binarystring(signatory.blob().data(), - signatory.blob().size())) - + ");"); - - auto message_gen = [&] { + soci::statement st = sql_.prepare + << "INSERT INTO account_has_signatory(account_id, public_key) " + "VALUES (:account_id, :pk)"; + st.exchange(soci::use(account_id)); + st.exchange(soci::use(signatory.hex())); + + auto msg = [&] { return (boost::format("failed to insert account signatory, account id: " "'%s', signatory hex string: '%s") % account_id % signatory.hex()) .str(); }; - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::deleteAccountSignatory( const shared_model::interface::types::AccountIdType &account_id, const shared_model::interface::types::PubkeyType &signatory) { - auto result = - execute_("DELETE FROM account_has_signatory WHERE account_id = " - + transaction_.quote(account_id) + " AND public_key = " - + transaction_.quote(pqxx::binarystring( - signatory.blob().data(), signatory.blob().size())) - + ";"); - - auto message_gen = [&] { + soci::statement st = sql_.prepare + << "DELETE FROM account_has_signatory WHERE account_id = " + ":account_id AND public_key = :pk"; + st.exchange(soci::use(account_id)); + st.exchange(soci::use(signatory.hex())); + + auto msg = [&] { return (boost::format("failed to delete account signatory, account id: " "'%s', signatory hex string: '%s'") % account_id % signatory.hex()) .str(); }; - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::deleteSignatory( const shared_model::interface::types::PubkeyType &signatory) { - pqxx::binarystring public_key(signatory.blob().data(), - signatory.blob().size()); - auto result = execute_("DELETE FROM signatory WHERE public_key = " - + transaction_.quote(public_key) - + " AND NOT EXISTS (SELECT 1 FROM account_has_signatory " - "WHERE public_key = " - + transaction_.quote(public_key) - + ") AND NOT EXISTS (SELECT 1 FROM peer WHERE public_key = " - + transaction_.quote(public_key) + ");"); - - auto message_gen = [&] { + soci::statement st = sql_.prepare + << "DELETE FROM signatory WHERE public_key = :pk AND NOT EXISTS " + "(SELECT 1 FROM account_has_signatory " + "WHERE public_key = :pk) AND NOT EXISTS (SELECT 1 FROM peer " + "WHERE public_key = :pk)"; + st.exchange(soci::use(signatory.hex(), "pk")); + + auto msg = [&] { return (boost::format( "failed to delete signatory, signatory hex string: '%s'") % signatory.hex()) .str(); }; - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::insertPeer( const shared_model::interface::Peer &peer) { - auto result = - execute_("INSERT INTO peer(public_key, address) VALUES (" - + transaction_.quote(pqxx::binarystring( - peer.pubkey().blob().data(), peer.pubkey().size())) - + ", " + transaction_.quote(peer.address()) + ");"); + soci::statement st = sql_.prepare + << "INSERT INTO peer(public_key, address) VALUES (:pk, :address)"; + st.exchange(soci::use(peer.pubkey().hex())); + st.exchange(soci::use(peer.address())); - auto message_gen = [&] { + auto msg = [&] { return (boost::format( "failed to insert peer, public key: '%s', address: '%s'") % peer.pubkey().hex() % peer.address()) .str(); }; - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::deletePeer( const shared_model::interface::Peer &peer) { - auto result = execute_( - "DELETE FROM peer WHERE public_key = " - + transaction_.quote(pqxx::binarystring(peer.pubkey().blob().data(), - peer.pubkey().size())) - + " AND address = " + transaction_.quote(peer.address()) + ";"); - auto message_gen = [&] { + soci::statement st = sql_.prepare + << "DELETE FROM peer WHERE public_key = :pk AND address = :address"; + st.exchange(soci::use(peer.pubkey().hex())); + st.exchange(soci::use(peer.address())); + + auto msg = [&] { return (boost::format( "failed to delete peer, public key: '%s', address: '%s'") % peer.pubkey().hex() % peer.address()) .str(); }; - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::insertDomain( const shared_model::interface::Domain &domain) { - auto result = - execute_("INSERT INTO domain(domain_id, default_role) VALUES (" - + transaction_.quote(domain.domainId()) + ", " - + transaction_.quote(domain.defaultRole()) + ");"); + soci::statement st = sql_.prepare + << "INSERT INTO domain(domain_id, default_role) VALUES (:id, " + ":role)"; + st.exchange(soci::use(domain.domainId())); + st.exchange(soci::use(domain.defaultRole())); - auto message_gen = [&] { + auto msg = [&] { return (boost::format("failed to insert domain, domain id: '%s', " "default role: '%s'") % domain.domainId() % domain.defaultRole()) .str(); }; - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::updateAccount( const shared_model::interface::Account &account) { - auto result = execute_( - "UPDATE account\n" - " SET quorum=" + - transaction_.quote(account.quorum()) + - "\n" - " WHERE account_id=" + - transaction_.quote(account.accountId()) + ";"); - - auto message_gen = [&] { + soci::statement st = sql_.prepare + << "UPDATE account SET quorum=:quorum WHERE account_id=:account_id"; + uint32_t quorum = account.quorum(); + st.exchange(soci::use(quorum)); + st.exchange(soci::use(account.accountId())); + + auto msg = [&] { return (boost::format( "failed to update account, account id: '%s', quorum: '%s'") % account.accountId() % account.quorum()) .str(); }; - return makeCommandResult(std::move(result), message_gen); + return execute(st, msg); } WsvCommandResult PostgresWsvCommand::setAccountKV( @@ -381,24 +393,31 @@ namespace iroha { const shared_model::interface::types::AccountIdType &creator_account_id, const std::string &key, const std::string &val) { - auto result = execute_( - "UPDATE account SET data = jsonb_set(CASE WHEN data ?" - + transaction_.quote(creator_account_id) - + " THEN data ELSE jsonb_set(data, " - + transaction_.quote("{" + creator_account_id + "}") + "," - + transaction_.quote("{}") + ") END," - + transaction_.quote("{" + creator_account_id + ", " + key + "}") - + "," + transaction_.quote("\"" + val + "\"") - + ") WHERE account_id=" + transaction_.quote(account_id) + ";"); - - auto message_gen = [&] { + soci::statement st = sql_.prepare + << "UPDATE account SET data = jsonb_set(" + "CASE WHEN data ?:creator_account_id THEN data ELSE " + "jsonb_set(data, :json, :empty_json) END, " + " :filled_json, :val) WHERE account_id=:account_id"; + std::string json = "{" + creator_account_id + "}"; + std::string empty_json = "{}"; + std::string filled_json = "{" + creator_account_id + ", " + key + "}"; + std::string value = "\"" + val + "\""; + st.exchange(soci::use(creator_account_id)); + st.exchange(soci::use(json)); + st.exchange(soci::use(empty_json)); + st.exchange(soci::use(filled_json)); + st.exchange(soci::use(value)); + st.exchange(soci::use(account_id)); + + auto msg = [&] { return (boost::format( "failed to set account key-value, account id: '%s', " "creator account id: '%s',\n key: '%s', value: '%s'") % account_id % creator_account_id % key % val) .str(); }; - return makeCommandResult(std::move(result), message_gen); + + return execute(st, msg); } } // namespace ametsuchi } // namespace iroha diff --git a/irohad/ametsuchi/impl/postgres_wsv_command.hpp b/irohad/ametsuchi/impl/postgres_wsv_command.hpp index 3c0c35d552..9f1cbe7ae1 100644 --- a/irohad/ametsuchi/impl/postgres_wsv_command.hpp +++ b/irohad/ametsuchi/impl/postgres_wsv_command.hpp @@ -20,14 +20,14 @@ #include "ametsuchi/wsv_command.hpp" -#include "ametsuchi/impl/postgres_wsv_common.hpp" +#include "ametsuchi/impl/soci_utils.hpp" namespace iroha { namespace ametsuchi { class PostgresWsvCommand : public WsvCommand { public: - explicit PostgresWsvCommand(pqxx::nontransaction &transaction); + explicit PostgresWsvCommand(soci::session &sql); WsvCommandResult insertRole( const shared_model::interface::types::RoleIdType &role_name) override; @@ -86,36 +86,7 @@ namespace iroha { shared_model::interface::permissions::Grantable permission) override; private: - pqxx::nontransaction &transaction_; - - using ExecuteType = decltype(makeExecuteResult(transaction_)); - ExecuteType execute_; - - /** - * Transforms result which contains pqxx to WsvCommandResult, - * which will have error message generated by error_generator - * appended to error received from given result - * @param result which can be received by calling execute_ - * @param error_generator function which must generate error message - * to be used as a return error. - * Function is passed instead of string to avoid overhead of string - * construction in successful case. - * @return WsvCommandResult with combined error message - * in case of result contains error - */ - template - WsvCommandResult makeCommandResult( - expected::Result &&result, - Function &&error_generator) const noexcept { - return result.match( - [](expected::Value v) -> WsvCommandResult { - return {}; - }, - [&error_generator]( - expected::Error e) -> WsvCommandResult { - return expected::makeError(error_generator() + "\n" + e.error); - }); - } + soci::session &sql_; }; } // namespace ametsuchi } // namespace iroha diff --git a/irohad/ametsuchi/impl/postgres_wsv_common.hpp b/irohad/ametsuchi/impl/postgres_wsv_common.hpp deleted file mode 100644 index ddc2b2c3f5..0000000000 --- a/irohad/ametsuchi/impl/postgres_wsv_common.hpp +++ /dev/null @@ -1,198 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef IROHA_POSTGRES_WSV_COMMON_HPP -#define IROHA_POSTGRES_WSV_COMMON_HPP - -#include -#include -#include - -#include "builders/default_builders.hpp" -#include "common/result.hpp" -#include "logger/logger.hpp" - -namespace iroha { - namespace ametsuchi { - - /** - * Return function which can execute SQL statements on provided transaction - * @param transaction on which to apply statement. - * @param logger is used to report an error. - * @return Result with pqxx::result in value case, or exception message - * if exception was caught - */ - inline auto makeExecuteResult(pqxx::nontransaction &transaction) noexcept { - return [&](const std::string &statement) noexcept - ->expected::Result { - try { - return expected::makeValue(transaction.exec(statement)); - } catch (const std::exception &e) { - return expected::makeError(e.what()); - } - }; - } - - /** - * Return function which can execute SQL statements on provided transaction - * This function is deprecated, and will be removed as soon as wsv_query - * will be refactored to return result - * @param transaction on which to apply statement. - * @param logger is used to report an error. - * @return boost::optional with pqxx::result in successful case, or nullopt - * if exception was caught - */ - inline auto makeExecuteOptional(pqxx::nontransaction &transaction, - logger::Logger &logger) noexcept { - return [&](const std::string &statement) noexcept - ->boost::optional { - try { - return transaction.exec(statement); - } catch (const std::exception &e) { - logger->error(e.what()); - return boost::none; - } - }; - } - - /** - * Transforms pqxx::result to vector of Ts by applying transform_func - * @tparam T - type to transform to - * @tparam Operator - type of transformation function, must return T - * @param result - pqxx::result which contains several rows from the - * database - * @param transform_func - function which transforms result row to T - * @return vector of target type - */ - template - std::vector transform(const pqxx::result &result, - Operator &&transform_func) noexcept { - std::vector values; - values.reserve(result.size()); - std::transform(result.begin(), - result.end(), - std::back_inserter(values), - transform_func); - - return values; - } - - /** - * Execute build function and return error in case it throws - * @tparam T - result value type - * @param f - function which returns BuilderResult - * @return whatever f returns, or error in case exception has been thrown - */ - template - static inline auto tryBuild(BuildFunc &&f) noexcept -> decltype(f()) { - try { - return f(); - } catch (std::exception &e) { - return expected::makeError(std::make_shared(e.what())); - } - } - - static inline shared_model::builder::BuilderResult< - shared_model::interface::Account> - makeAccount(const pqxx::row &row) noexcept { - return tryBuild([&row] { - return shared_model::builder::DefaultAccountBuilder() - .accountId(row.at("account_id").template as()) - .domainId(row.at("domain_id").template as()) - .quorum( - row.at("quorum") - .template as()) - .jsonData(row.at("data").template as()) - .build(); - }); - } - - static inline shared_model::builder::BuilderResult< - shared_model::interface::Asset> - makeAsset(const pqxx::row &row) noexcept { - return tryBuild([&row] { - return shared_model::builder::DefaultAssetBuilder() - .assetId(row.at("asset_id").template as()) - .domainId(row.at("domain_id").template as()) - .precision(row.at("precision").template as()) - .build(); - }); - } - - static inline shared_model::builder::BuilderResult< - shared_model::interface::AccountAsset> - makeAccountAsset(const pqxx::row &row) noexcept { - return tryBuild([&row] { - auto balance = shared_model::builder::DefaultAmountBuilder::fromString( - row.at("amount").template as()); - return balance | [&](const auto &balance_ptr) { - return shared_model::builder::DefaultAccountAssetBuilder() - .accountId(row.at("account_id").template as()) - .assetId(row.at("asset_id").template as()) - .balance(*balance_ptr) - .build(); - }; - }); - } - - static inline shared_model::builder::BuilderResult< - shared_model::interface::Peer> - makePeer(const pqxx::row &row) noexcept { - return tryBuild([&row] { - pqxx::binarystring public_key_str(row.at("public_key")); - shared_model::interface::types::PubkeyType pubkey(public_key_str.str()); - return shared_model::builder::DefaultPeerBuilder() - .pubkey(pubkey) - .address(row.at("address").template as()) - .build(); - }); - } - - static inline shared_model::builder::BuilderResult< - shared_model::interface::Domain> - makeDomain(const pqxx::row &row) noexcept { - return tryBuild([&row] { - return shared_model::builder::DefaultDomainBuilder() - .domainId(row.at("domain_id").template as()) - .defaultRole(row.at("default_role").template as()) - .build(); - }); - } - - /** - * Transforms result to optional - * value -> optional - * error -> nullopt - * @tparam T type of object inside - * @param result BuilderResult - * @return optional - */ - template - static inline boost::optional> fromResult( - const shared_model::builder::BuilderResult &result) { - return result.match( - [](const expected::Value> &v) { - return boost::make_optional(v.value); - }, - [](const expected::Error> &e) - -> boost::optional> { - return boost::none; - }); - } - } // namespace ametsuchi -} // namespace iroha -#endif // IROHA_POSTGRES_WSV_COMMON_HPP diff --git a/irohad/ametsuchi/impl/postgres_wsv_query.cpp b/irohad/ametsuchi/impl/postgres_wsv_query.cpp index c63f90d60f..0f602cb5bb 100644 --- a/irohad/ametsuchi/impl/postgres_wsv_query.cpp +++ b/irohad/ametsuchi/impl/postgres_wsv_query.cpp @@ -16,12 +16,39 @@ */ #include "ametsuchi/impl/postgres_wsv_query.hpp" -#include "backend/protobuf/from_old.hpp" + +#include + +#include "ametsuchi/impl/soci_utils.hpp" #include "backend/protobuf/permissions.hpp" +#include "common/result.hpp" + +namespace { + /** + * Transforms result to optional + * value -> optional + * error -> nullopt + * @tparam T type of object inside + * @param result BuilderResult + * @return optional + */ + template + boost::optional> fromResult( + shared_model::interface::CommonObjectsFactory::FactoryResult< + std::unique_ptr> &&result) { + return result.match( + [](iroha::expected::Value> &v) { + return boost::make_optional(std::shared_ptr(std::move(v.value))); + }, + [](iroha::expected::Error) + -> boost::optional> { return boost::none; }); + } +} // namespace namespace iroha { namespace ametsuchi { + using shared_model::interface::types::AccountDetailKeyType; using shared_model::interface::types::AccountIdType; using shared_model::interface::types::AssetIdType; using shared_model::interface::types::DomainIdType; @@ -36,218 +63,268 @@ namespace iroha { const std::string kAccountId = "account_id"; const std::string kDomainId = "domain_id"; - PostgresWsvQuery::PostgresWsvQuery(pqxx::nontransaction &transaction) - : transaction_(transaction), - log_(logger::log("PostgresWsvQuery")), - execute_{makeExecuteOptional(transaction_, log_)} {} - PostgresWsvQuery::PostgresWsvQuery( - std::unique_ptr connection, - std::unique_ptr transaction) - : connection_ptr_(std::move(connection)), - transaction_ptr_(std::move(transaction)), - transaction_(*transaction_ptr_), - log_(logger::log("PostgresWsvQuery")), - execute_{makeExecuteOptional(transaction_, log_)} {} + soci::session &sql, + std::shared_ptr factory) + : sql_(sql), factory_(factory), log_(logger::log("PostgresWsvQuery")) {} bool PostgresWsvQuery::hasAccountGrantablePermission( const AccountIdType &permitee_account_id, const AccountIdType &account_id, shared_model::interface::permissions::Grantable permission) { - return execute_( - "SELECT * FROM account_has_grantable_permissions WHERE " - "permittee_account_id = " - + transaction_.quote(permitee_account_id) - + " AND account_id = " + transaction_.quote(account_id) - + " AND permission = " - + transaction_.quote( - shared_model::proto::permissions::toString(permission)) - + ";") - | [](const auto &result) { return result.size() == 1; }; + const auto perm_str = + shared_model::interface::GrantablePermissionSet({permission}) + .toBitstring(); + int size; + soci::statement st = sql_.prepare + << "SELECT count(*) FROM account_has_grantable_permissions WHERE " + "permittee_account_id = :permittee_account_id AND account_id = " + ":account_id " + " AND permission & :permission = :permission "; + + st.exchange(soci::into(size)); + st.exchange(soci::use(permitee_account_id, "permittee_account_id")); + st.exchange(soci::use(account_id, "account_id")); + st.exchange(soci::use(perm_str, "permission")); + st.define_and_bind(); + st.execute(true); + + return size == 1; } boost::optional> PostgresWsvQuery::getAccountRoles( const AccountIdType &account_id) { - return execute_( - "SELECT role_id FROM account_has_roles WHERE account_id = " - + transaction_.quote(account_id) + ";") - | [&](const auto &result) { - return transform(result, [](const auto &row) { - return row.at(kRoleId).c_str(); - }); - }; + std::vector roles; + soci::indicator ind; + std::string row; + soci::statement st = + (sql_.prepare << "SELECT role_id FROM account_has_roles WHERE " + "account_id = :account_id", + soci::into(row, ind), + soci::use(account_id)); + st.execute(); + + processSoci( + st, ind, row, [&roles](std::string &row) { roles.push_back(row); }); + return roles; } boost::optional PostgresWsvQuery::getRolePermissions(const RoleIdType &role_name) { - return execute_( - "SELECT permission FROM role_has_permissions WHERE role_id " - "= " - + transaction_.quote(role_name) + ";") - | [&](const auto &result) { - shared_model::interface::RolePermissionSet set; - for (const auto &r : result) { - set.set(shared_model::interface::permissions::fromOldR( - r.at("permission").c_str())); - } - return set; - }; + shared_model::interface::RolePermissionSet set; + soci::indicator ind; + std::string row; + soci::statement st = + (sql_.prepare << "SELECT permission FROM role_has_permissions WHERE " + "role_id = :role_name", + soci::into(row, ind), + soci::use(role_name)); + st.execute(); + + processSoci(st, ind, row, [&set](std::string &row) { + set = shared_model::interface::RolePermissionSet(row); + }); + return set; } boost::optional> PostgresWsvQuery::getRoles() { - return execute_("SELECT role_id FROM role;") | [&](const auto &result) { - return transform( - result, [](const auto &row) { return row.at(kRoleId).c_str(); }); - }; + soci::rowset roles = + (sql_.prepare << "SELECT role_id FROM role"); + std::vector result; + for (const auto &role : roles) { + result.push_back(role); + } + return boost::make_optional(result); } boost::optional> PostgresWsvQuery::getAccount(const AccountIdType &account_id) { - return execute_("SELECT * FROM account WHERE account_id = " - + transaction_.quote(account_id) + ";") - | [&](const auto &result) - -> boost::optional< - std::shared_ptr> { - if (result.empty()) { - log_->info(kAccountNotFound, account_id); - return boost::none; - } + boost::optional domain_id, data; + boost::optional quorum; + soci::statement st = sql_.prepare + << "SELECT domain_id, quorum, data FROM account WHERE account_id = " + ":account_id"; - return fromResult(makeAccount(result.at(0))); - }; + st.exchange(soci::into(domain_id)); + st.exchange(soci::into(quorum)); + st.exchange(soci::into(data)); + st.exchange(soci::use(account_id, "account_id")); + + st.define_and_bind(); + st.execute(true); + + if (not domain_id) { + return boost::none; + } + + return fromResult(factory_->createAccount( + account_id, domain_id.get(), quorum.get(), data.get())); } boost::optional PostgresWsvQuery::getAccountDetail( - const std::string &account_id) { - return execute_("SELECT data#>>" + transaction_.quote("{}") - + " FROM account WHERE account_id = " - + transaction_.quote(account_id) + ";") - | [&](const auto &result) -> boost::optional { - if (result.empty()) { - log_->info(kAccountNotFound, account_id); - return boost::none; - } - auto row = result.at(0); - std::string res; - row.at(0) >> res; + const std::string &account_id, + const AccountDetailKeyType &key, + const AccountIdType &writer) { + boost::optional detail; - // if res is empty, then that key does not exist for this account - if (res.empty()) { - return boost::none; + if (key.empty() and writer.empty()) { + // retrieve all values for a specified account + std::string empty_json = "{}"; + sql_ << "SELECT data#>>:empty_json FROM account WHERE account_id = " + ":account_id;", + soci::into(detail), soci::use(empty_json), soci::use(account_id); + } else if (not key.empty() and not writer.empty()) { + // retrieve values for the account, under the key and added by the + // writer + std::string filled_json = "{\"" + writer + "\"" + ", \"" + key + "\"}"; + sql_ << "SELECT json_build_object(:writer::text, " + "json_build_object(:key::text, (SELECT data #>> :filled_json " + "FROM account WHERE account_id = :account_id)));", + soci::into(detail), soci::use(writer), soci::use(key), + soci::use(filled_json), soci::use(account_id); + } else if (not writer.empty()) { + // retrieve values added by the writer under all keys + sql_ << "SELECT json_build_object(:writer::text, (SELECT data -> " + ":writer FROM account WHERE account_id = :account_id));", + soci::into(detail), soci::use(writer, "writer"), + soci::use(account_id, "account_id"); + } else { + // retrieve values from all writers under the key + sql_ << "SELECT json_object_agg(key, value) AS json FROM (SELECT " + "json_build_object(kv.key, json_build_object(:key::text, " + "kv.value -> :key)) FROM jsonb_each((SELECT data FROM account " + "WHERE account_id = :account_id)) kv WHERE kv.value ? :key) AS " + "jsons, json_each(json_build_object);", + soci::into(detail), soci::use(key, "key"), + soci::use(account_id, "account_id"); + } + + return detail | [](auto &val) -> boost::optional { + // if val is empty, then there is no data for this account + if (not val.empty()) { + return val; } - return res; + return boost::none; }; } boost::optional> PostgresWsvQuery::getSignatories( const AccountIdType &account_id) { - return execute_( - "SELECT public_key FROM account_has_signatory WHERE " - "account_id = " - + transaction_.quote(account_id) + ";") - | [&](const auto &result) { - return transform(result, [&](const auto &row) { - pqxx::binarystring public_key_str(row.at(kPublicKey)); - return PubkeyType(public_key_str.str()); - }); - }; + std::vector pubkeys; + soci::indicator ind; + std::string row; + soci::statement st = + (sql_.prepare << "SELECT public_key FROM account_has_signatory WHERE " + "account_id = :account_id", + soci::into(row, ind), + soci::use(account_id)); + st.execute(); + + processSoci(st, ind, row, [&pubkeys](std::string &row) { + pubkeys.push_back(shared_model::crypto::PublicKey( + shared_model::crypto::Blob::fromHexString(row))); + }); + return boost::make_optional(pubkeys); } boost::optional> PostgresWsvQuery::getAsset(const AssetIdType &asset_id) { - pqxx::result result; - return execute_("SELECT * FROM asset WHERE asset_id = " - + transaction_.quote(asset_id) + ";") - | [&](const auto &result) - -> boost::optional< - std::shared_ptr> { - if (result.empty()) { - log_->info("Asset {} not found", asset_id); - return boost::none; - } - return fromResult(makeAsset(result.at(0))); - }; + boost::optional domain_id, data; + boost::optional precision; + soci::statement st = sql_.prepare + << "SELECT domain_id, precision FROM asset WHERE asset_id = " + ":account_id"; + st.exchange(soci::into(domain_id)); + st.exchange(soci::into(precision)); + st.exchange(soci::use(asset_id)); + + st.define_and_bind(); + st.execute(true); + + if (not domain_id) { + return boost::none; + } + + return fromResult( + factory_->createAsset(asset_id, domain_id.get(), precision.get())); } boost::optional< std::vector>> PostgresWsvQuery::getAccountAssets(const AccountIdType &account_id) { - return execute_("SELECT * FROM account_has_asset WHERE account_id = " - + transaction_.quote(account_id) + ";") - | [&](const auto &result) - -> boost::optional>> { - auto results = transform>(result, makeAccountAsset); - std::vector> - assets; - for (auto &r : results) { - r.match( - [&](expected::Value< - std::shared_ptr> &v) { - assets.push_back(v.value); - }, - [&](expected::Error> &e) { - log_->info(*e.error); - }); - } - return assets; - }; + using T = boost::tuple; + soci::rowset st = (sql_.prepare << "SELECT * FROM account_has_asset " + "WHERE account_id = :account_id", + soci::use(account_id)); + std::vector> + assets; + for (auto &t : st) { + fromResult(factory_->createAccountAsset( + account_id, + t.get<1>(), + shared_model::interface::Amount(t.get<2>()))) + | [&assets](const auto &asset) { assets.push_back(asset); }; + } + + return boost::make_optional(assets); } + boost::optional> PostgresWsvQuery::getAccountAsset(const AccountIdType &account_id, const AssetIdType &asset_id) { - return execute_("SELECT * FROM account_has_asset WHERE account_id = " - + transaction_.quote(account_id) - + " AND asset_id = " + transaction_.quote(asset_id) + ";") - | [&](const auto &result) - -> boost::optional< - std::shared_ptr> { - if (result.empty()) { - log_->info("Account {} does not have asset {}", account_id, asset_id); - return boost::none; - } + boost::optional amount; + soci::statement st = sql_.prepare + << "SELECT amount FROM account_has_asset WHERE account_id = " + ":account_id AND asset_id = :asset_id"; + st.exchange(soci::into(amount)); + st.exchange(soci::use(account_id)); + st.exchange(soci::use(asset_id)); + st.define_and_bind(); + st.execute(true); - return fromResult(makeAccountAsset(result.at(0))); - }; + if (not amount) { + return boost::none; + } + + return fromResult(factory_->createAccountAsset( + account_id, asset_id, shared_model::interface::Amount(amount.get()))); } boost::optional> PostgresWsvQuery::getDomain(const DomainIdType &domain_id) { - return execute_("SELECT * FROM domain WHERE domain_id = " - + transaction_.quote(domain_id) + ";") - | [&](const auto &result) - -> boost::optional< - std::shared_ptr> { - if (result.empty()) { - log_->info("Domain {} not found", domain_id); - return boost::none; - } - return fromResult(makeDomain(result.at(0))); - }; + boost::optional role; + soci::statement st = sql_.prepare + << "SELECT default_role FROM domain WHERE domain_id = :id LIMIT 1"; + st.exchange(soci::into(role)); + st.exchange(soci::use(domain_id)); + st.define_and_bind(); + st.execute(true); + + if (not role) { + return boost::none; + } + + return fromResult(factory_->createDomain(domain_id, role.get())); } boost::optional>> PostgresWsvQuery::getPeers() { - pqxx::result result; - return execute_("SELECT * FROM peer;") | [&](const auto &result) - -> boost::optional>> { - auto results = transform>(result, makePeer); - std::vector> peers; - for (auto &r : results) { - r.match( - [&](expected::Value< - std::shared_ptr> &v) { - peers.push_back(v.value); - }, - [&](expected::Error> &e) { - log_->info(*e.error); - }); - } - return peers; - }; + soci::rowset rows = + (sql_.prepare << "SELECT public_key, address FROM peer"); + std::vector> peers; + + for (auto &row : rows) { + auto address = row.get(1); + auto key = shared_model::crypto::PublicKey( + shared_model::crypto::Blob::fromHexString(row.get(0))); + + auto peer = factory_->createPeer(address, key); + peer.match( + [&](expected::Value> + &v) { peers.push_back(std::move(v.value)); }, + [&](expected::Error &e) { log_->info(e.error); }); + } + return peers; } } // namespace ametsuchi } // namespace iroha diff --git a/irohad/ametsuchi/impl/postgres_wsv_query.hpp b/irohad/ametsuchi/impl/postgres_wsv_query.hpp index 9a32b38d4c..536e780a62 100644 --- a/irohad/ametsuchi/impl/postgres_wsv_query.hpp +++ b/irohad/ametsuchi/impl/postgres_wsv_query.hpp @@ -20,17 +20,20 @@ #include "ametsuchi/wsv_query.hpp" -#include +#include -#include "postgres_wsv_common.hpp" +#include "interfaces/common_objects/common_objects_factory.hpp" +#include "logger/logger.hpp" namespace iroha { namespace ametsuchi { class PostgresWsvQuery : public WsvQuery { public: - explicit PostgresWsvQuery(pqxx::nontransaction &transaction); - PostgresWsvQuery(std::unique_ptr connection, - std::unique_ptr transaction); + PostgresWsvQuery( + soci::session &sql, + std::shared_ptr + factory); + boost::optional> getAccountRoles(const shared_model::interface::types::AccountIdType &account_id) override; @@ -42,30 +45,41 @@ namespace iroha { boost::optional> getAccount(const shared_model::interface::types::AccountIdType &account_id) override; + boost::optional getAccountDetail( - const shared_model::interface::types::AccountIdType &account_id) - override; + const shared_model::interface::types::AccountIdType &account_id, + const shared_model::interface::types::AccountDetailKeyType &key = "", + const shared_model::interface::types::AccountIdType &writer = + "") override; + boost::optional> getSignatories(const shared_model::interface::types::AccountIdType &account_id) override; + boost::optional> getAsset( const shared_model::interface::types::AssetIdType &asset_id) override; + boost::optional< std::vector>> getAccountAssets(const shared_model::interface::types::AccountIdType &account_id) override; + boost::optional> getAccountAsset( const shared_model::interface::types::AccountIdType &account_id, const shared_model::interface::types::AssetIdType &asset_id) override; + boost::optional< std::vector>> getPeers() override; + boost::optional> getRoles() override; + boost::optional> getDomain(const shared_model::interface::types::DomainIdType &domain_id) override; + bool hasAccountGrantablePermission( const shared_model::interface::types::AccountIdType &permitee_account_id, @@ -73,14 +87,9 @@ namespace iroha { shared_model::interface::permissions::Grantable permission) override; private: - std::unique_ptr connection_ptr_; - std::unique_ptr transaction_ptr_; - - pqxx::nontransaction &transaction_; + soci::session &sql_; + std::shared_ptr factory_; logger::Logger log_; - - using ExecuteType = decltype(makeExecuteOptional(transaction_, log_)); - ExecuteType execute_; }; } // namespace ametsuchi } // namespace iroha diff --git a/irohad/ametsuchi/impl/soci_utils.hpp b/irohad/ametsuchi/impl/soci_utils.hpp new file mode 100644 index 0000000000..6efcab4a87 --- /dev/null +++ b/irohad/ametsuchi/impl/soci_utils.hpp @@ -0,0 +1,42 @@ +/** + * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. + * http://soramitsu.co.jp + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IROHA_POSTGRES_WSV_COMMON_HPP +#define IROHA_POSTGRES_WSV_COMMON_HPP + +#include + +namespace iroha { + namespace ametsuchi { + template + inline void processSoci(soci::statement &st, + soci::indicator &ind, + ParamType &row, + Function f) { + while (st.fetch()) { + switch (ind) { + case soci::i_ok: + f(row); + case soci::i_null: + case soci::i_truncated: + break; + } + } + } + } // namespace ametsuchi +} // namespace iroha +#endif // IROHA_POSTGRES_WSV_COMMON_HPP diff --git a/irohad/ametsuchi/impl/storage_impl.cpp b/irohad/ametsuchi/impl/storage_impl.cpp index 89546016f6..a040bda4c3 100644 --- a/irohad/ametsuchi/impl/storage_impl.cpp +++ b/irohad/ametsuchi/impl/storage_impl.cpp @@ -1,22 +1,13 @@ /** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include "ametsuchi/impl/storage_impl.hpp" + +#include #include + #include "ametsuchi/impl/flat_file/flat_file.hpp" #include "ametsuchi/impl/mutable_storage_impl.hpp" #include "ametsuchi/impl/postgres_block_query.hpp" @@ -37,45 +28,44 @@ namespace iroha { std::unique_ptr block_store) : block_store(std::move(block_store)) {} - StorageImpl::StorageImpl(std::string block_store_dir, - PostgresOptions postgres_options, - std::unique_ptr block_store) + StorageImpl::StorageImpl( + std::string block_store_dir, + PostgresOptions postgres_options, + std::unique_ptr block_store, + std::shared_ptr connection, + std::shared_ptr factory) : block_store_dir_(std::move(block_store_dir)), postgres_options_(std::move(postgres_options)), block_store_(std::move(block_store)), - log_(logger::log("StorageImpl")) {} + connection_(connection), + factory_(factory), + log_(logger::log("StorageImpl")) { + soci::session sql(*connection_); + sql << init_; + } expected::Result, std::string> StorageImpl::createTemporaryWsv() { - auto postgres_connection = std::make_unique( - postgres_options_.optionsString()); - try { - postgres_connection->activate(); - } catch (const pqxx::broken_connection &e) { - return expected::makeError( - (boost::format(kPsqlBroken) % e.what()).str()); + std::shared_lock lock(drop_mutex); + if (connection_ == nullptr) { + return expected::makeError("Connection was closed"); } - auto wsv_transaction = - std::make_unique(*postgres_connection, kTmpWsv); + auto sql = std::make_unique(*connection_); return expected::makeValue>( - std::make_unique(std::move(postgres_connection), - std::move(wsv_transaction))); + std::make_unique(std::move(sql), factory_)); } expected::Result, std::string> StorageImpl::createMutableStorage() { - auto postgres_connection = std::make_unique( - postgres_options_.optionsString()); - try { - postgres_connection->activate(); - } catch (const pqxx::broken_connection &e) { - return expected::makeError( - (boost::format(kPsqlBroken) % e.what()).str()); + boost::optional top_hash; + + std::shared_lock lock(drop_mutex); + if (connection_ == nullptr) { + return expected::makeError("Connection was closed"); } - auto wsv_transaction = - std::make_unique(*postgres_connection, kTmpWsv); + auto sql = std::make_unique(*connection_); auto block_result = getBlockQuery()->getTopBlock(); return expected::makeValue>( std::make_unique( @@ -87,8 +77,8 @@ namespace iroha { [](expected::Error &) { return shared_model::interface::types::HashType(""); }), - std::move(postgres_connection), - std::move(wsv_transaction))); + std::move(sql), + factory_)); } bool StorageImpl::insertBlock(const shared_model::interface::Block &block) { @@ -139,36 +129,40 @@ namespace iroha { return inserted; } - void StorageImpl::dropStorage() { - log_->info("Drop ledger"); - auto drop = R"( -DROP TABLE IF EXISTS account_has_signatory; -DROP TABLE IF EXISTS account_has_asset; -DROP TABLE IF EXISTS role_has_permissions CASCADE; -DROP TABLE IF EXISTS account_has_roles; -DROP TABLE IF EXISTS account_has_grantable_permissions CASCADE; -DROP TABLE IF EXISTS account; -DROP TABLE IF EXISTS asset; -DROP TABLE IF EXISTS domain; -DROP TABLE IF EXISTS signatory; -DROP TABLE IF EXISTS peer; -DROP TABLE IF EXISTS role; -DROP TABLE IF EXISTS height_by_hash; -DROP TABLE IF EXISTS height_by_account_set; -DROP TABLE IF EXISTS index_by_creator_height; -DROP TABLE IF EXISTS index_by_id_height_asset; -)"; - + void StorageImpl::reset() { // erase db log_->info("drop db"); - pqxx::connection connection(postgres_options_.optionsString()); - pqxx::work txn(connection); - txn.exec(drop); - txn.commit(); - pqxx::work init_txn(connection); - init_txn.exec(init_); - init_txn.commit(); + soci::session sql(*connection_); + sql << reset_; + } + + void StorageImpl::dropStorage() { + log_->info("drop storage"); + if (connection_ == nullptr) { + log_->warn("Tried to drop storage without active connection"); + return; + } + + if (auto dbname = postgres_options_.dbname()) { + auto &db = dbname.value(); + std::unique_lock lock(drop_mutex); + log_->info("Drop database {}", db); + connection_.reset(); + soci::session sql(soci::postgresql, + postgres_options_.optionsStringWithoutDbName()); + // kill active connections + sql << R"( +SELECT pg_terminate_backend(pg_stat_activity.pid) +FROM pg_stat_activity +WHERE pg_stat_activity.datname = :dbname + AND pid <> pg_backend_pid();)", + soci::use(dbname.value()); + // perform dropping + sql << "DROP DATABASE " + db; + } else { + soci::session(*connection_) << drop_; + } // erase blocks log_->info("drop block store"); @@ -178,20 +172,24 @@ DROP TABLE IF EXISTS index_by_id_height_asset; expected::Result StorageImpl::createDatabaseIfNotExist( const std::string &dbname, const std::string &options_str_without_dbname) { - pqxx::lazyconnection temp_connection(options_str_without_dbname); - auto transaction = - std::make_unique(temp_connection); - // check if database dbname exists try { - auto result = transaction->exec( - "SELECT datname FROM pg_catalog.pg_database WHERE datname = " - + transaction->quote(dbname)); - if (result.size() == 0) { - transaction->exec("CREATE DATABASE " + dbname); + soci::session sql(soci::postgresql, options_str_without_dbname); + + int size; + std::string name = dbname; + + sql << "SELECT count(datname) FROM pg_catalog.pg_database WHERE " + "datname = :dbname", + soci::into(size), soci::use(name); + + if (size == 0) { + std::string query = "CREATE DATABASE "; + query += dbname; + sql << query; return expected::makeValue(true); } return expected::makeValue(false); - } catch (const pqxx::failure &e) { + } catch (std::exception &e) { return expected::makeError( std::string("Connection to PostgreSQL broken: ") + e.what()); } @@ -213,9 +211,24 @@ DROP TABLE IF EXISTS index_by_id_height_asset; return expected::makeValue(ConnectionContext(std::move(*block_store))); } + expected::Result, std::string> + StorageImpl::initPostgresConnection(std::string &options_str, + size_t pool_size) { + auto pool = std::make_shared(pool_size); + + for (size_t i = 0; i != pool_size; i++) { + soci::session &session = pool->at(i); + session.open(soci::postgresql, options_str); + } + return expected::makeValue(pool); + }; + expected::Result, std::string> - StorageImpl::create(std::string block_store_dir, - std::string postgres_options) { + StorageImpl::create( + std::string block_store_dir, + std::string postgres_options, + std::shared_ptr + factory) { boost::optional string_res = boost::none; PostgresOptions options(postgres_options); @@ -234,20 +247,27 @@ DROP TABLE IF EXISTS index_by_id_height_asset; } auto ctx_result = initConnections(block_store_dir); + auto db_result = initPostgresConnection(postgres_options); expected::Result, std::string> storage; ctx_result.match( [&](expected::Value &ctx) { - storage = expected::makeValue(std::shared_ptr( - new StorageImpl(block_store_dir, - options, - std::move(ctx.value.block_store)))); + db_result.match( + [&](expected::Value> + &connection) { + storage = expected::makeValue(std::shared_ptr( + new StorageImpl(block_store_dir, + options, + std::move(ctx.value.block_store), + connection.value, + factory))); + }, + [&](expected::Error &error) { storage = error; }); }, [&](expected::Error &error) { storage = error; }); return storage; } void StorageImpl::commit(std::unique_ptr mutableStorage) { - std::unique_lock write(rw_lock_); auto storage_ptr = std::move(mutableStorage); // get ownership of storage auto storage = static_cast(storage_ptr.get()); for (const auto &block : storage->block_store_) { @@ -259,42 +279,73 @@ DROP TABLE IF EXISTS index_by_id_height_asset; notifier_.get_subscriber().on_next(block.second); } - storage->transaction_->exec("COMMIT;"); + *(storage->sql_) << "COMMIT"; storage->committed = true; } - std::shared_ptr StorageImpl::getWsvQuery() const { - auto postgres_connection = std::make_unique( - postgres_options_.optionsString()); - try { - postgres_connection->activate(); - } catch (const pqxx::broken_connection &e) { - // TODO 29.03.2018 vdrobny IR-1184 Handle this exception - throw pqxx::broken_connection(e); + namespace { + /** + * Deleter for an object which uses connection_pool + * @tparam Query object type to delete + */ + template + class Deleter { + public: + Deleter(std::shared_ptr conn, size_t pool_pos) + : conn_(std::move(conn)), pool_pos_(pool_pos) {} + + void operator()(Query *q) const { + if (conn_ != nullptr) { + conn_->give_back(pool_pos_); + } + delete q; + } + + private: + std::shared_ptr conn_; + const size_t pool_pos_; + }; + + /** + * Factory method for query object creation which uses connection_pool + * @tparam Query object type to create + * @tparam Backend object type to use as a backend for Query + * @param b is a backend obj + * @param conn is pointer to connection pool for getting and releaseing + * the session + * @param log is a logger + * @param drop_mutex is mutex for preventing connection destruction + * during the function + * @return pointer to created query object + * note: blocks untils connection can be leased from the pool + */ + template + std::shared_ptr setupQuery( + Backend &b, + std::shared_ptr conn, + const logger::Logger &log, + std::shared_timed_mutex &drop_mutex) { + std::shared_lock lock(drop_mutex); + if (conn == nullptr) { + log->warn("Storage was deleted, cannot perform setup"); + return nullptr; + } + auto pool_pos = conn->lease(); + soci::session &session = conn->at(pool_pos); + lock.unlock(); + return {new Query(session, b), + Deleter(std::move(conn), pool_pos)}; } - auto wsv_transaction = - std::make_unique(*postgres_connection); + } // namespace - return std::make_shared(std::move(postgres_connection), - std::move(wsv_transaction)); + std::shared_ptr StorageImpl::getWsvQuery() const { + return setupQuery( + factory_, connection_, log_, drop_mutex); } std::shared_ptr StorageImpl::getBlockQuery() const { - auto postgres_connection = std::make_unique( - postgres_options_.optionsString()); - try { - postgres_connection->activate(); - } catch (const pqxx::broken_connection &e) { - // TODO 29.03.2018 vdrobny IR-1184 Handle this exception - throw pqxx::broken_connection(e); - } - auto wsv_transaction = - std::make_unique(*postgres_connection); - - return std::make_shared( - std::move(postgres_connection), - std::move(wsv_transaction), - *block_store_); + return setupQuery( + *block_store_, connection_, log_, drop_mutex); } rxcpp::observable> @@ -302,31 +353,44 @@ DROP TABLE IF EXISTS index_by_id_height_asset; return notifier_.get_observable(); } - template - static const std::string createPermissionTypes( - const std::string &type_name) { - std::string s = - "DO $$\nBEGIN\nIF NOT EXISTS (SELECT 1 FROM pg_type " - "WHERE typname='" + type_name + "')" - " THEN CREATE TYPE " + type_name + " AS ENUM ("; - const auto count = static_cast(Perm::COUNT); - for (size_t i = 0; i < count; ++i) { - s += "'" - + shared_model::proto::permissions::toString(static_cast(i)) - + "'"; - if (i != count - 1) { - s += ','; - } - } - return s + ");\nEND IF;\nEND $$;"; - } + const std::string &StorageImpl::drop_ = R"( +DROP TABLE IF EXISTS account_has_signatory; +DROP TABLE IF EXISTS account_has_asset; +DROP TABLE IF EXISTS role_has_permissions CASCADE; +DROP TABLE IF EXISTS account_has_roles; +DROP TABLE IF EXISTS account_has_grantable_permissions CASCADE; +DROP TABLE IF EXISTS account; +DROP TABLE IF EXISTS asset; +DROP TABLE IF EXISTS domain; +DROP TABLE IF EXISTS signatory; +DROP TABLE IF EXISTS peer; +DROP TABLE IF EXISTS role; +DROP TABLE IF EXISTS height_by_hash; +DROP TABLE IF EXISTS height_by_account_set; +DROP TABLE IF EXISTS index_by_creator_height; +DROP TABLE IF EXISTS index_by_id_height_asset; +)"; + + const std::string &StorageImpl::reset_ = R"( +DELETE FROM account_has_signatory; +DELETE FROM account_has_asset; +DELETE FROM role_has_permissions CASCADE; +DELETE FROM account_has_roles; +DELETE FROM account_has_grantable_permissions CASCADE; +DELETE FROM account; +DELETE FROM asset; +DELETE FROM domain; +DELETE FROM signatory; +DELETE FROM peer; +DELETE FROM role; +DELETE FROM height_by_hash; +DELETE FROM height_by_account_set; +DELETE FROM index_by_creator_height; +DELETE FROM index_by_id_height_asset; +)"; const std::string &StorageImpl::init_ = - createPermissionTypes( - "role_perm") - + createPermissionTypes< - shared_model::interface::permissions::Grantable>("grantable_perm") - + R"( + R"( CREATE TABLE IF NOT EXISTS role ( role_id character varying(32), PRIMARY KEY (role_id) @@ -337,7 +401,7 @@ CREATE TABLE IF NOT EXISTS domain ( PRIMARY KEY (domain_id) ); CREATE TABLE IF NOT EXISTS signatory ( - public_key bytea NOT NULL, + public_key varchar NOT NULL, PRIMARY KEY (public_key) ); CREATE TABLE IF NOT EXISTS account ( @@ -349,11 +413,11 @@ CREATE TABLE IF NOT EXISTS account ( ); CREATE TABLE IF NOT EXISTS account_has_signatory ( account_id character varying(288) NOT NULL REFERENCES account, - public_key bytea NOT NULL REFERENCES signatory, + public_key varchar NOT NULL REFERENCES signatory, PRIMARY KEY (account_id, public_key) ); CREATE TABLE IF NOT EXISTS peer ( - public_key bytea NOT NULL, + public_key varchar NOT NULL, address character varying(261) NOT NULL UNIQUE, PRIMARY KEY (public_key) ); @@ -372,8 +436,10 @@ CREATE TABLE IF NOT EXISTS account_has_asset ( ); CREATE TABLE IF NOT EXISTS role_has_permissions ( role_id character varying(32) NOT NULL REFERENCES role, - permission role_perm, - PRIMARY KEY (role_id, permission) + permission bit()" + + std::to_string(shared_model::interface::RolePermissionSet::size()) + + R"() NOT NULL, + PRIMARY KEY (role_id) ); CREATE TABLE IF NOT EXISTS account_has_roles ( account_id character varying(288) NOT NULL REFERENCES account, @@ -383,11 +449,14 @@ CREATE TABLE IF NOT EXISTS account_has_roles ( CREATE TABLE IF NOT EXISTS account_has_grantable_permissions ( permittee_account_id character varying(288) NOT NULL REFERENCES account, account_id character varying(288) NOT NULL REFERENCES account, - permission grantable_perm, - PRIMARY KEY (permittee_account_id, account_id, permission) + permission bit()" + + std::to_string( + shared_model::interface::GrantablePermissionSet::size()) + + R"() NOT NULL, + PRIMARY KEY (permittee_account_id, account_id) ); CREATE TABLE IF NOT EXISTS height_by_hash ( - hash bytea, + hash varchar, height text ); CREATE TABLE IF NOT EXISTS height_by_account_set ( diff --git a/irohad/ametsuchi/impl/storage_impl.hpp b/irohad/ametsuchi/impl/storage_impl.hpp index 19633aa97d..62567f64ae 100644 --- a/irohad/ametsuchi/impl/storage_impl.hpp +++ b/irohad/ametsuchi/impl/storage_impl.hpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_STORAGE_IMPL_HPP @@ -20,12 +8,15 @@ #include "ametsuchi/storage.hpp" -#include #include -#include #include + +#include +#include + #include "ametsuchi/impl/postgres_options.hpp" #include "ametsuchi/key_value_storage.hpp" +#include "interfaces/common_objects/common_objects_factory.hpp" #include "logger/logger.hpp" namespace iroha { @@ -48,9 +39,16 @@ namespace iroha { static expected::Result initConnections( std::string block_store_dir); + static expected::Result, + std::string> + initPostgresConnection(std::string &options_str, size_t pool_size = 10); + public: static expected::Result, std::string> create( - std::string block_store_dir, std::string postgres_connection); + std::string block_store_dir, + std::string postgres_connection, + std::shared_ptr + factory_); expected::Result, std::string> createTemporaryWsv() override; @@ -63,19 +61,20 @@ namespace iroha { * @param blocks - block for insertion * @return true if all blocks are inserted */ - virtual bool insertBlock( - const shared_model::interface::Block &block) override; + bool insertBlock(const shared_model::interface::Block &block) override; /** * Insert blocks without validation * @param blocks - collection of blocks for insertion * @return true if inserted */ - virtual bool insertBlocks( + bool insertBlocks( const std::vector> &blocks) override; - virtual void dropStorage() override; + void reset() override; + + void dropStorage() override; void commit(std::unique_ptr mutableStorage) override; @@ -89,7 +88,10 @@ namespace iroha { protected: StorageImpl(std::string block_store_dir, PostgresOptions postgres_options, - std::unique_ptr block_store); + std::unique_ptr block_store, + std::shared_ptr connection, + std::shared_ptr + factory); /** * Folder with raw blocks @@ -102,15 +104,20 @@ namespace iroha { private: std::unique_ptr block_store_; - // Allows multiple readers and a single writer - std::shared_timed_mutex rw_lock_; + std::shared_ptr connection_; - logger::Logger log_; + std::shared_ptr factory_; rxcpp::subjects::subject> notifier_; + logger::Logger log_; + + mutable std::shared_timed_mutex drop_mutex; + protected: + static const std::string &drop_; + static const std::string &reset_; static const std::string &init_; }; } // namespace ametsuchi diff --git a/irohad/ametsuchi/impl/temporary_wsv_impl.cpp b/irohad/ametsuchi/impl/temporary_wsv_impl.cpp index 9e69b70e3c..3ccd781ae2 100644 --- a/irohad/ametsuchi/impl/temporary_wsv_impl.cpp +++ b/irohad/ametsuchi/impl/temporary_wsv_impl.cpp @@ -1,78 +1,107 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include "ametsuchi/impl/temporary_wsv_impl.hpp" +#include "ametsuchi/impl/postgres_command_executor.hpp" #include "ametsuchi/impl/postgres_wsv_command.hpp" #include "ametsuchi/impl/postgres_wsv_query.hpp" -#include "amount/amount.hpp" namespace iroha { namespace ametsuchi { TemporaryWsvImpl::TemporaryWsvImpl( - std::unique_ptr connection, - std::unique_ptr transaction) - : connection_(std::move(connection)), - transaction_(std::move(transaction)), - wsv_(std::make_unique(*transaction_)), - executor_(std::make_unique(*transaction_)), + std::unique_ptr sql, + std::shared_ptr factory) + : sql_(std::move(sql)), + wsv_(std::make_shared(*sql_, factory)), + executor_(std::make_shared(*sql_)), + command_executor_(std::make_shared(*sql_)), + command_validator_(std::make_shared(wsv_)), log_(logger::log("TemporaryWSV")) { - auto query = std::make_shared(*transaction_); - auto command = std::make_shared(*transaction_); - command_executor_ = std::make_shared(query, command); - command_validator_ = std::make_shared(query); - transaction_->exec("BEGIN;"); + *sql_ << "BEGIN"; } - bool TemporaryWsvImpl::apply( + expected::Result TemporaryWsvImpl::apply( const shared_model::interface::Transaction &tx, - std::function apply_function) { + std::function( + const shared_model::interface::Transaction &, WsvQuery &)> + apply_function) { const auto &tx_creator = tx.creatorAccountId(); command_executor_->setCreatorAccountId(tx_creator); command_validator_->setCreatorAccountId(tx_creator); - auto execute_command = [this, &tx_creator](auto &command) { - auto account = wsv_->getAccount(tx_creator).value(); - if (not boost::apply_visitor(*command_validator_, command.get())) { - return false; + auto execute_command = + [this](auto &command) -> expected::Result { + // Validate command + return boost::apply_visitor(*command_validator_, command.get()) + // Execute command + | [this, &command] { + return boost::apply_visitor(*command_executor_, command.get()); + }; + }; + + auto savepoint_wrapper = createSavepoint("savepoint_temp_wsv"); + + return apply_function(tx, *wsv_) | + [savepoint = std::move(savepoint_wrapper), + &execute_command, + &tx]() -> expected::Result { + // check transaction's commands validity + const auto &commands = tx.commands(); + validation::CommandError cmd_error; + for (size_t i = 0; i < commands.size(); ++i) { + // in case of failed command, rollback and return + auto cmd_is_valid = + execute_command(commands[i]) + .match([](expected::Value &) { return true; }, + [i, &cmd_error](expected::Error &error) { + cmd_error = {error.error.command_name, + error.error.toString(), + true, + i}; + return false; + }); + if (not cmd_is_valid) { + return expected::makeError(cmd_error); + } } - auto result = boost::apply_visitor(*command_executor_, command.get()); - return result.match([](expected::Value &v) { return true; }, - [this](expected::Error &e) { - log_->error(e.error.toString()); - return false; - }); + // success + savepoint->release(); + return {}; }; + } - transaction_->exec("SAVEPOINT savepoint_;"); - auto result = - apply_function(tx, *wsv_) - and std::all_of( - tx.commands().begin(), tx.commands().end(), execute_command); - if (result) { - transaction_->exec("RELEASE SAVEPOINT savepoint_;"); - } else { - transaction_->exec("ROLLBACK TO SAVEPOINT savepoint_;"); - } - return result; + std::unique_ptr + TemporaryWsvImpl::createSavepoint(const std::string &name) { + return std::make_unique( + SavepointWrapperImpl(*this, name)); } TemporaryWsvImpl::~TemporaryWsvImpl() { - transaction_->exec("ROLLBACK;"); + *sql_ << "ROLLBACK"; + } + + TemporaryWsvImpl::SavepointWrapperImpl::SavepointWrapperImpl( + const iroha::ametsuchi::TemporaryWsvImpl &wsv, + std::string savepoint_name) + : sql_{wsv.sql_}, + savepoint_name_{std::move(savepoint_name)}, + is_released_{false} { + *sql_ << "SAVEPOINT " + savepoint_name_ + ";"; + }; + + void TemporaryWsvImpl::SavepointWrapperImpl::release() { + is_released_ = true; } + + TemporaryWsvImpl::SavepointWrapperImpl::~SavepointWrapperImpl() { + if (not is_released_) { + *sql_ << "ROLLBACK TO SAVEPOINT " + savepoint_name_ + ";"; + } else { + *sql_ << "RELEASE SAVEPOINT " + savepoint_name_ + ";"; + } + } + } // namespace ametsuchi } // namespace iroha diff --git a/irohad/ametsuchi/impl/temporary_wsv_impl.hpp b/irohad/ametsuchi/impl/temporary_wsv_impl.hpp index 0b962d4c43..2d81360c85 100644 --- a/irohad/ametsuchi/impl/temporary_wsv_impl.hpp +++ b/irohad/ametsuchi/impl/temporary_wsv_impl.hpp @@ -18,11 +18,11 @@ #ifndef IROHA_TEMPORARY_WSV_IMPL_HPP #define IROHA_TEMPORARY_WSV_IMPL_HPP -#include -#include +#include #include "ametsuchi/temporary_wsv.hpp" #include "execution/command_executor.hpp" +#include "interfaces/common_objects/common_objects_factory.hpp" #include "logger/logger.hpp" namespace iroha { @@ -30,21 +30,39 @@ namespace iroha { namespace ametsuchi { class TemporaryWsvImpl : public TemporaryWsv { public: - TemporaryWsvImpl(std::unique_ptr connection, - std::unique_ptr transaction); + struct SavepointWrapperImpl : public TemporaryWsv::SavepointWrapper { + SavepointWrapperImpl(const TemporaryWsvImpl &wsv, + std::string savepoint_name); - bool apply( + void release() override; + + ~SavepointWrapperImpl() override; + + private: + std::shared_ptr sql_; + std::string savepoint_name_; + bool is_released_; + }; + + explicit TemporaryWsvImpl(std::unique_ptr sql, + std::shared_ptr + factory); + + expected::Result apply( const shared_model::interface::Transaction &, - std::function function) override; + std::function( + const shared_model::interface::Transaction &, WsvQuery &)> + function) override; + + std::unique_ptr createSavepoint( + const std::string &name) override; ~TemporaryWsvImpl() override; private: - std::unique_ptr connection_; - std::unique_ptr transaction_; - std::unique_ptr wsv_; - std::unique_ptr executor_; + std::shared_ptr sql_; + std::shared_ptr wsv_; + std::shared_ptr executor_; std::shared_ptr command_executor_; std::shared_ptr command_validator_; diff --git a/irohad/ametsuchi/impl/wsv_restorer_impl.cpp b/irohad/ametsuchi/impl/wsv_restorer_impl.cpp index be2e9938f3..3b30d9fac8 100644 --- a/irohad/ametsuchi/impl/wsv_restorer_impl.cpp +++ b/irohad/ametsuchi/impl/wsv_restorer_impl.cpp @@ -28,13 +28,10 @@ namespace iroha { expected::Result WsvRestorerImpl::restoreWsv( Storage &storage) { // get all blocks starting from the genesis - std::vector> blocks; - storage.getBlockQuery()->getBlocksFrom(1).as_blocking().subscribe( - [&blocks](auto block) { - blocks.push_back(std::move(block)); - }); + std::vector> blocks= + storage.getBlockQuery()->getBlocksFrom(1); - storage.dropStorage(); + storage.reset(); if (not storage.insertBlocks(blocks)) return expected::makeError("cannot insert blocks"); diff --git a/irohad/ametsuchi/mutable_storage.hpp b/irohad/ametsuchi/mutable_storage.hpp index 21bd39f99f..1f0f069ff2 100644 --- a/irohad/ametsuchi/mutable_storage.hpp +++ b/irohad/ametsuchi/mutable_storage.hpp @@ -24,7 +24,8 @@ namespace shared_model { namespace interface { class Block; - } + class BlockVariant; + } // namespace interface } // namespace shared_model namespace iroha { @@ -38,6 +39,25 @@ namespace iroha { */ class MutableStorage { public: + /** + * Predicate type checking type T + */ + template + using MutableStoragePredicateType = + std::function; + + /** + * Checks if block satisfies predicated + * @param block block to be checked + * @param predicate function returning true if predicate satisfied and + * false otherwise + * @return result of predicate + */ + virtual bool check(const shared_model::interface::BlockVariant &block, + MutableStoragePredicateType) = 0; + /** * Applies a block to current mutable state * using logic specified in function @@ -54,10 +74,7 @@ namespace iroha { */ virtual bool apply( const shared_model::interface::Block &block, - std::function - function) = 0; + MutableStoragePredicateType function) = 0; virtual ~MutableStorage() = default; }; diff --git a/irohad/ametsuchi/storage.hpp b/irohad/ametsuchi/storage.hpp index f9d84b2959..5e13301d9a 100644 --- a/irohad/ametsuchi/storage.hpp +++ b/irohad/ametsuchi/storage.hpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_AMETSUCHI_H @@ -70,6 +58,11 @@ namespace iroha { virtual rxcpp::observable> on_commit() = 0; + /** + * Remove all information without dropping the storage + */ + virtual void reset() = 0; + /** * Remove all information from ledger */ diff --git a/irohad/ametsuchi/temporary_wsv.hpp b/irohad/ametsuchi/temporary_wsv.hpp index edd02851d3..726a8dbf42 100644 --- a/irohad/ametsuchi/temporary_wsv.hpp +++ b/irohad/ametsuchi/temporary_wsv.hpp @@ -22,6 +22,7 @@ #include "ametsuchi/wsv_command.hpp" #include "ametsuchi/wsv_query.hpp" +#include "validation/stateful_validator_common.hpp" namespace shared_model { namespace interface { @@ -38,27 +39,45 @@ namespace iroha { */ class TemporaryWsv { public: + /** + * Wrapper for savepoints in wsv state; rollbacks to savepoint, if + * destroyed without explicit release, releases it otherwise + */ + struct SavepointWrapper { + /** + * Release the savepoint + */ + virtual void release() = 0; + + virtual ~SavepointWrapper() = default; + }; + /** * Applies a transaction to current state * using logic specified in function * @param transaction Transaction to be applied * @param function Function that specifies the logic used to apply the * transaction - * Function parameters: - * - Transaction @see transaction - * - WsvQuery - world state view query interface for temporary storage - * Function returns true if the transaction is successfully applied, false - * otherwise. * @return True if transaction was successfully applied, false otherwise */ - virtual bool apply( + virtual expected::Result apply( const shared_model::interface::Transaction &, - std::function function) = 0; + std::function( + const shared_model::interface::Transaction &, WsvQuery &)> + function) = 0; + + /** + * Create a savepoint for wsv state + * @param name of savepoint to be created + * @return RAII wrapper for savepoints + */ + virtual std::unique_ptr createSavepoint( + const std::string &name) = 0; virtual ~TemporaryWsv() = default; + }; - } // namespace ametsuchi + } // namespace ametsuchi } // namespace iroha #endif // IROHA_TEMPORARYWSV_HPP diff --git a/irohad/ametsuchi/wsv_command.hpp b/irohad/ametsuchi/wsv_command.hpp index 8a2afa1103..d34f899db1 100644 --- a/irohad/ametsuchi/wsv_command.hpp +++ b/irohad/ametsuchi/wsv_command.hpp @@ -232,6 +232,7 @@ namespace iroha { */ virtual WsvCommandResult insertDomain( const shared_model::interface::Domain &domain) = 0; + }; } // namespace ametsuchi diff --git a/irohad/ametsuchi/wsv_query.hpp b/irohad/ametsuchi/wsv_query.hpp index dcd602ccca..6251789465 100644 --- a/irohad/ametsuchi/wsv_query.hpp +++ b/irohad/ametsuchi/wsv_query.hpp @@ -102,10 +102,16 @@ namespace iroha { /** * Get accounts information from its key-value storage * @param account_id - account to get details about + * @param key - only values under this key from Json are returned; default + * empty + * @param writer - only values, added by the writer's account, are + * returned; default empty * @return optional of account details */ virtual boost::optional getAccountDetail( - const std::string &account_id) = 0; + const std::string &account_id, + const std::string &key = "", + const std::string &writer = "") = 0; /** * Get signatories of account by user account_id diff --git a/irohad/execution/CMakeLists.txt b/irohad/execution/CMakeLists.txt index 6d8db100de..2f2f45ed28 100644 --- a/irohad/execution/CMakeLists.txt +++ b/irohad/execution/CMakeLists.txt @@ -1,16 +1,7 @@ -# Copyright 2018 Soramitsu Co., Ltd. # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +#Copyright Soramitsu Co., Ltd. All Rights Reserved. +#SPDX-License-Identifier: Apache-2.0 # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. add_library(common_execution impl/common_executor.cpp @@ -34,7 +25,7 @@ target_link_libraries(command_execution ) add_library(query_execution - impl/query_execution.cpp + impl/query_execution_impl.cpp ) target_link_libraries(query_execution diff --git a/irohad/execution/command_executor.hpp b/irohad/execution/command_executor.hpp index 045f92b7a5..e536ea180d 100644 --- a/irohad/execution/command_executor.hpp +++ b/irohad/execution/command_executor.hpp @@ -21,6 +21,7 @@ #include #include +#include "ametsuchi/command_executor.hpp" #include "ametsuchi/wsv_command.hpp" #include "ametsuchi/wsv_query.hpp" #include "builders/default_builders.hpp" @@ -44,276 +45,218 @@ namespace iroha { - /** - * Error for command execution. - * Contains command name, as well as an error message - */ - struct ExecutionError { - std::string command_name; - std::string error_message; - - std::string toString() const { - return (boost::format("%s: %s") % command_name % error_message).str(); - } - }; + using CommandError = ametsuchi::CommandError; /** - * ExecutionResult is a return type of all execute functions. - * If execute is successful, result will not contain anything (void value), - * because execute does not return any value. - * If execution is not successful, ExecutionResult will contain Execution - * error with explanation + * CommandResult is a return type of all execute and validate functions. + * If execute or validate is successful, result will not contain anything + * (void value), because execute and validate does not return any value. If + * execution or validation is not successful, CommandResult will contain + * Execution error with explanation * * Result is used because it allows to clear distinction between two states * - value and error. If we just returned error, it would be confusing, what - * value of an error to consider as a successful state of execution + * value of an error to consider as a successful state of execution or + * validation */ - using ExecutionResult = iroha::expected::Result; + using CommandResult = iroha::expected::Result; - class CommandExecutor : public boost::static_visitor { - public: - CommandExecutor(std::shared_ptr queries, - std::shared_ptr commands); - ExecutionResult operator()( - const shared_model::interface::AddAssetQuantity &command); - - ExecutionResult operator()(const shared_model::interface::AddPeer &command); + class CommandValidator : public boost::static_visitor { + public: + CommandValidator(std::shared_ptr queries); - ExecutionResult operator()( - const shared_model::interface::AddSignatory &command); + template + CommandResult operator()(const CommandType &command) { + return hasPermissions(command, *queries, creator_account_id) | + [&] { return isValid(command, *queries, creator_account_id); }; + } - ExecutionResult operator()( - const shared_model::interface::AppendRole &command); + void setCreatorAccountId(const shared_model::interface::types::AccountIdType + &creator_account_id); - ExecutionResult operator()( - const shared_model::interface::CreateAccount &command); + private: + CommandResult hasPermissions( + const shared_model::interface::AddAssetQuantity &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - ExecutionResult operator()( - const shared_model::interface::CreateAsset &command); + CommandResult hasPermissions( + const shared_model::interface::AddPeer &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - ExecutionResult operator()( - const shared_model::interface::CreateDomain &command); + CommandResult hasPermissions( + const shared_model::interface::AddSignatory &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - ExecutionResult operator()( - const shared_model::interface::CreateRole &command); + CommandResult hasPermissions( + const shared_model::interface::AppendRole &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - ExecutionResult operator()( - const shared_model::interface::DetachRole &command); + CommandResult hasPermissions( + const shared_model::interface::CreateAccount &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - ExecutionResult operator()( - const shared_model::interface::GrantPermission &command); + CommandResult hasPermissions( + const shared_model::interface::CreateAsset &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - ExecutionResult operator()( - const shared_model::interface::RemoveSignatory &command); + CommandResult hasPermissions( + const shared_model::interface::CreateDomain &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - ExecutionResult operator()( - const shared_model::interface::RevokePermission &command); + CommandResult hasPermissions( + const shared_model::interface::CreateRole &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - ExecutionResult operator()( - const shared_model::interface::SetAccountDetail &command); + CommandResult hasPermissions( + const shared_model::interface::DetachRole &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - ExecutionResult operator()( - const shared_model::interface::SetQuorum &command); + CommandResult hasPermissions( + const shared_model::interface::GrantPermission &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - ExecutionResult operator()( - const shared_model::interface::SubtractAssetQuantity &command); + CommandResult hasPermissions( + const shared_model::interface::RemoveSignatory &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - ExecutionResult operator()( - const shared_model::interface::TransferAsset &command); + CommandResult hasPermissions( + const shared_model::interface::RevokePermission &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - void setCreatorAccountId(const shared_model::interface::types::AccountIdType - &creator_account_id); + CommandResult hasPermissions( + const shared_model::interface::SetAccountDetail &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - private: - std::shared_ptr queries; - std::shared_ptr commands; - shared_model::interface::types::AccountIdType creator_account_id; + CommandResult hasPermissions( + const shared_model::interface::SetQuorum &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - shared_model::builder::AmountBuilderWithoutValidator amount_builder_; - shared_model::builder::DefaultAccountAssetBuilder account_asset_builder_; - shared_model::builder::DefaultAccountBuilder account_builder_; - shared_model::builder::DefaultAssetBuilder asset_builder_; - shared_model::builder::DefaultDomainBuilder domain_builder_; - }; + CommandResult hasPermissions( + const shared_model::interface::SubtractAssetQuantity &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - class CommandValidator : public boost::static_visitor { - public: - CommandValidator(std::shared_ptr queries); + CommandResult hasPermissions( + const shared_model::interface::TransferAsset &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - template - bool operator()(const CommandType &command) { - return hasPermissions(command, *queries, creator_account_id) - and isValid(command, *queries, creator_account_id); - } + CommandResult isValid( + const shared_model::interface::AddAssetQuantity &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - void setCreatorAccountId(const shared_model::interface::types::AccountIdType - &creator_account_id); + CommandResult isValid(const shared_model::interface::AddPeer &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); + + CommandResult isValid(const shared_model::interface::AddSignatory &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); + + CommandResult isValid(const shared_model::interface::AppendRole &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); + + CommandResult isValid(const shared_model::interface::CreateAccount &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); + + CommandResult isValid(const shared_model::interface::CreateAsset &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); + + CommandResult isValid(const shared_model::interface::CreateDomain &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); + + CommandResult isValid(const shared_model::interface::CreateRole &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); + + CommandResult isValid(const shared_model::interface::DetachRole &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); + + CommandResult isValid( + const shared_model::interface::GrantPermission &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - private: - bool hasPermissions( - const shared_model::interface::AddAssetQuantity &command, + CommandResult isValid( + const shared_model::interface::RemoveSignatory &command, iroha::ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id); - bool hasPermissions(const shared_model::interface::AddPeer &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool hasPermissions(const shared_model::interface::AddSignatory &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool hasPermissions(const shared_model::interface::AppendRole &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool hasPermissions(const shared_model::interface::CreateAccount &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool hasPermissions(const shared_model::interface::CreateAsset &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool hasPermissions(const shared_model::interface::CreateDomain &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool hasPermissions(const shared_model::interface::CreateRole &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool hasPermissions(const shared_model::interface::DetachRole &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool hasPermissions(const shared_model::interface::GrantPermission &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool hasPermissions(const shared_model::interface::RemoveSignatory &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool hasPermissions( + CommandResult isValid( const shared_model::interface::RevokePermission &command, iroha::ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id); - bool hasPermissions( + CommandResult isValid( const shared_model::interface::SetAccountDetail &command, iroha::ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id); - bool hasPermissions(const shared_model::interface::SetQuorum &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); + CommandResult isValid(const shared_model::interface::SetQuorum &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); - bool hasPermissions( + CommandResult isValid( const shared_model::interface::SubtractAssetQuantity &command, iroha::ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id); - bool hasPermissions(const shared_model::interface::TransferAsset &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::AddAssetQuantity &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::AddPeer &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::AddSignatory &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::AppendRole &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::CreateAccount &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::CreateAsset &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::CreateDomain &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::CreateRole &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::DetachRole &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::GrantPermission &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::RemoveSignatory &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::RevokePermission &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::SetAccountDetail &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::SetQuorum &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::SubtractAssetQuantity &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); - - bool isValid(const shared_model::interface::TransferAsset &command, - iroha::ametsuchi::WsvQuery &queries, - const shared_model::interface::types::AccountIdType - &creator_account_id); + CommandResult isValid(const shared_model::interface::TransferAsset &command, + iroha::ametsuchi::WsvQuery &queries, + const shared_model::interface::types::AccountIdType + &creator_account_id); std::shared_ptr queries; shared_model::interface::types::AccountIdType creator_account_id; diff --git a/irohad/execution/impl/command_executor.cpp b/irohad/execution/impl/command_executor.cpp index 5a48d0c143..3203bcac6a 100644 --- a/irohad/execution/impl/command_executor.cpp +++ b/irohad/execution/impl/command_executor.cpp @@ -16,6 +16,8 @@ */ #include +#include +#include #include "execution/command_executor.hpp" @@ -28,455 +30,26 @@ using namespace shared_model::detail; using namespace shared_model::interface::permissions; using namespace shared_model::proto::permissions; +using namespace std::literals::string_literals; namespace iroha { - expected::Error makeExecutionError( + expected::Error makeCommandError( const std::string &error_message, - const std::string command_name) noexcept { - return expected::makeError(ExecutionError{command_name, error_message}); + const std::string &command_name) noexcept { + return expected::makeError(CommandError{command_name, error_message}); } - ExecutionResult makeExecutionResult(const ametsuchi::WsvCommandResult &result, - std::string command_name) noexcept { + CommandResult makeCommandResult(const ametsuchi::WsvCommandResult &result, + std::string command_name) noexcept { return result.match( - [](const expected::Value &v) -> ExecutionResult { return {}; }, + [](const expected::Value &v) -> CommandResult { return {}; }, [&command_name]( - const expected::Error &e) -> ExecutionResult { - return expected::makeError(ExecutionError{command_name, e.error}); + const expected::Error &e) -> CommandResult { + return expected::makeError(CommandError{command_name, e.error}); }); } - CommandExecutor::CommandExecutor( - std::shared_ptr queries, - std::shared_ptr commands) - : queries(queries), commands(commands) {} - - void CommandExecutor::setCreatorAccountId( - const shared_model::interface::types::AccountIdType &creator_account_id) { - this->creator_account_id = creator_account_id; - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::AddAssetQuantity &command) { - std::string command_name = "AddAssetQuantity"; - auto asset = queries->getAsset(command.assetId()); - if (not asset) { - return makeExecutionError( - (boost::format("asset %s is absent") % command.assetId()).str(), - command_name); - } - - auto precision = asset.value()->precision(); - if (command.amount().precision() > precision) { - return makeExecutionError( - (boost::format("command precision is greater than asset precision: " - "expected %d, but got %d") - % precision % command.amount().precision()) - .str(), - command_name); - } - auto command_amount = - makeAmountWithPrecision(command.amount(), asset.value()->precision()); - if (not queries->getAccount(command.accountId())) { - return makeExecutionError( - (boost::format("account %s is absent") % command.accountId()).str(), - command_name); - } - auto account_asset = - queries->getAccountAsset(command.accountId(), command.assetId()); - - auto new_balance = command_amount | [this](const auto &amount) { - return amount_builder_.precision(amount->precision()) - .intValue(amount->intValue()) - .build(); - }; - using AccountAssetResult = - expected::Result, - iroha::ExecutionError>; - auto account_asset_new = new_balance.match( - [this, &account_asset, &command_name, &command]( - const expected::Value< - std::shared_ptr> - &new_balance_val) -> AccountAssetResult { - expected::PolymorphicResult - result; - if (account_asset) { - result = (*new_balance_val.value + account_asset.value()->balance()) - | [this, &command](const auto &balance) { - return account_asset_builder_.balance(*balance) - .accountId(command.accountId()) - .assetId(command.assetId()) - .build(); - }; - } else { - result = account_asset_builder_.balance(*new_balance_val.value) - .accountId(command.accountId()) - .assetId(command.assetId()) - .build(); - } - return result.match( - [](expected::Value< - std::shared_ptr> - &new_account_asset_val) -> AccountAssetResult { - return expected::makeValue(new_account_asset_val.value); - }, - [&command_name](const auto &error) -> AccountAssetResult { - return makeExecutionError(*error.error, command_name); - }); - }, - [&command_name](const auto &error) -> AccountAssetResult { - return makeExecutionError( - "amount builder failed. reason " + *error.error, command_name); - }); - - return account_asset_new.match( - [&](const expected::Value< - std::shared_ptr> - &account_asset_new_val) -> ExecutionResult { - return makeExecutionResult( - commands->upsertAccountAsset(*account_asset_new_val.value), - command_name); - }, - [&command_name](const auto &account_asset_error) -> ExecutionResult { - return makeExecutionError("account asset builder failed. reason " - + account_asset_error.error.toString(), - command_name); - }); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::AddPeer &command) { - return makeExecutionResult(commands->insertPeer(command.peer()), "AddPeer"); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::AddSignatory &command) { - auto result = commands->insertSignatory(command.pubkey()) | [&] { - return commands->insertAccountSignatory(command.accountId(), - command.pubkey()); - }; - return makeExecutionResult(result, "AddSignatory"); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::AppendRole &command) { - return makeExecutionResult( - commands->insertAccountRole(command.accountId(), command.roleName()), - "AppendRole"); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::CreateAccount &command) { - std::string command_name = "CreateAccount"; - auto account = - account_builder_ - .accountId(command.accountName() + "@" + command.domainId()) - .domainId(command.domainId()) - .quorum(1) - .jsonData("{}") - .build(); - return account.match( - [&](const expected::Value< - std::shared_ptr> &account_val) - -> ExecutionResult { - auto domain = queries->getDomain(command.domainId()); - if (not domain) { - return makeExecutionError( - (boost::format("Domain %s not found") % command.domainId()) - .str(), - command_name); - } - std::string domain_default_role = domain.value()->defaultRole(); - // Account must have unique initial pubkey - auto result = commands->insertSignatory(command.pubkey()) | [&] { - return commands->insertAccount(*account_val.value); - } | [&] { - return commands->insertAccountSignatory( - (*account_val.value).accountId(), command.pubkey()); - } | [&] { - return commands->insertAccountRole((*account_val.value).accountId(), - domain_default_role); - }; - return makeExecutionResult(result, command_name); - }, - [&command_name](const auto &error) -> ExecutionResult { - return makeExecutionError( - "account builder failed. reason " + *error.error, command_name); - }); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::CreateAsset &command) { - std::string command_name = "CreateAsset"; - auto new_asset = - asset_builder_.assetId(command.assetName() + "#" + command.domainId()) - .domainId(command.domainId()) - .precision(command.precision()) - .build(); - return new_asset.match( - [&](const expected::Value< - std::shared_ptr> &new_asset_val) - -> ExecutionResult { - // The insert will fail if asset already exists - return makeExecutionResult( - commands->insertAsset(*new_asset_val.value), command_name); - }, - [&command_name](const auto &error) -> ExecutionResult { - return makeExecutionError( - "asset builder failed. reason " + *error.error, command_name); - }); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::CreateDomain &command) { - std::string command_name = "CreateDomain"; - auto new_domain = domain_builder_.domainId(command.domainId()) - .defaultRole(command.userDefaultRole()) - .build(); - return new_domain.match( - [&](const expected::Value< - std::shared_ptr> &new_domain_val) - -> ExecutionResult { - // The insert will fail if domain already exist - return makeExecutionResult( - commands->insertDomain(*new_domain_val.value), command_name); - }, - [&command_name](const auto &error) -> ExecutionResult { - return makeExecutionError( - "domain builder failed. reason " + *error.error, command_name); - }); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::CreateRole &command) { - std::string command_name = "CreateRole"; - auto result = commands->insertRole(command.roleName()) | [&] { - return commands->insertRolePermissions(command.roleName(), - command.rolePermissions()); - }; - return makeExecutionResult(result, command_name); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::DetachRole &command) { - return makeExecutionResult( - commands->deleteAccountRole(command.accountId(), command.roleName()), - "DetachRole"); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::GrantPermission &command) { - return makeExecutionResult( - commands->insertAccountGrantablePermission( - command.accountId(), creator_account_id, command.permissionName()), - "GrantPermission"); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::RemoveSignatory &command) { - std::string command_name = "RemoveSignatory"; - - // Delete will fail if account signatory doesn't exist - auto result = - commands->deleteAccountSignatory(command.accountId(), command.pubkey()) - | [&] { return commands->deleteSignatory(command.pubkey()); }; - return makeExecutionResult(result, command_name); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::RevokePermission &command) { - return makeExecutionResult( - commands->deleteAccountGrantablePermission( - command.accountId(), creator_account_id, command.permissionName()), - "RevokePermission"); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::SetAccountDetail &command) { - auto creator = creator_account_id; - if (creator_account_id.empty()) { - // When creator is not known, it is genesis block - creator = "genesis"; - } - return makeExecutionResult( - commands->setAccountKV( - command.accountId(), creator, command.key(), command.value()), - "SetAccountDetail"); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::SetQuorum &command) { - std::string command_name = "SetQuorum"; - - auto account = queries->getAccount(command.accountId()); - if (not account) { - return makeExecutionError( - (boost::format("absent account %s") % command.accountId()).str(), - command_name); - } - auto account_new = account_builder_.domainId(account.value()->domainId()) - .accountId(account.value()->accountId()) - .jsonData(account.value()->jsonData()) - .quorum(command.newQuorum()) - .build(); - - return account_new.match( - [&](const expected::Value< - std::shared_ptr> &account_new_val) - -> ExecutionResult { - return makeExecutionResult( - commands->updateAccount(*account_new_val.value), command_name); - }, - [&command_name](const auto &error) -> ExecutionResult { - return makeExecutionError( - "account builder failed. reason " + *error.error, command_name); - }); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::SubtractAssetQuantity &command) { - std::string command_name = "SubtractAssetQuantity"; - auto asset = queries->getAsset(command.assetId()); - if (not asset) { - return makeExecutionError( - (boost::format("asset %s is absent") % command.assetId()).str(), - command_name); - } - auto precision = asset.value()->precision(); - if (command.amount().precision() > precision) { - return makeExecutionError( - (boost::format("command precision is greater than asset precision: " - "expected %d, but got %d") - % precision % command.amount().precision()) - .str(), - command_name); - } - auto command_amount = - makeAmountWithPrecision(command.amount(), asset.value()->precision()); - auto account_asset = - queries->getAccountAsset(command.accountId(), command.assetId()); - if (not account_asset) { - return makeExecutionError((boost::format("%s do not have %s") - % command.accountId() % command.assetId()) - .str(), - command_name); - } - auto account_asset_new = command_amount | - [&account_asset](const auto &amount) { - return account_asset.value()->balance() - *amount; - } - | [this, &account_asset](const auto &new_balance) { - return account_asset_builder_.balance(*new_balance) - .accountId(account_asset.value()->accountId()) - .assetId(account_asset.value()->assetId()) - .build(); - }; - - return account_asset_new.match( - [&](const expected::Value< - std::shared_ptr> - &account_asset_new_val) -> ExecutionResult { - return makeExecutionResult( - commands->upsertAccountAsset(*account_asset_new_val.value), - command_name); - }, - [&command_name](const auto &error) -> ExecutionResult { - return makeExecutionError( - "account asset builder failed. reason " + *error.error, - command_name); - }); - } - - ExecutionResult CommandExecutor::operator()( - const shared_model::interface::TransferAsset &command) { - std::string command_name = "TransferAsset"; - - auto src_account_asset = - queries->getAccountAsset(command.srcAccountId(), command.assetId()); - if (not src_account_asset) { - return makeExecutionError((boost::format("asset %s is absent of %s") - % command.assetId() % command.srcAccountId()) - .str(), - command_name); - } - auto dest_account_asset = - queries->getAccountAsset(command.destAccountId(), command.assetId()); - auto asset = queries->getAsset(command.assetId()); - if (not asset) { - return makeExecutionError((boost::format("asset %s is absent of %s") - % command.assetId() % command.destAccountId()) - .str(), - command_name); - } - auto precision = asset.value()->precision(); - if (command.amount().precision() > precision) { - return makeExecutionError( - (boost::format("command precision is greater than asset precision: " - "expected %d, but got %d") - % precision % command.amount().precision()) - .str(), - command_name); - } - auto command_amount = - makeAmountWithPrecision(command.amount(), asset.value()->precision()); - // Set new balance for source account - auto src_account_asset_new = command_amount | - [&src_account_asset](const auto &amount) { - return src_account_asset.value()->balance() - *amount; - } - | [this, &src_account_asset](const auto &new_src_balance) { - return account_asset_builder_ - .assetId(src_account_asset.value()->assetId()) - .accountId(src_account_asset.value()->accountId()) - .balance(*new_src_balance) - .build(); - }; - - auto dest_account_asset_new = command_amount | [&](const auto &amount) { - const auto kZero = boost::get< - expected::Value>>( - amount_builder_.precision(asset.value()->precision()) - .intValue(0) - .build()); - auto new_amount = - (dest_account_asset | [](const auto &ast) - -> boost::optional { - return {ast->balance()}; - }) - .get_value_or(*kZero.value) - + *amount; - return new_amount | [this, &command](const auto &new_dest_balance) { - return account_asset_builder_.assetId(command.assetId()) - .accountId(command.destAccountId()) - .balance(*new_dest_balance) - .build(); - }; - }; - - auto map_error = [&command_name](const auto &t) { - return expected::map_error( - t, [&command_name](const auto &error) -> ExecutionError { - return {"account asset builder failed. reason " + *error, - command_name}; - }); - }; - - return (map_error(src_account_asset_new) | - [&](std::shared_ptr - src_amount) -> ExecutionResult { - return map_error(dest_account_asset_new) | - [&](std::shared_ptr - dst_amount) -> ExecutionResult { - return makeExecutionResult( - commands->upsertAccountAsset(*src_amount) | - [&] { return commands->upsertAccountAsset(*dst_amount); }, - command_name); - }; - }); - } - // ----------------------| Validator |---------------------- CommandValidator::CommandValidator( @@ -488,220 +61,470 @@ namespace iroha { this->creator_account_id = creator_account_id; } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::AddAssetQuantity &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - // Check if creator has MoneyCreator permission. - // One can only add to his/her account + auto command_name = "AddAssetQuantity"; // TODO: 03.02.2018 grimadas IR-935, Separate asset creation for distinct // asset types, now: anyone having permission "can_add_asset_qty" can add // any asset - return creator_account_id == command.accountId() - and checkAccountRolePermission( - creator_account_id, queries, Role::kAddAssetQty); + if (not checkAccountRolePermission( + creator_account_id, queries, Role::kAddAssetQty)) { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Role::kAddAssetQty), + command_name); + } + return {}; } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::AddPeer &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return checkAccountRolePermission( - creator_account_id, queries, Role::kAddPeer); + auto command_name = "AddPeer"; + if (not checkAccountRolePermission( + creator_account_id, queries, Role::kAddPeer)) { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Role::kAddPeer), + command_name); + } + return {}; } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::AddSignatory &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return - // Case 1. When command creator wants to add signatory to their - // account and he has permission CanAddSignatory - (command.accountId() == creator_account_id - and checkAccountRolePermission( - creator_account_id, queries, Role::kAddSignatory)) - or - // Case 2. Creator has granted permission for it - (queries.hasAccountGrantablePermission(creator_account_id, - command.accountId(), - Grantable::kAddMySignatory)); + auto command_name = "AddSignatory"; + + auto creator_adds_signatory_to_his_account = [&creator_account_id, + &command]() { + return creator_account_id == command.accountId(); + }; + auto creator_has_permission_on_own = [&creator_account_id, &queries]() { + return checkAccountRolePermission( + creator_account_id, queries, Role::kAddSignatory); + }; + auto creator_has_grantable_permission = [&creator_account_id, + &command, + &queries]() { + return queries.hasAccountGrantablePermission( + creator_account_id, command.accountId(), Grantable::kAddMySignatory); + }; + + if (creator_adds_signatory_to_his_account()) { + if (creator_has_permission_on_own()) { + // 1. Creator adds signatory to his account, and he has permission for + // it + return {}; + } else if (creator_has_grantable_permission()) { + // 2. Creator adds signatory to his account, and he has grantable + // permission for it + return {}; + } else { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Role::kAddSignatory) + " for his own account", + command_name); + } + } else if (creator_has_grantable_permission()) { + // 3. Creator adds signatory to another account, and he has grantable + // permission for it + return {}; + } else { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Grantable::kAddMySignatory) + " for account " + + command.accountId(), + command_name); + } } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::AppendRole &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return checkAccountRolePermission( - creator_account_id, queries, Role::kAppendRole); + auto command_name = "AppendRole"; + if (not checkAccountRolePermission( + creator_account_id, queries, Role::kAppendRole)) { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Role::kAppendRole), + command_name); + } + return {}; } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::CreateAccount &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return checkAccountRolePermission( - creator_account_id, queries, Role::kCreateAccount); + auto command_name = "CreateAccount"; + if (not checkAccountRolePermission( + creator_account_id, queries, Role::kCreateAccount)) { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Role::kCreateAccount), + command_name); + } + return {}; } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::CreateAsset &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return checkAccountRolePermission( - creator_account_id, queries, Role::kCreateAsset); + auto command_name = "CreateAsset"; + if (not checkAccountRolePermission( + creator_account_id, queries, Role::kCreateAsset)) { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Role::kCreateAsset), + command_name); + } + return {}; } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::CreateDomain &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return checkAccountRolePermission( - creator_account_id, queries, Role::kCreateDomain); + auto command_name = "CreateDomain"; + if (not checkAccountRolePermission( + creator_account_id, queries, Role::kCreateDomain)) { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Role::kCreateDomain), + command_name); + } + return {}; } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::CreateRole &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return checkAccountRolePermission( - creator_account_id, queries, Role::kCreateRole); + auto command_name = "CreateRole"; + if (not checkAccountRolePermission( + creator_account_id, queries, Role::kCreateRole)) { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Role::kCreateRole), + command_name); + } + return {}; } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::DetachRole &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return checkAccountRolePermission( - creator_account_id, queries, Role::kDetachRole); + auto command_name = "DetachRole"; + if (not checkAccountRolePermission( + creator_account_id, queries, Role::kDetachRole)) { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Role::kDetachRole), + command_name); + } + return {}; } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::GrantPermission &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return checkAccountRolePermission( - creator_account_id, - queries, - shared_model::interface::permissions::permissionFor( - command.permissionName())); + auto command_name = "GrantPermission"; + if (not checkAccountRolePermission( + creator_account_id, + queries, + shared_model::interface::permissions::permissionFor( + command.permissionName()))) { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have grantable permission " + + toString(command.permissionName()) + " to grant", + command_name); + } + return {}; } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::RemoveSignatory &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return - // 1. Creator removes signatory from their account, and he must have - // permission on it - (creator_account_id == command.accountId() - and checkAccountRolePermission( - creator_account_id, queries, Role::kRemoveSignatory)) - // 2. Creator has granted permission on removal - or (queries.hasAccountGrantablePermission( - creator_account_id, - command.accountId(), - Grantable::kRemoveMySignatory)); + auto command_name = "RemoveSignatory"; + + auto creator_removes_signatory_from_his_account = [&creator_account_id, + &command]() { + return creator_account_id == command.accountId(); + }; + auto creator_has_permission_on_own = [&creator_account_id, &queries]() { + return checkAccountRolePermission( + creator_account_id, queries, Role::kRemoveSignatory); + }; + auto creator_has_grantable_permission = + [&creator_account_id, &command, &queries]() { + return queries.hasAccountGrantablePermission( + creator_account_id, + command.accountId(), + Grantable::kRemoveMySignatory); + }; + + if (creator_removes_signatory_from_his_account()) { + if (creator_has_permission_on_own()) { + // 1. Creator removes signatory from his account, and he has permission + // for it + return {}; + } else if (creator_has_grantable_permission()) { + // 2. Creator removes signatory from his account, and he has grantable + // permission for it + return {}; + } else { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Role::kRemoveSignatory) + " for his own account", + command_name); + } + } else if (creator_has_grantable_permission()) { + // 3. Creator removes signatory from another account, and he has grantable + // permission for it + return {}; + } else { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Grantable::kRemoveMySignatory) + " for account " + + command.accountId(), + command_name); + } } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::RevokePermission &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return queries.hasAccountGrantablePermission( - command.accountId(), creator_account_id, command.permissionName()); + auto command_name = "RevokePermission"; + if (not queries.hasAccountGrantablePermission(command.accountId(), + creator_account_id, + command.permissionName())) { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have grantable permission " + + toString(command.permissionName()) + " to revoke", + command_name); + } + return {}; } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::SetAccountDetail &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return - // Case 1. Creator set details for his account + auto command_name = "SetAccountDetail"; + + if ( + // Case 1. Creator sets details for his account creator_account_id == command.accountId() + // Case 2. Creator has permission to set account key/value or checkAccountRolePermission( creator_account_id, queries, Role::kSetDetail) - or - // Case 2. Creator has grantable permission to set account key/value - queries.hasAccountGrantablePermission(creator_account_id, - command.accountId(), - Grantable::kSetMyAccountDetail); + // Case 3. Creator has grantable permission to set account key/value + or queries.hasAccountGrantablePermission( + creator_account_id, + command.accountId(), + Grantable::kSetMyAccountDetail)) { + return {}; + } + + return makeCommandError("has permission command validation failed: account " + + creator_account_id + + " tries to set details for account " + + command.accountId() + ", but has neither " + + toString(Role::kSetDetail) + " nor grantable " + + toString(Grantable::kSetMyAccountDetail), + command_name); } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::SetQuorum &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return - // 1. Creator set quorum for his account -> must have permission - (creator_account_id == command.accountId() - and checkAccountRolePermission( - creator_account_id, queries, Role::kSetQuorum)) - // 2. Creator has granted permission on it - or (queries.hasAccountGrantablePermission(creator_account_id, - command.accountId(), - Grantable::kSetMyQuorum)); + auto command_name = "SetQuorum"; + + auto creator_sets_quorum_for_his_account = [&creator_account_id, + &command]() { + return creator_account_id == command.accountId(); + }; + auto creator_has_permission_on_own = [&creator_account_id, &queries]() { + return checkAccountRolePermission( + creator_account_id, queries, Role::kSetQuorum); + }; + auto creator_has_grantable_permission = + [&creator_account_id, &command, &queries]() { + return queries.hasAccountGrantablePermission( + creator_account_id, command.accountId(), Grantable::kSetMyQuorum); + }; + + if (creator_sets_quorum_for_his_account()) { + if (creator_has_permission_on_own()) { + // 1. Creator has permission quorum for his account + return {}; + } else if (creator_has_grantable_permission()) { + // 2. Creator has grantable permission for his account + return {}; + } else { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Role::kSetQuorum) + " for his own account", + command_name); + } + } else if (creator_has_grantable_permission()) { + // 3. Creator has grantable permission for another account + return {}; + } else { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Grantable::kSetMyQuorum) + " for account " + + command.accountId(), + command_name); + } } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::SubtractAssetQuantity &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return creator_account_id == command.accountId() - and checkAccountRolePermission( - creator_account_id, queries, Role::kSubtractAssetQty); + auto command_name = "SubtractAssetQuantity"; + if (checkAccountRolePermission( + creator_account_id, queries, Role::kSubtractAssetQty)) { + return {}; + } else { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have permission " + + toString(Role::kSubtractAssetQty) + " for his own account", + command_name); + } } - bool CommandValidator::hasPermissions( + CommandResult CommandValidator::hasPermissions( const shared_model::interface::TransferAsset &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return ( - // 1. Creator has granted permission on src_account_id - (creator_account_id != command.srcAccountId() - and queries.hasAccountGrantablePermission( - creator_account_id, - command.srcAccountId(), - Grantable::kTransferMyAssets)) - or - // 2. Creator transfer from their account - (creator_account_id == command.srcAccountId() - and checkAccountRolePermission( - creator_account_id, queries, Role::kTransfer))) - // For both cases, dest_account must have can_receive - and checkAccountRolePermission( - command.destAccountId(), queries, Role::kReceive); - } - - bool CommandValidator::isValid( + auto command_name = "TransferAsset"; + + auto creator_transfers_from_his_account = [&creator_account_id, + &command]() { + return creator_account_id == command.srcAccountId(); + }; + auto creator_has_permission_on_own = [&creator_account_id, &queries]() { + return checkAccountRolePermission( + creator_account_id, queries, Role::kTransfer); + }; + auto creator_has_grantable_permission = + [&creator_account_id, &command, &queries]() { + return queries.hasAccountGrantablePermission( + creator_account_id, + command.srcAccountId(), + Grantable::kTransferMyAssets); + }; + auto dest_can_receive = [&command, &queries]() { + return checkAccountRolePermission( + command.destAccountId(), queries, Role::kReceive); + }; + + if (dest_can_receive()) { + if (not creator_transfers_from_his_account()) { + if (creator_has_grantable_permission()) { + // 1. Creator has grantable permission on src_account_id + return {}; + } else { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have " + + toString(Grantable::kTransferMyAssets) + " for account " + + command.srcAccountId(), + command_name); + } + } else { + if (creator_has_permission_on_own()) { + // 2. Creator transfers from their account + return {}; + } else { + return makeCommandError( + "has permission command validation failed: account " + + creator_account_id + " does not have " + + toString(Role::kTransfer) + " for his own account", + command_name); + } + } + } else { + // For both cases, dest_account must have can_receive + return makeCommandError( + "has permission command validation failed: destination account " + + command.destAccountId() + " does not have " + + toString(Role::kReceive), + command_name); + } + } + + CommandResult CommandValidator::isValid( const shared_model::interface::AddAssetQuantity &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return true; + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::AddPeer &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return true; + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::AddSignatory &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return true; + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::AppendRole &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { + auto command_name = "AppendRole"; auto role_permissions = queries.getRolePermissions(command.roleName()); auto account_roles = queries.getAccountRoles(creator_account_id); - if (not role_permissions or not account_roles) { - return false; + if (not role_permissions) { + return makeCommandError( + "is valid command validation failed: no permissions in role " + + command.roleName(), + command_name); + } + if (not account_roles) { + return makeCommandError( + "is valid command validation failed: no roles in account " + + creator_account_id, + command_name); } shared_model::interface::RolePermissionSet account_permissions{}; @@ -712,133 +535,232 @@ namespace iroha { account_permissions |= *permissions; } - return role_permissions->isSubsetOf(account_permissions); + if (not role_permissions->isSubsetOf(account_permissions)) { + auto missing_permissions = ""s; + role_permissions.value().iterate( + [&account_permissions, &missing_permissions](const auto &perm) { + if (not account_permissions.test(perm)) { + missing_permissions += toString(perm) + ", "; + } + }); + + return makeCommandError( + "is valid command validation failed: account " + creator_account_id + + " does not have some of the permissions in a role " + + command.roleName() + + " he wants to append; such permissions are " + + missing_permissions, + command_name); + } + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::CreateAccount &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return true; + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::CreateAsset &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return true; + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::CreateDomain &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return true; + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::CreateRole &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { + auto command_name = "CreateRole"; auto set = command.rolePermissions(); + auto missing_permissions = ""s; for (size_t i = 0; i < set.size(); ++i) { auto perm = static_cast(i); if (set.test(perm) - && not checkAccountRolePermission( - creator_account_id, queries, perm)) { - return false; + and not checkAccountRolePermission( + creator_account_id, queries, perm)) { + missing_permissions += toString(perm) + ", "; } } - return true; + if (not missing_permissions.empty()) { + return makeCommandError( + "is valid command validation failed: account " + creator_account_id + + " does not have some of the permissions from a role " + + command.roleName() + + " he wants to create; such permissions are " + + missing_permissions, + command_name); + } + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::DetachRole &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return true; + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::GrantPermission &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return true; + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::RemoveSignatory &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { + auto command_name = "RemoveSignatory"; auto account = queries.getAccount(command.accountId()); auto signatories = queries.getSignatories(command.accountId()); - if (not(account and signatories)) { - // No account or signatories found - return false; + if (not account) { + return makeCommandError("is valid command validation failed: no account " + + command.accountId() + " found", + command_name); + } + if (not signatories) { + return makeCommandError( + "is valid command validation failed: no signatories in account " + + command.accountId() + " found", + command_name); } + auto newSignatoriesSize = signatories.value().size() - 1; // You can't remove if size of rest signatories less than the quorum - return newSignatoriesSize >= account.value()->quorum(); + if (newSignatoriesSize < account.value()->quorum()) { + return makeCommandError( + "is valid command validation failed: size of rest signatories " + "becomes less than the quorum; account id " + + command.accountId() + ", quorum " + + std::to_string(account.value()->quorum()) + + ", new desired size " + std::to_string(newSignatoriesSize), + command_name); + } + + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::RevokePermission &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return true; + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::SetAccountDetail &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return true; + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::SetQuorum &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { + auto command_name = "SetQuorum"; auto signatories = queries.getSignatories(command.accountId()); if (not(signatories)) { // No signatories of an account found - return false; + return makeCommandError( + "is valid command validation failed: no signatories of an account " + + command.accountId() + " found", + command_name); + } + if (command.newQuorum() <= 0 or command.newQuorum() >= 10) { + return makeCommandError( + "is valid command validation failed: account's " + + command.accountId() + + " new quorum size is " + "out of bounds; " + "value is " + std::to_string(command.newQuorum()), + command_name); } // You can't remove if size of rest signatories less than the quorum - return command.newQuorum() > 0 and command.newQuorum() < 10 - and signatories.value().size() >= command.newQuorum(); + if (signatories.value().size() < command.newQuorum()) { + return makeCommandError( + "is valid command validation failed: account's" + + command.accountId() + + " new quorum size " + "is greater than " + "the signatories amount; " + + std::to_string(command.newQuorum()) + + " vs " + std::to_string(signatories.value().size()), + command_name); + } + + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::SubtractAssetQuantity &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { - return true; + return {}; } - bool CommandValidator::isValid( + CommandResult CommandValidator::isValid( const shared_model::interface::TransferAsset &command, ametsuchi::WsvQuery &queries, const shared_model::interface::types::AccountIdType &creator_account_id) { + auto command_name = "TransferAsset"; auto asset = queries.getAsset(command.assetId()); if (not asset) { - return false; + return makeCommandError("is valid command validation failed: account " + + command.srcAccountId() + + ", no asset with such id " + + command.assetId(), + command_name); } // Amount is formed wrong if (command.amount().precision() > asset.value()->precision()) { - return false; + return makeCommandError( + "is valid command validation failed: account " + + command.srcAccountId() + + ", precision of command's " + "asset " + "amount is greater than the actual asset's one; " + std::to_string(command.amount().precision()) + " vs " + std::to_string(asset.value()->precision()), + command_name); } auto account_asset = queries.getAccountAsset(command.srcAccountId(), command.assetId()); if (not account_asset) { - return false; + return makeCommandError( + "is valid command validation failed: asset " + command.assetId() + + " does not exist on account " + command.srcAccountId(), + command_name); } // Check if dest account exist - return queries.getAccount(command.destAccountId()) and - // Balance in your wallet should be at least amount of transfer - compareAmount(account_asset.value()->balance(), command.amount()) >= 0; + if (not queries.getAccount(command.destAccountId())) { + return makeCommandError( + "is valid command validation failed: destination account with id " + + command.destAccountId() + " does not exist", + command_name); + } + // Balance in your wallet should be at least amount of transfer + if (compareAmount(account_asset.value()->balance(), command.amount()) < 0) { + return makeCommandError( + "is valid command validation failed: not enough " + "balance on account " + + command.srcAccountId() + "; transfer amount " + + command.amount().toStringRepr() + ", balance " + + account_asset.value()->balance().toStringRepr(), + command_name); + } + return {}; } } // namespace iroha diff --git a/irohad/execution/impl/query_execution.cpp b/irohad/execution/impl/query_execution_impl.cpp similarity index 57% rename from irohad/execution/impl/query_execution.cpp rename to irohad/execution/impl/query_execution_impl.cpp index 98fe5e629c..54eea0b3b3 100644 --- a/irohad/execution/impl/query_execution.cpp +++ b/irohad/execution/impl/query_execution_impl.cpp @@ -1,36 +1,26 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ -#include "execution/query_execution.hpp" +#include "execution/query_execution_impl.hpp" #include -#include "backend/protobuf/permissions.hpp" +#include "builders/protobuf/builder_templates/blocks_query_template.hpp" #include "execution/common_executor.hpp" +#include "interfaces/permissions.hpp" +#include "interfaces/queries/blocks_query.hpp" +#include "interfaces/queries/query.hpp" +#include "interfaces/query_responses/query_response.hpp" using namespace shared_model::interface::permissions; -using namespace shared_model::proto::permissions; using namespace iroha; using namespace iroha::ametsuchi; -QueryProcessingFactory::QueryProcessingFactory( - std::shared_ptr wsvQuery, - std::shared_ptr blockQuery) - : _wsvQuery(std::move(wsvQuery)), _blockQuery(std::move(blockQuery)) {} +QueryExecutionImpl::QueryExecutionImpl( + std::shared_ptr storage) + : storage_(storage) {} std::string getDomainFromName(const std::string &account_id) { std::vector res; @@ -59,139 +49,147 @@ shared_model::proto::TemplateQueryResponseBuilder<1> statefulFailed() { return buildError(); } -bool hasQueryPermission(const std::string &creator, - const std::string &target_account, - WsvQuery &wsv_query, - Role indiv_permission_id, - Role all_permission_id, - Role domain_permission_id) { +static bool hasQueryPermission(const std::string &creator, + const std::string &target_account, + WsvQuery &wsv_query, + Role indiv_permission_id, + Role all_permission_id, + Role domain_permission_id) { auto perms_set = iroha::getAccountPermissions(creator, wsv_query); - auto grantable = permissionOf(indiv_permission_id); - return - // 1. Creator has grant permission from other user - (creator != target_account - and wsv_query.hasAccountGrantablePermission( - creator, target_account, grantable)) - or // ----- Creator has role permission --------- - (perms_set - and ( - // 2. Creator want to query his account, must have role - // permission - (creator == target_account - and perms_set.value().test(indiv_permission_id)) - or // 3. Creator has global permission to get any account - perms_set.value().test(all_permission_id) - or // 4. Creator has domain permission - (getDomainFromName(creator) == getDomainFromName(target_account) - and perms_set.value().test(domain_permission_id)))); + if (not perms_set) { + return false; + } + + auto &set = perms_set.value(); + // Creator want to query his account, must have role + // permission + return (creator == target_account and set.test(indiv_permission_id)) or + // Creator has global permission to get any account + set.test(all_permission_id) or + // Creator has domain permission + (getDomainFromName(creator) == getDomainFromName(target_account) + and set.test(domain_permission_id)); } -bool QueryProcessingFactory::validate( + +bool QueryExecutionImpl::validate( const shared_model::interface::BlocksQuery &query) { return checkAccountRolePermission( - query.creatorAccountId(), *_wsvQuery, Role::kGetBlocks); + query.creatorAccountId(), *storage_->getWsvQuery(), Role::kGetBlocks); } -bool QueryProcessingFactory::validate( +bool QueryExecutionImpl::validate( + ametsuchi::WsvQuery &wq, const shared_model::interface::Query &query, const shared_model::interface::GetAssetInfo &get_asset_info) { return checkAccountRolePermission( - query.creatorAccountId(), *_wsvQuery, Role::kReadAssets); + query.creatorAccountId(), wq, Role::kReadAssets); } -bool QueryProcessingFactory::validate( +bool QueryExecutionImpl::validate( + ametsuchi::WsvQuery &wq, const shared_model::interface::Query &query, const shared_model::interface::GetRoles &get_roles) { return checkAccountRolePermission( - query.creatorAccountId(), *_wsvQuery, Role::kGetRoles); + query.creatorAccountId(), wq, Role::kGetRoles); } -bool QueryProcessingFactory::validate( +bool QueryExecutionImpl::validate( + ametsuchi::WsvQuery &wq, const shared_model::interface::Query &query, const shared_model::interface::GetRolePermissions &get_role_permissions) { return checkAccountRolePermission( - query.creatorAccountId(), *_wsvQuery, Role::kGetRoles); + query.creatorAccountId(), wq, Role::kGetRoles); } -bool QueryProcessingFactory::validate( +bool QueryExecutionImpl::validate( + ametsuchi::WsvQuery &wq, const shared_model::interface::Query &query, const shared_model::interface::GetAccount &get_account) { return hasQueryPermission(query.creatorAccountId(), get_account.accountId(), - *_wsvQuery, + wq, Role::kGetMyAccount, Role::kGetAllAccounts, Role::kGetDomainAccounts); } -bool QueryProcessingFactory::validate( +bool QueryExecutionImpl::validate( + ametsuchi::WsvQuery &wq, const shared_model::interface::Query &query, const shared_model::interface::GetSignatories &get_signatories) { return hasQueryPermission(query.creatorAccountId(), get_signatories.accountId(), - *_wsvQuery, + wq, Role::kGetMySignatories, Role::kGetAllSignatories, Role::kGetDomainSignatories); } -bool QueryProcessingFactory::validate( +bool QueryExecutionImpl::validate( + ametsuchi::WsvQuery &wq, const shared_model::interface::Query &query, const shared_model::interface::GetAccountAssets &get_account_assets) { return hasQueryPermission(query.creatorAccountId(), get_account_assets.accountId(), - *_wsvQuery, + wq, Role::kGetMyAccAst, Role::kGetAllAccAst, Role::kGetDomainAccAst); } -bool QueryProcessingFactory::validate( +bool QueryExecutionImpl::validate( + ametsuchi::WsvQuery &wq, const shared_model::interface::Query &query, const shared_model::interface::GetAccountDetail &get_account_detail) { return hasQueryPermission(query.creatorAccountId(), get_account_detail.accountId(), - *_wsvQuery, + wq, Role::kGetMyAccDetail, Role::kGetAllAccDetail, Role::kGetDomainAccDetail); } -bool QueryProcessingFactory::validate( +bool QueryExecutionImpl::validate( + ametsuchi::WsvQuery &wq, const shared_model::interface::Query &query, const shared_model::interface::GetAccountTransactions &get_account_transactions) { return hasQueryPermission(query.creatorAccountId(), get_account_transactions.accountId(), - *_wsvQuery, + wq, Role::kGetMyAccTxs, Role::kGetAllAccTxs, Role::kGetDomainAccTxs); } -bool QueryProcessingFactory::validate( +bool QueryExecutionImpl::validate( + ametsuchi::WsvQuery &wq, const shared_model::interface::Query &query, const shared_model::interface::GetAccountAssetTransactions &get_account_asset_transactions) { return hasQueryPermission(query.creatorAccountId(), get_account_asset_transactions.accountId(), - *_wsvQuery, + wq, Role::kGetMyAccAstTxs, Role::kGetAllAccAstTxs, Role::kGetDomainAccAstTxs); } -bool QueryProcessingFactory::validate( +bool QueryExecutionImpl::validate( + ametsuchi::WsvQuery &wq, const shared_model::interface::Query &query, const shared_model::interface::GetTransactions &get_transactions) { return checkAccountRolePermission( - query.creatorAccountId(), *_wsvQuery, Role::kGetMyTxs) + query.creatorAccountId(), wq, Role::kGetMyTxs) or checkAccountRolePermission( - query.creatorAccountId(), *_wsvQuery, Role::kGetAllTxs); + query.creatorAccountId(), wq, Role::kGetAllTxs); } -QueryProcessingFactory::QueryResponseBuilderDone -QueryProcessingFactory::executeGetAssetInfo( +QueryExecutionImpl::QueryResponseBuilderDone +QueryExecutionImpl::executeGetAssetInfo( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &, const shared_model::interface::GetAssetInfo &query) { - auto ast = _wsvQuery->getAsset(query.assetId()); + auto ast = wq.getAsset(query.assetId()); if (not ast) { return buildError(); @@ -203,10 +201,12 @@ QueryProcessingFactory::executeGetAssetInfo( return response; } -QueryProcessingFactory::QueryResponseBuilderDone -QueryProcessingFactory::executeGetRoles( +QueryExecutionImpl::QueryResponseBuilderDone +QueryExecutionImpl::executeGetRoles( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &, const shared_model::interface::GetRoles &queryQueryResponseBuilder) { - auto roles = _wsvQuery->getRoles(); + auto roles = wq.getRoles(); if (not roles) { return buildError(); } @@ -214,10 +214,12 @@ QueryProcessingFactory::executeGetRoles( return response; } -QueryProcessingFactory::QueryResponseBuilderDone -QueryProcessingFactory::executeGetRolePermissions( +QueryExecutionImpl::QueryResponseBuilderDone +QueryExecutionImpl::executeGetRolePermissions( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &, const shared_model::interface::GetRolePermissions &query) { - auto perm = _wsvQuery->getRolePermissions(query.roleId()); + auto perm = wq.getRolePermissions(query.roleId()); if (not perm) { return buildError(); } @@ -226,12 +228,14 @@ QueryProcessingFactory::executeGetRolePermissions( return response; } -QueryProcessingFactory::QueryResponseBuilderDone -QueryProcessingFactory::executeGetAccount( +QueryExecutionImpl::QueryResponseBuilderDone +QueryExecutionImpl::executeGetAccount( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &, const shared_model::interface::GetAccount &query) { - auto acc = _wsvQuery->getAccount(query.accountId()); + auto acc = wq.getAccount(query.accountId()); - auto roles = _wsvQuery->getAccountRoles(query.accountId()); + auto roles = wq.getAccountRoles(query.accountId()); if (not acc or not roles) { return buildError(); } @@ -241,10 +245,12 @@ QueryProcessingFactory::executeGetAccount( return response; } -QueryProcessingFactory::QueryResponseBuilderDone -QueryProcessingFactory::executeGetAccountAssets( +QueryExecutionImpl::QueryResponseBuilderDone +QueryExecutionImpl::executeGetAccountAssets( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &, const shared_model::interface::GetAccountAssets &query) { - auto acct_assets = _wsvQuery->getAccountAssets(query.accountId()); + auto acct_assets = wq.getAccountAssets(query.accountId()); if (not acct_assets) { return buildError(); @@ -260,10 +266,14 @@ QueryProcessingFactory::executeGetAccountAssets( return response; } -QueryProcessingFactory::QueryResponseBuilderDone -QueryProcessingFactory::executeGetAccountDetail( +QueryExecutionImpl::QueryResponseBuilderDone +QueryExecutionImpl::executeGetAccountDetail( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &, const shared_model::interface::GetAccountDetail &query) { - auto acct_detail = _wsvQuery->getAccountDetail(query.accountId()); + auto acct_detail = wq.getAccountDetail(query.accountId(), + query.key() ? *query.key() : "", + query.writer() ? *query.writer() : ""); if (not acct_detail) { return buildError(); } @@ -271,49 +281,61 @@ QueryProcessingFactory::executeGetAccountDetail( return response; } -QueryProcessingFactory::QueryResponseBuilderDone -QueryProcessingFactory::executeGetAccountAssetTransactions( +QueryExecutionImpl::QueryResponseBuilderDone +QueryExecutionImpl::executeGetAccountAssetTransactions( + ametsuchi::WsvQuery &, + ametsuchi::BlockQuery &bq, const shared_model::interface::GetAccountAssetTransactions &query) { - auto acc_asset_tx = _blockQuery->getAccountAssetTransactions( - query.accountId(), query.assetId()); + auto acc_asset_tx = + bq.getAccountAssetTransactions(query.accountId(), query.assetId()); std::vector txs; - acc_asset_tx.subscribe([&](const auto &tx) { - txs.push_back( - *std::static_pointer_cast(tx)); - }); + std::transform( + acc_asset_tx.begin(), + acc_asset_tx.end(), + std::back_inserter(txs), + [](const auto &tx) { + return *std::static_pointer_cast(tx); + }); auto response = QueryResponseBuilder().transactionsResponse(txs); return response; } -QueryProcessingFactory::QueryResponseBuilderDone -QueryProcessingFactory::executeGetAccountTransactions( +QueryExecutionImpl::QueryResponseBuilderDone +QueryExecutionImpl::executeGetAccountTransactions( + ametsuchi::WsvQuery &, + ametsuchi::BlockQuery &bq, const shared_model::interface::GetAccountTransactions &query) { - auto acc_tx = _blockQuery->getAccountTransactions(query.accountId()); + auto acc_tx = bq.getAccountTransactions(query.accountId()); std::vector txs; - acc_tx.subscribe([&](const auto &tx) { - txs.push_back( - *std::static_pointer_cast(tx)); - }); + std::transform( + acc_tx.begin(), + acc_tx.end(), + std::back_inserter(txs), + [](const auto &tx) { + return *std::static_pointer_cast(tx); + }); auto response = QueryResponseBuilder().transactionsResponse(txs); return response; } -QueryProcessingFactory::QueryResponseBuilderDone -QueryProcessingFactory::executeGetTransactions( +QueryExecutionImpl::QueryResponseBuilderDone +QueryExecutionImpl::executeGetTransactions( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &bq, const shared_model::interface::GetTransactions &q, const shared_model::interface::types::AccountIdType &accountId) { const std::vector &hashes = q.transactionHashes(); - auto transactions = _blockQuery->getTransactions(hashes); + auto transactions = bq.getTransactions(hashes); std::vector txs; bool can_get_all = - checkAccountRolePermission(accountId, *_wsvQuery, Role::kGetAllTxs); - transactions.subscribe([&](const auto &tx) { + checkAccountRolePermission(accountId, wq, Role::kGetAllTxs); + std::for_each(transactions.begin(), transactions.end(), [&](const auto &tx) { if (tx) { auto proto_tx = *std::static_pointer_cast(*tx); @@ -326,103 +348,127 @@ QueryProcessingFactory::executeGetTransactions( return response; } -QueryProcessingFactory::QueryResponseBuilderDone -QueryProcessingFactory::executeGetSignatories( +QueryExecutionImpl::QueryResponseBuilderDone +QueryExecutionImpl::executeGetSignatories( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &, const shared_model::interface::GetSignatories &query) { - auto signs = _wsvQuery->getSignatories(query.accountId()); + auto signs = wq.getSignatories(query.accountId()); if (not signs) { return buildError(); } auto response = QueryResponseBuilder().signatoriesResponse(*signs); return response; } -std::shared_ptr -QueryProcessingFactory::validateAndExecute( + +QueryExecutionImpl::QueryResponseBuilderDone +QueryExecutionImpl::executeGetPendingTransactions( + ametsuchi::WsvQuery &, + ametsuchi::BlockQuery &, + const shared_model::interface::GetPendingTransactions &query, + const shared_model::interface::types::AccountIdType &query_creator) { + std::vector txs; + // TODO 2018-07-04, igor-egorov, IR-1486, the core logic is to be implemented + auto response = QueryResponseBuilder().transactionsResponse(txs); + return response; +} + +std::unique_ptr +QueryExecutionImpl::validateAndExecute( const shared_model::interface::Query &query) { const auto &query_hash = query.hash(); QueryResponseBuilderDone builder; + auto wq = storage_->getWsvQuery(); + auto bq = storage_->getBlockQuery(); // TODO: 29/04/2018 x3medima18, Add visitor class, IR-1185 return visit_in_place( query.get(), [&](const shared_model::interface::GetAccount &q) { - if (not validate(query, q)) { + if (not validate(*wq, query, q)) { builder = statefulFailed(); } else { - builder = executeGetAccount(q); + builder = executeGetAccount(*wq, *bq, q); } return clone(builder.queryHash(query_hash).build()); }, [&](const shared_model::interface::GetSignatories &q) { - if (not validate(query, q)) { + if (not validate(*wq, query, q)) { builder = statefulFailed(); } else { - builder = executeGetSignatories(q); + builder = executeGetSignatories(*wq, *bq, q); } return clone(builder.queryHash(query_hash).build()); }, [&](const shared_model::interface::GetAccountTransactions &q) { - if (not validate(query, q)) { + if (not validate(*wq, query, q)) { builder = statefulFailed(); } else { - builder = executeGetAccountTransactions(q); + builder = executeGetAccountTransactions(*wq, *bq, q); } return clone(builder.queryHash(query_hash).build()); }, [&](const shared_model::interface::GetTransactions &q) { - if (not validate(query, q)) { + if (not validate(*wq, query, q)) { builder = statefulFailed(); } else { - builder = executeGetTransactions(q, query.creatorAccountId()); + builder = + executeGetTransactions(*wq, *bq, q, query.creatorAccountId()); } return clone(builder.queryHash(query_hash).build()); }, [&](const shared_model::interface::GetAccountAssetTransactions &q) { - if (not validate(query, q)) { + if (not validate(*wq, query, q)) { builder = statefulFailed(); } else { - builder = executeGetAccountAssetTransactions(q); + builder = executeGetAccountAssetTransactions(*wq, *bq, q); } return clone(builder.queryHash(query_hash).build()); }, [&](const shared_model::interface::GetAccountAssets &q) { - if (not validate(query, q)) { + if (not validate(*wq, query, q)) { builder = statefulFailed(); } else { - builder = executeGetAccountAssets(q); + builder = executeGetAccountAssets(*wq, *bq, q); } return clone(builder.queryHash(query_hash).build()); }, [&](const shared_model::interface::GetAccountDetail &q) { - if (not validate(query, q)) { + if (not validate(*wq, query, q)) { builder = statefulFailed(); } else { - builder = executeGetAccountDetail(q); + builder = executeGetAccountDetail(*wq, *bq, q); } return clone(builder.queryHash(query_hash).build()); }, [&](const shared_model::interface::GetRoles &q) { - if (not validate(query, q)) { + if (not validate(*wq, query, q)) { builder = statefulFailed(); } else { - builder = executeGetRoles(q); + builder = executeGetRoles(*wq, *bq, q); } return clone(builder.queryHash(query_hash).build()); }, [&](const shared_model::interface::GetRolePermissions &q) { - if (not validate(query, q)) { + if (not validate(*wq, query, q)) { builder = statefulFailed(); } else { - builder = executeGetRolePermissions(q); + builder = executeGetRolePermissions(*wq, *bq, q); } return clone(builder.queryHash(query_hash).build()); }, [&](const shared_model::interface::GetAssetInfo &q) { - if (not validate(query, q)) { + if (not validate(*wq, query, q)) { builder = statefulFailed(); } else { - builder = executeGetAssetInfo(q); + builder = executeGetAssetInfo(*wq, *bq, q); } return clone(builder.queryHash(query_hash).build()); + }, + [&](const shared_model::interface::GetPendingTransactions &q) { + // the query does not require validation + builder = executeGetPendingTransactions( + *wq, *bq, q, query.creatorAccountId()); + return clone(builder.queryHash(query_hash).build()); } ); diff --git a/irohad/execution/query_execution.hpp b/irohad/execution/query_execution.hpp index 60366bb4d4..62921be970 100644 --- a/irohad/execution/query_execution.hpp +++ b/irohad/execution/query_execution.hpp @@ -1,139 +1,43 @@ /** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ + #ifndef IROHA_QUERY_EXECUTION_HPP #define IROHA_QUERY_EXECUTION_HPP -#include "ametsuchi/block_query.hpp" -#include "ametsuchi/wsv_query.hpp" -#include "builders/protobuf/builder_templates/query_response_template.hpp" -#include "builders/protobuf/builder_templates/blocks_query_template.hpp" -#include "interfaces/query_responses/block_query_response.hpp" +#include namespace shared_model { namespace interface { - class QueryResponse; class Query; + class BlocksQuery; + class QueryResponse; } // namespace interface } // namespace shared_model namespace iroha { - /** - * Converting model objects to protobuf and vice versa - */ - class QueryProcessingFactory { - using QueryResponseBuilder = - shared_model::proto::TemplateQueryResponseBuilder<0>; - - using QueryResponseBuilderDone = - shared_model::proto::TemplateQueryResponseBuilder<1>; - + class QueryExecution { public: + virtual ~QueryExecution() = default; /** * Execute and validate query. * * @param query - * @return shared pointer to query response + * @return query response */ - std::shared_ptr validateAndExecute( - const shared_model::interface::Query &query); + virtual std::unique_ptr + validateAndExecute(const shared_model::interface::Query &query) = 0; /** - * - * @param wsvQuery - * @param blockQuery + * Perform BlocksQuery validation + * @param query to validate + * @return true if valid, false otherwise */ - QueryProcessingFactory(std::shared_ptr wsvQuery, - std::shared_ptr blockQuery); - bool validate(const shared_model::interface::BlocksQuery &query); - private: - - bool validate(const shared_model::interface::Query &query, - const shared_model::interface::GetAssetInfo &get_asset_info); - - bool validate(const shared_model::interface::Query &query, - const shared_model::interface::GetRoles &get_roles); - - bool validate(const shared_model::interface::Query &query, - const shared_model::interface::GetRolePermissions - &get_role_permissions); - - bool validate( - const shared_model::interface::Query &query, - const shared_model::interface::GetAccountAssets &get_account_assets); - - bool validate(const shared_model::interface::Query &query, - const shared_model::interface::GetAccount &get_account); - - bool validate( - const shared_model::interface::Query &query, - const shared_model::interface::GetSignatories &get_signatories); - - bool validate(const shared_model::interface::Query &query, - const shared_model::interface::GetAccountTransactions - &get_account_transactions); - - bool validate(const shared_model::interface::Query &query, - const shared_model::interface::GetAccountAssetTransactions - &get_account_asset_transactions); - - bool validate( - const shared_model::interface::Query &query, - const shared_model::interface::GetAccountDetail &get_account_detail); - - bool validate( - const shared_model::interface::Query &query, - const shared_model::interface::GetTransactions &get_transactions); - - QueryResponseBuilderDone executeGetAssetInfo( - const shared_model::interface::GetAssetInfo &get_asset_info); - - QueryResponseBuilderDone executeGetRoles( - const shared_model::interface::GetRoles &query); - - QueryResponseBuilderDone executeGetRolePermissions( - const shared_model::interface::GetRolePermissions &query); - - QueryResponseBuilderDone executeGetAccountAssets( - const shared_model::interface::GetAccountAssets &query); - - QueryResponseBuilderDone executeGetAccountDetail( - const shared_model::interface::GetAccountDetail &query); - - QueryResponseBuilderDone executeGetAccount( - const shared_model::interface::GetAccount &query); - - QueryResponseBuilderDone executeGetSignatories( - const shared_model::interface::GetSignatories &query); - - QueryResponseBuilderDone executeGetAccountAssetTransactions( - const shared_model::interface::GetAccountAssetTransactions &query); - - QueryResponseBuilderDone executeGetAccountTransactions( - const shared_model::interface::GetAccountTransactions &query); - - QueryResponseBuilderDone executeGetTransactions( - const shared_model::interface::GetTransactions &query, - const shared_model::interface::types::AccountIdType &accountId); - - std::shared_ptr _wsvQuery; - std::shared_ptr _blockQuery; + virtual bool validate( + const shared_model::interface::BlocksQuery &query) = 0; }; - } // namespace iroha #endif // IROHA_QUERY_EXECUTION_HPP diff --git a/irohad/execution/query_execution_impl.hpp b/irohad/execution/query_execution_impl.hpp new file mode 100644 index 0000000000..e556a2811a --- /dev/null +++ b/irohad/execution/query_execution_impl.hpp @@ -0,0 +1,143 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_QUERY_EXECUTION_IMPL_HPP +#define IROHA_QUERY_EXECUTION_IMPL_HPP + +#include "execution/query_execution.hpp" + +#include "ametsuchi/block_query.hpp" +#include "ametsuchi/storage.hpp" +#include "ametsuchi/wsv_query.hpp" +#include "builders/protobuf/builder_templates/query_response_template.hpp" +#include "interfaces/common_objects/types.hpp" + +namespace iroha { + + class QueryExecutionImpl : public QueryExecution { + using QueryResponseBuilder = + shared_model::proto::TemplateQueryResponseBuilder<0>; + + using QueryResponseBuilderDone = + shared_model::proto::TemplateQueryResponseBuilder<1>; + + public: + explicit QueryExecutionImpl(std::shared_ptr storage); + + std::unique_ptr validateAndExecute( + const shared_model::interface::Query &query) override; + bool validate(const shared_model::interface::BlocksQuery &query) override; + + private: + bool validate(ametsuchi::WsvQuery &wq, + const shared_model::interface::Query &query, + const shared_model::interface::GetAssetInfo &get_asset_info); + + bool validate(ametsuchi::WsvQuery &wq, + const shared_model::interface::Query &query, + const shared_model::interface::GetRoles &get_roles); + + bool validate(ametsuchi::WsvQuery &wq, + const shared_model::interface::Query &query, + const shared_model::interface::GetRolePermissions + &get_role_permissions); + + bool validate( + ametsuchi::WsvQuery &wq, + const shared_model::interface::Query &query, + const shared_model::interface::GetAccountAssets &get_account_assets); + + bool validate(ametsuchi::WsvQuery &wq, + const shared_model::interface::Query &query, + const shared_model::interface::GetAccount &get_account); + + bool validate( + ametsuchi::WsvQuery &wq, + const shared_model::interface::Query &query, + const shared_model::interface::GetSignatories &get_signatories); + + bool validate(ametsuchi::WsvQuery &wq, + const shared_model::interface::Query &query, + const shared_model::interface::GetAccountTransactions + &get_account_transactions); + + bool validate(ametsuchi::WsvQuery &wq, + const shared_model::interface::Query &query, + const shared_model::interface::GetAccountAssetTransactions + &get_account_asset_transactions); + + bool validate( + ametsuchi::WsvQuery &wq, + const shared_model::interface::Query &query, + const shared_model::interface::GetAccountDetail &get_account_detail); + + bool validate( + ametsuchi::WsvQuery &wq, + const shared_model::interface::Query &query, + const shared_model::interface::GetTransactions &get_transactions); + + QueryResponseBuilderDone executeGetAssetInfo( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &bq, + const shared_model::interface::GetAssetInfo &get_asset_info); + + QueryResponseBuilderDone executeGetRoles( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &bq, + const shared_model::interface::GetRoles &query); + + QueryResponseBuilderDone executeGetRolePermissions( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &bq, + const shared_model::interface::GetRolePermissions &query); + + QueryResponseBuilderDone executeGetAccountAssets( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &bq, + const shared_model::interface::GetAccountAssets &query); + + QueryResponseBuilderDone executeGetAccountDetail( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &bq, + const shared_model::interface::GetAccountDetail &query); + + QueryResponseBuilderDone executeGetAccount( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &bq, + const shared_model::interface::GetAccount &query); + + QueryResponseBuilderDone executeGetSignatories( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &bq, + const shared_model::interface::GetSignatories &query); + + QueryResponseBuilderDone executeGetAccountAssetTransactions( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &bq, + const shared_model::interface::GetAccountAssetTransactions &query); + + QueryResponseBuilderDone executeGetAccountTransactions( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &bq, + const shared_model::interface::GetAccountTransactions &query); + + QueryResponseBuilderDone executeGetTransactions( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &bq, + const shared_model::interface::GetTransactions &query, + const shared_model::interface::types::AccountIdType &accountId); + + QueryResponseBuilderDone executeGetPendingTransactions( + ametsuchi::WsvQuery &wq, + ametsuchi::BlockQuery &bq, + const shared_model::interface::GetPendingTransactions &query, + const shared_model::interface::types::AccountIdType &query_creator); + + std::shared_ptr storage_; + }; + +} // namespace iroha + +#endif // IROHA_QUERY_EXECUTION_IMPL_HPP diff --git a/irohad/main/CMakeLists.txt b/irohad/main/CMakeLists.txt index 9590c017a9..76f7f73b67 100644 --- a/irohad/main/CMakeLists.txt +++ b/irohad/main/CMakeLists.txt @@ -28,6 +28,7 @@ add_library(raw_block_loader impl/raw_block_loader.cpp) target_link_libraries(raw_block_loader shared_model_interfaces shared_model_proto_backend + shared_model_stateless_validation logger ) diff --git a/irohad/main/application.cpp b/irohad/main/application.cpp index 210428a2c9..95e3d6e56a 100644 --- a/irohad/main/application.cpp +++ b/irohad/main/application.cpp @@ -1,30 +1,22 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include "main/application.hpp" #include "ametsuchi/impl/postgres_ordering_service_persistent_state.hpp" #include "ametsuchi/impl/wsv_restorer_impl.hpp" +#include "backend/protobuf/common_objects/proto_common_objects_factory.hpp" #include "consensus/yac/impl/supermajority_checker_impl.hpp" +#include "execution/query_execution_impl.hpp" #include "multi_sig_transactions/gossip_propagation_strategy.hpp" #include "multi_sig_transactions/mst_processor_impl.hpp" #include "multi_sig_transactions/mst_processor_stub.hpp" #include "multi_sig_transactions/mst_time_provider_impl.hpp" #include "multi_sig_transactions/storage/mst_storage_impl.hpp" #include "multi_sig_transactions/transport/mst_transport_grpc.hpp" +#include "torii/impl/status_bus_impl.hpp" +#include "validators/field_validator.hpp" using namespace iroha; using namespace iroha::ametsuchi; @@ -75,7 +67,6 @@ void Irohad::init() { initWsvRestorer(); restoreWsv(); - initPeerQuery(); initCryptoProvider(); initValidators(); initOrderingGate(); @@ -84,6 +75,7 @@ void Irohad::init() { initConsensusGate(); initSynchronizer(); initPeerCommunicationService(); + initStatusBus(); initMstProcessor(); // Torii @@ -95,7 +87,7 @@ void Irohad::init() { * Dropping iroha daemon storage */ void Irohad::dropStorage() { - storage->dropStorage(); + storage->reset(); ordering_service_storage_->resetState(); } @@ -103,7 +95,10 @@ void Irohad::dropStorage() { * Initializing iroha daemon storage */ void Irohad::initStorage() { - auto storageResult = StorageImpl::create(block_store_dir_, pg_conn_); + auto factory = + std::make_shared>(); + auto storageResult = StorageImpl::create(block_store_dir_, pg_conn_, factory); storageResult.match( [&](expected::Value> &_storage) { storage = _storage.value; @@ -154,7 +149,10 @@ void Irohad::initCryptoProvider() { * Initializing validators */ void Irohad::initValidators() { - stateful_validator = std::make_shared(); + auto factory = std::make_unique>(); + stateful_validator = + std::make_shared(std::move(factory)); chain_validator = std::make_shared( std::make_shared()); @@ -225,8 +223,8 @@ void Irohad::initSynchronizer() { * Initializing peer communication service */ void Irohad::initPeerCommunicationService() { - pcs = std::make_shared(ordering_gate, - synchronizer); + pcs = std::make_shared( + ordering_gate, synchronizer, simulator); pcs->on_proposal().subscribe( [this](auto) { log_->info("~~~~~~~~~| PROPOSAL ^_^ |~~~~~~~~~ "); }); @@ -240,6 +238,11 @@ void Irohad::initPeerCommunicationService() { log_->info("[Init] => pcs"); } +void Irohad::initStatusBus() { + status_bus_ = std::make_shared(); + log_->info("[Init] => Tx status bus"); +} + void Irohad::initMstProcessor() { if (is_mst_supported_) { auto mst_transport = std::make_shared(); @@ -264,11 +267,15 @@ void Irohad::initMstProcessor() { * Initializing transaction command service */ void Irohad::initTransactionCommandService() { - auto tx_processor = - std::make_shared(pcs, mst_processor); + auto tx_processor = std::make_shared( + pcs, mst_processor, status_bus_); - command_service = std::make_shared<::torii::CommandService>( - tx_processor, storage, proposal_delay_); + command_service = + std::make_shared<::torii::CommandService>(tx_processor, + storage, + status_bus_, + std::chrono::seconds(1), + 2 * proposal_delay_); log_->info("[Init] => command service"); } @@ -277,7 +284,8 @@ void Irohad::initTransactionCommandService() { * Initializing query command service */ void Irohad::initQueryService() { - auto query_processor = std::make_shared(storage); + auto query_processor = std::make_shared( + storage, std::make_unique(storage)); query_service = std::make_shared<::torii::QueryService>(query_processor); diff --git a/irohad/main/application.hpp b/irohad/main/application.hpp index bcd4e99429..9cafb7ca8d 100644 --- a/irohad/main/application.hpp +++ b/irohad/main/application.hpp @@ -133,6 +133,8 @@ class Irohad { virtual void initPeerCommunicationService(); + virtual void initStatusBus(); + virtual void initMstProcessor(); virtual void initTransactionCommandService(); @@ -188,6 +190,8 @@ class Irohad { // mst std::shared_ptr mst_processor; + std::shared_ptr status_bus_; + // transaction service std::shared_ptr command_service; diff --git a/irohad/main/impl/block_loader_init.cpp b/irohad/main/impl/block_loader_init.cpp index 7a29000af9..d4afd069d8 100644 --- a/irohad/main/impl/block_loader_init.cpp +++ b/irohad/main/impl/block_loader_init.cpp @@ -28,10 +28,7 @@ auto BlockLoaderInit::createService(std::shared_ptr storage) { auto BlockLoaderInit::createLoader(std::shared_ptr peer_query, std::shared_ptr storage) { - return std::make_shared( - peer_query, - storage, - std::make_shared()); + return std::make_shared(peer_query, storage); } std::shared_ptr BlockLoaderInit::initBlockLoader( diff --git a/irohad/main/impl/ordering_init.cpp b/irohad/main/impl/ordering_init.cpp index 12ce15790b..3c47aea65b 100644 --- a/irohad/main/impl/ordering_init.cpp +++ b/irohad/main/impl/ordering_init.cpp @@ -40,13 +40,16 @@ namespace iroha { std::shared_ptr transport, std::shared_ptr persistent_state) { + auto factory = std::make_unique>(); return std::make_shared( wsv, max_size, rxcpp::observable<>::interval(delay_milliseconds, rxcpp::observe_on_new_thread()), transport, - persistent_state); + persistent_state, + std::move(factory)); } std::shared_ptr OrderingInit::initOrderingGate( diff --git a/irohad/model/CMakeLists.txt b/irohad/model/CMakeLists.txt index 77c02c7715..5992b853fe 100644 --- a/irohad/model/CMakeLists.txt +++ b/irohad/model/CMakeLists.txt @@ -36,7 +36,6 @@ target_link_libraries(model sha3_hash rxcpp logger - iroha_amount common_execution schema ed25519_crypto diff --git a/irohad/model/account_asset.hpp b/irohad/model/account_asset.hpp index f01a4295ba..cbb7b5396e 100644 --- a/irohad/model/account_asset.hpp +++ b/irohad/model/account_asset.hpp @@ -19,7 +19,6 @@ #define IROHA_ACCOUNT_ASSET_HPP #include -#include "amount/amount.hpp" namespace iroha { namespace model { @@ -31,7 +30,7 @@ namespace iroha { AccountAsset(const std::string &asset_id, const std::string &account_id, - const Amount &balance) + const std::string &balance) : asset_id(asset_id), account_id(account_id), balance(balance) {} /** @@ -47,7 +46,7 @@ namespace iroha { /** * Current balance */ - Amount balance; + std::string balance; }; } // namespace model } // namespace iroha diff --git a/irohad/model/commands/add_asset_quantity.hpp b/irohad/model/commands/add_asset_quantity.hpp index 6b3c3e68b8..578aeec104 100644 --- a/irohad/model/commands/add_asset_quantity.hpp +++ b/irohad/model/commands/add_asset_quantity.hpp @@ -19,7 +19,6 @@ #define IROHA_ADD_ASSET_QUANTITY_HPP #include -#include "amount/amount.hpp" #include "model/command.hpp" namespace iroha { @@ -29,11 +28,6 @@ namespace iroha { * Add amount of asset to an account */ struct AddAssetQuantity : public Command { - /** - * Account where to add assets - */ - std::string account_id; - /** * Asset to issue * Note: must exist in the system @@ -43,16 +37,14 @@ namespace iroha { /** * Amount to add to account asset */ - Amount amount; + std::string amount; bool operator==(const Command &command) const override; AddAssetQuantity() {} - AddAssetQuantity(const std::string &account_id, - const std::string &asset_id, - Amount amount) - : account_id(account_id), asset_id(asset_id), amount(amount) {} + AddAssetQuantity(const std::string &asset_id, const std::string &amount) + : asset_id(asset_id), amount(amount) {} }; } // namespace model } // namespace iroha diff --git a/irohad/model/commands/subtract_asset_quantity.hpp b/irohad/model/commands/subtract_asset_quantity.hpp index 401cdc1c3d..8aec619e00 100644 --- a/irohad/model/commands/subtract_asset_quantity.hpp +++ b/irohad/model/commands/subtract_asset_quantity.hpp @@ -18,7 +18,6 @@ #define IROHA_SUBTRACT_ASSET_QUANTITY_HPP #include -#include "amount/amount.hpp" #include "model/command.hpp" namespace iroha { @@ -28,11 +27,6 @@ namespace iroha { * Subtract amount of asset to an account */ struct SubtractAssetQuantity : public Command { - /** - * Account where to subtract assets - */ - std::string account_id; - /** * Asset to issue * Note: must exist in the system @@ -42,16 +36,15 @@ namespace iroha { /** * Amount to add to account asset */ - Amount amount; + std::string amount; bool operator==(const Command &command) const override; SubtractAssetQuantity() {} - SubtractAssetQuantity(const std::string &account_id, - const std::string &asset_id, - Amount amount) - : account_id(account_id), asset_id(asset_id), amount(amount) {} + SubtractAssetQuantity(const std::string &asset_id, + const std::string &amount) + : asset_id(asset_id), amount(amount) {} }; } // namespace model } // namespace iroha diff --git a/irohad/model/commands/transfer_asset.hpp b/irohad/model/commands/transfer_asset.hpp index 2d06653c3d..38683b9377 100644 --- a/irohad/model/commands/transfer_asset.hpp +++ b/irohad/model/commands/transfer_asset.hpp @@ -21,7 +21,6 @@ #include #include "common/types.hpp" #include "model/command.hpp" -#include "amount/amount.hpp" namespace iroha { namespace model { @@ -53,7 +52,7 @@ namespace iroha { /** * Amount of transferred asset */ - Amount amount; + std::string amount; bool operator==(const Command &command) const override; @@ -62,7 +61,7 @@ namespace iroha { TransferAsset(const std::string &src_account_id, const std::string &dest_account_id, const std::string &asset_id, - const Amount &amount) + const std::string &amount) : src_account_id(src_account_id), dest_account_id(dest_account_id), asset_id(asset_id), diff --git a/irohad/model/converters/impl/json_command_factory.cpp b/irohad/model/converters/impl/json_command_factory.cpp index fa7d02136f..70fd48aadb 100644 --- a/irohad/model/converters/impl/json_command_factory.cpp +++ b/irohad/model/converters/impl/json_command_factory.cpp @@ -37,37 +37,10 @@ #include "model/commands/transfer_asset.hpp" using namespace rapidjson; -using namespace boost::multiprecision; namespace iroha { namespace model { namespace converters { - template <> - struct Convert { - template - boost::optional operator()(T &&x) { - auto des = makeFieldDeserializer(x); - auto str_int_value = des.String("value"); - - if (not str_int_value) { - return boost::none; - } - - // check if value is actually number - std::regex e("\\d+"); - if (not std::regex_match(str_int_value.value(), e)) { - return boost::none; - } - - uint256_t value(str_int_value.value()); - uint8_t precision; - rapidjson::Document dd; - precision = des.document["precision"].GetUint(); - - return boost::make_optional(Amount(value, static_cast(precision))); - } - }; - template <> struct Convert { template @@ -80,9 +53,10 @@ namespace iroha { return boost::none; } - return boost::make_optional(Peer(address.value(), - iroha::hexstringToArray(pubkey.value()) - .value())); + return boost::make_optional(Peer( + address.value(), + iroha::hexstringToArray(pubkey.value()) + .value())); } }; @@ -149,18 +123,8 @@ namespace iroha { document.SetObject(); document.AddMember("command_type", "AddAssetQuantity", allocator); - document.AddMember( - "account_id", add_asset_quantity->account_id, allocator); document.AddMember("asset_id", add_asset_quantity->asset_id, allocator); - - Value amount; - amount.SetObject(); - amount.AddMember( - "value", add_asset_quantity->amount.getIntValue().str(), allocator); - amount.AddMember( - "precision", add_asset_quantity->amount.getPrecision(), allocator); - - document.AddMember("amount", amount, allocator); + document.AddMember("amount", add_asset_quantity->amount, allocator); return document; } @@ -169,9 +133,8 @@ namespace iroha { const Value &document) { auto des = makeFieldDeserializer(document); return make_optional_ptr() - | des.String(&AddAssetQuantity::account_id, "account_id") | des.String(&AddAssetQuantity::asset_id, "asset_id") - | des.Object(&AddAssetQuantity::amount, "amount") | toCommand; + | des.String(&AddAssetQuantity::amount, "amount") | toCommand; } // AddPeer @@ -401,15 +364,7 @@ namespace iroha { document.AddMember("asset_id", transfer_asset->asset_id, allocator); document.AddMember( "description", transfer_asset->description, allocator); - - Value amount; - amount.SetObject(); - amount.AddMember( - "value", transfer_asset->amount.getIntValue().str(), allocator); - amount.AddMember( - "precision", transfer_asset->amount.getPrecision(), allocator); - - document.AddMember("amount", amount, allocator); + document.AddMember("amount", transfer_asset->amount, allocator); return document; } @@ -422,7 +377,7 @@ namespace iroha { | des.String(&TransferAsset::dest_account_id, "dest_account_id") | des.String(&TransferAsset::asset_id, "asset_id") | des.String(&TransferAsset::description, "description") - | des.Object(&TransferAsset::amount, "amount") | toCommand; + | des.String(&TransferAsset::amount, "amount") | toCommand; } rapidjson::Document JsonCommandFactory::serializeAppendRole( @@ -563,21 +518,10 @@ namespace iroha { document.SetObject(); document.AddMember("command_type", "SubtractAssetQuantity", allocator); - document.AddMember( - "account_id", subtract_asset_quantity->account_id, allocator); document.AddMember( "asset_id", subtract_asset_quantity->asset_id, allocator); - - Value amount; - amount.SetObject(); - amount.AddMember("value", - subtract_asset_quantity->amount.getIntValue().str(), - allocator); - amount.AddMember("precision", - subtract_asset_quantity->amount.getPrecision(), - allocator); - - document.AddMember("amount", amount, allocator); + document.AddMember( + "amount", subtract_asset_quantity->amount, allocator); return document; } @@ -587,9 +531,8 @@ namespace iroha { const Value &document) { auto des = makeFieldDeserializer(document); return make_optional_ptr() - | des.String(&SubtractAssetQuantity::account_id, "account_id") | des.String(&SubtractAssetQuantity::asset_id, "asset_id") - | des.Object(&SubtractAssetQuantity::amount, "amount") | toCommand; + | des.String(&SubtractAssetQuantity::amount, "amount") | toCommand; } // Abstract diff --git a/irohad/model/converters/impl/pb_command_factory.cpp b/irohad/model/converters/impl/pb_command_factory.cpp index c70b2b9247..0bb769941d 100644 --- a/irohad/model/converters/impl/pb_command_factory.cpp +++ b/irohad/model/converters/impl/pb_command_factory.cpp @@ -159,20 +159,16 @@ namespace iroha { protocol::AddAssetQuantity PbCommandFactory::serializeAddAssetQuantity( const model::AddAssetQuantity &add_asset_quantity) { protocol::AddAssetQuantity pb_add_asset_quantity; - pb_add_asset_quantity.set_account_id(add_asset_quantity.account_id); pb_add_asset_quantity.set_asset_id(add_asset_quantity.asset_id); - auto amount = pb_add_asset_quantity.mutable_amount(); - amount->CopyFrom(serializeAmount(add_asset_quantity.amount)); + pb_add_asset_quantity.set_amount(add_asset_quantity.amount); return pb_add_asset_quantity; } model::AddAssetQuantity PbCommandFactory::deserializeAddAssetQuantity( const protocol::AddAssetQuantity &pb_add_asset_quantity) { model::AddAssetQuantity add_asset_quantity; - add_asset_quantity.account_id = pb_add_asset_quantity.account_id(); add_asset_quantity.asset_id = pb_add_asset_quantity.asset_id(); - add_asset_quantity.amount = - deserializeAmount(pb_add_asset_quantity.amount()); + add_asset_quantity.amount = pb_add_asset_quantity.amount(); return add_asset_quantity; } @@ -182,12 +178,9 @@ namespace iroha { PbCommandFactory::serializeSubtractAssetQuantity( const model::SubtractAssetQuantity &subtract_asset_quantity) { protocol::SubtractAssetQuantity pb_subtract_asset_quantity; - pb_subtract_asset_quantity.set_account_id( - subtract_asset_quantity.account_id); pb_subtract_asset_quantity.set_asset_id( subtract_asset_quantity.asset_id); - auto amount = pb_subtract_asset_quantity.mutable_amount(); - amount->CopyFrom(serializeAmount(subtract_asset_quantity.amount)); + pb_subtract_asset_quantity.set_amount(subtract_asset_quantity.amount); return pb_subtract_asset_quantity; } @@ -195,12 +188,9 @@ namespace iroha { PbCommandFactory::deserializeSubtractAssetQuantity( const protocol::SubtractAssetQuantity &pb_subtract_asset_quantity) { model::SubtractAssetQuantity subtract_asset_quantity; - subtract_asset_quantity.account_id = - pb_subtract_asset_quantity.account_id(); subtract_asset_quantity.asset_id = pb_subtract_asset_quantity.asset_id(); - subtract_asset_quantity.amount = - deserializeAmount(pb_subtract_asset_quantity.amount()); + subtract_asset_quantity.amount = pb_subtract_asset_quantity.amount(); return subtract_asset_quantity; } @@ -343,8 +333,7 @@ namespace iroha { pb_transfer_asset.set_dest_account_id(transfer_asset.dest_account_id); pb_transfer_asset.set_asset_id(transfer_asset.asset_id); pb_transfer_asset.set_description(transfer_asset.description); - auto amount = pb_transfer_asset.mutable_amount(); - amount->CopyFrom(serializeAmount(transfer_asset.amount)); + pb_transfer_asset.set_amount(transfer_asset.amount); return pb_transfer_asset; } @@ -357,8 +346,7 @@ namespace iroha { pb_subtract_asset_quantity.dest_account_id(); transfer_asset.asset_id = pb_subtract_asset_quantity.asset_id(); transfer_asset.description = pb_subtract_asset_quantity.description(); - transfer_asset.amount = - deserializeAmount(pb_subtract_asset_quantity.amount()); + transfer_asset.amount = pb_subtract_asset_quantity.amount(); return transfer_asset; } diff --git a/irohad/model/converters/impl/pb_common.cpp b/irohad/model/converters/impl/pb_common.cpp index de8d3aa88e..ca61c85720 100644 --- a/irohad/model/converters/impl/pb_common.cpp +++ b/irohad/model/converters/impl/pb_common.cpp @@ -19,33 +19,12 @@ #include "model/command.hpp" #include "model/commands/all.hpp" #include "model/domain.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" namespace iroha { namespace model { namespace converters { - protocol::Amount serializeAmount(iroha::Amount iroha_amount) { - protocol::Amount res; - res.set_precision(iroha_amount.getPrecision()); - auto value = res.mutable_value(); - auto vectorUint64s = iroha_amount.to_uint64s(); - value->set_first(vectorUint64s.at(0)); - value->set_second(vectorUint64s.at(1)); - value->set_third(vectorUint64s.at(2)); - value->set_fourth(vectorUint64s.at(3)); - return res; - } - - iroha::Amount deserializeAmount(protocol::Amount pb_amount) { - auto value = pb_amount.value(); - return {value.first(), - value.second(), - value.third(), - value.fourth(), - static_cast(pb_amount.precision())}; - } - protocol::Peer serializePeer(iroha::model::Peer iroha_peer) { protocol::Peer res; res.set_address(iroha_peer.address); @@ -85,8 +64,7 @@ namespace iroha { iroha::protocol::AccountAsset pb_account_asset{}; pb_account_asset.set_account_id(account_asset.account_id); pb_account_asset.set_asset_id(account_asset.asset_id); - pb_account_asset.set_allocated_balance( - new protocol::Amount(serializeAmount(account_asset.balance))); + pb_account_asset.set_balance(account_asset.balance); return pb_account_asset; } diff --git a/irohad/model/converters/impl/pb_query_response_factory.cpp b/irohad/model/converters/impl/pb_query_response_factory.cpp index e096cfd748..e79428ef46 100644 --- a/irohad/model/converters/impl/pb_query_response_factory.cpp +++ b/irohad/model/converters/impl/pb_query_response_factory.cpp @@ -168,8 +168,7 @@ namespace iroha { protocol::AccountAsset pb_account_asset; pb_account_asset.set_account_id(account_asset.account_id); pb_account_asset.set_asset_id(account_asset.asset_id); - auto pb_balance = pb_account_asset.mutable_balance(); - pb_balance->CopyFrom(serializeAmount(account_asset.balance)); + pb_account_asset.set_balance(account_asset.balance); return pb_account_asset; } @@ -177,7 +176,7 @@ namespace iroha { const protocol::AccountAsset &account_asset) const { model::AccountAsset res; res.account_id = account_asset.account_id(); - res.balance = deserializeAmount(account_asset.balance()); + res.balance = account_asset.balance(); res.asset_id = account_asset.asset_id(); return res; } @@ -187,15 +186,11 @@ namespace iroha { const model::AccountAssetResponse &accountAssetResponse) const { protocol::AccountAssetResponse pb_response; auto pb_account_asset = pb_response.mutable_account_assets(); - for (auto &asset: accountAssetResponse.acct_assets) { + for (auto &asset : accountAssetResponse.acct_assets) { auto pb_asset = new iroha::protocol::AccountAsset(); - pb_asset->set_asset_id( - asset.asset_id); - pb_asset->set_account_id( - asset.account_id); - auto pb_amount = pb_asset->mutable_balance(); - pb_amount->CopyFrom( - serializeAmount(asset.balance)); + pb_asset->set_asset_id(asset.asset_id); + pb_asset->set_account_id(asset.account_id); + pb_asset->set_balance(asset.balance); pb_account_asset->AddAllocated(pb_asset); } return pb_response; @@ -205,10 +200,11 @@ namespace iroha { PbQueryResponseFactory::deserializeAccountAssetResponse( const protocol::AccountAssetResponse &account_asset_response) const { model::AccountAssetResponse res; - for (int i = 0; i < account_asset_response.account_assets().size(); i++) { + for (int i = 0; i < account_asset_response.account_assets().size(); + i++) { auto model_asset = iroha::model::AccountAsset(); - model_asset.balance = deserializeAmount( - account_asset_response.account_assets(i).balance()); + model_asset.balance = + account_asset_response.account_assets(i).balance(); model_asset.account_id = account_asset_response.account_assets(i).account_id(); model_asset.asset_id = diff --git a/irohad/model/converters/impl/pb_transaction_factory.cpp b/irohad/model/converters/impl/pb_transaction_factory.cpp index c55d910dcd..2a4bb9ad72 100644 --- a/irohad/model/converters/impl/pb_transaction_factory.cpp +++ b/irohad/model/converters/impl/pb_transaction_factory.cpp @@ -28,7 +28,7 @@ namespace iroha { model::converters::PbCommandFactory factory; protocol::Transaction pbtx; - auto pl = pbtx.mutable_payload(); + auto pl = pbtx.mutable_payload()->mutable_reduced_payload(); pl->set_created_time(tx.created_ts); pl->set_creator_account_id(tx.creator_account_id); pl->set_quorum(tx.quorum); @@ -52,7 +52,7 @@ namespace iroha { model::converters::PbCommandFactory commandFactory; model::Transaction tx; - const auto &pl = pb_tx.payload(); + const auto &pl = pb_tx.payload().reduced_payload(); tx.creator_account_id = pl.creator_account_id(); tx.created_ts = pl.created_time(); tx.quorum = static_cast(pl.quorum()); diff --git a/irohad/model/converters/pb_common.hpp b/irohad/model/converters/pb_common.hpp index 47baf70f30..3d1e06646e 100644 --- a/irohad/model/converters/pb_common.hpp +++ b/irohad/model/converters/pb_common.hpp @@ -18,7 +18,6 @@ #ifndef IROHA_PB_COMMON_HPP #define IROHA_PB_COMMON_HPP -#include "amount/amount.hpp" #include "commands.pb.h" #include "common/types.hpp" #include "cryptography/ed25519_sha3_impl/internal/sha3_hash.hpp" @@ -28,15 +27,11 @@ #include "model/domain.hpp" #include "model/peer.hpp" #include "model/signature.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" namespace iroha { namespace model { namespace converters { - // amount - protocol::Amount serializeAmount(iroha::Amount iroha_amount); - iroha::Amount deserializeAmount(protocol::Amount pb_amount); - // peer protocol::Peer serializePeer(iroha::model::Peer iroha_peer); iroha::model::Peer deserializePeer(protocol::Peer pb_peer); diff --git a/irohad/model/converters/pb_query_response_factory.hpp b/irohad/model/converters/pb_query_response_factory.hpp index ccff362b33..8e92a06823 100644 --- a/irohad/model/converters/pb_query_response_factory.hpp +++ b/irohad/model/converters/pb_query_response_factory.hpp @@ -28,7 +28,7 @@ #include "model/queries/responses/roles_response.hpp" #include "model/queries/responses/signatories_response.hpp" #include "model/queries/responses/transactions_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" namespace iroha { namespace model { diff --git a/irohad/model/converters/pb_transaction_factory.hpp b/irohad/model/converters/pb_transaction_factory.hpp index a4dc32f205..4d1823f523 100644 --- a/irohad/model/converters/pb_transaction_factory.hpp +++ b/irohad/model/converters/pb_transaction_factory.hpp @@ -19,8 +19,8 @@ #define IROHA_PB_TRANSACTION_FACTORY_HPP #include -#include "block.pb.h" #include "model/transaction.hpp" +#include "transaction.pb.h" namespace iroha { namespace model { diff --git a/irohad/model/generators/command_generator.hpp b/irohad/model/generators/command_generator.hpp index ba65d4f75a..23f5f50dbe 100644 --- a/irohad/model/generators/command_generator.hpp +++ b/irohad/model/generators/command_generator.hpp @@ -19,13 +19,9 @@ #define IROHA_COMMAND_GENERATOR_HPP #include -#include "amount/amount.hpp" #include "generator/generator.hpp" namespace iroha { - - class Amount; - namespace model { struct Peer; @@ -73,14 +69,10 @@ namespace iroha { const std::string &account_id, uint32_t quorum); std::shared_ptr generateAddAssetQuantity( - const std::string &account_id, - const std::string &asset_id, - const Amount &amount); + const std::string &asset_id, const std::string &amount); std::shared_ptr generateSubtractAssetQuantity( - const std::string &account_id, - const std::string &asset_id, - const Amount &amount); + const std::string &asset_id, const std::string &amount); /** * Generate transfer assets from source account_id to target account_id * @param src_account_id - source account identifier @@ -93,7 +85,7 @@ namespace iroha { const std::string &src_account_id, const std::string &target_account_id, const std::string &asset_id, - const Amount &amount); + const std::string &amount); std::shared_ptr generateAppendRole( const std::string &account_id, const std::string &role_name); diff --git a/irohad/model/generators/impl/command_generator.cpp b/irohad/model/generators/impl/command_generator.cpp index 6c1fcd4220..9600550856 100644 --- a/irohad/model/generators/impl/command_generator.cpp +++ b/irohad/model/generators/impl/command_generator.cpp @@ -101,18 +101,13 @@ namespace iroha { } std::shared_ptr CommandGenerator::generateAddAssetQuantity( - const std::string &account_id, - const std::string &asset_id, - const Amount &amount) { - return generateCommand(account_id, asset_id, amount); + const std::string &asset_id, const std::string &amount) { + return generateCommand(asset_id, amount); } std::shared_ptr CommandGenerator::generateSubtractAssetQuantity( - const std::string &account_id, - const std::string &asset_id, - const Amount &amount) { - return generateCommand( - account_id, asset_id, amount); + const std::string &asset_id, const std::string &amount) { + return generateCommand(asset_id, amount); } std::shared_ptr CommandGenerator::generateSetQuorum( @@ -124,7 +119,7 @@ namespace iroha { const std::string &src_account, const std::string &dest_account, const std::string &asset_id, - const Amount &amount) { + const std::string &amount) { return generateCommand( src_account, dest_account, asset_id, amount); } diff --git a/irohad/model/impl/model_operators.cpp b/irohad/model/impl/model_operators.cpp index fe6dd7dae5..58934aca69 100644 --- a/irohad/model/impl/model_operators.cpp +++ b/irohad/model/impl/model_operators.cpp @@ -97,8 +97,7 @@ namespace iroha { if (! instanceof (command)) return false; auto add_asset_quantity = static_cast(command); - return add_asset_quantity.account_id == account_id - && add_asset_quantity.asset_id == asset_id + return add_asset_quantity.asset_id == asset_id && add_asset_quantity.amount == amount; } @@ -108,8 +107,7 @@ namespace iroha { return false; auto subtract_asset_quantity = static_cast(command); - return subtract_asset_quantity.account_id == account_id - && subtract_asset_quantity.asset_id == asset_id + return subtract_asset_quantity.asset_id == asset_id && subtract_asset_quantity.amount == amount; } diff --git a/irohad/multi_sig_transactions/transport/impl/mst_transport_grpc.cpp b/irohad/multi_sig_transactions/transport/impl/mst_transport_grpc.cpp index 114148d561..035ba6931d 100644 --- a/irohad/multi_sig_transactions/transport/impl/mst_transport_grpc.cpp +++ b/irohad/multi_sig_transactions/transport/impl/mst_transport_grpc.cpp @@ -54,7 +54,7 @@ grpc::Status MstTransportGrpc::SendState( auto from = std::make_shared( shared_model::proto::PeerBuilder() .address(peer.address()) - .pubkey(shared_model::crypto::PublicKey(peer.pubkey())) + .pubkey(shared_model::crypto::PublicKey(peer.peer_key())) .build()); subscriber_.lock()->onNewState(std::move(from), std::move(newState)); @@ -76,7 +76,7 @@ void MstTransportGrpc::sendState(const shared_model::interface::Peer &to, transport::MstState protoState; auto peer = protoState.mutable_peer(); - peer->set_pubkey(shared_model::crypto::toBinaryString(to.pubkey())); + peer->set_peer_key(shared_model::crypto::toBinaryString(to.pubkey())); peer->set_address(to.address()); for (auto &tx : providing_state.getTransactions()) { auto addtxs = protoState.add_transactions(); diff --git a/irohad/network/impl/block_loader_impl.cpp b/irohad/network/impl/block_loader_impl.cpp index 28b12e01b3..9d74f17480 100644 --- a/irohad/network/impl/block_loader_impl.cpp +++ b/irohad/network/impl/block_loader_impl.cpp @@ -23,20 +23,17 @@ #include "builders/protobuf/transport_builder.hpp" #include "interfaces/common_objects/peer.hpp" #include "network/impl/grpc_channel_builder.hpp" +#include "validators/default_validator.hpp" using namespace iroha::ametsuchi; using namespace iroha::network; using namespace shared_model::crypto; using namespace shared_model::interface; +namespace val = shared_model::validation; -BlockLoaderImpl::BlockLoaderImpl( - std::shared_ptr peer_query, - std::shared_ptr block_query, - std::shared_ptr - stateless_validator) - : peer_query_(std::move(peer_query)), - block_query_(std::move(block_query)), - stateless_validator_(stateless_validator) { +BlockLoaderImpl::BlockLoaderImpl(std::shared_ptr peer_query, + std::shared_ptr block_query) + : peer_query_(std::move(peer_query)), block_query_(std::move(block_query)) { log_ = logger::log("BlockLoaderImpl"); } @@ -45,6 +42,21 @@ const char *kTopBlockRetrieveFail = "Failed to retrieve top block"; const char *kPeerRetrieveFail = "Failed to retrieve peers"; const char *kPeerFindFail = "Failed to find requested peer"; +struct TimerWrapper : public val::FieldValidator { + explicit TimerWrapper(iroha::ts64_t t) + : FieldValidator(val::FieldValidator::kDefaultFutureGap, + [=] { return t; }) {} +}; +using BlockValidatorInternal = + val::BlockValidator>; +using Validator = + val::SignableModelValidator; + rxcpp::observable> BlockLoaderImpl::retrieveBlocks( const PublicKey &peer_pubkey) { return rxcpp::observable<>::create>( @@ -55,7 +67,8 @@ rxcpp::observable> BlockLoaderImpl::retrieveBlocks( expected::Value> block) { top_block = block.value; }, [this](expected::Error error) { - log_->error(kTopBlockRetrieveFail + std::string{": "} + error.error); + log_->error(kTopBlockRetrieveFail + std::string{": "} + + error.error); }); if (not top_block) { subscriber.on_completed(); @@ -79,22 +92,21 @@ rxcpp::observable> BlockLoaderImpl::retrieveBlocks( auto reader = this->getPeerStub(**peer).retrieveBlocks(&context, request); while (reader->Read(&block)) { - shared_model::proto::TransportBuilder< - shared_model::proto::Block, - shared_model::validation::DefaultSignableBlockValidator>() + shared_model::proto::TransportBuilder( + Validator(TimerWrapper(block.payload().created_time()))) .build(block) .match( // success case - [&subscriber]( - const iroha::expected::Value + [&subscriber](iroha::expected::Value &result) { subscriber.on_next( std::move(std::make_shared( - result.value))); + std::move(result.value)))); }, // fail case [this, - &context](const iroha::expected::Error &error) { + &context](iroha::expected::Error &error) { log_->error(error.error); context.TryCancel(); }); @@ -127,7 +139,8 @@ boost::optional> BlockLoaderImpl::retrieveBlock( // stateless validation of block auto result = std::make_shared(std::move(block)); - auto answer = stateless_validator_->validate(*result); + auto answer = BlockValidatorInternal(TimerWrapper(result->createdTime())) + .validate(*result); if (answer.hasErrors()) { log_->error(answer.reason()); return boost::none; diff --git a/irohad/network/impl/block_loader_impl.hpp b/irohad/network/impl/block_loader_impl.hpp index b78c75995f..ba11ba9f65 100644 --- a/irohad/network/impl/block_loader_impl.hpp +++ b/irohad/network/impl/block_loader_impl.hpp @@ -32,12 +32,8 @@ namespace iroha { namespace network { class BlockLoaderImpl : public BlockLoader { public: - BlockLoaderImpl( - std::shared_ptr peer_query, - std::shared_ptr block_query, - std::shared_ptr = - std::make_shared< - shared_model::validation::DefaultBlockValidator>()); + BlockLoaderImpl(std::shared_ptr peer_query, + std::shared_ptr block_query); rxcpp::observable> retrieveBlocks( @@ -70,8 +66,6 @@ namespace iroha { peer_connections_; std::shared_ptr peer_query_; std::shared_ptr block_query_; - std::shared_ptr - stateless_validator_; logger::Logger log_; }; diff --git a/irohad/network/impl/block_loader_service.cpp b/irohad/network/impl/block_loader_service.cpp index a0afac026d..2f93a64130 100644 --- a/irohad/network/impl/block_loader_service.cpp +++ b/irohad/network/impl/block_loader_service.cpp @@ -31,13 +31,11 @@ grpc::Status BlockLoaderService::retrieveBlocks( ::grpc::ServerContext *context, const proto::BlocksRequest *request, ::grpc::ServerWriter<::iroha::protocol::Block> *writer) { - storage_->getBlocksFrom(request->height()) - .map([](auto block) { - return std::dynamic_pointer_cast(block) - ->getTransport(); - }) - .as_blocking() - .subscribe([writer](auto block) { writer->Write(block); }); + auto blocks = storage_->getBlocksFrom(request->height()); + std::for_each(blocks.begin(), blocks.end(), [&writer](const auto &block) { + writer->Write(std::dynamic_pointer_cast(block) + ->getTransport()); + }); return grpc::Status::OK; } @@ -53,14 +51,14 @@ grpc::Status BlockLoaderService::retrieveBlock( } boost::optional result; - storage_->getBlocksFrom(1) - .filter([&hash](auto block) { return block->hash() == hash; }) - .map([](auto block) { - return std::dynamic_pointer_cast(block) - ->getTransport(); - }) - .as_blocking() - .subscribe([&result](auto block) { result = block; }); + auto blocks = storage_->getBlocksFrom(1); + std::for_each( + blocks.begin(), blocks.end(), [&result, &hash](const auto &block) { + if (block->hash() == hash) { + result = std::dynamic_pointer_cast(block) + ->getTransport(); + } + }); if (not result) { log_->info("Cannot find block with requested hash"); return grpc::Status(grpc::StatusCode::NOT_FOUND, "Block not found"); diff --git a/irohad/network/impl/peer_communication_service_impl.cpp b/irohad/network/impl/peer_communication_service_impl.cpp index 27776e156a..3dddd7bb66 100644 --- a/irohad/network/impl/peer_communication_service_impl.cpp +++ b/irohad/network/impl/peer_communication_service_impl.cpp @@ -16,28 +16,47 @@ limitations under the License. #include "network/impl/peer_communication_service_impl.hpp" +#include "interfaces/iroha_internal/transaction_batch.hpp" + namespace iroha { namespace network { PeerCommunicationServiceImpl::PeerCommunicationServiceImpl( std::shared_ptr ordering_gate, - std::shared_ptr synchronizer) + std::shared_ptr synchronizer, + std::shared_ptr + proposal_creator) : ordering_gate_(std::move(ordering_gate)), - synchronizer_(std::move(synchronizer)) { + synchronizer_(std::move(synchronizer)), + proposal_creator_(std::move(proposal_creator)) { log_ = logger::log("PCS"); } void PeerCommunicationServiceImpl::propagate_transaction( - std::shared_ptr - transaction) { + std::shared_ptr transaction) + const { log_->info("propagate tx"); ordering_gate_->propagateTransaction(transaction); } + void PeerCommunicationServiceImpl::propagate_batch( + const shared_model::interface::TransactionBatch &batch) const { + log_->info("propagate batch"); + for (const auto tx : batch.transactions()) { + ordering_gate_->propagateTransaction(tx); + } + } + rxcpp::observable> PeerCommunicationServiceImpl::on_proposal() const { return ordering_gate_->on_proposal(); } + rxcpp::observable< + std::shared_ptr> + PeerCommunicationServiceImpl::on_verified_proposal() const { + return proposal_creator_->on_verified_proposal(); + } + rxcpp::observable PeerCommunicationServiceImpl::on_commit() const { return synchronizer_->on_commit_chain(); } diff --git a/irohad/network/impl/peer_communication_service_impl.hpp b/irohad/network/impl/peer_communication_service_impl.hpp index 09e25613bf..489236a092 100644 --- a/irohad/network/impl/peer_communication_service_impl.hpp +++ b/irohad/network/impl/peer_communication_service_impl.hpp @@ -20,7 +20,9 @@ #include "network/ordering_gate.hpp" #include "network/peer_communication_service.hpp" +#include "simulator/verified_proposal_creator.hpp" #include "synchronizer/synchronizer.hpp" +#include "validation/stateful_validator_common.hpp" #include "logger/logger.hpp" @@ -30,20 +32,29 @@ namespace iroha { public: PeerCommunicationServiceImpl( std::shared_ptr ordering_gate, - std::shared_ptr synchronizer); + std::shared_ptr synchronizer, + std::shared_ptr proposal_creator); void propagate_transaction( std::shared_ptr - transaction) override; + transaction) const override; + + void propagate_batch(const shared_model::interface::TransactionBatch + &batch) const override; rxcpp::observable> on_proposal() const override; + rxcpp::observable< + std::shared_ptr> + on_verified_proposal() const override; + rxcpp::observable on_commit() const override; private: std::shared_ptr ordering_gate_; std::shared_ptr synchronizer_; + std::shared_ptr proposal_creator_; logger::Logger log_; }; } // namespace network diff --git a/irohad/network/ordering_gate.hpp b/irohad/network/ordering_gate.hpp index 35b6ca8806..fc28f7eb51 100644 --- a/irohad/network/ordering_gate.hpp +++ b/irohad/network/ordering_gate.hpp @@ -19,6 +19,8 @@ #define IROHA_ORDERING_SERVICE_HPP #include + +#include "interfaces/iroha_internal/transaction_batch.hpp" #include "network/peer_communication_service.hpp" namespace shared_model { @@ -42,7 +44,14 @@ namespace iroha { */ virtual void propagateTransaction( std::shared_ptr - transaction) = 0; + transaction) const = 0; + + /** + * Propagate a transaction batch for further processing + * @param batch + */ + virtual void propagateBatch( + const shared_model::interface::TransactionBatch &batch) const = 0; /** * Return observable of all proposals in the consensus diff --git a/irohad/network/ordering_gate_transport.hpp b/irohad/network/ordering_gate_transport.hpp index 34a9bcfc5b..d1bb21bfb4 100644 --- a/irohad/network/ordering_gate_transport.hpp +++ b/irohad/network/ordering_gate_transport.hpp @@ -22,6 +22,7 @@ namespace shared_model { namespace interface { class Transaction; + class TransactionBatch; class Proposal; } // namespace interface } // namespace shared_model @@ -68,6 +69,13 @@ namespace iroha { std::shared_ptr transaction) = 0; + /** + * Propagates transaction batch over network + * @param batch to be propagated + */ + virtual void propagateBatch( + const shared_model::interface::TransactionBatch &batch) = 0; + virtual ~OrderingGateTransport() = default; }; diff --git a/irohad/network/ordering_service_transport.hpp b/irohad/network/ordering_service_transport.hpp index e5ef06a9cf..98f80b8b97 100644 --- a/irohad/network/ordering_service_transport.hpp +++ b/irohad/network/ordering_service_transport.hpp @@ -19,7 +19,7 @@ #include #include "interfaces/iroha_internal/proposal.hpp" -#include "interfaces/transaction.hpp" +#include "interfaces/iroha_internal/transaction_batch.hpp" namespace iroha { namespace network { @@ -31,12 +31,11 @@ namespace iroha { class OrderingServiceNotification { public: /** - * Callback on receiving transaction - * @param transaction - transaction object itself + * Callback on receiving transaction(s) + * @param batch object, in which transaction(s) are packed */ - virtual void onTransaction( - std::shared_ptr - transaction) = 0; + virtual void onBatch( + shared_model::interface::TransactionBatch &&batch) = 0; virtual ~OrderingServiceNotification() = default; }; diff --git a/irohad/network/peer_communication_service.hpp b/irohad/network/peer_communication_service.hpp index 9c5c76d75c..6a6bf6c6da 100644 --- a/irohad/network/peer_communication_service.hpp +++ b/irohad/network/peer_communication_service.hpp @@ -20,11 +20,14 @@ #include +#include "validation/stateful_validator_common.hpp" + namespace shared_model { namespace interface { class Block; class Proposal; class Transaction; + class TransactionBatch; } // namespace interface } // namespace shared_model @@ -46,7 +49,14 @@ namespace iroha { */ virtual void propagate_transaction( std::shared_ptr - transaction) = 0; + transaction) const = 0; + + /** + * Propagate batch to the network + * @param batch - batch for propagation + */ + virtual void propagate_batch( + const shared_model::interface::TransactionBatch &batch) const = 0; /** * Event is triggered when proposal arrives from network. @@ -57,6 +67,14 @@ namespace iroha { std::shared_ptr> on_proposal() const = 0; + /** + * Event is triggered when verified proposal arrives + * @return verified proposal and list of stateful validation errors + */ + virtual rxcpp::observable< + std::shared_ptr> + on_verified_proposal() const = 0; + /** * Event is triggered when commit block arrives. * @return observable with sequence of committed blocks. diff --git a/irohad/ordering/impl/ordering_gate_impl.cpp b/irohad/ordering/impl/ordering_gate_impl.cpp index 1daf800f9d..3f4b78f649 100644 --- a/irohad/ordering/impl/ordering_gate_impl.cpp +++ b/irohad/ordering/impl/ordering_gate_impl.cpp @@ -43,14 +43,26 @@ namespace iroha { run_async_(run_async) {} void OrderingGateImpl::propagateTransaction( - std::shared_ptr - transaction) { + std::shared_ptr transaction) + const { log_->info("propagate tx, account_id: {}", " account_id: " + transaction->creatorAccountId()); transport_->propagateTransaction(transaction); } + void OrderingGateImpl::propagateBatch( + const shared_model::interface::TransactionBatch &batch) const { + if (batch.transactions().empty()) { + log_->warn("trying to propagate empty batch"); + return; + } + log_->info("propagate batch, account_id: {}", + batch.transactions().front()->creatorAccountId()); + + transport_->propagateBatch(batch); + } + rxcpp::observable> OrderingGateImpl::on_proposal() { return proposals_.get_observable(); diff --git a/irohad/ordering/impl/ordering_gate_impl.hpp b/irohad/ordering/impl/ordering_gate_impl.hpp index b00b9a770d..96509ce6fb 100644 --- a/irohad/ordering/impl/ordering_gate_impl.hpp +++ b/irohad/ordering/impl/ordering_gate_impl.hpp @@ -70,7 +70,11 @@ namespace iroha { void propagateTransaction( std::shared_ptr - transaction) override; + transaction) const override; + + void propagateBatch( + const shared_model::interface::TransactionBatch &batch) + const override; rxcpp::observable> on_proposal() override; diff --git a/irohad/ordering/impl/ordering_gate_transport_grpc.cpp b/irohad/ordering/impl/ordering_gate_transport_grpc.cpp index 15364dc778..54fd427f11 100644 --- a/irohad/ordering/impl/ordering_gate_transport_grpc.cpp +++ b/irohad/ordering/impl/ordering_gate_transport_grpc.cpp @@ -18,6 +18,7 @@ #include "backend/protobuf/transaction.hpp" #include "builders/protobuf/proposal.hpp" +#include "endpoint.pb.h" #include "interfaces/common_objects/types.hpp" #include "network/impl/grpc_channel_builder.hpp" @@ -29,24 +30,22 @@ grpc::Status OrderingGateTransportGrpc::onProposal( ::google::protobuf::Empty *response) { log_->info("receive proposal"); - std::vector transactions; - for (const auto &tx : request->transactions()) { - transactions.emplace_back(tx); - } - log_->info("transactions in proposal: {}", transactions.size()); - - auto proposal = std::make_shared( - shared_model::proto::ProposalBuilder() - .transactions(transactions) - .height(request->height()) - .createdTime(request->created_time()) - .build()); + auto proposal_res = factory_->createProposal(*request); + proposal_res.match( + [this](iroha::expected::Value< + std::unique_ptr> &v) { + log_->info("transactions in proposal: {}", + v.value->transactions().size()); - if (not subscriber_.expired()) { - subscriber_.lock()->onProposal(std::move(proposal)); - } else { - log_->error("(onProposal) No subscriber"); - } + if (not subscriber_.expired()) { + subscriber_.lock()->onProposal(std::move(v.value)); + } else { + log_->error("(onProposal) No subscriber"); + } + }, + [this](const iroha::expected::Error &e) { + log_->error("Received invalid proposal: {}", e.error); + }); return grpc::Status::OK; } @@ -56,7 +55,9 @@ OrderingGateTransportGrpc::OrderingGateTransportGrpc( : network::AsyncGrpcClient( logger::log("OrderingGate")), client_(network::createClient( - server_address)) {} + server_address)), + factory_(std::make_unique>()) {} void OrderingGateTransportGrpc::propagateTransaction( std::shared_ptr transaction) { @@ -73,6 +74,23 @@ void OrderingGateTransportGrpc::propagateTransaction( call->response_reader->Finish(&call->reply, &call->status, call); } +void OrderingGateTransportGrpc::propagateBatch( + const shared_model::interface::TransactionBatch &batch) { + log_->info("Propagate transaction batch (on transport)"); + auto call = new AsyncClientCall; + + iroha::protocol::TxList batch_transport; + for (const auto tx : batch.transactions()) { + new (batch_transport.add_transactions()) iroha::protocol::Transaction( + std::static_pointer_cast(tx) + ->getTransport()); + } + call->response_reader = + client_->AsynconBatch(&call->context, batch_transport, &cq_); + + call->response_reader->Finish(&call->reply, &call->status, call); +} + void OrderingGateTransportGrpc::subscribe( std::shared_ptr subscriber) { log_->info("Subscribe"); diff --git a/irohad/ordering/impl/ordering_gate_transport_grpc.hpp b/irohad/ordering/impl/ordering_gate_transport_grpc.hpp index 39292b42f8..40fa9514dd 100644 --- a/irohad/ordering/impl/ordering_gate_transport_grpc.hpp +++ b/irohad/ordering/impl/ordering_gate_transport_grpc.hpp @@ -19,10 +19,13 @@ #include +#include "backend/protobuf/proto_proposal_factory.hpp" +#include "interfaces/iroha_internal/transaction_batch.hpp" #include "logger/logger.hpp" #include "network/impl/async_grpc_client.hpp" #include "network/ordering_gate_transport.hpp" #include "ordering.grpc.pb.h" +#include "validators/default_validator.hpp" namespace shared_model { namespace interface { @@ -47,12 +50,18 @@ namespace iroha { std::shared_ptr transaction) override; + void propagateBatch( + const shared_model::interface::TransactionBatch &batch) override; + void subscribe(std::shared_ptr subscriber) override; private: std::weak_ptr subscriber_; std::unique_ptr client_; + std::unique_ptr> + factory_; }; } // namespace ordering diff --git a/irohad/ordering/impl/ordering_service_impl.cpp b/irohad/ordering/impl/ordering_service_impl.cpp index 84bded1186..15938d369d 100644 --- a/irohad/ordering/impl/ordering_service_impl.cpp +++ b/irohad/ordering/impl/ordering_service_impl.cpp @@ -4,12 +4,14 @@ */ #include "ordering/impl/ordering_service_impl.hpp" + #include #include + +#include + #include "ametsuchi/ordering_service_persistent_state.hpp" #include "ametsuchi/peer_query.hpp" -#include "backend/protobuf/proposal.hpp" -#include "backend/protobuf/transaction.hpp" #include "datetime/time.hpp" #include "network/ordering_service_transport.hpp" @@ -22,13 +24,15 @@ namespace iroha { std::shared_ptr transport, std::shared_ptr persistent_state, + std::unique_ptr factory, bool is_async) : wsv_(wsv), max_size_(max_size), + current_size_(0), transport_(transport), - persistent_state_(persistent_state) { - log_ = logger::log("OrderingServiceImpl"); - + persistent_state_(persistent_state), + factory_(std::move(factory)), + log_(logger::log("OrderingServiceImpl")) { // restore state of ordering service from persistent storage proposal_height_ = persistent_state_->loadProposalHeight().value(); @@ -43,8 +47,8 @@ namespace iroha { switch (v) { case ProposalEvent::kTimerEvent: return not queue_.empty(); - case ProposalEvent::kTransactionEvent: - return queue_.unsafe_size() >= max_size_; + case ProposalEvent::kBatchEvent: + return current_size_.load() >= max_size_; default: BOOST_ASSERT_MSG(false, "Unknown value"); } @@ -64,44 +68,56 @@ namespace iroha { } } - void OrderingServiceImpl::onTransaction( - std::shared_ptr transaction) { - queue_.push(transaction); - log_->info("Queue size is {}", queue_.unsafe_size()); + void OrderingServiceImpl::onBatch( + shared_model::interface::TransactionBatch &&batch) { + std::shared_lock batch_prop_lock( + batch_prop_mutex_); - // on_next calls should not be concurrent - std::lock_guard lk(mutex_); - transactions_.get_subscriber().on_next(ProposalEvent::kTransactionEvent); + current_size_.fetch_add(batch.transactions().size()); + queue_.push(std::make_unique( + std::move(batch))); + log_->info("Queue size is {}", current_size_.load()); + + batch_prop_lock.unlock(); + + std::lock_guard event_lock(event_mutex_); + transactions_.get_subscriber().on_next(ProposalEvent::kBatchEvent); } void OrderingServiceImpl::generateProposal() { - // TODO 05/03/2018 andrei IR-1046 Server-side shared model object - // factories with move semantics - iroha::protocol::Proposal proto_proposal; - proto_proposal.set_height(proposal_height_++); - proto_proposal.set_created_time(iroha::time::now()); + std::lock_guard lock(batch_prop_mutex_); log_->info("Start proposal generation"); - for (std::shared_ptr tx; - static_cast(proto_proposal.transactions_size()) < max_size_ - and queue_.try_pop(tx);) { - *proto_proposal.add_transactions() = - std::move(static_cast(tx.get()) - ->getTransport()); + std::vector> txs; + for (std::unique_ptr batch; + txs.size() < max_size_ and queue_.try_pop(batch);) { + auto batch_size = batch->transactions().size(); + txs.insert(std::end(txs), + std::make_move_iterator(std::begin(batch->transactions())), + std::make_move_iterator(std::end(batch->transactions()))); + current_size_ -= batch_size; } - auto proposal = std::make_unique( - std::move(proto_proposal)); + auto tx_range = txs | boost::adaptors::indirected; + auto proposal = factory_->createProposal( + proposal_height_++, iroha::time::now(), tx_range); - // Save proposal height to the persistent storage. - // In case of restart it reloads state. - if (persistent_state_->saveProposalHeight(proposal_height_)) { - publishProposal(std::move(proposal)); - } else { - // TODO(@l4l) 23/03/18: publish proposal independent of psql status - // IR-1162 - log_->warn( - "Proposal height cannot be saved. Skipping proposal publish"); - } + proposal.match( + [this](expected::Value< + std::unique_ptr> &v) { + // Save proposal height to the persistent storage. + // In case of restart it reloads state. + if (persistent_state_->saveProposalHeight(proposal_height_)) { + publishProposal(std::move(v.value)); + } else { + // TODO(@l4l) 23/03/18: publish proposal independent of psql + // status IR-1162 + log_->warn( + "Proposal height cannot be saved. Skipping proposal publish"); + } + }, + [this](expected::Error &e) { + log_->warn("Failed to initialize proposal: {}", e.error); + }); } void OrderingServiceImpl::publishProposal( diff --git a/irohad/ordering/impl/ordering_service_impl.hpp b/irohad/ordering/impl/ordering_service_impl.hpp index 88b3948d7c..0d55aa3042 100644 --- a/irohad/ordering/impl/ordering_service_impl.hpp +++ b/irohad/ordering/impl/ordering_service_impl.hpp @@ -6,14 +6,16 @@ #ifndef IROHA_ORDERING_SERVICE_IMPL_HPP #define IROHA_ORDERING_SERVICE_IMPL_HPP -#include #include -#include +#include + +#include #include #include "logger/logger.hpp" #include "network/ordering_service.hpp" #include "ordering.grpc.pb.h" +#include "interfaces/iroha_internal/proposal_factory.hpp" namespace iroha { @@ -40,6 +42,7 @@ namespace iroha { * @param proposal_timeout observable timeout for proposal creation * @param transport receive transactions and publish proposals * @param persistent_state storage for auxiliary information + * @param factory is used to generate proposals * @param is_async whether proposals are generated in a separate thread */ OrderingServiceImpl( @@ -49,15 +52,15 @@ namespace iroha { std::shared_ptr transport, std::shared_ptr persistent_state, + std::unique_ptr factory, bool is_async = true); /** - * Process transaction received from network - * Enqueues transaction and publishes corresponding event - * @param transaction + * Process transaction(s) received from network + * Enqueues transactions and publishes corresponding event + * @param batch, in which transactions are packed */ - void onTransaction(std::shared_ptr - transaction) override; + void onBatch(shared_model::interface::TransactionBatch &&batch) override; ~OrderingServiceImpl() override; @@ -73,7 +76,7 @@ namespace iroha { /** * Events for queue check strategy */ - enum class ProposalEvent { kTransactionEvent, kTimerEvent }; + enum class ProposalEvent { kBatchEvent, kTimerEvent }; /** * Collect transactions from queue @@ -84,7 +87,7 @@ namespace iroha { std::shared_ptr wsv_; tbb::concurrent_queue< - std::shared_ptr> + std::unique_ptr> queue_; /** @@ -92,6 +95,11 @@ namespace iroha { */ const size_t max_size_; + /** + * current number of transactions in a queue + */ + std::atomic_ulong current_size_; + std::shared_ptr transport_; /** @@ -115,9 +123,14 @@ namespace iroha { rxcpp::composite_subscription handle_; /** - * Mutex for incoming transactions + * Variables for concurrency */ - std::mutex mutex_; + /// mutex for both batch and proposal generation + std::shared_timed_mutex batch_prop_mutex_; + /// mutex for events activating + std::mutex event_mutex_; + + std::unique_ptr factory_; logger::Logger log_; }; diff --git a/irohad/ordering/impl/ordering_service_transport_grpc.cpp b/irohad/ordering/impl/ordering_service_transport_grpc.cpp index 7092ff53dc..259bbb5e69 100644 --- a/irohad/ordering/impl/ordering_service_transport_grpc.cpp +++ b/irohad/ordering/impl/ordering_service_transport_grpc.cpp @@ -18,7 +18,9 @@ #include "backend/protobuf/transaction.hpp" #include "builders/protobuf/proposal.hpp" +#include "interfaces/common_objects/transaction_sequence_common.hpp" #include "network/impl/grpc_channel_builder.hpp" +#include "validators/default_validator.hpp" using namespace iroha::ordering; @@ -35,14 +37,65 @@ grpc::Status OrderingServiceTransportGrpc::onTransaction( if (subscriber_.expired()) { log_->error("No subscriber"); } else { - subscriber_.lock()->onTransaction( - std::make_shared( - iroha::protocol::Transaction(*request))); + auto batch_result = + shared_model::interface::TransactionBatch::createTransactionBatch< + shared_model::validation::DefaultTransactionValidator>( + std::make_shared( + iroha::protocol::Transaction(*request))); + batch_result.match( + [this](iroha::expected::Value + &batch) { + subscriber_.lock()->onBatch(std::move(batch.value)); + }, + [this](const iroha::expected::Error &error) { + log_->error( + "Could not create batch from received single transaction: {}", + error.error); + }); } return ::grpc::Status::OK; } +grpc::Status OrderingServiceTransportGrpc::onBatch( + ::grpc::ServerContext *context, + const protocol::TxList *request, + ::google::protobuf::Empty *response) { + log_->info("OrderingServiceTransportGrpc::onBatch"); + if (subscriber_.expired()) { + log_->error("No subscriber"); + } else { + auto txs = + std::vector>( + request->transactions_size()); + std::transform( + std::begin(request->transactions()), + std::end(request->transactions()), + std::begin(txs), + [](const auto &tx) { + return std::make_shared(tx); + }); + + auto batch_result = + shared_model::interface::TransactionBatch::createTransactionBatch( + txs, + shared_model::validation::SignedTransactionsCollectionValidator< + shared_model::validation::DefaultTransactionValidator, + shared_model::validation::BatchOrderValidator>()); + batch_result.match( + [this](iroha::expected::Value + &batch) { + subscriber_.lock()->onBatch(std::move(batch.value)); + }, + [this](const iroha::expected::Error &error) { + log_->error( + "Could not create batch from received transaction list: {}", + error.error); + }); + } + return ::grpc::Status::OK; +} + void OrderingServiceTransportGrpc::publishProposal( std::unique_ptr proposal, const std::vector &peers) { diff --git a/irohad/ordering/impl/ordering_service_transport_grpc.hpp b/irohad/ordering/impl/ordering_service_transport_grpc.hpp index 5946fb9a1a..f2811ba8eb 100644 --- a/irohad/ordering/impl/ordering_service_transport_grpc.hpp +++ b/irohad/ordering/impl/ordering_service_transport_grpc.hpp @@ -19,11 +19,11 @@ #include -#include "block.pb.h" #include "logger/logger.hpp" #include "network/impl/async_grpc_client.hpp" #include "network/ordering_service_transport.hpp" #include "ordering.grpc.pb.h" +#include "transaction.pb.h" namespace iroha { namespace ordering { @@ -46,6 +46,10 @@ namespace iroha { const protocol::Transaction *request, ::google::protobuf::Empty *response) override; + grpc::Status onBatch(::grpc::ServerContext *context, + const protocol::TxList *request, + ::google::protobuf::Empty *response) override; + ~OrderingServiceTransportGrpc() = default; private: diff --git a/irohad/simulator/impl/simulator.cpp b/irohad/simulator/impl/simulator.cpp index 72efa5ec86..7748126efc 100644 --- a/irohad/simulator/impl/simulator.cpp +++ b/irohad/simulator/impl/simulator.cpp @@ -48,9 +48,10 @@ namespace iroha { notifier_.get_observable().subscribe( verified_proposal_subscription_, - [this](const std::shared_ptr - &verified_proposal) { - this->process_verified_proposal(*verified_proposal); + [this](std::shared_ptr + verified_proposal_and_errors) { + this->process_verified_proposal( + *verified_proposal_and_errors->first); }); } @@ -59,7 +60,8 @@ namespace iroha { verified_proposal_subscription_.unsubscribe(); } - rxcpp::observable> + rxcpp::observable< + std::shared_ptr> Simulator::on_verified_proposal() { return notifier_.get_observable(); } @@ -93,9 +95,11 @@ namespace iroha { temporaryStorageResult.match( [&](expected::Value> &temporaryStorage) { - auto validated_proposal = - validator_->validate(proposal, *temporaryStorage.value); - notifier_.get_subscriber().on_next(validated_proposal); + auto validated_proposal_and_errors = + std::make_shared( + validator_->validate(proposal, *temporaryStorage.value)); + notifier_.get_subscriber().on_next( + std::move(validated_proposal_and_errors)); }, [&](expected::Error &error) { log_->error(error.error); @@ -116,7 +120,7 @@ namespace iroha { return static_cast(tx); }); - auto sign_and_send = [this](const auto& any_block){ + auto sign_and_send = [this](const auto &any_block) { crypto_signer_->sign(*any_block); block_notifier_.get_subscriber().on_next(any_block); }; diff --git a/irohad/simulator/impl/simulator.hpp b/irohad/simulator/impl/simulator.hpp index 6709ec8c1c..59524fb4f4 100644 --- a/irohad/simulator/impl/simulator.hpp +++ b/irohad/simulator/impl/simulator.hpp @@ -49,7 +49,8 @@ namespace iroha { void process_proposal( const shared_model::interface::Proposal &proposal) override; - rxcpp::observable> + rxcpp::observable< + std::shared_ptr> on_verified_proposal() override; void process_verified_proposal( @@ -61,7 +62,7 @@ namespace iroha { private: // internal rxcpp::subjects::subject< - std::shared_ptr> + std::shared_ptr> notifier_; rxcpp::subjects::subject block_notifier_; diff --git a/irohad/simulator/verified_proposal_creator.hpp b/irohad/simulator/verified_proposal_creator.hpp index b8f7ac7fde..2fb8aae3d7 100644 --- a/irohad/simulator/verified_proposal_creator.hpp +++ b/irohad/simulator/verified_proposal_creator.hpp @@ -19,6 +19,7 @@ #define IROHA_VERIFIED_PROPOSAL_CREATOR_HPP #include +#include "validation/stateful_validator_common.hpp" namespace shared_model { namespace interface { @@ -46,7 +47,7 @@ namespace iroha { * @return */ virtual rxcpp::observable< - std::shared_ptr> + std::shared_ptr> on_verified_proposal() = 0; virtual ~VerifiedProposalCreator() = default; diff --git a/irohad/torii/CMakeLists.txt b/irohad/torii/CMakeLists.txt index 6f5ad86cd6..88099d588d 100644 --- a/irohad/torii/CMakeLists.txt +++ b/irohad/torii/CMakeLists.txt @@ -38,3 +38,11 @@ target_link_libraries(torii_service shared_model_stateless_validation processors ) + +add_library(status_bus + impl/status_bus_impl.cpp + ) +target_link_libraries(status_bus + rxcpp + shared_model_interfaces + ) diff --git a/irohad/torii/command_client.cpp b/irohad/torii/command_client.cpp index 81816c0613..7b8a8cf860 100644 --- a/irohad/torii/command_client.cpp +++ b/irohad/torii/command_client.cpp @@ -15,10 +15,10 @@ limitations under the License. #include -#include "block.pb.h" -#include "network/impl/grpc_channel_builder.hpp" #include "common/byteutils.hpp" +#include "network/impl/grpc_channel_builder.hpp" #include "torii/command_client.hpp" +#include "transaction.pb.h" namespace torii { @@ -56,6 +56,12 @@ namespace torii { return stub_->Torii(&context, tx, &a); } + grpc::Status CommandSyncClient::ListTorii(const iroha::protocol::TxList &tx_list) const { + google::protobuf::Empty a; + grpc::ClientContext context; + return stub_->ListTorii(&context, tx_list, &a); + } + grpc::Status CommandSyncClient::Status( const iroha::protocol::TxStatusRequest &request, iroha::protocol::ToriiResponse &response) const { diff --git a/irohad/torii/command_client.hpp b/irohad/torii/command_client.hpp index ec915d15c4..eb428ea821 100644 --- a/irohad/torii/command_client.hpp +++ b/irohad/torii/command_client.hpp @@ -46,6 +46,13 @@ namespace torii { */ grpc::Status Torii(const iroha::protocol::Transaction &tx) const; + /** + * requests list of txs to a torii server and returns response + * @param tx_list + * @return grpc::Status - returns connection is success or not. + */ + grpc::Status ListTorii(const iroha::protocol::TxList &tx_list) const; + /** * @param tx * @param response returns ToriiResponse if succeeded diff --git a/irohad/torii/command_service.hpp b/irohad/torii/command_service.hpp index 87ea0c69f0..03c2233272 100644 --- a/irohad/torii/command_service.hpp +++ b/irohad/torii/command_service.hpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef TORII_COMMAND_SERVICE_HPP @@ -29,6 +17,7 @@ #include "endpoint.pb.h" #include "logger/logger.hpp" #include "torii/processor/transaction_processor.hpp" +#include "torii/status_bus.hpp" namespace torii { /** @@ -38,15 +27,18 @@ namespace torii { public: /** * Creates a new instance of CommandService - * @param pb_factory - model->protobuf and vice versa converter * @param tx_processor - processor of received transactions - * @param block_query - to query transactions outside the cache - * @param proposal_delay - time of a one proposal propagation. + * @param storage - to query transactions outside the cache + * @param status_bus is a common notifier for tx statuses + * @param initial_timeout - streaming timeout when tx is not received + * @param nonfinal_timeout - streaming timeout when tx is being processed */ CommandService( std::shared_ptr tx_processor, std::shared_ptr storage, - std::chrono::milliseconds proposal_delay); + std::shared_ptr status_bus, + std::chrono::milliseconds initial_timeout, + std::chrono::milliseconds nonfinal_timeout); /** * Disable copying in any way to prevent potential issues with common @@ -61,6 +53,12 @@ namespace torii { */ void Torii(const iroha::protocol::Transaction &tx); + /** + * Actual implementation of sync Torii in CommandService + * @param tx_lis - transactions we've received + */ + void ListTorii(const iroha::protocol::TxList &tx_list); + /** * Torii call via grpc * @param context - call context (see grpc docs for details) @@ -72,6 +70,17 @@ namespace torii { const iroha::protocol::Transaction *request, google::protobuf::Empty *response) override; + /** + * Torii call for transactions list via grpc + * @param context - call context (see grpc docs for details) + * @param request - list of transactions received + * @param response - no actual response (grpc stub for empty answer) + * @return - grpc::Status + */ + virtual grpc::Status ListTorii(grpc::ServerContext *context, + const iroha::protocol::TxList *request, + google::protobuf::Empty *response) override; + /** * Request to retrieve a status of any particular transaction * @param request - TxStatusRequest object which identifies transaction @@ -102,12 +111,11 @@ namespace torii { * the some final transaction status (which cannot change anymore) * @param request- TxStatusRequest object which identifies transaction * uniquely - * @param response_writer - grpc::ServerWriter which can repeatedly send - * transaction statuses back to the client + * @return observable with transaction statuses */ - void StatusStream( - iroha::protocol::TxStatusRequest const &request, - grpc::ServerWriter &response_writer); + rxcpp::observable< + std::shared_ptr> + StatusStream(const shared_model::crypto::Hash &hash); /** * StatusStream call via grpc @@ -118,19 +126,30 @@ namespace torii { * transaction statuses back to the client * @return - grpc::Status */ - virtual grpc::Status StatusStream( - grpc::ServerContext *context, - const iroha::protocol::TxStatusRequest *request, - grpc::ServerWriter *response_writer) - override; + grpc::Status StatusStream(grpc::ServerContext *context, + const iroha::protocol::TxStatusRequest *request, + grpc::ServerWriter + *response_writer) override; private: - bool checkCacheAndSend( - const boost::optional &resp, - grpc::ServerWriter &response_writer) - const; + /** + * Execute events scheduled in run loop until it is not empty and the + * subscriber is active + * @param subscription - tx status subscription + * @param run_loop - gRPC thread run loop + */ + inline void handleEvents(rxcpp::composite_subscription &subscription, + rxcpp::schedulers::run_loop &run_loop); - bool isFinalStatus(const iroha::protocol::TxStatus &status) const; + /** + * Share tx status and log it + * @param who identifier for the logging + * @param hash of the tx + * @param response to be pushed + */ + void pushStatus(const std::string &who, + const shared_model::crypto::Hash &hash, + const iroha::protocol::ToriiResponse &response); private: using CacheType = iroha::cache::Cache tx_processor_; std::shared_ptr storage_; - std::chrono::milliseconds proposal_delay_; - std::chrono::milliseconds start_tx_processing_duration_; + std::shared_ptr status_bus_; + std::chrono::milliseconds initial_timeout_; + std::chrono::milliseconds nonfinal_timeout_; std::shared_ptr cache_; + logger::Logger log_; }; diff --git a/irohad/torii/impl/command_service.cpp b/irohad/torii/impl/command_service.cpp index 607c8431ee..9dd6dbf806 100644 --- a/irohad/torii/impl/command_service.cpp +++ b/irohad/torii/impl/command_service.cpp @@ -19,94 +19,183 @@ #include -#include "backend/protobuf/transaction_responses/proto_tx_response.hpp" - #include "ametsuchi/block_query.hpp" #include "backend/protobuf/transaction.hpp" +#include "backend/protobuf/transaction_responses/proto_tx_response.hpp" #include "builders/protobuf/transaction_responses/proto_transaction_status_builder.hpp" -#include "builders/protobuf/transport_builder.hpp" +#include "builders/protobuf/transaction_sequence_builder.hpp" #include "common/byteutils.hpp" +#include "common/is_any.hpp" +#include "common/timeout.hpp" #include "common/types.hpp" #include "cryptography/default_hash_provider.hpp" -#include "endpoint.pb.h" #include "validators/default_validator.hpp" -using namespace std::chrono_literals; - namespace torii { CommandService::CommandService( std::shared_ptr tx_processor, std::shared_ptr storage, - std::chrono::milliseconds proposal_delay) + std::shared_ptr status_bus, + std::chrono::milliseconds initial_timeout, + std::chrono::milliseconds nonfinal_timeout) : tx_processor_(tx_processor), storage_(storage), - proposal_delay_(proposal_delay), - start_tx_processing_duration_(1s), + status_bus_(status_bus), + initial_timeout_(initial_timeout), + nonfinal_timeout_(nonfinal_timeout), cache_(std::make_shared()), log_(logger::log("CommandService")) { // Notifier for all clients - tx_processor_->transactionNotifier().subscribe([this](auto iroha_response) { - // Find response in cache + status_bus_->statuses().subscribe([this](auto iroha_response) { + // find response for this tx in cache; if status of received response + // isn't "greater" than cached one, dismiss received one auto proto_response = std::static_pointer_cast( iroha_response); auto tx_hash = proto_response->transactionHash(); + auto cached_tx_state = cache_->findItem(tx_hash); + if (cached_tx_state + and proto_response->getTransport().tx_status() + <= cached_tx_state->tx_status()) { + return; + } cache_->addItem(tx_hash, proto_response->getTransport()); }); } - void CommandService::Torii(const iroha::protocol::Transaction &request) { - shared_model::crypto::Hash tx_hash; - iroha::protocol::ToriiResponse response; + namespace { + iroha::protocol::ToriiResponse makeResponse( + const shared_model::crypto::Hash &h, + const iroha::protocol::TxStatus &status) { + iroha::protocol::ToriiResponse response; + response.set_tx_hash(shared_model::crypto::toBinaryString(h)); + response.set_tx_status(status); + return response; + } + } // namespace + void CommandService::Torii(const iroha::protocol::Transaction &request) { shared_model::proto::TransportBuilder< shared_model::proto::Transaction, - shared_model::validation::DefaultSignableTransactionValidator>() + shared_model::validation::DefaultSignedTransactionValidator>() .build(request) .match( - [this, &tx_hash, &response]( + [this]( // success case iroha::expected::Value &iroha_tx) { - tx_hash = iroha_tx.value.hash(); + auto tx_hash = iroha_tx.value.hash(); if (cache_->findItem(tx_hash) and iroha_tx.value.quorum() < 2) { log_->warn("Found transaction {} in cache, ignoring", tx_hash.hex()); return; } - // setting response - response.set_tx_hash(tx_hash.toString()); - response.set_tx_status( - iroha::protocol::TxStatus::STATELESS_VALIDATION_SUCCESS); - // Send transaction to iroha tx_processor_->transactionHandle( std::make_shared( std::move(iroha_tx.value))); + + this->pushStatus( + "Torii", + std::move(tx_hash), + makeResponse( + tx_hash, + iroha::protocol::TxStatus::STATELESS_VALIDATION_SUCCESS)); }, - [this, &tx_hash, &request, &response](const auto &error) { + [this, &request](const auto &error) { // getting hash from invalid transaction auto blobPayload = shared_model::proto::makeBlob(request.payload()); - tx_hash = shared_model::crypto::DefaultHashProvider::makeHash( - blobPayload); + auto tx_hash = + shared_model::crypto::DefaultHashProvider::makeHash( + blobPayload); log_->warn("Stateless invalid tx: {}, hash: {}", error.error, tx_hash.hex()); // setting response - response.set_tx_hash( - shared_model::crypto::toBinaryString(tx_hash)); - response.set_tx_status( + auto response = makeResponse( + tx_hash, iroha::protocol::TxStatus::STATELESS_VALIDATION_FAILED); response.set_error_message(std::move(error.error)); + + this->pushStatus( + "Torii", std::move(tx_hash), std::move(response)); + }); + } + + void CommandService::ListTorii(const iroha::protocol::TxList &tx_list) { + shared_model::proto::TransportBuilder< + shared_model::interface::TransactionSequence, + shared_model::validation::DefaultUnsignedTxCollectionValidator>() + .build(tx_list) + .match( + [this]( + // success case + iroha::expected::Value< + shared_model::interface::TransactionSequence> + &tx_sequence) { + auto txs = tx_sequence.value.transactions(); + std::for_each(txs.begin(), txs.end(), [this](auto &tx) { + auto tx_hash = tx->hash(); + if (cache_->findItem(tx_hash) and tx->quorum() < 2) { + log_->warn("Found transaction {} in cache, ignoring", + tx_hash.hex()); + return; + } + + // Send transaction to iroha + tx_processor_->transactionHandle(tx); + + this->pushStatus( + "ToriiList", + std::move(tx_hash), + makeResponse(tx_hash, + iroha::protocol::TxStatus:: + STATELESS_VALIDATION_SUCCESS)); + }); + }, + [this, &tx_list](auto &error) { + auto &txs = tx_list.transactions(); + if (txs.empty()) { + log_->warn("Received empty transaction sequence"); + return; + } + // form an error message, shared between all txs in a sequence + auto first_tx_blob = + shared_model::proto::makeBlob(txs[0].payload()); + auto first_tx_hash = + shared_model::crypto::DefaultHashProvider::makeHash( + first_tx_blob); + auto last_tx_blob = + shared_model::proto::makeBlob(txs[txs.size() - 1].payload()); + auto last_tx_hash = + shared_model::crypto::DefaultHashProvider::makeHash( + last_tx_blob); + auto sequence_error = + "Stateless invalid tx in transaction sequence, beginning " + "with tx : " + + first_tx_hash.hex() + " and ending with tx " + + last_tx_hash.hex() + ". Error is: " + error.error; + + // set error response for each transaction in a sequence + std::for_each( + txs.begin(), txs.end(), [this, &sequence_error](auto &tx) { + auto hash = + shared_model::crypto::DefaultHashProvider::makeHash( + shared_model::proto::makeBlob(tx.payload())); + + auto response = makeResponse( + hash, + iroha::protocol::TxStatus::STATELESS_VALIDATION_FAILED); + response.set_error_message(sequence_error); + + this->pushStatus( + "ToriiList", std::move(hash), std::move(response)); + }); }); - log_->debug("Torii: adding item to cache: {}, status {} ", - tx_hash.hex(), - response.tx_status()); - cache_->addItem(tx_hash, response); } grpc::Status CommandService::Torii( @@ -117,6 +206,13 @@ namespace torii { return grpc::Status::OK; } + grpc::Status CommandService::ListTorii(grpc::ServerContext *context, + const iroha::protocol::TxList *request, + google::protobuf::Empty *response) { + ListTorii(*request); + return grpc::Status::OK; + } + void CommandService::Status(const iroha::protocol::TxStatusRequest &request, iroha::protocol::ToriiResponse &response) { auto tx_hash = shared_model::crypto::Hash(request.tx_hash()); @@ -125,18 +221,15 @@ namespace torii { response.CopyFrom(*resp); } else { response.set_tx_hash(request.tx_hash()); - if (storage_->getBlockQuery()->hasTxWithHash( - shared_model::crypto::Hash(request.tx_hash()))) { + auto hash = shared_model::crypto::Hash(request.tx_hash()); + if (storage_->getBlockQuery()->hasTxWithHash(hash)) { response.set_tx_status(iroha::protocol::TxStatus::COMMITTED); + cache_->addItem(std::move(hash), response); } else { log_->warn("Asked non-existing tx: {}", iroha::bytestringToHexstring(request.tx_hash())); response.set_tx_status(iroha::protocol::TxStatus::NOT_RECEIVED); } - log_->debug("Status: adding item to cache: {}, status {}", - tx_hash.hex(), - response.tx_status()); - cache_->addItem(tx_hash, response); } } @@ -148,139 +241,140 @@ namespace torii { return grpc::Status::OK; } - void CommandService::StatusStream( - iroha::protocol::TxStatusRequest const &request, - grpc::ServerWriter &response_writer) { - auto resp = cache_->findItem(shared_model::crypto::Hash(request.tx_hash())); - if (checkCacheAndSend(resp, response_writer)) { - return; - } - auto finished = std::make_shared>(false); - auto subscription = rxcpp::composite_subscription(); - auto request_hash = - std::make_shared(request.tx_hash()); - - /// Condition variable to ensure that current method will not return before - /// transaction is processed or a timeout reached. It blocks current thread - /// and waits for thread from subscribe() to unblock. - auto cv = std::make_shared(); - - log_->debug("StatusStream before subscribe(), hash: {}", - request_hash->hex()); - - tx_processor_->transactionNotifier() - .filter([&request_hash](auto response) { - return response->transactionHash() == *request_hash; - }) - .subscribe( - subscription, - [&](std::shared_ptr - iroha_response) { - auto proto_response = std::static_pointer_cast< - shared_model::proto::TransactionResponse>(iroha_response); - - log_->debug("subscribe new status: {}, hash {}", - proto_response->toString(), - proto_response->transactionHash().hex()); - - iroha::protocol::ToriiResponse resp_sub = - proto_response->getTransport(); - - if (isFinalStatus(resp_sub.tx_status())) { - response_writer.WriteLast(resp_sub, grpc::WriteOptions()); - *finished = true; - cv->notify_one(); - } else { - response_writer.Write(resp_sub); - } - }); + /** + * Statuses considered final for streaming. Observable stops value emission + * after receiving a value of one of the following types + * @tparam T concrete response type + */ + template + constexpr bool FinalStatusValue = + iroha::is_any, + shared_model::interface::StatelessFailedTxResponse, + shared_model::interface::StatefulFailedTxResponse, + shared_model::interface::CommittedTxResponse, + shared_model::interface::MstExpiredResponse>::value; - std::mutex wait_subscription; - std::unique_lock lock(wait_subscription); - - log_->debug("StatusStream waiting start, hash: {}", request_hash->hex()); - - /// we expect that start_tx_processing_duration_ will be enough - /// to at least start tx processing. - /// Otherwise we think there is no such tx at all. - cv->wait_for(lock, start_tx_processing_duration_); - - log_->debug("StatusStream waiting finish, hash: {}", request_hash->hex()); - - if (not *finished) { - resp = cache_->findItem(shared_model::crypto::Hash(request.tx_hash())); - if (not resp) { - log_->warn("StatusStream request processing timeout, hash: {}", - request_hash->hex()); - // TODO 05/03/2018 andrei IR-1046 Server-side shared model object - // factories with move semantics - auto resp_none = shared_model::proto::TransactionStatusBuilder() - .txHash(*request_hash) - .notReceived() - .build(); - response_writer.WriteLast(resp_none.getTransport(), - grpc::WriteOptions()); - } else { - log_->debug( - "Tx processing was started but unfinished, awaiting more, hash: {}", - request_hash->hex()); - /// We give it 2*proposal_delay time until timeout. - cv->wait_for(lock, 2 * proposal_delay_); - - /// status can be in the cache if it was finalized before we subscribed - if (not *finished) { - log_->debug("Transaction {} still not finished", request_hash->hex()); - resp = - cache_->findItem(shared_model::crypto::Hash(request.tx_hash())); - log_->debug("Status of tx {} in cache: {}", - request_hash->hex(), - resp->tx_status()); - } - } - } else { - log_->debug("StatusStream request processed successfully, hash: {}", - request_hash->hex()); - } - subscription.unsubscribe(); - log_->debug("StatusStream unsubscribed"); + rxcpp::observable< + std::shared_ptr> + CommandService::StatusStream(const shared_model::crypto::Hash &hash) { + using ResponsePtrType = + std::shared_ptr; + ResponsePtrType initial_status = + clone(shared_model::proto::TransactionResponse( + cache_->findItem(hash).value_or([&] { + log_->debug("tx not received: {}", hash.toString()); + return shared_model::proto::TransactionStatusBuilder() + .txHash(hash) + .notReceived() + .build() + .getTransport(); + }()))); + return status_bus_ + ->statuses() + // prepend initial status + .start_with(initial_status) + // select statuses with requested hash + .filter( + [&](auto response) { return response->transactionHash() == hash; }) + // successfully complete the observable if final status is received. + // final status is included in the observable + .lift([](rxcpp::subscriber dest) { + return rxcpp::make_subscriber( + dest, [=](ResponsePtrType response) { + dest.on_next(response); + iroha::visit_in_place( + response->get(), + [dest](const auto &resp) + -> std::enable_if_t> { + dest.on_completed(); + }, + [](const auto &resp) + -> std::enable_if_t< + not FinalStatusValue>{}); + }); + }); } grpc::Status CommandService::StatusStream( grpc::ServerContext *context, const iroha::protocol::TxStatusRequest *request, grpc::ServerWriter *response_writer) { - StatusStream(*request, *response_writer); + rxcpp::schedulers::run_loop rl; + + auto current_thread = + rxcpp::observe_on_one_worker(rxcpp::schedulers::make_run_loop(rl)); + + rxcpp::composite_subscription subscription; + + auto hash = shared_model::crypto::Hash(request->tx_hash()); + + static auto client_id_format = boost::format("Peer: '%s', %s"); + std::string client_id = + (client_id_format % context->peer() % hash.toString()).str(); + + StatusStream(hash) + // convert to transport objects + .map([&](auto response) { + log_->debug("mapped {}, {}", response->toString(), client_id); + return std::static_pointer_cast< + shared_model::proto::TransactionResponse>(response) + ->getTransport(); + }) + // set a corresponding observable timeout based on status value + .lift( + iroha::makeTimeout( + [&](const auto &response) { + return response.tx_status() + == iroha::protocol::TxStatus::NOT_RECEIVED + ? initial_timeout_ + : nonfinal_timeout_; + }, + current_thread)) + // complete the observable if client is disconnected + .take_while([=](const auto &) { + auto is_cancelled = context->IsCancelled(); + if (is_cancelled) { + log_->debug("client unsubscribed, {}", client_id); + } + return not is_cancelled; + }) + .subscribe(subscription, + [&](iroha::protocol::ToriiResponse response) { + if (response_writer->Write(response)) { + log_->debug("status written, {}", client_id); + } + }, + [&](std::exception_ptr ep) { + log_->debug("processing timeout, {}", client_id); + }, + [&] { log_->debug("stream done, {}", client_id); }); + + // run loop while subscription is active or there are pending events in the + // queue + handleEvents(subscription, rl); + + log_->debug("status stream done, {}", client_id); return grpc::Status::OK; } - bool CommandService::checkCacheAndSend( - const boost::optional &resp, - grpc::ServerWriter &response_writer) - const { - if (resp) { - if (isFinalStatus(resp->tx_status())) { - log_->debug("Transaction {} in service cache and final", - iroha::bytestringToHexstring(resp->tx_hash())); - response_writer.WriteLast(*resp, grpc::WriteOptions()); - return true; - } - log_->debug("Transaction {} in service cache and not final", - iroha::bytestringToHexstring(resp->tx_hash())); - response_writer.Write(*resp); + void CommandService::handleEvents(rxcpp::composite_subscription &subscription, + rxcpp::schedulers::run_loop &run_loop) { + while (subscription.is_subscribed() or not run_loop.empty()) { + run_loop.dispatch(); } - return false; } - bool CommandService::isFinalStatus( - const iroha::protocol::TxStatus &status) const { - using namespace iroha::protocol; - std::vector final_statuses = { - TxStatus::STATELESS_VALIDATION_FAILED, - TxStatus::STATEFUL_VALIDATION_FAILED, - TxStatus::NOT_RECEIVED, - TxStatus::COMMITTED}; - return (std::find( - std::begin(final_statuses), std::end(final_statuses), status)) - != std::end(final_statuses); + void CommandService::pushStatus( + const std::string &who, + const shared_model::crypto::Hash &hash, + const iroha::protocol::ToriiResponse &response) { + log_->debug("{}: adding item to cache: {}, status {} ", + who, + hash.hex(), + response.tx_status()); + status_bus_->publish( + std::make_shared( + std::move(response))); } + } // namespace torii diff --git a/irohad/torii/impl/query_service.cpp b/irohad/torii/impl/query_service.cpp index d1736ac53c..d44b7ca055 100644 --- a/irohad/torii/impl/query_service.cpp +++ b/irohad/torii/impl/query_service.cpp @@ -25,19 +25,7 @@ namespace torii { QueryService::QueryService( std::shared_ptr query_processor) - : query_processor_(query_processor), log_(logger::log("Query Service")) { - // Subscribe on result from iroha - query_processor_->queryNotifier().subscribe( - [this](const std::shared_ptr - &iroha_response) { - auto proto_response = - static_cast(*iroha_response) - .getTransport(); - - auto hash = iroha_response->queryHash(); - cache_.addItem(hash, proto_response); - }); - } + : query_processor_(query_processor), log_(logger::log("Query Service")) {} void QueryService::Find(iroha::protocol::Query const &request, iroha::protocol::QueryResponse &response) { @@ -54,23 +42,19 @@ namespace torii { shared_model::proto::TransportBuilder< shared_model::proto::Query, - shared_model::validation::DefaultQueryValidator>() + shared_model::validation::DefaultSignableQueryValidator>() .build(request) .match( [this, &hash, &response]( const iroha::expected::Value &query) { // Send query to iroha - query_processor_->queryHandle( - std::make_shared(query.value)); - auto result_response = cache_.findItem(hash); - if (result_response) { - response.CopyFrom(result_response.value()); - } else { - response.mutable_error_response()->set_reason( - iroha::protocol::ErrorResponse::NOT_SUPPORTED); - cache_.addItem(hash, response); - } + auto result_response = + static_cast( + *query_processor_->queryHandle(query.value)) + .getTransport(); + response.CopyFrom(result_response); + cache_.addItem(hash, response); }, [&hash, &response](const iroha::expected::Error &error) { @@ -96,17 +80,14 @@ namespace torii { log_->debug("Fetching commits"); shared_model::proto::TransportBuilder< shared_model::proto::BlocksQuery, - shared_model::validation::DefaultBlocksQueryValidator>() + shared_model::validation::DefaultSignableBlocksQueryValidator>() .build(*request) .match( [this, context, request, writer]( const iroha::expected::Value &query) { rxcpp::composite_subscription sub; - query_processor_ - ->blocksQueryHandle( - std::make_shared( - query.value)) + query_processor_->blocksQueryHandle(query.value) .as_blocking() .subscribe( sub, diff --git a/irohad/torii/impl/status_bus_impl.cpp b/irohad/torii/impl/status_bus_impl.cpp new file mode 100644 index 0000000000..e24a2edf8e --- /dev/null +++ b/irohad/torii/impl/status_bus_impl.cpp @@ -0,0 +1,21 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "torii/impl/status_bus_impl.hpp" + +namespace iroha { + namespace torii { + StatusBusImpl::StatusBusImpl(rxcpp::observe_on_one_worker worker) + : worker_(worker), subject_(worker_) {} + + void StatusBusImpl::publish(StatusBus::Objects resp) { + subject_.get_subscriber().on_next(resp); + } + + rxcpp::observable StatusBusImpl::statuses() { + return subject_.get_observable(); + } + } // namespace torii +} // namespace iroha diff --git a/irohad/torii/impl/status_bus_impl.hpp b/irohad/torii/impl/status_bus_impl.hpp new file mode 100644 index 0000000000..f56e99405a --- /dev/null +++ b/irohad/torii/impl/status_bus_impl.hpp @@ -0,0 +1,33 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef TORII_STATUS_BUS_IMPL +#define TORII_STATUS_BUS_IMPL + +#include "torii/status_bus.hpp" + +namespace iroha { + namespace torii { + /** + * StatusBus implementation + */ + class StatusBusImpl : public StatusBus { + public: + StatusBusImpl( + rxcpp::observe_on_one_worker worker = rxcpp::observe_on_new_thread()); + + void publish(StatusBus::Objects) override; + /// Subscribers will be invoked in separate thread + rxcpp::observable statuses() override; + + // Need to create once, otherwise will create thread for each subscriber + rxcpp::observe_on_one_worker worker_; + rxcpp::subjects::synchronize + subject_; + }; + } // namespace torii +} // namespace iroha + +#endif // TORII_STATUS_BUS_IMPL diff --git a/irohad/torii/processor/CMakeLists.txt b/irohad/torii/processor/CMakeLists.txt index 7dc7685717..d87aba6fbc 100644 --- a/irohad/torii/processor/CMakeLists.txt +++ b/irohad/torii/processor/CMakeLists.txt @@ -10,4 +10,5 @@ target_link_libraries(processors PUBLIC mst_processor shared_model_proto_builders query_execution + status_bus ) diff --git a/irohad/torii/processor/impl/query_processor_impl.cpp b/irohad/torii/processor/impl/query_processor_impl.cpp index e00adfd0c1..419afadcab 100644 --- a/irohad/torii/processor/impl/query_processor_impl.cpp +++ b/irohad/torii/processor/impl/query_processor_impl.cpp @@ -1,23 +1,19 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include "torii/processor/query_processor_impl.hpp" -#include "boost/range/size.hpp" + +#include + +#include "ametsuchi/wsv_query.hpp" +#include "builders/protobuf/builder_templates/query_response_template.hpp" #include "builders/protobuf/query_responses/proto_block_query_response_builder.hpp" +#include "interfaces/queries/blocks_query.hpp" +#include "interfaces/queries/query.hpp" +#include "interfaces/query_responses/block_query_response.hpp" +#include "interfaces/query_responses/query_response.hpp" #include "validation/utils.hpp" namespace iroha { @@ -27,7 +23,7 @@ namespace iroha { * @param hash - original query hash * @return QueryRepsonse */ - std::shared_ptr buildStatefulError( + auto buildStatefulError( const shared_model::interface::types::HashType &hash) { return clone( shared_model::proto::TemplateQueryResponseBuilder<>() @@ -51,12 +47,13 @@ namespace iroha { } QueryProcessorImpl::QueryProcessorImpl( - std::shared_ptr storage) - : storage_(storage) { + std::shared_ptr storage, + std::shared_ptr qry_exec) + : storage_(storage), qry_exec_(qry_exec) { storage_->on_commit().subscribe( [this](std::shared_ptr block) { auto response = buildBlocksQueryBlock(*block); - blocksQuerySubject_.get_subscriber().on_next(response); + blocks_query_subject_.get_subscriber().on_next(response); }); } template @@ -78,51 +75,29 @@ namespace iroha { QueryProcessorImpl::checkSignatories( const shared_model::interface::BlocksQuery &); - void QueryProcessorImpl::queryHandle( - std::shared_ptr qry) { - if (not checkSignatories(*qry)) { - auto response = buildStatefulError(qry->hash()); - std::lock_guard lock(notifier_mutex_); - subject_.get_subscriber().on_next(response); - return; + std::unique_ptr + QueryProcessorImpl::queryHandle(const shared_model::interface::Query &qry) { + if (not checkSignatories(qry)) { + return buildStatefulError(qry.hash()); } - const auto &wsv_query = storage_->getWsvQuery(); - auto qpf = QueryProcessingFactory(wsv_query, storage_->getBlockQuery()); - auto qpf_response = qpf.validateAndExecute(*qry); - auto qry_resp = - std::static_pointer_cast( - qpf_response); - std::lock_guard lock(notifier_mutex_); - subject_.get_subscriber().on_next( - std::make_shared( - qry_resp->getTransport())); + return qry_exec_->validateAndExecute(qry); } rxcpp::observable< std::shared_ptr> QueryProcessorImpl::blocksQueryHandle( - std::shared_ptr qry) { - if (not checkSignatories(*qry)) { + const shared_model::interface::BlocksQuery &qry) { + if (not checkSignatories(qry)) { auto response = buildBlocksQueryError("Wrong signatories"); - return rxcpp::observable<>::just( - std::shared_ptr( - response)); + return rxcpp::observable<>::just(response); } - const auto &wsv_query = storage_->getWsvQuery(); - auto qpf = QueryProcessingFactory(wsv_query, storage_->getBlockQuery()); - if (not qpf.validate(*qry)) { + + if (not qry_exec_->validate(qry)) { auto response = buildBlocksQueryError("Stateful invalid"); - return rxcpp::observable<>::just( - std::shared_ptr( - response)); + return rxcpp::observable<>::just(response); } - return blocksQuerySubject_.get_observable(); - } - - rxcpp::observable> - QueryProcessorImpl::queryNotifier() { - return subject_.get_observable(); + return blocks_query_subject_.get_observable(); } } // namespace torii diff --git a/irohad/torii/processor/impl/transaction_processor_impl.cpp b/irohad/torii/processor/impl/transaction_processor_impl.cpp index 2e118fc33d..5762d0f37b 100644 --- a/irohad/torii/processor/impl/transaction_processor_impl.cpp +++ b/irohad/torii/processor/impl/transaction_processor_impl.cpp @@ -1,47 +1,57 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include "torii/processor/transaction_processor_impl.hpp" +#include + #include "backend/protobuf/transaction.hpp" #include "interfaces/iroha_internal/block.hpp" #include "interfaces/iroha_internal/proposal.hpp" +#include "interfaces/iroha_internal/transaction_sequence.hpp" +#include "validation/stateful_validator_common.hpp" namespace iroha { namespace torii { using network::PeerCommunicationService; + static std::string composeErrorMessage( + const validation::TransactionError &tx_error) { + if (not tx_error.first.tx_passed_initial_validation) { + return (boost::format("Stateful validation error: transaction %s " + "did not pass initial verification: " + "checking '%s', error message '%s'") + % tx_error.second.hex() % tx_error.first.name + % tx_error.first.error) + .str(); + } + return (boost::format("Stateful validation error in transaction %s: " + "command '%s' with index '%d' did not pass " + "verification with error '%s'") + % tx_error.second.hex() % tx_error.first.name + % tx_error.first.index % tx_error.first.error) + .str(); + } + TransactionProcessorImpl::TransactionProcessorImpl( std::shared_ptr pcs, - std::shared_ptr mst_processor) - : pcs_(std::move(pcs)), mst_processor_(std::move(mst_processor)) { - log_ = logger::log("TxProcessor"); - - // insert all txs from proposal to proposal set + std::shared_ptr mst_processor, + std::shared_ptr status_bus) + : pcs_(std::move(pcs)), + mst_processor_(std::move(mst_processor)), + status_bus_(std::move(status_bus)), + log_(logger::log("TxProcessor")) { + // notify about stateless success pcs_->on_proposal().subscribe([this](auto model_proposal) { for (const auto &tx : model_proposal->transactions()) { - auto hash = tx.hash(); - proposal_set_.insert(hash); + const auto &hash = tx.hash(); log_->info("on proposal stateless success: {}", hash.hex()); // different on_next() calls (this one and below) can happen in // different threads and we don't expect emitting them concurrently - std::lock_guard lock(notifier_mutex_); - notifier_.get_subscriber().on_next( + status_bus_->publish( shared_model::builder::DefaultTransactionStatusBuilder() .statelessValidationSuccess() .txHash(hash) @@ -49,49 +59,63 @@ namespace iroha { } }); - // move commited txs from proposal to candidate map + // process stateful validation results + pcs_->on_verified_proposal().subscribe( + [this](std::shared_ptr + proposal_and_errors) { + // notify about failed txs + const auto &errors = proposal_and_errors->second; + std::lock_guard lock(notifier_mutex_); + for (const auto &tx_error : errors) { + auto error_msg = composeErrorMessage(tx_error); + log_->info(error_msg); + status_bus_->publish( + shared_model::builder::DefaultTransactionStatusBuilder() + .statefulValidationFailed() + .txHash(tx_error.second) + .errorMsg(error_msg) + .build()); + } + // notify about success txs + for (const auto &successful_tx : + proposal_and_errors->first->transactions()) { + log_->info("on stateful validation success: {}", + successful_tx.hash().hex()); + status_bus_->publish( + shared_model::builder::DefaultTransactionStatusBuilder() + .statefulValidationSuccess() + .txHash(successful_tx.hash()) + .build()); + } + }); + + // commit transactions pcs_->on_commit().subscribe([this](Commit blocks) { blocks.subscribe( - // on next.. + // on next [this](auto model_block) { - for (const auto &tx : model_block->transactions()) { - auto hash = tx.hash(); - if (this->proposal_set_.find(hash) != proposal_set_.end()) { - proposal_set_.erase(hash); - candidate_set_.insert(hash); - log_->info("on commit stateful success: {}", hash.hex()); - std::lock_guard lock(notifier_mutex_); - notifier_.get_subscriber().on_next( - shared_model::builder::DefaultTransactionStatusBuilder() - .statefulValidationSuccess() - .txHash(hash) - .build()); - } - } + current_txs_hashes_.reserve(model_block->transactions().size()); + std::transform(model_block->transactions().begin(), + model_block->transactions().end(), + std::back_inserter(current_txs_hashes_), + [](const auto &tx) { return tx.hash(); }); }, // on complete [this]() { - for (auto &tx_hash : proposal_set_) { - log_->info("on commit stateful failed: {}", tx_hash.hex()); + if (current_txs_hashes_.empty()) { + log_->info("there are no transactions to be committed"); + } else { std::lock_guard lock(notifier_mutex_); - notifier_.get_subscriber().on_next( - shared_model::builder::DefaultTransactionStatusBuilder() - .statefulValidationFailed() - .txHash(tx_hash) - .build()); - } - proposal_set_.clear(); - - for (auto &tx_hash : candidate_set_) { - log_->info("on commit committed: {}", tx_hash.hex()); - std::lock_guard lock(notifier_mutex_); - notifier_.get_subscriber().on_next( - shared_model::builder::DefaultTransactionStatusBuilder() - .committed() - .txHash(tx_hash) - .build()); + for (const auto &tx_hash : current_txs_hashes_) { + log_->info("on commit committed: {}", tx_hash.hex()); + status_bus_->publish( + shared_model::builder::DefaultTransactionStatusBuilder() + .committed() + .txHash(tx_hash) + .build()); + } } - candidate_set_.clear(); + current_txs_hashes_.clear(); }); }); @@ -102,7 +126,7 @@ namespace iroha { mst_processor_->onExpiredTransactions().subscribe([this](auto &&tx) { log_->info("MST tx expired"); std::lock_guard lock(notifier_mutex_); - this->notifier_.get_subscriber().on_next( + this->status_bus_->publish( shared_model::builder::DefaultTransactionStatusBuilder() .mstExpired() .txHash(tx->hash()) @@ -112,7 +136,8 @@ namespace iroha { } void TransactionProcessorImpl::transactionHandle( - std::shared_ptr transaction) { + std::shared_ptr transaction) + const { log_->info("handle transaction"); if (boost::size(transaction->signatures()) < transaction->quorum()) { log_->info("waiting for quorum signatures"); @@ -124,10 +149,21 @@ namespace iroha { pcs_->propagate_transaction(transaction); } - rxcpp::observable< - std::shared_ptr> - TransactionProcessorImpl::transactionNotifier() { - return notifier_.get_observable(); + void TransactionProcessorImpl::transactionSequenceHandle( + const shared_model::interface::TransactionSequence + &transaction_sequence) const { + for (const auto &batch : transaction_sequence.batches()) { + if (batch.hasAllSignatures()) { + pcs_->propagate_batch(batch); + } else { + // TODO kamilsa 16.07.18 propagate full batch to mst when its + // interface is updated + for (const auto tx : batch.transactions()) { + mst_processor_->propagateTransaction(tx); + } + } + } } + } // namespace torii } // namespace iroha diff --git a/irohad/torii/processor/query_processor.hpp b/irohad/torii/processor/query_processor.hpp index 7267d717a8..a19cf56288 100644 --- a/irohad/torii/processor/query_processor.hpp +++ b/irohad/torii/processor/query_processor.hpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_QUERY_PROCESSOR_HPP @@ -20,10 +8,16 @@ #include -#include "interfaces/queries/blocks_query.hpp" -#include "interfaces/queries/query.hpp" -#include "interfaces/query_responses/block_query_response.hpp" -#include "interfaces/query_responses/query_response.hpp" +#include + +namespace shared_model { + namespace interface { + class Query; + class BlocksQuery; + class QueryResponse; + class BlockQueryResponse; + } // namespace interface +} // namespace shared_model namespace iroha { namespace torii { @@ -34,11 +28,12 @@ namespace iroha { class QueryProcessor { public: /** - * Register client query - * @param query - client intent + * Perform client query + * @param qry - client intent + * @return resulted response */ - virtual void queryHandle( - std::shared_ptr qry) = 0; + virtual std::unique_ptr + queryHandle(const shared_model::interface::Query &qry) = 0; /** * Register client blocks query * @param query - client intent @@ -46,15 +41,7 @@ namespace iroha { */ virtual rxcpp::observable< std::shared_ptr> - blocksQueryHandle( - std::shared_ptr qry) = 0; - /** - * Subscribe for query responses - * @return observable with query responses - */ - virtual rxcpp::observable< - std::shared_ptr> - queryNotifier() = 0; + blocksQueryHandle(const shared_model::interface::BlocksQuery &qry) = 0; virtual ~QueryProcessor(){}; }; diff --git a/irohad/torii/processor/query_processor_impl.hpp b/irohad/torii/processor/query_processor_impl.hpp index 6079b53779..d98fe7791d 100644 --- a/irohad/torii/processor/query_processor_impl.hpp +++ b/irohad/torii/processor/query_processor_impl.hpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_QUERY_PROCESSOR_IMPL_HPP @@ -26,11 +14,12 @@ namespace iroha { namespace torii { /** - * QueryProcessor provides start point for queries in the whole system + * QueryProcessorImpl provides implementation of QueryProcessor */ class QueryProcessorImpl : public QueryProcessor { public: - explicit QueryProcessorImpl(std::shared_ptr storage); + QueryProcessorImpl(std::shared_ptr storage, + std::shared_ptr qry_exec); /** * Checks if query has needed signatures @@ -40,39 +29,20 @@ namespace iroha { template bool checkSignatories(const Q &qry); - /** - * Register client query - * @param query - client intent - */ - void queryHandle( - std::shared_ptr qry) override; + std::unique_ptr queryHandle( + const shared_model::interface::Query &qry) override; - /** - * Register client block query - * @param query - client intent - * @return observable with block query responses - */ rxcpp::observable< std::shared_ptr> blocksQueryHandle( - std::shared_ptr qry) override; - - /** - * Subscribe for query responses - * @return observable with query responses - */ - rxcpp::observable> - queryNotifier() override; + const shared_model::interface::BlocksQuery &qry) override; private: - rxcpp::subjects::subject< - std::shared_ptr> - subject_; rxcpp::subjects::subject< std::shared_ptr> - blocksQuerySubject_; + blocks_query_subject_; std::shared_ptr storage_; - std::mutex notifier_mutex_; + std::shared_ptr qry_exec_; }; } // namespace torii } // namespace iroha diff --git a/irohad/torii/processor/transaction_processor.hpp b/irohad/torii/processor/transaction_processor.hpp index 3892ae10d6..2f48408c49 100644 --- a/irohad/torii/processor/transaction_processor.hpp +++ b/irohad/torii/processor/transaction_processor.hpp @@ -24,6 +24,7 @@ namespace shared_model { namespace interface { class Transaction; class TransactionResponse; + class TransactionSequence; } // namespace interface } // namespace shared_model @@ -42,15 +43,16 @@ namespace iroha { */ virtual void transactionHandle( std::shared_ptr - transaction) = 0; + transaction) const = 0; /** - * Subscribers will be notified with transaction status - * @return observable for subscribing + * Process transaction sequence and propagate batches from it either to + * the MST or PCS + * @param transaction_sequence - transaction sequence for processing */ - virtual rxcpp::observable< - std::shared_ptr> - transactionNotifier() = 0; + virtual void transactionSequenceHandle( + const shared_model::interface::TransactionSequence + &transaction_sequence) const = 0; virtual ~TransactionProcessor() = default; }; diff --git a/irohad/torii/processor/transaction_processor_impl.hpp b/irohad/torii/processor/transaction_processor_impl.hpp index 031e0736fa..4a249f573c 100644 --- a/irohad/torii/processor/transaction_processor_impl.hpp +++ b/irohad/torii/processor/transaction_processor_impl.hpp @@ -25,6 +25,7 @@ #include "multi_sig_transactions/mst_processor.hpp" #include "network/peer_communication_service.hpp" #include "torii/processor/transaction_processor.hpp" +#include "torii/status_bus.hpp" namespace iroha { namespace torii { @@ -33,18 +34,20 @@ namespace iroha { /** * @param pcs - provide information proposals and commits * @param mst_processor is a handler for multisignature transactions + * @param status_bus is a common notifier for tx statuses */ TransactionProcessorImpl( std::shared_ptr pcs, - std::shared_ptr mst_processor); + std::shared_ptr mst_processor, + std::shared_ptr status_bus); void transactionHandle( std::shared_ptr transaction) - override; + const override; - rxcpp::observable< - std::shared_ptr> - transactionNotifier() override; + void transactionSequenceHandle( + const shared_model::interface::TransactionSequence + &transaction_sequence) const override; private: // connections @@ -52,12 +55,9 @@ namespace iroha { // processing std::shared_ptr mst_processor_; - std::unordered_set - proposal_set_; - std::unordered_set - candidate_set_; + std::vector current_txs_hashes_; + + std::shared_ptr status_bus_; // internal rxcpp::subjects::subject< diff --git a/irohad/torii/query_service.hpp b/irohad/torii/query_service.hpp index 4a6cb16c1b..4dab055ea1 100644 --- a/irohad/torii/query_service.hpp +++ b/irohad/torii/query_service.hpp @@ -20,7 +20,7 @@ limitations under the License. #include #include "endpoint.grpc.pb.h" #include "endpoint.pb.h" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "backend/protobuf/queries/proto_blocks_query.hpp" #include "backend/protobuf/queries/proto_query.hpp" diff --git a/irohad/torii/status_bus.hpp b/irohad/torii/status_bus.hpp new file mode 100644 index 0000000000..35f8c41bba --- /dev/null +++ b/irohad/torii/status_bus.hpp @@ -0,0 +1,40 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef TORII_STATUS_BUS +#define TORII_STATUS_BUS + +#include +#include "interfaces/transaction_responses/tx_response.hpp" + +namespace iroha { + namespace torii { + /** + * Interface of bus for transaction statuses + */ + class StatusBus { + public: + virtual ~StatusBus() = default; + + /// Objects that represent status to operate with + using Objects = + std::shared_ptr; + + /** + * Shares object among the bus subscribers + * @param object to share + * note: guaranteed to be non-blocking call + */ + virtual void publish(Objects) = 0; + + /** + * @return observable over objects in bus + */ + virtual rxcpp::observable statuses() = 0; + }; + } // namespace torii +} // namespace iroha + +#endif // TORII_STATUS_BUS diff --git a/irohad/validation/CMakeLists.txt b/irohad/validation/CMakeLists.txt index d0be3ffb91..4b2e2b34e5 100644 --- a/irohad/validation/CMakeLists.txt +++ b/irohad/validation/CMakeLists.txt @@ -22,11 +22,11 @@ target_link_libraries(stateful_validator rxcpp shared_model_interfaces logger - shared_model_proto_backend ) add_library(chain_validator - impl/chain_validator_impl.cpp) + impl/chain_validator_impl.cpp + ) target_link_libraries(chain_validator rxcpp shared_model_interfaces diff --git a/irohad/validation/impl/stateful_validator_impl.cpp b/irohad/validation/impl/stateful_validator_impl.cpp index f273d51699..d1fabca75c 100644 --- a/irohad/validation/impl/stateful_validator_impl.cpp +++ b/irohad/validation/impl/stateful_validator_impl.cpp @@ -17,67 +17,246 @@ #include "validation/impl/stateful_validator_impl.hpp" -#include -#include -#include "builders/protobuf/proposal.hpp" +#include +#include + +#include "backend/protobuf/transaction.hpp" +#include "common/result.hpp" #include "validation/utils.hpp" namespace iroha { namespace validation { + /** + * Forms a readable error string from transaction signatures and account + * signatories + * @param signatures of the transaction + * @param signatories of the transaction creator + * @return well-formed error string + */ + static std::string formSignaturesErrorMsg( + const shared_model::interface::types::SignatureRangeType &signatures, + const std::vector + &signatories) { + std::string signatures_string, signatories_string; + for (const auto &signature : signatures) { + signatures_string.append(signature.publicKey().toString().append("\n")); + } + for (const auto &signatory : signatories) { + signatories_string.append(signatory.toString().append("\n")); + } + return (boost::format( + "stateful validator error: signatures in transaction are not " + "account signatories:\n" + "signatures' public keys: %s\n" + "signatories: %s") + % signatures_string % signatories_string) + .str(); + } + + /** + * Initially checks the transaction: its creator, signatures etc + * @param tx to be checked + * @param queries to get data from + * @return result with void, if check is succesfull, command error otherwise + */ + static expected::Result + initiallyCheckTransaction(const shared_model::interface::Transaction &tx, + ametsuchi::WsvQuery &queries) { + return expected::Result( + [&]() -> expected::Result< + std::shared_ptr, + validation::CommandError> { + // Check if tx creator has account + auto account = queries.getAccount(tx.creatorAccountId()); + if (account) { + return expected::makeValue(*account); + } + return expected::makeError( + validation::CommandError{"looking up tx creator's account", + (boost::format("could not fetch " + "account with id %s") + % tx.creatorAccountId()) + .str(), + false}); + }() | + [&](const auto &account) + -> expected::Result< + std::vector< + shared_model::interface::types::PubkeyType>, + validation::CommandError> { + // Check if account has signatories and quorum to execute + // transaction + if (boost::size(tx.signatures()) >= account->quorum()) { + auto signatories = queries.getSignatories(tx.creatorAccountId()); + if (signatories) { + return expected::makeValue(*signatories); + } + return expected::makeError(validation::CommandError{ + "looking up tx creator's signatories", + (boost::format("could not fetch " + "signatories of " + "account %s") + % tx.creatorAccountId()) + .str(), + false}); + } + return expected::makeError(validation::CommandError{ + "comparing number of tx signatures to account's quorum", + (boost::format( + "not enough " + "signatures in transaction; account's quorum %d, " + "transaction's " + "signatures amount %d") + % account->quorum() % boost::size(tx.signatures())) + .str(), + false}); + } | [&tx](const auto &signatories) + -> expected::Result { + // Check if signatures in transaction are in account + // signatory + if (signaturesSubset(tx.signatures(), signatories)) { + return {}; + } + return expected::makeError(validation::CommandError{ + "signatures are a subset of signatories", + formSignaturesErrorMsg(tx.signatures(), signatories), + false}); + }); + } - StatefulValidatorImpl::StatefulValidatorImpl() { - log_ = logger::log("SFV"); + /** + * Complements initial transaction check with command-by-command check + * @param temporary_wsv to apply commands on + * @param transactions_errors_log to write errors to + * @param tx to be checked + * @return empty result, if check is succesfull, command error otherwise + */ + static bool checkTransactions( + ametsuchi::TemporaryWsv &temporary_wsv, + validation::TransactionsErrors &transactions_errors_log, + const shared_model::interface::Transaction &tx) { + return temporary_wsv.apply(tx, initiallyCheckTransaction) + .match([](expected::Value &) { return true; }, + [&tx, &transactions_errors_log]( + expected::Error &error) { + transactions_errors_log.push_back( + std::make_pair(error.error, tx.hash())); + return false; + }); + }; + + /** + * Validate all transactions supplied; includes special rules, such as batch + * validation etc + * @param txs to be validated + * @param temporary_wsv to apply transactions on + * @param transactions_errors_log to write errors to + * @param log to write errors to console + * @return vector of proto transactions, which passed stateful validation + */ + static std::vector validateTransactions( + const shared_model::interface::types::TransactionsCollectionType &txs, + ametsuchi::TemporaryWsv &temporary_wsv, + validation::TransactionsErrors &transactions_errors_log, + const logger::Logger &log) { + std::vector valid_proto_txs{}; + // TODO: kamilsa IR-1010 20.02.2018 rework validation logic, so that + // casts to proto are not needed and stateful validator does not know + // about the transport + auto txs_begin = std::begin(txs); + auto txs_end = std::end(txs); + for (size_t i = 0; i < txs.size(); ++i) { + auto current_tx_it = txs_begin + i; + if (not current_tx_it->batchMeta() + or current_tx_it->batchMeta()->get()->type() + != shared_model::interface::types::BatchType::ATOMIC) { + // if transaction does not belong to atomic batch + if (checkTransactions( + temporary_wsv, transactions_errors_log, *current_tx_it)) { + // and it is valid + valid_proto_txs.push_back( + static_cast( + *current_tx_it)); + } + } else { + // find the batch end in proposal's transactions + auto batch_end_hash = + current_tx_it->batchMeta()->get()->reducedHashes().back(); + auto batch_end_it = + std::find_if(current_tx_it, txs_end, [&batch_end_hash](auto &tx) { + return tx.reducedHash() == batch_end_hash; + }); + if (batch_end_it == txs_end) { + // exceptional case, such batch should not have passed stateless + // validation, so fail the whole proposal + auto batch_error_msg = + (boost::format("batch is formed incorrectly: could not " + "find end of batch; " + "first transaction is %s, supposed last " + "transaction is %s") + % current_tx_it->hash().hex() % batch_end_hash.hex()) + .str(); + transactions_errors_log.emplace_back(std::make_pair( + validation::CommandError{ + "batch stateful validation", batch_error_msg, true}, + current_tx_it->hash())); + log->error(std::move(batch_error_msg)); + return std::vector{}; + } + + // check all batch's transactions for validness + auto savepoint = temporary_wsv.createSavepoint( + "batch_" + current_tx_it->hash().hex()); + if (std::all_of(current_tx_it, + batch_end_it + 1, + [&temporary_wsv, &transactions_errors_log](auto &tx) { + return checkTransactions( + temporary_wsv, transactions_errors_log, tx); + })) { + // batch is successful; add it to the list of valid_txs and + // release savepoint + std::transform( + current_tx_it, + batch_end_it + 1, + std::back_inserter(valid_proto_txs), + [](const auto &tx) { + return static_cast( + tx); + }); + savepoint->release(); + } + + // move directly to transaction after batch + i += std::distance(current_tx_it, batch_end_it); + } + } + return valid_proto_txs; } - std::shared_ptr - StatefulValidatorImpl::validate( + StatefulValidatorImpl::StatefulValidatorImpl( + std::unique_ptr factory) + : factory_(std::move(factory)), log_(logger::log("SFV")) {} + + validation::VerifiedProposalAndErrors StatefulValidatorImpl::validate( const shared_model::interface::Proposal &proposal, ametsuchi::TemporaryWsv &temporaryWsv) { log_->info("transactions in proposal: {}", proposal.transactions().size()); - auto checking_transaction = [](const auto &tx, auto &queries) { - return bool(queries.getAccount(tx.creatorAccountId()) | - [&](const auto &account) { - // Check if tx creator has account and has quorum to - // execute transaction - return boost::size(tx.signatures()) >= account->quorum() - ? queries.getSignatories(tx.creatorAccountId()) - : boost::none; - } - | - [&](const auto &signatories) { - // Check if signatures in transaction are account - // signatory - return signaturesSubset(tx.signatures(), signatories) - ? boost::make_optional(signatories) - : boost::none; - }); - }; - // Filter only valid transactions - auto filter = [&temporaryWsv, checking_transaction](auto &tx) { - return temporaryWsv.apply(tx, checking_transaction); - }; + auto transactions_errors_log = validation::TransactionsErrors{}; + auto valid_proto_txs = validateTransactions( + proposal.transactions(), temporaryWsv, transactions_errors_log, log_); - // TODO: kamilsa IR-1010 20.02.2018 rework validation logic, so that this - // cast is not needed and stateful validator does not know about the - // transport - auto valid_proto_txs = - proposal.transactions() | boost::adaptors::filtered(filter) - | boost::adaptors::transformed([](auto &tx) { - return static_cast(tx); - }); - auto validated_proposal = shared_model::proto::ProposalBuilder() - .createdTime(proposal.createdTime()) - .height(proposal.height()) - .transactions(valid_proto_txs) - .createdTime(proposal.createdTime()) - .build(); + // Since proposal came from ordering gate it was already validated. + // All transactions has been validated as well + // This allows for unsafe construction of proposal + auto validated_proposal = factory_->unsafeCreateProposal( + proposal.height(), proposal.createdTime(), valid_proto_txs); log_->info("transactions in verified proposal: {}", - validated_proposal.transactions().size()); - return std::make_shared( - validated_proposal.getTransport()); + validated_proposal->transactions().size()); + return std::make_pair(std::move(validated_proposal), + transactions_errors_log); } } // namespace validation } // namespace iroha diff --git a/irohad/validation/impl/stateful_validator_impl.hpp b/irohad/validation/impl/stateful_validator_impl.hpp index dcb47ea67f..0cc36d07eb 100644 --- a/irohad/validation/impl/stateful_validator_impl.hpp +++ b/irohad/validation/impl/stateful_validator_impl.hpp @@ -17,6 +17,7 @@ #ifndef IROHA_STATEFUL_VALIDATIOR_IMPL_HPP #define IROHA_STATEFUL_VALIDATIOR_IMPL_HPP +#include "interfaces/iroha_internal/unsafe_proposal_factory.hpp" #include "validation/stateful_validator.hpp" #include "logger/logger.hpp" @@ -29,23 +30,18 @@ namespace iroha { */ class StatefulValidatorImpl : public StatefulValidator { public: - StatefulValidatorImpl(); - - /** - * Function perform stateful validation on proposal - * and return proposal with valid transactions - * @param proposal - proposal for validation - * @param wsv - temporary wsv for validation, - * this wsv not affected on ledger, - * all changes after removing wsv will be ignored - * @return proposal with valid transactions - */ - std::shared_ptr validate( + explicit StatefulValidatorImpl( + std::unique_ptr + factory); + + VerifiedProposalAndErrors validate( const shared_model::interface::Proposal &proposal, ametsuchi::TemporaryWsv &temporaryWsv) override; + std::unique_ptr factory_; logger::Logger log_; }; + } // namespace validation } // namespace iroha diff --git a/irohad/validation/stateful_validator.hpp b/irohad/validation/stateful_validator.hpp index 4e267b226f..a1bfe7164f 100644 --- a/irohad/validation/stateful_validator.hpp +++ b/irohad/validation/stateful_validator.hpp @@ -18,6 +18,7 @@ limitations under the License. #include "ametsuchi/temporary_wsv.hpp" #include "interfaces/iroha_internal/proposal.hpp" +#include "validation/stateful_validator_common.hpp" namespace iroha { namespace validation { @@ -36,9 +37,10 @@ namespace iroha { * @param wsv - temporary wsv for validation, * this wsv not affected on ledger, * all changes after removing wsv will be ignored - * @return proposal with valid transactions + * @return proposal with valid transactions and errors, which appeared in + * a process of validating */ - virtual std::shared_ptr validate( + virtual VerifiedProposalAndErrors validate( const shared_model::interface::Proposal &proposal, ametsuchi::TemporaryWsv &temporaryWsv) = 0; }; diff --git a/irohad/validation/stateful_validator_common.hpp b/irohad/validation/stateful_validator_common.hpp new file mode 100644 index 0000000000..6f3ac85518 --- /dev/null +++ b/irohad/validation/stateful_validator_common.hpp @@ -0,0 +1,56 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_STATEFUL_VALIDATOR_COMMON_HPP +#define IROHA_STATEFUL_VALIDATOR_COMMON_HPP + +#include + +#include "interfaces/common_objects/types.hpp" + +namespace shared_model { + namespace interface { + class Proposal; + } +} // namespace shared_model + +namespace iroha { + namespace validation { + + /// Type of command error report + struct CommandError { + /// Name of the failed command + std::string name; + + /// Error, with which the command failed + std::string error; + + /// Shows, if transaction has passed initial validation + bool tx_passed_initial_validation; + + /// Position of the failed command in transaction + size_t index = 0; + }; + + /// Type of transaction error, which appeared during validation + /// process; contains names of commands, commands errors themselves, + /// commands indices and transaction hashes + using TransactionError = + std::pair; + + /// Collection of transactions errors + using TransactionsErrors = std::vector; + + /// Type of verified proposal and errors appeared in the process; first + /// dimension of errors vector is transaction, second is error itself with + /// number of transaction, where it happened + using VerifiedProposalAndErrors = + std::pair, + TransactionsErrors>; + + } // namespace validation +} // namespace iroha + +#endif // IROHA_STATEFUL_VALIDATOR_COMMON_HPP diff --git a/irohad/validation/utils.hpp b/irohad/validation/utils.hpp index d080f9cd20..7ee6bca64b 100644 --- a/irohad/validation/utils.hpp +++ b/irohad/validation/utils.hpp @@ -3,9 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include +#ifndef IROHA_VALIDATION_UTILS +#define IROHA_VALIDATION_UTILS + #include #include + +#include + #include "cryptography/public_key.hpp" #include "interfaces/common_objects/types.hpp" @@ -37,3 +42,5 @@ namespace iroha { } // namespace validation } // namespace iroha + +#endif // IROHA_VALIDATION_UTILS diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index e82607000a..bb73fbdb4d 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -1,4 +1,8 @@ -add_subdirectory(amount) +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + add_subdirectory(common) add_subdirectory(crypto) add_subdirectory(logger) diff --git a/libs/amount/CMakeLists.txt b/libs/amount/CMakeLists.txt deleted file mode 100644 index 1abbd107a9..0000000000 --- a/libs/amount/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. -# http://soramitsu.co.jp -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -add_library(iroha_amount - amount.cpp - ) - -target_link_libraries(iroha_amount - boost - ) diff --git a/libs/amount/amount.cpp b/libs/amount/amount.cpp deleted file mode 100644 index e27644a26d..0000000000 --- a/libs/amount/amount.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "amount/amount.hpp" - -#include -#include - -using namespace boost::multiprecision; - -namespace iroha { - - // to raise to power integer values - static int ipow(int base, int exp) { - int result = 1; - while (exp != 0) { - if (exp & 1) { - result *= base; - } - exp >>= 1; - base *= base; - } - - return result; - } - - static uint256_t getJointUint256(uint64_t first, - uint64_t second, - uint64_t third, - uint64_t fourth) { - // join 4 uint64_t into single uint256_t by means of logic or operator - uint256_t res(0); - res |= first; - res <<= 64; - res |= second; - res <<= 64; - res |= third; - res <<= 64; - res |= fourth; - return res; - } - - Amount::Amount() {} - - Amount::Amount(uint256_t value) : value_(value) {} - - Amount::Amount(uint256_t amount, uint8_t precision) - : value_(amount), precision_(precision) {} - - Amount::Amount(uint64_t first, - uint64_t second, - uint64_t third, - uint64_t fourth) - : Amount(first, second, third, fourth, 0) {} - - Amount::Amount(uint64_t first, - uint64_t second, - uint64_t third, - uint64_t fourth, - uint8_t precision) - : precision_(precision) { - value_ = getJointUint256(first, second, third, fourth); - } - - Amount::Amount(const Amount &am) - : value_(am.value_), precision_(am.precision_) {} - - Amount &Amount::operator=(const Amount &other) { - // check for self-assignment - if (&other == this) - return *this; - value_ = other.value_; - precision_ = other.precision_; - return *this; - } - - Amount::Amount(Amount &&am) : value_(am.value_), precision_(am.precision_) {} - - Amount &Amount::operator=(Amount &&other) { - std::swap(value_, other.value_); - std::swap(precision_, other.precision_); - return *this; - } - - boost::optional Amount::createFromString(std::string str_amount) { - // check if valid number - std::regex e("([0-9]*\\.[0-9]+|[0-9]+)"); - if (!std::regex_match(str_amount, e)) { - return boost::none; - } - - // get precision - auto dot_place = str_amount.find('.'); - size_t precision; - if (dot_place > str_amount.size()) { - precision = 0; - } else { - precision = str_amount.size() - dot_place - 1; - // erase dot from the string - str_amount.erase(std::remove(str_amount.begin(), str_amount.end(), '.'), - str_amount.end()); - } - - auto begin = str_amount.find_first_not_of('0'); - - // create uint256 value from obtained string - uint256_t value = 0; - if (begin <= str_amount.size()) { - value = uint256_t(str_amount.substr(begin)); - } - return Amount(value, precision); - } - - uint256_t Amount::getIntValue() { - return value_; - } - - uint8_t Amount::getPrecision() { - return precision_; - } - - std::vector Amount::to_uint64s() { - std::vector array(4); - for (int i = 0; i < 4; i++) { - constexpr boost::multiprecision::uint256_t mask_bits = - std::numeric_limits::max(); - uint64_t res = ((value_ >> (i * 64)) & mask_bits).convert_to(); - array[3 - i] = res; - } - return array; - } - - Amount Amount::percentage(uint256_t percents) const { - uint256_t new_val = value_ * percents / 100; - return {new_val, precision_}; - } - - Amount Amount::percentage(const Amount &am) const { - // multiply two amount values - uint256_t new_value = value_ * am.value_; - - // new value should be decreased by the scale of am to move floating point - // to the left, as it is done when we multiply manually - new_value /= uint256_t(pow(10, am.precision_)); - // to take percentage value we need divide by 100 - new_value /= 100; - return {new_value, precision_}; - } - - Amount Amount::add(const Amount &other) const { - auto new_val = value_ + other.value_; - return {new_val, precision_}; - } - - Amount Amount::subtract(const Amount &other) const { - auto new_val = value_ - other.value_; - return {new_val, precision_}; - } - - int Amount::compareTo(const Amount &other) const { - if (precision_ == other.precision_) { - return (value_ < other.value_) ? -1 : (value_ > other.value_) ? 1 : 0; - } - // when different precisions transform to have the same scale - auto max_precision = std::max(precision_, other.precision_); - auto val1 = value_ * ipow(10, max_precision - precision_); - auto val2 = other.value_ * ipow(10, max_precision - other.precision_); - return (val1 < val2) ? -1 : (val1 > val2) ? 1 : 0; - } - - bool Amount::operator==(const Amount &other) const { - return compareTo(other) == 0; - } - - bool Amount::operator!=(const Amount &other) const { - return compareTo(other) != 0; - } - - bool Amount::operator<(const Amount &other) const { - return compareTo(other) < 0; - } - - bool Amount::operator>(const Amount &other) const { - return compareTo(other) > 0; - } - - bool Amount::operator<=(const Amount &other) const { - return compareTo(other) <= 0; - } - - bool Amount::operator>=(const Amount &other) const { - return compareTo(other) >= 0; - } - - std::string Amount::to_string() const { - if (precision_ > 0) { - cpp_dec_float_50 float50(value_); - float50 /= pow(10, precision_); - return float50.str(precision_, std::ios_base::fixed); - } - return value_.str(0, std::ios_base::fixed); - } -} // namespace iroha diff --git a/libs/amount/amount.hpp b/libs/amount/amount.hpp deleted file mode 100644 index 3a472d1d9f..0000000000 --- a/libs/amount/amount.hpp +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef IROHA_AMOUNT_H -#define IROHA_AMOUNT_H - -#include -#include -#include -#include -#include -#include - -namespace iroha { - - /** - * Keeps integer and scale values allowing performing math - * operations on them - */ - class Amount { - public: - /** - * Creates Amount with integer = 0 and scale = 0 - */ - Amount(); - - /** - * Amount with integer = amount and scale = 0 - * @param amount integer part - */ - Amount(boost::multiprecision::uint256_t amount); - - /** - * Amount with provided integer and scale part - * @param amount integer part - * @param precision scale part - */ - Amount(boost::multiprecision::uint256_t amount, uint8_t precision); - - Amount(uint64_t first, uint64_t second, uint64_t third, uint64_t fourth); - - Amount(uint64_t first, - uint64_t second, - uint64_t third, - uint64_t fourth, - uint8_t precision); - - std::vector to_uint64s(); - - /** - * Copy constructor - */ - Amount(const Amount &); - Amount &operator=(const Amount &); - - /** - * Move constructor - */ - Amount(Amount &&); - Amount &operator=(Amount &&); - - boost::multiprecision::uint256_t getIntValue(); - uint8_t getPrecision(); - - static boost::optional createFromString(std::string str_amount); - - /** - * Takes percentage from current amount - * @param percents - * @return - */ - Amount percentage(boost::multiprecision::uint256_t percents) const; - - /** - * Takes percentage represented as amount value - * The current scale and scale of percents may differ - * @param percents - * @return - */ - Amount percentage(const Amount &percents) const; - - /** - * Sums up two optionals of the amounts. - * Requires to have the same scale. - * Otherwise nullopt is returned - * @param a left term - * @param b right term - * @param optional result - */ - friend boost::optional operator+(boost::optional a, - boost::optional b) { - // check precisions - if (a->precision_ != b->precision_) { - return boost::none; - } - auto res = a->add(*b); - // check overflow - if (res.value_ < a->value_ or res.value_ < b->value_) { - return boost::none; - } - return res; - } - - /** - * Subtracts right term from the left term - * Requires to have the same scale. - * Otherwise nullopt is returned - * @param a left term - * @param b right term - * @param optional result - */ - friend boost::optional operator-(boost::optional a, - boost::optional b) { - // check precisions - if (a->precision_ != b->precision_) { - return boost::none; - } - // check if a greater than b - if (a->value_ < b->value_) { - return boost::none; - } - return a->subtract(*b); - } - - /** - * Comparisons are possible between amounts with different precisions. - * - * @return - */ - bool operator==(const Amount &) const; - bool operator!=(const Amount &) const; - bool operator<(const Amount &) const; - bool operator>(const Amount &) const; - bool operator<=(const Amount &) const; - bool operator>=(const Amount &) const; - - std::string to_string() const; - ~Amount() = default; - - private: - /** - * Support function for comparison operators. - * Returns 0 when equal, -1 when current Amount smaller, and 1 when it is - * greater - * @param other - * @return - */ - int compareTo(const Amount &other) const; - - /** - * Sums two amounts. - * @return - */ - Amount add(const Amount &) const; - /** - * Subtracts one amount from another. - * Requires to have the same scale between both amounts. - * Otherwise nullopt is returned - * @return - */ - Amount subtract(const Amount &) const; - - boost::multiprecision::uint256_t value_{0}; - uint8_t precision_{0}; - }; -} // namespace iroha -#endif // IROHA_AMOUNT_H diff --git a/libs/cache/abstract_cache.hpp b/libs/cache/abstract_cache.hpp index fdab575acf..66bb14ae91 100644 --- a/libs/cache/abstract_cache.hpp +++ b/libs/cache/abstract_cache.hpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include namespace iroha { @@ -41,6 +41,8 @@ namespace iroha { * @return high border of cache limit (@see AbstractCache#addItem) */ uint32_t getIndexSizeHigh() const { + // shared lock + std::shared_lock lock(access_mutex_); return constUnderlying().getIndexSizeHighImpl(); } @@ -48,6 +50,8 @@ namespace iroha { * @return low border of cache limit (@see AbstractCache#addItem) */ uint32_t getIndexSizeLow() const { + // shared lock + std::shared_lock lock(access_mutex_); return constUnderlying().getIndexSizeLowImpl(); } @@ -55,6 +59,8 @@ namespace iroha { * @return amount of items in cache */ uint32_t getCacheItemCount() const { + // shared lock + std::shared_lock lock(access_mutex_); return constUnderlying().getCacheItemCountImpl(); } @@ -69,7 +75,8 @@ namespace iroha { * @param value - value to insert */ void addItem(const KeyType &key, const ValueType &value) { - std::lock_guard lock(access_mutex_); + // exclusive lock + std::lock_guard lock(access_mutex_); underlying().addItemImpl(key, value); } @@ -79,7 +86,8 @@ namespace iroha { * @return Optional of ValueType */ boost::optional findItem(const KeyType &key) const { - std::lock_guard lock(access_mutex_); + // shared lock + std::shared_lock lock(access_mutex_); return constUnderlying().findItemImpl(key); } @@ -91,7 +99,7 @@ namespace iroha { return static_cast(*this); } - mutable std::mutex access_mutex_; + mutable std::shared_timed_mutex access_mutex_; }; } // namespace cache } // namespace iroha diff --git a/libs/cache/single_pointer_cache.hpp b/libs/cache/single_pointer_cache.hpp new file mode 100644 index 0000000000..dc75fb606b --- /dev/null +++ b/libs/cache/single_pointer_cache.hpp @@ -0,0 +1,76 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_SINGLE_POINTER_CACHE_HPP +#define IROHA_SINGLE_POINTER_CACHE_HPP + +#include +#include + +namespace iroha { + namespace cache { + + /** + * Thread-safely stores and returns shared pointer to an element of template + * type + */ + template + class SinglePointerCache { + public: + /** + * Pointer to data type + */ + using DataPointer = std::shared_ptr; + + /** + * Insert data to the cache + * @param pointer to the data to be inserted + */ + void insert(DataPointer data); + + /** + * Get data from the cache + * @return pointer to the stored data + */ + DataPointer get() const; + + /** + * Delete data inside the cache + */ + void release(); + + private: + DataPointer stored_data_; + + mutable std::mutex mutex_; + }; + + template + void SinglePointerCache::insert( + SinglePointerCache::DataPointer data) { + std::lock_guard lock(mutex_); + + stored_data_ = std::move(data); + } + + template + typename SinglePointerCache::DataPointer + SinglePointerCache::get() const { + std::lock_guard lock(mutex_); + + return stored_data_; + } + + template + void SinglePointerCache::release() { + std::lock_guard lock(mutex_); + + stored_data_.reset(); + } + + } // namespace cache +} // namespace iroha + +#endif // IROHA_SINGLE_POINTER_CACHE_HPP diff --git a/libs/common/is_any.hpp b/libs/common/is_any.hpp new file mode 100644 index 0000000000..bf2d560719 --- /dev/null +++ b/libs/common/is_any.hpp @@ -0,0 +1,34 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_IS_ANY_HPP +#define IROHA_IS_ANY_HPP + +#include + +namespace iroha { + + template + struct is_any : std::false_type {}; + + template + struct is_any : std::is_same {}; + + /** + * Disjunctive type check. Returns true if first type is contained in provided + * list, false otherwise + * @tparam T first type + * @tparam First head of types list + * @tparam Rest tail of types list + */ + template + struct is_any + : std::integral_constant::value + || is_any::value> {}; + +} // namespace iroha + +#endif // IROHA_IS_ANY_HPP diff --git a/libs/common/timeout.hpp b/libs/common/timeout.hpp new file mode 100644 index 0000000000..fb66605758 --- /dev/null +++ b/libs/common/timeout.hpp @@ -0,0 +1,216 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_TIMEOUT_HPP +#define IROHA_TIMEOUT_HPP + +#include + +namespace iroha { + + /** + * This class is mostly the same as rxcpp::operators::timeout, + * the only change is that it accepts a selector lambda which generates + * a duration based on observable value instead of a fixed duration + * Return an observable that terminates with timeout_error if a particular + * timespan has passed without emitting another item from the source + * observable + * Timespan is generated with selector from the last received value + * @tparam T value type + * @tparam Selector the type of the transforming function + * which returns time interval + * @tparam Coordination the type of the scheduler + */ + template + struct timeout { + typedef rxcpp::util::decay_t source_value_type; + typedef rxcpp::util::decay_t coordination_type; + typedef typename coordination_type::coordinator_type coordinator_type; + typedef rxcpp::util::decay_t select_type; + + struct timeout_values { + timeout_values(select_type s, coordination_type c) + : selector(std::move(s)), coordination(c) {} + + select_type selector; + coordination_type coordination; + }; + timeout_values initial; + + timeout(select_type s, coordination_type coordination) + : initial(std::move(s), coordination) {} + + template + struct timeout_observer { + typedef timeout_observer this_type; + typedef rxcpp::util::decay_t value_type; + typedef rxcpp::util::decay_t dest_type; + typedef rxcpp::observer observer_type; + + struct timeout_subscriber_values : public timeout_values { + timeout_subscriber_values(rxcpp::composite_subscription cs, + dest_type d, + timeout_values v, + coordinator_type c) + : timeout_values(v), + cs(std::move(cs)), + dest(std::move(d)), + coordinator(std::move(c)), + worker(coordinator.get_worker()), + index(0) {} + + rxcpp::composite_subscription cs; + dest_type dest; + coordinator_type coordinator; + rxcpp::schedulers::worker worker; + mutable std::size_t index; + }; + typedef std::shared_ptr state_type; + state_type state; + + timeout_observer(rxcpp::composite_subscription cs, + dest_type d, + timeout_values v, + coordinator_type c) + : state(std::make_shared( + timeout_subscriber_values( + std::move(cs), std::move(d), v, std::move(c)))) { + auto localState = state; + + auto disposer = [=](const rxcpp::schedulers::schedulable &) { + localState->cs.unsubscribe(); + localState->dest.unsubscribe(); + localState->worker.unsubscribe(); + }; + auto selectedDisposer = on_exception( + [&]() { return localState->coordinator.act(disposer); }, + localState->dest); + if (selectedDisposer.empty()) { + return; + } + + localState->dest.add( + [=]() { localState->worker.schedule(selectedDisposer.get()); }); + localState->cs.add( + [=]() { localState->worker.schedule(selectedDisposer.get()); }); + } + + static std::function + produce_timeout(std::size_t id, state_type state) { + auto produce = [id, state](const rxcpp::schedulers::schedulable &) { + if (id != state->index) + return; + + state->dest.on_error(std::make_exception_ptr( + rxcpp::timeout_error("timeout has occurred"))); + }; + + auto selectedProduce = on_exception( + [&]() { return state->coordinator.act(produce); }, state->dest); + if (selectedProduce.empty()) { + return std::function(); + } + + return std::function( + selectedProduce.get()); + } + + template + void on_next(Value &&v) const { + auto localState = state; + + auto selected = on_exception( + [&]() { return localState->selector(std::forward(v)); }, + localState->dest); + if (selected.empty()) { + return; + } + + auto work = [v, localState, period = std::move(selected.get())]( + const rxcpp::schedulers::schedulable &) { + auto new_id = ++localState->index; + auto produce_time = localState->worker.now() + period; + + localState->dest.on_next(v); + localState->worker.schedule(produce_time, + produce_timeout(new_id, localState)); + }; + auto selectedWork = + on_exception([&]() { return localState->coordinator.act(work); }, + localState->dest); + if (selectedWork.empty()) { + return; + } + localState->worker.schedule(selectedWork.get()); + } + + void on_error(std::exception_ptr e) const { + auto localState = state; + auto work = [e, localState](const rxcpp::schedulers::schedulable &) { + localState->dest.on_error(e); + }; + auto selectedWork = + on_exception([&]() { return localState->coordinator.act(work); }, + localState->dest); + if (selectedWork.empty()) { + return; + } + localState->worker.schedule(selectedWork.get()); + } + + void on_completed() const { + auto localState = state; + auto work = [localState](const rxcpp::schedulers::schedulable &) { + localState->dest.on_completed(); + }; + auto selectedWork = + on_exception([&]() { return localState->coordinator.act(work); }, + localState->dest); + if (selectedWork.empty()) { + return; + } + localState->worker.schedule(selectedWork.get()); + } + + static rxcpp::subscriber make(dest_type d, + timeout_values v) { + auto cs = rxcpp::composite_subscription(); + auto coordinator = v.coordination.create_coordinator(); + + return rxcpp::make_subscriber( + cs, + observer_type(this_type( + cs, std::move(d), std::move(v), std::move(coordinator)))); + } + }; + + template + auto operator()(Subscriber dest) const + -> decltype(timeout_observer::make(std::move(dest), + initial)) { + return timeout_observer::make(std::move(dest), initial); + } + }; + + template < + typename T, + typename Selector, + typename Coordination, + class ResolvedSelector = rxcpp::util::decay_t, + class Duration = decltype( + std::declval()((std::declval>()))), + class Enabled = rxcpp::util::enable_if_all_true_type_t< + rxcpp::is_coordination, + rxcpp::util::is_duration>, + class Timeout = + timeout>> + static auto makeTimeout(Selector &&s, Coordination &&cn) { + return Timeout(std::forward(s), std::forward(cn)); + }; + +} // namespace iroha + +#endif // IROHA_TIMEOUT_HPP diff --git a/libs/crypto/keys_manager_impl.cpp b/libs/crypto/keys_manager_impl.cpp index f9d7d968f8..b557e5f447 100644 --- a/libs/crypto/keys_manager_impl.cpp +++ b/libs/crypto/keys_manager_impl.cpp @@ -37,11 +37,12 @@ namespace iroha { template static std::string encrypt(const T &key, const std::string &pass_phrase) { std::string ciphertext(key.size(), '\0'); + const size_t min_pass_size = 1; // pass_size will always be > 0 - const auto pass_size = std::max(1ul, pass_phrase.size()); + const auto pass_size = std::max(min_pass_size, pass_phrase.size()); // When pass_phrase is empty it, pass_phrase[0] is "\0", so no out_of_range // exception is possible - for (auto i = 0u; i < key.size(); i++) { + for (size_t i = 0; i < key.size(); i++) { ciphertext[i] = key[i] ^ pass_phrase[i % pass_size]; } return ciphertext; diff --git a/libs/logger/logger.cpp b/libs/logger/logger.cpp index 95453ab8ef..08553e6fa8 100644 --- a/libs/logger/logger.cpp +++ b/libs/logger/logger.cpp @@ -37,25 +37,28 @@ namespace logger { return red("<--- " + string); } - static void setGlobalPattern() { - spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%F] %n %v"); + static void setGlobalPattern(spdlog::logger &logger) { + logger.set_pattern("[%Y-%m-%d %H:%M:%S.%F] %n %v"); } - static void setDebugPattern() { - spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%F][th:%t][%l] %n %v"); + static void setDebugPattern(spdlog::logger &logger) { + logger.set_pattern("[%Y-%m-%d %H:%M:%S.%F][th:%t][%l] %n %v"); } static std::shared_ptr createLogger(const std::string &tag, bool debug_mode = true) { + auto logger = spdlog::stdout_color_mt(tag); if (debug_mode) { - setDebugPattern(); + setDebugPattern(*logger); } else { - setGlobalPattern(); + setGlobalPattern(*logger); } - return spdlog::stdout_color_mt(tag); + return logger; } Logger log(const std::string &tag) { + static std::mutex mutex; + std::lock_guard lock(mutex); auto logger = spdlog::get(tag); if (logger == nullptr) { logger = createLogger(tag); diff --git a/patch/close.patch b/patch/close.patch deleted file mode 100644 index 3138b895b6..0000000000 --- a/patch/close.patch +++ /dev/null @@ -1,29 +0,0 @@ -diff --git a/lib/randombytes/random/random.c b/lib/randombytes/random/random.c -index e5eca79..e6e862e 100644 ---- a/lib/randombytes/random/random.c -+++ b/lib/randombytes/random/random.c -@@ -13,6 +13,7 @@ int randombytes(unsigned char *p, int len) { - while (completed < len) { - ssize_t result = read(source, p + completed, len - completed); - if (result < 0) { -+ close(source); - return ED25519_ERROR; - } - completed += result; -diff --git a/lib/randombytes/urandom/urandom.c b/lib/randombytes/urandom/urandom.c -index ecad2cf..5b4ec0d 100644 ---- a/lib/randombytes/urandom/urandom.c -+++ b/lib/randombytes/urandom/urandom.c -@@ -9,9 +9,12 @@ int randombytes(unsigned char *p, int len) { - } else { - ssize_t result = read(source, p, len); - if (result < 0) { -+ close(source); - return ED25519_ERROR; /* something went wrong */ - } - } - -+ close(source); -+ - return ED25519_SUCCESS; - } diff --git a/patch/libprotobuf-mutator.patch b/patch/libprotobuf-mutator.patch new file mode 100644 index 0000000000..d816af0007 --- /dev/null +++ b/patch/libprotobuf-mutator.patch @@ -0,0 +1,107 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index c018d45..9497144 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -18,6 +18,7 @@ project(LibProtobufMutator CXX) + enable_language(C) + enable_language(CXX) + ++option(TESTING "Enable test building" ON) + option(LIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF + "Automatically download working protobuf" OFF) + option(LIB_PROTO_MUTATOR_WITH_ASAN "Enable address sanitizer" OFF) +@@ -110,20 +111,19 @@ else() + include_directories(${CMAKE_CURRENT_BINARY_DIR}) + endif() + +-enable_testing() +- +-include(googletest) ++if (TESTING) ++ enable_testing() ++ find_package(GTest) ++ if (NOT GTEST_FOUND) ++ include(googletest) ++ endif() + +-if (NOT LIB_PROTO_MUTATOR_CTEST_JOBS) +- ProcessorCount(LIB_PROTO_MUTATOR_CTEST_JOBS) ++ if (NOT LIB_PROTO_MUTATOR_CTEST_JOBS) ++ ProcessorCount(LIB_PROTO_MUTATOR_CTEST_JOBS) ++ endif() ++ add_custom_target(check ++ COMMAND ${CMAKE_CTEST_COMMAND} -j${LIB_PROTO_MUTATOR_CTEST_JOBS} --output-on-failure) + endif() +-add_custom_target(check +- COMMAND ${CMAKE_CTEST_COMMAND} -j${LIB_PROTO_MUTATOR_CTEST_JOBS} --output-on-failure) + + add_subdirectory(src) + +-if (NOT "${LIB_PROTO_MUTATOR_FUZZER_LIBRARIES}" STREQUAL "" OR +- NOT "${FUZZING_FLAGS}" STREQUAL "") +- add_subdirectory(examples EXCLUDE_FROM_ALL) +-endif() +- +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index 5c13d2d..624f8b6 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -24,31 +24,33 @@ target_link_libraries(protobuf-mutator + set_property(TARGET protobuf-mutator + PROPERTY COMPILE_FLAGS "${NO_FUZZING_FLAGS}") + +-protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS +- mutator_test_proto2.proto +- mutator_test_proto3.proto) ++if (TESTING) ++ protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ++ mutator_test_proto2.proto ++ mutator_test_proto3.proto) + +-add_executable(mutator_test +- mutator_test.cc +- utf8_fix_test.cc +- weighted_reservoir_sampler_test.cc +- ${PROTO_SRCS}) +-target_link_libraries(mutator_test +- protobuf-mutator +- ${ZLIB_LIBRARIES} +- ${GTEST_BOTH_LIBRARIES} +- ${CMAKE_THREAD_LIBS_INIT}) ++ add_executable(mutator_test ++ mutator_test.cc ++ utf8_fix_test.cc ++ weighted_reservoir_sampler_test.cc ++ ${PROTO_SRCS}) ++ target_link_libraries(mutator_test ++ protobuf-mutator ++ ${ZLIB_LIBRARIES} ++ ${GTEST_BOTH_LIBRARIES} ++ ${CMAKE_THREAD_LIBS_INIT}) + +-ProcessorCount(CPU_COUNT) +-math(EXPR TEST_SHARDS_COUNT 2*${CPU_COUNT}) +-math(EXPR TEST_SHARDS_MAX ${TEST_SHARDS_COUNT}-1) +-foreach(SHARD RANGE ${TEST_SHARDS_MAX}) +- add_test(test.protobuf_mutator_test_${SHARD} mutator_test --gtest_color=yes AUTO) +- set_property( +- TEST test.protobuf_mutator_test_${SHARD} +- APPEND PROPERTY ENVIRONMENT +- GTEST_SHARD_INDEX=${SHARD} +- GTEST_TOTAL_SHARDS=${TEST_SHARDS_COUNT}) +-endforeach(SHARD) ++ ProcessorCount(CPU_COUNT) ++ math(EXPR TEST_SHARDS_COUNT 2*${CPU_COUNT}) ++ math(EXPR TEST_SHARDS_MAX ${TEST_SHARDS_COUNT}-1) ++ foreach(SHARD RANGE ${TEST_SHARDS_MAX}) ++ add_test(test.protobuf_mutator_test_${SHARD} mutator_test --gtest_color=yes AUTO) ++ set_property( ++ TEST test.protobuf_mutator_test_${SHARD} ++ APPEND PROPERTY ENVIRONMENT ++ GTEST_SHARD_INDEX=${SHARD} ++ GTEST_TOTAL_SHARDS=${TEST_SHARDS_COUNT}) ++ endforeach(SHARD) + +-add_dependencies(check mutator_test) ++ add_dependencies(check mutator_test) ++endif() diff --git a/schema/.gitignore b/schema/.gitignore deleted file mode 100644 index ef4ee898b9..0000000000 --- a/schema/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.h -*.cc diff --git a/schema/CMakeLists.txt b/schema/CMakeLists.txt index 070f748287..b35bb294cd 100644 --- a/schema/CMakeLists.txt +++ b/schema/CMakeLists.txt @@ -1,97 +1,55 @@ -# -# Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. -# http://soramitsu.co.jp -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -compile_proto_to_cpp(block.proto) -compile_proto_to_cpp(proposal.proto) -compile_proto_to_cpp(primitive.proto) -compile_proto_to_cpp(commands.proto) -compile_proto_to_cpp(queries.proto) -compile_proto_to_cpp(responses.proto) -compile_proto_to_cpp(endpoint.proto) - -if (IROHA_ROOT_PROJECT) - compile_proto_to_grpc_cpp(endpoint.proto) - compile_proto_to_grpc_cpp(yac.proto) - compile_proto_to_grpc_cpp(ordering.proto) - compile_proto_to_grpc_cpp(loader.proto) - compile_proto_to_grpc_cpp(mst.proto) -endif () - -add_library(schema - block.pb.cc - commands.pb.cc - primitive.pb.cc - queries.pb.cc - responses.pb.cc - endpoint.pb.cc - proposal.pb.cc +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +set(SM_SCHEMA_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../shared_model/schema/) +set(SCHEMA_PATH ${SM_SCHEMA_PATH}) +compile_proto_only_grpc_to_cpp(endpoint.proto "-I${SM_SCHEMA_PATH}") +set(SCHEMA_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +compile_proto_to_grpc_cpp(yac.proto) +compile_proto_to_grpc_cpp(ordering.proto "-I${SM_SCHEMA_PATH}") +compile_proto_to_grpc_cpp(loader.proto "-I${SM_SCHEMA_PATH}") +compile_proto_to_grpc_cpp(mst.proto "-I${SM_SCHEMA_PATH}") + +add_library(endpoint + endpoint.grpc.pb.cc + ) +target_link_libraries(endpoint + grpc++ + schema ) -target_link_libraries(schema - protobuf +add_library(yac_grpc + yac.pb.cc + yac.grpc.pb.cc ) -target_include_directories(schema PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} +target_link_libraries(yac_grpc + protobuf + grpc++ ) -if (IROHA_ROOT_PROJECT) - add_library(endpoint - endpoint.pb.cc - endpoint.grpc.pb.cc - ) - target_link_libraries(endpoint - protobuf - grpc++ - schema - ) - - add_library(yac_grpc - yac.pb.cc - yac.grpc.pb.cc - ) - target_link_libraries(yac_grpc - protobuf - grpc++ - ) - - add_library(ordering_grpc - ordering.pb.cc - ordering.grpc.pb.cc - ) - target_link_libraries(ordering_grpc - schema - grpc++ - ) +add_library(ordering_grpc + ordering.pb.cc + ordering.grpc.pb.cc + ) +target_link_libraries(ordering_grpc + schema + grpc++ + ) - add_library(loader_grpc - loader.pb.cc - loader.grpc.pb.cc - ) - target_link_libraries(loader_grpc - schema - grpc++ - ) +add_library(loader_grpc + loader.pb.cc + loader.grpc.pb.cc + ) +target_link_libraries(loader_grpc + schema + grpc++ + ) - add_library(mst_grpc - mst.pb.cc - mst.grpc.pb.cc - ) - target_link_libraries(mst_grpc - schema - grpc++ - ) -endif () +add_library(mst_grpc + mst.pb.cc + mst.grpc.pb.cc + ) +target_link_libraries(mst_grpc + schema + grpc++ + ) diff --git a/schema/block.proto b/schema/block.proto deleted file mode 100644 index 5e2c891cfa..0000000000 --- a/schema/block.proto +++ /dev/null @@ -1,35 +0,0 @@ -syntax = "proto3"; -package iroha.protocol; -import "commands.proto"; -import "primitive.proto"; - -message Header { - uint64 created_time = 1; - repeated Signature signatures = 2; -} - - message Transaction { - message Payload { - repeated Command commands = 1; - string creator_account_id = 2; - uint64 created_time = 3; - uint32 quorum = 4; - } - - Payload payload = 1; - repeated Signature signatures = 2; - } - - message Block { - // everything that should be signed: - message Payload { - repeated Transaction transactions = 1; - uint32 tx_number = 2; // the number of transactions inside. Maximum 16384 or 2^14 - uint64 height = 3; // the current block number in a ledger - bytes prev_block_hash = 5; // Previous block hash - uint64 created_time = 6; - } - - Payload payload = 1; - repeated Signature signatures = 2; - } diff --git a/schema/mst.proto b/schema/mst.proto index 202b061201..5e9f37549d 100644 --- a/schema/mst.proto +++ b/schema/mst.proto @@ -1,18 +1,13 @@ syntax = "proto3"; package iroha.network.transport; -import "block.proto"; +import "transaction.proto"; +import "primitive.proto"; import "google/protobuf/empty.proto"; -// TODO: @l4l (04/05/18) remove in favor of primitive.proto IR-1321 -message Peer { - bytes pubkey = 1; - string address = 2; -} - message MstState { repeated iroha.protocol.Transaction transactions = 1; - Peer peer = 2; + iroha.protocol.Peer peer = 2; } service MstTransportGrpc { diff --git a/schema/ordering.proto b/schema/ordering.proto index 2c776fcd59..c8a3aac100 100644 --- a/schema/ordering.proto +++ b/schema/ordering.proto @@ -1,8 +1,9 @@ syntax = "proto3"; package iroha.ordering.proto; -import "block.proto"; +import "transaction.proto"; import "proposal.proto"; +import "endpoint.proto"; import "google/protobuf/empty.proto"; service OrderingGateTransportGrpc { @@ -11,4 +12,5 @@ service OrderingGateTransportGrpc { service OrderingServiceTransportGrpc { rpc onTransaction (iroha.protocol.Transaction) returns (google.protobuf.Empty); + rpc onBatch (iroha.protocol.TxList) returns (google.protobuf.Empty); } diff --git a/shared_model/CMakeLists.txt b/shared_model/CMakeLists.txt index a3eb447f6a..a567446448 100644 --- a/shared_model/CMakeLists.txt +++ b/shared_model/CMakeLists.txt @@ -1,33 +1,22 @@ -# Copyright 2018 Soramitsu Co., Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 cmake_minimum_required(VERSION 3.5.1) project(shared_model C CXX) -set(IROHA_SCHEMA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../schema") +set(IROHA_SCHEMA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/schema") +set(SM_SCHEMA_DIR "${IROHA_SCHEMA_DIR}") include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../libs - ${IROHA_SCHEMA_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) if (NOT IROHA_ROOT_PROJECT) set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) if (NOT MSVC) - set(CMAKE_CXX_FLAGS "-std=c++14 -Wall") - set(CMAKE_CXX_FLAGS_RELEASE "-O3") + set(CMAKE_CXX_FLAGS "-std=c++14 -Wall -fdiagnostics-color=always") + set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wno-error=deprecated-declarations") set(CMAKE_CXX_FLAGS_DEBUG "-g -Wextra -Wno-unused-parameter -Wno-deprecated-declarations -O0") endif () @@ -44,10 +33,9 @@ if (NOT IROHA_ROOT_PROJECT) include(FeatureSummary) include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/functions.cmake) include(cmake/dependencies.cmake) + set(SCHEMA_OUT_DIR ${CMAKE_BINARY_DIR}/schema) - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../schema schema) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../libs/generator generator) - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../libs/amount amount) endif () add_subdirectory(backend) @@ -58,6 +46,7 @@ add_subdirectory(cryptography) add_subdirectory(interfaces) add_subdirectory(utils) add_subdirectory(validators) +add_subdirectory(schema) if (NOT IROHA_ROOT_PROJECT) if (TESTING) diff --git a/shared_model/backend/protobuf/CMakeLists.txt b/shared_model/backend/protobuf/CMakeLists.txt index 27c1c40f2d..afa9734548 100644 --- a/shared_model/backend/protobuf/CMakeLists.txt +++ b/shared_model/backend/protobuf/CMakeLists.txt @@ -1,4 +1,11 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + add_library(shared_model_proto_backend + impl/block.cpp + impl/proposal.cpp impl/permissions.cpp commands/impl/proto_add_asset_quantity.cpp commands/impl/proto_add_peer.cpp @@ -28,6 +35,7 @@ add_library(shared_model_proto_backend queries/impl/proto_get_roles.cpp queries/impl/proto_get_signatories.cpp queries/impl/proto_get_transactions.cpp + queries/impl/proto_get_pending_transactions.cpp queries/impl/proto_blocks_query.cpp queries/impl/proto_query_payload_meta.cpp ) @@ -53,5 +61,4 @@ endif () target_link_libraries(shared_model_proto_backend schema shared_model_interfaces - iroha_amount ) diff --git a/shared_model/backend/protobuf/batch_meta.hpp b/shared_model/backend/protobuf/batch_meta.hpp new file mode 100644 index 0000000000..8bf48b70f8 --- /dev/null +++ b/shared_model/backend/protobuf/batch_meta.hpp @@ -0,0 +1,68 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_PROTO_BATCH_META_HPP +#define IROHA_PROTO_BATCH_META_HPP + +#include +#include + +#include "backend/protobuf/common_objects/trivial_proto.hpp" +#include "backend/protobuf/util.hpp" +#include "interfaces/common_objects/amount.hpp" +#include "interfaces/common_objects/types.hpp" +#include "transaction.pb.h" +#include "utils/lazy_initializer.hpp" + +namespace shared_model { + namespace proto { + class BatchMeta final + : public CopyableProto { + public: + template + explicit BatchMeta(BatchMetaType &&batch_meta) + : CopyableProto(std::forward(batch_meta)), + type_{[this] { + unsigned which = proto_->GetDescriptor() + ->FindFieldByName("type") + ->enum_type() + ->FindValueByNumber(proto_->type()) + ->index(); + return static_cast(which); + }}, + reduced_hashes_{[this] { + return boost::accumulate( + proto_->reduced_hashes(), + ReducedHashesType{}, + [](auto &&acc, const auto &hash) { + acc.emplace_back(hash); + return std::forward(acc); + }); + }} {} + + BatchMeta(const BatchMeta &o) : BatchMeta(o.proto_) {} + + BatchMeta(BatchMeta &&o) noexcept : BatchMeta(std::move(o.proto_)) {} + + interface::types::BatchType type() const override { + return *type_; + }; + const ReducedHashesType &reducedHashes() const override { + return *reduced_hashes_; + }; + + private: + template + using Lazy = detail::LazyInitializer; + + Lazy type_; + + const Lazy reduced_hashes_; + }; // namespace proto + } // namespace proto +} // namespace shared_model +#endif // IROHA_PROTO_AMOUNT_HPP diff --git a/shared_model/backend/protobuf/block.hpp b/shared_model/backend/protobuf/block.hpp index bed9ed4952..061c105a78 100644 --- a/shared_model/backend/protobuf/block.hpp +++ b/shared_model/backend/protobuf/block.hpp @@ -23,7 +23,7 @@ #include "backend/protobuf/common_objects/signature.hpp" #include "backend/protobuf/transaction.hpp" #include "backend/protobuf/util.hpp" -#include "common_objects/trivial_proto.hpp" +#include "common_objects/noncopyable_proto.hpp" #include "interfaces/common_objects/types.hpp" #include "block.pb.h" @@ -31,95 +31,57 @@ namespace shared_model { namespace proto { - class Block final : public CopyableProto { + class Block final : public NonCopyableProto { public: - template - explicit Block(BlockType &&block) - : CopyableProto(std::forward(block)) {} + using NonCopyableProto::NonCopyableProto; - Block(const Block &o) : Block(o.proto_) {} - - Block(Block &&o) noexcept : Block(std::move(o.proto_)) {} + Block(Block &&o) noexcept; + Block &operator=(Block &&o) noexcept; interface::types::TransactionsCollectionType transactions() - const override { - return *transactions_; - } + const override; - interface::types::HeightType height() const override { - return payload_.height(); - } + interface::types::HeightType height() const override; - const interface::types::HashType &prevHash() const override { - return *prev_hash_; - } + const interface::types::HashType &prevHash() const override; - const interface::types::BlobType &blob() const override { - return *blob_; - } + const interface::types::BlobType &blob() const override; - interface::types::SignatureRangeType signatures() const override { - return *signatures_; - } + interface::types::SignatureRangeType signatures() const override; - // TODO Alexey Chernyshov - 2018-03-28 - - // rework code duplication after fix protobuf - // https://soramitsu.atlassian.net/browse/IR-1175 bool addSignature(const crypto::Signed &signed_blob, - const crypto::PublicKey &public_key) override { - // if already has such signature - if (std::find_if(signatures_->begin(), - signatures_->end(), - [&public_key](const auto &signature) { - return signature.publicKey() == public_key; - }) - != signatures_->end()) { - return false; - } - - auto sig = proto_->add_signatures(); - sig->set_signature(crypto::toBinaryString(signed_blob)); - sig->set_pubkey(crypto::toBinaryString(public_key)); - - signatures_.invalidate(); - return true; - } - - interface::types::TimestampType createdTime() const override { - return payload_.created_time(); - } - - interface::types::TransactionsNumberType txsNumber() const override { - return payload_.tx_number(); - } - - const interface::types::BlobType &payload() const override { - return *payload_blob_; - } + const crypto::PublicKey &public_key) override; + + interface::types::TimestampType createdTime() const override; + + interface::types::TransactionsNumberType txsNumber() const override; + + const interface::types::BlobType &payload() const override; private: // lazy template using Lazy = detail::LazyInitializer; - const iroha::protocol::Block::Payload &payload_{proto_->payload()}; + iroha::protocol::Block::Payload &payload_{*proto_.mutable_payload()}; - const Lazy> transactions_{[this] { - return std::vector(payload_.transactions().begin(), - payload_.transactions().end()); + Lazy> transactions_{[this] { + return std::vector( + payload_.mutable_transactions()->begin(), + payload_.mutable_transactions()->end()); }}; - const Lazy blob_{ - [this] { return makeBlob(*proto_); }}; + Lazy blob_{ + [this] { return makeBlob(proto_); }}; - const Lazy prev_hash_{[this] { - return interface::types::HashType(proto_->payload().prev_block_hash()); + Lazy prev_hash_{[this] { + return interface::types::HashType(proto_.payload().prev_block_hash()); }}; - const Lazy> signatures_{[this] { - auto signatures = proto_->signatures() + Lazy> signatures_{[this] { + auto signatures = proto_.signatures() | boost::adaptors::transformed([](const auto &x) { return proto::Signature(x); }); @@ -127,7 +89,7 @@ namespace shared_model { signatures.end()); }}; - const Lazy payload_blob_{ + Lazy payload_blob_{ [this] { return makeBlob(payload_); }}; }; } // namespace proto diff --git a/shared_model/backend/protobuf/commands/impl/proto_add_asset_quantity.cpp b/shared_model/backend/protobuf/commands/impl/proto_add_asset_quantity.cpp index eef87be27b..e3ec072c42 100644 --- a/shared_model/backend/protobuf/commands/impl/proto_add_asset_quantity.cpp +++ b/shared_model/backend/protobuf/commands/impl/proto_add_asset_quantity.cpp @@ -12,8 +12,9 @@ namespace shared_model { AddAssetQuantity::AddAssetQuantity(CommandType &&command) : CopyableProto(std::forward(command)), add_asset_quantity_{proto_->add_asset_quantity()}, - amount_{ - [this] { return proto::Amount(add_asset_quantity_.amount()); }} {} + amount_{[this] { + return interface::Amount(add_asset_quantity_.amount()); + }} {} // TODO 30/05/2018 andrei Reduce boilerplate code in variant classes template AddAssetQuantity::AddAssetQuantity( @@ -29,10 +30,6 @@ namespace shared_model { AddAssetQuantity::AddAssetQuantity(AddAssetQuantity &&o) noexcept : AddAssetQuantity(std::move(o.proto_)) {} - const interface::types::AccountIdType &AddAssetQuantity::accountId() const { - return add_asset_quantity_.account_id(); - } - const interface::types::AssetIdType &AddAssetQuantity::assetId() const { return add_asset_quantity_.asset_id(); } diff --git a/shared_model/backend/protobuf/commands/impl/proto_subtract_asset_quantity.cpp b/shared_model/backend/protobuf/commands/impl/proto_subtract_asset_quantity.cpp index 032e33e2a6..c94264b36b 100644 --- a/shared_model/backend/protobuf/commands/impl/proto_subtract_asset_quantity.cpp +++ b/shared_model/backend/protobuf/commands/impl/proto_subtract_asset_quantity.cpp @@ -13,7 +13,7 @@ namespace shared_model { : CopyableProto(std::forward(command)), subtract_asset_quantity_{proto_->subtract_asset_quantity()}, amount_{[this] { - return proto::Amount(subtract_asset_quantity_.amount()); + return interface::Amount(subtract_asset_quantity_.amount()); }} {} template SubtractAssetQuantity::SubtractAssetQuantity( @@ -30,11 +30,6 @@ namespace shared_model { SubtractAssetQuantity &&o) noexcept : SubtractAssetQuantity(std::move(o.proto_)) {} - const interface::types::AccountIdType &SubtractAssetQuantity::accountId() - const { - return subtract_asset_quantity_.account_id(); - } - const interface::types::AssetIdType &SubtractAssetQuantity::assetId() const { return subtract_asset_quantity_.asset_id(); diff --git a/shared_model/backend/protobuf/commands/impl/proto_transfer_asset.cpp b/shared_model/backend/protobuf/commands/impl/proto_transfer_asset.cpp index 308a824c2a..c4c45cb7f9 100644 --- a/shared_model/backend/protobuf/commands/impl/proto_transfer_asset.cpp +++ b/shared_model/backend/protobuf/commands/impl/proto_transfer_asset.cpp @@ -12,7 +12,8 @@ namespace shared_model { TransferAsset::TransferAsset(CommandType &&command) : CopyableProto(std::forward(command)), transfer_asset_{proto_->transfer_asset()}, - amount_{[this] { return proto::Amount(transfer_asset_.amount()); }} {} + amount_{ + [this] { return interface::Amount(transfer_asset_.amount()); }} {} template TransferAsset::TransferAsset(TransferAsset::TransportType &); template TransferAsset::TransferAsset(const TransferAsset::TransportType &); diff --git a/shared_model/backend/protobuf/commands/proto_add_asset_quantity.hpp b/shared_model/backend/protobuf/commands/proto_add_asset_quantity.hpp index e7ba66d1c6..fceb3996a5 100644 --- a/shared_model/backend/protobuf/commands/proto_add_asset_quantity.hpp +++ b/shared_model/backend/protobuf/commands/proto_add_asset_quantity.hpp @@ -20,9 +20,9 @@ #include "interfaces/commands/add_asset_quantity.hpp" -#include "backend/protobuf/common_objects/amount.hpp" #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "commands.pb.h" +#include "interfaces/common_objects/amount.hpp" #include "utils/lazy_initializer.hpp" namespace shared_model { @@ -39,8 +39,6 @@ namespace shared_model { AddAssetQuantity(AddAssetQuantity &&o) noexcept; - const interface::types::AccountIdType &accountId() const override; - const interface::types::AssetIdType &assetId() const override; const interface::Amount &amount() const override; @@ -52,7 +50,7 @@ namespace shared_model { const iroha::protocol::AddAssetQuantity &add_asset_quantity_; - const Lazy amount_; + const Lazy amount_; }; } // namespace proto diff --git a/shared_model/backend/protobuf/commands/proto_add_peer.hpp b/shared_model/backend/protobuf/commands/proto_add_peer.hpp index 8e03adf5e7..054f82171a 100644 --- a/shared_model/backend/protobuf/commands/proto_add_peer.hpp +++ b/shared_model/backend/protobuf/commands/proto_add_peer.hpp @@ -19,6 +19,7 @@ #define IROHA_PROTO_ADD_PEER_HPP #include "backend/protobuf/common_objects/peer.hpp" +#include "commands.pb.h" #include "interfaces/commands/add_peer.hpp" #include "interfaces/common_objects/peer.hpp" diff --git a/shared_model/backend/protobuf/commands/proto_subtract_asset_quantity.hpp b/shared_model/backend/protobuf/commands/proto_subtract_asset_quantity.hpp index a69d2fd760..1ab529e42f 100644 --- a/shared_model/backend/protobuf/commands/proto_subtract_asset_quantity.hpp +++ b/shared_model/backend/protobuf/commands/proto_subtract_asset_quantity.hpp @@ -20,9 +20,9 @@ #include "interfaces/commands/subtract_asset_quantity.hpp" -#include "backend/protobuf/common_objects/amount.hpp" #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "commands.pb.h" +#include "interfaces/common_objects/amount.hpp" #include "utils/lazy_initializer.hpp" namespace shared_model { @@ -39,8 +39,6 @@ namespace shared_model { SubtractAssetQuantity(SubtractAssetQuantity &&o) noexcept; - const interface::types::AccountIdType &accountId() const override; - const interface::types::AssetIdType &assetId() const override; const interface::Amount &amount() const override; @@ -52,7 +50,7 @@ namespace shared_model { const iroha::protocol::SubtractAssetQuantity &subtract_asset_quantity_; - const Lazy amount_; + const Lazy amount_; }; } // namespace proto diff --git a/shared_model/backend/protobuf/commands/proto_transfer_asset.hpp b/shared_model/backend/protobuf/commands/proto_transfer_asset.hpp index 1274e8727a..da5b7bda2f 100644 --- a/shared_model/backend/protobuf/commands/proto_transfer_asset.hpp +++ b/shared_model/backend/protobuf/commands/proto_transfer_asset.hpp @@ -20,8 +20,8 @@ #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "commands.pb.h" -#include "backend/protobuf/common_objects/amount.hpp" #include "interfaces/commands/transfer_asset.hpp" +#include "interfaces/common_objects/amount.hpp" namespace shared_model { namespace proto { @@ -54,7 +54,7 @@ namespace shared_model { const iroha::protocol::TransferAsset &transfer_asset_; - const Lazy amount_; + const Lazy amount_; }; } // namespace proto diff --git a/shared_model/backend/protobuf/common_objects/account.hpp b/shared_model/backend/protobuf/common_objects/account.hpp index 5a2a27b2e7..296b0acdee 100644 --- a/shared_model/backend/protobuf/common_objects/account.hpp +++ b/shared_model/backend/protobuf/common_objects/account.hpp @@ -21,7 +21,7 @@ #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "backend/protobuf/util.hpp" #include "interfaces/common_objects/account.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/common_objects/account_asset.hpp b/shared_model/backend/protobuf/common_objects/account_asset.hpp index 43b0e8e4a3..5537fc6d93 100644 --- a/shared_model/backend/protobuf/common_objects/account_asset.hpp +++ b/shared_model/backend/protobuf/common_objects/account_asset.hpp @@ -18,11 +18,10 @@ #ifndef IROHA_PROTO_ACCOUNT_ASSET_HPP #define IROHA_PROTO_ACCOUNT_ASSET_HPP -#include "backend/protobuf/common_objects/amount.hpp" #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "backend/protobuf/util.hpp" #include "interfaces/common_objects/account_asset.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { @@ -57,7 +56,8 @@ namespace shared_model { template using Lazy = detail::LazyInitializer; - const Lazy balance_{[this] { return Amount(proto_->balance()); }}; + const Lazy balance_{ + [this] { return interface::Amount(proto_->balance()); }}; }; } // namespace proto } // namespace shared_model diff --git a/shared_model/backend/protobuf/common_objects/amount.hpp b/shared_model/backend/protobuf/common_objects/amount.hpp deleted file mode 100644 index 0817a2000c..0000000000 --- a/shared_model/backend/protobuf/common_objects/amount.hpp +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef IROHA_PROTO_AMOUNT_HPP -#define IROHA_PROTO_AMOUNT_HPP - -#include "interfaces/common_objects/amount.hpp" - -#include - -#include "backend/protobuf/common_objects/trivial_proto.hpp" -#include "backend/protobuf/util.hpp" -#include "primitive.pb.h" -#include "utils/lazy_initializer.hpp" - -namespace shared_model { - namespace proto { - - /** - * converts protobuf amount to uint256_t, assuming big-endian order. - * @param amount - protobuf object which is assumed to have 4 values - * @return uint256_t representation of proto object - */ - template - boost::multiprecision::uint256_t convertToUInt256( - const AmountType &amount) noexcept { - using boost::multiprecision::uint256_t; - constexpr auto offset = 64u; - uint256_t result; - result |= uint256_t{amount.first()} << offset * 3; - result |= uint256_t{amount.second()} << offset * 2; - result |= uint256_t{amount.third()} << offset; - result |= uint256_t{amount.fourth()}; - return result; - } - - /** - * Sets protobuf value to specified uint256_t. - * @param value - protobuf value, which will be changed - * @param amount - integer value - */ - template - void convertToProtoAmount( - ValueType &value, - const boost::multiprecision::uint256_t &amount) noexcept { - constexpr auto offset = 64u; - constexpr boost::multiprecision::uint256_t mask_bits = - std::numeric_limits::max(); - auto convert = [&](auto i) { - // Select two middle bits from 011011 and offset = 2 - // 011011 >> (2 * 1) = 000110 - // Have to mask 2 high bits to prevent any overflows - // 000110 & 000011 = 000010 - return ((amount >> (offset * i)) & mask_bits) - .template convert_to(); - }; - value.set_first(convert(3)); - value.set_second(convert(2)); - value.set_third(convert(1)); - value.set_fourth(convert(0)); - } - - class Amount final : public CopyableProto { - public: - template - explicit Amount(AmountType &&amount) - : CopyableProto(std::forward(amount)), - multiprecision_repr_( - [this] { return convertToUInt256(proto_->value()); }) {} - - Amount(const Amount &o) : Amount(o.proto_) {} - - Amount(Amount &&o) noexcept : Amount(std::move(o.proto_)) {} - - const boost::multiprecision::uint256_t &intValue() const override { - return *multiprecision_repr_; - } - - interface::types::PrecisionType precision() const override { - return proto_->precision(); - } - - private: - // lazy - template - using Lazy = detail::LazyInitializer; - - const Lazy multiprecision_repr_; - }; - } // namespace proto -} // namespace shared_model -#endif // IROHA_PROTO_AMOUNT_HPP diff --git a/shared_model/backend/protobuf/common_objects/asset.hpp b/shared_model/backend/protobuf/common_objects/asset.hpp index 16ffd72bbe..2ee32f941d 100644 --- a/shared_model/backend/protobuf/common_objects/asset.hpp +++ b/shared_model/backend/protobuf/common_objects/asset.hpp @@ -21,7 +21,7 @@ #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "backend/protobuf/util.hpp" #include "interfaces/common_objects/asset.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/common_objects/domain.hpp b/shared_model/backend/protobuf/common_objects/domain.hpp index 7e0ecfb2ce..24bdb71b3b 100644 --- a/shared_model/backend/protobuf/common_objects/domain.hpp +++ b/shared_model/backend/protobuf/common_objects/domain.hpp @@ -20,7 +20,7 @@ #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "interfaces/common_objects/domain.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/common_objects/noncopyable_proto.hpp b/shared_model/backend/protobuf/common_objects/noncopyable_proto.hpp new file mode 100644 index 0000000000..1ccb970f67 --- /dev/null +++ b/shared_model/backend/protobuf/common_objects/noncopyable_proto.hpp @@ -0,0 +1,41 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_NONCOPYABLE_PROTO_HPP +#define IROHA_NONCOPYABLE_PROTO_HPP + +/** + * Generic class for handling proto objects which are not intended to be copied. + * @tparam Iface is interface to inherit from + * @tparam Proto is protobuf container + * @tparam Impl is implementation of Iface + */ +template +class NonCopyableProto : public Iface { + public: + using TransportType = Proto; + + /* + * Construct object from transport. Transport can be moved or copied. + */ + template + NonCopyableProto(Transport &&ref) : proto_(std::forward(ref)){} + + NonCopyableProto(const NonCopyableProto &o) = delete; + NonCopyableProto &operator=(const NonCopyableProto &o) = delete; + + const Proto &getTransport() const { + return proto_; + } + + protected: + typename Iface::ModelType *clone() const override final { + return new Impl(proto_); + } + + Proto proto_; +}; + +#endif // IROHA_NONCOPYABLE_PROTO_HPP diff --git a/shared_model/backend/protobuf/common_objects/peer.hpp b/shared_model/backend/protobuf/common_objects/peer.hpp index 29bc72b2e1..c4cb3a0b1e 100644 --- a/shared_model/backend/protobuf/common_objects/peer.hpp +++ b/shared_model/backend/protobuf/common_objects/peer.hpp @@ -21,7 +21,7 @@ #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "backend/protobuf/util.hpp" #include "interfaces/common_objects/peer.hpp" -#include "responses.pb.h" +#include "primitive.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/common_objects/proto_common_objects_factory.hpp b/shared_model/backend/protobuf/common_objects/proto_common_objects_factory.hpp new file mode 100644 index 0000000000..5da490fc3f --- /dev/null +++ b/shared_model/backend/protobuf/common_objects/proto_common_objects_factory.hpp @@ -0,0 +1,201 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_PROTO_COMMON_OBJECTS_FACTORY_HPP +#define IROHA_PROTO_COMMON_OBJECTS_FACTORY_HPP + +#include + +#include "backend/protobuf/common_objects/account.hpp" +#include "backend/protobuf/common_objects/account_asset.hpp" +#include "backend/protobuf/common_objects/asset.hpp" +#include "backend/protobuf/common_objects/domain.hpp" +#include "backend/protobuf/common_objects/peer.hpp" +#include "backend/protobuf/common_objects/signature.hpp" +#include "common/result.hpp" +#include "interfaces/common_objects/common_objects_factory.hpp" +#include "primitive.pb.h" +#include "validators/answer.hpp" + +namespace shared_model { + namespace proto { + /** + * ProtoCommonObjectsFactory constructs protobuf-based objects. + * It performs stateless validation with provided validator + * @tparam Validator + */ + template + class ProtoCommonObjectsFactory : public interface::CommonObjectsFactory { + public: + FactoryResult> createPeer( + const interface::types::AddressType &address, + const interface::types::PubkeyType &public_key) override { + iroha::protocol::Peer peer; + peer.set_address(address); + peer.set_peer_key(crypto::toBinaryString(public_key)); + auto proto_peer = std::make_unique(std::move(peer)); + + auto errors = + validate(*proto_peer, [this](const auto &peer, auto &reasons) { + validator_.validatePeer(reasons, peer); + }); + + if (errors) { + return iroha::expected::makeError(errors.reason()); + } + + return iroha::expected::makeValue>( + std::move(proto_peer)); + } + + FactoryResult> createAccount( + const interface::types::AccountIdType &account_id, + const interface::types::DomainIdType &domain_id, + interface::types::QuorumType quorum, + const interface::types::JsonType &jsonData) override { + iroha::protocol::Account account; + account.set_account_id(account_id); + account.set_domain_id(domain_id); + account.set_quorum(quorum); + account.set_json_data(jsonData); + + auto proto_account = std::make_unique(std::move(account)); + + auto errors = validate( + *proto_account, [this](const auto &account, auto &reasons) { + validator_.validateAccountId(reasons, account.accountId()); + validator_.validateDomainId(reasons, account.domainId()); + validator_.validateQuorum(reasons, account.quorum()); + }); + + if (errors) { + return iroha::expected::makeError(errors.reason()); + } + + return iroha::expected::makeValue>( + std::move(proto_account)); + } + + FactoryResult> + createAccountAsset(const interface::types::AccountIdType &account_id, + const interface::types::AssetIdType &asset_id, + const interface::Amount &balance) override { + iroha::protocol::AccountAsset asset; + asset.set_account_id(account_id); + asset.set_asset_id(asset_id); + asset.set_balance(balance.toStringRepr()); + + auto proto_asset = std::make_unique(std::move(asset)); + + auto errors = + validate(*proto_asset, [this](const auto &asset, auto &reasons) { + validator_.validateAccountId(reasons, asset.accountId()); + validator_.validateAssetId(reasons, asset.assetId()); + }); + + if (errors) { + return iroha::expected::makeError(errors.reason()); + } + + return iroha::expected::makeValue< + std::unique_ptr>(std::move(proto_asset)); + } + + FactoryResult> createAsset( + const interface::types::AssetIdType &asset_id, + const interface::types::DomainIdType &domain_id, + interface::types::PrecisionType precision) override { + iroha::protocol::Asset asset; + asset.set_asset_id(asset_id); + asset.set_domain_id(domain_id); + asset.set_precision(precision); + + auto proto_asset = std::make_unique(std::move(asset)); + + auto errors = + validate(*proto_asset, [this](const auto &asset, auto &reasons) { + validator_.validateAssetId(reasons, asset.assetId()); + validator_.validateDomainId(reasons, asset.domainId()); + }); + + if (errors) { + return iroha::expected::makeError(errors.reason()); + } + + return iroha::expected::makeValue>( + std::move(proto_asset)); + } + + FactoryResult> createDomain( + const interface::types::DomainIdType &domain_id, + const interface::types::RoleIdType &default_role) override { + iroha::protocol::Domain domain; + domain.set_domain_id(domain_id); + domain.set_default_role(default_role); + + auto proto_domain = std::make_unique(std::move(domain)); + + auto errors = + validate(*proto_domain, [this](const auto &domain, auto &reason) { + validator_.validateDomainId(reason, domain.domainId()); + validator_.validateRoleId(reason, domain.defaultRole()); + }); + + if (errors) { + return iroha::expected::makeError(errors.reason()); + } + + return iroha::expected::makeValue>( + std::move(proto_domain)); + } + + FactoryResult> createSignature( + const interface::types::PubkeyType &key, + const interface::Signature::SignedType &signed_data) override { + iroha::protocol::Signature signature; + signature.set_pubkey(crypto::toBinaryString(key)); + signature.set_signature(crypto::toBinaryString(signed_data)); + + auto proto_singature = + std::make_unique(std::move(signature)); + + auto errors = validate( + *proto_singature, [this](const auto &signature, auto &reason) { + validator_.validatePubkey(reason, signature.publicKey()); + }); + + if (errors) { + return iroha::expected::makeError(errors.reason()); + } + + return iroha::expected::makeValue< + std::unique_ptr>(std::move(proto_singature)); + } + + private: + /** + * Perform validation of a given object + * @param o - object to be validated + * @param f - function which populates reason parameter with errors. + * second parameter (reasons) must be passed by non-const reference + * @return validation result + */ + template + validation::Answer validate(const T &o, ValidationFunc &&f) const { + shared_model::validation::Answer errors; + validation::ReasonsGroupType reasons; + f(o, reasons); + if (not reasons.second.empty()) { + errors.addReason(std::move(reasons)); + } + return errors; + } + + Validator validator_; + }; + } // namespace proto +} // namespace shared_model + +#endif // IROHA_PROTO_COMMON_OBJECTS_FACTORY_HPP diff --git a/shared_model/backend/protobuf/from_old.hpp b/shared_model/backend/protobuf/from_old.hpp deleted file mode 100644 index 9407c1e1a9..0000000000 --- a/shared_model/backend/protobuf/from_old.hpp +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef SHARED_MODEL_PROTOBUF_FROM_OLD -#define SHARED_MODEL_PROTOBUF_FROM_OLD - -#include -#include -#include "backend/protobuf/permissions.hpp" - -namespace shared_model { - namespace interface { - namespace permissions { - static inline Role fromOldR(const std::string &s) { - iroha::protocol::RolePermission role; - if (not RolePermission_Parse(s, &role)) { - throw std::invalid_argument(s); - } - return proto::permissions::fromTransport(role); - } - - static inline RolePermissionSet fromOldR(const std::set &s) { - RolePermissionSet set{}; - for (auto &el : s) { - set.set(fromOldR(el)); - } - return set; - } - } // namespace permissions - } // namespace interface -} // namespace shared_model - -#endif // SHARED_MODEL_PROTOBUF_FROM_OLD diff --git a/shared_model/backend/protobuf/impl/block.cpp b/shared_model/backend/protobuf/impl/block.cpp new file mode 100644 index 0000000000..759c9d8d18 --- /dev/null +++ b/shared_model/backend/protobuf/impl/block.cpp @@ -0,0 +1,80 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "backend/protobuf/block.hpp" + +namespace shared_model { + namespace proto { + Block::Block(Block &&o) noexcept : NonCopyableProto(std::move(o.proto_)) {} + + Block &Block::operator=(Block &&o) noexcept { + proto_ = std::move(o.proto_); + payload_ = *proto_.mutable_payload(); + + transactions_.invalidate(); + blob_.invalidate(); + prev_hash_.invalidate(); + signatures_.invalidate(); + payload_blob_.invalidate(); + + return *this; + } + + interface::types::TransactionsCollectionType Block::transactions() const { + return *transactions_; + } + + interface::types::HeightType Block::height() const { + return payload_.height(); + } + + const interface::types::HashType &Block::prevHash() const { + return *prev_hash_; + } + + const interface::types::BlobType &Block::blob() const { + return *blob_; + } + + interface::types::SignatureRangeType Block::signatures() const { + return *signatures_; + } + + // TODO Alexey Chernyshov - 2018-03-28 - + // rework code duplication from transaction, block after fix protobuf + // https://soramitsu.atlassian.net/browse/IR-1175 + bool Block::addSignature(const crypto::Signed &signed_blob, + const crypto::PublicKey &public_key) { + // if already has such signature + if (std::find_if(signatures_->begin(), + signatures_->end(), + [&public_key](const auto &signature) { + return signature.publicKey() == public_key; + }) + != signatures_->end()) { + return false; + } + + auto sig = proto_.add_signatures(); + sig->set_signature(crypto::toBinaryString(signed_blob)); + sig->set_pubkey(crypto::toBinaryString(public_key)); + + signatures_.invalidate(); + return true; + } + + interface::types::TimestampType Block::createdTime() const { + return payload_.created_time(); + } + + interface::types::TransactionsNumberType Block::txsNumber() const { + return payload_.tx_number(); + } + + const interface::types::BlobType &Block::payload() const { + return *payload_blob_; + } + } // namespace proto +} // namespace shared_model diff --git a/shared_model/backend/protobuf/impl/proposal.cpp b/shared_model/backend/protobuf/impl/proposal.cpp new file mode 100644 index 0000000000..53ddd5c71b --- /dev/null +++ b/shared_model/backend/protobuf/impl/proposal.cpp @@ -0,0 +1,35 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "backend/protobuf/proposal.hpp" + +namespace shared_model { + namespace proto { + using namespace interface::types; + + Proposal::Proposal(Proposal &&o) noexcept + : NonCopyableProto(std::move(o.proto_)) {} + + Proposal &Proposal::operator=(Proposal &&o) noexcept { + proto_ = std::move(o.proto_); + transactions_.invalidate(); + + return *this; + } + + TransactionsCollectionType Proposal::transactions() const { + return *transactions_; + } + + TimestampType Proposal::createdTime() const { + return proto_.created_time(); + } + + HeightType Proposal::height() const { + return proto_.height(); + } + + } // namespace proto +} // namespace shared_model diff --git a/shared_model/backend/protobuf/proposal.hpp b/shared_model/backend/protobuf/proposal.hpp index 56a9f67f74..e16d0e4719 100644 --- a/shared_model/backend/protobuf/proposal.hpp +++ b/shared_model/backend/protobuf/proposal.hpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_SHARED_MODEL_PROTO_PROPOSAL_HPP @@ -21,48 +9,38 @@ #include "backend/protobuf/transaction.hpp" #include "interfaces/iroha_internal/proposal.hpp" -#include "common_objects/trivial_proto.hpp" +#include "common_objects/noncopyable_proto.hpp" #include "interfaces/common_objects/types.hpp" #include "proposal.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { - namespace proto { - class Proposal final : public CopyableProto { + class Proposal final : public NonCopyableProto { public: - template - explicit Proposal(ProposalType &&proposal) - : CopyableProto(std::forward(proposal)) {} - - Proposal(const Proposal &o) : Proposal(o.proto_) {} + using NonCopyableProto::NonCopyableProto; - Proposal(Proposal &&o) noexcept : Proposal(std::move(o.proto_)) {} + Proposal(Proposal &&o) noexcept; + Proposal &operator=(Proposal &&o) noexcept; interface::types::TransactionsCollectionType transactions() - const override { - return *transactions_; - } + const override; - interface::types::TimestampType createdTime() const override { - return proto_->created_time(); - } + interface::types::TimestampType createdTime() const override; - interface::types::HeightType height() const override { - return proto_->height(); - } + interface::types::HeightType height() const override; private: - // lazy template using Lazy = detail::LazyInitializer; const Lazy> transactions_{[this] { - return std::vector(proto_->transactions().begin(), - proto_->transactions().end()); + return std::vector( + proto_.mutable_transactions()->begin(), + proto_.mutable_transactions()->end()); }}; }; } // namespace proto diff --git a/shared_model/backend/protobuf/proto_proposal_factory.hpp b/shared_model/backend/protobuf/proto_proposal_factory.hpp new file mode 100644 index 0000000000..84047b232c --- /dev/null +++ b/shared_model/backend/protobuf/proto_proposal_factory.hpp @@ -0,0 +1,82 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_PROTO_PROPOSAL_FACTORY_HPP +#define IROHA_PROTO_PROPOSAL_FACTORY_HPP + +#include "backend/protobuf/proposal.hpp" +#include "interfaces/iroha_internal/proposal_factory.hpp" +#include "interfaces/iroha_internal/unsafe_proposal_factory.hpp" +#include "proposal.pb.h" + +namespace shared_model { + namespace proto { + template + class ProtoProposalFactory : public interface::ProposalFactory, + public interface::UnsafeProposalFactory { + public: + FactoryResult> createProposal( + interface::types::HeightType height, + interface::types::TimestampType created_time, + const interface::types::TransactionsCollectionType &transactions) + override { + return createProposal( + createProtoProposal(height, created_time, transactions)); + } + + std::unique_ptr unsafeCreateProposal( + interface::types::HeightType height, + interface::types::TimestampType created_time, + const interface::types::TransactionsCollectionType &transactions) + override { + return std::make_unique( + createProtoProposal(height, created_time, transactions)); + } + + /** + * Create and validate proposal using protobuf object + */ + FactoryResult> createProposal( + const iroha::protocol::Proposal &proposal) { + return validate(std::make_unique(proposal)); + } + + private: + iroha::protocol::Proposal createProtoProposal( + interface::types::HeightType height, + interface::types::TimestampType created_time, + const interface::types::TransactionsCollectionType &transactions) { + iroha::protocol::Proposal proposal; + + proposal.set_height(height); + proposal.set_created_time(created_time); + + for (const auto &tx : transactions) { + *proposal.add_transactions() = + static_cast(tx) + .getTransport(); + } + + return proposal; + } + + FactoryResult> validate( + std::unique_ptr proposal) { + auto errors = validator_.validate(*proposal); + + if (errors) { + return iroha::expected::makeError(errors.reason()); + } + + return iroha::expected::makeValue>( + std::move(proposal)); + } + + Validator validator_; + }; + } // namespace proto +} // namespace shared_model + +#endif // IROHA_PROTO_PROPOSAL_FACTORY_HPP diff --git a/shared_model/backend/protobuf/queries/impl/proto_get_account_detail.cpp b/shared_model/backend/protobuf/queries/impl/proto_get_account_detail.cpp index 156f7e3668..9e50bcdff4 100644 --- a/shared_model/backend/protobuf/queries/impl/proto_get_account_detail.cpp +++ b/shared_model/backend/protobuf/queries/impl/proto_get_account_detail.cpp @@ -27,7 +27,23 @@ namespace shared_model { : GetAccountDetail(std::move(o.proto_)) {} const interface::types::AccountIdType &GetAccountDetail::accountId() const { - return account_detail_.account_id(); + return account_detail_.opt_account_id_case() + ? account_detail_.account_id() + : proto_->payload().meta().creator_account_id(); + } + + boost::optional + GetAccountDetail::key() const { + return account_detail_.opt_key_case() + ? boost::make_optional(account_detail_.key()) + : boost::none; + } + + boost::optional GetAccountDetail::writer() + const { + return account_detail_.opt_writer_case() + ? boost::make_optional(account_detail_.writer()) + : boost::none; } } // namespace proto diff --git a/shared_model/backend/protobuf/queries/impl/proto_get_pending_transactions.cpp b/shared_model/backend/protobuf/queries/impl/proto_get_pending_transactions.cpp new file mode 100644 index 0000000000..9b90b1b033 --- /dev/null +++ b/shared_model/backend/protobuf/queries/impl/proto_get_pending_transactions.cpp @@ -0,0 +1,31 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "backend/protobuf/queries/proto_get_pending_transactions.hpp" + +namespace shared_model { + namespace proto { + + template + GetPendingTransactions::GetPendingTransactions(QueryType &&query) + : CopyableProto(std::forward(query)) {} + + template GetPendingTransactions::GetPendingTransactions( + GetPendingTransactions::TransportType &); + template GetPendingTransactions::GetPendingTransactions( + const GetPendingTransactions::TransportType &); + template GetPendingTransactions::GetPendingTransactions( + GetPendingTransactions::TransportType &&); + + GetPendingTransactions::GetPendingTransactions( + const GetPendingTransactions &o) + : GetPendingTransactions(o.proto_) {} + + GetPendingTransactions::GetPendingTransactions( + GetPendingTransactions &&o) noexcept + : GetPendingTransactions(std::move(o.proto_)) {} + + } // namespace proto +} // namespace shared_model diff --git a/shared_model/backend/protobuf/queries/proto_get_account_detail.hpp b/shared_model/backend/protobuf/queries/proto_get_account_detail.hpp index 066694d9e4..d167f8b48e 100644 --- a/shared_model/backend/protobuf/queries/proto_get_account_detail.hpp +++ b/shared_model/backend/protobuf/queries/proto_get_account_detail.hpp @@ -38,6 +38,10 @@ namespace shared_model { const interface::types::AccountIdType &accountId() const override; + boost::optional key() const override; + + boost::optional writer() const override; + private: // ------------------------------| fields |------------------------------- diff --git a/shared_model/backend/protobuf/queries/proto_get_pending_transactions.hpp b/shared_model/backend/protobuf/queries/proto_get_pending_transactions.hpp new file mode 100644 index 0000000000..97ba387b44 --- /dev/null +++ b/shared_model/backend/protobuf/queries/proto_get_pending_transactions.hpp @@ -0,0 +1,30 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_PROTO_GET_PENDING_TRANSACTIONS_HPP +#define IROHA_PROTO_GET_PENDING_TRANSACTIONS_HPP + +#include "backend/protobuf/common_objects/trivial_proto.hpp" +#include "interfaces/queries/get_pending_transactions.hpp" +#include "queries.pb.h" + +namespace shared_model { + namespace proto { + class GetPendingTransactions final + : public CopyableProto { + public: + template + explicit GetPendingTransactions(QueryType &&query); + + GetPendingTransactions(const GetPendingTransactions &o); + + GetPendingTransactions(GetPendingTransactions &&o) noexcept; + }; + } // namespace proto +} // namespace shared_model + +#endif // IROHA_PROTO_GET_PENDING_TRANSACTIONS_HPP diff --git a/shared_model/backend/protobuf/queries/proto_query.hpp b/shared_model/backend/protobuf/queries/proto_query.hpp index 0bd4768cd9..c984950293 100644 --- a/shared_model/backend/protobuf/queries/proto_query.hpp +++ b/shared_model/backend/protobuf/queries/proto_query.hpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_SHARED_MODEL_PROTO_QUERY_HPP @@ -32,6 +20,7 @@ #include "backend/protobuf/queries/proto_get_roles.hpp" #include "backend/protobuf/queries/proto_get_signatories.hpp" #include "backend/protobuf/queries/proto_get_transactions.hpp" +#include "backend/protobuf/queries/proto_get_pending_transactions.hpp" #include "queries.pb.h" #include "utils/lazy_initializer.hpp" @@ -51,7 +40,8 @@ namespace shared_model { GetAccountDetail, GetRoles, GetRolePermissions, - GetAssetInfo>; + GetAssetInfo, + GetPendingTransactions>; /// list of types in proto variant using ProtoQueryListType = ProtoQueryVariantType::types; diff --git a/shared_model/backend/protobuf/query_responses/impl/proto_role_permissions_response.cpp b/shared_model/backend/protobuf/query_responses/impl/proto_role_permissions_response.cpp index 32c68bb1c2..3cd47c5941 100644 --- a/shared_model/backend/protobuf/query_responses/impl/proto_role_permissions_response.cpp +++ b/shared_model/backend/protobuf/query_responses/impl/proto_role_permissions_response.cpp @@ -5,7 +5,6 @@ #include "backend/protobuf/query_responses/proto_role_permissions_response.hpp" #include -#include "backend/protobuf/from_old.hpp" #include "backend/protobuf/permissions.hpp" #include "utils/string_builder.hpp" diff --git a/shared_model/backend/protobuf/query_responses/proto_account_asset_response.hpp b/shared_model/backend/protobuf/query_responses/proto_account_asset_response.hpp index 217a5d7050..5b072afcaf 100644 --- a/shared_model/backend/protobuf/query_responses/proto_account_asset_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_account_asset_response.hpp @@ -24,10 +24,9 @@ #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "interfaces/common_objects/types.hpp" #include "interfaces/query_responses/account_asset_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" - namespace shared_model { namespace proto { class AccountAssetResponse final @@ -42,7 +41,8 @@ namespace shared_model { AccountAssetResponse(AccountAssetResponse &&o); - const interface::types::AccountAssetCollectionType accountAssets() const override; + const interface::types::AccountAssetCollectionType accountAssets() + const override; private: template diff --git a/shared_model/backend/protobuf/query_responses/proto_account_detail_response.hpp b/shared_model/backend/protobuf/query_responses/proto_account_detail_response.hpp index f6df0be1d5..fdb2d5850f 100644 --- a/shared_model/backend/protobuf/query_responses/proto_account_detail_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_account_detail_response.hpp @@ -21,7 +21,7 @@ #include "backend/protobuf/common_objects/account_asset.hpp" #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "interfaces/query_responses/account_detail_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/query_responses/proto_account_response.hpp b/shared_model/backend/protobuf/query_responses/proto_account_response.hpp index a6be1d2f21..00c83cb42e 100644 --- a/shared_model/backend/protobuf/query_responses/proto_account_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_account_response.hpp @@ -23,7 +23,7 @@ #include "backend/protobuf/common_objects/account.hpp" #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "interfaces/query_responses/account_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/query_responses/proto_asset_response.hpp b/shared_model/backend/protobuf/query_responses/proto_asset_response.hpp index 695d61a82c..0c50efab44 100644 --- a/shared_model/backend/protobuf/query_responses/proto_asset_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_asset_response.hpp @@ -21,7 +21,7 @@ #include "backend/protobuf/common_objects/asset.hpp" #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "interfaces/query_responses/asset_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/query_responses/proto_block_error_response.hpp b/shared_model/backend/protobuf/query_responses/proto_block_error_response.hpp index 79b7096864..0d9322d9e9 100644 --- a/shared_model/backend/protobuf/query_responses/proto_block_error_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_block_error_response.hpp @@ -8,7 +8,7 @@ #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "interfaces/query_responses/block_error_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { @@ -25,7 +25,7 @@ namespace shared_model { BlockErrorResponse(BlockErrorResponse &&o); - const interface::types::DescriptionType &message() const override ; + const interface::types::DescriptionType &message() const override; private: template diff --git a/shared_model/backend/protobuf/query_responses/proto_block_query_response.hpp b/shared_model/backend/protobuf/query_responses/proto_block_query_response.hpp index 95b6d0327d..079c0073c6 100644 --- a/shared_model/backend/protobuf/query_responses/proto_block_query_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_block_query_response.hpp @@ -12,7 +12,7 @@ #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "interfaces/queries/query.hpp" #include "interfaces/query_responses/block_query_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" #include "utils/variant_deserializer.hpp" diff --git a/shared_model/backend/protobuf/query_responses/proto_block_response.hpp b/shared_model/backend/protobuf/query_responses/proto_block_response.hpp index 7b25631793..42e2eef472 100644 --- a/shared_model/backend/protobuf/query_responses/proto_block_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_block_response.hpp @@ -9,7 +9,7 @@ #include "backend/protobuf/block.hpp" #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "interfaces/query_responses/block_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/query_responses/proto_concrete_error_query_response.hpp b/shared_model/backend/protobuf/query_responses/proto_concrete_error_query_response.hpp index e6b31f7029..171e2ac269 100644 --- a/shared_model/backend/protobuf/query_responses/proto_concrete_error_query_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_concrete_error_query_response.hpp @@ -28,7 +28,7 @@ #include "interfaces/query_responses/error_responses/not_supported_error_response.hpp" #include "interfaces/query_responses/error_responses/stateful_failed_error_response.hpp" #include "interfaces/query_responses/error_responses/stateless_failed_error_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" namespace shared_model { namespace proto { diff --git a/shared_model/backend/protobuf/query_responses/proto_error_query_response.hpp b/shared_model/backend/protobuf/query_responses/proto_error_query_response.hpp index 1526d98048..b43d8389ec 100644 --- a/shared_model/backend/protobuf/query_responses/proto_error_query_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_error_query_response.hpp @@ -21,7 +21,7 @@ #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "backend/protobuf/query_responses/proto_concrete_error_query_response.hpp" #include "interfaces/query_responses/error_query_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/query_responses/proto_query_response.hpp b/shared_model/backend/protobuf/query_responses/proto_query_response.hpp index eb79b93c0e..185931ca9e 100644 --- a/shared_model/backend/protobuf/query_responses/proto_query_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_query_response.hpp @@ -30,7 +30,7 @@ #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "interfaces/query_responses/query_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/query_responses/proto_role_permissions_response.hpp b/shared_model/backend/protobuf/query_responses/proto_role_permissions_response.hpp index e6d075d6e1..d5fc704a66 100644 --- a/shared_model/backend/protobuf/query_responses/proto_role_permissions_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_role_permissions_response.hpp @@ -20,7 +20,7 @@ #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "interfaces/query_responses/role_permissions.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/query_responses/proto_roles_response.hpp b/shared_model/backend/protobuf/query_responses/proto_roles_response.hpp index f141e1fa3f..d78d1c1511 100644 --- a/shared_model/backend/protobuf/query_responses/proto_roles_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_roles_response.hpp @@ -20,7 +20,7 @@ #include "backend/protobuf/common_objects/trivial_proto.hpp" #include "interfaces/query_responses/roles_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/query_responses/proto_signatories_response.hpp b/shared_model/backend/protobuf/query_responses/proto_signatories_response.hpp index 31eb7cb320..cf5beb68f9 100644 --- a/shared_model/backend/protobuf/query_responses/proto_signatories_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_signatories_response.hpp @@ -21,7 +21,7 @@ #include "interfaces/query_responses/signatories_response.hpp" #include "backend/protobuf/common_objects/trivial_proto.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/query_responses/proto_transaction_response.hpp b/shared_model/backend/protobuf/query_responses/proto_transaction_response.hpp index d2535f2923..25f26b4740 100644 --- a/shared_model/backend/protobuf/query_responses/proto_transaction_response.hpp +++ b/shared_model/backend/protobuf/query_responses/proto_transaction_response.hpp @@ -22,7 +22,7 @@ #include "backend/protobuf/transaction.hpp" #include "interfaces/common_objects/types.hpp" #include "interfaces/query_responses/transactions_response.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" #include "utils/lazy_initializer.hpp" namespace shared_model { diff --git a/shared_model/backend/protobuf/transaction.hpp b/shared_model/backend/protobuf/transaction.hpp index d650a4bd43..ea17910493 100644 --- a/shared_model/backend/protobuf/transaction.hpp +++ b/shared_model/backend/protobuf/transaction.hpp @@ -24,7 +24,7 @@ #include "backend/protobuf/commands/proto_command.hpp" #include "backend/protobuf/common_objects/signature.hpp" -#include "block.pb.h" +#include "batch_meta.hpp" #include "utils/lazy_initializer.hpp" namespace shared_model { @@ -43,7 +43,7 @@ namespace shared_model { : Transaction(std::move(o.proto_)) {} const interface::types::AccountIdType &creatorAccountId() const override { - return payload_.creator_account_id(); + return reduced_payload_.creator_account_id(); } Transaction::CommandsType commands() const override { @@ -58,6 +58,10 @@ namespace shared_model { return *blobTypePayload_; } + const interface::types::BlobType &reducedPayload() const override { + return *blobTypeReducedPayload_; + } + interface::types::SignatureRangeType signatures() const override { return *signatures_; } @@ -83,11 +87,16 @@ namespace shared_model { } interface::types::TimestampType createdTime() const override { - return payload_.created_time(); + return reduced_payload_.created_time(); } interface::types::QuorumType quorum() const override { - return payload_.quorum(); + return reduced_payload_.quorum(); + } + + boost::optional> batchMeta() + const override { + return *meta_; } private: @@ -97,9 +106,12 @@ namespace shared_model { const iroha::protocol::Transaction::Payload &payload_{proto_->payload()}; + const iroha::protocol::Transaction::Payload::ReducedPayload + reduced_payload_{proto_->payload().reduced_payload()}; + const Lazy> commands_{[this] { - return std::vector(payload_.commands().begin(), - payload_.commands().end()); + return std::vector(reduced_payload_.commands().begin(), + reduced_payload_.commands().end()); }}; const Lazy blob_{ @@ -108,6 +120,19 @@ namespace shared_model { const Lazy blobTypePayload_{ [this] { return makeBlob(payload_); }}; + const Lazy blobTypeReducedPayload_{ + [this] { return makeBlob(reduced_payload_); }}; + + const Lazy>> meta_{ + [this]() -> boost::optional> { + if (payload_.has_batch()) { + std::shared_ptr b = + std::make_shared(payload_.batch()); + return b; + } + return boost::none; + }}; + const Lazy> signatures_{[this] { auto signatures = proto_->signatures() | boost::adaptors::transformed([](const auto &x) { diff --git a/shared_model/bindings/CMakeLists.txt b/shared_model/bindings/CMakeLists.txt index ef5fc7ffbe..f1a8d46ee8 100644 --- a/shared_model/bindings/CMakeLists.txt +++ b/shared_model/bindings/CMakeLists.txt @@ -18,6 +18,7 @@ add_library(bindings model_query_builder.cpp model_blocks_query_builder.cpp model_crypto.cpp + client_api.cpp ) target_link_libraries(bindings @@ -25,7 +26,6 @@ target_link_libraries(bindings shared_model_cryptography generator shared_model_stateless_validation - iroha_amount ) @@ -34,13 +34,6 @@ if (SWIG_PYTHON OR SWIG_JAVA OR SWIG_CSHARP OR SWIG_NODE) find_package(swig REQUIRED) include(${SWIG_USE_FILE}) - if (SWIG_JAVA AND SWIG_JAVA_PKG) - set(CMAKE_SWIG_FLAGS -package ${SWIG_JAVA_PKG}) - string(REPLACE "." "/" CMAKE_SWIG_OUTDIR ${SWIG_JAVA_PKG}) - else() - set(CMAKE_SWIG_FLAGS "") - endif() - set_source_files_properties(bindings.i PROPERTIES CPLUSPLUS ON) set_property(GLOBAL PROPERTY SWIG_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}) @@ -89,7 +82,14 @@ endif() if (SWIG_JAVA) find_package(JNI REQUIRED) + if (SWIG_JAVA_PKG) + set(CMAKE_SWIG_FLAGS -package ${SWIG_JAVA_PKG}) + string(REPLACE "." "/" CMAKE_SWIG_OUTDIR ${SWIG_JAVA_PKG}) + else() + set(CMAKE_SWIG_FLAGS "") + endif() myswig_add_library(irohajava LANGUAGE java SOURCES bindings.i) + unset(CMAKE_SWIG_FLAGS) swig_link_libraries(irohajava ${Java_LIBRARIES} bindings) # the include path to jni.h and jni_md.h target_include_directories(${SWIG_MODULE_irohajava_REAL_NAME} PUBLIC diff --git a/shared_model/bindings/bindings.i b/shared_model/bindings/bindings.i index 07ed2f83da..e530555100 100644 --- a/shared_model/bindings/bindings.i +++ b/shared_model/bindings/bindings.i @@ -124,6 +124,7 @@ namespace std { #include "bindings/model_crypto.hpp" #include "bindings/model_proto.hpp" #include "builders/protobuf/unsigned_proto.hpp" +#include "bindings/client_api.hpp" %} %include "cryptography/blob.hpp" @@ -146,6 +147,7 @@ namespace std { %include "bindings/model_blocks_query_builder.hpp" %include "bindings/model_crypto.hpp" %include "bindings/model_proto.hpp" +%include "bindings/client_api.hpp" %template (UnsignedTx) shared_model::proto::UnsignedWrapper; %template (UnsignedQuery) shared_model::proto::UnsignedWrapper; diff --git a/shared_model/bindings/client_api.cpp b/shared_model/bindings/client_api.cpp new file mode 100644 index 0000000000..fbaf0cdf49 --- /dev/null +++ b/shared_model/bindings/client_api.cpp @@ -0,0 +1,124 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "bindings/client_api.hpp" +#include "backend/protobuf/transaction.hpp" +#include "backend/protobuf/util.hpp" +#include "common/types.hpp" +#include "cryptography/crypto_provider/crypto_signer.hpp" +#include "cryptography/ed25519_sha3_impl/internal/sha3_hash.hpp" +#include "validators/default_validator.hpp" + +namespace shared_model { + namespace bindings { + + std::string convert(const Blob &blob) { + return std::string(blob.begin(), blob.end()); + } + + template + boost::optional get(const std::string &blob) { + Proto proto; + if (proto.ParseFromString(blob)) { + return proto; + } + return {}; + } + + using namespace iroha; + void validateTransaction(const Blob &b) { + auto blob = convert(b); + auto s = get(blob) | [](auto tx) { + static validation::DefaultSignedTransactionValidator val; + return boost::make_optional( + val.validate(proto::Transaction(tx)).reason()); + }; + if (s) { + auto &r = s.value(); + if (r == "") { + return; + } + throw std::invalid_argument(r); + } + throw std::invalid_argument("unknown object"); + } + + void validateQuery(const Blob &b) { + auto blob = convert(b); + auto s = get(blob) | [](auto qry) { + static validation::DefaultSignableQueryValidator val; + return boost::make_optional(val.validate(proto::Query(qry)).reason()); + }; + if (s) { + auto &r = s.value(); + if (r == "") { + return; + } + throw std::invalid_argument(r); + } + throw std::invalid_argument("unknown object"); + } + + Blob signTransaction(const Blob &b, const crypto::Keypair &key) { + auto blob = convert(b); + auto s = get(blob) | [&key](auto tx) { + auto signature = + crypto::CryptoSigner<>::sign(proto::makeBlob(tx.payload()), key); + + auto sig = tx.add_signatures(); + sig->set_signature(crypto::toBinaryString(signature)); + sig->set_pubkey(crypto::toBinaryString(key.publicKey())); + return boost::make_optional(tx); + }; + if (s) { + return proto::makeBlob(s.value()).blob(); + } + throw std::invalid_argument("unknown object"); + } + + Blob signQuery(const Blob &b, const crypto::Keypair &key) { + auto blob = convert(b); + auto s = get(blob) | [&key](auto qry) { + auto signature = + crypto::CryptoSigner<>::sign(proto::makeBlob(qry.payload()), key); + + auto sig = qry.mutable_signature(); + sig->set_signature(crypto::toBinaryString(signature)); + sig->set_pubkey(crypto::toBinaryString(key.publicKey())); + return boost::make_optional(qry); + }; + if (s) { + return proto::makeBlob(s.value()).blob(); + } + throw std::invalid_argument("unknown object"); + } + + Blob hashTransaction(const Blob &b) { + auto blob = convert(b); + auto s = get(blob) | [](auto tx) { + auto pl = proto::makeBlob(tx.payload()); + return boost::make_optional( + sha3_256(pl.blob().data(), pl.blob().size())); + }; + if (s) { + return Blob(s->begin(), s->end()); + } + throw std::invalid_argument("unknown object"); + } + + Blob hashQuery(const Blob &b) { + auto blob = convert(b); + auto s = get(blob) | [](auto qry) { + auto pl = proto::makeBlob(qry.payload()); + return boost::make_optional( + sha3_256(pl.blob().data(), pl.blob().size())); + }; + if (s) { + return Blob(s->begin(), s->end()); + } + throw std::invalid_argument("unknown object"); + } + } // namespace bindings +} // namespace shared_model diff --git a/shared_model/bindings/client_api.hpp b/shared_model/bindings/client_api.hpp new file mode 100644 index 0000000000..591ba18b70 --- /dev/null +++ b/shared_model/bindings/client_api.hpp @@ -0,0 +1,56 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "cryptography/keypair.hpp" + +namespace shared_model { + namespace bindings { + using Blob = std::vector; + + /** + * Validate protobuf transaction + * @param blob to validate + * @return string with errors, empty if none + */ + void validateTransaction(const Blob &); + + /** + * Validate protobuf query + * @param blob to validate + * @return string with errors, empty if none + */ + void validateQuery(const Blob &); + + /** + * Signs protobuf transaction + * @param blob to sign + * @param key is keypair for signing + * @return signed blob + */ + Blob signTransaction(const Blob &, const crypto::Keypair &); + + /** + * Signs protobuf query + * @param blob to sign + * @param key is keypair for signing + * @return signed blob + */ + Blob signQuery(const Blob &, const crypto::Keypair &); + + /** + * Get the hash of given protobuf transaction + * @param blob to calculate hash from + * @return hash of the blob + */ + Blob hashTransaction(const Blob &); + + /** + * Get the hash of given protobuf query + * @param blob to calculate hash from + * @return hash of the blob + */ + Blob hashQuery(const Blob &); + } // namespace bindings +} // namespace shared_model diff --git a/shared_model/bindings/model_query_builder.cpp b/shared_model/bindings/model_query_builder.cpp index 9ee0acfb3e..58e5e37d3e 100644 --- a/shared_model/bindings/model_query_builder.cpp +++ b/shared_model/bindings/model_query_builder.cpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include "bindings/model_query_builder.hpp" @@ -85,8 +73,15 @@ namespace shared_model { } ModelQueryBuilder ModelQueryBuilder::getAccountDetail( - const interface::types::AccountIdType &account_id) { - return ModelQueryBuilder(builder_.getAccountDetail(account_id)); + const interface::types::AccountIdType &account_id, + const interface::types::AccountDetailKeyType &key, + const interface::types::AccountIdType &writer) { + return ModelQueryBuilder( + builder_.getAccountDetail(account_id, key, writer)); + } + + ModelQueryBuilder ModelQueryBuilder::getPendingTransactions() { + return ModelQueryBuilder(builder_.getPendingTransactions()); } proto::UnsignedWrapper ModelQueryBuilder::build() { diff --git a/shared_model/bindings/model_query_builder.hpp b/shared_model/bindings/model_query_builder.hpp index 1fcd63a160..ed7d256aef 100644 --- a/shared_model/bindings/model_query_builder.hpp +++ b/shared_model/bindings/model_query_builder.hpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_SHARED_MODEL_MODEL_QUERY_BUILDER_HPP @@ -135,11 +123,21 @@ namespace shared_model { /** * Retrieves details for a given account * @param account_id - account to retrieve details from - * @param detail - key to retrieve + * @param key - under which keys data should be returned + * @param writer - from which writers details should be returned * @return builder with getAccountDetail query inside */ ModelQueryBuilder getAccountDetail( - const interface::types::AccountIdType &account_id); + const interface::types::AccountIdType &account_id = "", + const interface::types::AccountDetailKeyType &key = "", + const interface::types::AccountIdType &writer = ""); + + /** + * Retrieves all pending (not fully signed) multisignature transactions or + * batches of transactions. + * @return builder with getPendingTransactions query inside + */ + ModelQueryBuilder getPendingTransactions(); /** * Builds result with all appended fields diff --git a/shared_model/bindings/model_transaction_builder.cpp b/shared_model/bindings/model_transaction_builder.cpp index 3886471563..728a9b39a4 100644 --- a/shared_model/bindings/model_transaction_builder.cpp +++ b/shared_model/bindings/model_transaction_builder.cpp @@ -38,12 +38,17 @@ namespace shared_model { return ModelTransactionBuilder(builder_.quorum(quorum)); } + ModelTransactionBuilder ModelTransactionBuilder::batchMeta( + interface::types::BatchType type, + const std::vector &hashes) { + return ModelTransactionBuilder(builder_.batchMeta(type, hashes)); + } + ModelTransactionBuilder ModelTransactionBuilder::addAssetQuantity( - const interface::types::AccountIdType &account_id, const interface::types::AssetIdType &asset_id, const std::string &amount) { return ModelTransactionBuilder( - builder_.addAssetQuantity(account_id, asset_id, amount)); + builder_.addAssetQuantity(asset_id, amount)); } ModelTransactionBuilder ModelTransactionBuilder::addPeer( @@ -140,11 +145,10 @@ namespace shared_model { } ModelTransactionBuilder ModelTransactionBuilder::subtractAssetQuantity( - const interface::types::AccountIdType &account_id, const interface::types::AssetIdType &asset_id, const std::string &amount) { return ModelTransactionBuilder( - builder_.subtractAssetQuantity(account_id, asset_id, amount)); + builder_.subtractAssetQuantity(asset_id, amount)); } ModelTransactionBuilder ModelTransactionBuilder::transferAsset( diff --git a/shared_model/bindings/model_transaction_builder.hpp b/shared_model/bindings/model_transaction_builder.hpp index 478076892a..1b678e1d91 100644 --- a/shared_model/bindings/model_transaction_builder.hpp +++ b/shared_model/bindings/model_transaction_builder.hpp @@ -61,15 +61,23 @@ namespace shared_model { */ ModelTransactionBuilder quorum(interface::types::QuorumType quorum); + /** + * Sets batch meta + * @param type - one of ATOMIC or ORDERED + * @param hashes - vector of hashes of transactions in this batch + * @return builder with batchMeta set + */ + ModelTransactionBuilder batchMeta( + interface::types::BatchType type, + const std::vector &hashes); + /** * Adds given quantity of given asset to account - * @param account_id - account id * @param asset_id - asset id * @param amount - amount of asset to add * @return builder with asset quantity command appended */ ModelTransactionBuilder addAssetQuantity( - const interface::types::AccountIdType &account_id, const interface::types::AssetIdType &asset_id, const std::string &amount); @@ -211,13 +219,11 @@ namespace shared_model { /** * Subtracts asset quantity - * @param account_id - account id to subtract asset quantity from * @param asset_id - asset id to subtract * @param amount - amount to subtract * @return builder with subtract asset quantity command appended */ ModelTransactionBuilder subtractAssetQuantity( - const interface::types::AccountIdType &account_id, const interface::types::AssetIdType &asset_id, const std::string &amount); diff --git a/shared_model/builders/common_objects/account_asset_builder.hpp b/shared_model/builders/common_objects/account_asset_builder.hpp index 5cb1b1f9b1..cf3915b38f 100644 --- a/shared_model/builders/common_objects/account_asset_builder.hpp +++ b/shared_model/builders/common_objects/account_asset_builder.hpp @@ -35,7 +35,7 @@ namespace shared_model { * to perform stateless validation on model fields */ template - class AccountAssetBuilder + class DEPRECATED AccountAssetBuilder : public CommonObjectBuilder { diff --git a/shared_model/builders/common_objects/account_builder.hpp b/shared_model/builders/common_objects/account_builder.hpp index cac6611e6f..33465f7e99 100644 --- a/shared_model/builders/common_objects/account_builder.hpp +++ b/shared_model/builders/common_objects/account_builder.hpp @@ -35,9 +35,10 @@ namespace shared_model { * to perform stateless validation on model fields */ template - class AccountBuilder : public CommonObjectBuilder { + class DEPRECATED AccountBuilder + : public CommonObjectBuilder { public: AccountBuilder accountId( const interface::types::AccountIdType &account_id) { diff --git a/shared_model/builders/common_objects/amount_builder.hpp b/shared_model/builders/common_objects/amount_builder.hpp deleted file mode 100644 index e243d5140b..0000000000 --- a/shared_model/builders/common_objects/amount_builder.hpp +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef IROHA_AMOUNT_BUILDER_HPP -#define IROHA_AMOUNT_BUILDER_HPP - -#include - -#include "builders/common_objects/common.hpp" -#include "interfaces/common_objects/amount.hpp" - -// TODO: 14.02.2018 nickaleks Add check for uninitialized fields IR-972 - -namespace shared_model { - namespace builder { - - /** - * AmountBuilder is a class, used for construction of Amount objects - * @tparam BuilderImpl is a type, which defines builder for implementation - * of shared_model. Since we return abstract classes, it is necessary for - * them to be instantiated with some concrete implementation - * @tparam Validator is a type, whose responsibility is - * to perform stateless validation on model fields - */ - template - class AmountBuilder : public CommonObjectBuilder { - public: - AmountBuilder intValue(const boost::multiprecision::uint256_t &value) { - AmountBuilder copy(*this); - copy.builder_ = this->builder_.intValue(value); - return copy; - } - - AmountBuilder precision( - const interface::types::PrecisionType &precision) { - AmountBuilder copy(*this); - copy.builder_ = this->builder_.precision(precision); - return copy; - } - - /** - * Constructs Amount object from given string - * @param str_amount string in format "100.00", - * where there are exactly precision numbers after dot - * @return Amount constructed from string - */ - static BuilderResult fromString( - std::string str_amount) { - // taken from iroha::model::Amount - // check if valid number - std::regex e("([0-9]*\\.[0-9]+|[0-9]+)"); - if (!std::regex_match(str_amount, e)) { - return iroha::expected::makeError( - std::make_shared("number string is invalid")); - } - - // get precision - auto dot_place = str_amount.find('.'); - size_t precision; - if (dot_place > str_amount.size()) { - precision = 0; - } else { - precision = str_amount.size() - dot_place - 1; - // erase dot from the string - str_amount.erase( - std::remove(str_amount.begin(), str_amount.end(), '.'), - str_amount.end()); - } - - auto begin = str_amount.find_first_not_of('0'); - - // create uint256 value from obtained string - boost::multiprecision::uint256_t value = 0; - if (begin <= str_amount.size()) { - value = boost::multiprecision::uint256_t(str_amount.substr(begin)); - } - - AmountBuilder builder; - return builder.intValue(value).precision(precision).build(); - } - - protected: - virtual std::string builderName() const override { - return "Amount Builder"; - } - - virtual validation::ReasonsGroupType validate( - const interface::Amount &object) override { - validation::ReasonsGroupType reasons; - this->validator_.validateAmount(reasons, object); - - return reasons; - } - }; - } // namespace builder -} // namespace shared_model - -#endif // IROHA_AMOUNT_BUILDER_HPP diff --git a/shared_model/builders/common_objects/asset_builder.hpp b/shared_model/builders/common_objects/asset_builder.hpp index 79a2a812f4..d8a4e1e0ed 100644 --- a/shared_model/builders/common_objects/asset_builder.hpp +++ b/shared_model/builders/common_objects/asset_builder.hpp @@ -36,7 +36,7 @@ namespace shared_model { * to perform stateless validation on model fields */ template - class AssetBuilder + class DEPRECATED AssetBuilder : public CommonObjectBuilder { public: AssetBuilder assetId(const interface::types::AccountIdType &asset_id) { diff --git a/shared_model/builders/common_objects/common.hpp b/shared_model/builders/common_objects/common.hpp index 32c51b533d..ec0f771f47 100644 --- a/shared_model/builders/common_objects/common.hpp +++ b/shared_model/builders/common_objects/common.hpp @@ -19,6 +19,7 @@ #define IROHA_BUILDERS_COMMON_HPP #include "common/result.hpp" +#include "utils/swig_keyword_hider.hpp" #include "validators/answer.hpp" // TODO: 16.02.2018 nickaleks: Add validators for common_objects IR-986 @@ -43,7 +44,7 @@ namespace shared_model { * @tparam Validator - validation object */ template - class CommonObjectBuilder { + class DEPRECATED CommonObjectBuilder { public: /** * build() constructs specified object and performs stateless validation diff --git a/shared_model/builders/common_objects/domain_builder.hpp b/shared_model/builders/common_objects/domain_builder.hpp index a7a47c3e2e..c6a3e0527d 100644 --- a/shared_model/builders/common_objects/domain_builder.hpp +++ b/shared_model/builders/common_objects/domain_builder.hpp @@ -25,9 +25,10 @@ namespace shared_model { namespace builder { template - class DomainBuilder : public CommonObjectBuilder { + class DEPRECATED DomainBuilder + : public CommonObjectBuilder { public: DomainBuilder defaultRole( const interface::types::RoleIdType &default_role) { diff --git a/shared_model/builders/common_objects/peer_builder.hpp b/shared_model/builders/common_objects/peer_builder.hpp index 1f1a71b995..1da7229e30 100644 --- a/shared_model/builders/common_objects/peer_builder.hpp +++ b/shared_model/builders/common_objects/peer_builder.hpp @@ -35,7 +35,7 @@ namespace shared_model { * to perform stateless validation on model fields */ template - class PeerBuilder + class DEPRECATED PeerBuilder : public CommonObjectBuilder { public: PeerBuilder address(const interface::types::AddressType &address) { diff --git a/shared_model/builders/common_objects/signature_builder.hpp b/shared_model/builders/common_objects/signature_builder.hpp index 50bd9bcf6d..dadccdc5a4 100644 --- a/shared_model/builders/common_objects/signature_builder.hpp +++ b/shared_model/builders/common_objects/signature_builder.hpp @@ -36,9 +36,10 @@ namespace shared_model { * to perform stateless validation on model fields */ template - class SignatureBuilder : public CommonObjectBuilder { + class DEPRECATED SignatureBuilder + : public CommonObjectBuilder { public: SignatureBuilder publicKey( const shared_model::interface::types::PubkeyType &key) { diff --git a/shared_model/builders/default_builders.hpp b/shared_model/builders/default_builders.hpp index e19898c0a0..a61dc39433 100644 --- a/shared_model/builders/default_builders.hpp +++ b/shared_model/builders/default_builders.hpp @@ -20,14 +20,12 @@ #include "builders/common_objects/account_asset_builder.hpp" #include "builders/common_objects/account_builder.hpp" -#include "builders/common_objects/amount_builder.hpp" #include "builders/common_objects/asset_builder.hpp" #include "builders/common_objects/domain_builder.hpp" #include "builders/common_objects/peer_builder.hpp" #include "builders/common_objects/signature_builder.hpp" #include "builders/protobuf/common_objects/proto_account_asset_builder.hpp" #include "builders/protobuf/common_objects/proto_account_builder.hpp" -#include "builders/protobuf/common_objects/proto_amount_builder.hpp" #include "builders/protobuf/common_objects/proto_asset_builder.hpp" #include "builders/protobuf/common_objects/proto_domain_builder.hpp" #include "builders/protobuf/common_objects/proto_peer_builder.hpp" @@ -58,10 +56,6 @@ namespace shared_model { shared_model::proto::PeerBuilder, shared_model::validation::FieldValidator>; - using DefaultAmountBuilder = shared_model::builder::AmountBuilder< - shared_model::proto::AmountBuilder, - shared_model::validation::FieldValidator>; - using DefaultDomainBuilder = shared_model::builder::DomainBuilder< shared_model::proto::DomainBuilder, shared_model::validation::FieldValidator>; @@ -74,10 +68,6 @@ namespace shared_model { shared_model::proto::SignatureBuilder, shared_model::validation::FieldValidator>; - using AmountBuilderWithoutValidator = shared_model::builder::AmountBuilder< - shared_model::proto::AmountBuilder, - shared_model::validation::AmountTrueValidator>; - using DefaultBlockQueryResponseBuilder = shared_model::builder::BlockQueryResponseBuilder< shared_model::proto::BlockQueryResponseBuilder>; diff --git a/shared_model/builders/protobuf/CMakeLists.txt b/shared_model/builders/protobuf/CMakeLists.txt index 747a7b4929..f0817979d8 100644 --- a/shared_model/builders/protobuf/CMakeLists.txt +++ b/shared_model/builders/protobuf/CMakeLists.txt @@ -22,4 +22,5 @@ target_link_libraries( shared_model_proto_builders shared_model_interfaces shared_model_proto_backend + shared_model_stateless_validation ) diff --git a/shared_model/builders/protobuf/block.hpp b/shared_model/builders/protobuf/block.hpp index fb6b44a925..56cb8445b6 100644 --- a/shared_model/builders/protobuf/block.hpp +++ b/shared_model/builders/protobuf/block.hpp @@ -25,10 +25,10 @@ namespace shared_model { using BlockBuilder = TemplateBlockBuilder<>; - using UnsignedBlockBuilder = - TemplateBlockBuilder<0, - shared_model::validation::DefaultBlockValidator, - shared_model::proto::Block>; + using UnsignedBlockBuilder = TemplateBlockBuilder< + 0, + shared_model::validation::DefaultUnsignedBlockValidator, + shared_model::proto::Block>; } // namespace proto } // namespace shared_model diff --git a/shared_model/builders/protobuf/block_variant_transport_builder.hpp b/shared_model/builders/protobuf/block_variant_transport_builder.hpp index 6d6f3cc8b5..20e3495198 100644 --- a/shared_model/builders/protobuf/block_variant_transport_builder.hpp +++ b/shared_model/builders/protobuf/block_variant_transport_builder.hpp @@ -19,7 +19,7 @@ namespace shared_model { * @tparam SV Stateless validator type */ template - class TransportBuilder { + class DEPRECATED TransportBuilder { private: /** * Create container type (i.e. Block or EmptyBlock) diff --git a/shared_model/builders/protobuf/builder_templates/block_template.hpp b/shared_model/builders/protobuf/builder_templates/block_template.hpp index b8c805e1e7..91b0777123 100644 --- a/shared_model/builders/protobuf/builder_templates/block_template.hpp +++ b/shared_model/builders/protobuf/builder_templates/block_template.hpp @@ -40,9 +40,9 @@ namespace shared_model { * @tparam BT -- build type of built object returned by build method */ template > - class TemplateBlockBuilder { + class DEPRECATED TemplateBlockBuilder { private: template friend class TemplateBlockBuilder; diff --git a/shared_model/builders/protobuf/builder_templates/blocks_query_template.hpp b/shared_model/builders/protobuf/builder_templates/blocks_query_template.hpp index 191b0df7af..18757ae5d0 100644 --- a/shared_model/builders/protobuf/builder_templates/blocks_query_template.hpp +++ b/shared_model/builders/protobuf/builder_templates/blocks_query_template.hpp @@ -8,8 +8,8 @@ #include "backend/protobuf/queries/proto_blocks_query.hpp" #include "builders/protobuf/unsigned_proto.hpp" -#include "interfaces/queries/blocks_query.hpp" #include "interfaces/common_objects/types.hpp" +#include "interfaces/queries/blocks_query.hpp" #include "interfaces/transaction.hpp" #include "queries.pb.h" #include "validators/default_validator.hpp" @@ -26,9 +26,9 @@ namespace shared_model { * @tparam BT -- build type of built object returned by build method */ template > - class TemplateBlocksQueryBuilder { + typename SV = validation::DefaultUnsignedBlocksQueryValidator, + typename BT = UnsignedWrapper> + class DEPRECATED TemplateBlocksQueryBuilder { private: template friend class TemplateBlocksQueryBuilder; @@ -46,7 +46,8 @@ namespace shared_model { using ProtoBlocksQuery = iroha::protocol::BlocksQuery; template - TemplateBlocksQueryBuilder(const TemplateBlocksQueryBuilder &o) + TemplateBlocksQueryBuilder( + const TemplateBlocksQueryBuilder &o) : query_(o.query_), stateless_validator_(o.stateless_validator_) {} /** diff --git a/shared_model/builders/protobuf/builder_templates/empty_block_template.hpp b/shared_model/builders/protobuf/builder_templates/empty_block_template.hpp index 4f1727de57..a16a4bdd2c 100644 --- a/shared_model/builders/protobuf/builder_templates/empty_block_template.hpp +++ b/shared_model/builders/protobuf/builder_templates/empty_block_template.hpp @@ -26,7 +26,7 @@ namespace shared_model { template > - class TemplateEmptyBlockBuilder { + class DEPRECATED TemplateEmptyBlockBuilder { private: template friend class TemplateEmptyBlockBuilder; diff --git a/shared_model/builders/protobuf/builder_templates/proposal_template.hpp b/shared_model/builders/protobuf/builder_templates/proposal_template.hpp index 431d04bf71..8720d663c7 100644 --- a/shared_model/builders/protobuf/builder_templates/proposal_template.hpp +++ b/shared_model/builders/protobuf/builder_templates/proposal_template.hpp @@ -34,7 +34,7 @@ namespace shared_model { * @tparam SV -- stateless validator called when build method is invoked */ template - class TemplateProposalBuilder { + class DEPRECATED TemplateProposalBuilder { private: template friend class TemplateProposalBuilder; diff --git a/shared_model/builders/protobuf/builder_templates/query_response_template.hpp b/shared_model/builders/protobuf/builder_templates/query_response_template.hpp index ebb100653a..7a90ff11c3 100644 --- a/shared_model/builders/protobuf/builder_templates/query_response_template.hpp +++ b/shared_model/builders/protobuf/builder_templates/query_response_template.hpp @@ -8,11 +8,10 @@ #include "backend/protobuf/permissions.hpp" #include "backend/protobuf/query_responses/proto_query_response.hpp" -#include "builders/protobuf/helpers.hpp" #include "common/visitor.hpp" #include "interfaces/common_objects/types.hpp" #include "interfaces/permissions.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" namespace shared_model { namespace proto { @@ -28,7 +27,7 @@ namespace shared_model { * set */ template - class TemplateQueryResponseBuilder { + class DEPRECATED TemplateQueryResponseBuilder { public: template TemplateQueryResponseBuilder(TemplateQueryResponseBuilder &&o) diff --git a/shared_model/builders/protobuf/builder_templates/query_template.hpp b/shared_model/builders/protobuf/builder_templates/query_template.hpp index 13d0d6e3cb..703739e027 100644 --- a/shared_model/builders/protobuf/builder_templates/query_template.hpp +++ b/shared_model/builders/protobuf/builder_templates/query_template.hpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_PROTO_QUERY_BUILDER_TEMPLATE_HPP @@ -39,9 +27,9 @@ namespace shared_model { * @tparam BT -- build type of built object returned by build method */ template > - class TemplateQueryBuilder { + class DEPRECATED TemplateQueryBuilder { private: template friend class TemplateQueryBuilder; @@ -155,10 +143,21 @@ namespace shared_model { }); } - auto getAccountDetail(const interface::types::AccountIdType &account_id) { + auto getAccountDetail( + const interface::types::AccountIdType &account_id = "", + const interface::types::AccountDetailKeyType &key = "", + const interface::types::AccountIdType &writer = "") { return queryField([&](auto proto_query) { auto query = proto_query->mutable_get_account_detail(); - query->set_account_id(account_id); + if (not account_id.empty()) { + query->set_account_id(account_id); + } + if (not key.empty()) { + query->set_key(key); + } + if (not writer.empty()) { + query->set_writer(writer); + } }); } @@ -202,6 +201,12 @@ namespace shared_model { return getTransactions({hashes...}); } + auto getPendingTransactions() const { + return queryField([&](auto proto_query) { + proto_query->mutable_get_pending_transactions(); + }); + } + auto build() const { static_assert(S == (1 << TOTAL) - 1, "Required fields are not set"); if (not query_.has_payload()) { diff --git a/shared_model/builders/protobuf/builder_templates/transaction_template.hpp b/shared_model/builders/protobuf/builder_templates/transaction_template.hpp index 2ebb85832f..d0ef48fe71 100644 --- a/shared_model/builders/protobuf/builder_templates/transaction_template.hpp +++ b/shared_model/builders/protobuf/builder_templates/transaction_template.hpp @@ -22,13 +22,11 @@ #include -#include "block.pb.h" #include "commands.pb.h" #include "primitive.pb.h" +#include "transaction.pb.h" -#include "amount/amount.hpp" #include "backend/protobuf/permissions.hpp" -#include "builders/protobuf/helpers.hpp" #include "builders/protobuf/unsigned_proto.hpp" #include "interfaces/common_objects/types.hpp" #include "interfaces/permissions.hpp" @@ -47,7 +45,7 @@ namespace shared_model { template > - class TemplateTransactionBuilder { + class DEPRECATED TemplateTransactionBuilder { private: template friend class TemplateTransactionBuilder; @@ -94,7 +92,9 @@ namespace shared_model { template auto addCommand(Transformation t) const { NextBuilder copy = *this; - t(copy.transaction_.mutable_payload()->add_commands()); + t(copy.transaction_.mutable_payload() + ->mutable_reduced_payload() + ->add_commands()); return copy; } @@ -105,29 +105,45 @@ namespace shared_model { auto creatorAccountId( const interface::types::AccountIdType &account_id) const { return transform([&](auto &tx) { - tx.mutable_payload()->set_creator_account_id(account_id); + tx.mutable_payload() + ->mutable_reduced_payload() + ->set_creator_account_id(account_id); + }); + } + + auto batchMeta(interface::types::BatchType type, + std::vector hashes) const { + return transform<0>([&](auto &tx) { + tx.mutable_payload()->mutable_batch()->set_type( + static_cast< + iroha::protocol::Transaction::Payload::BatchMeta::BatchType>( + type)); + for (const auto &hash : hashes) { + tx.mutable_payload()->mutable_batch()->add_reduced_hashes( + crypto::toBinaryString(hash)); + } }); } auto createdTime(interface::types::TimestampType created_time) const { return transform([&](auto &tx) { - tx.mutable_payload()->set_created_time(created_time); + tx.mutable_payload()->mutable_reduced_payload()->set_created_time( + created_time); }); } auto quorum(interface::types::QuorumType quorum) const { - return transform( - [&](auto &tx) { tx.mutable_payload()->set_quorum(quorum); }); + return transform([&](auto &tx) { + tx.mutable_payload()->mutable_reduced_payload()->set_quorum(quorum); + }); } - auto addAssetQuantity(const interface::types::AccountIdType &account_id, - const interface::types::AssetIdType &asset_id, + auto addAssetQuantity(const interface::types::AssetIdType &asset_id, const std::string &amount) const { return addCommand([&](auto proto_command) { auto command = proto_command->mutable_add_asset_quantity(); - command->set_account_id(account_id); command->set_asset_id(asset_id); - initializeProtobufAmount(command->mutable_amount(), amount); + command->set_amount(amount); }); } @@ -265,15 +281,12 @@ namespace shared_model { }); } - auto subtractAssetQuantity( - const interface::types::AccountIdType &account_id, - const interface::types::AssetIdType &asset_id, - const std::string &amount) const { + auto subtractAssetQuantity(const interface::types::AssetIdType &asset_id, + const std::string &amount) const { return addCommand([&](auto proto_command) { auto command = proto_command->mutable_subtract_asset_quantity(); - command->set_account_id(account_id); command->set_asset_id(asset_id); - initializeProtobufAmount(command->mutable_amount(), amount); + command->set_amount(amount); }); } @@ -288,7 +301,7 @@ namespace shared_model { command->set_dest_account_id(dest_account_id); command->set_asset_id(asset_id); command->set_description(description); - initializeProtobufAmount(command->mutable_amount(), amount); + command->set_amount(amount); }); } diff --git a/shared_model/builders/protobuf/common_objects/proto_account_asset_builder.hpp b/shared_model/builders/protobuf/common_objects/proto_account_asset_builder.hpp index d3d45871c1..9e2a07a482 100644 --- a/shared_model/builders/protobuf/common_objects/proto_account_asset_builder.hpp +++ b/shared_model/builders/protobuf/common_objects/proto_account_asset_builder.hpp @@ -19,7 +19,7 @@ #define IROHA_PROTO_ACCOUNT_ASSET_BUILDER_HPP #include "backend/protobuf/common_objects/account_asset.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" namespace shared_model { namespace proto { @@ -27,7 +27,7 @@ namespace shared_model { * AccountAssetBuilder is used to construct AccountAsset proto objects with * initialized protobuf implementation */ - class AccountAssetBuilder { + class DEPRECATED AccountAssetBuilder { public: shared_model::proto::AccountAsset build() { return shared_model::proto::AccountAsset( @@ -50,9 +50,7 @@ namespace shared_model { AccountAssetBuilder balance(const interface::Amount &amount) { AccountAssetBuilder copy(*this); - auto *amount_proto = copy.account_asset_.mutable_balance(); - convertToProtoAmount(*amount_proto->mutable_value(), amount.intValue()); - amount_proto->set_precision(amount.precision()); + *copy.account_asset_.mutable_balance() = amount.toStringRepr(); return copy; } diff --git a/shared_model/builders/protobuf/common_objects/proto_account_builder.hpp b/shared_model/builders/protobuf/common_objects/proto_account_builder.hpp index ea1e9122eb..487dfbad4d 100644 --- a/shared_model/builders/protobuf/common_objects/proto_account_builder.hpp +++ b/shared_model/builders/protobuf/common_objects/proto_account_builder.hpp @@ -19,7 +19,7 @@ #define IROHA_PROTO_ACCOUNT_BUILDER_HPP #include "backend/protobuf/common_objects/account.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" namespace shared_model { namespace proto { @@ -28,7 +28,7 @@ namespace shared_model { * AccountBuilder is used to construct Account proto objects with * initialized protobuf implementation */ - class AccountBuilder { + class DEPRECATED AccountBuilder { public: shared_model::proto::Account build() { return shared_model::proto::Account(iroha::protocol::Account(account_)); diff --git a/shared_model/builders/protobuf/common_objects/proto_amount_builder.hpp b/shared_model/builders/protobuf/common_objects/proto_amount_builder.hpp deleted file mode 100644 index 2b8a16537e..0000000000 --- a/shared_model/builders/protobuf/common_objects/proto_amount_builder.hpp +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef IROHA_PROTO_AMOUNT_BUILDER_HPP -#define IROHA_PROTO_AMOUNT_BUILDER_HPP - -#include "backend/protobuf/common_objects/amount.hpp" -#include "primitive.pb.h" - -namespace shared_model { - namespace proto { - - /** - * AmountBuilder is used to construct Amount proto objects with initialized - * protobuf implementation - */ - class AmountBuilder { - public: - shared_model::proto::Amount build() { - return shared_model::proto::Amount(iroha::protocol::Amount(amount_)); - } - - AmountBuilder intValue(const boost::multiprecision::uint256_t &value) { - AmountBuilder copy(*this); - convertToProtoAmount(*copy.amount_.mutable_value(), value); - return copy; - } - - AmountBuilder precision( - const interface::types::PrecisionType &precision) { - AmountBuilder copy(*this); - copy.amount_.set_precision(precision); - return copy; - } - - private: - iroha::protocol::Amount amount_; - }; - } // namespace proto -} // namespace shared_model -#endif // IROHA_PROTO_AMOUNT_BUILDER_HPP diff --git a/shared_model/builders/protobuf/common_objects/proto_asset_builder.hpp b/shared_model/builders/protobuf/common_objects/proto_asset_builder.hpp index f0c7bd71e6..a412725e46 100644 --- a/shared_model/builders/protobuf/common_objects/proto_asset_builder.hpp +++ b/shared_model/builders/protobuf/common_objects/proto_asset_builder.hpp @@ -19,7 +19,7 @@ #define IROHA_PROTO_ASSET_BUILDER_HPP #include "backend/protobuf/common_objects/asset.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" namespace shared_model { namespace proto { @@ -28,7 +28,7 @@ namespace shared_model { * AssetBuilder is used to construct Asset proto objects with initialized * protobuf implementation */ - class AssetBuilder { + class DEPRECATED AssetBuilder { public: shared_model::proto::Asset build() { return shared_model::proto::Asset(iroha::protocol::Asset(asset_)); diff --git a/shared_model/builders/protobuf/common_objects/proto_domain_builder.hpp b/shared_model/builders/protobuf/common_objects/proto_domain_builder.hpp index 55b1ec6a97..3dad699e59 100644 --- a/shared_model/builders/protobuf/common_objects/proto_domain_builder.hpp +++ b/shared_model/builders/protobuf/common_objects/proto_domain_builder.hpp @@ -19,11 +19,11 @@ #define IROHA_PROTO_DOMAIN_BUILDER_HPP #include "backend/protobuf/common_objects/domain.hpp" -#include "responses.pb.h" +#include "qry_responses.pb.h" namespace shared_model { namespace proto { - class DomainBuilder { + class DEPRECATED DomainBuilder { public: shared_model::proto::Domain build() { return shared_model::proto::Domain(iroha::protocol::Domain(domain_)); diff --git a/shared_model/builders/protobuf/common_objects/proto_peer_builder.hpp b/shared_model/builders/protobuf/common_objects/proto_peer_builder.hpp index 78334efb74..a6d20de1b3 100644 --- a/shared_model/builders/protobuf/common_objects/proto_peer_builder.hpp +++ b/shared_model/builders/protobuf/common_objects/proto_peer_builder.hpp @@ -28,7 +28,7 @@ namespace shared_model { * PeerBuilder is used to construct Peer proto objects with initialized * protobuf implementation */ - class PeerBuilder { + class DEPRECATED PeerBuilder { public: shared_model::proto::Peer build() { return shared_model::proto::Peer(iroha::protocol::Peer(peer_)); diff --git a/shared_model/builders/protobuf/common_objects/proto_signature_builder.hpp b/shared_model/builders/protobuf/common_objects/proto_signature_builder.hpp index e920d6d47d..501edd720f 100644 --- a/shared_model/builders/protobuf/common_objects/proto_signature_builder.hpp +++ b/shared_model/builders/protobuf/common_objects/proto_signature_builder.hpp @@ -29,7 +29,7 @@ namespace shared_model { * SignatureBuilder is used to construct Signature proto objects with * initialized protobuf implementation */ - class SignatureBuilder { + class DEPRECATED SignatureBuilder { public: shared_model::proto::Signature build() { return shared_model::proto::Signature( diff --git a/shared_model/builders/protobuf/helpers.hpp b/shared_model/builders/protobuf/helpers.hpp deleted file mode 100644 index 5f6d3dc31e..0000000000 --- a/shared_model/builders/protobuf/helpers.hpp +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef IROHA_PROTO_BUILDER_HELPER_HPP -#define IROHA_PROTO_BUILDER_HELPER_HPP - -#include "amount/amount.hpp" -#include "primitive.pb.h" - -namespace shared_model { - namespace proto { - /** - * Initialize protobuf Amout by text number representation - * @param proto is Amount that is being set - * @param str_amount is string representation of Amount - */ - static inline void initializeProtobufAmount(iroha::protocol::Amount *proto, - const std::string &str_amount) { - iroha::Amount::createFromString(str_amount) | [proto](auto &&amount) { - auto proto_value = proto->mutable_value(); - auto uint64s = amount.to_uint64s(); - proto_value->set_first(uint64s.at(0)); - proto_value->set_second(uint64s.at(1)); - proto_value->set_third(uint64s.at(2)); - proto_value->set_fourth(uint64s.at(3)); - proto->set_precision(amount.getPrecision()); - }; - } - } // namespace proto -} // namespace shared_model - -#endif // IROHA_PROTO_BUILDER_HELPER_HPP diff --git a/shared_model/builders/protobuf/query_responses/proto_block_query_response_builder.hpp b/shared_model/builders/protobuf/query_responses/proto_block_query_response_builder.hpp index c9d0a39b74..79e3701bb1 100644 --- a/shared_model/builders/protobuf/query_responses/proto_block_query_response_builder.hpp +++ b/shared_model/builders/protobuf/query_responses/proto_block_query_response_builder.hpp @@ -10,7 +10,7 @@ namespace shared_model { namespace proto { - class BlockQueryResponseBuilder { + class DEPRECATED BlockQueryResponseBuilder { public: shared_model::proto::BlockQueryResponse build() &&; diff --git a/shared_model/builders/protobuf/transaction.hpp b/shared_model/builders/protobuf/transaction.hpp index 5330db7576..77d2ae7134 100644 --- a/shared_model/builders/protobuf/transaction.hpp +++ b/shared_model/builders/protobuf/transaction.hpp @@ -18,19 +18,6 @@ #ifndef IROHA_PROTO_TRANSACTION_BUILDER_HPP #define IROHA_PROTO_TRANSACTION_BUILDER_HPP -#include "backend/protobuf/transaction.hpp" - -#include - -#include "block.pb.h" -#include "commands.pb.h" -#include "primitive.pb.h" - -#include "amount/amount.hpp" -#include "builders/protobuf/helpers.hpp" -#include "builders/protobuf/unsigned_proto.hpp" -#include "interfaces/common_objects/types.hpp" -#include "validators/default_validator.hpp" #include "builders/protobuf/builder_templates/transaction_template.hpp" namespace shared_model { diff --git a/shared_model/builders/protobuf/transaction_responses/proto_transaction_status_builder.cpp b/shared_model/builders/protobuf/transaction_responses/proto_transaction_status_builder.cpp index d364563413..fd7feeefbb 100644 --- a/shared_model/builders/protobuf/transaction_responses/proto_transaction_status_builder.cpp +++ b/shared_model/builders/protobuf/transaction_responses/proto_transaction_status_builder.cpp @@ -88,5 +88,11 @@ namespace shared_model { return copy; } + TransactionStatusBuilder TransactionStatusBuilder::errorMsg(const std::string &msg) { + TransactionStatusBuilder copy(*this); + copy.tx_response_.set_error_message(msg); + return copy; + } + } // namespace proto } // namespace shared_model diff --git a/shared_model/builders/protobuf/transaction_responses/proto_transaction_status_builder.hpp b/shared_model/builders/protobuf/transaction_responses/proto_transaction_status_builder.hpp index 06d7dcf973..f98500fe82 100644 --- a/shared_model/builders/protobuf/transaction_responses/proto_transaction_status_builder.hpp +++ b/shared_model/builders/protobuf/transaction_responses/proto_transaction_status_builder.hpp @@ -22,7 +22,7 @@ namespace shared_model { namespace proto { - class TransactionStatusBuilder { + class DEPRECATED TransactionStatusBuilder { public: shared_model::proto::TransactionResponse build() &&; @@ -44,6 +44,8 @@ namespace shared_model { TransactionStatusBuilder txHash(const crypto::Hash &hash); + TransactionStatusBuilder errorMsg(const std::string &msg); + private: iroha::protocol::ToriiResponse tx_response_; }; diff --git a/shared_model/builders/protobuf/transaction_sequence_builder.hpp b/shared_model/builders/protobuf/transaction_sequence_builder.hpp new file mode 100644 index 0000000000..0caf7f68b0 --- /dev/null +++ b/shared_model/builders/protobuf/transaction_sequence_builder.hpp @@ -0,0 +1,53 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_TRANSACTION_SEQUENCE_BUILDER_HPP +#define IROHA_TRANSACTION_SEQUENCE_BUILDER_HPP + +#include "builders/protobuf/transport_builder.hpp" +#include "interfaces/common_objects/types.hpp" +#include "interfaces/iroha_internal/transaction_sequence.hpp" + +namespace shared_model { + namespace proto { + + /** + * Class for building Transaction Sequence + * @tparam SV Stateless validator type + */ + template + class DEPRECATED TransportBuilder { + public: + TransportBuilder( + SV stateless_validator = SV()) + : stateless_validator_(stateless_validator) {} + + /** + * Builds TransactionSequence from transport object + * @param transport protobuf object from which TransactionSequence is + * built + * @return Result containing either TransactionSequence or message string + */ + template + iroha::expected::Result + build(const T &transport) { + const auto &txs = transport.transactions(); + std::vector> shm_txs; + std::transform(txs.begin(), + txs.end(), + std::back_inserter(shm_txs), + [](const iroha::protocol::Transaction &tx) { + return std::make_shared(tx); + }); + return interface::TransactionSequence::createTransactionSequence( + shm_txs, stateless_validator_); + } + + private: + SV stateless_validator_; + }; + } // namespace proto +} // namespace shared_model +#endif // IROHA_TRANSACTION_SEQUENCE_BUILDER_HPP diff --git a/shared_model/builders/protobuf/transport_builder.hpp b/shared_model/builders/protobuf/transport_builder.hpp index 6623f69d7c..2d912d01b2 100644 --- a/shared_model/builders/protobuf/transport_builder.hpp +++ b/shared_model/builders/protobuf/transport_builder.hpp @@ -30,7 +30,7 @@ namespace shared_model { * @tparam SV Stateless validator type */ template - class TransportBuilder { + class DEPRECATED TransportBuilder { public: TransportBuilder(const SV &validator = SV()) : stateless_validator_(validator) {} diff --git a/shared_model/builders/protobuf/unsigned_proto.hpp b/shared_model/builders/protobuf/unsigned_proto.hpp index 07d8bf95d6..48448b3c04 100644 --- a/shared_model/builders/protobuf/unsigned_proto.hpp +++ b/shared_model/builders/protobuf/unsigned_proto.hpp @@ -28,9 +28,12 @@ namespace shared_model { /** * Class for holding built but still unsigned objects * @tparam T - type of object received from builder + * + * NOTE: finish() moves internal object, so calling methods after + * finish() throws an exception */ template - class UnsignedWrapper { + class DEPRECATED UnsignedWrapper { public: using ModelType = T; @@ -42,6 +45,23 @@ namespace shared_model { explicit UnsignedWrapper(T &&o) : object_(std::move(o)) {} + UnsignedWrapper(UnsignedWrapper &&w) + : object_(std::move(w.object_)), + object_finalized_(w.object_finalized_) { + w.object_finalized_ = true; + } + + UnsignedWrapper &operator=(UnsignedWrapper &&w) { + object_ = std::move(w.object_); + object_finalized_ = w.object_finalized_; + w.object_finalized_ = true; + + return *this; + } + + UnsignedWrapper(const UnsignedWrapper &o) = default; + UnsignedWrapper &operator=(const UnsignedWrapper &w) = default; + /** * Add signature and retrieve signed result * @param signature - signature to add @@ -50,6 +70,9 @@ namespace shared_model { UnsignedWrapper &signAndAddSignature(const crypto::Keypair &keypair) { auto signedBlob = shared_model::crypto::CryptoSigner<>::sign( shared_model::crypto::Blob(object_.payload()), keypair); + if (object_finalized_) { + throw std::runtime_error("object has already been finalized"); + } object_.addSignature(signedBlob, keypair.publicKey()); // TODO: 05.12.2017 luckychess think about false case return *this; @@ -63,15 +86,29 @@ namespace shared_model { if (boost::size(object_.signatures()) == 0) { throw std::invalid_argument("Cannot get object without signatures"); } - return object_; + if (object_finalized_) { + throw std::runtime_error("object has already been finalized"); + } + + object_finalized_ = true; + return std::move(object_); } interface::types::HashType hash() { return object_.hash(); } + template + typename std::enable_if< + std::is_base_of::value, + interface::types::HashType>::type + reducedHash() { + return object_.reducedHash(); + } + private: T object_; + bool object_finalized_{false}; }; } // namespace proto } // namespace shared_model diff --git a/shared_model/builders/query_responses/block_query_response_builder.hpp b/shared_model/builders/query_responses/block_query_response_builder.hpp index a2b63eec28..7cc708b591 100644 --- a/shared_model/builders/query_responses/block_query_response_builder.hpp +++ b/shared_model/builders/query_responses/block_query_response_builder.hpp @@ -16,7 +16,7 @@ namespace shared_model { * @tparam BuilderImpl */ template - class BlockQueryResponseBuilder { + class DEPRECATED BlockQueryResponseBuilder { public: std::shared_ptr build() { return clone(builder_.build()); diff --git a/shared_model/builders/transaction_responses/transaction_status_builder.hpp b/shared_model/builders/transaction_responses/transaction_status_builder.hpp index b08b7c271c..1e4d030eae 100644 --- a/shared_model/builders/transaction_responses/transaction_status_builder.hpp +++ b/shared_model/builders/transaction_responses/transaction_status_builder.hpp @@ -31,7 +31,7 @@ namespace shared_model { * @tparam BuilderImpl */ template - class TransactionStatusBuilder { + class DEPRECATED TransactionStatusBuilder { public: std::shared_ptr build() { return clone(builder_.build()); @@ -85,6 +85,12 @@ namespace shared_model { return copy; } + TransactionStatusBuilder errorMsg(const std::string &msg) { + TransactionStatusBuilder copy(*this); + copy.builder_ = this->builder_.errorMsg(msg); + return copy; + } + private: BuilderImpl builder_; }; diff --git a/shared_model/clean.sh b/shared_model/clean.sh index e0e2b76a27..a3615ae37a 100755 --- a/shared_model/clean.sh +++ b/shared_model/clean.sh @@ -1,4 +1,3 @@ #!/bin/bash -rm ../schema/*.{cc,h} rm -rf external rm -rf build diff --git a/shared_model/converters/protobuf/json_proto_converter.hpp b/shared_model/converters/protobuf/json_proto_converter.hpp index 4444a31e17..5854b0728c 100644 --- a/shared_model/converters/protobuf/json_proto_converter.hpp +++ b/shared_model/converters/protobuf/json_proto_converter.hpp @@ -34,7 +34,7 @@ namespace shared_model { * @return json string */ template - std::string modelToJson(T message) { + std::string modelToJson(const T &message) { std::string result; google::protobuf::util::MessageToJsonString(message.getTransport(), &result); diff --git a/shared_model/cryptography/blob.hpp b/shared_model/cryptography/blob.hpp index b1a9d30e7c..9639d6d7a0 100644 --- a/shared_model/cryptography/blob.hpp +++ b/shared_model/cryptography/blob.hpp @@ -23,7 +23,6 @@ #include "interfaces/base/model_primitive.hpp" #include "utils/lazy_initializer.hpp" -#include "utils/swig_keyword_hider.hpp" namespace shared_model { namespace crypto { diff --git a/shared_model/interfaces/CMakeLists.txt b/shared_model/interfaces/CMakeLists.txt index 7fc19a1872..73d4cb6215 100644 --- a/shared_model/interfaces/CMakeLists.txt +++ b/shared_model/interfaces/CMakeLists.txt @@ -1,16 +1,7 @@ -# Copyright 2017 Soramitsu Co., Ltd. # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 # -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. add_library(shared_model_interfaces impl/permissions.cpp @@ -42,8 +33,10 @@ add_library(shared_model_interfaces queries/impl/get_roles.cpp queries/impl/get_signatories.cpp queries/impl/get_transactions.cpp + queries/impl/get_pending_transactions.cpp queries/impl/blocks_query.cpp queries/impl/query_payload_meta.cpp + common_objects/impl/amount.cpp ) if (IROHA_ROOT_PROJECT) @@ -63,10 +56,12 @@ if (IROHA_ROOT_PROJECT) query_responses/impl/block_response.cpp iroha_internal/block_variant.cpp iroha_internal/transaction_sequence.cpp + iroha_internal/transaction_batch.cpp ) endif () target_link_libraries(shared_model_interfaces shared_model_cryptography + schema ${Boost_LIBRARIES} ) diff --git a/shared_model/interfaces/base/signable.hpp b/shared_model/interfaces/base/signable.hpp index 11bcb1162f..bdacab65ae 100644 --- a/shared_model/interfaces/base/signable.hpp +++ b/shared_model/interfaces/base/signable.hpp @@ -80,8 +80,10 @@ namespace shared_model { */ bool operator==(const Model &rhs) const override { return this->hash() == rhs.hash() - and boost::equal(this->signatures(), rhs.signatures()) - and this->createdTime() == rhs.createdTime(); + // is_permutation consumes ~O(N^2) + and std::is_permutation(signatures().begin(), + signatures().end(), + rhs.signatures().begin()); } const types::HashType &hash() const { diff --git a/shared_model/interfaces/commands/add_asset_quantity.hpp b/shared_model/interfaces/commands/add_asset_quantity.hpp index f704f9e66b..24e768e8a3 100644 --- a/shared_model/interfaces/commands/add_asset_quantity.hpp +++ b/shared_model/interfaces/commands/add_asset_quantity.hpp @@ -30,10 +30,6 @@ namespace shared_model { */ class AddAssetQuantity : public ModelPrimitive { public: - /** - * @return Identity of user, that add quantity - */ - virtual const types::AccountIdType &accountId() const = 0; /** * @return asset identifier */ diff --git a/shared_model/interfaces/commands/impl/add_asset_quantity.cpp b/shared_model/interfaces/commands/impl/add_asset_quantity.cpp index f6d5c13833..f26dcdb337 100644 --- a/shared_model/interfaces/commands/impl/add_asset_quantity.cpp +++ b/shared_model/interfaces/commands/impl/add_asset_quantity.cpp @@ -11,15 +11,13 @@ namespace shared_model { std::string AddAssetQuantity::toString() const { return detail::PrettyStringBuilder() .init("AddAssetQuantity") - .append("account_id", accountId()) .append("asset_id", assetId()) .append("amount", amount().toString()) .finalize(); } bool AddAssetQuantity::operator==(const ModelType &rhs) const { - return accountId() == rhs.accountId() and assetId() == rhs.assetId() - and amount() == rhs.amount(); + return assetId() == rhs.assetId() and amount() == rhs.amount(); } } // namespace interface diff --git a/shared_model/interfaces/commands/impl/subtract_asset_quantity.cpp b/shared_model/interfaces/commands/impl/subtract_asset_quantity.cpp index ead13191d2..9928f4dc59 100644 --- a/shared_model/interfaces/commands/impl/subtract_asset_quantity.cpp +++ b/shared_model/interfaces/commands/impl/subtract_asset_quantity.cpp @@ -11,15 +11,13 @@ namespace shared_model { std::string SubtractAssetQuantity::toString() const { return detail::PrettyStringBuilder() .init("SubtractAssetQuantity") - .append("account_id", accountId()) .append("asset_id", assetId()) .append("amount", amount().toString()) .finalize(); } bool SubtractAssetQuantity::operator==(const ModelType &rhs) const { - return accountId() == rhs.accountId() and assetId() == rhs.assetId() - and amount() == rhs.amount(); + return assetId() == rhs.assetId() and amount() == rhs.amount(); } } // namespace interface diff --git a/shared_model/interfaces/commands/subtract_asset_quantity.hpp b/shared_model/interfaces/commands/subtract_asset_quantity.hpp index da43458fd3..9e181c5440 100644 --- a/shared_model/interfaces/commands/subtract_asset_quantity.hpp +++ b/shared_model/interfaces/commands/subtract_asset_quantity.hpp @@ -30,10 +30,6 @@ namespace shared_model { */ class SubtractAssetQuantity : public ModelPrimitive { public: - /** - * @return Identity of user to subtract quantity from - */ - virtual const types::AccountIdType &accountId() const = 0; /** * @return asset identifier */ diff --git a/shared_model/interfaces/common_objects/amount.hpp b/shared_model/interfaces/common_objects/amount.hpp index 5fd0d7cb0f..3d54da2b35 100644 --- a/shared_model/interfaces/common_objects/amount.hpp +++ b/shared_model/interfaces/common_objects/amount.hpp @@ -1,30 +1,16 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_SHARED_MODEL_AMOUNT_HPP #define IROHA_SHARED_MODEL_AMOUNT_HPP -#include -#include -#include - #include "interfaces/base/model_primitive.hpp" + +#include #include "interfaces/common_objects/types.hpp" -#include "utils/string_builder.hpp" +#include "utils/lazy_initializer.hpp" namespace shared_model { namespace interface { @@ -32,53 +18,52 @@ namespace shared_model { /** * Representation of fixed point number */ - class Amount : public ModelPrimitive { + class Amount final : public ModelPrimitive { public: + explicit Amount(const std::string &amount); + explicit Amount(std::string &&amount); + + Amount(const Amount &o); + Amount(Amount &&o) noexcept; + /** * Gets integer representation value, which ignores precision * @return amount represented as integer value, which ignores precision */ - virtual const boost::multiprecision::uint256_t &intValue() const = 0; + const boost::multiprecision::uint256_t &intValue() const; /** * Gets the position of precision * @return the position of precision */ - virtual types::PrecisionType precision() const = 0; + types::PrecisionType precision() const; /** - * Checks equality of objects inside - * @param rhs - other wrapped value - * @return true, if wrapped objects are same + * String representation. + * @return string representation of the asset. */ - bool operator==(const ModelType &rhs) const override { - return intValue() == rhs.intValue() and precision() == rhs.precision(); - } + std::string toStringRepr() const; /** - * String representation. - * @return string representation of the asset. + * Checks equality of objects inside + * @param rhs - other wrapped value + * @return true, if wrapped objects are same */ - std::string toStringRepr() const { - if (precision() > 0) { - boost::multiprecision::cpp_dec_float_50 float50(intValue()); - float50 /= pow(10, precision()); - return float50.str(precision(), std::ios_base::fixed); - } - return intValue().str(0, std::ios_base::fixed); - } + bool operator==(const ModelType &rhs) const override; /** * Stringify the data. * @return the content of asset. */ - std::string toString() const override { - return detail::PrettyStringBuilder() - .init("Amount") - .append("intValue", intValue().str()) - .append("precision", std::to_string(precision())) - .finalize(); - } + std::string toString() const override; + + Amount *clone() const override; + + private: + const std::string amount_; + + interface::types::PrecisionType precision_; + const boost::multiprecision::uint256_t multiprecision_repr_; }; } // namespace interface } // namespace shared_model diff --git a/shared_model/interfaces/common_objects/common_objects_factory.hpp b/shared_model/interfaces/common_objects/common_objects_factory.hpp new file mode 100644 index 0000000000..64161c4361 --- /dev/null +++ b/shared_model/interfaces/common_objects/common_objects_factory.hpp @@ -0,0 +1,82 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_COMMON_OBJECTS_FACTORY_HPP +#define IROHA_COMMON_OBJECTS_FACTORY_HPP + +#include + +#include "common/result.hpp" +#include "interfaces/common_objects/account.hpp" +#include "interfaces/common_objects/account_asset.hpp" +#include "interfaces/common_objects/asset.hpp" +#include "interfaces/common_objects/domain.hpp" +#include "interfaces/common_objects/peer.hpp" +#include "interfaces/common_objects/signature.hpp" +#include "interfaces/common_objects/types.hpp" + +namespace shared_model { + namespace interface { + /** + * CommonObjectsFactory provides methods to construct common objects + * such as peer, account etc. + */ + class CommonObjectsFactory { + public: + template + using FactoryResult = iroha::expected::Result; + + /** + * Create peer instance + */ + virtual FactoryResult> createPeer( + const types::AddressType &address, + const types::PubkeyType &public_key) = 0; + + /** + * Create account instance + */ + virtual FactoryResult> createAccount( + const types::AccountIdType &account_id, + const types::DomainIdType &domain_id, + types::QuorumType quorum, + const types::JsonType &jsonData) = 0; + + /** + * Create account asset instance + */ + virtual FactoryResult> createAccountAsset( + const types::AccountIdType &account_id, + const types::AssetIdType &asset_id, + const Amount &balance) = 0; + + /** + * Create asset instance + */ + virtual FactoryResult> createAsset( + const types::AssetIdType &asset_id, + const types::DomainIdType &domain_id, + types::PrecisionType precision) = 0; + + /** + * Create domain instance + */ + virtual FactoryResult> createDomain( + const types::DomainIdType &domain_id, + const types::RoleIdType &default_role) = 0; + + /** + * Create signature instance + */ + virtual FactoryResult> createSignature( + const interface::types::PubkeyType &key, + const interface::Signature::SignedType &signed_data) = 0; + + virtual ~CommonObjectsFactory() = default; + }; + } // namespace interface +} // namespace shared_model + +#endif // IROHA_COMMONOBJECTSFACTORY_HPP diff --git a/shared_model/interfaces/common_objects/impl/amount.cpp b/shared_model/interfaces/common_objects/impl/amount.cpp new file mode 100644 index 0000000000..37b462034f --- /dev/null +++ b/shared_model/interfaces/common_objects/impl/amount.cpp @@ -0,0 +1,74 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "interfaces/common_objects/amount.hpp" + +#include + +#include +#include "utils/string_builder.hpp" + +namespace shared_model { + namespace interface { + Amount::Amount(const std::string &amount) : Amount(std::string(amount)) {} + Amount::Amount(std::string &&amount) + : amount_(std::move(amount)), + precision_(0), + multiprecision_repr_([this] { + static const std::regex r("([0-9]+)(\\.([0-9]+))?"); + // 123.456 will have the following groups: + // [0] -> 123.456, [1] -> 123 + // [2] -> .456, [3] -> 456 + std::smatch match; + if (std::regex_match(this->amount_, match, r) + && match.size() == 4) { + this->precision_ = match[3].length(); + auto str = match[0].str(); + size_t pos = match[1].length(); + // remove dot if it exist + if (pos < str.size()) { + str.erase(str.begin() + pos); + } + // remove leading zeroes + str.erase(0, + std::min(str.find_first_not_of('0'), str.size() - 1)); + return boost::multiprecision::uint256_t(str); + } + return std::numeric_limits::min(); + }()) {} + + Amount::Amount(const Amount &o) : Amount(std::string(o.amount_)) {} + + Amount::Amount(Amount &&o) noexcept + : Amount(std::move(const_cast(o.amount_))) {} + + const boost::multiprecision::uint256_t &Amount::intValue() const { + return multiprecision_repr_; + } + + types::PrecisionType Amount::precision() const { + return precision_; + } + + std::string Amount::toStringRepr() const { + return amount_; + } + + bool Amount::operator==(const ModelType &rhs) const { + return amount_ == rhs.amount_; + } + + std::string Amount::toString() const { + return detail::PrettyStringBuilder() + .init("Amount") + .append("value", amount_) + .finalize(); + } + + Amount *Amount::clone() const { + return new Amount(*this); + } + } // namespace interface +} // namespace shared_model diff --git a/shared_model/interfaces/common_objects/transaction_sequence_common.hpp b/shared_model/interfaces/common_objects/transaction_sequence_common.hpp index 30dfc1b150..4b11eebcee 100644 --- a/shared_model/interfaces/common_objects/transaction_sequence_common.hpp +++ b/shared_model/interfaces/common_objects/transaction_sequence_common.hpp @@ -13,14 +13,22 @@ namespace shared_model { namespace interface { + class TransactionBatch; + namespace types { using TransactionsForwardCollectionType = boost::any_range; - } - } // namespace interface + + using SharedTxsCollectionType = std::vector>; + + // TODO: IR-1514 kamilsa 09.07.2018 Introduce batch type with batch + // invariant and return range of them + using BatchesCollectionType = std::vector; + } // namespace types + } // namespace interface } // namespace shared_model #endif // IROHA_TRANSACTION_SEQUENCE_COMMON_HPP diff --git a/shared_model/interfaces/common_objects/types.hpp b/shared_model/interfaces/common_objects/types.hpp index 1f50e124c8..54b15eb034 100644 --- a/shared_model/interfaces/common_objects/types.hpp +++ b/shared_model/interfaces/common_objects/types.hpp @@ -26,6 +26,7 @@ #include #include "cryptography/hash.hpp" #include "cryptography/public_key.hpp" +#include "utils/swig_keyword_hider.hpp" namespace shared_model { @@ -98,6 +99,8 @@ namespace shared_model { const AccountAsset &>; /// Type of the transfer message using DescriptionType = std::string; + + enum class BatchType { ATOMIC = 0, ORDERED = 1}; } // namespace types } // namespace interface } // namespace shared_model diff --git a/shared_model/interfaces/impl/permissions.cpp b/shared_model/interfaces/impl/permissions.cpp index 4bbe142075..78626682ca 100644 --- a/shared_model/interfaces/impl/permissions.cpp +++ b/shared_model/interfaces/impl/permissions.cpp @@ -28,23 +28,6 @@ namespace shared_model { return Role::COUNT; } - Grantable permissionOf(Role g) { - switch (g) { - case Role::kAddMySignatory: - return Grantable::kAddMySignatory; - case Role::kRemoveMySignatory: - return Grantable::kRemoveMySignatory; - case Role::kSetMyQuorum: - return Grantable::kSetMyQuorum; - case Role::kSetMyAccountDetail: - return Grantable::kSetMyAccountDetail; - case Role::kTransferMyAssets: - return Grantable::kTransferMyAssets; - default:; - } - return Grantable::COUNT; - } - bool isValid(Role perm) noexcept { auto p = static_cast(perm); return p < static_cast(Role::COUNT); @@ -74,8 +57,16 @@ PermissionSet::PermissionSet(std::initializer_list list) { } template -size_t PermissionSet::size() const { - return Parent::size(); +PermissionSet::PermissionSet(const std::string &bitstring) : Parent(bitstring) {} + +template +std::string PermissionSet::toBitstring() const { + return Parent::to_string(); +} + +template +size_t PermissionSet::size() { + return bit(Perm::COUNT); } template @@ -84,6 +75,12 @@ PermissionSet &PermissionSet::reset() { return *this; } +template +PermissionSet &PermissionSet::set() { + Parent::set(); + return *this; +} + template PermissionSet &PermissionSet::set(Perm p) { Parent::set(bit(p), true); diff --git a/shared_model/interfaces/iroha_internal/batch_meta.hpp b/shared_model/interfaces/iroha_internal/batch_meta.hpp new file mode 100644 index 0000000000..270eef47eb --- /dev/null +++ b/shared_model/interfaces/iroha_internal/batch_meta.hpp @@ -0,0 +1,50 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_SHARED_MODEL_BATCH_META_HPP +#define IROHA_SHARED_MODEL_BATCH_META_HPP + +#include "interfaces/base/model_primitive.hpp" +#include "interfaces/common_objects/types.hpp" + +namespace shared_model { + namespace interface { + + /** + * Representation of fixed point number + */ + class BatchMeta : public ModelPrimitive { + public: + virtual types::BatchType type() const = 0; + + std::string toString() const override { + return detail::PrettyStringBuilder() + .init("BatchMeta") + .append("Type", + type() == types::BatchType::ATOMIC ? "ATOMIC" : "ORDERED") + .appendAll(reducedHashes(), + [](auto &hash) { return hash.toString(); }) + .finalize(); + } + /// type of hashes collection + using ReducedHashesType = std::vector; + + /** + * @return Hashes of transactions to fetch + */ + virtual const ReducedHashesType &reducedHashes() const = 0; + /** + * Checks equality of objects inside + * @param rhs - other wrapped value + * @return true, if wrapped objects are same + */ + bool operator==(const ModelType &rhs) const override { + return boost::equal(reducedHashes(), rhs.reducedHashes()) + and type() == rhs.type(); + } + }; + } // namespace interface +} // namespace shared_model +#endif // IROHA_SHARED_MODEL_BATCH_META_HPP diff --git a/shared_model/interfaces/iroha_internal/proposal_factory.hpp b/shared_model/interfaces/iroha_internal/proposal_factory.hpp new file mode 100644 index 0000000000..fc62eb34ba --- /dev/null +++ b/shared_model/interfaces/iroha_internal/proposal_factory.hpp @@ -0,0 +1,36 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_PROPOSAL_FACTORY_HPP +#define IROHA_PROPOSAL_FACTORY_HPP + +#include + +#include "common/result.hpp" +#include "interfaces/common_objects/types.hpp" + +namespace shared_model { + namespace interface { + class Proposal; + + /** + * ProposalFactory creates proposal + */ + class ProposalFactory { + public: + template + using FactoryResult = iroha::expected::Result; + + virtual FactoryResult> createProposal( + types::HeightType height, + types::TimestampType created_time, + const types::TransactionsCollectionType &transactions) = 0; + + virtual ~ProposalFactory() = default; + }; + } // namespace interface +} // namespace shared_model + +#endif // IROHA_PROPOSAL_FACTORY_HPP diff --git a/shared_model/interfaces/iroha_internal/transaction_batch.cpp b/shared_model/interfaces/iroha_internal/transaction_batch.cpp new file mode 100644 index 0000000000..8c0dec47ea --- /dev/null +++ b/shared_model/interfaces/iroha_internal/transaction_batch.cpp @@ -0,0 +1,138 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "interfaces/iroha_internal/transaction_batch.hpp" +#include "utils/string_builder.hpp" +#include "validators/field_validator.hpp" +#include "validators/transaction_validator.hpp" +#include "validators/transactions_collection/batch_order_validator.hpp" + +namespace shared_model { + namespace interface { + + /** + * check if all transactions belong to the same batch + * @param txs transactions to be checked + * @return true if all transactions from the same batch and false otherwise + */ + static bool allTxsInSameBatch(const types::SharedTxsCollectionType &txs) { + // Empty batch is still batch, so txs can be empty + if (txs.empty()) { + return true; + } + + // take batch meta of the first transaction and compare it with batch + // metas of remaining transactions + auto batch_meta = txs.front()->batchMeta(); + if (not batch_meta) { + return false; + } + + return std::none_of(++txs.begin(), + txs.end(), + [front_batch_meta = batch_meta.value()]( + const std::shared_ptr tx) { + return tx->batchMeta() + ? **tx->batchMeta() != *front_batch_meta + : false; + }); + }; + + template + iroha::expected::Result + TransactionBatch::createTransactionBatch( + const types::SharedTxsCollectionType &transactions, + const validation::TransactionsCollectionValidator + &validator) { + auto answer = validator.validatePointers(transactions); + if (not allTxsInSameBatch(transactions)) { + answer.addReason(std::make_pair( + "Transaction batch: ", + std::vector{ + "Provided transactions are not from the same batch"})); + } + + if (answer.hasErrors()) { + return iroha::expected::makeError(answer.reason()); + } + return iroha::expected::makeValue(TransactionBatch(transactions)); + } + + template iroha::expected::Result + TransactionBatch::createTransactionBatch( + const types::SharedTxsCollectionType &transactions, + const validation::TransactionsCollectionValidator< + validation::TransactionValidator< + validation::FieldValidator, + validation::CommandValidatorVisitor< + validation::FieldValidator>>, + validation::BatchOrderValidator> &validator); + + template iroha::expected::Result + TransactionBatch::createTransactionBatch( + const types::SharedTxsCollectionType &transactions, + const validation::TransactionsCollectionValidator< + validation::TransactionValidator< + validation::FieldValidator, + validation::CommandValidatorVisitor< + validation::FieldValidator>>, + validation::AnyOrderValidator> &validator); + + template + iroha::expected::Result + TransactionBatch::createTransactionBatch( + std::shared_ptr transaction, + const TransactionValidator &transaction_validator) { + auto answer = transaction_validator.validate(*transaction); + if (answer.hasErrors()) { + return iroha::expected::makeError(answer.reason()); + } + return iroha::expected::makeValue( + TransactionBatch(types::SharedTxsCollectionType{transaction})); + }; + + template iroha::expected::Result + TransactionBatch::createTransactionBatch( + std::shared_ptr transaction, + const validation::TransactionValidator< + validation::FieldValidator, + validation::CommandValidatorVisitor> + &validator); + + const types::SharedTxsCollectionType &TransactionBatch::transactions() + const { + return transactions_; + } + + const types::HashType &TransactionBatch::reducedHash() const { + if (not reduced_hash_) { + reduced_hash_ = TransactionBatch::calculateReducedBatchHash( + transactions_ | boost::adaptors::transformed([](const auto &tx) { + return tx->reducedHash(); + })); + } + return reduced_hash_.value(); + } + + bool TransactionBatch::hasAllSignatures() const { + return std::all_of( + transactions_.begin(), transactions_.end(), [](const auto tx) { + return boost::size(tx->signatures()) >= tx->quorum(); + }); + } + + std::string TransactionBatch::toString() const { + return detail::PrettyStringBuilder() + .init("Batch") + .append("reducedHash", reducedHash().toString()) + .append("hasAllSignatures", hasAllSignatures() ? "true" : "false") + .append("transactions") + .appendAll(transactions(), [](auto &tx) { return tx->toString(); }) + .finalize(); + } + + } // namespace interface +} // namespace shared_model diff --git a/shared_model/interfaces/iroha_internal/transaction_batch.hpp b/shared_model/interfaces/iroha_internal/transaction_batch.hpp new file mode 100644 index 0000000000..30da44b513 --- /dev/null +++ b/shared_model/interfaces/iroha_internal/transaction_batch.hpp @@ -0,0 +1,104 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_TRANSACTION_BATCH_HPP +#define IROHA_TRANSACTION_BATCH_HPP + +#include "common/result.hpp" +#include "interfaces/common_objects/transaction_sequence_common.hpp" +#include "validators/transactions_collection/transactions_collection_validator.hpp" + +namespace shared_model { + namespace interface { + + class TransactionBatch { + public: + /** + * Create transaction batch out of collection of transactions + * @tparam TransactionValidator validates every single transaction + * @tparam OrderValidator validates order of transactions + * @param transactions collection of transactions, should be from the same + * batch + * @param validator transactions collection validator with provided + * transaction validator and order validator + * @return valid batch of transactions + */ + template + static iroha::expected::Result + createTransactionBatch(const types::SharedTxsCollectionType &transactions, + const validation::TransactionsCollectionValidator< + TransactionValidator, + OrderValidator> &validator); + + /** + * Creates transaction batch from single transaction + * @tparam TransactionValidator validates every single transaction + * @param transaction is transaction being validated and used to create + * batch + * @param transaction_validator transaction validation logic + * @return batch with single transaction + * @note transactions in such batches may not have batch meta information + */ + template + static iroha::expected::Result + createTransactionBatch(std::shared_ptr transaction, + const TransactionValidator &transaction_validator = + TransactionValidator()); + + /** + * Get transactions list + * @return list of transactions from the batch + */ + const types::SharedTxsCollectionType &transactions() const; + + /** + * Get the concatenation of reduced hashes as a single hash + * @param reduced_hashes collection of reduced hashes + * @return concatenated reduced hashes + */ + const types::HashType &reducedHash() const; + + /** + * Checks if every transaction has quorum signatures + * @return true if every transaction has quorum signatures, false + * otherwise + */ + bool hasAllSignatures() const; + + /** + * @return string representation of the object + */ + std::string toString() const; + + /** + * Get the concatenation of reduced hashes as a single hash + * That kind of hash does not respect batch type + * @tparam Collection type of const ref iterator + * @param reduced_hashes + * @return concatenated reduced hashes + */ + template + static types::HashType calculateReducedBatchHash( + const Collection &reduced_hashes) { + std::stringstream concatenated_hash; + for (const auto &hash : reduced_hashes) { + concatenated_hash << hash.hex(); + } + return types::HashType::fromHexString(concatenated_hash.str()); + } + + private: + explicit TransactionBatch( + const types::SharedTxsCollectionType &transactions) + : transactions_(transactions) {} + + types::SharedTxsCollectionType transactions_; + + mutable boost::optional reduced_hash_; + }; + } // namespace interface +} // namespace shared_model + +#endif // IROHA_TRANSACTION_BATCH_HPP diff --git a/shared_model/interfaces/iroha_internal/transaction_sequence.cpp b/shared_model/interfaces/iroha_internal/transaction_sequence.cpp index 2f21609c48..025413a28e 100644 --- a/shared_model/interfaces/iroha_internal/transaction_sequence.cpp +++ b/shared_model/interfaces/iroha_internal/transaction_sequence.cpp @@ -5,28 +5,114 @@ #include "interfaces/iroha_internal/transaction_sequence.hpp" +#include "interfaces/iroha_internal/transaction_batch.hpp" +#include "validators/field_validator.hpp" +#include "validators/transaction_validator.hpp" +#include "validators/transactions_collection/batch_order_validator.hpp" + + namespace shared_model { namespace interface { + template iroha::expected::Result TransactionSequence::createTransactionSequence( - const types::TransactionsForwardCollectionType &transactions, - const validation::TransactionsCollectionValidator &validator) { - auto answer = validator.validate(transactions); - if (answer.hasErrors()) { - return iroha::expected::makeError(answer.reason()); + const types::SharedTxsCollectionType &transactions, + const validation::TransactionsCollectionValidator + &validator) { + std::unordered_map>, + interface::types::HashType::Hasher> + extracted_batches; + std::vector batches; + + auto transaction_validator = validator.getTransactionValidator(); + + auto insert_batch = + [&batches](const iroha::expected::Value &value) { + batches.push_back(value.value); + }; + + validation::Answer result; + for (const auto &tx : transactions) { + if (auto meta = tx->batchMeta()) { + auto hashes = meta.get()->reducedHashes(); + auto batch_hash = TransactionBatch::calculateReducedBatchHash(hashes); + extracted_batches[batch_hash].push_back(tx); + } else { + TransactionBatch::createTransactionBatch(tx, transaction_validator) + .match(insert_batch, [&tx, &result](const auto &err) { + result.addReason(std::make_pair( + std::string("Error in transaction with reduced hash: ") + + tx->reducedHash().hex(), + std::vector{err.error})); + }); + } + } + + for (const auto &it : extracted_batches) { + TransactionBatch::createTransactionBatch(it.second, validator) + .match(insert_batch, [&it, &result](const auto &err) { + result.addReason(std::make_pair( + it.first.toString(), std::vector{err.error})); + }); + } + + if (result.hasErrors()) { + return iroha::expected::makeError(result.reason()); + } + + return iroha::expected::makeValue(TransactionSequence(batches)); + } + + template iroha::expected::Result + TransactionSequence::createTransactionSequence( + const types::SharedTxsCollectionType &transactions, + const validation::TransactionsCollectionValidator< + validation::TransactionValidator< + validation::FieldValidator, + validation::CommandValidatorVisitor< + validation::FieldValidator>>, + validation::AnyOrderValidator> &validator); + + template iroha::expected::Result + TransactionSequence::createTransactionSequence( + const types::SharedTxsCollectionType &transactions, + const validation::TransactionsCollectionValidator< + validation::TransactionValidator< + validation::FieldValidator, + validation::CommandValidatorVisitor< + validation::FieldValidator>>, + validation::BatchOrderValidator> &validator); + + const types::SharedTxsCollectionType &TransactionSequence::transactions() + const { + if (not transactions_) { + types::SharedTxsCollectionType result; + auto transactions_amount = 0u; + for (const auto &batch : batches_) { + transactions_amount += batch.transactions().size(); + } + result.reserve(transactions_amount); + for (const auto &batch : batches_) { + auto &transactions = batch.transactions(); + std::copy(transactions.begin(), + transactions.end(), + std::back_inserter(result)); + } + transactions_.emplace(std::move(result)); } - return iroha::expected::makeValue(TransactionSequence(transactions)); + return transactions_.value(); } - types::TransactionsForwardCollectionType - TransactionSequence::transactions() { - return transactions_; + const types::BatchesCollectionType &TransactionSequence::batches() const { + return batches_; } TransactionSequence::TransactionSequence( - const types::TransactionsForwardCollectionType &transactions) - : transactions_(transactions) {} + const types::BatchesCollectionType &batches) + : batches_(batches) {} } // namespace interface } // namespace shared_model diff --git a/shared_model/interfaces/iroha_internal/transaction_sequence.hpp b/shared_model/interfaces/iroha_internal/transaction_sequence.hpp index 9c5a8fe0ed..1ed6cc4af8 100644 --- a/shared_model/interfaces/iroha_internal/transaction_sequence.hpp +++ b/shared_model/interfaces/iroha_internal/transaction_sequence.hpp @@ -8,6 +8,7 @@ #include "common/result.hpp" #include "interfaces/common_objects/transaction_sequence_common.hpp" +#include "interfaces/iroha_internal/transaction_batch.hpp" #include "validators/transactions_collection/transactions_collection_validator.hpp" namespace shared_model { @@ -21,29 +22,41 @@ namespace shared_model { */ class TransactionSequence { public: + /** * Creator of transaction sequence * @param transactions collection of transactions * @param validator validator of the collections - * @return Result containing transaction sequence if validation successful - * and string message containing error otherwise + * @return Result containing transaction sequence if validation + * successful and string message containing error otherwise */ + template static iroha::expected::Result createTransactionSequence( - const types::TransactionsForwardCollectionType &transactions, - const validation::TransactionsCollectionValidator &validator); + const types::SharedTxsCollectionType &transactions, + const validation::TransactionsCollectionValidator< + TransactionValidator, + OrderValidator> &validator); + + /** + * Retrieves transactions from all batches as single collection + * @return all batches transactions + */ + const types::SharedTxsCollectionType &transactions() const; /** - * Get transactions collection - * @return transactions collection + * Get batches in transaction sequence + * Note that transaction without batch meta are returned as batch with + * single transaction + * @return collection of batches from transaction sequence */ - types::TransactionsForwardCollectionType transactions(); + const types::BatchesCollectionType &batches() const; private: - explicit TransactionSequence( - const types::TransactionsForwardCollectionType &transactions); + explicit TransactionSequence(const types::BatchesCollectionType &batches); - types::TransactionsForwardCollectionType transactions_; + types::BatchesCollectionType batches_; + mutable boost::optional transactions_; }; } // namespace interface diff --git a/shared_model/interfaces/iroha_internal/unsafe_proposal_factory.hpp b/shared_model/interfaces/iroha_internal/unsafe_proposal_factory.hpp new file mode 100644 index 0000000000..fad05f0007 --- /dev/null +++ b/shared_model/interfaces/iroha_internal/unsafe_proposal_factory.hpp @@ -0,0 +1,32 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_UNSAFE_PROPOSAL_FACTORY_HPP +#define IROHA_UNSAFE_PROPOSAL_FACTORY_HPP + +#include + +#include "interfaces/common_objects/types.hpp" + +namespace shared_model { + namespace interface { + class Proposal; + + /** + * UnsafeProposalFactory creates proposal without stateless validation + */ + class UnsafeProposalFactory { + public: + virtual std::unique_ptr unsafeCreateProposal( + types::HeightType height, + types::TimestampType created_time, + const types::TransactionsCollectionType &transactions) = 0; + + virtual ~UnsafeProposalFactory() = default; + }; + } // namespace interface +} // namespace shared_model + +#endif // IROHA_UNSAFE_PROPOSAL_FACTORY_HPP diff --git a/shared_model/interfaces/permissions.hpp b/shared_model/interfaces/permissions.hpp index 484ad39538..75165c91b4 100644 --- a/shared_model/interfaces/permissions.hpp +++ b/shared_model/interfaces/permissions.hpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include namespace shared_model { namespace interface { @@ -73,8 +73,6 @@ namespace shared_model { }; Role permissionFor(Grantable); - // TODO(@l4l) 19/06/18: Remove with IR-1452 - Grantable permissionOf(Role); /** * @param perm protocol object for checking @@ -98,9 +96,13 @@ namespace shared_model { public: PermissionSet(); PermissionSet(std::initializer_list list); + explicit PermissionSet(const std::string &bitstring); - size_t size() const; + std::string toBitstring() const; + + static size_t size(); PermissionSet &reset(); + PermissionSet &set(); PermissionSet &set(Perm p); PermissionSet &unset(Perm p); diff --git a/shared_model/interfaces/queries/get_account_detail.hpp b/shared_model/interfaces/queries/get_account_detail.hpp index 08bdd45bc9..e65dc78e58 100644 --- a/shared_model/interfaces/queries/get_account_detail.hpp +++ b/shared_model/interfaces/queries/get_account_detail.hpp @@ -18,13 +18,24 @@ #ifndef IROHA_SHARED_MODEL_GET_ACCOUNT_DETAIL_HPP #define IROHA_SHARED_MODEL_GET_ACCOUNT_DETAIL_HPP +#include + #include "interfaces/base/model_primitive.hpp" #include "interfaces/common_objects/types.hpp" namespace shared_model { namespace interface { /** - * Query for get all account's assets and balance + * Query for get all account's details; the algorithm of retrieving them is + * the following: + * - if query has only accountId, all details about the specified account + * will be returned + * - if there is a key in a query, details written by all writers under + * this key will be returned + * - if there is a writer in a query, all details written by this writer + * will be returned + * - if there are both key and writer in a query, details written by this + * writer AND under this key will be returned */ class GetAccountDetail : public ModelPrimitive { public: @@ -33,6 +44,16 @@ namespace shared_model { */ virtual const types::AccountIdType &accountId() const = 0; + /** + * @return key from key-value storage + */ + virtual boost::optional key() const = 0; + + /** + * @return account identifier of writer + */ + virtual boost::optional writer() const = 0; + std::string toString() const override; bool operator==(const ModelType &rhs) const override; diff --git a/shared_model/interfaces/queries/get_pending_transactions.hpp b/shared_model/interfaces/queries/get_pending_transactions.hpp new file mode 100644 index 0000000000..291684a018 --- /dev/null +++ b/shared_model/interfaces/queries/get_pending_transactions.hpp @@ -0,0 +1,30 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_SHARED_MODEL_GET_PENDING_TRANSACTIONS_HPP +#define IROHA_SHARED_MODEL_GET_PENDING_TRANSACTIONS_HPP + +#include "interfaces/base/model_primitive.hpp" +#include "interfaces/common_objects/types.hpp" + +namespace shared_model { + namespace interface { + + /** + * Get all pending (not fully signed) multisignature transactions or batches + * of transactions. + */ + class GetPendingTransactions + : public ModelPrimitive { + public: + std::string toString() const override; + + bool operator==(const ModelType &rhs) const override; + }; + + } // namespace interface +} // namespace shared_model + +#endif // IROHA_SHARED_MODEL_GET_PENDING_TRANSACTIONS_HPP diff --git a/shared_model/interfaces/queries/impl/get_account_detail.cpp b/shared_model/interfaces/queries/impl/get_account_detail.cpp index 57d6557150..f5816a0cc1 100644 --- a/shared_model/interfaces/queries/impl/get_account_detail.cpp +++ b/shared_model/interfaces/queries/impl/get_account_detail.cpp @@ -12,11 +12,14 @@ namespace shared_model { return detail::PrettyStringBuilder() .init("GetAccountDetail") .append("account_id", accountId()) + .append("key", key() ? *key() : "") + .append("writer", writer() ? *writer() : "") .finalize(); } bool GetAccountDetail::operator==(const ModelType &rhs) const { - return accountId() == rhs.accountId(); + return accountId() == rhs.accountId() and key() == rhs.key() + and writer() == rhs.writer(); } } // namespace interface diff --git a/shared_model/interfaces/queries/impl/get_pending_transactions.cpp b/shared_model/interfaces/queries/impl/get_pending_transactions.cpp new file mode 100644 index 0000000000..7c0007ea8e --- /dev/null +++ b/shared_model/interfaces/queries/impl/get_pending_transactions.cpp @@ -0,0 +1,22 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "interfaces/queries/get_pending_transactions.hpp" + +namespace shared_model { + namespace interface { + + std::string GetPendingTransactions::toString() const { + return detail::PrettyStringBuilder() + .init("GetPendingTransactions") + .finalize(); + } + + bool GetPendingTransactions::operator==(const ModelType &rhs) const { + return true; + } + + } // namespace interface +} // namespace shared_model diff --git a/shared_model/interfaces/queries/query.hpp b/shared_model/interfaces/queries/query.hpp index ca3cedb638..80d1b0565f 100644 --- a/shared_model/interfaces/queries/query.hpp +++ b/shared_model/interfaces/queries/query.hpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_SHARED_MODEL_QUERY_HPP @@ -32,6 +20,7 @@ #include "interfaces/queries/get_roles.hpp" #include "interfaces/queries/get_signatories.hpp" #include "interfaces/queries/get_transactions.hpp" +#include "interfaces/queries/get_pending_transactions.hpp" #include "interfaces/queries/query_payload_meta.hpp" namespace shared_model { @@ -59,7 +48,8 @@ namespace shared_model { GetAccountDetail, GetRoles, GetRolePermissions, - GetAssetInfo>; + GetAssetInfo, + GetPendingTransactions>; /// Types of concrete commands, in attached variant using QueryListType = QueryVariantType::types; diff --git a/shared_model/interfaces/transaction.hpp b/shared_model/interfaces/transaction.hpp index 2468b4ff37..929de17b9a 100644 --- a/shared_model/interfaces/transaction.hpp +++ b/shared_model/interfaces/transaction.hpp @@ -21,6 +21,7 @@ #include "interfaces/base/signable.hpp" #include "interfaces/commands/command.hpp" #include "interfaces/common_objects/types.hpp" +#include "iroha_internal/batch_meta.hpp" #include "utils/string_builder.hpp" namespace shared_model { @@ -30,7 +31,8 @@ namespace shared_model { * Transaction class represent well-formed intent from client to change * state of ledger. */ - class Transaction : public Signable { + using HashProvider = shared_model::crypto::Sha3_256; + class Transaction : public Signable { public: /** * @return creator of transaction @@ -52,6 +54,22 @@ namespace shared_model { */ virtual CommandsType commands() const = 0; + /** + * @return object payload (everything except signatures) + */ + virtual const types::BlobType &reducedPayload() const = 0; + + const types::HashType &reducedHash() const { + if (reduced_hash_ == boost::none) { + reduced_hash_.emplace(HashProvider::makeHash(reducedPayload())); + } + return *reduced_hash_; + } + /* + * @return Batch Meta if exists + */ + virtual boost::optional> batchMeta() const = 0; + std::string toString() const override { return detail::PrettyStringBuilder() .init("Transaction") @@ -62,10 +80,16 @@ namespace shared_model { .append("commands") .appendAll(commands(), [](auto &command) { return command.toString(); }) + .append("batch_meta", + batchMeta() ? batchMeta()->get()->toString() : "") + .append("reducedHash", reducedHash().toString()) .append("signatures") .appendAll(signatures(), [](auto &sig) { return sig.toString(); }) .finalize(); } + + private: + mutable boost::optional reduced_hash_; }; } // namespace interface diff --git a/shared_model/interfaces/transaction_responses/tx_response.hpp b/shared_model/interfaces/transaction_responses/tx_response.hpp index e47fcaa0ab..dd56105038 100644 --- a/shared_model/interfaces/transaction_responses/tx_response.hpp +++ b/shared_model/interfaces/transaction_responses/tx_response.hpp @@ -56,7 +56,11 @@ namespace shared_model { // ------------------------| Primitive override |------------------------- std::string toString() const override { - return boost::apply_visitor(detail::ToStringVisitor(), get()); + return detail::PrettyStringBuilder() + .init("TransactionResponse") + .append("transactionHash", transactionHash().hex()) + .append(boost::apply_visitor(detail::ToStringVisitor(), get())) + .finalize(); } bool operator==(const ModelType &rhs) const override { diff --git a/shared_model/packages/android/android-build.sh b/shared_model/packages/android/android-build.sh index 3bea98a854..6a3b6783ba 100755 --- a/shared_model/packages/android/android-build.sh +++ b/shared_model/packages/android/android-build.sh @@ -93,7 +93,7 @@ VERBOSE=1 cmake --build ./protobuf/.build --target install -- -j"$CORES" # ed25519 git clone https://github.com/hyperledger/iroha-ed25519.git -(cd ./iroha-ed25519 ; git checkout e7188b8393dbe5ac54378610d53630bd4a180038) +(cd ./iroha-ed25519 ; git checkout f42953c631fae93011612f6b1ee33f1f88c3f8af) cmake "${ANDROID_TOOLCHAIN_ARGS[@]}" "${INSTALL_ARGS[@]}" -DTESTING=OFF -DCMAKE_BUILD_TYPE="$BUILD_TYPE" -DBUILD=STATIC -H./iroha-ed25519 -B./iroha-ed25519/build VERBOSE=1 cmake --build ./iroha-ed25519/build --target install -- -j"$CORES" mv "$DEPS_DIR"/lib/static/libed25519.a "$DEPS_DIR"/lib; rmdir "$DEPS_DIR"/lib/static/ diff --git a/shared_model/packages/ios/ios-build.sh b/shared_model/packages/ios/ios-build.sh index 8bd9b86219..7875d28317 100755 --- a/shared_model/packages/ios/ios-build.sh +++ b/shared_model/packages/ios/ios-build.sh @@ -92,7 +92,7 @@ VERBOSE=1 cmake --build ./protobuf/.build --target install -- -j"$CORES" # ed25519 git clone https://github.com/hyperledger/iroha-ed25519.git (cd ./iroha-ed25519; -git checkout e7188b8393dbe5ac54378610d53630bd4a180038) +git checkout f42953c631fae93011612f6b1ee33f1f88c3f8af) cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE" "${IOS_TOOLCHAIN_ARGS[@]}" "${INSTALL_ARGS[@]}" -DTESTING=OFF -DBUILD=STATIC -H./iroha-ed25519 -B./iroha-ed25519/build VERBOSE=1 cmake --build ./iroha-ed25519/build --target install -- -j"$CORES" mv "$DEPS_DIR"/lib/static/libed25519.a "$DEPS_DIR"/lib; @@ -109,7 +109,6 @@ mkdir lib mkdir include cp "$DEPS_DIR"/lib/lib${PROTOBUF_LIB_NAME}.a \ "$DEPS_DIR"/lib/libed25519.a \ - ./iroha/shared_model/build/amount/libiroha_amount.a \ ./iroha/shared_model/build/generator/libgenerator.a \ ./iroha/shared_model/build/bindings/libbindings.a \ ./iroha/shared_model/build/cryptography/ed25519_sha3_impl/internal/libed25519_crypto.a \ diff --git a/shared_model/packages/javascript/binding.gyp b/shared_model/packages/javascript/binding.gyp index a157f52f4a..e8b0af2989 100644 --- a/shared_model/packages/javascript/binding.gyp +++ b/shared_model/packages/javascript/binding.gyp @@ -77,9 +77,9 @@ 'dependencies': [ 'shared_model' ], 'include_dirs': [ '<(iroha_lib_dir)', + '<(iroha_lib_dir)/schema', # TODO: Remove these include directories when Shared Model # will be completely separated from Iroha - '<(iroha_lib_dir)/../schema', '<(iroha_lib_dir)/../libs' ], 'sources': [ diff --git a/shared_model/packages/javascript/tests/txbuilder.js b/shared_model/packages/javascript/tests/txbuilder.js index af46727baf..5642f00d53 100644 --- a/shared_model/packages/javascript/tests/txbuilder.js +++ b/shared_model/packages/javascript/tests/txbuilder.js @@ -9,7 +9,7 @@ const assetId = 'coin#test' const testAccountId = 'test@test' test('ModelTransactionBuilder tests', function (t) { - t.plan(133) + t.plan(127) let crypto = new iroha.ModelCrypto() let keypair = crypto.convertFromExisting(publicKey, privateKey) @@ -38,14 +38,11 @@ test('ModelTransactionBuilder tests', function (t) { t.comment('Testing addAssetQuantity()') t.throws(() => correctTx.addAssetQuantity(), /Error: Illegal number of arguments/, 'Should throw Illegal number of arguments') t.throws(() => correctTx.addAssetQuantity(''), /Error: Illegal number of arguments/, 'Should throw Illegal number of arguments') - t.throws(() => correctTx.addAssetQuantity('', ''), /Error: Illegal number of arguments/, 'Should throw Illegal number of arguments') - t.throws(() => correctTx.addAssetQuantity('', '', '').build(), /AddAssetQuantity: \[\[Wrongly formed account_id, passed value: ''(.*)Wrongly formed asset_id, passed value: ''(.*)Amount must be greater than 0, passed value: 0 \]\]/, 'Should throw wrongly formed account_id, asset_id, Amount must be greater than 0') - t.throws(() => correctTx.addAssetQuantity(adminAccountId, assetId, '0').build(), /AddAssetQuantity: \[\[Amount must be greater than 0, passed value: 0 \]\]/, 'Should throw Amount must be greater than 0') - t.throws(() => correctTx.addAssetQuantity('', assetId, '1000').build(), /Wrongly formed account_id, passed value: ''/, 'Should throw Wrongly formed account_id') - t.throws(() => correctTx.addAssetQuantity('@@@', assetId, '1000').build(), /Wrongly formed account_id, passed value: '@@@'/, 'Should throw Wrongly formed account_id') - t.throws(() => correctTx.addAssetQuantity(adminAccountId, '', '1000').build(), /Wrongly formed asset_id, passed value: ''/, 'Should throw Wrongly formed asset_id') - t.throws(() => correctTx.addAssetQuantity(adminAccountId, '###', '1000').build(), /Wrongly formed asset_id, passed value: '###'/, 'Should throw Wrongly formed asset_id') - t.doesNotThrow(() => correctTx.addAssetQuantity(adminAccountId, assetId, '1000').build(), null, 'Should not throw any exceptions') + t.throws(() => correctTx.addAssetQuantity('', '').build(), /AddAssetQuantity: \[\[Wrongly formed asset_id, passed value: ''(.*)Amount must be greater than 0, passed value: 0 \]\]/, 'Should throw wrongly formed account_id, asset_id, Amount must be greater than 0') + t.throws(() => correctTx.addAssetQuantity(assetId, '0').build(), /AddAssetQuantity: \[\[Amount must be greater than 0, passed value: 0 \]\]/, 'Should throw Amount must be greater than 0') + t.throws(() => correctTx.addAssetQuantity('', '1000').build(), /Wrongly formed asset_id, passed value: ''/, 'Should throw Wrongly formed asset_id') + t.throws(() => correctTx.addAssetQuantity('###', '1000').build(), /Wrongly formed asset_id, passed value: '###'/, 'Should throw Wrongly formed asset_id') + t.doesNotThrow(() => correctTx.addAssetQuantity(assetId, '1000').build(), null, 'Should not throw any exceptions') // addPeer() tests t.comment('Testing addPeer()') @@ -128,7 +125,7 @@ test('ModelTransactionBuilder tests', function (t) { validPermissions.set(iroha.Role_kAddPeer) validPermissions.set(iroha.Role_kAddAssetQty) - t.throws(() => correctTx.createRole('new_user_role', emptyPerm).build(), /Permission set should contain at least one permission/, 'Should throw Permission set should contain at least one permission') + t.doesNotThrow(() => correctTx.createRole('new_user_role', emptyPerm).build(), null, 'Should not throw any exceptions') t.throws(() => correctTx.createRole('', validPermissions).build(), /Wrongly formed role_id, passed value: ''/, 'Should throw Wrongly formed role_id') t.throws(() => correctTx.createRole('@@@', validPermissions).build(), /Wrongly formed role_id, passed value: '@@@'/, 'Should throw Wrongly formed role_id') t.throws(() => correctTx.createRole('new_user_role', '').build(), /argument 3 of type 'shared_model::interface::RolePermissionSet/, 'Should throw ...argument 3 of type...') @@ -188,16 +185,13 @@ test('ModelTransactionBuilder tests', function (t) { t.comment('Testing subtractAssetQuantity()') t.throws(() => correctTx.subtractAssetQuantity(), /Error: Illegal number of arguments/, 'Should throw Illegal number of arguments') t.throws(() => correctTx.subtractAssetQuantity(''), /Error: Illegal number of arguments/, 'Should throw Illegal number of arguments') - t.throws(() => correctTx.subtractAssetQuantity('', ''), /Error: Illegal number of arguments/, 'Should throw Illegal number of arguments') - t.throws(() => correctTx.subtractAssetQuantity('', '', '').build(), /SubtractAssetQuantity: \[\[Wrongly formed account_id, passed value: ''(.*)Wrongly formed asset_id, passed value: ''(.*)Amount must be greater than 0, passed value: 0 \]\]/, 'Should throw wrongly formed account_id, asset_id, Amount must be greater than 0') - t.throws(() => correctTx.subtractAssetQuantity(adminAccountId, assetId, '0').build(), /SubtractAssetQuantity: \[\[Amount must be greater than 0, passed value: 0 \]\]/, 'Should throw Amount must be greater than 0') + t.throws(() => correctTx.subtractAssetQuantity('', '').build(), /SubtractAssetQuantity: \[\[Wrongly formed asset_id, passed value: ''(.*)Amount must be greater than 0, passed value: 0 \]\]/, 'Should throw wrongly formed account_id, asset_id, Amount must be greater than 0') + t.throws(() => correctTx.subtractAssetQuantity(assetId, '0').build(), /SubtractAssetQuantity: \[\[Amount must be greater than 0, passed value: 0 \]\]/, 'Should throw Amount must be greater than 0') // TODO: MAYBE Throw an exception on real amount - // t.throws(() => correctTx.subtractAssetQuantity(adminAccountId, assetId, '0.123').build(), /SubtractAssetQuantity: \[\[Amount must be integer, passed value: 0.123 \]\]/, 'Should throw Amount must be integer') - t.throws(() => correctTx.subtractAssetQuantity('', assetId, '1000').build(), /Wrongly formed account_id, passed value: ''/, 'Should throw Wrongly formed account_id') - t.throws(() => correctTx.subtractAssetQuantity('@@@', assetId, '1000').build(), /Wrongly formed account_id, passed value: '@@@'/, 'Should throw Wrongly formed account_id') - t.throws(() => correctTx.subtractAssetQuantity(adminAccountId, '', '1000').build(), /Wrongly formed asset_id, passed value: ''/, 'Should throw Wrongly formed asset_id') - t.throws(() => correctTx.subtractAssetQuantity(adminAccountId, '###', '1000').build(), /Wrongly formed asset_id, passed value: '###'/, 'Should throw Wrongly formed asset_id') - t.doesNotThrow(() => correctTx.subtractAssetQuantity(adminAccountId, assetId, '1000').build(), null, 'Should not throw any exceptions') + // t.throws(() => correctTx.subtractAssetQuantity(assetId, '0.123').build(), /SubtractAssetQuantity: \[\[Amount must be integer, passed value: 0.123 \]\]/, 'Should throw Amount must be integer') + t.throws(() => correctTx.subtractAssetQuantity('', '1000').build(), /Wrongly formed asset_id, passed value: ''/, 'Should throw Wrongly formed asset_id') + t.throws(() => correctTx.subtractAssetQuantity('###', '1000').build(), /Wrongly formed asset_id, passed value: '###'/, 'Should throw Wrongly formed asset_id') + t.doesNotThrow(() => correctTx.subtractAssetQuantity(assetId, '1000').build(), null, 'Should not throw any exceptions') // transferAsset() tests t.comment('Testing transferAsset()') diff --git a/shared_model/schema/CMakeLists.txt b/shared_model/schema/CMakeLists.txt new file mode 100644 index 0000000000..bdfde1c457 --- /dev/null +++ b/shared_model/schema/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +set(SCHEMA_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +compile_proto_to_cpp(block.proto) +compile_proto_to_cpp(transaction.proto) +compile_proto_to_cpp(commands.proto) +compile_proto_to_cpp(primitive.proto) +compile_proto_to_cpp(proposal.proto) +compile_proto_to_cpp(qry_responses.proto) +compile_proto_to_cpp(queries.proto) +compile_proto_to_cpp(endpoint.proto) + +add_library(schema + ${SCHEMA_OUT_DIR}/block.pb.cc + ${SCHEMA_OUT_DIR}/transaction.pb.cc + ${SCHEMA_OUT_DIR}/commands.pb.cc + ${SCHEMA_OUT_DIR}/primitive.pb.cc + ${SCHEMA_OUT_DIR}/proposal.pb.cc + ${SCHEMA_OUT_DIR}/qry_responses.pb.cc + ${SCHEMA_OUT_DIR}/queries.pb.cc + ${SCHEMA_OUT_DIR}/endpoint.pb.cc) + +target_link_libraries(schema + protobuf + ) +target_include_directories(schema PUBLIC + ${SCHEMA_OUT_DIR} + ) diff --git a/shared_model/schema/block.proto b/shared_model/schema/block.proto new file mode 100644 index 0000000000..8f10455e69 --- /dev/null +++ b/shared_model/schema/block.proto @@ -0,0 +1,23 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; +package iroha.protocol; +import "primitive.proto"; +import "transaction.proto"; + +message Block { + // everything that should be signed: + message Payload { + repeated Transaction transactions = 1; + uint32 tx_number = 2; // the number of transactions inside. Maximum 16384 or 2^14 + uint64 height = 3; // the current block number in a ledger + bytes prev_block_hash = 5; // Previous block hash + uint64 created_time = 6; + } + + Payload payload = 1; + repeated Signature signatures = 2; +} diff --git a/schema/commands.proto b/shared_model/schema/commands.proto similarity index 90% rename from schema/commands.proto rename to shared_model/schema/commands.proto index d6c4243ce4..c163755474 100644 --- a/schema/commands.proto +++ b/shared_model/schema/commands.proto @@ -1,11 +1,15 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + syntax = "proto3"; package iroha.protocol; import "primitive.proto"; message AddAssetQuantity { - string account_id = 1; - string asset_id = 2; - Amount amount = 3; + string asset_id = 1; + string amount = 2; } message AddPeer { @@ -55,7 +59,7 @@ message TransferAsset { string dest_account_id = 2; string asset_id = 3; string description = 4; - Amount amount = 5; + string amount = 5; } message AppendRole { @@ -84,9 +88,8 @@ message RevokePermission { } message SubtractAssetQuantity { - string account_id = 1; - string asset_id = 2; - Amount amount = 3; + string asset_id = 1; + string amount = 2; } message Command { diff --git a/schema/endpoint.proto b/shared_model/schema/endpoint.proto similarity index 74% rename from schema/endpoint.proto rename to shared_model/schema/endpoint.proto index da522c9550..6bc224b835 100644 --- a/schema/endpoint.proto +++ b/shared_model/schema/endpoint.proto @@ -1,12 +1,16 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + syntax = "proto3"; package iroha.protocol; -import "block.proto"; +import "transaction.proto"; import "queries.proto"; +import "qry_responses.proto"; import "google/protobuf/empty.proto"; -import "responses.proto"; - enum TxStatus { STATELESS_VALIDATION_FAILED = 0; @@ -28,13 +32,17 @@ message TxStatusRequest{ bytes tx_hash = 1; } +message TxList { + repeated Transaction transactions = 1; +} + service CommandService { rpc Torii (Transaction) returns (google.protobuf.Empty); + rpc ListTorii (TxList) returns (google.protobuf.Empty); rpc Status (TxStatusRequest) returns (ToriiResponse); rpc StatusStream(TxStatusRequest) returns (stream ToriiResponse); } - service QueryService { rpc Find (Query) returns (QueryResponse); rpc FetchCommits (BlocksQuery) returns (stream BlockQueryResponse); diff --git a/schema/primitive.proto b/shared_model/schema/primitive.proto similarity index 89% rename from schema/primitive.proto rename to shared_model/schema/primitive.proto index e930eb7516..c5214e0b14 100644 --- a/schema/primitive.proto +++ b/shared_model/schema/primitive.proto @@ -1,8 +1,12 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + /** * Messages related to primitive types, used in Commands and Queries * - * Contains RolePermission, GrantablePermission, Signature, - * uint256, Amount, and Peer. + * Contains RolePermission, GrantablePermission, Signature, and Peer. */ syntax = "proto3"; @@ -86,18 +90,6 @@ message Signature { bytes signature = 2; } -message uint256 { - uint64 first = 1; - uint64 second = 2; - uint64 third = 3; - uint64 fourth = 4; -} - -message Amount { - uint256 value = 1; - uint32 precision = 2; -} - message Peer { string address = 1; bytes peer_key = 2; diff --git a/schema/proposal.proto b/shared_model/schema/proposal.proto similarity index 57% rename from schema/proposal.proto rename to shared_model/schema/proposal.proto index e31c9d951e..b0f4d6e33e 100644 --- a/schema/proposal.proto +++ b/shared_model/schema/proposal.proto @@ -1,7 +1,12 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + syntax = "proto3"; package iroha.protocol; -import "block.proto"; +import "transaction.proto"; message Proposal { uint64 height = 1; diff --git a/schema/responses.proto b/shared_model/schema/qry_responses.proto similarity index 94% rename from schema/responses.proto rename to shared_model/schema/qry_responses.proto index d50d5f8393..2726064f49 100644 --- a/schema/responses.proto +++ b/shared_model/schema/qry_responses.proto @@ -1,6 +1,12 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + syntax = "proto3"; package iroha.protocol; import "block.proto"; +import "transaction.proto"; import "primitive.proto"; // *** WSV data structure *** // @@ -25,7 +31,7 @@ message Account { message AccountAsset { string asset_id = 1; string account_id = 2; - Amount balance = 3; + string balance = 3; } // *** Responses *** // diff --git a/schema/queries.proto b/shared_model/schema/queries.proto similarity index 81% rename from schema/queries.proto rename to shared_model/schema/queries.proto index 3615bdc996..1f817cb5f4 100644 --- a/schema/queries.proto +++ b/shared_model/schema/queries.proto @@ -1,3 +1,8 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + syntax = "proto3"; package iroha.protocol; @@ -29,7 +34,15 @@ message GetAccountAssets { } message GetAccountDetail { - string account_id = 1; + oneof opt_account_id{ + string account_id = 1; + } + oneof opt_key{ + string key = 2; + } + oneof opt_writer{ + string writer = 3; + } } message GetAssetInfo { @@ -44,6 +57,10 @@ message GetRolePermissions{ string role_id = 1; } +message GetPendingTransactions { + +} + message QueryPayloadMeta { uint64 created_time = 1; string creator_account_id = 2; @@ -66,6 +83,7 @@ message Query { GetRoles get_roles = 10; GetRolePermissions get_role_permissions = 11; GetAssetInfo get_asset_info = 12; + GetPendingTransactions get_pending_transactions = 13; } } diff --git a/shared_model/schema/transaction.proto b/shared_model/schema/transaction.proto new file mode 100644 index 0000000000..05742958c9 --- /dev/null +++ b/shared_model/schema/transaction.proto @@ -0,0 +1,38 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +syntax = "proto3"; +package iroha.protocol; +import "commands.proto"; +import "primitive.proto"; + +message Transaction { + message Payload { + message BatchMeta{ + enum BatchType{ + ATOMIC = 0; + ORDERED = 1; + } + BatchType type = 1; + // array of reduced hashes of all txs from the batch + repeated bytes reduced_hashes = 2; + } + message ReducedPayload{ + repeated Command commands = 1; + string creator_account_id = 2; + uint64 created_time = 3; + uint32 quorum = 4; + } + // transcation fields + ReducedPayload reduced_payload = 1; + // batch meta fields if tx belong to any batch + oneof optional_batch_meta{ + BatchMeta batch = 5; + } + } + + Payload payload = 1; + repeated Signature signatures = 2; +} diff --git a/shared_model/utils/amount_utils.cpp b/shared_model/utils/amount_utils.cpp index 45a88d5a10..7e3256ec07 100644 --- a/shared_model/utils/amount_utils.cpp +++ b/shared_model/utils/amount_utils.cpp @@ -1,33 +1,17 @@ /** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include "utils/amount_utils.hpp" - #include -#include "builders/default_builders.hpp" -#include "builders/protobuf/common_objects/proto_amount_builder.hpp" - namespace shared_model { namespace detail { const boost::multiprecision::uint256_t ten = 10; boost::multiprecision::uint256_t increaseValuePrecision( - boost::multiprecision::uint256_t value, int degree) { + const boost::multiprecision::uint256_t &value, int degree) { return value * pow(ten, degree); } @@ -53,10 +37,12 @@ namespace shared_model { % b.intValue().str()) .str())); } - return shared_model::builder::AmountBuilderWithoutValidator() - .precision(max_precision) - .intValue(val_a + val_b) - .build(); + std::string val = (val_a + val_b).str(); + if (max_precision != 0) { + val.insert((val.rbegin() + max_precision).base(), '.'); + } + return iroha::expected::makeValue( + std::make_shared(std::move(val))); } /** @@ -69,13 +55,6 @@ namespace shared_model { std::string> operator-(const shared_model::interface::Amount &a, const shared_model::interface::Amount &b) { - // check if a greater than b - if (a.intValue() < b.intValue()) { - return iroha::expected::makeError(std::make_shared( - (boost::format("minuend is smaller than subtrahend (%s - %s)") - % a.intValue().str() % b.intValue().str()) - .str())); - } auto max_precision = std::max(a.precision(), b.precision()); auto val_a = increaseValuePrecision(a.intValue(), max_precision - a.precision()); @@ -85,10 +64,14 @@ namespace shared_model { return iroha::expected::makeError( std::make_shared("new precision overflows number")); } - return shared_model::builder::AmountBuilderWithoutValidator() - .precision(max_precision) - .intValue(val_a - val_b) - .build(); + auto diff = val_a - val_b; + std::string val = diff.str(); + if (max_precision != 0 && val.size() > max_precision + 1) { + auto ptr = val.rbegin() + max_precision; + val.insert(ptr.base(), '.'); + } + return iroha::expected::makeValue( + std::make_shared(std::move(val))); } /** @@ -113,10 +96,12 @@ namespace shared_model { return iroha::expected::makeError( std::make_shared("operation overflows number")); } - return shared_model::builder::AmountBuilderWithoutValidator() - .precision(new_precision) - .intValue(val_amount) - .build(); + std::string val = val_amount.str(); + if (new_precision != 0) { + val.insert((val.rbegin() + new_precision).base(), '.'); + } + return iroha::expected::makeValue( + std::make_shared(std::move(val))); } int compareAmount(const shared_model::interface::Amount &a, diff --git a/shared_model/utils/amount_utils.hpp b/shared_model/utils/amount_utils.hpp index 6d92c0c2d7..4e27208595 100644 --- a/shared_model/utils/amount_utils.hpp +++ b/shared_model/utils/amount_utils.hpp @@ -21,6 +21,7 @@ #include #include "common/result.hpp" +#include "interfaces/common_objects/amount.hpp" namespace shared_model { namespace interface { @@ -29,7 +30,7 @@ namespace shared_model { namespace detail { boost::multiprecision::uint256_t increaseValuePrecision( - boost::multiprecision::uint256_t value, int degree); + const boost::multiprecision::uint256_t &value, int degree); /** * Sums up two amounts. diff --git a/shared_model/utils/lazy_initializer.hpp b/shared_model/utils/lazy_initializer.hpp index bdcf135294..f299bde6c8 100644 --- a/shared_model/utils/lazy_initializer.hpp +++ b/shared_model/utils/lazy_initializer.hpp @@ -35,6 +35,23 @@ namespace shared_model { using GeneratorType = std::function; public: + /** + * Default constructor prevents compilation error in pre 7.2 gcc. + * + * Short explanation: gcc needs default constructor + * when the constructor is inherited (via using Base::Base) + * if the inherited class has default initalizers for any of its members. + * This can be found in shared_model/backend/protobuf/protobuf/block.hpp + * where Lazy fields are initialized via default initializers. + * + * Note that this does not result in default constructor being called, so + * the resulting code is still correct. This is an issue of gcc compiler + * which is resolved in 7.2 + * + * For more details: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054 + */ + LazyInitializer(); + template explicit LazyInitializer(T &&generator) : generator_(std::forward(generator)) {} diff --git a/shared_model/validators/CMakeLists.txt b/shared_model/validators/CMakeLists.txt index 47bb9ff10d..8889323b8a 100644 --- a/shared_model/validators/CMakeLists.txt +++ b/shared_model/validators/CMakeLists.txt @@ -17,6 +17,7 @@ add_library(shared_model_stateless_validation field_validator.cpp transactions_collection/signed_transactions_collection_validator.cpp transactions_collection/unsigned_transactions_collection_validator.cpp + transactions_collection/batch_order_validator.cpp ) target_link_libraries(shared_model_stateless_validation diff --git a/shared_model/validators/any_block_validator.hpp b/shared_model/validators/any_block_validator.hpp index ebb8a94d51..2aeeb129d2 100644 --- a/shared_model/validators/any_block_validator.hpp +++ b/shared_model/validators/any_block_validator.hpp @@ -36,7 +36,7 @@ namespace shared_model { block_variant, [this](auto block) { return validate(*block); }); } - private: + protected: BlockValidator non_empty_block_validator_; EmptyBlockValidator empty_block_validator_; }; diff --git a/shared_model/validators/block_validator.hpp b/shared_model/validators/block_validator.hpp index 31b1b95c8c..e0d6e6b50d 100644 --- a/shared_model/validators/block_validator.hpp +++ b/shared_model/validators/block_validator.hpp @@ -33,21 +33,31 @@ namespace shared_model { /** * Class that validates block */ - template - class BlockValidator : public ContainerValidator { + template + class BlockValidator + : public ContainerValidator { public: + using ContainerValidator< + interface::Block, + FieldValidator, + TransactionValidator, + TransactionsCollectionValidator>::ContainerValidator; /** * Applies validation on block * @param block * @return Answer containing found error if any */ Answer validate(const interface::Block &block) const { - return ContainerValidator::validate(block, - "Block"); + return ContainerValidator< + interface::Block, + FieldValidator, + TransactionValidator, + TransactionsCollectionValidator>::validate(block, "Block"); } }; diff --git a/shared_model/validators/blocks_query_validator.hpp b/shared_model/validators/blocks_query_validator.hpp index 9d84df9299..fce54e096f 100644 --- a/shared_model/validators/blocks_query_validator.hpp +++ b/shared_model/validators/blocks_query_validator.hpp @@ -18,7 +18,8 @@ namespace shared_model { template class BlocksQueryValidator { public: - BlocksQueryValidator(const FieldValidator &field_validator = FieldValidator()) + BlocksQueryValidator( + const FieldValidator &field_validator = FieldValidator()) : field_validator_(field_validator) {} /** @@ -42,7 +43,7 @@ namespace shared_model { return answer; } - private: + protected: Answer answer_; FieldValidator field_validator_; }; diff --git a/shared_model/validators/container_validator.hpp b/shared_model/validators/container_validator.hpp index afec679e87..74edbf5a97 100644 --- a/shared_model/validators/container_validator.hpp +++ b/shared_model/validators/container_validator.hpp @@ -34,7 +34,8 @@ namespace shared_model { */ template + typename TransactionValidator, + typename TransactionsCollectionValidator> class ContainerValidator { protected: void validateTransaction( @@ -50,18 +51,25 @@ namespace shared_model { ReasonsGroupType &reason, const interface::types::TransactionsCollectionType &transactions) const { - for (const auto &tx : transactions) { - validateTransaction(reason, tx); + auto answer = transactions_collection_validator_.validate(transactions); + if (answer.hasErrors()) { + reason.second.push_back(answer.reason()); } } public: - ContainerValidator( + explicit ContainerValidator( + const FieldValidator &field_validator = FieldValidator(), + const TransactionsCollectionValidator + &transactions_collection_validator = + TransactionsCollectionValidator(), const TransactionValidator &transaction_validator = - TransactionValidator(), - const FieldValidator &field_validator = FieldValidator()) - : transaction_validator_(transaction_validator), + TransactionValidator()) + : transactions_collection_validator_( + transactions_collection_validator), + transaction_validator_(transaction_validator), field_validator_(field_validator) {} + Answer validate(const Iface &cont, std::string reason_name) const { Answer answer; ReasonsGroupType reason; @@ -76,6 +84,7 @@ namespace shared_model { } private: + TransactionsCollectionValidator transactions_collection_validator_; TransactionValidator transaction_validator_; protected: diff --git a/shared_model/validators/default_validator.hpp b/shared_model/validators/default_validator.hpp index 072d2043ad..2ee1428ede 100644 --- a/shared_model/validators/default_validator.hpp +++ b/shared_model/validators/default_validator.hpp @@ -27,45 +27,113 @@ #include "validators/query_validator.hpp" #include "validators/signable_validator.hpp" #include "validators/transaction_validator.hpp" +#include "validators/transactions_collection/batch_order_validator.hpp" +#include "validators/transactions_collection/signed_transactions_collection_validator.hpp" +#include "validators/transactions_collection/unsigned_transactions_collection_validator.hpp" namespace shared_model { namespace validation { + + // -----------------------| Transaction validation |------------------------ + + /** + * Transaction validator which checks stateless validation WITHOUT + * signatures + */ using DefaultTransactionValidator = TransactionValidator>; - using DefaultQueryValidator = - QueryValidator>; - using DefaultBlocksQueryValidator = BlocksQueryValidator; - using DefaultProposalValidator = - ProposalValidator; - - using DefaultBlockValidator = - BlockValidator; - - using DefaultEmptyBlockValidator = EmptyBlockValidator; - using DefaultAnyBlockValidator = - AnyBlockValidator; - - using DefaultSignableTransactionValidator = + /** + * Transaction validator which checks stateless validation + */ + using DefaultSignedTransactionValidator = SignableModelValidator; + // --------------------------| Query validation |--------------------------- + + /** + * Query validator which checks stateless validation WITHOUT signatures + */ + using DefaultUnsignedQueryValidator = + QueryValidator>; + + /** + * Block query validator checks stateless validation WITHOUT signatures + */ + using DefaultUnsignedBlocksQueryValidator = + BlocksQueryValidator; + + /** + * Query validator which checks stateless validation including signatures + */ using DefaultSignableQueryValidator = - SignableModelValidator; - using DefaultSignableProposalValidator = - SignableModelValidator; + // --------------------------| Block validation |--------------------------- + + /** + * Block validator which checks blocks WITHOUT signatures + */ + using DefaultUnsignedBlockValidator = BlockValidator< + FieldValidator, + DefaultTransactionValidator, + SignedTransactionsCollectionValidator>; + + /** + * Block validator which checks blocks including signatures + */ using DefaultSignableBlockValidator = - SignableModelValidator; + + /** + * @deprecated + * In https://soramitsu.atlassian.net/browse/IR-1418 should be removed + */ + using DefaultEmptyBlockValidator = EmptyBlockValidator; + + /** + * @deprecated + * In https://soramitsu.atlassian.net/browse/IR-1418 should be removed + */ + using DefaultAnyBlockValidator = + AnyBlockValidator; + + // ------------------------| Proposal validation |-------------------------- + + /** + * Proposal validator which checks stateless validation of proposal + */ + using DefaultProposalValidator = ProposalValidator< + FieldValidator, + DefaultTransactionValidator, + UnsignedTransactionsCollectionValidator>; + + // -----------------| Transaction collection validation |------------------- + + /** + * Check sequence of transactions without signatures + */ + using DefaultUnsignedTxCollectionValidator = + UnsignedTransactionsCollectionValidator; + } // namespace validation } // namespace shared_model diff --git a/shared_model/validators/empty_block_validator.hpp b/shared_model/validators/empty_block_validator.hpp index a3e18b3342..64267892e2 100644 --- a/shared_model/validators/empty_block_validator.hpp +++ b/shared_model/validators/empty_block_validator.hpp @@ -34,7 +34,7 @@ namespace shared_model { return answer; } - private: + protected: FieldValidator field_validator_; }; diff --git a/shared_model/validators/field_validator.cpp b/shared_model/validators/field_validator.cpp index 9f11e2684d..7a09ee72e7 100644 --- a/shared_model/validators/field_validator.cpp +++ b/shared_model/validators/field_validator.cpp @@ -70,8 +70,9 @@ namespace shared_model { const std::regex FieldValidator::detail_key_regex_(detail_key_pattern_); const std::regex FieldValidator::role_id_regex_(role_id_pattern_); - FieldValidator::FieldValidator(time_t future_gap) - : future_gap_(future_gap) {} + FieldValidator::FieldValidator(time_t future_gap, + TimeFunction time_provider) + : future_gap_(future_gap), time_provider_(time_provider) {} void FieldValidator::validateAccountId( ReasonsGroupType &reason, @@ -133,8 +134,9 @@ namespace shared_model { if (not std::regex_match(address, peer_address_regex_)) { auto message = (boost::format("Wrongly formed peer address, passed value: '%s'. " - "Field should have valid IPv4 format or be a valid " - "hostname following RFC1123 specification") + "Field should have a valid 'host:port' format where " + "host is IPv4 or a " + "hostname following RFC1035, RFC1123 specifications") % address) .str(); reason.second.push_back(std::move(message)); @@ -250,24 +252,6 @@ namespace shared_model { } } - void FieldValidator::validateRolePermissions( - ReasonsGroupType &reason, - const interface::RolePermissionSet &permissions) const { - if (permissions.none()) { - reason.second.push_back( - "Permission set should contain at least one permission"); - } - } - - void FieldValidator::validateGrantablePermissions( - ReasonsGroupType &reason, - const interface::GrantablePermissionSet &permissions) const { - if (permissions.none()) { - reason.second.push_back( - "Permission set should contain at least one permission"); - } - } - void FieldValidator::validateQuorum( ReasonsGroupType &reason, const interface::types::QuorumType &quorum) const { @@ -292,7 +276,7 @@ namespace shared_model { void FieldValidator::validateCreatedTime( ReasonsGroupType &reason, const interface::types::TimestampType ×tamp) const { - iroha::ts64_t now = iroha::time::now(); + iroha::ts64_t now = time_provider_(); if (now + future_gap_ < timestamp) { auto message = (boost::format("bad timestamp: sent from future, " @@ -302,7 +286,7 @@ namespace shared_model { reason.second.push_back(std::move(message)); } - if (now > max_delay + timestamp) { + if (now > kMaxDelay + timestamp) { auto message = (boost::format("bad timestamp: too old, timestamp: %llu, now: %llu") % timestamp % now) @@ -326,6 +310,9 @@ namespace shared_model { ReasonsGroupType &reason, const interface::types::SignatureRangeType &signatures, const crypto::Blob &source) const { + if (boost::empty(signatures)) { + reason.second.push_back("Signatures cannot be empty"); + } for (const auto &signature : signatures) { const auto &sign = signature.signedData(); const auto &pkey = signature.publicKey(); @@ -368,6 +355,9 @@ namespace shared_model { .str()); } } + void FieldValidator::validateBatchMeta( + shared_model::validation::ReasonsGroupType &reason, + const interface::BatchMeta &batch_meta) const {} void FieldValidator::validateHeight( shared_model::validation::ReasonsGroupType &reason, diff --git a/shared_model/validators/field_validator.hpp b/shared_model/validators/field_validator.hpp index 24cc829948..2b95d1a156 100644 --- a/shared_model/validators/field_validator.hpp +++ b/shared_model/validators/field_validator.hpp @@ -36,8 +36,14 @@ namespace shared_model { * and query */ class FieldValidator { + private: + using TimeFunction = std::function; + public: - FieldValidator(time_t future_gap = default_future_gap); + FieldValidator(time_t future_gap = kDefaultFutureGap, + TimeFunction time_provider = [] { + return iroha::time::now(); + }); void validateAccountId( ReasonsGroupType &reason, @@ -116,14 +122,6 @@ namespace shared_model { ReasonsGroupType &reason, const interface::permissions::Grantable &permission) const; - void validateRolePermissions( - ReasonsGroupType &reason, - const interface::RolePermissionSet &permissions) const; - - void validateGrantablePermissions( - ReasonsGroupType &reason, - const interface::GrantablePermissionSet &permissions) const; - void validateQuorum(ReasonsGroupType &reason, const interface::types::QuorumType &quorum) const; @@ -151,6 +149,10 @@ namespace shared_model { ReasonsGroupType &reason, const interface::types::DescriptionType &description) const; + void validateBatchMeta( + ReasonsGroupType &reason, + const interface::BatchMeta &description) const; + void validateHeight(ReasonsGroupType &reason, const interface::types::HeightType &height) const; @@ -180,11 +182,15 @@ namespace shared_model { // gap for future transactions time_t future_gap_; + // time provider callback + TimeFunction time_provider_; + + public: // max-delay between tx creation and validation - static constexpr auto max_delay = + static constexpr auto kMaxDelay = std::chrono::hours(24) / std::chrono::milliseconds(1); // default value for future_gap field of FieldValidator - static constexpr auto default_future_gap = + static constexpr auto kDefaultFutureGap = std::chrono::minutes(5) / std::chrono::milliseconds(1); // size of key diff --git a/shared_model/validators/proposal_validator.hpp b/shared_model/validators/proposal_validator.hpp index 714a4c1c69..e0c50ceb92 100644 --- a/shared_model/validators/proposal_validator.hpp +++ b/shared_model/validators/proposal_validator.hpp @@ -33,21 +33,31 @@ namespace shared_model { /** * Class that validates proposal */ - template - class ProposalValidator : public ContainerValidator { + template + class ProposalValidator + : public ContainerValidator { public: + using ContainerValidator< + interface::Proposal, + FieldValidator, + TransactionValidator, + TransactionsCollectionValidator>::ContainerValidator; /** * Applies validation on proposal * @param proposal * @return Answer containing found error if any */ Answer validate(const interface::Proposal &prop) const { - return ContainerValidator::validate(prop, - "Proposal"); + return ContainerValidator< + interface::Proposal, + FieldValidator, + TransactionValidator, + TransactionsCollectionValidator>::validate(prop, "Proposal"); } }; diff --git a/shared_model/validators/query_validator.hpp b/shared_model/validators/query_validator.hpp index 05e183fb3b..3a9ed8ae98 100644 --- a/shared_model/validators/query_validator.hpp +++ b/shared_model/validators/query_validator.hpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_SHARED_MODEL_QUERY_VALIDATOR_HPP @@ -20,7 +8,7 @@ #include -#include "interfaces/queries/query.hpp" +#include "backend/protobuf/queries/proto_query.hpp" #include "validators/answer.hpp" namespace shared_model { @@ -137,6 +125,14 @@ namespace shared_model { return reason; } + ReasonsGroupType operator()( + const interface::GetPendingTransactions &qry) const { + ReasonsGroupType reason; + reason.first = "GetPendingTransactions"; + + return reason; + } + private: FieldValidator validator_; }; @@ -174,15 +170,26 @@ namespace shared_model { answer.addReason(std::move(qry_reason)); } - auto reason = boost::apply_visitor(query_field_validator_, qry.get()); - if (not reason.second.empty()) { + auto qry_case = static_cast(qry) + .getTransport() + .payload() + .query_case(); + if (iroha::protocol::Query_Payload::QUERY_NOT_SET == qry_case) { + ReasonsGroupType reason; + reason.first = "Undefined"; + reason.second.push_back("query is undefined"); answer.addReason(std::move(reason)); + } else { + auto reason = boost::apply_visitor(query_field_validator_, qry.get()); + if (not reason.second.empty()) { + answer.addReason(std::move(reason)); + } } return answer; } - private: + protected: Answer answer_; FieldValidator field_validator_; QueryFieldValidator query_field_validator_; diff --git a/shared_model/validators/signable_validator.hpp b/shared_model/validators/signable_validator.hpp index 034e07d437..313251eb1c 100644 --- a/shared_model/validators/signable_validator.hpp +++ b/shared_model/validators/signable_validator.hpp @@ -26,13 +26,16 @@ namespace shared_model { template class SignableModelValidator : public ModelValidator { - // using ModelValidator::ModelValidator; public: + explicit SignableModelValidator( + FieldValidator &&validator = FieldValidator()) + : ModelValidator(std::move(validator)) {} + Answer validate(const Model &model) const { auto answer = ModelValidator::validate(model); std::string reason_name = "Signature"; ReasonsGroupType reason(reason_name, GroupedReasons()); - FieldValidator().validateSignatures( + ModelValidator::field_validator_.validateSignatures( reason, model.signatures(), model.payload()); if (not reason.second.empty()) { answer.addReason(std::move(reason)); diff --git a/shared_model/validators/transaction_validator.hpp b/shared_model/validators/transaction_validator.hpp index 8fd3f257fb..b34c96b4b6 100644 --- a/shared_model/validators/transaction_validator.hpp +++ b/shared_model/validators/transaction_validator.hpp @@ -21,8 +21,9 @@ #include #include +#include "backend/protobuf/commands/proto_command.hpp" #include "backend/protobuf/permissions.hpp" -#include "interfaces/transaction.hpp" +#include "backend/protobuf/transaction.hpp" #include "validators/answer.hpp" namespace shared_model { @@ -45,7 +46,6 @@ namespace shared_model { ReasonsGroupType reason; addInvalidCommand(reason, "AddAssetQuantity"); - validator_.validateAccountId(reason, aaq.accountId()); validator_.validateAssetId(reason, aaq.assetId()); validator_.validateAmount(reason, aaq.amount()); @@ -118,7 +118,14 @@ namespace shared_model { addInvalidCommand(reason, "CreateRole"); validator_.validateRoleId(reason, cr.roleName()); - validator_.validateRolePermissions(reason, cr.rolePermissions()); + for (auto i : static_cast(cr) + .getTransport() + .create_role() + .permissions()) { + validator_.validateRolePermission( + reason, + static_cast(i)); + } return reason; } @@ -189,7 +196,6 @@ namespace shared_model { ReasonsGroupType reason; addInvalidCommand(reason, "SubtractAssetQuantity"); - validator_.validateAccountId(reason, saq.accountId()); validator_.validateAssetId(reason, saq.assetId()); validator_.validateAmount(reason, saq.amount()); @@ -260,12 +266,25 @@ namespace shared_model { tx.creatorAccountId()); field_validator_.validateCreatedTime(tx_reason, tx.createdTime()); field_validator_.validateQuorum(tx_reason, tx.quorum()); + if (tx.batchMeta() != boost::none) + field_validator_.validateBatchMeta(tx_reason, **tx.batchMeta()); if (not tx_reason.second.empty()) { answer.addReason(std::move(tx_reason)); } for (const auto &command : tx.commands()) { + auto cmd_case = + static_cast(command) + .getTransport() + .command_case(); + if (iroha::protocol::Command::COMMAND_NOT_SET == cmd_case) { + ReasonsGroupType reason; + reason.first = "Undefined"; + reason.second.push_back("command is undefined"); + answer.addReason(std::move(reason)); + continue; + } auto reason = boost::apply_visitor(command_validator_, command.get()); if (not reason.second.empty()) { answer.addReason(std::move(reason)); @@ -275,7 +294,7 @@ namespace shared_model { return answer; } - private: + protected: FieldValidator field_validator_; CommandValidator command_validator_; }; diff --git a/shared_model/validators/transactions_collection/any_order_validator.hpp b/shared_model/validators/transactions_collection/any_order_validator.hpp new file mode 100644 index 0000000000..14409055b5 --- /dev/null +++ b/shared_model/validators/transactions_collection/any_order_validator.hpp @@ -0,0 +1,23 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_ANY_ORDER_VALIDATOR_HPP +#define IROHA_ANY_ORDER_VALIDATOR_HPP + +#include "order_validator.hpp" + +namespace shared_model { + namespace validation { + class AnyOrderValidator : public OrderValidator { + public: + Answer validate(const interface::types::SharedTxsCollectionType + &transactions) const override { + return Answer(); + }; + }; + } // namespace validation +} // namespace shared_model + +#endif // IROHA_ANY_ORDER_VALIDATOR_HPP diff --git a/shared_model/validators/transactions_collection/batch_order_validator.cpp b/shared_model/validators/transactions_collection/batch_order_validator.cpp new file mode 100644 index 0000000000..003d33a73d --- /dev/null +++ b/shared_model/validators/transactions_collection/batch_order_validator.cpp @@ -0,0 +1,112 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "batch_order_validator.hpp" +#include "interfaces/iroha_internal/batch_meta.hpp" + +namespace shared_model { + namespace validation { + std::string BatchOrderValidator::canFollow( + boost::optional> tr1, + boost::optional> tr2) const { + boost::optional> batch1 = + tr1 ? tr1.value()->batchMeta() : boost::none; + boost::optional> batch2 = + tr2 ? tr2.value()->batchMeta() : boost::none; + // both transactions are not a part of any batch + if (not batch1 and not batch2) { + return ""; + } + // beginning of a batch + if (not batch1) { + if (batch2.get()->reducedHashes().size() == 0) { + return (boost::format("Tx %s has a batch of 0 transactions") + % tr2.value()->hash().hex()) + .str(); + } + if (batch2.get()->reducedHashes().front() + != tr2.value()->reducedHash()) { + return (boost::format("Tx %s is a first transaction of a batch, but " + "it's reduced hash %s doesn't match the first " + "reduced hash in batch %s") + % tr2.value()->hash().hex() + % batch2.get()->reducedHashes().front().hex() + % tr2.value()->reducedHash().hex()) + .str(); + } + return ""; + } + // end of a batch + if (not batch2) { + if (batch1.get()->reducedHashes().back() + != tr1.value()->reducedHash()) { + return (boost::format("Tx %s is a last transaction of a batch, but " + "it's reduced hash %s doesn't match the last " + "reduced hash in batch %s") + % tr1.value()->hash().hex() + % batch1.get()->reducedHashes().back().hex() + % tr1.value()->reducedHash().hex()) + .str(); + } + return ""; + } + // inside of a batch + auto it1 = boost::find(batch1.get()->reducedHashes(), + tr1.value()->reducedHash()); + auto it2 = boost::find(batch2.get()->reducedHashes(), + tr2.value()->reducedHash()); + if (it1 == end(batch1.get()->reducedHashes()) + or it2 == end(batch2.get()->reducedHashes()) + or next(it1) == end(batch1.get()->reducedHashes()) + or *next(it1) != *it2) { + // end of the bach and beginning of the next + if (canFollow(tr1, boost::none) == "" + and canFollow(boost::none, tr2) == "") { + return ""; + } + return (boost::format("Tx %s is followed by %s, but their reduced" + "hashed doesn't follow each other in their batch" + "meta") + % tr1.value()->hash().hex() % tr2.value()->hash().hex()) + .str(); + } + if (**batch1 != **batch2) { + return (boost::format("Tx %s and %s are part of the same batch, but " + "their batch metas doesn't match") + % tr1.value()->hash().hex() % tr2.value()->hash().hex()) + .str(); + } + return ""; + } + + Answer BatchOrderValidator::validate( + const interface::types::SharedTxsCollectionType &transactions) const { + Answer res; + ReasonsGroupType reason; + reason.first = "Transaction order"; + boost::optional + prev_transaction = boost::none; + + for (auto &transaction : transactions) { + auto message = canFollow(prev_transaction, transaction); + if (message != "") { + reason.second.push_back(message); + } + prev_transaction = transaction; + } + auto message = canFollow(prev_transaction, boost::none); + if (message != "") { + reason.second.push_back(message); + } + if (not reason.second.empty()) { + res.addReason(std::move(reason)); + } + return res; + } + } // namespace validation +} // namespace shared_model diff --git a/shared_model/validators/transactions_collection/batch_order_validator.hpp b/shared_model/validators/transactions_collection/batch_order_validator.hpp new file mode 100644 index 0000000000..25eb427b67 --- /dev/null +++ b/shared_model/validators/transactions_collection/batch_order_validator.hpp @@ -0,0 +1,33 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_BATCH_ORDER_VALIDATOR_HPP +#define IROHA_BATCH_ORDER_VALIDATOR_HPP + +#include "order_validator.hpp" + +namespace shared_model { + namespace validation { + class BatchOrderValidator : public OrderValidator { + private: + /** + * check order of transaction, recieves 2 consequtive transactions in + * sequence and check weater they can be a part of correct sequence + * @param t1, t2 transactions to check the order of boost::none to specify + * beginning or end of sequence + * @return empty string if order is correct or error message + */ + std::string canFollow( + boost::optional> tr1, + boost::optional> tr2) const; + + public: + virtual Answer validate(const interface::types::SharedTxsCollectionType + &transactions) const override; + }; + } // namespace validation +} // namespace shared_model + +#endif // IROHA_ORDER_VALIDATOR_HPP diff --git a/shared_model/validators/transactions_collection/order_validator.hpp b/shared_model/validators/transactions_collection/order_validator.hpp new file mode 100644 index 0000000000..de59c487df --- /dev/null +++ b/shared_model/validators/transactions_collection/order_validator.hpp @@ -0,0 +1,22 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_ORDER_VALIDATOR_HPP +#define IROHA_ORDER_VALIDATOR_HPP + +#include "interfaces/common_objects/transaction_sequence_common.hpp" +#include "validators/answer.hpp" + +namespace shared_model { + namespace validation { + class OrderValidator { + public: + virtual Answer validate(const interface::types::SharedTxsCollectionType + &transactions) const = 0; + }; + } // namespace validation +} // namespace shared_model + +#endif // IROHA_ORDER_VALIDATOR_HPP diff --git a/shared_model/validators/transactions_collection/signed_transactions_collection_validator.cpp b/shared_model/validators/transactions_collection/signed_transactions_collection_validator.cpp index c23266de92..4a91d9ad51 100644 --- a/shared_model/validators/transactions_collection/signed_transactions_collection_validator.cpp +++ b/shared_model/validators/transactions_collection/signed_transactions_collection_validator.cpp @@ -5,14 +5,50 @@ #include "validators/transactions_collection/signed_transactions_collection_validator.hpp" +#include +#include "validators/field_validator.hpp" +#include "validators/transaction_validator.hpp" +#include "validators/transactions_collection/batch_order_validator.hpp" + namespace shared_model { namespace validation { - Answer SignedTransactionsCollectionValidator::validate( - const interface::types::TransactionsForwardCollectionType &transactions) - const { - return Answer(); + template + Answer SignedTransactionsCollectionValidator:: + validatePointers(const interface::types::SharedTxsCollectionType + &transactions) const { + Answer res = + SignedTransactionsCollectionValidator::order_validator_.validate( + transactions); + ReasonsGroupType reason; + reason.first = "Transaction list"; + for (const auto &tx : transactions) { + auto answer = + SignedTransactionsCollectionValidator::transaction_validator_ + .validate(*tx); + if (answer.hasErrors()) { + auto message = + (boost::format("Tx %s : %s") % tx->hash().hex() % answer.reason()) + .str(); + reason.second.push_back(message); + } + } + if (not reason.second.empty()) { + res.addReason(std::move(reason)); + } + return res; } + template class SignedTransactionsCollectionValidator< + TransactionValidator>, + AnyOrderValidator>; + + template class SignedTransactionsCollectionValidator< + TransactionValidator>, + BatchOrderValidator>; + } // namespace validation } // namespace shared_model diff --git a/shared_model/validators/transactions_collection/signed_transactions_collection_validator.hpp b/shared_model/validators/transactions_collection/signed_transactions_collection_validator.hpp index 882e8b3009..2bb35a3274 100644 --- a/shared_model/validators/transactions_collection/signed_transactions_collection_validator.hpp +++ b/shared_model/validators/transactions_collection/signed_transactions_collection_validator.hpp @@ -6,6 +6,7 @@ #ifndef IROHA_SIGNED_TRANSACTIONS_COLLECTION_VALIDATOR_HPP #define IROHA_SIGNED_TRANSACTIONS_COLLECTION_VALIDATOR_HPP +#include "validators/transactions_collection/any_order_validator.hpp" #include "validators/transactions_collection/transactions_collection_validator.hpp" namespace shared_model { @@ -16,11 +17,17 @@ namespace shared_model { * transaction from the collection to be unsigned. Batch logic should be * checked */ + template class SignedTransactionsCollectionValidator - : public TransactionsCollectionValidator { + : public TransactionsCollectionValidator { public: - Answer validate(const interface::types::TransactionsForwardCollectionType - &transactions) const override; + using TransactionsCollectionValidator< + TransactionValidator, + OrderValidator>::TransactionsCollectionValidator; + Answer validatePointers(const interface::types::SharedTxsCollectionType + &transactions) const override; }; } // namespace validation diff --git a/shared_model/validators/transactions_collection/transactions_collection_validator.hpp b/shared_model/validators/transactions_collection/transactions_collection_validator.hpp index 3255295720..d8e2b5f0b9 100644 --- a/shared_model/validators/transactions_collection/transactions_collection_validator.hpp +++ b/shared_model/validators/transactions_collection/transactions_collection_validator.hpp @@ -6,8 +6,10 @@ #ifndef IROHA_TRANSACTION_SEQUENCE_VALIDATOR_HPP #define IROHA_TRANSACTION_SEQUENCE_VALIDATOR_HPP +#include #include "interfaces/common_objects/transaction_sequence_common.hpp" #include "validators/answer.hpp" +#include "validators/transactions_collection/any_order_validator.hpp" namespace shared_model { namespace validation { @@ -16,16 +18,44 @@ namespace shared_model { * Validator of transaction's collection, this is not fair implementation * now, it always returns empty answer */ + template class TransactionsCollectionValidator { + protected: + TransactionValidator transaction_validator_; + OrderValidator order_validator_; + public: + TransactionsCollectionValidator( + const TransactionValidator &transactions_validator = + TransactionValidator(), + const OrderValidator &order_validator = OrderValidator()) + : order_validator_(order_validator) {} + + // TODO: IR-1505, igor-egorov, 2018-07-05 Remove method below when + // proposal and block will return collection of shared transactions /** * Validates collection of transactions * @param transactions collection of transactions * @return Answer containing errors if any */ - virtual Answer validate( - const interface::types::TransactionsForwardCollectionType - &transactions) const = 0; + Answer validate(const interface::types::TransactionsForwardCollectionType + &transactions) const { + interface::types::SharedTxsCollectionType res; + std::transform(std::begin(transactions), + std::end(transactions), + std::back_inserter(res), + [](const auto &tx) { return clone(tx); }); + return validatePointers(res); + } + + const TransactionValidator &getTransactionValidator() const { + return transaction_validator_; + } + + virtual Answer validatePointers( + const interface::types::SharedTxsCollectionType &transactions) + const = 0; }; } // namespace validation diff --git a/shared_model/validators/transactions_collection/unsigned_transactions_collection_validator.cpp b/shared_model/validators/transactions_collection/unsigned_transactions_collection_validator.cpp index af8bdf9715..b8027fb863 100644 --- a/shared_model/validators/transactions_collection/unsigned_transactions_collection_validator.cpp +++ b/shared_model/validators/transactions_collection/unsigned_transactions_collection_validator.cpp @@ -5,14 +5,51 @@ #include "validators/transactions_collection/unsigned_transactions_collection_validator.hpp" +#include +#include "validators/field_validator.hpp" +#include "validators/transaction_validator.hpp" +#include "validators/transactions_collection/batch_order_validator.hpp" + namespace shared_model { namespace validation { - Answer UnsignedTransactionsCollectionValidator::validate( - const interface::types::TransactionsForwardCollectionType &transactions) - const { - return Answer(); + template + Answer UnsignedTransactionsCollectionValidator:: + validatePointers(const interface::types::SharedTxsCollectionType + &transactions) const { + Answer res = + UnsignedTransactionsCollectionValidator::order_validator_.validate( + transactions); + ReasonsGroupType reason; + reason.first = "Transaction list"; + for (const auto &tx : transactions) { + auto answer = + UnsignedTransactionsCollectionValidator::transaction_validator_ + .validate(*tx); + if (answer.hasErrors()) { + auto message = + (boost::format("Tx %s : %s") % tx->hash().hex() % answer.reason()) + .str(); + reason.second.push_back(message); + } + } + + if (not reason.second.empty()) { + res.addReason(std::move(reason)); + } + return res; } + template class UnsignedTransactionsCollectionValidator< + TransactionValidator>, + AnyOrderValidator>; + + template class UnsignedTransactionsCollectionValidator< + TransactionValidator>, + BatchOrderValidator>; + } // namespace validation } // namespace shared_model diff --git a/shared_model/validators/transactions_collection/unsigned_transactions_collection_validator.hpp b/shared_model/validators/transactions_collection/unsigned_transactions_collection_validator.hpp index 5afdbbcf97..b25bc665b9 100644 --- a/shared_model/validators/transactions_collection/unsigned_transactions_collection_validator.hpp +++ b/shared_model/validators/transactions_collection/unsigned_transactions_collection_validator.hpp @@ -6,6 +6,7 @@ #ifndef IROHA_UNSIGNED_TRANSACTIONS_SEQUENCE_VALIDATOR_HPP #define IROHA_UNSIGNED_TRANSACTIONS_SEQUENCE_VALIDATOR_HPP +#include "validators/transactions_collection/any_order_validator.hpp" #include "validators/transactions_collection/transactions_collection_validator.hpp" namespace shared_model { @@ -15,11 +16,17 @@ namespace shared_model { * Unsigned transactions collection validator allows to some transaction * from the collection to be unsigned. Batch logic should be checked */ + template class UnsignedTransactionsCollectionValidator - : public TransactionsCollectionValidator { + : public TransactionsCollectionValidator { public: - Answer validate(const interface::types::TransactionsForwardCollectionType - &transactions) const override; + using TransactionsCollectionValidator< + TransactionValidator, + OrderValidator>::TransactionsCollectionValidator; + Answer validatePointers(const interface::types::SharedTxsCollectionType + &transactions) const override; }; } // namespace validation diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index fc482af82d..27c93e962b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -42,7 +42,7 @@ parts: - rxcpp - rapidjson - libpq - - pqxx + - soci - tbb - ed25519 cmake: @@ -88,7 +88,7 @@ parts: - -DgRPC_PROTOBUF_PROVIDER=package - -DgRPC_GFLAGS_PROVIDER=package - -DBUILD_SHARED_LIBS=ON - after: [cmake, protobuf, c-ares] + after: [cmake, protobuf, c-ares, gflags] spdlog: source: https://github.com/gabime/spdlog.git source-commit: f85a08622e20b74bff34381cafcb8ef8167b29d0 @@ -120,12 +120,21 @@ parts: make -C src/bin/pg_config install DESTDIR=$SNAPCRAFT_PART_INSTALL make -C src/interfaces/libpq install DESTDIR=$SNAPCRAFT_PART_INSTALL make -C src/include install DESTDIR=$SNAPCRAFT_PART_INSTALL - pqxx: - source: https://github.com/jtv/libpqxx.git - source-commit: 5b17abce5ac2b1a2f8278718405b7ade8bb30ae9 - plugin: autotools - configflags: [--disable-documentation, --with-pic] - after: [libpq] + soci: + source: https://github.com/SOCI/soci.git + source-commit: 111b50af8c3876ea392367640b4bd83b4f903ab8 + plugin: cmake + configflags: + - -DWITH_BOOST=ON + - -DWITH_DB2=OFF + - -DWITH_FIREBIRD=OFF + - -DWITH_MYSQL=OFF + - -DWITH_ODBC=OFF + - -DWITH_ORACLE=OFF + - -DWITH_POSTGRESQL=ON + - -DWITH_SQLITE3=OFF + - -DCMAKE_INSTALL_PREFIX=/ + after: [cmake, boost, libpq] tbb: source: https://github.com/01org/tbb.git source-commit: eb6336ad29450f2a64af5123ca1b9429ff6bc11d @@ -141,7 +150,7 @@ parts: mv $SNAPCRAFT_PART_INSTALL/include $SNAPCRAFT_PART_INSTALL/usr/local/include ed25519: source: https://github.com/hyperledger/iroha-ed25519.git - source-commit: e7188b8393dbe5ac54378610d53630bd4a180038 + source-commit: f42953c631fae93011612f6b1ee33f1f88c3f8af plugin: cmake configflags: [-DTESTING=OFF] after: [cmake] diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cd9ea35c24..15fb956b03 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -17,6 +17,8 @@ set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/test_bin) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + add_subdirectory(module) add_subdirectory(framework) add_subdirectory(integration) @@ -26,3 +28,7 @@ add_subdirectory(system) if(BENCHMARKING) add_subdirectory(benchmark) endif() + +if (FUZZING) + add_subdirectory(fuzzing) +endif() diff --git a/test/benchmark/CMakeLists.txt b/test/benchmark/CMakeLists.txt index 47a2ab44b9..607a74641c 100644 --- a/test/benchmark/CMakeLists.txt +++ b/test/benchmark/CMakeLists.txt @@ -11,3 +11,17 @@ target_link_libraries(benchmark_example benchmark shared_model_stateless_validation ) + +add_executable(bm_proto_creation + bm_proto_creation.cpp + ) + +target_include_directories(bm_proto_creation PUBLIC + ${PROJECT_SOURCE_DIR}/test + ) + +target_link_libraries(bm_proto_creation + benchmark + shared_model_proto_backend + ) + diff --git a/test/benchmark/bm_proto_creation.cpp b/test/benchmark/bm_proto_creation.cpp new file mode 100644 index 0000000000..73c44fc554 --- /dev/null +++ b/test/benchmark/bm_proto_creation.cpp @@ -0,0 +1,239 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * When passing through the pipeline, we need to form proposal + * out of transactions and then make blocks out of it. This results in several + * transformations of underlying transport implementation, + * which can be visibly slow. + * + * The purpose of this benchmark is to keep track of performance costs related + * to blocks and proposals copying/moving. + * + * Each benchmark runs transaction() and commands() call to + * initialize possibly lazy fields. + */ + +#include + +#include "backend/protobuf/block.hpp" +#include "datetime/time.hpp" +#include "module/shared_model/builders/protobuf/test_block_builder.hpp" +#include "module/shared_model/builders/protobuf/test_proposal_builder.hpp" +#include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" + +/// number of commands in a single transaction +constexpr int number_of_commands = 5; + +/// number of transactions in a single block +constexpr int number_of_txs = 100; + +class BlockBenchmark : public benchmark::Fixture { + public: + // Block cannot be copy-assigned, that's why the state is kept in a builder + TestBlockBuilder complete_builder; + + /** + * Initialize block builder for benchmarks + */ + void SetUp(benchmark::State &st) override { + TestBlockBuilder builder; + TestTransactionBuilder txbuilder; + + auto base_tx = txbuilder.createdTime(iroha::time::now()).quorum(1); + + for (int i = 0; i < number_of_commands; i++) { + base_tx.transferAsset("player@one", "player@two", "coin", "", "5.00"); + } + + std::vector txs; + + for (int i = 0; i < number_of_txs; i++) { + txs.push_back(base_tx.build()); + } + + complete_builder = + builder.createdTime(iroha::time::now()).height(1).transactions(txs); + } +}; + +class ProposalBenchmark : public benchmark::Fixture { + public: + // Block cannot be copy-assigned, that's why state is kept in a builder + TestProposalBuilder complete_builder; + + void SetUp(benchmark::State &st) override { + TestProposalBuilder builder; + TestTransactionBuilder txbuilder; + + auto base_tx = txbuilder.createdTime(iroha::time::now()).quorum(1); + + for (int i = 0; i < number_of_commands; i++) { + base_tx.transferAsset("player@one", "player@two", "coin", "", "5.00"); + } + + std::vector txs; + + for (int i = 0; i < number_of_txs; i++) { + txs.push_back(base_tx.build()); + } + + complete_builder = + builder.createdTime(iroha::time::now()).height(1).transactions(txs); + } +}; + +/** + * calls getters of a given object (block or proposal), + * so that lazy fields are initialized. + * @param obj - Block or Proposal + */ +template +void checkLoop(const T &obj) { + for (const auto &tx : obj.transactions()) { + benchmark::DoNotOptimize(tx.commands()); + } +} + +/** + * Runs a function and updates timer of the given state + */ +template +void runBenchmark(benchmark::State &st, Func &&f) { + auto start = std::chrono::high_resolution_clock::now(); + f(); + auto end = std::chrono::high_resolution_clock::now(); + + auto elapsed_seconds = + std::chrono::duration_cast>( + end - start); + + st.SetIterationTime(elapsed_seconds.count()); +} + +/** + * Benchmark block creation by copying protobuf object + */ +BENCHMARK_DEFINE_F(BlockBenchmark, TransportCopyTest)(benchmark::State &st) { + while (st.KeepRunning()) { + auto block = complete_builder.build(); + + runBenchmark(st, [&block] { + shared_model::proto::Block copy(block.getTransport()); + checkLoop(copy); + }); + } +} + +/** + * Benchmark block creation by moving protobuf object + */ +BENCHMARK_DEFINE_F(BlockBenchmark, TransportMoveTest)(benchmark::State &st) { + while (st.KeepRunning()) { + auto block = complete_builder.build(); + iroha::protocol::Block proto_block = block.getTransport(); + + runBenchmark(st, [&proto_block] { + shared_model::proto::Block copy(std::move(proto_block)); + checkLoop(copy); + }); + } +} + +/** + * Benchmark block creation by moving another block + */ +BENCHMARK_DEFINE_F(BlockBenchmark, MoveTest)(benchmark::State &st) { + while (st.KeepRunning()) { + auto block = complete_builder.build(); + + runBenchmark(st, [&block] { + shared_model::proto::Block copy = std::move(block); + checkLoop(copy); + }); + } +} + +/** + * Benchmark block creation by cloning another block + */ +BENCHMARK_DEFINE_F(BlockBenchmark, CloneTest)(benchmark::State &st) { + while (st.KeepRunning()) { + auto block = complete_builder.build(); + + runBenchmark(st, [&block] { + auto copy = clone(block); + checkLoop(*copy); + }); + } +} + +/** + * Benchmark proposal creation by copying protobuf object + */ +BENCHMARK_DEFINE_F(ProposalBenchmark, TransportCopyTest)(benchmark::State &st) { + while (st.KeepRunning()) { + auto proposal = complete_builder.build(); + + runBenchmark(st, [&proposal] { + shared_model::proto::Proposal copy(proposal.getTransport()); + checkLoop(copy); + }); + } +} + +/** + * Benchmark proposal creation by moving protobuf object + */ +BENCHMARK_DEFINE_F(ProposalBenchmark, TransportMoveTest)(benchmark::State &st) { + while (st.KeepRunning()) { + auto proposal = complete_builder.build(); + iroha::protocol::Proposal proto_proposal = proposal.getTransport(); + + runBenchmark(st, [&proto_proposal] { + shared_model::proto::Proposal copy(std::move(proto_proposal)); + checkLoop(copy); + }); + } +} + +/** + * Benchmark proposal creation by moving another proposal + */ +BENCHMARK_DEFINE_F(ProposalBenchmark, MoveTest)(benchmark::State &st) { + while (st.KeepRunning()) { + auto proposal = complete_builder.build(); + + runBenchmark(st, [&] { + shared_model::proto::Proposal copy(std::move(proposal.getTransport())); + checkLoop(copy); + }); + } +} + +/** + * Benchmark proposal creation by cloning another proposal + */ +BENCHMARK_DEFINE_F(ProposalBenchmark, CloneTest)(benchmark::State &st) { + while (st.KeepRunning()) { + auto proposal = complete_builder.build(); + + runBenchmark(st, [&proposal] { + auto copy = clone(proposal); + checkLoop(*copy); + }); + } +} + +BENCHMARK_REGISTER_F(BlockBenchmark, MoveTest)->UseManualTime(); +BENCHMARK_REGISTER_F(BlockBenchmark, CloneTest)->UseManualTime(); +BENCHMARK_REGISTER_F(BlockBenchmark, TransportMoveTest)->UseManualTime(); +BENCHMARK_REGISTER_F(BlockBenchmark, TransportCopyTest)->UseManualTime(); +BENCHMARK_REGISTER_F(ProposalBenchmark, MoveTest)->UseManualTime(); +BENCHMARK_REGISTER_F(ProposalBenchmark, CloneTest)->UseManualTime(); +BENCHMARK_REGISTER_F(ProposalBenchmark, TransportMoveTest)->UseManualTime(); +BENCHMARK_REGISTER_F(ProposalBenchmark, TransportCopyTest)->UseManualTime(); + +BENCHMARK_MAIN(); diff --git a/test/framework/batch_helper.hpp b/test/framework/batch_helper.hpp new file mode 100644 index 0000000000..ddcc6eeaf5 --- /dev/null +++ b/test/framework/batch_helper.hpp @@ -0,0 +1,295 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_BATCH_HELPER_HPP +#define IROHA_BATCH_HELPER_HPP + +#include + +#include "framework/result_fixture.hpp" +#include "interfaces/iroha_internal/transaction_batch.hpp" +#include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" +#include "validators/transactions_collection/batch_order_validator.hpp" + +namespace framework { + namespace batch { + + /** + * Creates transaction builder with set creator + * @tparam TransactionBuilderType type of tranasction builder + * @return prepared transaction builder + */ + template + auto prepareTransactionBuilder( + const std::string &creator, + const size_t &created_time = iroha::time::now(), + const shared_model::interface::types::QuorumType &quorum = 1) { + return TransactionBuilderType() + .setAccountQuorum(creator, 1) + .creatorAccountId(creator) + .createdTime(created_time) + .quorum(quorum); + } + + /** + * Creates unsigned transaction builder with set creator + * @return prepared transaction builder + */ + auto prepareUnsignedTransactionBuilder( + const std::string &creator, + const size_t &created_time = iroha::time::now(), + const shared_model::interface::types::QuorumType &quorum = 1) { + return prepareTransactionBuilder( + creator, created_time, quorum); + } + + /** + * Create unsigned batch with given fields of transactions: batch type and + * creator account. + * @param btype_creator_pairs vector of pairs. First element in every pair + * is batch type and second is creator account + * @param now created time for every transaction + * @return batch with the same size as size of range of pairs + */ + auto createUnsignedBatchTransactions( + std::vector> btype_creator_pairs, + size_t now = iroha::time::now()) { + std::vector reduced_hashes; + for (const auto &btype_creator : btype_creator_pairs) { + auto tx = prepareTransactionBuilder(btype_creator.second, now).build(); + reduced_hashes.push_back(tx.reducedHash()); + } + + shared_model::interface::types::SharedTxsCollectionType txs; + + std::transform( + btype_creator_pairs.begin(), + btype_creator_pairs.end(), + std::back_inserter(txs), + [&now, &reduced_hashes](const auto &btype_creator) + -> shared_model::interface::types::SharedTxsCollectionType:: + value_type { + return clone( + prepareTransactionBuilder(btype_creator.second, now) + .batchMeta(btype_creator.first, reduced_hashes) + .build()); + }); + return txs; + } + + /** + * Creates batch transactions, where every transaction has single signature + * @param btype_creator_pairs vector of pairs of batch type and creator + * account id + * @param now created time for every transaction + * @param quorum quorum for every transaction + * @return batch with the same size as size of range of pairs + */ + auto createBatchOneSignTransactions( + std::vector> btype_creator_pairs, + size_t now = iroha::time::now(), + const shared_model::interface::types::QuorumType &quorum = 1) { + std::vector reduced_hashes; + for (const auto &btype_creator : btype_creator_pairs) { + auto tx = + prepareUnsignedTransactionBuilder(btype_creator.second, now, quorum) + .build(); + reduced_hashes.push_back(tx.reducedHash()); + } + + shared_model::interface::types::SharedTxsCollectionType txs; + + std::transform( + btype_creator_pairs.begin(), + btype_creator_pairs.end(), + std::back_inserter(txs), + [&now, &reduced_hashes, &quorum](const auto &btype_creator) + -> shared_model::interface::types::SharedTxsCollectionType:: + value_type { + return clone( + prepareUnsignedTransactionBuilder( + btype_creator.second, now, quorum) + .batchMeta(btype_creator.first, reduced_hashes) + .build() + .signAndAddSignature( + shared_model::crypto:: + DefaultCryptoAlgorithmType:: + generateKeypair()) + .finish()); + }); + return txs; + } + + /** + * Creates atomic batch from provided creator accounts + * @param creators vector of creator account ids + * @return unsigned batch of the same size as the size of creator account + * ids + */ + auto createUnsignedBatchTransactions( + shared_model::interface::types::BatchType batch_type, + const std::vector &creators, + size_t now = iroha::time::now()) { + std::vector> fields; + std::transform(creators.begin(), + creators.end(), + std::back_inserter(fields), + [&batch_type](const auto creator) { + return std::make_pair(batch_type, creator); + }); + return createUnsignedBatchTransactions(fields, now); + } + + /** + * Creates transaction collection for the batch of given type and size + * @param batch_type type of the creted transactions + * @param batch_size size of the created collection of transactions + * @param now created time for every transactions + * @return unsigned batch + */ + auto createUnsignedBatchTransactions( + shared_model::interface::types::BatchType batch_type, + uint32_t batch_size, + size_t now = iroha::time::now()) { + auto range = boost::irange(0, (int)batch_size); + std::vector creators; + + std::transform(range.begin(), + range.end(), + std::back_inserter(creators), + [](const auto &id) { + return std::string("account") + std::to_string(id) + + "@domain"; + }); + + return createUnsignedBatchTransactions(batch_type, creators, now); + } + + auto createValidBatch(const size_t &size) { + using namespace shared_model::validation; + using TxValidator = + TransactionValidator>; + + using TxsValidator = + UnsignedTransactionsCollectionValidator; + + auto batch_type = shared_model::interface::types::BatchType::ATOMIC; + std::vector> + transaction_fields; + for (size_t i = 0; i < size; ++i) { + transaction_fields.push_back(std::make_pair( + batch_type, "account" + std::to_string(i) + "@domain")); + } + + auto txs = createBatchOneSignTransactions(transaction_fields); + auto result_batch = + shared_model::interface::TransactionBatch::createTransactionBatch( + txs, TxsValidator()); + + return framework::expected::val(result_batch).value().value; + } + + /** + * Namespace provides useful functions which are related to implementation + * but they are internal API + */ + namespace internal { + + /** + * Create list of hashes + * @tparam TxBuilderCollection - type of builder + * @param builders - initializer list which contains all builders + * @return vector with transactions hashes + */ + template + auto fetchReducedHashes( + const std::initializer_list &builders) { + std::vector hashes; + std::transform( + builders.begin(), + builders.end(), + std::back_inserter(hashes), + [](const auto &builder) { return builder.build().reducedHash(); }); + return hashes; + } + + /** + * Variadic to initializer list adapter + */ + template + auto fetchReducedHashes(const TxBuilders &... builders) { + return fetchReducedHashes({builders...}); + } + + /** + * Function create transactions from builder and passed hashes + * @tparam TxBuilder - type of one builder + * @param hashes - vector of hashes of transactions + * @param builders - initializer list of tx builders + * @return transactions with correct batch meta + */ + template + auto makeTxBatchCollection( + const std::vector &hashes, + std::initializer_list builders) { + shared_model::interface::types::SharedTxsCollectionType transactions; + + std::transform( + builders.begin(), + builders.end(), + std::back_inserter(transactions), + [&hashes](const auto &builder) { + return makePolyTxFromBuilder(builder.batchMeta( + shared_model::interface::types::BatchType::ATOMIC, hashes)); + }); + return transactions; + } + + /** + * Variadic to initializer list adapter + */ + template + auto makeTxBatchCollection(TxBuilders &&... builders) { + auto hashes = fetchReducedHashes(builders...); + return makeTxBatchCollection(std::move(hashes), + {std::forward(builders)...}); + } + } // namespace internal + + /** + * Create test batch from passed transaction builders + * @tparam TxBuilders - variadic types of tx builders + * @return shared_ptr for batch + */ + template + auto makeTestBatch(TxBuilders &&... builders) { + auto transactions = internal::makeTxBatchCollection( + std::forward(builders)...); + + using namespace shared_model::validation; + using TxValidator = + TransactionValidator>; + + using TxsValidator = + UnsignedTransactionsCollectionValidator; + + auto batch = + shared_model::interface::TransactionBatch::createTransactionBatch( + transactions, TxsValidator()); + + return std::make_shared( + framework::expected::val(batch).value().value); + } + + } // namespace batch +} // namespace framework + +#endif // IROHA_BATCH_HELPER_HPP diff --git a/test/framework/integration_framework/integration_test_framework.cpp b/test/framework/integration_framework/integration_test_framework.cpp index 8b2b1664d4..412102cb3b 100644 --- a/test/framework/integration_framework/integration_test_framework.cpp +++ b/test/framework/integration_framework/integration_test_framework.cpp @@ -19,6 +19,8 @@ #include +#include + #include "backend/protobuf/block.hpp" #include "backend/protobuf/queries/proto_query.hpp" #include "backend/protobuf/query_responses/proto_query_response.hpp" @@ -179,10 +181,30 @@ namespace integration_framework { std::function validation) { log_->info("send transaction"); + + // Required for StatusBus synchronization + boost::barrier bar1(2); + auto bar2 = std::make_shared(2); + iroha_instance_->instance_->getStatusBus() + ->statuses() + .filter([&](auto s) { return s->transactionHash() == tx.hash(); }) + .take(1) + .subscribe([&bar1, b2 = std::weak_ptr(bar2)](auto s) { + bar1.wait(); + if (auto lock = b2.lock()) { + lock->wait(); + } + }); + iroha_instance_->getIrohaInstance()->getCommandService()->Torii( tx.getTransport()); + // make sure that the first (stateless) status is come + bar1.wait(); // fetch status of transaction shared_model::proto::TransactionResponse status = getTxStatus(tx.hash()); + // make sure that the following statuses (stateful/commited) + // isn't reached the bus yet + bar2->wait(); // check validation function validation(status); @@ -195,6 +217,13 @@ namespace integration_framework { return *this; } + IntegrationTestFramework &IntegrationTestFramework::sendTxAwait( + const shared_model::proto::Transaction &tx, + std::function check) { + sendTx(tx).skipProposal().checkBlock(check); + return *this; + } + IntegrationTestFramework &IntegrationTestFramework::sendQuery( const shared_model::proto::Query &qry, std::function diff --git a/test/framework/integration_framework/integration_test_framework.hpp b/test/framework/integration_framework/integration_test_framework.hpp index 26d35a9cea..7bd8abac3e 100644 --- a/test/framework/integration_framework/integration_test_framework.hpp +++ b/test/framework/integration_framework/integration_test_framework.hpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_INTEGRATION_FRAMEWORK_HPP @@ -29,6 +17,7 @@ #include #include +#include "backend/protobuf/query_responses/proto_query_response.hpp" #include "framework/integration_framework/iroha_instance.hpp" #include "framework/integration_framework/test_irohad.hpp" #include "logger/logger.hpp" @@ -139,6 +128,17 @@ namespace integration_framework { IntegrationTestFramework &sendTx( const shared_model::proto::Transaction &tx); + /** + * Send transaction to Iroha with awaiting proposal + * and without status validation + * @param tx - transaction for sending + * @param check - callback for checking committed block + * @return this + */ + IntegrationTestFramework &sendTxAwait( + const shared_model::proto::Transaction &tx, + std::function check); + /** * Check current status of transaction * @param hash - hash of transaction to check diff --git a/test/framework/integration_framework/iroha_instance.cpp b/test/framework/integration_framework/iroha_instance.cpp index ec73b6a149..3a4d4dd03d 100644 --- a/test/framework/integration_framework/iroha_instance.cpp +++ b/test/framework/integration_framework/iroha_instance.cpp @@ -43,7 +43,7 @@ namespace integration_framework { is_mst_supported_(mst_support) {} void IrohaInstance::makeGenesis(const shared_model::interface::Block &block) { - instance_->storage->dropStorage(); + instance_->storage->reset(); rawInsertBlock(block); instance_->init(); } diff --git a/test/framework/integration_framework/test_irohad.hpp b/test/framework/integration_framework/test_irohad.hpp index 25c324d9d8..214a7a1735 100644 --- a/test/framework/integration_framework/test_irohad.hpp +++ b/test/framework/integration_framework/test_irohad.hpp @@ -64,6 +64,10 @@ namespace integration_framework { return crypto_signer_; } + auto getStatusBus() { + return status_bus_; + } + void run() override { internal_server = std::make_unique( "127.0.0.1:" + std::to_string(internal_port_)); diff --git a/test/fuzzing/CMakeLists.txt b/test/fuzzing/CMakeLists.txt new file mode 100644 index 0000000000..6eb746455b --- /dev/null +++ b/test/fuzzing/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=fuzzer,address") + +find_package(protobuf-mutator REQUIRED) + +add_executable(torii_fuzzing torii_fuzz.cpp) +target_link_libraries(torii_fuzzing + rxcpp + gtest::gtest + gmock::gmock + ametsuchi + libs_common + torii_service + protobuf-mutator + ) + +add_executable(status_fuzzing status_fuzz.cpp) +target_link_libraries(status_fuzzing + rxcpp + gtest::gtest + gmock::gmock + ametsuchi + libs_common + torii_service + protobuf-mutator + ) + +add_executable(find_fuzzing find_fuzz.cpp) +target_link_libraries(find_fuzzing + rxcpp + gtest::gtest + gmock::gmock + ametsuchi + libs_common + torii_service + protobuf-mutator + ) diff --git a/test/fuzzing/find_fuzz.cpp b/test/fuzzing/find_fuzz.cpp new file mode 100644 index 0000000000..3c2c71ce63 --- /dev/null +++ b/test/fuzzing/find_fuzz.cpp @@ -0,0 +1,54 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "libfuzzer/libfuzzer_macro.h" +#include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" +#include "module/irohad/execution/execution_mocks.hpp" +#include "module/irohad/network/network_mocks.hpp" +#include "torii/processor/query_processor_impl.hpp" +#include "torii/query_service.hpp" + +using namespace std::chrono_literals; +using testing::_; +using testing::Return; + +struct QueryFixture { + std::shared_ptr service_; + std::shared_ptr qry_processor_; + std::shared_ptr qry_exec_; + std::shared_ptr storage_; + std::shared_ptr bq_; + std::shared_ptr wq_; + + QueryFixture() { + storage_ = std::make_shared(); + bq_ = std::make_shared(); + wq_ = std::make_shared(); + EXPECT_CALL(*storage_, getBlockQuery()).WillRepeatedly(Return(bq_)); + EXPECT_CALL(*storage_, getWsvQuery()).WillRepeatedly(Return(wq_)); + qry_exec_ = std::make_shared(); + qry_processor_ = + std::make_shared(storage_, qry_exec_); + service_ = std::make_shared(qry_processor_); + } +}; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) { + static QueryFixture handler; + if (size < 1) { + return 0; + } + + EXPECT_CALL(*handler.qry_exec_, validateAndExecute(_)) + .WillRepeatedly(testing::Invoke([](auto &) { return nullptr; })); + iroha::protocol::Query qry; + if (protobuf_mutator::libfuzzer::LoadProtoInput(true, data, size, &qry)) { + iroha::protocol::QueryResponse resp; + handler.service_->Find(qry, resp); + } + return 0; +} diff --git a/test/fuzzing/status_fuzz.cpp b/test/fuzzing/status_fuzz.cpp new file mode 100644 index 0000000000..3feec01488 --- /dev/null +++ b/test/fuzzing/status_fuzz.cpp @@ -0,0 +1,74 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "libfuzzer/libfuzzer_macro.h" +#include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" +#include "module/irohad/multi_sig_transactions/mst_mocks.hpp" +#include "module/irohad/network/network_mocks.hpp" +#include "torii/command_service.hpp" +#include "torii/processor/transaction_processor_impl.hpp" + +using namespace std::chrono_literals; +using testing::_; +using testing::Return; + +struct CommandFixture { + std::shared_ptr service_; + std::shared_ptr tx_processor_; + std::shared_ptr storage_; + std::shared_ptr pcs_; + std::shared_ptr mst_processor_; + std::shared_ptr bq_; + + rxcpp::subjects::subject> + prop_notifier_; + rxcpp::subjects::subject< + std::shared_ptr> + vprop_notifier_; + rxcpp::subjects::subject commit_notifier_; + rxcpp::subjects::subject mst_notifier_; + + CommandFixture() { + pcs_ = std::make_shared(); + EXPECT_CALL(*pcs_, on_proposal()) + .WillRepeatedly(Return(prop_notifier_.get_observable())); + EXPECT_CALL(*pcs_, on_commit()) + .WillRepeatedly(Return(commit_notifier_.get_observable())); + EXPECT_CALL(*pcs_, on_verified_proposal()) + .WillRepeatedly(Return(vprop_notifier_.get_observable())); + + mst_processor_ = std::make_shared(); + EXPECT_CALL(*mst_processor_, onPreparedTransactionsImpl()) + .WillRepeatedly(Return(mst_notifier_.get_observable())); + EXPECT_CALL(*mst_processor_, onExpiredTransactionsImpl()) + .WillRepeatedly(Return(mst_notifier_.get_observable())); + + storage_ = std::make_shared(); + bq_ = std::make_shared(); + EXPECT_CALL(*storage_, getBlockQuery()).WillRepeatedly(Return(bq_)); + tx_processor_ = std::make_shared( + pcs_, mst_processor_); + service_ = std::make_shared( + tx_processor_, storage_, 15s, 15s); + } +}; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) { + static CommandFixture handler; + if (size < 1) { + return 0; + } + EXPECT_CALL(*handler.bq_, hasTxWithHash(_)) + .WillRepeatedly(Return(static_cast(data[0]))); + iroha::protocol::TxStatusRequest tx; + if (protobuf_mutator::libfuzzer::LoadProtoInput( + true, data + 1, size - 1, &tx)) { + iroha::protocol::ToriiResponse resp; + handler.service_->Status(tx, resp); + } + return 0; +} diff --git a/test/fuzzing/torii_fuzz.cpp b/test/fuzzing/torii_fuzz.cpp new file mode 100644 index 0000000000..e6e86b3539 --- /dev/null +++ b/test/fuzzing/torii_fuzz.cpp @@ -0,0 +1,57 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "libfuzzer/libfuzzer_macro.h" +#include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" +#include "module/irohad/multi_sig_transactions/mst_mocks.hpp" +#include "module/irohad/network/network_mocks.hpp" +#include "torii/command_service.hpp" +#include "torii/processor/transaction_processor_impl.hpp" + +using namespace std::chrono_literals; +using testing::Return; + +struct CommandFixture { + std::shared_ptr service_; + std::shared_ptr tx_processor_; + std::shared_ptr pcs_; + std::shared_ptr mst_processor_; + + rxcpp::subjects::subject> + prop_notifier_; + rxcpp::subjects::subject< + std::shared_ptr> + vprop_notifier_; + rxcpp::subjects::subject commit_notifier_; + rxcpp::subjects::subject mst_notifier_; + + CommandFixture() { + pcs_ = std::make_shared(); + EXPECT_CALL(*pcs_, on_proposal()) + .WillRepeatedly(Return(prop_notifier_.get_observable())); + EXPECT_CALL(*pcs_, on_commit()) + .WillRepeatedly(Return(commit_notifier_.get_observable())); + EXPECT_CALL(*pcs_, on_verified_proposal()) + .WillRepeatedly(Return(vprop_notifier_.get_observable())); + + mst_processor_ = std::make_shared(); + EXPECT_CALL(*mst_processor_, onPreparedTransactionsImpl()) + .WillRepeatedly(Return(mst_notifier_.get_observable())); + EXPECT_CALL(*mst_processor_, onExpiredTransactionsImpl()) + .WillRepeatedly(Return(mst_notifier_.get_observable())); + + tx_processor_ = std::make_shared( + pcs_, mst_processor_); + service_ = std::make_shared( + tx_processor_, nullptr, 15s, 15s); + } +}; + +DEFINE_BINARY_PROTO_FUZZER(const iroha::protocol::Transaction &tx) { + static CommandFixture handler; + handler.service_->Torii(tx); +} diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index a63e3c16a3..eb60795e34 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -16,6 +16,7 @@ # add_subdirectory(acceptance) +add_subdirectory(binary) add_subdirectory(consensus) add_subdirectory(pipeline) add_subdirectory(transport) diff --git a/test/integration/acceptance/CMakeLists.txt b/test/integration/acceptance/CMakeLists.txt index 91fc764b7e..73876617d6 100644 --- a/test/integration/acceptance/CMakeLists.txt +++ b/test/integration/acceptance/CMakeLists.txt @@ -16,7 +16,7 @@ # add_library(acceptance_fixture acceptance_fixture.cpp) target_link_libraries(acceptance_fixture - gtest + gtest::gtest integration_framework shared_model_proto_builders ) diff --git a/test/integration/acceptance/acceptance_fixture.cpp b/test/integration/acceptance/acceptance_fixture.cpp index c15505f390..dd165ffd29 100644 --- a/test/integration/acceptance/acceptance_fixture.cpp +++ b/test/integration/acceptance/acceptance_fixture.cpp @@ -29,7 +29,7 @@ AcceptanceFixture::AcceptanceFixture() status.get())); }), initial_time(iroha::time::now()), - tx_counter(0){} + nonce_counter(0) {} TestUnsignedTransactionBuilder AcceptanceFixture::createUser( const std::string &user, const shared_model::crypto::PublicKey &key) { @@ -94,7 +94,7 @@ auto AcceptanceFixture::baseTx() auto AcceptanceFixture::baseQry() -> decltype(base(TestUnsignedQueryBuilder())) { - return base(TestUnsignedQueryBuilder()); + return base(TestUnsignedQueryBuilder()).queryCounter(nonce_counter); } template @@ -119,5 +119,5 @@ template auto AcceptanceFixture::complete( .finish()); iroha::time::time_t AcceptanceFixture::getUniqueTime() { - return initial_time + tx_counter++; + return initial_time + nonce_counter++; } diff --git a/test/integration/acceptance/acceptance_fixture.hpp b/test/integration/acceptance/acceptance_fixture.hpp index 16698008d6..59e23860a3 100644 --- a/test/integration/acceptance/acceptance_fixture.hpp +++ b/test/integration/acceptance/acceptance_fixture.hpp @@ -122,7 +122,7 @@ class AcceptanceFixture : public ::testing::Test { private: iroha::time::time_t initial_time; /// number of created transactions, used to provide unique time - int tx_counter; + int nonce_counter; }; #endif // IROHA_ACCEPTANCE_FIXTURE_HPP diff --git a/test/integration/acceptance/add_asset_qty_test.cpp b/test/integration/acceptance/add_asset_qty_test.cpp index 066fcd8260..f1eace0fff 100644 --- a/test/integration/acceptance/add_asset_qty_test.cpp +++ b/test/integration/acceptance/add_asset_qty_test.cpp @@ -31,7 +31,7 @@ TEST_F(AddAssetQuantity, Basic) { .sendTx(makeUserWithPerms()) .skipProposal() .skipBlock() - .sendTx(complete(baseTx().addAssetQuantity(kUserId, kAsset, kAmount))) + .sendTx(complete(baseTx().addAssetQuantity(kAsset, kAmount))) .skipProposal() .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 1); }) @@ -49,7 +49,7 @@ TEST_F(AddAssetQuantity, NoPermissions) { .sendTx(makeUserWithPerms({interface::permissions::Role::kGetMyTxs})) .skipProposal() .skipBlock() - .sendTx(complete(baseTx().addAssetQuantity(kUserId, kAsset, kAmount))) + .sendTx(complete(baseTx().addAssetQuantity(kAsset, kAmount))) .skipProposal() .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) @@ -68,7 +68,7 @@ TEST_F(AddAssetQuantity, NegativeAmount) { .sendTx(makeUserWithPerms()) .skipProposal() .skipBlock() - .sendTx(complete(baseTx().addAssetQuantity(kUserId, kAsset, "-1.0")), + .sendTx(complete(baseTx().addAssetQuantity(kAsset, "-1.0")), checkStatelessInvalid); } @@ -84,7 +84,7 @@ TEST_F(AddAssetQuantity, ZeroAmount) { .sendTx(makeUserWithPerms()) .skipProposal() .skipBlock() - .sendTx(complete(baseTx().addAssetQuantity(kUserId, kAsset, "0.0")), + .sendTx(complete(baseTx().addAssetQuantity(kAsset, "0.0")), checkStatelessInvalid); } @@ -97,41 +97,20 @@ TEST_F(AddAssetQuantity, ZeroAmount) { */ TEST_F(AddAssetQuantity, Uint256DestOverflow) { std::string uint256_halfmax = - "723700557733226221397318656304299424082937404160253525246609900049457060" - "2495.0"; // 2**252 - 1 + "578960446186580977117854925043439539266349923328202820197287920039565648" + "19966.0"; // 2**255 - 2 IntegrationTestFramework(1) .setInitialState(kAdminKeypair) .sendTx(makeUserWithPerms()) .skipProposal() .skipBlock() // Add first half of the maximum - .sendTx( - complete(baseTx().addAssetQuantity(kUserId, kAsset, uint256_halfmax))) + .sendTx(complete(baseTx().addAssetQuantity(kAsset, uint256_halfmax))) .skipProposal() .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 1); }) // Add second half of the maximum - .sendTx( - complete(baseTx().addAssetQuantity(kUserId, kAsset, uint256_halfmax))) - .skipProposal() - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) - .done(); -} - -/** - * @given some user with all required permissions - * @when execute tx with AddAssetQuantity command with nonexistent account - * @then there is an empty proposal - */ -TEST_F(AddAssetQuantity, NonexistentAccount) { - std::string nonexistent = "inexist@test"; - IntegrationTestFramework(1) - .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms()) - .skipProposal() - .skipBlock() - .sendTx(complete(baseTx().addAssetQuantity(nonexistent, kAsset, kAmount))) + .sendTx(complete(baseTx().addAssetQuantity(kAsset, uint256_halfmax))) .skipProposal() .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) @@ -150,69 +129,9 @@ TEST_F(AddAssetQuantity, NonexistentAsset) { .sendTx(makeUserWithPerms()) .skipProposal() .skipBlock() - .sendTx( - complete(baseTx().addAssetQuantity(kUserId, nonexistent, kAmount))) - .skipProposal() - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) - .done(); -} - -/** - * @given some user with all required permission - * @when execute tx with AddAssetQuantity command to some other user - * @then there is no tx in proposal - */ -TEST_F(AddAssetQuantity, OtherUser) { - IntegrationTestFramework(1) - .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms()) - .skipProposal() - .skipBlock() - .sendTx(complete(baseTx().addAssetQuantity( - IntegrationTestFramework::kAdminId, kAsset, kAmount))) + .sendTx(complete(baseTx().addAssetQuantity(nonexistent, kAmount))) .skipProposal() .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) .done(); } - -/** - * @given pair of user in different domains with all required permission - * @when first one execute a tx with AddAssetQuantity command to the second one - * @then the tx hasn't passed stateless validation - * (aka skipProposal throws) - */ -TEST_F(AddAssetQuantity, OtherDomain) { - const auto kNewRole = "newrl"; - const auto kNewDomain = "newdom"; - const auto kNewUser = "newusr"; - IntegrationTestFramework(2) - .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms()) - // Generate new domain, new user and an asset - .sendTx( - TestUnsignedTransactionBuilder() - .creatorAccountId( - integration_framework::IntegrationTestFramework::kAdminId) - .createdTime(getUniqueTime()) - .createRole(kNewRole, - {interface::permissions::Role::kGetMyTxs}) - .createDomain(kNewDomain, kNewRole) - .createAccount( - kNewUser, - kNewDomain, - crypto::DefaultCryptoAlgorithmType::generateKeypair() - .publicKey()) - .createAsset(IntegrationTestFramework::kAssetName, kNewDomain, 1) - .quorum(1) - .build() - .signAndAddSignature(kAdminKeypair) - .finish()) - .skipProposal() - // Make sure everything is committed - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 2); }) - .sendTx(complete(baseTx().addAssetQuantity(kNewUser, kAsset, kAmount)), - checkStatelessInvalid); -} diff --git a/test/integration/acceptance/create_role_test.cpp b/test/integration/acceptance/create_role_test.cpp index f6c9fb0478..e9534cd925 100644 --- a/test/integration/acceptance/create_role_test.cpp +++ b/test/integration/acceptance/create_role_test.cpp @@ -98,7 +98,9 @@ TEST_F(CreateRole, EmptyPerms) { .sendTx(makeUserWithPerms()) .skipProposal() .skipBlock() - .sendTx(complete(baseTx({})), checkStatelessInvalid); + .sendTxAwait(complete(baseTx({})), [](auto &block) { + ASSERT_EQ(block->transactions().size(), 1); + }); } /** diff --git a/test/integration/acceptance/get_account_assets_test.cpp b/test/integration/acceptance/get_account_assets_test.cpp index ab65cb4c4b..c7f5265c3a 100644 --- a/test/integration/acceptance/get_account_assets_test.cpp +++ b/test/integration/acceptance/get_account_assets_test.cpp @@ -31,14 +31,13 @@ class GetAccountAssets : public AcceptanceFixture { /// Create command for adding assets auto addAssets() { - return complete( - AcceptanceFixture::baseTx().addAssetQuantity(kUserId, kAsset, "1")); + return complete(AcceptanceFixture::baseTx().addAssetQuantity(kAsset, "1")); } /// Create command for removing assets auto removeAssets() { - return complete(AcceptanceFixture::baseTx().subtractAssetQuantity( - kUserId, kAsset, "1")); + return complete( + AcceptanceFixture::baseTx().subtractAssetQuantity(kAsset, "1")); } /** @@ -50,14 +49,20 @@ class GetAccountAssets : public AcceptanceFixture { return complete(baseQry().queryCounter(1).getAccountAssets(kUserId)); } - const std::string kNewRole = "rl"; -}; + static auto checkAccountAssets(int quantity) { + return + [quantity](const shared_model::proto::QueryResponse &query_response) { + ASSERT_NO_THROW({ + const auto &resp = boost::apply_visitor( + framework::SpecifiedVisitor(), + query_response.get()); + + ASSERT_EQ(resp.accountAssets().size(), quantity); + }); + }; + } -/// Callback for checking that status is AccountAssetResponse -auto checkValid = [](auto &status) { - ASSERT_NO_THROW(boost::apply_visitor( - framework::SpecifiedVisitor(), - status.get())); + const std::string kNewRole = "rl"; }; /** @@ -66,6 +71,8 @@ auto checkValid = [](auto &status) { * @then there is an AccountAssetResponse */ TEST_F(GetAccountAssets, AddedAssets) { + auto check_single_asset = checkAccountAssets(1); + IntegrationTestFramework(1) .setInitialState(kAdminKeypair) .sendTx(makeUserWithPerms()) @@ -74,7 +81,7 @@ TEST_F(GetAccountAssets, AddedAssets) { .sendTx(addAssets()) .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 1); }) - .sendQuery(makeQuery(), checkValid) + .sendQuery(makeQuery(), check_single_asset) .done(); } @@ -84,6 +91,8 @@ TEST_F(GetAccountAssets, AddedAssets) { * @then there is an AccountAssetResponse */ TEST_F(GetAccountAssets, RemovedAssets) { + auto check_single_asset = checkAccountAssets(1); + IntegrationTestFramework(1) .setInitialState(kAdminKeypair) .sendTx(makeUserWithPerms()) @@ -95,7 +104,7 @@ TEST_F(GetAccountAssets, RemovedAssets) { .sendTx(removeAssets()) .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 1); }) - .sendQuery(makeQuery(), checkValid) + .sendQuery(makeQuery(), check_single_asset) .done(); } @@ -105,11 +114,13 @@ TEST_F(GetAccountAssets, RemovedAssets) { * @then there is an AccountAssetResponse */ TEST_F(GetAccountAssets, NonAddedAssets) { + auto check_zero_assets = checkAccountAssets(0); + IntegrationTestFramework(1) .setInitialState(kAdminKeypair) .sendTx(makeUserWithPerms()) .skipProposal() .skipBlock() - .sendQuery(makeQuery(), checkValid) + .sendQuery(makeQuery(), check_zero_assets) .done(); } diff --git a/test/integration/acceptance/grant_permission_test.cpp b/test/integration/acceptance/grant_permission_test.cpp index fdbb238c96..6901a78cf7 100644 --- a/test/integration/acceptance/grant_permission_test.cpp +++ b/test/integration/acceptance/grant_permission_test.cpp @@ -103,9 +103,9 @@ class GrantPermissionTest : public AcceptanceFixture { permitee_account_name + "@" + kDomain; const std::string account_id = account_name + "@" + kDomain; return (TxBuilder() - .creatorAccountId(permitee_account_id) - .createdTime(getUniqueTime()) - .*f)(account_id, permitee_key.publicKey()) + .creatorAccountId(permitee_account_id) + .createdTime(getUniqueTime()) + .*f)(account_id, permitee_key.publicKey()) .quorum(1) .build() .signAndAddSignature(permitee_key) @@ -185,7 +185,7 @@ class GrantPermissionTest : public AcceptanceFixture { return TxBuilder() .creatorAccountId(creator_account_id) .createdTime(getUniqueTime()) - .addAssetQuantity(creator_account_id, asset_id, amount) + .addAssetQuantity(asset_id, amount) .transferAsset( creator_account_id, receiver_account_id, asset_id, "", amount) .quorum(1) @@ -293,19 +293,19 @@ class GrantPermissionTest : public AcceptanceFixture { int quantity, bool is_contained) { return [&signatory, quantity, is_contained]( - const shared_model::proto::QueryResponse &query_response) { + const shared_model::proto::QueryResponse &query_response) { ASSERT_NO_THROW({ - const auto &resp = boost::apply_visitor( - framework::SpecifiedVisitor(), - query_response.get()); + const auto &resp = boost::apply_visitor( + framework::SpecifiedVisitor(), + query_response.get()); - ASSERT_EQ(resp.keys().size(), quantity); - auto &keys = resp.keys(); + ASSERT_EQ(resp.keys().size(), quantity); + auto &keys = resp.keys(); - ASSERT_EQ((std::find(keys.begin(), keys.end(), signatory.publicKey()) - != keys.end()), - is_contained); - }); + ASSERT_EQ((std::find(keys.begin(), keys.end(), signatory.publicKey()) + != keys.end()), + is_contained); + }); }; } @@ -317,14 +317,14 @@ class GrantPermissionTest : public AcceptanceFixture { */ static auto checkQuorum(int quorum_quantity) { return [quorum_quantity]( - const shared_model::proto::QueryResponse &query_response) { + const shared_model::proto::QueryResponse &query_response) { ASSERT_NO_THROW({ - const auto &resp = boost::apply_visitor( - framework::SpecifiedVisitor(), - query_response.get()); + const auto &resp = boost::apply_visitor( + framework::SpecifiedVisitor(), + query_response.get()); - ASSERT_EQ(resp.account().quorum(), quorum_quantity); - }); + ASSERT_EQ(resp.account().quorum(), quorum_quantity); + }); }; } @@ -337,14 +337,14 @@ class GrantPermissionTest : public AcceptanceFixture { static auto checkAccountDetail(const std::string &key, const std::string &detail) { return [&key, - &detail](const shared_model::proto::QueryResponse &query_response) { + &detail](const shared_model::proto::QueryResponse &query_response) { ASSERT_NO_THROW({ - const auto &resp = boost::apply_visitor( - framework::SpecifiedVisitor(), - query_response.get()); - ASSERT_TRUE(resp.detail().find(key) != std::string::npos); - ASSERT_TRUE(resp.detail().find(detail) != std::string::npos); - }); + const auto &resp = boost::apply_visitor( + framework::SpecifiedVisitor(), + query_response.get()); + ASSERT_TRUE(resp.detail().find(key) != std::string::npos); + ASSERT_TRUE(resp.detail().find(detail) != std::string::npos); + }); }; } @@ -424,7 +424,7 @@ TEST_F(GrantPermissionTest, GrantAddSignatoryPermission) { .skipProposal() .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 1); }) - // Add signatory + // Add signatory .sendTx( permiteeModifySignatory(&TestUnsignedTransactionBuilder::addSignatory, kAccount2, @@ -629,7 +629,7 @@ TEST_F(GrantPermissionTest, GrantWithoutGrantPermissions) { createTwoAccounts(itf, {Role::kReceive}, {Role::kReceive}); for (auto &perm : kAllGrantable) { itf.sendTx( - accountGrantToAccount(kAccount1, kAccount1Keypair, kAccount2, perm)) + accountGrantToAccount(kAccount1, kAccount1Keypair, kAccount2, perm)) .skipProposal() .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }); diff --git a/test/integration/acceptance/invalid_fields_test.cpp b/test/integration/acceptance/invalid_fields_test.cpp index 69f5ceb5b2..7c490e1bc6 100644 --- a/test/integration/acceptance/invalid_fields_test.cpp +++ b/test/integration/acceptance/invalid_fields_test.cpp @@ -4,9 +4,9 @@ */ #include -#include "block.pb.h" #include "framework/integration_framework/integration_test_framework.hpp" #include "integration/acceptance/acceptance_fixture.hpp" +#include "transaction.pb.h" using namespace integration_framework; using namespace shared_model; diff --git a/test/integration/acceptance/subtract_asset_qty_test.cpp b/test/integration/acceptance/subtract_asset_qty_test.cpp index 45ffbf44e3..70df2d3648 100644 --- a/test/integration/acceptance/subtract_asset_qty_test.cpp +++ b/test/integration/acceptance/subtract_asset_qty_test.cpp @@ -30,7 +30,7 @@ class SubtractAssetQuantity : public AcceptanceFixture { * @return built tx that adds kAmount assets to the users */ auto replenish() { - return complete(baseTx().addAssetQuantity(kUserId, kAsset, kAmount)); + return complete(baseTx().addAssetQuantity(kAsset, kAmount)); } const std::string kAmount = "1.0"; @@ -50,8 +50,7 @@ TEST_F(SubtractAssetQuantity, Everything) { .sendTx(replenish()) .skipProposal() .skipBlock() - .sendTx( - complete(baseTx().subtractAssetQuantity(kUserId, kAsset, kAmount))) + .sendTx(complete(baseTx().subtractAssetQuantity(kAsset, kAmount))) .skipProposal() .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 1); }) @@ -73,7 +72,7 @@ TEST_F(SubtractAssetQuantity, Overdraft) { .sendTx(replenish()) .skipProposal() .skipBlock() - .sendTx(complete(baseTx().subtractAssetQuantity(kUserId, kAsset, "2.0"))) + .sendTx(complete(baseTx().subtractAssetQuantity(kAsset, "2.0"))) .skipProposal() .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) @@ -94,8 +93,7 @@ TEST_F(SubtractAssetQuantity, NoPermissions) { .sendTx(replenish()) .skipProposal() .skipBlock() - .sendTx( - complete(baseTx().subtractAssetQuantity(kUserId, kAsset, kAmount))) + .sendTx(complete(baseTx().subtractAssetQuantity(kAsset, kAmount))) .skipProposal() .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) @@ -117,7 +115,7 @@ TEST_F(SubtractAssetQuantity, NegativeAmount) { .sendTx(replenish()) .skipProposal() .skipBlock() - .sendTx(complete(baseTx().subtractAssetQuantity(kUserId, kAsset, "-1.0")), + .sendTx(complete(baseTx().subtractAssetQuantity(kAsset, "-1.0")), checkStatelessInvalid); } @@ -136,33 +134,10 @@ TEST_F(SubtractAssetQuantity, ZeroAmount) { .sendTx(replenish()) .skipProposal() .skipBlock() - .sendTx(complete(baseTx().subtractAssetQuantity(kUserId, kAsset, "0.0")), + .sendTx(complete(baseTx().subtractAssetQuantity(kAsset, "0.0")), checkStatelessInvalid); } -/** - * @given some user with all required permissions - * @when execute tx with SubtractAssetQuantity command with nonexitent account - * @then there is an empty proposal - */ -TEST_F(SubtractAssetQuantity, NonexistentAccount) { - std::string nonexistent = "inexist@test"; - IntegrationTestFramework(1) - .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms()) - .skipProposal() - .skipBlock() - .sendTx(replenish()) - .skipProposal() - .skipBlock() - .sendTx(complete( - baseTx().subtractAssetQuantity(nonexistent, kAsset, kAmount))) - .skipProposal() - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) - .done(); -} - /** * @given some user with all required permissions * @when execute tx with SubtractAssetQuantity command with nonexistent asset @@ -178,30 +153,7 @@ TEST_F(SubtractAssetQuantity, NonexistentAsset) { .sendTx(replenish()) .skipProposal() .skipBlock() - .sendTx(complete( - baseTx().subtractAssetQuantity(kUserId, nonexistent, kAmount))) - .skipProposal() - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) - .done(); -} - -/** - * @given some user with all required permissions - * @when execute tx with SubtractAssetQuantity command to some other user - * @then there is no tx in proposal - */ -TEST_F(SubtractAssetQuantity, OtherUser) { - IntegrationTestFramework(1) - .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms()) - .skipProposal() - .skipBlock() - .sendTx(replenish()) - .skipProposal() - .skipBlock() - .sendTx(complete(baseTx().subtractAssetQuantity( - IntegrationTestFramework::kAdminId, kAsset, kAmount))) + .sendTx(complete(baseTx().subtractAssetQuantity(nonexistent, kAmount))) .skipProposal() .checkBlock( [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) diff --git a/test/integration/acceptance/transfer_asset_test.cpp b/test/integration/acceptance/transfer_asset_test.cpp index 196e97426d..38049d6ecb 100644 --- a/test/integration/acceptance/transfer_asset_test.cpp +++ b/test/integration/acceptance/transfer_asset_test.cpp @@ -19,159 +19,105 @@ using namespace shared_model; class TransferAsset : public AcceptanceFixture { public: /** - * Creates the transaction with the user creation commands + * Creates the transaction with the first user creation commands * @param perms are the permissions of the user - * @return built tx and a hash of its payload + * @return built tx */ - auto makeUserWithPerms(const std::string &user, - const crypto::Keypair &key, - const interface::RolePermissionSet &perms, - const std::string &role) { - return createUserWithPerms(user, key.publicKey(), role, perms) + auto makeFirstUser(const interface::RolePermissionSet &perms = { + interface::permissions::Role::kTransfer}) { + auto new_perms = perms; + new_perms.set(interface::permissions::Role::kAddAssetQty); + const std::string kRole1 = "roleone"; + return AcceptanceFixture::makeUserWithPerms(kRole1, new_perms); + } + + /** + * Creates the transaction with the second user creation commands + * @param perms are the permissions of the user + * @return built tx + */ + auto makeSecondUser(const interface::RolePermissionSet &perms = { + interface::permissions::Role::kReceive}) { + return createUserWithPerms(kUser2, kUser2Keypair.publicKey(), kRole2, perms) .build() .signAndAddSignature(kAdminKeypair) .finish(); } - proto::Transaction addAssets(const std::string &user, - const crypto::Keypair &key) { - return addAssets(user, key, kAmount); + proto::Transaction addAssets() { + return addAssets(kAmount); } - proto::Transaction addAssets(const std::string &user, - const crypto::Keypair &key, - const std::string &amount) { - const std::string kUserId = user + "@test"; - return proto::TransactionBuilder() - .creatorAccountId(kUserId) - .createdTime(getUniqueTime()) - .addAssetQuantity(kUserId, kAsset, amount) - .quorum(1) - .build() - .signAndAddSignature(key) - .finish(); + proto::Transaction addAssets(const std::string &amount) { + return complete(baseTx().addAssetQuantity(kAsset, amount)); } - /** - * Create valid base pre-build transaction - * @return pre-build tx - */ - auto baseTx() { - return TestUnsignedTransactionBuilder() - .creatorAccountId(kUser1 + "@test") - .createdTime(getUniqueTime()) - .quorum(1); + proto::Transaction makeTransfer(const std::string &amount) { + return complete( + baseTx().transferAsset(kUserId, kUser2Id, kAsset, kDesc, amount)); } - /** - * Completes pre-build transaction - * @param builder is a pre-built tx - * @return built tx - */ - template - auto completeTx(TestTransactionBuilder builder) { - return builder.build().signAndAddSignature(kUser1Keypair).finish(); + proto::Transaction makeTransfer() { + return makeTransfer(kAmount); } const std::string kAmount = "1.0"; const std::string kDesc = "description"; - const std::string kUser1 = "userone"; - const std::string kUser2 = "usertwo"; - const std::string kRole1 = "roleone"; const std::string kRole2 = "roletwo"; - const std::string kUser1Id = kUser1 + "@test"; + const std::string kUser2 = "usertwo"; const std::string kUser2Id = kUser2 + "@test"; - const crypto::Keypair kUser1Keypair = - crypto::DefaultCryptoAlgorithmType::generateKeypair(); const crypto::Keypair kUser2Keypair = crypto::DefaultCryptoAlgorithmType::generateKeypair(); - const interface::RolePermissionSet kPerms{ - interface::permissions::Role::kAddAssetQty, - interface::permissions::Role::kTransfer, - interface::permissions::Role::kReceive}; }; +#define check(i) [](auto &block) { ASSERT_EQ(block->transactions().size(), i); } + /** - * @given some user with all required permissions + * @given pair of users with all required permissions * @when execute tx with TransferAsset command * @then there is the tx in proposal */ TEST_F(TransferAsset, Basic) { IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, kUser1Keypair, kPerms, kRole1)) - .skipProposal() - .skipBlock() - .sendTx(makeUserWithPerms(kUser2, kUser2Keypair, kPerms, kRole2)) - .skipProposal() - .skipBlock() - .sendTx(addAssets(kUser1, kUser1Keypair)) - .skipProposal() - .skipBlock() - .sendTx(completeTx( - baseTx().transferAsset(kUser1Id, kUser2Id, kAsset, kDesc, kAmount))) - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 1); }) + .sendTxAwait(makeFirstUser(), check(1)) + .sendTxAwait(makeSecondUser(), check(1)) + .sendTxAwait(addAssets(), check(1)) + .sendTxAwait(makeTransfer(), check(1)) .done(); } /** - * @given some user with only can_transfer permission + * @given pair of users + * AND the first user without can_transfer permission * @when execute tx with TransferAsset command * @then there is an empty proposal */ -TEST_F(TransferAsset, WithOnlyCanTransferPerm) { +TEST_F(TransferAsset, WithoutCanTransfer) { IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, - kUser1Keypair, - {interface::permissions::Role::kTransfer}, - kRole1)) - .skipProposal() - .skipBlock() - .sendTx(makeUserWithPerms(kUser2, kUser2Keypair, kPerms, kRole2)) - .skipProposal() - .skipBlock() - .sendTx(addAssets(kUser1, kUser1Keypair)) - .skipProposal() - .skipBlock() - .sendTx(baseTx() - .transferAsset(kUser1Id, kUser2Id, kAsset, kDesc, kAmount) - .build() - .signAndAddSignature(kUser1Keypair) - .finish()) - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) + .sendTxAwait(makeFirstUser({}), check(1)) + .sendTxAwait(makeSecondUser(), check(1)) + .sendTxAwait(addAssets(), check(1)) + .sendTxAwait(makeTransfer(), check(0)) .done(); } /** - * @given some user with only can_receive permission + * @given pair of users + * AND the second user without can_receive permission * @when execute tx with TransferAsset command * @then there is an empty proposal */ -TEST_F(TransferAsset, WithOnlyCanReceivePerm) { +TEST_F(TransferAsset, WithoutCanReceive) { IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, - kUser1Keypair, - {interface::permissions::Role::kReceive}, - kRole1)) - .skipProposal() - .skipBlock() - .sendTx(makeUserWithPerms(kUser2, kUser2Keypair, kPerms, kRole2)) - .skipProposal() - .skipBlock() - .sendTx(addAssets(kUser1, kUser1Keypair)) - .skipProposal() - .skipBlock() - .sendTx(baseTx() - .transferAsset(kUser1Id, kUser2Id, kAsset, kDesc, kAmount) - .build() - .signAndAddSignature(kUser1Keypair) - .finish()) - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) + .sendTxAwait(makeFirstUser(), check(1)) + // TODO(@l4l) 23/06/18: remove permission with IR-1367 + .sendTxAwait(makeSecondUser({interface::permissions::Role::kAddPeer}), + check(1)) + .sendTxAwait(addAssets(), check(1)) + .sendTxAwait(makeTransfer(), check(0)) .done(); } @@ -184,19 +130,11 @@ TEST_F(TransferAsset, NonexistentDest) { std::string nonexistent = "inexist@test"; IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, kUser1Keypair, kPerms, kRole1)) - .skipProposal() - .skipBlock() - .sendTx(addAssets(kUser1, kUser1Keypair)) - .skipProposal() - .skipBlock() - .sendTx(baseTx() - .transferAsset(kUser1Id, nonexistent, kAsset, kDesc, kAmount) - .build() - .signAndAddSignature(kUser1Keypair) - .finish()) - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) + .sendTxAwait(makeFirstUser(), check(1)) + .sendTxAwait(addAssets(), check(1)) + .sendTxAwait(complete(baseTx().transferAsset( + kUserId, nonexistent, kAsset, kDesc, kAmount)), + check(0)) .done(); } @@ -209,23 +147,12 @@ TEST_F(TransferAsset, NonexistentAsset) { std::string nonexistent = "inexist#test"; IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, kUser1Keypair, kPerms, kRole1)) - .skipProposal() - .skipBlock() - .sendTx(makeUserWithPerms(kUser2, kUser2Keypair, kPerms, kRole2)) - .skipProposal() - .skipBlock() - .sendTx(addAssets(kUser1, kUser1Keypair)) - .skipProposal() - .skipBlock() - .sendTx( - baseTx() - .transferAsset(kUser1Id, kUser2Id, nonexistent, kDesc, kAmount) - .build() - .signAndAddSignature(kUser1Keypair) - .finish()) - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) + .sendTxAwait(makeFirstUser(), check(1)) + .sendTxAwait(makeSecondUser(), check(1)) + .sendTxAwait(addAssets(), check(1)) + .sendTxAwait(complete(baseTx().transferAsset( + kUserId, kUser2Id, nonexistent, kDesc, kAmount)), + check(0)) .done(); } @@ -238,21 +165,11 @@ TEST_F(TransferAsset, NonexistentAsset) { TEST_F(TransferAsset, NegativeAmount) { IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, kUser1Keypair, kPerms, kRole1)) - .skipProposal() - .skipBlock() - .sendTx(makeUserWithPerms(kUser2, kUser2Keypair, kPerms, kRole2)) - .skipProposal() - .skipBlock() - .sendTx(addAssets(kUser1, kUser1Keypair)) - .skipProposal() - .skipBlock() - .sendTx(baseTx() - .transferAsset(kUser1Id, kUser2Id, kAsset, kDesc, "-1.0") - .build() - .signAndAddSignature(kUser1Keypair) - .finish(), - checkStatelessInvalid); + .sendTxAwait(makeFirstUser(), check(1)) + .sendTxAwait(makeSecondUser(), check(1)) + .sendTxAwait(addAssets(), check(1)) + .sendTx(makeTransfer("-1.0"), checkStatelessInvalid) + .done(); } /** @@ -262,19 +179,13 @@ TEST_F(TransferAsset, NegativeAmount) { * (aka skipProposal throws) */ TEST_F(TransferAsset, ZeroAmount) { - IntegrationTestFramework(3) + IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, kUser1Keypair, kPerms, kRole1)) - .sendTx(makeUserWithPerms(kUser2, kUser2Keypair, kPerms, kRole2)) - .sendTx(addAssets(kUser1, kUser1Keypair)) - .skipProposal() - .skipBlock() - .sendTx(baseTx() - .transferAsset(kUser1Id, kUser2Id, kAsset, kDesc, "0.0") - .build() - .signAndAddSignature(kUser1Keypair) - .finish(), - checkStatelessInvalid); + .sendTxAwait(makeFirstUser(), check(1)) + .sendTxAwait(makeSecondUser(), check(1)) + .sendTxAwait(addAssets(), check(1)) + .sendTx(makeTransfer("0.0"), checkStatelessInvalid) + .done(); } /** @@ -283,15 +194,14 @@ TEST_F(TransferAsset, ZeroAmount) { * @then it passed to the proposal */ TEST_F(TransferAsset, EmptyDesc) { - IntegrationTestFramework(4) + IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, kUser1Keypair, kPerms, kRole1)) - .sendTx(makeUserWithPerms(kUser2, kUser2Keypair, kPerms, kRole2)) - .sendTx(addAssets(kUser1, kUser1Keypair)) - .sendTx(completeTx( - baseTx().transferAsset(kUser1Id, kUser2Id, kAsset, "", kAmount))) - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 4); }) + .sendTxAwait(makeFirstUser(), check(1)) + .sendTxAwait(makeSecondUser(), check(1)) + .sendTxAwait(addAssets(), check(1)) + .sendTxAwait(complete(baseTx().transferAsset( + kUserId, kUser2Id, kAsset, "", kAmount)), + check(1)) .done(); } @@ -302,17 +212,15 @@ TEST_F(TransferAsset, EmptyDesc) { * (aka skipProposal throws) */ TEST_F(TransferAsset, LongDesc) { - std::string long_desc(100000, 'a'); - auto invalid_tx = completeTx( - baseTx().transferAsset(kUser1Id, kUser2Id, kAsset, long_desc, kAmount)); - IntegrationTestFramework(3) + IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, kUser1Keypair, kPerms, kRole1)) - .sendTx(makeUserWithPerms(kUser2, kUser2Keypair, kPerms, kRole2)) - .sendTx(addAssets(kUser1, kUser1Keypair)) - .skipProposal() - .skipBlock() - .sendTx(invalid_tx, checkStatelessInvalid) + .sendTxAwait(makeFirstUser(), check(1)) + .sendTxAwait(makeSecondUser(), check(1)) + .sendTxAwait(addAssets(), check(1)) + .sendTx( + complete(baseTx().transferAsset( + kUserId, kUser2Id, kAsset, std::string(100000, 'a'), kAmount)), + checkStatelessInvalid) .done(); } @@ -324,20 +232,10 @@ TEST_F(TransferAsset, LongDesc) { TEST_F(TransferAsset, MoreThanHas) { IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, kUser1Keypair, kPerms, kRole1)) - .skipProposal() - .skipBlock() - .sendTx(makeUserWithPerms(kUser2, kUser2Keypair, kPerms, kRole2)) - .skipProposal() - .skipBlock() - .sendTx(addAssets(kUser1, kUser1Keypair, "50.0")) - .skipProposal() - .skipBlock() - .sendTx(completeTx( - baseTx().transferAsset(kUser1Id, kUser2Id, kAsset, kDesc, "100.0"))) - .skipProposal() - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) + .sendTxAwait(makeFirstUser(), check(1)) + .sendTxAwait(makeSecondUser(), check(1)) + .sendTxAwait(addAssets("50.0"), check(1)) + .sendTxAwait(makeTransfer("100.0"), check(0)) .done(); } @@ -351,36 +249,19 @@ TEST_F(TransferAsset, MoreThanHas) { */ TEST_F(TransferAsset, Uint256DestOverflow) { std::string uint256_halfmax = - "723700557733226221397318656304299424082937404160253525246609900049457060" - "2495.0"; // 2**252 - 1 + "578960446186580977117854925043439539266349923328202820197287920039565648" + "19966.0"; // 2**255 - 2 IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, kUser1Keypair, kPerms, kRole1)) - .skipProposal() - .skipBlock() - .sendTx(makeUserWithPerms(kUser2, kUser2Keypair, kPerms, kRole2)) - .skipProposal() - .skipBlock() - .sendTx(addAssets(kUser1, kUser1Keypair, uint256_halfmax)) - .skipProposal() - .skipBlock() + .sendTxAwait(makeFirstUser(), check(1)) + .sendTxAwait(makeSecondUser(), check(1)) + .sendTxAwait(addAssets(uint256_halfmax), check(1)) // Send first half of the maximum - .sendTx(completeTx(baseTx().transferAsset( - kUser1Id, kUser2Id, kAsset, kDesc, uint256_halfmax))) - .skipProposal() - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 1); }) + .sendTxAwait(makeTransfer(uint256_halfmax), check(1)) // Restore self balance - .sendTx(addAssets(kUser1, kUser1Keypair, uint256_halfmax)) - .skipProposal() - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 1); }) + .sendTxAwait(addAssets(uint256_halfmax), check(1)) // Send second half of the maximum - .sendTx(completeTx(baseTx().transferAsset( - kUser1Id, kUser2Id, kAsset, kDesc, uint256_halfmax))) - .skipProposal() - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 0); }) + .sendTxAwait(makeTransfer(uint256_halfmax), check(0)) .done(); } @@ -392,14 +273,12 @@ TEST_F(TransferAsset, Uint256DestOverflow) { * (aka skipProposal throws) */ TEST_F(TransferAsset, SourceIsDest) { - IntegrationTestFramework(2) + IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, kUser1Keypair, kPerms, kRole1)) - .sendTx(addAssets(kUser1, kUser1Keypair)) - .skipProposal() - .skipBlock() - .sendTx(completeTx(baseTx().transferAsset( - kUser1Id, kUser1Id, kAsset, kDesc, kAmount)), + .sendTxAwait(makeFirstUser(), check(1)) + .sendTxAwait(addAssets(), check(1)) + .sendTx(complete(baseTx().transferAsset( + kUserId, kUserId, kAsset, kDesc, kAmount)), checkStatelessInvalid); } @@ -410,40 +289,31 @@ TEST_F(TransferAsset, SourceIsDest) { * @then the tx is commited */ TEST_F(TransferAsset, InterDomain) { - const auto kNewRole = "newrl"; - const auto kNewDomain = "newdom"; - const auto kUser2Id = kUser2 + "@" + kNewDomain; + const std::string kNewDomain = "newdom"; + const std::string kUser2Id = kUser2 + "@" + kNewDomain; + const std::string kNewAssetId = + IntegrationTestFramework::kAssetName + "#" + kNewDomain; + + auto make_second_user = + baseTx() + .creatorAccountId(IntegrationTestFramework::kAdminId) + .createRole(kRole2, {interface::permissions::Role::kReceive}) + .createDomain(kNewDomain, kRole2) + .createAccount(kUser2, kNewDomain, kUser2Keypair.publicKey()) + .createAsset(IntegrationTestFramework::kAssetName, kNewDomain, 1) + .build() + .signAndAddSignature(kAdminKeypair) + .finish(); + auto add_assets = complete(baseTx().addAssetQuantity(kNewAssetId, kAmount)); + auto make_transfer = complete( + baseTx().transferAsset(kUserId, kUser2Id, kNewAssetId, kDesc, kAmount)); + IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, kUser1Keypair, kPerms, kRole1)) - .skipProposal() - .skipBlock() - .sendTx( - shared_model::proto::TransactionBuilder() - .creatorAccountId( - integration_framework::IntegrationTestFramework::kAdminId) - .createdTime(getUniqueTime()) - .createRole(kNewRole, {interface::permissions::Role::kReceive}) - .createDomain(kNewDomain, kNewRole) - .createAccount( - kUser2, - kNewDomain, - crypto::DefaultCryptoAlgorithmType::generateKeypair() - .publicKey()) - .createAsset(IntegrationTestFramework::kAssetName, kNewDomain, 1) - .quorum(1) - .build() - .signAndAddSignature(kAdminKeypair) - .finish()) - .skipProposal() - .skipBlock() - .sendTx(addAssets(kUser1, kUser1Keypair, kAmount)) - .skipProposal() - .skipBlock() - .sendTx(completeTx( - baseTx().transferAsset(kUser1Id, kUser2Id, kAsset, kDesc, kAmount))) - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 1); }) + .sendTxAwait(makeFirstUser(), check(1)) + .sendTxAwait(make_second_user, check(1)) + .sendTxAwait(add_assets, check(1)) + .sendTxAwait(make_transfer, check(1)) .done(); } @@ -457,14 +327,27 @@ TEST_F(TransferAsset, BigPrecision) { const std::string kNewAsset = IntegrationTestFramework::kAssetName + "a"; const std::string kNewAssetId = kNewAsset + "#" + IntegrationTestFramework::kDefaultDomain; - const auto precision = 5; + const auto kPrecision = 5; const std::string kInitial = "500"; const std::string kForTransfer = "1"; const std::string kLeft = "499"; + auto create_asset = + baseTx() + .creatorAccountId( + integration_framework::IntegrationTestFramework::kAdminId) + .createAsset( + kNewAsset, IntegrationTestFramework::kDefaultDomain, kPrecision) + .build() + .signAndAddSignature(kAdminKeypair) + .finish(); + auto add_assets = complete(baseTx().addAssetQuantity(kNewAssetId, kInitial)); + auto make_transfer = complete(baseTx().transferAsset( + kUserId, kUser2Id, kNewAssetId, kDesc, kForTransfer)); + auto check_balance = [](std::string account_id, std::string val) { return [a = std::move(account_id), - v = val + "." + std::string(precision, '0')](auto &resp) { + v = val](auto &resp) { auto &acc_ast = boost::apply_visitor( framework::SpecifiedVisitor(), resp.get()); @@ -475,12 +358,11 @@ TEST_F(TransferAsset, BigPrecision) { } }; }; + auto make_query = [this](std::string account_id) { - return proto::QueryBuilder() + return baseQry() .creatorAccountId(IntegrationTestFramework::kAdminId) - .createdTime(getUniqueTime()) .getAccountAssets(account_id) - .queryCounter(1) .build() .signAndAddSignature(kAdminKeypair) .finish(); @@ -488,50 +370,12 @@ TEST_F(TransferAsset, BigPrecision) { IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(makeUserWithPerms(kUser1, kUser1Keypair, kPerms, kRole1)) - .skipProposal() - .skipBlock() - .sendTx(makeUserWithPerms(kUser2, kUser2Keypair, kPerms, kRole2)) - .skipProposal() - .skipBlock() - .sendTx(proto::TransactionBuilder() - .creatorAccountId( - integration_framework::IntegrationTestFramework::kAdminId) - .createdTime(getUniqueTime()) - .quorum(1) - .createAsset(kNewAsset, - IntegrationTestFramework::kDefaultDomain, - precision) - .build() - .signAndAddSignature(kAdminKeypair) - .finish()) - .skipProposal() - .checkBlock([](auto &block) { - ASSERT_EQ(block->transactions().size(), 1) << "Cannot create asset"; - }) - .sendTx(proto::TransactionBuilder() - .creatorAccountId(kUser1Id) - .createdTime(getUniqueTime()) - .quorum(1) - .addAssetQuantity(kUser1Id, kNewAssetId, kInitial) - .build() - .signAndAddSignature(kUser1Keypair) - .finish()) - .checkBlock([](auto &block) { - ASSERT_EQ(block->transactions().size(), 1) << "Cannot add assets"; - }) - .sendTx(proto::TransactionBuilder() - .creatorAccountId(kUser1Id) - .createdTime(getUniqueTime()) - .quorum(1) - .transferAsset( - kUser1Id, kUser2Id, kNewAssetId, kDesc, kForTransfer) - .build() - .signAndAddSignature(kUser1Keypair) - .finish()) - .checkBlock( - [](auto &block) { ASSERT_EQ(block->transactions().size(), 1); }) - .sendQuery(make_query(kUser1Id), check_balance(kUser1Id, kLeft)) + .sendTxAwait(makeFirstUser(), check(1)) + .sendTxAwait(makeSecondUser(), check(1)) + .sendTxAwait(create_asset, check(1)) + .sendTxAwait(add_assets, check(1)) + .sendTxAwait(make_transfer, check(1)) + .sendQuery(make_query(kUserId), check_balance(kUserId, kLeft)) .sendQuery(make_query(kUser2Id), check_balance(kUser2Id, kForTransfer)) .done(); } diff --git a/test/integration/acceptance/tx_acceptance_test.cpp b/test/integration/acceptance/tx_acceptance_test.cpp index 43150d7c39..32f7b179c7 100644 --- a/test/integration/acceptance/tx_acceptance_test.cpp +++ b/test/integration/acceptance/tx_acceptance_test.cpp @@ -10,7 +10,6 @@ class AcceptanceTest : public AcceptanceFixture { public: const std::string kAdmin = "admin@test"; - const std::string kNonUser = "nonuser@test"; const std::function checkStatelessValid = [](auto &status) { @@ -31,6 +30,20 @@ class AcceptanceTest : public AcceptanceFixture { const std::shared_ptr &)> checkStatefulValid = [](auto &block) { ASSERT_EQ(block->transactions().size(), 1); }; + + template + auto baseTx() { + return Builder() + .createdTime(getUniqueTime()) + .creatorAccountId(kAdmin) + .addAssetQuantity(kAsset, "1.0") + .quorum(1); + } + + template + auto complete(T t) { + return t.build().signAndAddSignature(kAdminKeypair).finish(); + } }; /** @@ -40,18 +53,11 @@ class AcceptanceTest : public AcceptanceFixture { * AND STATEFUL_VALIDATION_FAILED on that tx */ TEST_F(AcceptanceTest, NonExistentCreatorAccountId) { - auto tx = TestUnsignedTransactionBuilder() - .createdTime(iroha::time::now()) - .creatorAccountId(kNonUser) - .addAssetQuantity(kAdmin, kAsset, "1.0") - .quorum(1) - .build() - .signAndAddSignature(kAdminKeypair) - .finish(); - + const std::string kNonUser = "nonuser@test"; integration_framework::IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(tx, checkStatelessValid) + .sendTx(complete(baseTx<>().creatorAccountId(kNonUser)), + checkStatelessValid) .checkProposal(checkProposal) .checkBlock(checkStatefulInvalid) .done(); @@ -64,17 +70,11 @@ TEST_F(AcceptanceTest, NonExistentCreatorAccountId) { * AND STATEFUL_VALIDATION_SUCCESS on that tx */ TEST_F(AcceptanceTest, Transaction1HourOld) { - auto tx = TestUnsignedTransactionBuilder() - .createdTime(iroha::time::now(std::chrono::hours(-1))) - .creatorAccountId(kAdmin) - .addAssetQuantity(kAdmin, kAsset, "1.0") - .quorum(1) - .build() - .signAndAddSignature(kAdminKeypair) - .finish(); integration_framework::IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(tx, checkStatelessValid) + .sendTx(complete(baseTx<>().createdTime( + iroha::time::now(std::chrono::hours(-1)))), + checkStatelessValid) .skipProposal() .checkBlock(checkStatefulValid) .done(); @@ -87,18 +87,11 @@ TEST_F(AcceptanceTest, Transaction1HourOld) { * AND STATEFUL_VALIDATION_SUCCESS on that tx */ TEST_F(AcceptanceTest, DISABLED_TransactionLess24HourOld) { - auto tx = TestUnsignedTransactionBuilder() - .createdTime(iroha::time::now(std::chrono::hours(24) - - std::chrono::minutes(1))) - .creatorAccountId(kAdmin) - .addAssetQuantity(kAdmin, kAsset, "1.0") - .quorum(1) - .build() - .signAndAddSignature(kAdminKeypair) - .finish(); integration_framework::IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(tx, checkStatelessValid) + .sendTx(complete(baseTx<>().createdTime(iroha::time::now( + std::chrono::hours(24) - std::chrono::minutes(1)))), + checkStatelessValid) .skipProposal() .checkBlock(checkStatefulValid) .done(); @@ -110,18 +103,11 @@ TEST_F(AcceptanceTest, DISABLED_TransactionLess24HourOld) { * @then receive STATELESS_VALIDATION_FAILED status */ TEST_F(AcceptanceTest, TransactionMore24HourOld) { - auto tx = TestUnsignedTransactionBuilder() - .createdTime(iroha::time::now(std::chrono::hours(24) - + std::chrono::minutes(1))) - .creatorAccountId(kAdmin) - .addAssetQuantity(kAdmin, kAsset, "1.0") - .quorum(1) - .build() - .signAndAddSignature(kAdminKeypair) - .finish(); integration_framework::IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(tx, checkStatelessInvalid) + .sendTx(complete(baseTx<>().createdTime(iroha::time::now( + std::chrono::hours(24) + std::chrono::minutes(1)))), + checkStatelessInvalid) .done(); } @@ -132,19 +118,11 @@ TEST_F(AcceptanceTest, TransactionMore24HourOld) { * AND STATEFUL_VALIDATION_SUCCESS on that tx */ TEST_F(AcceptanceTest, Transaction5MinutesFromFuture) { - auto tx = TestUnsignedTransactionBuilder() - .createdTime(iroha::time::now(std::chrono::minutes(5) - - std::chrono::seconds(10))) - .creatorAccountId(kAdmin) - .addAssetQuantity(kAdmin, kAsset, "1.0") - .quorum(1) - .build() - .signAndAddSignature(kAdminKeypair) - .finish(); - integration_framework::IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(tx, checkStatelessValid) + .sendTx(complete(baseTx<>().createdTime(iroha::time::now( + std::chrono::minutes(5) - std::chrono::seconds(10)))), + checkStatelessValid) .skipProposal() .checkBlock(checkStatefulValid) .done(); @@ -156,17 +134,11 @@ TEST_F(AcceptanceTest, Transaction5MinutesFromFuture) { * @then receive STATELESS_VALIDATION_FAILED status */ TEST_F(AcceptanceTest, Transaction10MinutesFromFuture) { - auto tx = TestUnsignedTransactionBuilder() - .createdTime(iroha::time::now(std::chrono::minutes(10))) - .creatorAccountId(kAdmin) - .addAssetQuantity(kAdmin, kAsset, "1.0") - .quorum(1) - .build() - .signAndAddSignature(kAdminKeypair) - .finish(); integration_framework::IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(tx, checkStatelessInvalid) + .sendTx(complete(baseTx<>().createdTime( + iroha::time::now(std::chrono::minutes(10)))), + checkStatelessInvalid) .done(); } @@ -177,12 +149,7 @@ TEST_F(AcceptanceTest, Transaction10MinutesFromFuture) { */ TEST_F(AcceptanceTest, TransactionEmptyPubKey) { shared_model::proto::Transaction tx = - TestTransactionBuilder() - .createdTime(iroha::time::now()) - .creatorAccountId(kAdmin) - .addAssetQuantity(kAdmin, kAsset, "1.0") - .quorum(1) - .build(); + baseTx().build(); auto signedBlob = shared_model::crypto::CryptoSigner<>::sign( shared_model::crypto::Blob(tx.payload()), kAdminKeypair); @@ -200,12 +167,7 @@ TEST_F(AcceptanceTest, TransactionEmptyPubKey) { */ TEST_F(AcceptanceTest, TransactionEmptySignedblob) { shared_model::proto::Transaction tx = - TestTransactionBuilder() - .createdTime(iroha::time::now()) - .creatorAccountId(kAdmin) - .addAssetQuantity(kAdmin, kAsset, "1.0") - .quorum(1) - .build(); + baseTx().build(); tx.addSignature(shared_model::crypto::Signed(""), kAdminKeypair.publicKey()); integration_framework::IntegrationTestFramework(1) .setInitialState(kAdminKeypair) @@ -220,12 +182,7 @@ TEST_F(AcceptanceTest, TransactionEmptySignedblob) { */ TEST_F(AcceptanceTest, TransactionInvalidPublicKey) { shared_model::proto::Transaction tx = - TestTransactionBuilder() - .createdTime(iroha::time::now()) - .creatorAccountId(kAdmin) - .addAssetQuantity(kAdmin, kAsset, "1.0") - .quorum(1) - .build(); + baseTx().build(); auto signedBlob = shared_model::crypto::CryptoSigner<>::sign( shared_model::crypto::Blob(tx.payload()), kAdminKeypair); tx.addSignature( @@ -246,12 +203,7 @@ TEST_F(AcceptanceTest, TransactionInvalidPublicKey) { */ TEST_F(AcceptanceTest, TransactionInvalidSignedBlob) { shared_model::proto::Transaction tx = - TestTransactionBuilder() - .createdTime(iroha::time::now()) - .creatorAccountId(kAdmin) - .addAssetQuantity(kAdmin, kAsset, "1.0") - .quorum(1) - .build(); + baseTx().build(); auto signedBlob = shared_model::crypto::CryptoSigner<>::sign( shared_model::crypto::Blob(tx.payload()), kAdminKeypair); @@ -274,19 +226,27 @@ TEST_F(AcceptanceTest, TransactionInvalidSignedBlob) { * AND STATEFUL_VALIDATION_SUCCESS on that tx */ TEST_F(AcceptanceTest, TransactionValidSignedBlob) { - shared_model::proto::Transaction tx = - TestUnsignedTransactionBuilder() - .createdTime(iroha::time::now()) - .creatorAccountId(kAdmin) - .addAssetQuantity(kAdmin, kAsset, "1.0") - .quorum(1) - .build() - .signAndAddSignature(kAdminKeypair) - .finish(); integration_framework::IntegrationTestFramework(1) .setInitialState(kAdminKeypair) - .sendTx(tx, checkStatelessValid) + .sendTx(complete(baseTx<>()), checkStatelessValid) .skipProposal() .checkBlock(checkStatefulValid) .done(); } + +/** + * @given some user + * @when sending transaction without any signature + * @then the response is STATELESS_VALIDATION_FAILED + */ +TEST_F(AcceptanceTest, EmptySignatures) { + std::string kAccountId = "some@account"; + auto proto_tx = baseTx().build().getTransport(); + proto_tx.clear_signatures(); + auto tx = shared_model::proto::Transaction(proto_tx); + + integration_framework::IntegrationTestFramework(1) + .setInitialState(kAdminKeypair) + .sendTx(tx, checkStatelessInvalid) + .done(); +} diff --git a/test/integration/binary/CMakeLists.txt b/test/integration/binary/CMakeLists.txt new file mode 100644 index 0000000000..96aa78ec4b --- /dev/null +++ b/test/integration/binary/CMakeLists.txt @@ -0,0 +1,39 @@ + +if (SWIG_PYTHON OR SWIG_JAVA) + get_property(SWIG_BUILD_DIR GLOBAL PROPERTY SWIG_BUILD_DIR) +endif () + +if (SWIG_PYTHON) + if (SUPPORT_PYTHON2) + find_package(PythonInterp 2.7 REQUIRED) + else () + find_package(PythonInterp 3.5 REQUIRED) + endif () + + # TODO, igor-egorov, 2018-06-27, IR-1481, move that foreach to a separate target + foreach (item "block" "commands" "primitive" "queries") + compile_proto_to_python("${item}.proto") + list(APPEND PROTO_SWIG_DEPS "${SWIG_BUILD_DIR}/${item}_pb2.py") + endforeach (item) + + add_executable(binary_test + launchers.cpp + binaries_test.cpp + ) + target_link_libraries(binary_test + gtest::main + shared_model_proto_backend + integration_framework + bindings + ) + target_include_directories(binary_test PUBLIC ${PROJECT_SOURCE_DIR}/test) + + add_dependencies(binary_test irohapy) + add_test( + NAME "python_binary_test" + COMMAND ${CMAKE_COMMAND} -E + env "PYTHON_INTERPRETER=${PYTHON_EXECUTABLE}" "PYTHONPATH=${SWIG_BUILD_DIR}" + "ROOT_DIR=${PROJECT_SOURCE_DIR}" $ --gtest_filter=*/0* + ) + +endif () diff --git a/test/integration/binary/binaries_test.cpp b/test/integration/binary/binaries_test.cpp new file mode 100644 index 0000000000..b7e7084076 --- /dev/null +++ b/test/integration/binary/binaries_test.cpp @@ -0,0 +1,204 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "integration/binary/binaries_test_fixture.hpp" + +using namespace shared_model::interface; + +using BinaryTestTypes = ::testing::Types; + +TYPED_TEST_CASE(BinaryTestFixture, BinaryTestTypes); + +// -------------------------- Transactions -------------------------- + +TYPED_TEST(BinaryTestFixture, can_create_account) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_set_detail) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_grant_can_set_my_account_detail) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_set_my_account_detail) { + this->doTest(3); +} + +TYPED_TEST(BinaryTestFixture, can_create_asset) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_receive) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_transfer) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_grant_can_transfer_my_assets) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_transfer_my_assets) { + this->doTest(3); +} + +TYPED_TEST(BinaryTestFixture, can_add_asset_qty) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_subtract_asset_qty) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_create_domain) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_add_peer) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_create_role) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_append_role) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_detach_role) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_add_signatory) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_grant_can_add_my_signatory) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_grant_can_remove_my_signatory) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_grant_can_set_my_quorum) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_add_my_signatory) { + this->doTest(3); +} + +TYPED_TEST(BinaryTestFixture, can_remove_signatory) { + this->doTest(2); +} + +TYPED_TEST(BinaryTestFixture, can_set_my_quorum) { + this->doTest(3); +} + +TYPED_TEST(BinaryTestFixture, can_remove_my_signatory) { + this->doTest(3); +} + +TYPED_TEST(BinaryTestFixture, can_set_quorum) { + this->doTest(2); +} + +// -------------------------- Queries -------------------------- + +TYPED_TEST(BinaryTestFixture, can_get_all_acc_detail) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_all_accounts) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_domain_acc_detail) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_domain_accounts) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_my_acc_detail) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_my_account) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_all_acc_ast) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_domain_acc_ast) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_my_acc_ast) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_all_acc_ast_txs) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_domain_acc_ast_txs) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_my_acc_ast_txs) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_all_acc_txs) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_domain_acc_txs) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_my_acc_txs) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_read_assets) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_roles) { + this->template doTest(1, 2); +} + +TYPED_TEST(BinaryTestFixture, can_get_all_signatories) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_domain_signatories) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_my_signatories) { + this->template doTest(1, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_all_txs) { + this->template doTest(3, 1); +} + +TYPED_TEST(BinaryTestFixture, can_get_my_txs) { + this->template doTest(3, 1); +} diff --git a/test/integration/binary/binaries_test_fixture.hpp b/test/integration/binary/binaries_test_fixture.hpp new file mode 100644 index 0000000000..8e8c52a3d2 --- /dev/null +++ b/test/integration/binary/binaries_test_fixture.hpp @@ -0,0 +1,205 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_BINARIES_TEST_FIXTURE_HPP +#define IROHA_BINARIES_TEST_FIXTURE_HPP + +#include + +#include +#include "builders/protobuf/block.hpp" +#include "framework/integration_framework/integration_test_framework.hpp" +#include "framework/specified_visitor.hpp" +#include "integration/binary/launchers.hpp" + + +namespace shared_model { + + namespace proto { + class Query; + } + + namespace interface { + + class Block; + class AccountDetailResponse; + class AccountAssetResponse; + class AccountResponse; + class AssetResponse; + class RolePermissionsResponse; + class RolesResponse; + class SignatoriesResponse; + class TransactionsResponse; + + } // namespace interface + +} // namespace shared_model + +namespace query_validation { + + using QueryIterator = std::vector::iterator; + + namespace internal { + + /** + * Helper type that is used as a marker of the end of types list in variadic + * templates. + * Should not be used outside of query_validation namespace. + */ + class Void {}; + + /** + * Asserts that actual query response type equals to expected type. + * + * @tparam ExpectedResponseType - expected type of QueryResponse object + * @param response - QueryResponse object to check + */ + template + inline void checkQueryResponseType( + const shared_model::proto::QueryResponse &response) { + ASSERT_NO_THROW(boost::apply_visitor( + framework::SpecifiedVisitor(), response.get())); + } + + /** + * Recursively iterates over a list of types and a vector of queries, + * executes queries and asserts that query responses types equal to expected + * types. + * + * @tparam Head - the type of expected query response for the current query + * @tparam Tail - the rest of expected query response types + * @param it - points to query needs to be checked on current step + * @param end - queries' vector iterator that points to .end() + * @param itf - already initialized ITF instance that will be used queries + * execution. + */ + template + inline void _validateQueries(::query_validation::QueryIterator it, + ::query_validation::QueryIterator end, + integration_framework::IntegrationTestFramework &itf) { + if (it != end) { + itf.sendQuery(*it, checkQueryResponseType); + _validateQueries(++it, end, itf); + } + } + + /** + * Handles terminating scenario for recursion made over variadic templates. + */ + template <> + inline void _validateQueries( + ::query_validation::QueryIterator it, + ::query_validation::QueryIterator end, + integration_framework::IntegrationTestFramework &itf){}; + + } // namespace internal + + /** + * Sequentially executes queries via ITF and asserts that types of actual + * query responses equal to expected types. + * + * @tparam ExpectedResponsesTypes - A list of types of expected query + * responses. The order of types should match the order of queries in a + * vector. + * @param it - queries' vector iterator that points to .begin() + * @param end - queries' vector iterator that points to .end() + * @param itf - already initialized ITF instance that will be used queries + * execution. + */ + template + inline void validateQueriesResponseTypes(QueryIterator it, + QueryIterator end, + integration_framework::IntegrationTestFramework &itf) { + internal::_validateQueries( + it, end, itf); + } + +} // namespace query_validation + +/** + * @tparam Launcher - class that can run scripts on target language for + * transactions' and queries' binaries generation. + */ +template +class BinaryTestFixture : public ::testing::Test { + public: + Launcher launcher; + + /** + * Generates genesis block for ITF initialization using the first transaction + * and keypair received from launcher. + * + * @return - genesis block + */ + shared_model::proto::Block genesis() { + return makeGenesis(launcher.transactions[0], *launcher.admin_key); + } + + /** + * Callback that asserts that passed block contains one transaction. + * + * @param result - Block object + */ + static void blockWithTransactionValidation( + const std::shared_ptr &result) { + ASSERT_EQ(result->transactions().size(), 1); + } + + /** + * Initalizes ITF. Sequentially applies transacttions and then queries + * provided by launcher. Checks that every transaction was committed. + * Asserts that query response types match to expected types. + * + * @tparam ExpectedQueryResponsesTypes - list of expected query response + * types. The order should match order of queries that were provided by + * launcher. + * @param transactions_expected - expected amount of transactions + * @param queries_expected - expected amount of queries + */ + template + void doTest(const unsigned &transactions_expected = 0, + const unsigned &queries_expected = 0) { + if (launcher.initialized(transactions_expected, queries_expected)) { + integration_framework::IntegrationTestFramework itf(1); + + itf.setInitialState(launcher.admin_key.value(), genesis()); + + std::for_each( + std::next( // first transaction was used as genesis transaction + launcher.transactions.begin()), + launcher.transactions.end(), + [&itf](const auto &tx) { + itf.sendTx(tx).checkBlock(BinaryTestFixture::blockWithTransactionValidation); + }); + + query_validation::validateQueriesResponseTypes< + ExpectedQueryResponsesTypes...>( + launcher.queries.begin(), launcher.queries.end(), itf); + + itf.done(); + } + } + + protected: + virtual void SetUp() { + launcher(::testing::UnitTest::GetInstance()->current_test_info()->name()); + } + + shared_model::proto::Block makeGenesis( + const shared_model::proto::Transaction &genesis_tx, + const shared_model::crypto::Keypair &keypair) { + return shared_model::proto::BlockBuilder() + .transactions(std::vector{genesis_tx}) + .height(1) + .prevHash(shared_model::crypto::DefaultHashProvider::makeHash( + shared_model::crypto::Blob(""))) + .createdTime(iroha::time::now()) + .build() + .signAndAddSignature(keypair) + .finish(); + } +}; + +#endif // IROHA_BINARIES_TEST_FIXTURE_HPP diff --git a/test/integration/binary/launchers.cpp b/test/integration/binary/launchers.cpp new file mode 100644 index 0000000000..c53cf68a7b --- /dev/null +++ b/test/integration/binary/launchers.cpp @@ -0,0 +1,102 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "integration/binary/launchers.hpp" + +#include + +#include +#include "bindings/model_crypto.hpp" +#include "common/byteutils.hpp" + +using namespace boost::process; + +namespace binary_test { + + constexpr auto cTimeToKill = std::chrono::minutes(15); + + void Launcher::operator()(const std::string &example) { + ipstream pipe; + const auto &command = launchCommand(example); + if (command.empty()) { + FAIL() << "Launcher provided empty command"; + } + child c(command, std_out > pipe); + auto terminated = c.wait_for(cTimeToKill); + if (not terminated) { + c.terminate(); + FAIL() << "Child process was terminated because execution time limit " + "has been exceeded"; + } + readBinaries(pipe); + } + + void Launcher::readBinaries(ipstream &stream) { + transactions.clear(); + queries.clear(); + std::string packed_line; + std::string raw_payload; + while (stream and std::getline(stream, packed_line) + and packed_line.size() > 1) { + raw_payload = packed_line.substr(1); + if (auto byte_string = iroha::hexstringToBytestring(raw_payload)) { + auto binary_type = packed_line.at(0); + switch (binary_type) { + case 'K': { + if (not admin_key) { + admin_key = shared_model::bindings::ModelCrypto().fromPrivateKey( + raw_payload); + } + break; + } + case 'T': { + iroha::protocol::Transaction proto_tx; + if (proto_tx.ParseFromString(*byte_string)) { + transactions.emplace_back(std::move(proto_tx)); + } + break; + } + case 'Q': { + iroha::protocol::Query proto_query; + if (proto_query.ParseFromString(*byte_string)) { + queries.emplace_back(std::move(proto_query)); + } + break; + } + } // switch (binary_type) + } + } + } + + bool Launcher::initialized(const unsigned &transactions_expected, + const unsigned &queries_expected) { + checkAsserts(transactions_expected, queries_expected); + return admin_key and transactions.size() == transactions_expected + and queries.size() == queries_expected; + } + + void Launcher::checkAsserts(const unsigned &transactions_expected, + const unsigned &queries_expected) { + ASSERT_TRUE(admin_key); + ASSERT_EQ(transactions.size(), transactions_expected); + ASSERT_EQ(queries.size(), queries_expected); + } + + std::string PythonLauncher::launchCommand(const std::string &example) { + std::stringstream s; + // TODO, igor-egorov, 2018-06-30, IR-1488, avoid bash and use + // boost::filesystem + s << "bash -c \"${PYTHON_INTERPRETER} " + "${ROOT_DIR}/example/python/permissions/" + << example << ".py\""; + return s.str(); + } + + std::string JavaLauncher::launchCommand(const std::string &example) { + return ""; + // TODO, igor-egorov, 2018-06-20, IR-1389 + } + +} // namespace binary_test diff --git a/test/integration/binary/launchers.hpp b/test/integration/binary/launchers.hpp new file mode 100644 index 0000000000..7bf220b88d --- /dev/null +++ b/test/integration/binary/launchers.hpp @@ -0,0 +1,83 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef IROHA_LAUNCHERS_HPP +#define IROHA_LAUNCHERS_HPP + +#include +#include +#include +#include "backend/protobuf/queries/proto_query.hpp" +#include "backend/protobuf/transaction.hpp" +#include "cryptography/keypair.hpp" + +namespace binary_test { + + /** + * Specifies interface for running language-dependent generators of + * transactions' and queries' binaries for test cases. + */ + class Launcher { + public: + /** + * Composes language-dependent command for launching a script for a certain + * test case. + * + * @param test_case - name of test case + * @return - string, a command to be executed + */ + virtual std::string launchCommand(const std::string &test_case) = 0; + + /** + * Runs external binaries generator for a specific test_case and consumes + * its output. Initializes admin_key, transactions vector, and queries + * vector fields. + * + * @param example - name of test_case scenario + */ + void operator()(const std::string &example); + + /** + * Asserts that amount of received transactions and queries equals to + * expected values. + * + * @param transactions_expected - expected amount of transactions + * @param queries_expected - expected amount of queries + * @return - assertion result + */ + bool initialized(const unsigned &transactions_expected = 0, + const unsigned &queries_expected = 0); + + boost::optional admin_key; + std::vector transactions; + std::vector queries; + + protected: + /** + * Parses binaries generator output and initalizes class fields. + * + * @param stream - standard output of external binaries generator. + * It is expected that entities are divided by newline separator. + * The first entity is expected to be a binary of admin's keypair. + * The next lines are transactions' or queries' binaries. + * The first read transaction will be considered as genesis transaction. + */ + void readBinaries(boost::process::ipstream &stream); + void checkAsserts(const unsigned &transactions_expected, + const unsigned &queries_expected); + }; + + class PythonLauncher : public Launcher { + public: + std::string launchCommand(const std::string &example) override; + }; + + class JavaLauncher : public Launcher { + public: + std::string launchCommand(const std::string &example) override; + }; + +} // namespace binary_test + +#endif // IROHA_LAUNCHERS_HPP diff --git a/test/integration/pipeline/pipeline_test.cpp b/test/integration/pipeline/pipeline_test.cpp index 004a14342d..ec40c8e841 100644 --- a/test/integration/pipeline/pipeline_test.cpp +++ b/test/integration/pipeline/pipeline_test.cpp @@ -71,7 +71,7 @@ TEST(PipelineIntegrationTest, SendTx) { auto tx = shared_model::proto::TransactionBuilder() .createdTime(iroha::time::now()) .creatorAccountId(kUser) - .addAssetQuantity(kUser, kAsset, "1.0") + .addAssetQuantity(kAsset, "1.0") .quorum(1) .build() .signAndAddSignature( diff --git a/test/integration/transport/CMakeLists.txt b/test/integration/transport/CMakeLists.txt index ba36f9b9e6..8d8617e791 100644 --- a/test/integration/transport/CMakeLists.txt +++ b/test/integration/transport/CMakeLists.txt @@ -6,5 +6,4 @@ target_link_libraries(ordering_gate_service_test ordering_service shared_model_stateless_validation shared_model_cryptography_model - iroha_amount ) diff --git a/test/integration/transport/ordering_gate_service_test.cpp b/test/integration/transport/ordering_gate_service_test.cpp index 112b38d54c..ad3c7fe073 100644 --- a/test/integration/transport/ordering_gate_service_test.cpp +++ b/test/integration/transport/ordering_gate_service_test.cpp @@ -17,6 +17,7 @@ #include +#include "backend/protobuf/proto_proposal_factory.hpp" #include "builders/protobuf/common_objects/proto_peer_builder.hpp" #include "builders/protobuf/transaction.hpp" #include "framework/test_subscriber.hpp" @@ -44,6 +45,8 @@ using wPeer = std::shared_ptr; class OrderingGateServiceTest : public ::testing::Test { public: OrderingGateServiceTest() { + factory_ = std::make_unique>(); pcs_ = std::make_shared(); EXPECT_CALL(*pcs_, on_commit()) .WillRepeatedly(Return(commit_subject_.get_observable())); @@ -64,7 +67,8 @@ class OrderingGateServiceTest : public ::testing::Test { max_proposal, proposal_timeout.get_observable(), service_transport, - fake_persistent_state); + fake_persistent_state, + std::move(factory_)); service_transport->subscribe(service); } @@ -145,7 +149,7 @@ class OrderingGateServiceTest : public ::testing::Test { shared_model::proto::TransactionBuilder() .createdTime(iroha::time::now()) .creatorAccountId("admin@ru") - .addAssetQuantity("admin@tu", "coin#coin", "1.0") + .addAssetQuantity("coin#coin", "1.0") .quorum(1) .build() .signAndAddSignature( @@ -190,6 +194,8 @@ class OrderingGateServiceTest : public ::testing::Test { std::shared_ptr gate_transport; std::shared_ptr service_transport; + std::unique_ptr factory_; + const std::string kAddress = "127.0.0.1"; }; diff --git a/test/module/iroha-cli/client_test.cpp b/test/module/iroha-cli/client_test.cpp index 7b51079630..2a40e74e1f 100644 --- a/test/module/iroha-cli/client_test.cpp +++ b/test/module/iroha-cli/client_test.cpp @@ -1,33 +1,25 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include "builders/protobuf/common_objects/proto_account_builder.hpp" +#include "builders/protobuf/proposal.hpp" #include "model/sha3_hash.hpp" #include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" #include "module/irohad/multi_sig_transactions/mst_mocks.hpp" #include "module/irohad/network/network_mocks.hpp" #include "module/irohad/validation/validation_mocks.hpp" +#include "module/shared_model/builders/protobuf/test_proposal_builder.hpp" #include "module/shared_model/builders/protobuf/test_query_builder.hpp" #include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" #include "client.hpp" +#include "execution/query_execution_impl.hpp" #include "main/server_runner.hpp" #include "torii/command_service.hpp" +#include "torii/impl/status_bus_impl.hpp" #include "torii/processor/query_processor_impl.hpp" #include "torii/processor/transaction_processor_impl.hpp" #include "torii/query_service.hpp" @@ -51,7 +43,19 @@ using namespace iroha::validation; using namespace shared_model::proto; using namespace std::chrono_literals; -constexpr std::chrono::milliseconds proposal_delay = 10s; +constexpr std::chrono::milliseconds initial_timeout = 1s; +constexpr std::chrono::milliseconds nonfinal_timeout = 2 * 10s; + +/** +Here we imitate the behavior of StatusStram client but on a bit lower level. So +the do-while cycle imitates client resubscription to the stream. Stream +"expiration" is a valid designed case (see pr #1615 for the details). + +The number of attempts (3) is a magic constant here. The idea behind this number +is the following: only one resubscription is usually enough to pass the test; if +three resubscribes were not enough, then most likely there is another bug. + */ +constexpr uint32_t status_read_attempts = 3; class ClientServerTest : public testing::Test { public: @@ -74,29 +78,36 @@ class ClientServerTest : public testing::Test { .WillRepeatedly(Return(prop_notifier.get_observable())); EXPECT_CALL(*pcsMock, on_commit()) .WillRepeatedly(Return(commit_notifier.get_observable())); + EXPECT_CALL(*pcsMock, on_verified_proposal()) + .WillRepeatedly(Return(verified_prop_notifier.get_observable())); EXPECT_CALL(*mst, onPreparedTransactionsImpl()) .WillRepeatedly(Return(mst_prepared_notifier.get_observable())); EXPECT_CALL(*mst, onExpiredTransactionsImpl()) .WillRepeatedly(Return(mst_expired_notifier.get_observable())); + auto status_bus = std::make_shared(); auto tx_processor = - std::make_shared(pcsMock, mst); + std::make_shared( + pcsMock, mst, status_bus); auto pb_tx_factory = std::make_shared(); //----------- Query Service ---------- - - auto qpi = std::make_shared(storage); - EXPECT_CALL(*storage, getWsvQuery()).WillRepeatedly(Return(wsv_query)); EXPECT_CALL(*storage, getBlockQuery()).WillRepeatedly(Return(block_query)); + auto qpi = std::make_shared( + storage, std::make_shared(storage)); + //----------- Server run ---------------- runner - ->append(std::make_unique( - tx_processor, storage, proposal_delay)) + ->append(std::make_unique(tx_processor, + storage, + status_bus, + initial_timeout, + nonfinal_timeout)) .append(std::make_unique(qpi)) .run() .match( @@ -119,6 +130,9 @@ class ClientServerTest : public testing::Test { std::shared_ptr pcsMock; std::shared_ptr mst; + rxcpp::subjects::subject< + std::shared_ptr> + verified_prop_notifier; rxcpp::subjects::subject mst_prepared_notifier; rxcpp::subjects::subject mst_expired_notifier; @@ -180,12 +194,86 @@ TEST_F(ClientServerTest, SendTxWhenStatelessInvalid) { ASSERT_EQ(iroha_cli::CliClient(ip, port).sendTx(shm_tx).answer, iroha_cli::CliClient::OK); - auto tx_hash = shm_tx.hash(); - auto res = iroha_cli::CliClient(ip, port).getTxStatus( - shared_model::crypto::toBinaryString(tx_hash)); - ASSERT_EQ(res.answer.tx_status(), + auto getAnswer = [&]() { + return iroha_cli::CliClient(ip, port) + .getTxStatus(shared_model::crypto::toBinaryString(shm_tx.hash())) + .answer; + }; + decltype(getAnswer()) answer; + auto read_attempt_counter(status_read_attempts); + do { + answer = getAnswer(); + } while (answer.tx_status() + != iroha::protocol::TxStatus::STATELESS_VALIDATION_FAILED + and --read_attempt_counter); + ASSERT_EQ(answer.tx_status(), iroha::protocol::TxStatus::STATELESS_VALIDATION_FAILED); - ASSERT_NE(res.answer.error_message().size(), 0); + ASSERT_NE(answer.error_message().size(), 0); +} + +/** + * This test checks, if tx, which did not pass stateful validation, is shown to + * client with a corresponding status and error message + * + * @given real client and mocked pcs + * @when sending a stateless valid transaction @and failing it at stateful + * validation + * @then ensure that client sees: + * - status of this transaction as STATEFUL_VALIDATION_FAILED + * - error message is the same, as the one with which transaction was + * failed + */ +TEST_F(ClientServerTest, SendTxWhenStatefulInvalid) { + iroha_cli::CliClient client(ip, port); + EXPECT_CALL(*pcsMock, propagate_transaction(_)).Times(1); + + // creating stateful invalid tx + auto tx = TransactionBuilder() + .creatorAccountId("some@account") + .createdTime(iroha::time::now()) + .transferAsset("some@account", + "another@account", + "doge#doge", + "some transfer", + "100.0") + .quorum(1) + .build() + .signAndAddSignature( + shared_model::crypto::DefaultCryptoAlgorithmType:: + generateKeypair()) + .finish(); + ASSERT_EQ(client.sendTx(tx).answer, iroha_cli::CliClient::OK); + + // fail the tx + auto verified_proposal = std::make_shared( + TestProposalBuilder().height(0).createdTime(iroha::time::now()).build()); + verified_prop_notifier.get_subscriber().on_next( + std::make_shared( + std::make_pair(verified_proposal, + iroha::validation::TransactionsErrors{std::make_pair( + iroha::validation::CommandError{ + "CommandName", "CommandError", true, 2}, + tx.hash())}))); + auto stringified_error = "Stateful validation error in transaction " + + tx.hash().hex() + ": command 'CommandName' with " + "index '2' did not pass verification with " + "error 'CommandError'"; + + auto getAnswer = [&]() { + return client.getTxStatus(shared_model::crypto::toBinaryString(tx.hash())) + .answer; + }; + decltype(getAnswer()) answer; + auto read_attempt_counter(status_read_attempts); + do { + // check it really failed with specific message + answer = getAnswer(); + } while (answer.tx_status() + != iroha::protocol::TxStatus::STATEFUL_VALIDATION_FAILED + and --read_attempt_counter); + ASSERT_EQ(answer.tx_status(), + iroha::protocol::TxStatus::STATEFUL_VALIDATION_FAILED); + ASSERT_EQ(answer.error_message(), stringified_error); } TEST_F(ClientServerTest, SendQueryWhenInvalidJson) { @@ -237,20 +325,15 @@ TEST_F(ClientServerTest, SendQueryWhenValid) { EXPECT_CALL(*wsv_query, getSignatories("admin@test")) .WillRepeatedly(Return(signatories)); - EXPECT_CALL( - *wsv_query, - hasAccountGrantablePermission( - "admin@test", - "test@test", - shared_model::interface::permissions::permissionOf( - shared_model::interface::permissions::Role::kGetMyAccDetail))) - .WillOnce(Return(true)); - - EXPECT_CALL(*wsv_query, getAccountDetail("test@test")) + EXPECT_CALL(*wsv_query, getAccountDetail("test@test", "", "")) .WillOnce(Return(boost::make_optional(std::string("value")))); + const std::vector kRole{"role"}; EXPECT_CALL(*wsv_query, getAccountRoles("admin@test")) - .WillOnce(Return(boost::none)); + .WillOnce(Return(boost::make_optional(kRole))); + EXPECT_CALL(*wsv_query, getRolePermissions(kRole[0])) + .WillOnce(Return(shared_model::interface::RolePermissionSet{ + shared_model::interface::permissions::Role::kGetAllAccDetail})); auto query = QueryBuilder() .createdTime(iroha::time::now()) @@ -271,15 +354,6 @@ TEST_F(ClientServerTest, SendQueryWhenStatefulInvalid) { EXPECT_CALL(*wsv_query, getSignatories("admin@test")) .WillRepeatedly(Return(signatories)); - EXPECT_CALL( - *wsv_query, - hasAccountGrantablePermission( - "admin@test", - "test@test", - shared_model::interface::permissions::permissionOf( - shared_model::interface::permissions::Role::kGetMyAccDetail))) - .WillOnce(Return(false)); - EXPECT_CALL(*wsv_query, getAccountRoles("admin@test")) .WillOnce(Return(boost::none)); diff --git a/test/module/irohad/ametsuchi/CMakeLists.txt b/test/module/irohad/ametsuchi/CMakeLists.txt index 197464d502..2c4af25796 100644 --- a/test/module/irohad/ametsuchi/CMakeLists.txt +++ b/test/module/irohad/ametsuchi/CMakeLists.txt @@ -42,6 +42,13 @@ target_link_libraries(kv_storage_test ametsuchi_fixture ) +addtest(mutable_storage_test mutable_storage_test.cpp) +target_link_libraries(mutable_storage_test + ametsuchi + libs_common + ametsuchi_fixture + ) + addtest(storage_init_test storage_init_test.cpp) target_link_libraries(storage_init_test ametsuchi @@ -55,8 +62,17 @@ target_link_libraries(postgres_options_test libs_common ) +addtest(postgres_executor_test postgres_executor_test.cpp) +target_link_libraries(postgres_executor_test + integration_framework + ametsuchi + libs_common + ) + add_library(ametsuchi_fixture INTERFACE) target_link_libraries(ametsuchi_fixture INTERFACE - pqxx integration_framework_config_helper + shared_model_proto_backend + SOCI::core + SOCI::postgresql ) diff --git a/test/module/irohad/ametsuchi/ametsuchi_fixture.hpp b/test/module/irohad/ametsuchi/ametsuchi_fixture.hpp index cddf2e248f..aea5b6a570 100644 --- a/test/module/irohad/ametsuchi/ametsuchi_fixture.hpp +++ b/test/module/irohad/ametsuchi/ametsuchi_fixture.hpp @@ -1,32 +1,23 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_AMETSUCHI_FIXTURE_HPP #define IROHA_AMETSUCHI_FIXTURE_HPP #include +#include +#include #include #include #include -#include #include "ametsuchi/impl/storage_impl.hpp" +#include "backend/protobuf/common_objects/proto_common_objects_factory.hpp" #include "common/files.hpp" #include "framework/config_helper.hpp" #include "logger/logger.hpp" +#include "validators/field_validator.hpp" namespace iroha { namespace ametsuchi { @@ -36,53 +27,44 @@ namespace iroha { class AmetsuchiTest : public ::testing::Test { public: AmetsuchiTest() - : pgopt_(integration_framework::getPostgresCredsOrDefault() - + " dbname=" + dbname_) { + : pgopt_("dbname=" + dbname_ + " " + + integration_framework::getPostgresCredsOrDefault()) { auto log = logger::testLog("AmetsuchiTest"); boost::filesystem::create_directory(block_store_path); } protected: - virtual void clear() { - pqxx::work txn(*connection); - txn.exec(drop_); - txn.commit(); - - iroha::remove_dir_contents(block_store_path); - } - virtual void disconnect() { - connection->disconnect(); + sql->close(); } virtual void connect() { - StorageImpl::create(block_store_path, pgopt_) + StorageImpl::create(block_store_path, pgopt_, factory) .match([&](iroha::expected::Value> &_storage) { storage = _storage.value; }, [](iroha::expected::Error &error) { FAIL() << "StorageImpl: " << error.error; }); - connection = std::make_shared(pgopt_); - try { - connection->activate(); - } catch (const pqxx::broken_connection &e) { - FAIL() << "Connection to PostgreSQL broken: " << e.what(); - } + sql = std::make_shared(soci::postgresql, pgopt_); } void SetUp() override { connect(); - storage->dropStorage(); } void TearDown() override { - clear(); - disconnect(); + storage->dropStorage(); } - std::shared_ptr connection; + std::shared_ptr sql; + + std::shared_ptr> + factory = + std::make_shared>(); std::shared_ptr storage; @@ -98,23 +80,6 @@ namespace iroha { .string(); // TODO(warchant): IR-1019 hide SQLs under some interface - const std::string drop_ = R"( -DROP TABLE IF EXISTS account_has_signatory; -DROP TABLE IF EXISTS account_has_asset; -DROP TABLE IF EXISTS role_has_permissions; -DROP TABLE IF EXISTS account_has_roles; -DROP TABLE IF EXISTS account_has_grantable_permissions; -DROP TABLE IF EXISTS account; -DROP TABLE IF EXISTS asset; -DROP TABLE IF EXISTS domain; -DROP TABLE IF EXISTS signatory; -DROP TABLE IF EXISTS peer; -DROP TABLE IF EXISTS role; -DROP TABLE IF EXISTS height_by_hash; -DROP TABLE IF EXISTS height_by_account_set; -DROP TABLE IF EXISTS index_by_creator_height; -DROP TABLE IF EXISTS index_by_id_height_asset; -)"; const std::string init_ = R"( CREATE TABLE IF NOT EXISTS role ( @@ -127,7 +92,7 @@ CREATE TABLE IF NOT EXISTS domain ( PRIMARY KEY (domain_id) ); CREATE TABLE IF NOT EXISTS signatory ( - public_key bytea NOT NULL, + public_key varchar NOT NULL, PRIMARY KEY (public_key) ); CREATE TABLE IF NOT EXISTS account ( @@ -139,11 +104,11 @@ CREATE TABLE IF NOT EXISTS account ( ); CREATE TABLE IF NOT EXISTS account_has_signatory ( account_id character varying(288) NOT NULL REFERENCES account, - public_key bytea NOT NULL REFERENCES signatory, + public_key varchar NOT NULL REFERENCES signatory, PRIMARY KEY (account_id, public_key) ); CREATE TABLE IF NOT EXISTS peer ( - public_key bytea NOT NULL, + public_key varchar NOT NULL, address character varying(261) NOT NULL UNIQUE, PRIMARY KEY (public_key) ); @@ -177,7 +142,7 @@ CREATE TABLE IF NOT EXISTS account_has_grantable_permissions ( PRIMARY KEY (permittee_account_id, account_id, permission_id) ); CREATE TABLE IF NOT EXISTS height_by_hash ( - hash bytea, + hash varchar, height text ); CREATE TABLE IF NOT EXISTS height_by_account_set ( diff --git a/test/module/irohad/ametsuchi/ametsuchi_mocks.hpp b/test/module/irohad/ametsuchi/ametsuchi_mocks.hpp index 6a1d975817..49456315cc 100644 --- a/test/module/irohad/ametsuchi/ametsuchi_mocks.hpp +++ b/test/module/irohad/ametsuchi/ametsuchi_mocks.hpp @@ -21,6 +21,7 @@ #include #include #include "ametsuchi/block_query.hpp" +#include "ametsuchi/key_value_storage.hpp" #include "ametsuchi/mutable_factory.hpp" #include "ametsuchi/mutable_storage.hpp" #include "ametsuchi/peer_query.hpp" @@ -28,7 +29,6 @@ #include "ametsuchi/temporary_factory.hpp" #include "ametsuchi/temporary_wsv.hpp" #include "ametsuchi/wsv_query.hpp" -#include "ametsuchi/key_value_storage.hpp" #include "common/result.hpp" #include "interfaces/common_objects/peer.hpp" @@ -39,8 +39,10 @@ namespace iroha { MOCK_METHOD1(getAccountRoles, boost::optional>( const std::string &account_id)); - MOCK_METHOD1(getAccountDetail, - boost::optional(const std::string &account_id)); + MOCK_METHOD3(getAccountDetail, + boost::optional(const std::string &account_id, + const std::string &key, + const std::string &writer)); MOCK_METHOD1(getRolePermissions, boost::optional( const std::string &role_name)); @@ -154,27 +156,27 @@ namespace iroha { public: MOCK_METHOD1( getAccountTransactions, - rxcpp::observable( + std::vector( const shared_model::interface::types::AccountIdType &account_id)); MOCK_METHOD1(getTxByHashSync, boost::optional( const shared_model::crypto::Hash &hash)); MOCK_METHOD2( getAccountAssetTransactions, - rxcpp::observable( + std::vector( const shared_model::interface::types::AccountIdType &account_id, const shared_model::interface::types::AssetIdType &asset_id)); MOCK_METHOD1( getTransactions, - rxcpp::observable>( + std::vector>( const std::vector &tx_hashes)); MOCK_METHOD2(getBlocks, - rxcpp::observable( + std::vector( shared_model::interface::types::HeightType, uint32_t)); MOCK_METHOD1(getBlocksFrom, - rxcpp::observable( + std::vector( shared_model::interface::types::HeightType)); - MOCK_METHOD1(getTopBlocks, rxcpp::observable(uint32_t)); + MOCK_METHOD1(getTopBlocks, std::vector(uint32_t)); MOCK_METHOD0(getTopBlock, expected::Result(void)); MOCK_METHOD1(hasTxWithHash, bool(const shared_model::crypto::Hash &hash)); MOCK_METHOD0(getTopBlockHeight, uint32_t(void)); @@ -187,8 +189,33 @@ namespace iroha { expected::Result, std::string>(void)); }; + class MockTemporaryWsv : public TemporaryWsv { + public: + MOCK_METHOD2( + apply, + expected::Result( + const shared_model::interface::Transaction &, + std::function( + const shared_model::interface::Transaction &, WsvQuery &)>)); + MOCK_METHOD1( + createSavepoint, + std::unique_ptr(const std::string &)); + }; + + class MockTemporaryWsvSavepointWrapper + : public TemporaryWsv::SavepointWrapper { + MOCK_METHOD0(release, void(void)); + }; + class MockMutableStorage : public MutableStorage { public: + MOCK_METHOD2( + check, + bool(const shared_model::interface::BlockVariant &, + std::function< + bool(const shared_model::interface::BlockVariant &, + WsvQuery &, + const shared_model::interface::types::HashType &)>)); MOCK_METHOD2( apply, bool(const shared_model::interface::Block &, @@ -245,10 +272,11 @@ namespace iroha { MOCK_METHOD1(insertBlocks, bool(const std::vector< std::shared_ptr> &)); + MOCK_METHOD0(reset, void(void)); MOCK_METHOD0(dropStorage, void(void)); rxcpp::observable> - on_commit() { + on_commit() override { return notifier.get_observable(); } void commit(std::unique_ptr storage) override { @@ -261,8 +289,7 @@ namespace iroha { class MockKeyValueStorage : public KeyValueStorage { public: MOCK_METHOD2(add, bool(Identifier, const Bytes &)); - MOCK_CONST_METHOD1(get, - boost::optional(Identifier)); + MOCK_CONST_METHOD1(get, boost::optional(Identifier)); MOCK_CONST_METHOD0(directory, std::string(void)); MOCK_CONST_METHOD0(last_id, Identifier(void)); MOCK_METHOD0(dropAll, void(void)); diff --git a/test/module/irohad/ametsuchi/ametsuchi_test.cpp b/test/module/irohad/ametsuchi/ametsuchi_test.cpp index 5e1f94a72c..ca32c6ee06 100644 --- a/test/module/irohad/ametsuchi/ametsuchi_test.cpp +++ b/test/module/irohad/ametsuchi/ametsuchi_test.cpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include @@ -37,37 +25,7 @@ using namespace shared_model::interface::permissions; auto zero_string = std::string(32, '0'); auto fake_hash = shared_model::crypto::Hash(zero_string); auto fake_pubkey = shared_model::crypto::PublicKey(zero_string); -using AmountBuilder = shared_model::builder::AmountBuilderWithoutValidator; -/** - * Return shared pointer to amount from result, or throw exception - * @return amount from result - */ -std::shared_ptr getAmount( - const shared_model::builder::BuilderResult - &result) { - return framework::expected::val(result)->value; -} - -/** - * Shortcut to create CallExact observable wrapper, subscribe with given lambda, - * and validate the number of calls with optional custom output - * @tparam O observable type - * @tparam F on_next function type - * @param o observable object - * @param f function object - * @param call_count number of expected calls - * @param msg custom validation failure message - */ -template -void validateCalls(O &&o, - F &&f, - uint64_t call_count, - const std::string &msg = {}) { - auto wrap = make_test_subscriber(std::forward(o), call_count); - wrap.subscribe(std::forward(f)); - ASSERT_TRUE(wrap.validate()) << "Expected " << call_count << " calls" << msg; -} /** * Validate getAccountTransaction with given parameters @@ -82,11 +40,11 @@ void validateAccountTransactions(B &&blocks, const std::string &account, int call_count, int command_count) { - validateCalls( - blocks->getAccountTransactions(account), - [&](const auto &tx) { EXPECT_EQ(tx->commands().size(), command_count); }, - call_count, - " for " + account); + auto txs = blocks->getAccountTransactions(account); + ASSERT_EQ(txs.size(), call_count); + std::for_each(txs.begin(), txs.end(), [&](const auto &tx) { + EXPECT_EQ(tx->commands().size(), command_count); + }); } /** @@ -104,11 +62,11 @@ void validateAccountAssetTransactions(B &&blocks, const std::string &asset, int call_count, int command_count) { - validateCalls( - blocks->getAccountAssetTransactions(account, asset), - [&](const auto &tx) { EXPECT_EQ(tx->commands().size(), command_count); }, - call_count, - " for " + account + " " + asset); + auto txs = blocks->getAccountAssetTransactions(account, asset); + ASSERT_EQ(txs.size(), call_count); + std::for_each(txs.begin(), txs.end(), [&](const auto &tx) { + EXPECT_EQ(tx->commands().size(), command_count); + }); } /** @@ -178,10 +136,7 @@ TEST_F(AmetsuchiTest, GetBlocksCompletedWhenCalled) { apply(storage, block); - auto completed_wrapper = - make_test_subscriber(blocks->getBlocks(1, 1)); - completed_wrapper.subscribe(); - ASSERT_TRUE(completed_wrapper.validate()); + ASSERT_EQ(*blocks->getBlocks(1, 1)[0], block); } TEST_F(AmetsuchiTest, SampleTest) { @@ -193,8 +148,6 @@ TEST_F(AmetsuchiTest, SampleTest) { user1id = "userone@ru", user2id = "usertwo@ru", assetname = "rub", assetid = "rub#ru"; - std::string account, src_account, dest_account, asset; - // Block 1 auto block1 = TestBlockBuilder() .transactions(std::vector( @@ -220,10 +173,10 @@ TEST_F(AmetsuchiTest, SampleTest) { TestBlockBuilder() .transactions(std::vector( {TestTransactionBuilder() - .creatorAccountId("admin2") + .creatorAccountId(user1id) .createAccount(user2name, domain, fake_pubkey) .createAsset(assetname, domain, 1) - .addAssetQuantity(user1id, assetid, "150.0") + .addAssetQuantity(assetid, "150.0") .transferAsset( user1id, user2id, assetid, "Transfer asset", "100.0") .build()})) @@ -233,21 +186,21 @@ TEST_F(AmetsuchiTest, SampleTest) { apply(storage, block2); validateAccountAsset( - wsv, user1id, assetid, *getAmount(AmountBuilder::fromString("50.0"))); + wsv, user1id, assetid, shared_model::interface::Amount("50.0")); validateAccountAsset( - wsv, user2id, assetid, *getAmount(AmountBuilder::fromString("100.0"))); + wsv, user2id, assetid, shared_model::interface::Amount("100.0")); // Block store tests auto hashes = {block1.hash(), block2.hash()}; - validateCalls(blocks->getBlocks(1, 2), - [i = 0, &hashes](auto eachBlock) mutable { - EXPECT_EQ(*(hashes.begin() + i), eachBlock->hash()); - ++i; - }, - 2); + + auto stored_blocks = blocks->getBlocks(1, 2); + ASSERT_EQ(2, stored_blocks.size()); + for (size_t i = 0; i < stored_blocks.size(); i++) { + EXPECT_EQ(*(hashes.begin() + i), stored_blocks[i]->hash()); + } validateAccountTransactions(blocks, "admin1", 1, 3); - validateAccountTransactions(blocks, "admin2", 1, 4); + validateAccountTransactions(blocks, user1id, 1, 4); validateAccountTransactions(blocks, "non_existing_user", 0, 0); validateAccountAssetTransactions(blocks, user1id, assetid, 1, 4); @@ -291,8 +244,6 @@ TEST_F(AmetsuchiTest, queryGetAccountAssetTransactionsTest) { asset2name = "assettwo", asset1id = "assetone#domain", asset2id = "assettwo#domain"; - std::string account, src_account, dest_account, asset; - // 1st tx auto txn1 = TestTransactionBuilder() @@ -305,16 +256,22 @@ TEST_F(AmetsuchiTest, queryGetAccountAssetTransactionsTest) { .createAccount(user3name, domain, fake_pubkey) .createAsset(asset1name, domain, 1) .createAsset(asset2name, domain, 1) - .addAssetQuantity(user1id, asset1id, "300.0") - .addAssetQuantity(user2id, asset2id, "250.0") .build(); - auto block1 = - TestBlockBuilder() - .height(1) - .transactions(std::vector({txn1})) - .prevHash(fake_hash) - .build(); + auto block1 = TestBlockBuilder() + .height(1) + .transactions(std::vector( + {txn1, + TestTransactionBuilder() + .creatorAccountId(user1id) + .addAssetQuantity(asset1id, "300.0") + .build(), + TestTransactionBuilder() + .creatorAccountId(user2id) + .addAssetQuantity(asset2id, "250.0") + .build()})) + .prevHash(fake_hash) + .build(); apply(storage, block1); @@ -325,9 +282,9 @@ TEST_F(AmetsuchiTest, queryGetAccountAssetTransactionsTest) { // Check querying assets for users validateAccountAsset( - wsv, user1id, asset1id, *getAmount(AmountBuilder::fromString("300.0"))); + wsv, user1id, asset1id, shared_model::interface::Amount("300.0")); validateAccountAsset( - wsv, user2id, asset2id, *getAmount(AmountBuilder::fromString("250.0"))); + wsv, user2id, asset2id, shared_model::interface::Amount("250.0")); // 2th tx (user1 -> user2 # asset1) auto txn2 = @@ -346,9 +303,9 @@ TEST_F(AmetsuchiTest, queryGetAccountAssetTransactionsTest) { // Check account asset after transfer assets validateAccountAsset( - wsv, user1id, asset1id, *getAmount(AmountBuilder::fromString("180.0"))); + wsv, user1id, asset1id, shared_model::interface::Amount("180.0")); validateAccountAsset( - wsv, user2id, asset1id, *getAmount(AmountBuilder::fromString("120.0"))); + wsv, user2id, asset1id, shared_model::interface::Amount("120.0")); // 3rd tx // (user2 -> user3 # asset2) @@ -370,24 +327,22 @@ TEST_F(AmetsuchiTest, queryGetAccountAssetTransactionsTest) { apply(storage, block3); validateAccountAsset( - wsv, user2id, asset2id, *getAmount(AmountBuilder::fromString("90.0"))); + wsv, user2id, asset2id, shared_model::interface::Amount("90.0")); validateAccountAsset( - wsv, user3id, asset2id, *getAmount(AmountBuilder::fromString("150.0"))); + wsv, user3id, asset2id, shared_model::interface::Amount("150.0")); validateAccountAsset( - wsv, user1id, asset2id, *getAmount(AmountBuilder::fromString("10.0"))); + wsv, user1id, asset2id, shared_model::interface::Amount("10.0")); // Block store test auto hashes = {block1.hash(), block2.hash(), block3.hash()}; - validateCalls(blocks->getBlocks(1, 3), - [i = 0, &hashes](auto eachBlock) mutable { - EXPECT_EQ(*(hashes.begin() + i), eachBlock->hash()); - ++i; - }, - 3); - - validateAccountTransactions(blocks, admin, 1, 9); - validateAccountTransactions(blocks, user1id, 1, 1); - validateAccountTransactions(blocks, user2id, 1, 2); + + auto stored_blocks = blocks->getBlocks(1, 3); + ASSERT_EQ(3, stored_blocks.size()); + for (size_t i = 0; i < stored_blocks.size(); i++) { + EXPECT_EQ(*(hashes.begin() + i), stored_blocks[i]->hash()); + } + + validateAccountTransactions(blocks, admin, 1, 7); validateAccountTransactions(blocks, user3id, 0, 0); // (user1 -> user2 # asset1) @@ -625,10 +580,6 @@ TEST_F(AmetsuchiTest, TestingStorageWhenInsertBlock) { ASSERT_NE(0, wsv->getPeers().value().size()); - log->info("Drop ledger"); - - storage->dropStorage(); - ASSERT_TRUE(wrapper.validate()); } @@ -662,65 +613,9 @@ TEST_F(AmetsuchiTest, TestingStorageWhenCommitBlock) { storage->commit(std::move(mutable_storage)); - storage->dropStorage(); - ASSERT_TRUE(wrapper.validate()); } -TEST_F(AmetsuchiTest, TestingStorageWhenDropAll) { - auto logger = logger::testLog("TestStorage"); - logger->info( - "Test case: create storage " - "=> insert block " - "=> assert that written" - " => drop all " - "=> assert that all deleted "); - - auto log = logger::testLog("TestStorage"); - log->info( - "Test case: create storage " - "=> insert block " - "=> assert that inserted"); - std::shared_ptr storage; - auto storageResult = StorageImpl::create(block_store_path, pgopt_); - storageResult.match( - [&](iroha::expected::Value> &_storage) { - storage = _storage.value; - }, - [](iroha::expected::Error &error) { - FAIL() << "StorageImpl: " << error.error; - }); - ASSERT_TRUE(storage); - auto wsv = storage->getWsvQuery(); - ASSERT_EQ(0, wsv->getPeers().value().size()); - - log->info("Try insert block"); - - auto inserted = storage->insertBlock(getBlock()); - ASSERT_TRUE(inserted); - - log->info("Request ledger information"); - - ASSERT_NE(0, wsv->getPeers().value().size()); - - log->info("Drop ledger"); - - storage->dropStorage(); - - ASSERT_EQ(0, wsv->getPeers().value().size()); - std::shared_ptr new_storage; - auto new_storageResult = StorageImpl::create(block_store_path, pgopt_); - storageResult.match( - [&](iroha::expected::Value> &_storage) { - new_storage = _storage.value; - }, - [](iroha::expected::Error &error) { - FAIL() << "StorageImpl: " << error.error; - }); - ASSERT_EQ(0, wsv->getPeers().value().size()); - new_storage->dropStorage(); -} - /** * @given initialized storage * @when insert block with 2 transactions in @@ -931,11 +826,7 @@ TEST_F(AmetsuchiTest, TestRestoreWSV) { EXPECT_TRUE(res); // spoil WSV - pqxx::work txn(*connection); - txn.exec(R"( -DELETE FROM domain; -)"); - txn.commit(); + *sql << "DELETE FROM domain"; // check there is no data in WSV res = storage->getWsvQuery()->getDomain("test"); diff --git a/test/module/irohad/ametsuchi/block_query_test.cpp b/test/module/irohad/ametsuchi/block_query_test.cpp index 471b7d4751..45e0feccd1 100644 --- a/test/module/irohad/ametsuchi/block_query_test.cpp +++ b/test/module/irohad/ametsuchi/block_query_test.cpp @@ -20,7 +20,6 @@ #include "ametsuchi/impl/postgres_block_index.hpp" #include "ametsuchi/impl/postgres_block_query.hpp" #include "converters/protobuf/json_proto_converter.hpp" -#include "framework/test_subscriber.hpp" #include "framework/result_fixture.hpp" #include "module/irohad/ametsuchi/ametsuchi_fixture.hpp" #include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" @@ -28,7 +27,6 @@ #include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" using namespace iroha::ametsuchi; -using namespace framework::test_subscriber; using testing::Return; @@ -41,21 +39,13 @@ class BlockQueryTest : public AmetsuchiTest { ASSERT_TRUE(tmp); file = std::move(*tmp); mock_file = std::make_shared(); - postgres_connection = std::make_unique(pgopt_); - try { - postgres_connection->activate(); - } catch (const pqxx::broken_connection &e) { - FAIL() << "Connection to PostgreSQL broken: " << e.what(); - } - transaction = std::make_unique( - *postgres_connection, "Postgres block indexes"); + sql = std::make_unique(soci::postgresql, pgopt_); - index = std::make_shared(*transaction); - blocks = std::make_shared(*transaction, *file); - empty_blocks = - std::make_shared(*transaction, *mock_file); + index = std::make_shared(*sql); + blocks = std::make_shared(*sql, *file); + empty_blocks = std::make_shared(*sql, *mock_file); - transaction->exec(init_); + *sql << init_; // First transaction in block1 auto txn1_1 = TestTransactionBuilder().creatorAccountId(creator1).build(); @@ -89,7 +79,7 @@ class BlockQueryTest : public AmetsuchiTest { .prevHash(block1.hash()) .build(); - for (const auto &b : {block1, block2}) { + for (const auto &b : {std::move(block1), std::move(block2)}) { file->add(b.height(), iroha::stringToBytes( shared_model::converters::protobuf::modelToJson(b))); @@ -98,8 +88,7 @@ class BlockQueryTest : public AmetsuchiTest { } } - std::unique_ptr postgres_connection; - std::unique_ptr transaction; + std::unique_ptr sql; std::vector tx_hashes; std::shared_ptr blocks; std::shared_ptr empty_blocks; @@ -121,11 +110,11 @@ class BlockQueryTest : public AmetsuchiTest { */ TEST_F(BlockQueryTest, GetAccountTransactionsFromSeveralBlocks) { // Check that creator1 has created 3 transactions - auto getCreator1TxWrapper = make_test_subscriber( - blocks->getAccountTransactions(creator1), 3); - getCreator1TxWrapper.subscribe( - [this](auto val) { EXPECT_EQ(val->creatorAccountId(), creator1); }); - ASSERT_TRUE(getCreator1TxWrapper.validate()); + auto txs = blocks->getAccountTransactions(creator1); + ASSERT_EQ(txs.size(), 3); + std::for_each(txs.begin(), txs.end(), [&](const auto &tx) { + EXPECT_EQ(tx->creatorAccountId(), creator1); + }); } /** @@ -137,11 +126,11 @@ TEST_F(BlockQueryTest, GetAccountTransactionsFromSeveralBlocks) { */ TEST_F(BlockQueryTest, GetAccountTransactionsFromSingleBlock) { // Check that creator1 has created 1 transaction - auto getCreator2TxWrapper = make_test_subscriber( - blocks->getAccountTransactions(creator2), 1); - getCreator2TxWrapper.subscribe( - [this](auto val) { EXPECT_EQ(val->creatorAccountId(), creator2); }); - ASSERT_TRUE(getCreator2TxWrapper.validate()); + auto txs = blocks->getAccountTransactions(creator2); + ASSERT_EQ(txs.size(), 1); + std::for_each(txs.begin(), txs.end(), [&](const auto &tx) { + EXPECT_EQ(tx->creatorAccountId(), creator2); + }); } /** @@ -152,10 +141,8 @@ TEST_F(BlockQueryTest, GetAccountTransactionsFromSingleBlock) { */ TEST_F(BlockQueryTest, GetAccountTransactionsNonExistingUser) { // Check that "nonexisting" user has no transaction - auto getNonexistingTxWrapper = make_test_subscriber( - blocks->getAccountTransactions("nonexisting user"), 0); - getNonexistingTxWrapper.subscribe(); - ASSERT_TRUE(getNonexistingTxWrapper.validate()); + auto txs = blocks->getAccountTransactions("nonexisting user"); + ASSERT_EQ(txs.size(), 0); } /** @@ -166,20 +153,12 @@ TEST_F(BlockQueryTest, GetAccountTransactionsNonExistingUser) { * @then queried transactions */ TEST_F(BlockQueryTest, GetTransactionsExistingTxHashes) { - auto wrapper = make_test_subscriber( - blocks->getTransactions({tx_hashes[1], tx_hashes[3]}), 2); - wrapper.subscribe([this](auto tx) { - static auto subs_cnt = 0; - subs_cnt++; - if (subs_cnt == 1) { - ASSERT_TRUE(tx); - EXPECT_EQ(tx_hashes[1], (*tx)->hash()); - } else { - ASSERT_TRUE(tx); - EXPECT_EQ(tx_hashes[3], (*tx)->hash()); - } - }); - ASSERT_TRUE(wrapper.validate()); + auto txs = blocks->getTransactions({tx_hashes[1], tx_hashes[3]}); + ASSERT_EQ(txs.size(), 2); + ASSERT_TRUE(txs[0]); + ASSERT_TRUE(txs[1]); + ASSERT_EQ(txs[0].get()->hash(), tx_hashes[1]); + ASSERT_EQ(txs[1].get()->hash(), tx_hashes[3]); } /** @@ -193,11 +172,11 @@ TEST_F(BlockQueryTest, GetTransactionsIncludesNonExistingTxHashes) { shared_model::crypto::Hash invalid_tx_hash_1(zero_string), invalid_tx_hash_2(std::string( shared_model::crypto::DefaultCryptoAlgorithmType::kHashLength, '9')); - auto wrapper = make_test_subscriber( - blocks->getTransactions({invalid_tx_hash_1, invalid_tx_hash_2}), 2); - wrapper.subscribe( - [](auto transaction) { EXPECT_EQ(boost::none, transaction); }); - ASSERT_TRUE(wrapper.validate()); + + auto txs = blocks->getTransactions({invalid_tx_hash_1, invalid_tx_hash_2}); + ASSERT_EQ(txs.size(), 2); + ASSERT_FALSE(txs[0]); + ASSERT_FALSE(txs[1]); } /** @@ -209,10 +188,8 @@ TEST_F(BlockQueryTest, GetTransactionsIncludesNonExistingTxHashes) { */ TEST_F(BlockQueryTest, GetTransactionsWithEmpty) { // transactions' hashes are empty. - auto wrapper = - make_test_subscriber(blocks->getTransactions({}), 0); - wrapper.subscribe(); - ASSERT_TRUE(wrapper.validate()); + auto txs = blocks->getTransactions({}); + ASSERT_EQ(txs.size(), 0); } /** @@ -225,19 +202,11 @@ TEST_F(BlockQueryTest, GetTransactionsWithEmpty) { TEST_F(BlockQueryTest, GetTransactionsWithInvalidTxAndValidTx) { // TODO 15/11/17 motxx - Use EqualList VerificationStrategy shared_model::crypto::Hash invalid_tx_hash_1(zero_string); - auto wrapper = make_test_subscriber( - blocks->getTransactions({invalid_tx_hash_1, tx_hashes[0]}), 2); - wrapper.subscribe([this](auto tx) { - static auto subs_cnt = 0; - subs_cnt++; - if (subs_cnt == 1) { - EXPECT_EQ(boost::none, tx); - } else { - EXPECT_TRUE(tx); - EXPECT_EQ(tx_hashes[0], (*tx)->hash()); - } - }); - ASSERT_TRUE(wrapper.validate()); + auto txs = blocks->getTransactions({invalid_tx_hash_1, tx_hashes[0]}); + ASSERT_EQ(txs.size(), 2); + ASSERT_FALSE(txs[0]); + ASSERT_TRUE(txs[1]); + ASSERT_EQ(txs[1].get()->hash(), tx_hashes[0]); } /** @@ -247,9 +216,8 @@ TEST_F(BlockQueryTest, GetTransactionsWithInvalidTxAndValidTx) { * @then nothing is returned */ TEST_F(BlockQueryTest, GetNonExistentBlock) { - auto wrapper = make_test_subscriber(blocks->getBlocks(1000, 1), 0); - wrapper.subscribe(); - ASSERT_TRUE(wrapper.validate()); + auto stored_blocks = blocks->getBlocks(1000, 1); + ASSERT_TRUE(stored_blocks.empty()); } /** @@ -259,9 +227,8 @@ TEST_F(BlockQueryTest, GetNonExistentBlock) { * @then returned exactly 1 block */ TEST_F(BlockQueryTest, GetExactlyOneBlock) { - auto wrapper = make_test_subscriber(blocks->getBlocks(1, 1), 1); - wrapper.subscribe(); - ASSERT_TRUE(wrapper.validate()); + auto stored_blocks = blocks->getBlocks(1, 1); + ASSERT_EQ(stored_blocks.size(), 1); } /** @@ -271,9 +238,8 @@ TEST_F(BlockQueryTest, GetExactlyOneBlock) { * @then no blocks returned */ TEST_F(BlockQueryTest, GetBlocks_Count0) { - auto wrapper = make_test_subscriber(blocks->getBlocks(1, 0), 0); - wrapper.subscribe(); - ASSERT_TRUE(wrapper.validate()); + auto stored_blocks = blocks->getBlocks(1, 0); + ASSERT_TRUE(stored_blocks.empty()); } /** @@ -283,9 +249,8 @@ TEST_F(BlockQueryTest, GetBlocks_Count0) { * @then no blocks returned */ TEST_F(BlockQueryTest, GetZeroBlock) { - auto wrapper = make_test_subscriber(blocks->getBlocks(0, 1), 0); - wrapper.subscribe(); - ASSERT_TRUE(wrapper.validate()); + auto stored_blocks = blocks->getBlocks(0, 1); + ASSERT_TRUE(stored_blocks.empty()); } /** @@ -295,15 +260,13 @@ TEST_F(BlockQueryTest, GetZeroBlock) { * @then returned all blocks (2) */ TEST_F(BlockQueryTest, GetBlocksFrom1) { - auto wrapper = - make_test_subscriber(blocks->getBlocksFrom(1), blocks_total); - size_t counter = 1; - wrapper.subscribe([&counter](const auto &b) { - // wrapper returns blocks 1 and 2 - ASSERT_EQ(b->height(), counter++) - << "block height: " << b->height() << "counter: " << counter; - }); - ASSERT_TRUE(wrapper.validate()); + auto stored_blocks = blocks->getBlocksFrom(1); + ASSERT_EQ(stored_blocks.size(), blocks_total); + for (size_t i = 0; i < stored_blocks.size(); i++) { + auto b = stored_blocks[i]; + ASSERT_EQ(b->height(), i + 1) + << "block height: " << b->height() << "counter: " << i; + } } /** @@ -324,11 +287,8 @@ TEST_F(BlockQueryTest, GetBlockButItIsNotJSON) { block_file << content; block_file.close(); - auto wrapper = - make_test_subscriber(blocks->getBlocks(block_n, 1), 0); - wrapper.subscribe(); - - ASSERT_TRUE(wrapper.validate()); + auto stored_blocks = blocks->getBlocks(block_n, 1); + ASSERT_TRUE(stored_blocks.empty()); } /** @@ -352,11 +312,8 @@ TEST_F(BlockQueryTest, GetBlockButItIsInvalidBlock) { block_file << content; block_file.close(); - auto wrapper = - make_test_subscriber(blocks->getBlocks(block_n, 1), 0); - wrapper.subscribe(); - - ASSERT_TRUE(wrapper.validate()); + auto stored_blocks = blocks->getBlocks(block_n, 1); + ASSERT_TRUE(stored_blocks.empty()); } /** @@ -367,14 +324,14 @@ TEST_F(BlockQueryTest, GetBlockButItIsInvalidBlock) { */ TEST_F(BlockQueryTest, GetTop2Blocks) { size_t blocks_n = 2; // top 2 blocks - auto wrapper = - make_test_subscriber(blocks->getTopBlocks(blocks_n), blocks_n); - size_t counter = blocks_total - blocks_n + 1; - wrapper.subscribe( - [&counter](const auto &b) { ASSERT_EQ(b->height(), counter++); }); + auto stored_blocks = blocks->getTopBlocks(blocks_n); + ASSERT_EQ(stored_blocks.size(), blocks_n); - ASSERT_TRUE(wrapper.validate()); + for (size_t i = 0; i < blocks_n; i++) { + auto b = stored_blocks[i]; + ASSERT_EQ(b->height(), i + 1); + } } /** @@ -422,5 +379,6 @@ TEST_F(BlockQueryTest, GetTopBlockFail) { auto top_block_error = framework::expected::err(empty_blocks->getTopBlock()); ASSERT_TRUE(top_block_error); - ASSERT_EQ(top_block_error.value().error, "error while fetching the last block"); + ASSERT_EQ(top_block_error.value().error, + "error while fetching the last block"); } diff --git a/test/module/irohad/ametsuchi/block_query_transfer_test.cpp b/test/module/irohad/ametsuchi/block_query_transfer_test.cpp index de91e7252f..caa7c764aa 100644 --- a/test/module/irohad/ametsuchi/block_query_transfer_test.cpp +++ b/test/module/irohad/ametsuchi/block_query_transfer_test.cpp @@ -37,19 +37,12 @@ namespace iroha { ASSERT_TRUE(tmp); file = std::move(*tmp); - postgres_connection = std::make_unique(pgopt_); - try { - postgres_connection->activate(); - } catch (const pqxx::broken_connection &e) { - FAIL() << "Connection to PostgreSQL broken: " << e.what(); - } - transaction = std::make_unique( - *postgres_connection, "Postgres block indexes"); - - index = std::make_shared(*transaction); - blocks = std::make_shared(*transaction, *file); - - transaction->exec(init_); + sql = std::make_unique(soci::postgresql, pgopt_); + + index = std::make_shared(*sql); + blocks = std::make_shared(*sql, *file); + + *sql << init_; } void insert(const shared_model::proto::Block &block) { @@ -59,8 +52,7 @@ namespace iroha { index->index(block); } - std::unique_ptr postgres_connection; - std::unique_ptr transaction; + std::unique_ptr sql; std::vector tx_hashes; std::shared_ptr blocks; std::shared_ptr index; @@ -135,11 +127,9 @@ namespace iroha { tx_hashes.push_back(block.transactions().back().hash()); insert(block); - auto wrapper = make_test_subscriber( - blocks->getAccountAssetTransactions(creator1, asset), 1); - wrapper.subscribe( - [this](auto val) { ASSERT_EQ(tx_hashes.at(0), val->hash()); }); - ASSERT_TRUE(wrapper.validate()); + auto txs = blocks->getAccountAssetTransactions(creator1, asset); + ASSERT_EQ(txs.size(), 1); + ASSERT_EQ(txs[0]->hash(), tx_hashes[0]); } /** @@ -153,11 +143,9 @@ namespace iroha { tx_hashes.push_back(block.transactions().back().hash()); insert(block); - auto wrapper = make_test_subscriber( - blocks->getAccountAssetTransactions(creator2, asset), 1); - wrapper.subscribe( - [this](auto val) { ASSERT_EQ(tx_hashes.at(0), val->hash()); }); - ASSERT_TRUE(wrapper.validate()); + auto txs = blocks->getAccountAssetTransactions(creator2, asset); + ASSERT_EQ(txs.size(), 1); + ASSERT_EQ(txs[0]->hash(), tx_hashes[0]); } /** @@ -171,11 +159,9 @@ namespace iroha { tx_hashes.push_back(block.transactions().back().hash()); insert(block); - auto wrapper = make_test_subscriber( - blocks->getAccountAssetTransactions(creator3, asset), 1); - wrapper.subscribe( - [this](auto val) { ASSERT_EQ(tx_hashes.at(0), val->hash()); }); - ASSERT_TRUE(wrapper.validate()); + auto txs = blocks->getAccountAssetTransactions(creator3, asset); + ASSERT_EQ(txs.size(), 1); + ASSERT_EQ(txs[0]->hash(), tx_hashes[0]); } /** @@ -195,13 +181,11 @@ namespace iroha { tx_hashes.push_back(block.transactions().back().hash()); insert(block2); - auto wrapper = make_test_subscriber( - blocks->getAccountAssetTransactions(creator1, asset), 2); - wrapper.subscribe([ i = 0, this ](auto val) mutable { - ASSERT_EQ(tx_hashes.at(i), val->hash()); - ++i; - }); - ASSERT_TRUE(wrapper.validate()); + auto txs = blocks->getAccountAssetTransactions(creator1, asset); + ASSERT_EQ(txs.size(), 2); + for (size_t i = 0; i < txs.size(); i++) { + ASSERT_EQ(txs[i]->hash(), tx_hashes[i]); + } } } // namespace ametsuchi } // namespace iroha diff --git a/test/module/irohad/ametsuchi/kv_storage_test.cpp b/test/module/irohad/ametsuchi/kv_storage_test.cpp index 9a52a1240f..b85e071dea 100644 --- a/test/module/irohad/ametsuchi/kv_storage_test.cpp +++ b/test/module/irohad/ametsuchi/kv_storage_test.cpp @@ -38,7 +38,7 @@ class KVTest : public AmetsuchiTest { protected: void SetUp() override { AmetsuchiTest::SetUp(); - auto storageResult = StorageImpl::create(block_store_path, pgopt_); + auto storageResult = StorageImpl::create(block_store_path, pgopt_, factory); storageResult.match( [&](iroha::expected::Value> &_storage) { storage = _storage.value; diff --git a/test/module/irohad/ametsuchi/mutable_storage_test.cpp b/test/module/irohad/ametsuchi/mutable_storage_test.cpp new file mode 100644 index 0000000000..d7645e4da1 --- /dev/null +++ b/test/module/irohad/ametsuchi/mutable_storage_test.cpp @@ -0,0 +1,74 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "module/irohad/ametsuchi/ametsuchi_fixture.hpp" +#include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" +#include "module/shared_model/builders/protobuf/test_block_builder.hpp" + +using namespace iroha::ametsuchi; +using testing::Bool; +using testing::Values; +using testing::WithParamInterface; + +class MutableStorageTest : public AmetsuchiTest, + public WithParamInterface { + protected: + void SetUp() override { + AmetsuchiTest::SetUp(); + + storage->createMutableStorage().match( + [this](iroha::expected::Value> + &mut_storage) { + mutable_storage_ = std::move(mut_storage.value); + }, + [](const auto &) { FAIL() << "Mutable storage cannot be created"; }); + } + + void TearDown() override { + mutable_storage_.reset(); + AmetsuchiTest::TearDown(); + }; + + shared_model::interface::BlockVariant getBlock() { + return std::make_shared( + TestBlockBuilder() + .transactions(std::vector({})) + .height(1) + .prevHash(fake_hash) + .build()); + } + + std::string zero_string{32, '0'}; + shared_model::crypto::Hash fake_hash{zero_string}; + shared_model::crypto::PublicKey fake_pubkey{zero_string}; + std::unique_ptr mutable_storage_; +}; + +/** + * @given mutable storage + * @when check block method takes block and predicated returning boolean value + * @then the same block is processed in predicate + * @and returned value returned by check function is the same as result of + * predicate + */ +TEST_P(MutableStorageTest, TestCheckBlock) { + auto expected_block = getBlock(); + bool expected_res = GetParam(); + ASSERT_EQ(expected_res, + mutable_storage_->check( + expected_block, + [&expected_block, &expected_res]( + const auto &block, const auto &, const auto &) { + EXPECT_EQ(expected_block, block); + return expected_res; + })); +} + +INSTANTIATE_TEST_CASE_P(MutableStorageParameterizedTest, + MutableStorageTest, + // note additional comma is needed to make it compile + // https://github.com/google/googletest/issues/1419 + Bool(), ); diff --git a/test/module/irohad/ametsuchi/postgres_executor_test.cpp b/test/module/irohad/ametsuchi/postgres_executor_test.cpp new file mode 100644 index 0000000000..7cff9aba81 --- /dev/null +++ b/test/module/irohad/ametsuchi/postgres_executor_test.cpp @@ -0,0 +1,868 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "ametsuchi/impl/postgres_command_executor.hpp" +#include "ametsuchi/impl/postgres_wsv_query.hpp" +#include "framework/result_fixture.hpp" +#include "module/irohad/ametsuchi/ametsuchi_fixture.hpp" +#include "module/shared_model/builders/protobuf/test_account_builder.hpp" +#include "module/shared_model/builders/protobuf/test_asset_builder.hpp" +#include "module/shared_model/builders/protobuf/test_domain_builder.hpp" +#include "module/shared_model/builders/protobuf/test_peer_builder.hpp" +#include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" + +namespace iroha { + namespace ametsuchi { + + using namespace framework::expected; + + class CommandExecutorTest : public AmetsuchiTest { + public: + CommandExecutorTest() { + domain = clone( + TestDomainBuilder().domainId("domain").defaultRole(role).build()); + + account = clone(TestAccountBuilder() + .domainId(domain->domainId()) + .accountId("id@" + domain->domainId()) + .quorum(1) + .jsonData(R"({"id@domain": {"key": "value"}})") + .build()); + role_permissions.set( + shared_model::interface::permissions::Role::kAddMySignatory); + grantable_permission = + shared_model::interface::permissions::Grantable::kAddMySignatory; + pubkey = std::make_unique( + std::string('1', 32)); + } + + void SetUp() override { + AmetsuchiTest::SetUp(); + sql = std::make_unique(soci::postgresql, pgopt_); + + auto factory = + std::make_shared>(); + query = std::make_unique(*sql, factory); + executor = std::make_unique(*sql); + + *sql << init_; + } + + CommandResult execute( + const std::unique_ptr &command, + const shared_model::interface::types::AccountIdType &creator = + "id@domain") { + executor->setCreatorAccountId(creator); + return boost::apply_visitor(*executor, command->get()); + } + + // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework function with + // CommandBuilder + /** + * Hepler function to build command and wrap it into + * std::unique_ptr<> + * @param builder command builder + * @return command + */ + std::unique_ptr buildCommand( + const TestTransactionBuilder &builder) { + return clone(builder.build().commands().front()); + } + + std::string role = "role"; + shared_model::interface::RolePermissionSet role_permissions; + shared_model::interface::permissions::Grantable grantable_permission; + std::unique_ptr account; + std::unique_ptr domain; + std::unique_ptr pubkey; + + std::unique_ptr sql; + + std::unique_ptr command; + + std::unique_ptr query; + std::unique_ptr executor; + }; + + class AddAccountAssetTest : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", domain->domainId(), *pubkey))))); + } + /** + * Add default asset and check that it is done + */ + void addAsset() { + auto asset = clone(TestAccountAssetBuilder() + .domainId(domain->domainId()) + .assetId(asset_id) + .precision(1) + .build()); + + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAsset( + "coin", domain->domainId(), 1))))); + } + + shared_model::interface::types::AssetIdType asset_id = + "coin#" + domain->domainId(); + }; + + /** + * @given command + * @when trying to add account asset + * @then account asset is successfully added + */ + TEST_F(AddAccountAssetTest, ValidAddAccountAssetTest) { + addAsset(); + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, "1.0") + .creatorAccountId(account->accountId()))))); + auto account_asset = + query->getAccountAsset(account->accountId(), asset_id); + ASSERT_TRUE(account_asset); + ASSERT_EQ("1.0", account_asset.get()->balance().toStringRepr()); + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, "1.0") + .creatorAccountId(account->accountId()))))); + account_asset = query->getAccountAsset(account->accountId(), asset_id); + ASSERT_TRUE(account_asset); + ASSERT_EQ("2.0", account_asset.get()->balance().toStringRepr()); + } + + /** + * @given command + * @when trying to add account asset with non-existing asset + * @then account asset fails to be added + */ + TEST_F(AddAccountAssetTest, AddAccountAssetTestInvalidAsset) { + ASSERT_TRUE(err( + execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, "1.0") + .creatorAccountId(account->accountId()))))); + } + + /** + * @given command + * @when trying to add account asset with non-existing account + * @then account asset fails to added + */ + TEST_F(AddAccountAssetTest, AddAccountAssetTestInvalidAccount) { + addAsset(); + ASSERT_TRUE( + err(execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, "1.0") + .creatorAccountId("some@domain")), + "some@domain"))); + } + + /** + * @given command + * @when trying to add account asset that overflows + * @then account asset fails to added + */ + TEST_F(AddAccountAssetTest, AddAccountAssetTestUint256Overflow) { + std::string uint256_halfmax = + "57896044618658097711785492504343953926634992332820282019728792003956" + "5648" + "19966.0"; // 2**255 - 2tra + addAsset(); + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, uint256_halfmax) + .creatorAccountId(account->accountId()))))); + ASSERT_TRUE(err( + execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, uint256_halfmax) + .creatorAccountId(account->accountId()))))); + } + + class AddPeer : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + peer = clone(TestPeerBuilder().build()); + } + std::unique_ptr peer; + }; + + /** + * @given command + * @when trying to add peer + * @then peer is successfully added + */ + TEST_F(AddPeer, ValidAddPeerTest) { + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().addPeer(peer->address(), peer->pubkey()))))); + } + + class AddSignatory : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", + domain->domainId(), + shared_model::interface::types::PubkeyType( + std::string('5', 32))))))); + } + }; + + /** + * @given command + * @when trying to add signatory + * @then signatory is successfully added + */ + TEST_F(AddSignatory, ValidAddSignatoryTest) { + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().addSignatory( + account->accountId(), *pubkey))))); + auto signatories = query->getSignatories(account->accountId()); + ASSERT_TRUE(signatories); + ASSERT_TRUE(std::find(signatories->begin(), signatories->end(), *pubkey) + != signatories->end()); + } + + class AppendRole : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole("role2", role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", domain->domainId(), *pubkey))))); + } + }; + + /** + * @given command + * @when trying to append role + * @then role is successfully appended + */ + TEST_F(AppendRole, ValidAppendRoleTest) { + ASSERT_TRUE(val(execute(buildCommand(TestTransactionBuilder().appendRole( + account->accountId(), "role2"))))); + auto roles = query->getAccountRoles(account->accountId()); + ASSERT_TRUE(roles); + ASSERT_TRUE(std::find(roles->begin(), roles->end(), "role2") + != roles->end()); + } + + class CreateAccount : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + account = clone(TestAccountBuilder() + .domainId(domain->domainId()) + .accountId("id@" + domain->domainId()) + .quorum(1) + .jsonData("{}") + .build()); + } + }; + + /** + * @given command and no target domain in ledger + * @when trying to create account + * @then account is not created + */ + TEST_F(CreateAccount, InvalidCreateAccountNoDomainTest) { + ASSERT_TRUE( + err(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", domain->domainId(), *pubkey))))); + } + + /** + * @given command ] + * @when trying to create account + * @then account is created + */ + TEST_F(CreateAccount, ValidCreateAccountWithDomainTest) { + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", domain->domainId(), *pubkey))))); + auto acc = query->getAccount(account->accountId()); + ASSERT_TRUE(acc); + ASSERT_EQ(*account.get(), *acc.get()); + } + + class CreateAsset : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + } + shared_model::interface::types::AssetIdType asset_name = "coin"; + shared_model::interface::types::AssetIdType asset_id = + "coin#" + domain->domainId(); + }; + + /** + * @given command and no target domain in ledger + * @when trying to create asset + * @then asset is not created + */ + TEST_F(CreateAsset, InvalidCreateAssetNoDomainTest) { + ASSERT_TRUE(err(execute(buildCommand(TestTransactionBuilder().createAsset( + asset_name, domain->domainId(), 1))))); + } + + /** + * @given command ] + * @when trying to create asset + * @then asset is created + */ + TEST_F(CreateAsset, ValidCreateAssetWithDomainTest) { + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + auto asset = clone(TestAccountAssetBuilder() + .domainId(domain->domainId()) + .assetId(asset_id) + .precision(1) + .build()); + ASSERT_TRUE(val(execute(buildCommand(TestTransactionBuilder().createAsset( + "coin", domain->domainId(), 1))))); + auto ass = query->getAsset(asset->assetId()); + ASSERT_TRUE(ass); + ASSERT_EQ(*asset.get(), *ass.get()); + } + + class CreateDomain : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + } + }; + + /** + * @given command when there is no role + * @when trying to create domain + * @then domain is not created + */ + TEST_F(CreateDomain, InvalidCreateDomainWhenNoRoleTest) { + ASSERT_TRUE(err(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + } + + /** + * @given command when there is no role + * @when trying to create domain + * @then domain is not created + */ + TEST_F(CreateDomain, ValidCreateDomainTest) { + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + auto dom = query->getDomain(domain->domainId()); + ASSERT_TRUE(dom); + ASSERT_EQ(*dom.get(), *domain.get()); + } + + class CreateRole : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + } + }; + + /** + * @given command + * @when trying to create role + * @then role is created + */ + TEST_F(CreateRole, ValidCreateRoleTest) { + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + auto rl = query->getRolePermissions(role); + ASSERT_TRUE(rl); + ASSERT_EQ(rl.get(), role_permissions); + } + + class DetachRole : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole("role2", role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", domain->domainId(), *pubkey))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().appendRole( + account->accountId(), "role2"))))); + } + }; + + /** + * @given command + * @when trying to detach role + * @then role is detached + */ + TEST_F(DetachRole, ValidDetachRoleTest) { + ASSERT_TRUE(val(execute(buildCommand(TestTransactionBuilder().detachRole( + account->accountId(), "role2"))))); + auto roles = query->getAccountRoles(account->accountId()); + ASSERT_TRUE(roles); + ASSERT_TRUE(std::find(roles->begin(), roles->end(), "role2") + == roles->end()); + } + + class GrantPermission : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole("role2", role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", domain->domainId(), *pubkey))))); + } + }; + + /** + * @given command + * @when trying to grant permission + * @then permission is granted + */ + TEST_F(GrantPermission, ValidGrantPermissionTest) { + auto perm = shared_model::interface::permissions::Grantable::kSetMyQuorum; + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .grantPermission(account->accountId(), perm) + .creatorAccountId(account->accountId()))))); + auto has_perm = query->hasAccountGrantablePermission( + account->accountId(), account->accountId(), perm); + ASSERT_TRUE(has_perm); + } + + class RemoveSignatory : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + pubkey = std::make_unique( + std::string('1', 32)); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", domain->domainId(), *pubkey))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().addSignatory( + account->accountId(), + shared_model::interface::types::PubkeyType( + std::string('5', 32))))))); + } + std::unique_ptr pubkey; + }; + + /** + * @given command + * @when trying to remove signatory + * @then signatory is successfully removed + */ + TEST_F(RemoveSignatory, ValidRemoveSignatoryTest) { + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().removeSignatory( + account->accountId(), *pubkey))))); + auto signatories = query->getSignatories(account->accountId()); + ASSERT_TRUE(signatories); + ASSERT_TRUE(std::find(signatories->begin(), signatories->end(), *pubkey) + == signatories->end()); + } + + class RevokePermission : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", domain->domainId(), *pubkey))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder() + .grantPermission(account->accountId(), grantable_permission) + .creatorAccountId(account->accountId()))))); + } + }; + + /** + * @given command + * @when trying to revoke permission + * @then permission is revoked + */ + TEST_F(RevokePermission, ValidRevokePermissionTest) { + auto perm = + shared_model::interface::permissions::Grantable::kRemoveMySignatory; + ASSERT_TRUE(query->hasAccountGrantablePermission( + account->accountId(), account->accountId(), grantable_permission)); + + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .grantPermission(account->accountId(), perm) + .creatorAccountId(account->accountId()))))); + ASSERT_TRUE(query->hasAccountGrantablePermission( + account->accountId(), account->accountId(), grantable_permission)); + ASSERT_TRUE(query->hasAccountGrantablePermission( + account->accountId(), account->accountId(), perm)); + + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder() + .revokePermission(account->accountId(), grantable_permission) + .creatorAccountId(account->accountId()))))); + ASSERT_FALSE(query->hasAccountGrantablePermission( + account->accountId(), account->accountId(), grantable_permission)); + ASSERT_TRUE(query->hasAccountGrantablePermission( + account->accountId(), account->accountId(), perm)); + } + + class SetAccountDetail : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", domain->domainId(), *pubkey))))); + } + }; + + /** + * @given command + * @when trying to set kv + * @then kv is set + */ + TEST_F(SetAccountDetail, ValidSetAccountDetailTest) { + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder() + .setAccountDetail(account->accountId(), "key", "value") + .creatorAccountId(account->accountId()))))); + auto kv = query->getAccountDetail(account->accountId()); + ASSERT_TRUE(kv); + ASSERT_EQ(kv.get(), "{\"id@domain\": {\"key\": \"value\"}}"); + } + + class SetQuorum : public CommandExecutorTest { + public: + void SetUp() override { + CommandExecutorTest::SetUp(); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", domain->domainId(), *pubkey))))); + } + }; + + /** + * @given command + * @when trying to set kv + * @then kv is set + */ + TEST_F(SetQuorum, ValidSetQuorumTest) { + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().setAccountQuorum( + account->accountId(), 3))))); + } + + class SubtractAccountAssetTest : public CommandExecutorTest { + void SetUp() override { + CommandExecutorTest::SetUp(); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", domain->domainId(), *pubkey))))); + } + + public: + /** + * Add default asset and check that it is done + */ + void addAsset() { + auto asset = clone(TestAccountAssetBuilder() + .domainId(domain->domainId()) + .assetId(asset_id) + .precision(1) + .build()); + + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAsset( + "coin", domain->domainId(), 1))))); + } + + shared_model::interface::types::AssetIdType asset_id = + "coin#" + domain->domainId(); + }; + + /** + * @given command + * @when trying to subtract account asset + * @then account asset is successfully subtracted + */ + TEST_F(SubtractAccountAssetTest, ValidSubtractAccountAssetTest) { + addAsset(); + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, "1.0") + .creatorAccountId(account->accountId()))))); + auto account_asset = + query->getAccountAsset(account->accountId(), asset_id); + ASSERT_TRUE(account_asset); + ASSERT_EQ("1.0", account_asset.get()->balance().toStringRepr()); + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, "1.0") + .creatorAccountId(account->accountId()))))); + account_asset = query->getAccountAsset(account->accountId(), asset_id); + ASSERT_TRUE(account_asset); + ASSERT_EQ("2.0", account_asset.get()->balance().toStringRepr()); + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .subtractAssetQuantity(asset_id, "1.0") + .creatorAccountId(account->accountId()))))); + account_asset = query->getAccountAsset(account->accountId(), asset_id); + ASSERT_TRUE(account_asset); + ASSERT_EQ("1.0", account_asset.get()->balance().toStringRepr()); + } + + /** + * @given command + * @when trying to subtract account asset with non-existing asset + * @then account asset fails to be subtracted + */ + TEST_F(SubtractAccountAssetTest, SubtractAccountAssetTestInvalidAsset) { + ASSERT_TRUE(err( + execute(buildCommand(TestTransactionBuilder() + .subtractAssetQuantity(asset_id, "1.0") + .creatorAccountId(account->accountId()))))); + } + + /** + * @given command + * @when trying to add account subtract with non-existing account + * @then account asset fails to subtracted + */ + TEST_F(SubtractAccountAssetTest, SubtractAccountAssetTestInvalidAccount) { + addAsset(); + ASSERT_TRUE( + err(execute(buildCommand(TestTransactionBuilder() + .subtractAssetQuantity(asset_id, "1.0") + .creatorAccountId("some@domain"))))); + } + + /** + * @given command + * @when trying to add account asset with wrong precision + * @then account asset fails to added + */ + TEST_F(SubtractAccountAssetTest, SubtractAccountAssetTestInvalidPrecision) { + addAsset(); + ASSERT_TRUE(err( + execute(buildCommand(TestTransactionBuilder() + .subtractAssetQuantity(asset_id, "1.0000") + .creatorAccountId(account->accountId()))))); + } + + /** + * @given command + * @when trying to add account asset that overflows + * @then account asset fails to added + */ + TEST_F(SubtractAccountAssetTest, SubtractAccountAssetTestUint256Overflow) { + addAsset(); + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, "1.0") + .creatorAccountId(account->accountId()))))); + ASSERT_TRUE(err( + execute(buildCommand(TestTransactionBuilder() + .subtractAssetQuantity(asset_id, "2.0") + .creatorAccountId(account->accountId()))))); + } + + class TransferAccountAssetTest : public CommandExecutorTest { + void SetUp() override { + CommandExecutorTest::SetUp(); + + account2 = clone(TestAccountBuilder() + .domainId(domain->domainId()) + .accountId("id2@" + domain->domainId()) + .quorum(1) + .jsonData("{}") + .build()); + + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createRole(role, role_permissions))))); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().createDomain(domain->domainId(), role))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id", domain->domainId(), *pubkey))))); + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAccount( + "id2", domain->domainId(), *pubkey))))); + } + + public: + /** + * Add default asset and check that it is done + */ + void addAsset() { + auto asset = clone(TestAccountAssetBuilder() + .domainId(domain->domainId()) + .assetId(asset_id) + .precision(1) + .build()); + + ASSERT_TRUE( + val(execute(buildCommand(TestTransactionBuilder().createAsset( + "coin", domain->domainId(), 1))))); + } + + shared_model::interface::types::AssetIdType asset_id = + "coin#" + domain->domainId(); + std::unique_ptr account2; + }; + + /** + * @given command + * @when trying to add transfer asset + * @then account asset is successfully transfered + */ + TEST_F(TransferAccountAssetTest, ValidTransferAccountAssetTest) { + addAsset(); + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, "1.0") + .creatorAccountId(account->accountId()))))); + auto account_asset = + query->getAccountAsset(account->accountId(), asset_id); + ASSERT_TRUE(account_asset); + ASSERT_EQ("1.0", account_asset.get()->balance().toStringRepr()); + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, "1.0") + .creatorAccountId(account->accountId()))))); + account_asset = query->getAccountAsset(account->accountId(), asset_id); + ASSERT_TRUE(account_asset); + ASSERT_EQ("2.0", account_asset.get()->balance().toStringRepr()); + ASSERT_TRUE(val(execute(buildCommand( + TestTransactionBuilder().transferAsset(account->accountId(), + account2->accountId(), + asset_id, + "desc", + "1.0"))))); + account_asset = query->getAccountAsset(account->accountId(), asset_id); + ASSERT_TRUE(account_asset); + ASSERT_EQ("1.0", account_asset.get()->balance().toStringRepr()); + account_asset = query->getAccountAsset(account2->accountId(), asset_id); + ASSERT_TRUE(account_asset); + ASSERT_EQ("1.0", account_asset.get()->balance().toStringRepr()); + } + + /** + * @given command + * @when trying to transfer account asset with non-existing asset + * @then account asset fails to be transfered + */ + TEST_F(TransferAccountAssetTest, TransferAccountAssetTestInvalidAsset) { + ASSERT_TRUE(err(execute(buildCommand( + TestTransactionBuilder().transferAsset(account->accountId(), + account2->accountId(), + asset_id, + "desc", + "1.0"))))); + } + + /** + * @given command + * @when trying to transfer account asset with non-existing account + * @then account asset fails to transfered + */ + TEST_F(TransferAccountAssetTest, TransferAccountAssetTestInvalidAccount) { + addAsset(); + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, "1.0") + .creatorAccountId(account->accountId()))))); + ASSERT_TRUE( + err(execute(buildCommand(TestTransactionBuilder().transferAsset( + account->accountId(), "some@domain", asset_id, "desc", "1.0"))))); + ASSERT_TRUE(err(execute(buildCommand( + TestTransactionBuilder().transferAsset("some@domain", + account2->accountId(), + asset_id, + "desc", + "1.0"))))); + } + + /** + * @given command + * @when trying to transfer account asset that overflows + * @then account asset fails to transfered + */ + TEST_F(TransferAccountAssetTest, TransferAccountAssetOwerdraftTest) { + addAsset(); + ASSERT_TRUE(val( + execute(buildCommand(TestTransactionBuilder() + .addAssetQuantity(asset_id, "1.0") + .creatorAccountId(account->accountId()))))); + ASSERT_TRUE(err(execute(buildCommand( + TestTransactionBuilder().transferAsset(account->accountId(), + account2->accountId(), + asset_id, + "desc", + "2.0"))))); + } + + } // namespace ametsuchi +} // namespace iroha diff --git a/test/module/irohad/ametsuchi/storage_init_test.cpp b/test/module/irohad/ametsuchi/storage_init_test.cpp index fe3a3e40e1..a978bc0c3f 100644 --- a/test/module/irohad/ametsuchi/storage_init_test.cpp +++ b/test/module/irohad/ametsuchi/storage_init_test.cpp @@ -4,11 +4,15 @@ */ #include +#include +#include #include #include #include #include "ametsuchi/impl/storage_impl.hpp" +#include "backend/protobuf/common_objects/proto_common_objects_factory.hpp" #include "framework/config_helper.hpp" +#include "validators/field_validator.hpp" using namespace iroha::ametsuchi; using namespace iroha::expected; @@ -32,11 +36,15 @@ class StorageInitTest : public ::testing::Test { std::string pg_opt_without_dbname_; std::string pgopt_; + std::shared_ptr> + factory = std::make_shared>(); + void TearDown() override { - auto temp_connection = - std::make_unique(pg_opt_without_dbname_); - auto nontx = std::make_unique(*temp_connection); - nontx->exec("DROP DATABASE IF EXISTS " + dbname_); + soci::session sql(soci::postgresql, pg_opt_without_dbname_); + std::string query = "DROP DATABASE IF EXISTS " + dbname_; + sql << query; } }; @@ -46,16 +54,15 @@ class StorageInitTest : public ::testing::Test { * @then Database is created */ TEST_F(StorageInitTest, CreateStorageWithDatabase) { - StorageImpl::create(block_store_path, pgopt_) + StorageImpl::create(block_store_path, pgopt_, factory) .match([](const Value> &) { SUCCEED(); }, [](const Error &error) { FAIL() << error.error; }); - auto temp_connection = - std::make_unique(pg_opt_without_dbname_); - auto transaction = std::make_unique(*temp_connection); - auto result = transaction->exec( - "SELECT datname FROM pg_catalog.pg_database WHERE datname = " - + transaction->quote(dbname_)); - ASSERT_EQ(result.size(), 1); + soci::session sql(soci::postgresql, pg_opt_without_dbname_); + int size; + sql << "SELECT COUNT(datname) FROM pg_catalog.pg_database WHERE datname = " + ":dbname", + soci::into(size), soci::use(dbname_); + ASSERT_EQ(size, 1); } /** @@ -66,7 +73,7 @@ TEST_F(StorageInitTest, CreateStorageWithDatabase) { TEST_F(StorageInitTest, CreateStorageWithInvalidPgOpt) { std::string pg_opt = "host=localhost port=5432 users=nonexistinguser dbname=test"; - StorageImpl::create(block_store_path, pg_opt) + StorageImpl::create(block_store_path, pg_opt, factory) .match( [](const Value> &) { FAIL() << "storage created, but should not"; diff --git a/test/module/irohad/ametsuchi/wsv_query_command_test.cpp b/test/module/irohad/ametsuchi/wsv_query_command_test.cpp index 654060cdb0..a93adc3bda 100644 --- a/test/module/irohad/ametsuchi/wsv_query_command_test.cpp +++ b/test/module/irohad/ametsuchi/wsv_query_command_test.cpp @@ -20,6 +20,7 @@ #include "framework/result_fixture.hpp" #include "module/irohad/ametsuchi/ametsuchi_fixture.hpp" #include "module/shared_model/builders/protobuf/test_account_builder.hpp" +#include "module/shared_model/builders/protobuf/test_asset_builder.hpp" #include "module/shared_model/builders/protobuf/test_domain_builder.hpp" #include "module/shared_model/builders/protobuf/test_peer_builder.hpp" @@ -48,19 +49,12 @@ namespace iroha { void SetUp() override { AmetsuchiTest::SetUp(); - postgres_connection = std::make_unique(pgopt_); - try { - postgres_connection->activate(); - } catch (const pqxx::broken_connection &e) { - FAIL() << "Connection to PostgreSQL broken: " << e.what(); - } - wsv_transaction = - std::make_unique(*postgres_connection); - - command = std::make_unique(*wsv_transaction); - query = std::make_unique(*wsv_transaction); - - wsv_transaction->exec(init_); + sql = std::make_unique(soci::postgresql, pgopt_); + + command = std::make_unique(*sql); + query = std::make_unique(*sql, factory); + + *sql << init_; } std::string role = "role"; @@ -69,8 +63,7 @@ namespace iroha { std::unique_ptr account; std::unique_ptr domain; - std::unique_ptr postgres_connection; - std::unique_ptr wsv_transaction; + std::unique_ptr sql; std::unique_ptr command; std::unique_ptr query; @@ -104,6 +97,11 @@ namespace iroha { ASSERT_EQ(0, roles->size()); } + TEST_F(RoleTest, InsertTwoRole) { + ASSERT_TRUE(val(command->insertRole("role"))); + ASSERT_TRUE(err(command->insertRole("role"))); + } + class RolePermissionsTest : public WsvQueryCommandTest { void SetUp() override { WsvQueryCommandTest::SetUp(); @@ -121,7 +119,7 @@ namespace iroha { auto permissions = query->getRolePermissions(role); ASSERT_TRUE(permissions); - ASSERT_EQ(role_permissions, permissions); + ASSERT_EQ(role_permissions, permissions.get()); } /** @@ -206,7 +204,7 @@ namespace iroha { /** * @given inserted role, domain, account - * @when update json data in account + * @when update json data in account * @then get account and check json data is the same */ TEST_F(AccountTest, UpdateAccountJSONData) { @@ -233,7 +231,96 @@ namespace iroha { * @then getAccountDetail will return nullopt */ TEST_F(AccountTest, GetAccountDetailInvalidWhenNotFound) { - EXPECT_FALSE(query->getAccountDetail("invalid account id")); + EXPECT_FALSE(query->getAccountDetail("invalid account id", "", "")); + } + + /** + * @given details, inserted for one account + * @when performing query to retrieve all account's details + * @then getAccountDetail will return all details of this account + */ + TEST_F(AccountTest, GetAccountDetailWithAccount) { + ASSERT_TRUE(val(command->insertAccount(*account))); + ASSERT_TRUE(val(command->setAccountKV( + account->accountId(), account->accountId(), "some_key", "some_val"))); + + auto acc_details = query->getAccountDetail(account->accountId(), "", ""); + ASSERT_TRUE(acc_details); + ASSERT_EQ(R"({"id@domain": {"key": "value", "some_key": "some_val"}})", + *acc_details); + } + + /** + * @given details, inserted into one account by two writers, with one of the + * keys repeated + * @when performing query to retrieve details under this key + * @then getAccountDetail will return details from both writers under the + * specified key + */ + TEST_F(AccountTest, GetAccountDetailWithKey) { + ASSERT_TRUE(val(command->insertAccount(*account))); + ASSERT_TRUE(val(command->setAccountKV( + account->accountId(), account->accountId(), "some_key", "some_val"))); + ASSERT_TRUE(val(command->setAccountKV(account->accountId(), + account->accountId(), + "another_key", + "another_val"))); + ASSERT_TRUE(val(command->setAccountKV( + account->accountId(), "admin", "some_key", "even_third_val"))); + + auto acc_details = + query->getAccountDetail(account->accountId(), "some_key", ""); + ASSERT_TRUE(acc_details); + ASSERT_EQ( + "{ \"admin\" : {\"some_key\" : \"even_third_val\"}, " + "\"id@domain\" : {\"some_key\" : \"some_val\"} }", + *acc_details); + } + + /** + * @given details, inserted into one account by two writers + * @when performing query to retrieve details, added by one of the writers + * @then getAccountDetail will return only details, added by the specified + * writer + */ + TEST_F(AccountTest, GetAccountDetailWithWriter) { + ASSERT_TRUE(val(command->insertAccount(*account))); + ASSERT_TRUE(val(command->setAccountKV( + account->accountId(), account->accountId(), "some_key", "some_val"))); + ASSERT_TRUE(val(command->setAccountKV( + account->accountId(), "admin", "another_key", "another_val"))); + + auto acc_details = + query->getAccountDetail(account->accountId(), "", "admin"); + ASSERT_TRUE(acc_details); + ASSERT_EQ(R"({"admin" : {"another_key": "another_val"}})", + *acc_details); + } + + /** + * @given details, inserted into one account by two writers, with one of the + * keys repeated + * @when performing query to retrieve details under this key and added by + * one of the writers + * @then getAccountDetail will return only details, which are under the + * specified key and added by the specified writer + */ + TEST_F(AccountTest, GetAccountDetailWithKeyAndWriter) { + ASSERT_TRUE(val(command->insertAccount(*account))); + ASSERT_TRUE(val(command->setAccountKV( + account->accountId(), account->accountId(), "some_key", "some_val"))); + ASSERT_TRUE(val(command->setAccountKV(account->accountId(), + account->accountId(), + "another_key", + "another_val"))); + ASSERT_TRUE(val(command->setAccountKV( + account->accountId(), "admin", "some_key", "even_third_val"))); + + auto acc_details = query->getAccountDetail( + account->accountId(), "some_key", account->accountId()); + ASSERT_TRUE(acc_details); + ASSERT_EQ(R"({"id@domain" : {"some_key" : "some_val"}})", + *acc_details); } class AccountRoleTest : public WsvQueryCommandTest { @@ -459,20 +546,15 @@ namespace iroha { // skip database setup void SetUp() override { AmetsuchiTest::SetUp(); - postgres_connection = std::make_unique(pgopt_); - try { - postgres_connection->activate(); - } catch (const pqxx::broken_connection &e) { - FAIL() << "Connection to PostgreSQL broken: " << e.what(); - } - wsv_transaction = - std::make_unique(*postgres_connection); - - command = std::make_unique(*wsv_transaction); - query = std::make_unique(*wsv_transaction); + sql = std::make_unique(soci::postgresql, pgopt_); + + command = std::make_unique(*sql); + query = std::make_unique(*sql, factory); } }; + std::unique_ptr sql; + /** * @given not set up database * @when performing query to retrieve information from nonexisting tables diff --git a/test/module/irohad/consensus/yac/CMakeLists.txt b/test/module/irohad/consensus/yac/CMakeLists.txt index 3cf40ea7c0..811f962b4e 100644 --- a/test/module/irohad/consensus/yac/CMakeLists.txt +++ b/test/module/irohad/consensus/yac/CMakeLists.txt @@ -84,6 +84,7 @@ addtest(yac_hash_provider_test yac_hash_provider_test.cpp) target_link_libraries(yac_hash_provider_test yac shared_model_cryptography + shared_model_stateless_validation ) addtest(yac_common_test yac_common_test.cpp) diff --git a/test/module/irohad/execution/CMakeLists.txt b/test/module/irohad/execution/CMakeLists.txt index 3068bc81a7..9b524bd031 100644 --- a/test/module/irohad/execution/CMakeLists.txt +++ b/test/module/irohad/execution/CMakeLists.txt @@ -17,3 +17,10 @@ target_link_libraries(command_validate_execute_test command_execution shared_model_stateless_validation ) + +addtest(query_execution_test query_execution_test.cpp) +target_link_libraries(query_execution_test + query_execution + shared_model_interfaces + shared_model_stateless_validation + ) diff --git a/test/module/irohad/execution/command_validate_execute_test.cpp b/test/module/irohad/execution/command_validate_execute_test.cpp index efc0f77abc..e2355549e2 100644 --- a/test/module/irohad/execution/command_validate_execute_test.cpp +++ b/test/module/irohad/execution/command_validate_execute_test.cpp @@ -55,8 +55,6 @@ class CommandValidateExecuteTest : public ::testing::Test { void SetUp() override { wsv_query = std::make_shared>(); wsv_command = std::make_shared>(); - - executor = std::make_unique(wsv_query, wsv_command); validator = std::make_unique(wsv_query); shared_model::builder::AccountBuilder< @@ -105,27 +103,12 @@ class CommandValidateExecuteTest : public ::testing::Test { FAIL() << *e.error; }); - shared_model::builder::AmountBuilder< - shared_model::proto::AmountBuilder, - shared_model::validation::FieldValidator>() - .intValue(150) - .precision(2) - .build() - .match( - [&](expected::Value< - std::shared_ptr> &v) { - balance = v.value; - }, - [](expected::Error> &e) { - FAIL() << *e.error; - }); - shared_model::builder::AccountAssetBuilder< shared_model::proto::AccountAssetBuilder, shared_model::validation::FieldValidator>() .assetId(kAssetId) .accountId(kAccountId) - .balance(*balance) + .balance(balance) .build() .match( [&](expected::Value< @@ -137,21 +120,10 @@ class CommandValidateExecuteTest : public ::testing::Test { }); } - iroha::ExecutionResult validateAndExecute( + iroha::CommandResult validate( const std::unique_ptr &command) { validator->setCreatorAccountId(creator->accountId()); - - if (boost::apply_visitor(*validator, command->get())) { - return execute(command); - } - return expected::makeError( - iroha::ExecutionError{"Validate", "validation of a command failed"}); - } - - iroha::ExecutionResult execute( - const std::unique_ptr &command) { - executor->setCreatorAccountId(creator->accountId()); - return boost::apply_visitor(*executor, command->get()); + return boost::apply_visitor(*validator, command->get()); } /// return result with empty error message @@ -160,9 +132,9 @@ class CommandValidateExecuteTest : public ::testing::Test { } /// Returns error from result or throws error in case result contains value - iroha::ExecutionResult::ErrorType checkErrorCase( - const iroha::ExecutionResult &result) { - return boost::get(result); + iroha::CommandResult::ErrorType checkErrorCase( + const iroha::CommandResult &result) { + return boost::get(result); } const std::string kMaxAmountStr = @@ -192,7 +164,8 @@ class CommandValidateExecuteTest : public ::testing::Test { shared_model::interface::RolePermissionSet role_permissions; std::shared_ptr creator, account; - std::shared_ptr balance; + shared_model::interface::Amount balance = + shared_model::interface::Amount("1.50"); std::shared_ptr asset; std::shared_ptr wallet; @@ -201,7 +174,6 @@ class CommandValidateExecuteTest : public ::testing::Test { std::shared_ptr wsv_query; std::shared_ptr wsv_command; - std::unique_ptr executor; std::unique_ptr validator; }; @@ -213,8 +185,8 @@ class AddAssetQuantityTest : public CommandValidateExecuteTest { role_permissions = {Role::kAddAssetQty}; // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().addAssetQuantity( - creator->accountId(), kAssetId, kAmount)); + command = buildCommand( + TestTransactionBuilder().addAssetQuantity(kAssetId, kAmount)); add_asset_quantity = getConcreteCommand(command); } @@ -228,20 +200,12 @@ class AddAssetQuantityTest : public CommandValidateExecuteTest { * @then executor will be passed */ TEST_F(AddAssetQuantityTest, ValidWhenNewWallet) { - EXPECT_CALL(*wsv_query, getAccountAsset(add_asset_quantity->accountId(), _)) - .WillOnce(Return(boost::none)); - EXPECT_CALL(*wsv_query, getAsset(add_asset_quantity->assetId())) - .WillOnce(Return(asset)); - EXPECT_CALL(*wsv_query, getAccount(add_asset_quantity->accountId())) - .WillOnce(Return(account)); - EXPECT_CALL(*wsv_command, upsertAccountAsset(_)) - .WillOnce(Return(WsvCommandResult())); EXPECT_CALL(*wsv_query, getAccountRoles(creator->accountId())) .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -250,20 +214,11 @@ TEST_F(AddAssetQuantityTest, ValidWhenNewWallet) { * @then executor will be passed */ TEST_F(AddAssetQuantityTest, ValidWhenExistingWallet) { - EXPECT_CALL(*wsv_query, - getAccountAsset(add_asset_quantity->accountId(), - add_asset_quantity->assetId())) - .WillOnce(Return(wallet)); - EXPECT_CALL(*wsv_query, getAsset(kAssetId)).WillOnce(Return(asset)); - EXPECT_CALL(*wsv_query, getAccount(add_asset_quantity->accountId())) - .WillOnce(Return(account)); - EXPECT_CALL(*wsv_command, upsertAccountAsset(_)) - .WillOnce(Return(WsvCommandResult())); - EXPECT_CALL(*wsv_query, getAccountRoles(add_asset_quantity->accountId())) + EXPECT_CALL(*wsv_query, getAccountRoles(creator->accountId())) .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -272,30 +227,9 @@ TEST_F(AddAssetQuantityTest, ValidWhenExistingWallet) { * @then executor will be failed */ TEST_F(AddAssetQuantityTest, InvalidWhenNoRoles) { - EXPECT_CALL(*wsv_query, getAccountRoles(add_asset_quantity->accountId())) + EXPECT_CALL(*wsv_query, getAccountRoles(creator->accountId())) .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given AddAssetQuantity with amount with wrong precision (must be 2) - * @when command is executed - * @then executor will be failed - */ -TEST_F(AddAssetQuantityTest, InvalidWhenWrongPrecision) { - // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - IR-1276 - rework with - // CommandBuilder - command = buildCommand(TestTransactionBuilder().addAssetQuantity( - creator->accountId(), kAssetId, kAmountWrongPrecision)); - add_asset_quantity = - getConcreteCommand(command); - - EXPECT_CALL(*wsv_query, getAccountRoles(kAdminId)) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, getAsset(kAssetId)).WillOnce(Return(asset)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -305,15 +239,12 @@ TEST_F(AddAssetQuantityTest, InvalidWhenWrongPrecision) { */ TEST_F(AddAssetQuantityTest, InvalidWhenNoAccount) { // Account to add does not exist - EXPECT_CALL(*wsv_query, getAccountRoles(add_asset_quantity->accountId())) + EXPECT_CALL(*wsv_query, getAccountRoles(creator->accountId())) .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, getAsset(kAssetId)).WillOnce(Return(asset)); - EXPECT_CALL(*wsv_query, getAccount(add_asset_quantity->accountId())) - .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -324,8 +255,8 @@ TEST_F(AddAssetQuantityTest, InvalidWhenNoAccount) { TEST_F(AddAssetQuantityTest, InvalidWhenNoAsset) { // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - IR-1276 - rework with // CommandBuilder - command = buildCommand(TestTransactionBuilder().addAssetQuantity( - creator->accountId(), kNoAssetId, kAmount)); + command = buildCommand( + TestTransactionBuilder().addAssetQuantity(kNoAssetId, kAmount)); add_asset_quantity = getConcreteCommand(command); @@ -334,10 +265,7 @@ TEST_F(AddAssetQuantityTest, InvalidWhenNoAsset) { EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, getAsset(add_asset_quantity->assetId())) - .WillOnce(Return(boost::none)); - - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -347,24 +275,17 @@ TEST_F(AddAssetQuantityTest, InvalidWhenNoAsset) { */ TEST_F(AddAssetQuantityTest, InvalidWhenAssetAdditionFails) { // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().addAssetQuantity( - creator->accountId(), kAssetId, kMaxAmountStr)); + command = buildCommand( + TestTransactionBuilder().addAssetQuantity(kAssetId, kMaxAmountStr)); add_asset_quantity = getConcreteCommand(command); - EXPECT_CALL(*wsv_query, - getAccountAsset(add_asset_quantity->accountId(), - add_asset_quantity->assetId())) - .WillOnce(Return(wallet)); - EXPECT_CALL(*wsv_query, getAsset(kAssetId)).WillOnce(Return(asset)); - EXPECT_CALL(*wsv_query, getAccount(add_asset_quantity->accountId())) - .WillOnce(Return(account)); - EXPECT_CALL(*wsv_query, getAccountRoles(add_asset_quantity->accountId())) + EXPECT_CALL(*wsv_query, getAccountRoles(creator->accountId())) .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } class SubtractAssetQuantityTest : public CommandValidateExecuteTest { @@ -375,8 +296,8 @@ class SubtractAssetQuantityTest : public CommandValidateExecuteTest { role_permissions = {Role::kSubtractAssetQty}; // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().subtractAssetQuantity( - creator->accountId(), kAssetId, kAmount)); + command = buildCommand( + TestTransactionBuilder().subtractAssetQuantity(kAssetId, kAmount)); subtract_asset_quantity = getConcreteCommand( command); @@ -386,23 +307,6 @@ class SubtractAssetQuantityTest : public CommandValidateExecuteTest { subtract_asset_quantity; }; -/** - * @given SubtractAssetQuantity - * @when account doesn't have wallet of target asset - * @then executor will be failed - */ -TEST_F(SubtractAssetQuantityTest, InvalidWhenNoWallet) { - EXPECT_CALL(*wsv_query, - getAccountAsset(subtract_asset_quantity->accountId(), - subtract_asset_quantity->assetId())) - .WillOnce(Return(boost::none)); - EXPECT_CALL(*wsv_query, getAccountRoles(subtract_asset_quantity->accountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, getAsset(kAssetId)).WillOnce(Return(asset)); - ASSERT_TRUE(err(validateAndExecute(command))); -} /** * @given SubtractAssetQuantity @@ -410,116 +314,11 @@ TEST_F(SubtractAssetQuantityTest, InvalidWhenNoWallet) { * @then executor will be passed */ TEST_F(SubtractAssetQuantityTest, ValidWhenExistingWallet) { - EXPECT_CALL(*wsv_query, - getAccountAsset(subtract_asset_quantity->accountId(), - subtract_asset_quantity->assetId())) - .WillOnce(Return(wallet)); - EXPECT_CALL(*wsv_query, getAsset(kAssetId)).WillOnce(Return(asset)); - EXPECT_CALL(*wsv_command, upsertAccountAsset(_)) - .WillOnce(Return(WsvCommandResult())); - EXPECT_CALL(*wsv_query, getAccountRoles(subtract_asset_quantity->accountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .WillOnce(Return(role_permissions)); - ASSERT_TRUE(val(validateAndExecute(command))); -} - -/** - * @given SubtractAssetQuantity - * @when arguments amount is greater than wallet's amount - * @then executor will be failed - */ -TEST_F(SubtractAssetQuantityTest, InvalidWhenOverAmount) { - // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().subtractAssetQuantity( - creator->accountId(), kAssetId, kAmountOverflow)); - subtract_asset_quantity = - getConcreteCommand( - command); - - EXPECT_CALL(*wsv_query, - getAccountAsset(subtract_asset_quantity->accountId(), - subtract_asset_quantity->assetId())) - .WillOnce(Return(wallet)); - - EXPECT_CALL(*wsv_query, getAccountRoles(subtract_asset_quantity->accountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, getAsset(kAssetId)).WillOnce(Return(asset)); - - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given SubtractAssetQuantity - * @when account doesn't have role - * @then executor will be failed - */ -TEST_F(SubtractAssetQuantityTest, InvalidWhenNoRoles) { - EXPECT_CALL(*wsv_query, getAccountRoles(subtract_asset_quantity->accountId())) - .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given SubtractAssetQuantity - * @when arguments amount precision is invalid (greater than 2) - * @then executor will be failed - */ -TEST_F(SubtractAssetQuantityTest, InvalidWhenWrongPrecision) { - // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().subtractAssetQuantity( - creator->accountId(), kAssetId, kAmountWrongPrecision)); - subtract_asset_quantity = - getConcreteCommand( - command); - - EXPECT_CALL(*wsv_query, getAccountRoles(kAdminId)) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, getAsset(kAssetId)).WillOnce(Return(asset)); - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given SubtractAssetQuantity - * @when account doesn't exist - * @then executor will be failed - */ -TEST_F(SubtractAssetQuantityTest, InvalidWhenNoAccount) { - // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().subtractAssetQuantity( - kNoAcountId, kAssetId, kAmount)); - subtract_asset_quantity = - getConcreteCommand( - command); - - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given SubtractAssetQuantity - * @when asset doesn't exist - * @then executor will be failed - */ -TEST_F(SubtractAssetQuantityTest, InvalidWhenNoAsset) { - // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().subtractAssetQuantity( - creator->accountId(), kNoAssetId, kAmount)); - subtract_asset_quantity = - getConcreteCommand( - command); - - EXPECT_CALL(*wsv_query, getAccountRoles(kAdminId)) + EXPECT_CALL(*wsv_query, getAccountRoles(creator->accountId())) .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, getAsset(subtract_asset_quantity->assetId())) - .WillOnce(Return(boost::none)); - - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } class AddSignatoryTest : public CommandValidateExecuteTest { @@ -550,13 +349,7 @@ TEST_F(AddSignatoryTest, ValidWhenCreatorHasPermissions) { hasAccountGrantablePermission( kAdminId, add_signatory->accountId(), Grantable::kAddMySignatory)) .WillOnce(Return(true)); - EXPECT_CALL(*wsv_command, insertSignatory(add_signatory->pubkey())) - .WillOnce(Return(WsvCommandResult())); - EXPECT_CALL(*wsv_command, - insertAccountSignatory(add_signatory->accountId(), - add_signatory->pubkey())) - .WillOnce(Return(WsvCommandResult())); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -575,14 +368,7 @@ TEST_F(AddSignatoryTest, ValidWhenSameAccount) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_command, insertSignatory(add_signatory->pubkey())) - .WillOnce(Return(WsvCommandResult())); - EXPECT_CALL(*wsv_command, - insertAccountSignatory(add_signatory->accountId(), - add_signatory->pubkey())) - .WillOnce(Return(WsvCommandResult())); - - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -597,7 +383,7 @@ TEST_F(AddSignatoryTest, InvalidWhenNoPermissions) { kAdminId, add_signatory->accountId(), Grantable::kAddMySignatory)) .WillOnce(Return(false)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -618,7 +404,7 @@ TEST_F(AddSignatoryTest, InvalidWhenNoAccount) { kAdminId, add_signatory->accountId(), Grantable::kAddMySignatory)) .WillOnce(Return(false)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -638,10 +424,8 @@ TEST_F(AddSignatoryTest, InvalidWhenSameKey) { hasAccountGrantablePermission( kAdminId, add_signatory->accountId(), Grantable::kAddMySignatory)) .WillOnce(Return(true)); - EXPECT_CALL(*wsv_command, insertSignatory(add_signatory->pubkey())) - .WillOnce(Return(makeEmptyError())); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } class CreateAccountTest : public CommandValidateExecuteTest { @@ -678,20 +462,8 @@ TEST_F(CreateAccountTest, ValidWhenNewAccount) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, getDomain(kDomainId)) - .WillOnce(Return(default_domain)); - EXPECT_CALL(*wsv_command, insertSignatory(create_account->pubkey())) - .Times(1) - .WillOnce(Return(WsvCommandResult())); - EXPECT_CALL(*wsv_command, insertAccount(_)) - .WillOnce(Return(WsvCommandResult())); - EXPECT_CALL(*wsv_command, - insertAccountSignatory(kAccountId, create_account->pubkey())) - .WillOnce(Return(WsvCommandResult())); - EXPECT_CALL(*wsv_command, insertAccountRole(kAccountId, kAdminRole)) - .WillOnce(Return(WsvCommandResult())); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -703,7 +475,7 @@ TEST_F(CreateAccountTest, InvalidWhenNoPermissions) { // Creator has no permission EXPECT_CALL(*wsv_query, getAccountRoles(kAdminId)) .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -716,9 +488,8 @@ TEST_F(CreateAccountTest, InvalidWhenNoDomain) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, getDomain(kDomainId)).WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } class CreateAssetTest : public CommandValidateExecuteTest { @@ -748,10 +519,7 @@ TEST_F(CreateAssetTest, ValidWhenCreatorHasPermissions) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_command, insertAsset(_)) - .WillOnce(Return(WsvCommandResult())); - - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -765,18 +533,7 @@ TEST_F(CreateAssetTest, InvalidWhenNoPermissions) { EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given CreateAsset - * @when command tries to create asset, but insertion fails - * @then execute() fails - */ -TEST_F(CreateAssetTest, InvalidWhenAssetInsertionFails) { - EXPECT_CALL(*wsv_command, insertAsset(_)).WillOnce(Return(makeEmptyError())); - - ASSERT_TRUE(err(execute(command))); + ASSERT_TRUE(err(validate(command))); } class CreateDomainTest : public CommandValidateExecuteTest { @@ -807,10 +564,7 @@ TEST_F(CreateDomainTest, ValidWhenCreatorHasPermissions) { EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_command, insertDomain(_)) - .WillOnce(Return(WsvCommandResult())); - - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -821,18 +575,7 @@ TEST_F(CreateDomainTest, ValidWhenCreatorHasPermissions) { TEST_F(CreateDomainTest, InvalidWhenNoPermissions) { EXPECT_CALL(*wsv_query, getAccountRoles(kAdminId)) .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given CreateDomain - * @when command tries to create domain, but insertion fails - * @then execute() fails - */ -TEST_F(CreateDomainTest, InvalidWhenDomainInsertionFails) { - EXPECT_CALL(*wsv_command, insertDomain(_)).WillOnce(Return(makeEmptyError())); - - ASSERT_TRUE(err(execute(command))); + ASSERT_TRUE(err(validate(command))); } class RemoveSignatoryTest : public CommandValidateExecuteTest { @@ -880,13 +623,7 @@ TEST_F(RemoveSignatoryTest, ValidWhenMultipleKeys) { EXPECT_CALL(*wsv_query, getSignatories(remove_signatory->accountId())) .WillOnce(Return(many_pubkeys)); - EXPECT_CALL(*wsv_command, - deleteAccountSignatory(remove_signatory->accountId(), - remove_signatory->pubkey())) - .WillOnce(Return(WsvCommandResult())); - EXPECT_CALL(*wsv_command, deleteSignatory(remove_signatory->pubkey())) - .WillOnce(Return(WsvCommandResult())); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -915,7 +652,7 @@ TEST_F(RemoveSignatoryTest, InvalidWhenSingleKey) { EXPECT_CALL(*wsv_command, deleteSignatory(remove_signatory->pubkey())) .Times(0); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -930,7 +667,7 @@ TEST_F(RemoveSignatoryTest, InvalidWhenNoPermissions) { Grantable::kRemoveMySignatory)) .WillOnce(Return(false)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -961,7 +698,7 @@ TEST_F(RemoveSignatoryTest, InvalidWhenNoKey) { getSignatories(wrong_key_remove_signatory->accountId())) .WillOnce(Return(account_pubkeys)); - ASSERT_TRUE(err(validateAndExecute(wrong_key_command))); + ASSERT_TRUE(err(validate(wrong_key_command))); } /** @@ -982,7 +719,7 @@ TEST_F(RemoveSignatoryTest, InvalidWhenNoAccount) { EXPECT_CALL(*wsv_query, getSignatories(remove_signatory->accountId())) .WillOnce(Return(many_pubkeys)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -1004,7 +741,7 @@ TEST_F(RemoveSignatoryTest, InvalidWhenNoSignatories) { EXPECT_CALL(*wsv_query, getSignatories(remove_signatory->accountId())) .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -1026,7 +763,7 @@ TEST_F(RemoveSignatoryTest, InvalidWhenNoAccountAndSignatories) { EXPECT_CALL(*wsv_query, getSignatories(remove_signatory->accountId())) .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -1051,21 +788,7 @@ TEST_F(RemoveSignatoryTest, InvalidWhenNoPermissionToRemoveFromSelf) { kAdminId, kAdminId, Grantable::kRemoveMySignatory)) .WillOnce(Return(false)); - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given RemoveSignatory - * @when command tries to remove signatory but deletion fails - * @then execute() fails - */ -TEST_F(RemoveSignatoryTest, InvalidWhenAccountSignatoryDeletionFails) { - EXPECT_CALL(*wsv_command, - deleteAccountSignatory(remove_signatory->accountId(), - remove_signatory->pubkey())) - .WillOnce(Return(makeEmptyError())); - - ASSERT_TRUE(err(execute(command))); + ASSERT_TRUE(err(validate(command))); } class SetQuorumTest : public CommandValidateExecuteTest { @@ -1106,14 +829,9 @@ TEST_F(SetQuorumTest, ValidWhenCreatorHasPermissions) { hasAccountGrantablePermission( kAdminId, set_quorum->accountId(), Grantable::kSetMyQuorum)) .WillOnce(Return(true)); - EXPECT_CALL(*wsv_query, getAccount(set_quorum->accountId())) - .WillOnce(Return(account)); EXPECT_CALL(*wsv_query, getSignatories(set_quorum->accountId())) .WillOnce(Return(account_pubkeys)); - EXPECT_CALL(*wsv_command, updateAccount(_)) - .WillOnce(Return(WsvCommandResult())); - - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -1126,14 +844,9 @@ TEST_F(SetQuorumTest, ValidWhenSameAccount) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, getAccount(creator_set_quorum->accountId())) - .WillOnce(Return(account)); EXPECT_CALL(*wsv_query, getSignatories(creator_set_quorum->accountId())) .WillOnce(Return(account_pubkeys)); - EXPECT_CALL(*wsv_command, updateAccount(_)) - .WillOnce(Return(WsvCommandResult())); - - ASSERT_TRUE(val(validateAndExecute(creator_command))); + ASSERT_TRUE(val(validate(creator_command))); } /** * @given SetQuorum and creator has not grantable permissions @@ -1146,7 +859,7 @@ TEST_F(SetQuorumTest, InvalidWhenNoPermissions) { kAdminId, set_quorum->accountId(), Grantable::kSetMyQuorum)) .WillOnce(Return(false)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** * @given SetQuorum and account parameter is invalid @@ -1164,7 +877,7 @@ TEST_F(SetQuorumTest, InvalidWhenNoAccount) { kAdminId, set_quorum->accountId(), Grantable::kSetMyQuorum)) .WillOnce(Return(false)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -1180,10 +893,7 @@ TEST_F(SetQuorumTest, InvalidWhenNoAccountButPassedPermissions) { EXPECT_CALL(*wsv_query, getSignatories(creator_set_quorum->accountId())) .WillOnce(Return(account_pubkeys)); - EXPECT_CALL(*wsv_query, getAccount(creator_set_quorum->accountId())) - .WillOnce(Return(boost::none)); - - ASSERT_TRUE(err(validateAndExecute(creator_command))); + ASSERT_TRUE(val(validate(creator_command))); } /** @@ -1200,7 +910,7 @@ TEST_F(SetQuorumTest, InvalidWhenNoSignatories) { EXPECT_CALL(*wsv_query, getSignatories(creator_set_quorum->accountId())) .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(creator_command))); + ASSERT_TRUE(err(validate(creator_command))); } /** @@ -1222,7 +932,7 @@ TEST_F(SetQuorumTest, InvalidWhenNotEnoughSignatories) { .WillOnce(Return(acc_pubkeys)); EXPECT_CALL(*wsv_command, updateAccount(_)).Times(0); - ASSERT_TRUE(err(validateAndExecute(creator_command))); + ASSERT_TRUE(err(validate(creator_command))); } class TransferAssetTest : public CommandValidateExecuteTest { @@ -1233,13 +943,13 @@ class TransferAssetTest : public CommandValidateExecuteTest { src_wallet = clone(shared_model::proto::AccountAssetBuilder() .assetId(kAssetId) .accountId(kAdminId) - .balance(*balance) + .balance(balance) .build()); dst_wallet = clone(shared_model::proto::AccountAssetBuilder() .assetId(kAssetId) .accountId(kAccountId) - .balance(*balance) + .balance(balance) .build()); role_permissions = {Role::kTransfer, Role::kReceive}; @@ -1256,41 +966,6 @@ class TransferAssetTest : public CommandValidateExecuteTest { std::shared_ptr transfer_asset; }; -/** - * @given TransferAsset and destination account has not AccountAsset - * @when command is executed and new AccountAsset will be created - * @then execute successes - */ -TEST_F(TransferAssetTest, ValidWhenNewWallet) { - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->destAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->srcAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .Times(2) - .WillRepeatedly(Return(role_permissions)); - - EXPECT_CALL(*wsv_query, getAccountAsset(transfer_asset->destAccountId(), _)) - .WillOnce(Return(boost::none)); - - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->srcAccountId(), - transfer_asset->assetId())) - .Times(2) - .WillRepeatedly(Return(src_wallet)); - EXPECT_CALL(*wsv_query, getAsset(transfer_asset->assetId())) - .Times(2) - .WillRepeatedly(Return(asset)); - EXPECT_CALL(*wsv_query, getAccount(transfer_asset->destAccountId())) - .WillOnce(Return(account)); - - EXPECT_CALL(*wsv_command, upsertAccountAsset(_)) - .Times(2) - .WillRepeatedly(Return(WsvCommandResult())); - - ASSERT_TRUE(val(validateAndExecute(command))); -} - /** * @given TransferAsset * @when command is executed @@ -1305,233 +980,6 @@ TEST_F(TransferAssetTest, ValidWhenExistingWallet) { EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .Times(2) .WillRepeatedly(Return(role_permissions)); - - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->destAccountId(), - transfer_asset->assetId())) - .WillOnce(Return(dst_wallet)); - - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->srcAccountId(), - transfer_asset->assetId())) - .Times(2) - .WillRepeatedly(Return(src_wallet)); - EXPECT_CALL(*wsv_query, getAsset(transfer_asset->assetId())) - .Times(2) - .WillRepeatedly(Return(asset)); - EXPECT_CALL(*wsv_query, getAccount(transfer_asset->destAccountId())) - .WillOnce(Return(account)); - - EXPECT_CALL(*wsv_command, upsertAccountAsset(_)) - .Times(2) - .WillRepeatedly(Return(WsvCommandResult())); - - ASSERT_TRUE(val(validateAndExecute(command))); -} - -/** - * @given TransferAsset and creator has permissions - * @when command tries to transfer - * @then execute succeses - */ -TEST_F(TransferAssetTest, ValidWhenCreatorHasPermission) { - // Transfer creator is not connected to account - // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().transferAsset( - kAccountId, kAdminId, kAssetId, kDescription, kAmount)); - auto transfer_asset = - getConcreteCommand(command); - - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - kAdminId, kAccountId, Grantable::kTransferMyAssets)) - .WillOnce(Return(true)); - - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->destAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .WillOnce(Return(role_permissions)); - - EXPECT_CALL(*wsv_query, getAccountAsset(transfer_asset->destAccountId(), _)) - .WillOnce(Return(boost::none)); - - EXPECT_CALL(*wsv_query, getAccountAsset(transfer_asset->srcAccountId(), _)) - .Times(2) - .WillRepeatedly(Return(src_wallet)); - EXPECT_CALL(*wsv_query, getAsset(transfer_asset->assetId())) - .Times(2) - .WillRepeatedly(Return(asset)); - EXPECT_CALL(*wsv_query, getAccount(transfer_asset->destAccountId())) - .WillOnce(Return(account)); - - EXPECT_CALL(*wsv_command, upsertAccountAsset(_)) - .Times(2) - .WillRepeatedly(Return(WsvCommandResult())); - - ASSERT_TRUE(val(validateAndExecute(command))); -} - -/** - * @given TransferAsset and creator has not account roles - * @when command is executed - * @then execute fails - */ -TEST_F(TransferAssetTest, InvalidWhenNoPermissions) { - // Creator has no permissions - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->srcAccountId())) - .WillOnce(Return(boost::none)); - - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given TransferAsset and destination account doesn't have any role - * @when command is executed - * @then execute fails - */ -TEST_F(TransferAssetTest, InvalidWhenNoDestAccount) { - // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().transferAsset( - kAdminId, kNoAcountId, kAssetId, kDescription, kAmount)); - auto transfer_asset = - getConcreteCommand(command); - - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->destAccountId())) - .WillOnce(Return(boost::none)); - - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->srcAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .WillOnce(Return(role_permissions)); - - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given TransferAsset and source account doesn't have asset - * @when command is executed - * @then execute fails - */ -TEST_F(TransferAssetTest, InvalidWhenNoSrcAccountAsset) { - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->destAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->srcAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .Times(2) - .WillRepeatedly(Return(role_permissions)); - - EXPECT_CALL(*wsv_query, getAsset(transfer_asset->assetId())) - .WillOnce(Return(asset)); - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->srcAccountId(), - transfer_asset->assetId())) - .WillOnce(Return(boost::none)); - - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given TransferAsset - * @when command tries to transfer asset from non-existing account - * @then execute fails - */ -TEST_F(TransferAssetTest, InvalidWhenNoSrcAccountAssetDuringExecute) { - // No source account asset exists - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->destAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->srcAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .Times(2) - .WillRepeatedly(Return(role_permissions)); - - EXPECT_CALL(*wsv_query, getAsset(transfer_asset->assetId())) - .WillOnce(Return(asset)); - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->srcAccountId(), - transfer_asset->assetId())) - .Times(2) - .WillOnce(Return(src_wallet)) - .WillOnce(Return(boost::none)); - EXPECT_CALL(*wsv_query, getAccount(transfer_asset->destAccountId())) - .WillOnce(Return(account)); - - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given TransferAsset - * @when command tries to transfer non-existent asset - * @then isValid fails - */ -TEST_F(TransferAssetTest, InvalidWhenNoAssetDuringValidation) { - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->destAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->srcAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .Times(2) - .WillRepeatedly(Return(role_permissions)); - - EXPECT_CALL(*wsv_query, getAsset(transfer_asset->assetId())) - .WillOnce(Return(boost::none)); - - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given TransferAsset - * @when command tries to transfer non-existent asset - * @then execute fails - */ -TEST_F(TransferAssetTest, InvalidWhenNoAssetId) { - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->destAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->srcAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .Times(2) - .WillRepeatedly(Return(role_permissions)); - - EXPECT_CALL(*wsv_query, getAsset(transfer_asset->assetId())) - .WillOnce(Return(asset)) - .WillOnce(Return(boost::none)); - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->srcAccountId(), - transfer_asset->assetId())) - .Times(2) - .WillRepeatedly(Return(src_wallet)); - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->destAccountId(), - transfer_asset->assetId())) - .WillOnce(Return(dst_wallet)); - EXPECT_CALL(*wsv_query, getAccount(transfer_asset->destAccountId())) - .WillOnce(Return(account)); - - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given TransferAsset - * @when command tries to transfer amount which is less than source balance - * @then execute fails - */ -TEST_F(TransferAssetTest, InvalidWhenInsufficientFunds) { - // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().transferAsset( - kAdminId, kAccountId, kAssetId, kDescription, kAmountOverflow)); - auto transfer_asset = - getConcreteCommand(command); - - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->destAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->srcAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .Times(2) - .WillRepeatedly(Return(role_permissions)); - EXPECT_CALL(*wsv_query, getAccountAsset(transfer_asset->srcAccountId(), transfer_asset->assetId())) @@ -1540,140 +988,7 @@ TEST_F(TransferAssetTest, InvalidWhenInsufficientFunds) { .WillOnce(Return(asset)); EXPECT_CALL(*wsv_query, getAccount(transfer_asset->destAccountId())) .WillOnce(Return(account)); - - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given TransferAsset - * @when command tries to transfer amount which is less than source balance - * @then execute fails - */ -TEST_F(TransferAssetTest, InvalidWhenInsufficientFundsDuringExecute) { - // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().transferAsset( - kAdminId, kAccountId, kAssetId, kDescription, kAmountOverflow)); - auto transfer_asset = - getConcreteCommand(command); - - EXPECT_CALL(*wsv_query, getAsset(transfer_asset->assetId())) - .WillOnce(Return(asset)); - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->srcAccountId(), - transfer_asset->assetId())) - .WillOnce(Return(src_wallet)); - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->destAccountId(), - transfer_asset->assetId())) - .WillOnce(Return(dst_wallet)); - - ASSERT_TRUE(err(execute(command))); -} - -/** - * @given TransferAsset - * @when command tries to transfer amount which has wrong precesion (must be 2) - * @then execute fails - */ -TEST_F(TransferAssetTest, InvalidWhenWrongPrecision) { - // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().transferAsset( - kAdminId, kAccountId, kAssetId, kDescription, kAmountWrongPrecision)); - auto transfer_asset = - getConcreteCommand(command); - - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->destAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getAccountRoles(transfer_asset->srcAccountId())) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) - .Times(2) - .WillRepeatedly(Return(role_permissions)); - - EXPECT_CALL(*wsv_query, getAsset(transfer_asset->assetId())) - .WillOnce(Return(asset)); - - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given TransferAsset - * @when command tries to transfer amount with wrong precision - * @then execute fails - */ -TEST_F(TransferAssetTest, InvalidWhenWrongPrecisionDuringExecute) { - // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().transferAsset( - kAdminId, kAccountId, kAssetId, kDescription, kAmountWrongPrecision)); - auto transfer_asset = - getConcreteCommand(command); - - EXPECT_CALL(*wsv_query, getAsset(transfer_asset->assetId())) - .WillOnce(Return(asset)); - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->srcAccountId(), - transfer_asset->assetId())) - .WillOnce(Return(src_wallet)); - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->destAccountId(), - transfer_asset->assetId())) - .WillOnce(Return(dst_wallet)); - - ASSERT_TRUE(err(execute(command))); -} - -/** - * @given TransferAsset - * @when command tries to transfer asset which overflows destination balance - * @then execute fails - */ -TEST_F(TransferAssetTest, InvalidWhenAmountOverflow) { - std::shared_ptr max_balance = clone( - shared_model::proto::AmountBuilder() - .intValue( - std::numeric_limits::max()) - .precision(2) - .build()); - - std::shared_ptr max_wallet = - clone(shared_model::proto::AccountAssetBuilder() - .assetId(src_wallet->assetId()) - .accountId(src_wallet->accountId()) - .balance(*max_balance) - .build()); - - EXPECT_CALL(*wsv_query, getAsset(transfer_asset->assetId())) - .WillOnce(Return(asset)); - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->srcAccountId(), - transfer_asset->assetId())) - .WillOnce(Return(src_wallet)); - EXPECT_CALL(*wsv_query, - getAccountAsset(transfer_asset->destAccountId(), - transfer_asset->assetId())) - .WillOnce(Return(max_wallet)); - - ASSERT_TRUE(err(execute(command))); -} - -/** - * @given TransferAsset and creator has not grantable permissions - * @when command tries to transfer - * @then execute fails - */ -TEST_F(TransferAssetTest, InvalidWhenCreatorHasNoPermission) { - // Transfer creator is not connected to account - // TODO 2018-04-20 Alexey Chernyshov - IR-1276 - rework with CommandBuilder - command = buildCommand(TestTransactionBuilder().transferAsset( - kAccountId, kAdminId, kAssetId, kDescription, kAmount)); - auto transfer_asset = - getConcreteCommand(command); - - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - kAdminId, kAccountId, Grantable::kTransferMyAssets)) - .WillOnce(Return(false)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } class AddPeerTest : public CommandValidateExecuteTest { @@ -1700,9 +1015,8 @@ TEST_F(AddPeerTest, ValidCase) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_command, insertPeer(_)).WillOnce(Return(WsvCommandResult())); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -1716,19 +1030,9 @@ TEST_F(AddPeerTest, InvalidCaseWhenNoPermissions) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } -/** - * @given AddPeer - * @when command tries to insert peer but insertion fails - * @then execute failed - */ -TEST_F(AddPeerTest, InvalidCaseWhenInsertPeerFails) { - EXPECT_CALL(*wsv_command, insertPeer(_)).WillOnce(Return(makeEmptyError())); - - ASSERT_TRUE(err(execute(command))); -} class CreateRoleTest : public CommandValidateExecuteTest { public: @@ -1757,13 +1061,7 @@ TEST_F(CreateRoleTest, ValidCase) { .WillRepeatedly(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillRepeatedly(Return(role_permissions)); - EXPECT_CALL(*wsv_command, insertRole(create_role->roleName())) - .WillOnce(Return(WsvCommandResult())); - EXPECT_CALL(*wsv_command, - insertRolePermissions(create_role->roleName(), - create_role->rolePermissions())) - .WillOnce(Return(WsvCommandResult())); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -1776,7 +1074,7 @@ TEST_F(CreateRoleTest, InvalidCaseWhenNoPermissions) { .WillRepeatedly(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillRepeatedly(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -1795,18 +1093,7 @@ TEST_F(CreateRoleTest, InvalidCaseWhenRoleSuperset) { .WillRepeatedly(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillRepeatedly(Return(role_permissions)); - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given CreateRole - * @when command tries to create new role, but insertion fails - * @then execute failed - */ -TEST_F(CreateRoleTest, InvalidCaseWhenRoleInsertionFails) { - EXPECT_CALL(*wsv_command, insertRole(create_role->roleName())) - .WillOnce(Return(makeEmptyError())); - ASSERT_TRUE(err(execute(command))); + ASSERT_TRUE(err(validate(command))); } class AppendRoleTest : public CommandValidateExecuteTest { @@ -1841,11 +1128,7 @@ TEST_F(AppendRoleTest, ValidCase) { EXPECT_CALL(*wsv_query, getRolePermissions(kMasterRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL( - *wsv_command, - insertAccountRole(append_role->accountId(), append_role->roleName())) - .WillOnce(Return(WsvCommandResult())); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -1858,7 +1141,7 @@ TEST_F(AppendRoleTest, InvalidCaseNoPermissions) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -1875,7 +1158,7 @@ TEST_F(AppendRoleTest, InvalidCaseNoAccountRole) { .WillOnce(Return(role_permissions)); EXPECT_CALL(*wsv_query, getRolePermissions(kMasterRole)) .WillOnce(Return(role_permissions)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -1894,7 +1177,7 @@ TEST_F(AppendRoleTest, InvalidCaseNoAccountRoleAndNoPermission) { .WillOnce(Return(role_permissions)); EXPECT_CALL(*wsv_query, getRolePermissions(kMasterRole)) .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -1915,20 +1198,7 @@ TEST_F(AppendRoleTest, InvalidCaseRoleHasNoPermissions) { EXPECT_CALL(*wsv_query, getRolePermissions(kMasterRole)) .WillOnce(Return(role_permissions)); - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given AppendRole - * @when command tries to append role, but insertion of account fails - * @then execute() fails - */ -TEST_F(AppendRoleTest, InvalidCaseInsertAccountRoleFails) { - EXPECT_CALL( - *wsv_command, - insertAccountRole(append_role->accountId(), append_role->roleName())) - .WillOnce(Return(makeEmptyError())); - ASSERT_TRUE(err(execute(command))); + ASSERT_TRUE(err(validate(command))); } class DetachRoleTest : public CommandValidateExecuteTest { @@ -1957,11 +1227,7 @@ TEST_F(DetachRoleTest, ValidCase) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL( - *wsv_command, - deleteAccountRole(detach_role->accountId(), detach_role->roleName())) - .WillOnce(Return(WsvCommandResult())); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -1974,20 +1240,7 @@ TEST_F(DetachRoleTest, InvalidCase) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given DetachRole - * @when deletion of account role fails - * @then execute fails() - */ -TEST_F(DetachRoleTest, InvalidCaseWhenDeleteAccountRoleFails) { - EXPECT_CALL( - *wsv_command, - deleteAccountRole(detach_role->accountId(), detach_role->roleName())) - .WillOnce(Return(makeEmptyError())); - ASSERT_TRUE(err(execute(command))); + ASSERT_TRUE(err(validate(command))); } class GrantPermissionTest : public CommandValidateExecuteTest { @@ -2018,12 +1271,7 @@ TEST_F(GrantPermissionTest, ValidCase) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_command, - insertAccountGrantablePermission(grant_permission->accountId(), - creator->accountId(), - expected_permission)) - .WillOnce(Return(WsvCommandResult())); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -2036,21 +1284,7 @@ TEST_F(GrantPermissionTest, InvalidCaseWhenNoPermissions) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(boost::none)); - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given GrantPermission - * @when command tries to grant permission but insertion fails - * @then execute() fails - */ -TEST_F(GrantPermissionTest, InvalidCaseWhenInsertGrantablePermissionFails) { - EXPECT_CALL(*wsv_command, - insertAccountGrantablePermission(grant_permission->accountId(), - creator->accountId(), - expected_permission)) - .WillOnce(Return(makeEmptyError())); - ASSERT_TRUE(err(execute(command))); + ASSERT_TRUE(err(validate(command))); } class RevokePermissionTest : public CommandValidateExecuteTest { @@ -2082,12 +1316,7 @@ TEST_F(RevokePermissionTest, ValidCase) { hasAccountGrantablePermission( revoke_permission->accountId(), kAdminId, expected_permission)) .WillOnce(Return(true)); - EXPECT_CALL(*wsv_command, - deleteAccountGrantablePermission(revoke_permission->accountId(), - creator->accountId(), - expected_permission)) - .WillOnce(Return(WsvCommandResult())); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -2101,21 +1330,7 @@ TEST_F(RevokePermissionTest, InvalidCaseNoPermissions) { hasAccountGrantablePermission( revoke_permission->accountId(), kAdminId, expected_permission)) .WillOnce(Return(false)); - ASSERT_TRUE(err(validateAndExecute(command))); -} - -/** - * @given RevokePermission - * @when deleting permission fails - * @then execute fails - */ -TEST_F(RevokePermissionTest, InvalidCaseDeleteAccountPermissionvFails) { - EXPECT_CALL(*wsv_command, - deleteAccountGrantablePermission(revoke_permission->accountId(), - creator->accountId(), - expected_permission)) - .WillOnce(Return(makeEmptyError())); - ASSERT_TRUE(err(execute(command))); + ASSERT_TRUE(err(validate(command))); } class SetAccountDetailTest : public CommandValidateExecuteTest { @@ -2145,13 +1360,7 @@ class SetAccountDetailTest : public CommandValidateExecuteTest { * @then successfully execute the command */ TEST_F(SetAccountDetailTest, ValidWhenSetOwnAccount) { - EXPECT_CALL(*wsv_command, - setAccountKV(set_account_detail->accountId(), - creator->accountId(), - set_account_detail->key(), - set_account_detail->value())) - .WillOnce(Return(WsvCommandResult())); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -2172,7 +1381,7 @@ TEST_F(SetAccountDetailTest, InvalidWhenOtherCreator) { hasAccountGrantablePermission( kAdminId, set_account_detail->accountId(), kNeededPermission)) .WillOnce(Return(false)); - ASSERT_TRUE(err(validateAndExecute(command))); + ASSERT_TRUE(err(validate(command))); } /** @@ -2192,13 +1401,7 @@ TEST_F(SetAccountDetailTest, ValidWhenHasRolePermission) { EXPECT_CALL(*wsv_query, getRolePermissions(kAdminRole)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_command, - setAccountKV(set_account_detail->accountId(), - creator->accountId(), - set_account_detail->key(), - set_account_detail->value())) - .WillOnce(Return(WsvCommandResult())); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } /** @@ -2219,26 +1422,6 @@ TEST_F(SetAccountDetailTest, ValidWhenHasGrantblePermission) { hasAccountGrantablePermission( kAdminId, set_account_detail->accountId(), kNeededPermission)) .WillOnce(Return(true)); - EXPECT_CALL(*wsv_command, - setAccountKV(set_account_detail->accountId(), - creator->accountId(), - set_account_detail->key(), - set_account_detail->value())) - .WillOnce(Return(WsvCommandResult())); - ASSERT_TRUE(val(validateAndExecute(command))); + ASSERT_TRUE(val(validate(command))); } -/** - * @given SetAccountDetail - * @when command tries to set details, but setting key-value fails - * @then execute fails - */ -TEST_F(SetAccountDetailTest, InvalidWhenSetAccountKVFails) { - EXPECT_CALL(*wsv_command, - setAccountKV(set_account_detail->accountId(), - creator->accountId(), - set_account_detail->key(), - set_account_detail->value())) - .WillOnce(Return(makeEmptyError())); - ASSERT_TRUE(err(execute(command))); -} diff --git a/test/module/irohad/execution/execution_mocks.hpp b/test/module/irohad/execution/execution_mocks.hpp new file mode 100644 index 0000000000..480e3f4dfd --- /dev/null +++ b/test/module/irohad/execution/execution_mocks.hpp @@ -0,0 +1,20 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef IROHA_EXECUTION_MOCKS +#define IROHA_EXECUTION_MOCKS + +#include +#include "execution/query_execution.hpp" + +class MockQueryExecution : public iroha::QueryExecution { + public: + MOCK_METHOD1(validateAndExecute, + std::unique_ptr( + const shared_model::interface::Query &)); + MOCK_METHOD1(validate, bool(const shared_model::interface::BlocksQuery &)); +}; + +#endif // IROHA_EXECUTION_MOCKS diff --git a/test/module/irohad/validation/query_execution.cpp b/test/module/irohad/execution/query_execution_test.cpp similarity index 80% rename from test/module/irohad/validation/query_execution.cpp rename to test/module/irohad/execution/query_execution_test.cpp index 351448d862..d96ee3b70c 100644 --- a/test/module/irohad/validation/query_execution.cpp +++ b/test/module/irohad/execution/query_execution_test.cpp @@ -1,33 +1,19 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this/her file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" #include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" #include "builders/common_objects/account_asset_builder.hpp" -#include "builders/common_objects/amount_builder.hpp" #include "builders/common_objects/asset_builder.hpp" #include "builders/protobuf/common_objects/proto_account_asset_builder.hpp" #include "builders/protobuf/common_objects/proto_account_builder.hpp" -#include "builders/protobuf/common_objects/proto_amount_builder.hpp" #include "builders/protobuf/common_objects/proto_asset_builder.hpp" #include "builders/protobuf/queries.hpp" #include "builders/query_responses/block_query_response_builder.hpp" -#include "execution/query_execution.hpp" +#include "execution/query_execution_impl.hpp" #include "framework/specified_visitor.hpp" #include "framework/test_subscriber.hpp" #include "module/shared_model/builders/protobuf/test_query_builder.hpp" @@ -53,10 +39,10 @@ class QueryValidateExecuteTest : public ::testing::Test { void SetUp() override { wsv_query = std::make_shared>(); block_query = std::make_shared>(); - factory = std::make_shared(wsv_query, block_query); - - EXPECT_CALL(*wsv_query, hasAccountGrantablePermission(_, _, _)) - .WillRepeatedly(Return(false)); + storage = std::make_shared(); + EXPECT_CALL(*storage, getWsvQuery()).WillRepeatedly(Return(wsv_query)); + EXPECT_CALL(*storage, getBlockQuery()).WillRepeatedly(Return(block_query)); + qry_exec = std::make_shared(storage); creator = clone(shared_model::proto::AccountBuilder() .accountId(admin_id) @@ -74,7 +60,7 @@ class QueryValidateExecuteTest : public ::testing::Test { } std::shared_ptr validateAndExecute( const shared_model::interface::Query &query) { - return factory->validateAndExecute(query); + return qry_exec->validateAndExecute(query); } /** @@ -91,16 +77,14 @@ class QueryValidateExecuteTest : public ::testing::Test { * @param N * @return observable with transactions */ - rxcpp::observable getDefaultTransactions( - const std::string &creator, size_t N) { - return rxcpp::observable<>::iterate([&creator, &N, this] { - std::vector result; - for (size_t i = 0; i < N; ++i) { - auto current = makeTransaction(creator); - result.push_back(current); - } - return result; - }()); + std::vector getDefaultTransactions( + const std::string &creator, size_t N) { + std::vector result; + for (size_t i = 0; i < N; ++i) { + auto current = makeTransaction(creator); + result.push_back(current); + } + return result; } std::string admin_id = "admin@test", account_id = "test@test", @@ -113,8 +97,9 @@ class QueryValidateExecuteTest : public ::testing::Test { std::shared_ptr creator, account; std::shared_ptr wsv_query; std::shared_ptr block_query; + std::shared_ptr storage; - std::shared_ptr factory; + std::shared_ptr qry_exec; }; class GetAccountTest : public QueryValidateExecuteTest { @@ -214,40 +199,6 @@ TEST_F(GetAccountTest, DomainAccountValidCase) { }); } -/** - * @given initialized storage, granted permission - * @when get account information about other user - * @then Return users account - */ -TEST_F(GetAccountTest, GrantAccountValidCase) { - auto query = TestQueryBuilder() - .creatorAccountId(admin_id) - .getAccount(account_id) - .build(); - - role_permissions = {}; - - EXPECT_CALL(*wsv_query, getAccountRoles(admin_id)) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(admin_role)) - .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - admin_id, account_id, permissionOf(Role::kGetMyAccount))) - .WillOnce(Return(true)); - - EXPECT_CALL(*wsv_query, getAccount(account_id)).WillOnce(Return(account)); - EXPECT_CALL(*wsv_query, getAccountRoles(account_id)) - .WillOnce(Return(admin_roles)); - auto response = validateAndExecute(query); - ASSERT_NO_THROW({ - const auto &cast_resp = boost::apply_visitor( - framework::SpecifiedVisitor(), - response->get()); - ASSERT_EQ(cast_resp.account().accountId(), account_id); - }); -} - /** * @given initialized storage, domain permission * @when get account information about other user in the other domain @@ -265,10 +216,6 @@ TEST_F(GetAccountTest, DifferentDomainAccountInValidCase) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(admin_role)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - admin_id, "test@test2", permissionOf(Role::kGetMyAccount))) - .WillOnce(Return(false)); auto response = validateAndExecute(query); @@ -312,26 +259,12 @@ class GetAccountAssetsTest : public QueryValidateExecuteTest { void SetUp() override { QueryValidateExecuteTest::SetUp(); - std::shared_ptr amount; - shared_model::builder::AmountBuilder< - shared_model::proto::AmountBuilder, - shared_model::validation::FieldValidator>() - .intValue(100) - .precision(2) - .build() - .match( - [&](iroha::expected::Value< - std::shared_ptr> &v) { - amount = v.value; - }, - [](iroha::expected::Error>) {}); - shared_model::builder::AccountAssetBuilder< shared_model::proto::AccountAssetBuilder, shared_model::validation::FieldValidator>() .assetId(asset_id) .accountId(admin_id) - .balance(*amount) + .balance(shared_model::interface::Amount("1.00")) .build() .match( [&](iroha::expected::Value< @@ -470,50 +403,6 @@ TEST_F(GetAccountAssetsTest, DomainAccountValidCase) { }); } -/** - * @given initialized storage, granted permission - * @when get account information about other user - * @then Return account assets - */ -TEST_F(GetAccountAssetsTest, GrantAccountValidCase) { - auto query = TestQueryBuilder() - .creatorAccountId(admin_id) - .getAccountAssets(account_id) - .build(); - - accountAsset = clone(shared_model::proto::AccountAssetBuilder() - .assetId(accountAsset->assetId()) - .accountId(account_id) - .balance(accountAsset->balance()) - .build()); - role_permissions = {}; - - EXPECT_CALL(*wsv_query, getAccountRoles(admin_id)) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(admin_role)) - .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - admin_id, account_id, permissionOf(Role::kGetMyAccAst))) - .WillOnce(Return(true)); - - EXPECT_CALL(*wsv_query, getAccountAssets(account_id)) - .WillOnce(Return( - std::vector>( - {accountAsset}))); - - auto response = validateAndExecute(query); - ASSERT_NO_THROW({ - const auto &cast_resp = boost::apply_visitor( - framework::SpecifiedVisitor< - shared_model::interface::AccountAssetResponse>(), - response->get()); - - ASSERT_EQ(cast_resp.accountAssets()[0].accountId(), account_id); - ASSERT_EQ(cast_resp.accountAssets()[0].assetId(), asset_id); - }); -} - /** * @given initialized storage, domain permission * @when get account information about other user in the other domain @@ -544,10 +433,6 @@ TEST_F(GetAccountAssetsTest, DifferentDomainAccountInValidCase) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(admin_role)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - admin_id, "test@test2", permissionOf(Role::kGetMyAccAst))) - .WillOnce(Return(false)); auto response = validateAndExecute(query); @@ -698,41 +583,6 @@ TEST_F(GetSignatoriesTest, DomainAccountValidCase) { }); } -/** - * @given initialized storage, granted permission - * @when get account information about other user - * @then Return signatories - */ -TEST_F(GetSignatoriesTest, GrantAccountValidCase) { - auto query = TestQueryBuilder() - .creatorAccountId(admin_id) - .getSignatories(account_id) - .build(); - - role_permissions = {}; - - EXPECT_CALL(*wsv_query, getAccountRoles(admin_id)) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(admin_role)) - .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - admin_id, account_id, permissionOf(Role::kGetMySignatories))) - .WillOnce(Return(true)); - - EXPECT_CALL(*wsv_query, getSignatories(account_id)).WillOnce(Return(signs)); - - auto response = validateAndExecute(query); - ASSERT_NO_THROW({ - const auto &cast_resp = boost::apply_visitor( - framework::SpecifiedVisitor< - shared_model::interface::SignatoriesResponse>(), - response->get()); - - ASSERT_EQ(cast_resp.keys().size(), 1); - }); -} - /** * @given initialized storage, domain permission * @when get account information about other user in the other domain @@ -750,11 +600,6 @@ TEST_F(GetSignatoriesTest, DifferentDomainAccountInValidCase) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(admin_role)) .WillOnce(Return(role_permissions)); - EXPECT_CALL( - *wsv_query, - hasAccountGrantablePermission( - admin_id, "test@test2", permissionOf(Role::kGetMySignatories))) - .WillOnce(Return(false)); auto response = validateAndExecute(query); @@ -798,10 +643,10 @@ class GetAccountTransactionsTest : public QueryValidateExecuteTest { void SetUp() override { QueryValidateExecuteTest::SetUp(); role_permissions = {Role::kGetMyAccTxs}; - txs_observable = getDefaultTransactions(account_id, N); + txs = getDefaultTransactions(account_id, N); } - rxcpp::observable txs_observable; + std::vector txs; size_t N = 3; }; @@ -821,10 +666,10 @@ TEST_F(GetAccountTransactionsTest, MyAccountValidCase) { EXPECT_CALL(*wsv_query, getRolePermissions(admin_role)) .WillOnce(Return(role_permissions)); - txs_observable = getDefaultTransactions(admin_id, N); + txs = getDefaultTransactions(admin_id, N); EXPECT_CALL(*block_query, getAccountTransactions(admin_id)) - .WillOnce(Return(txs_observable)); + .WillOnce(Return(txs)); auto response = validateAndExecute(query); ASSERT_NO_THROW({ @@ -859,7 +704,7 @@ TEST_F(GetAccountTransactionsTest, AllAccountValidCase) { .WillOnce(Return(role_permissions)); EXPECT_CALL(*block_query, getAccountTransactions(account_id)) - .WillOnce(Return(txs_observable)); + .WillOnce(Return(txs)); auto response = validateAndExecute(query); ASSERT_NO_THROW({ @@ -894,46 +739,7 @@ TEST_F(GetAccountTransactionsTest, DomainAccountValidCase) { .WillOnce(Return(role_permissions)); EXPECT_CALL(*block_query, getAccountTransactions(account_id)) - .WillOnce(Return(txs_observable)); - - auto response = validateAndExecute(query); - ASSERT_NO_THROW({ - const auto &cast_resp = boost::apply_visitor( - framework::SpecifiedVisitor< - shared_model::interface::TransactionsResponse>(), - response->get()); - - ASSERT_EQ(cast_resp.transactions().size(), N); - for (const auto &tx : cast_resp.transactions()) { - EXPECT_EQ(account_id, tx.creatorAccountId()); - } - }); -} - -/** - * @given initialized storage, granted permission - * @when get account information about other user - * @then Return error - */ -TEST_F(GetAccountTransactionsTest, GrantAccountValidCase) { - auto query = TestQueryBuilder() - .creatorAccountId(admin_id) - .getAccountTransactions(account_id) - .build(); - - role_permissions = {}; - - EXPECT_CALL(*wsv_query, getAccountRoles(admin_id)) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(admin_role)) - .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - admin_id, account_id, permissionOf(Role::kGetMyAccTxs))) - .WillOnce(Return(true)); - - EXPECT_CALL(*block_query, getAccountTransactions(account_id)) - .WillOnce(Return(txs_observable)); + .WillOnce(Return(txs)); auto response = validateAndExecute(query); ASSERT_NO_THROW({ @@ -966,10 +772,6 @@ TEST_F(GetAccountTransactionsTest, DifferentDomainAccountInValidCase) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(admin_role)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - admin_id, "test@test2", permissionOf(Role::kGetMyAccTxs))) - .WillOnce(Return(false)); auto response = validateAndExecute(query); @@ -998,7 +800,7 @@ TEST_F(GetAccountTransactionsTest, NoAccountExist) { .WillOnce(Return(role_permissions)); EXPECT_CALL(*block_query, getAccountTransactions("none")) - .WillOnce(Return(rxcpp::observable<>::empty())); + .WillOnce(Return(std::vector())); auto response = validateAndExecute(query); ASSERT_NO_THROW( @@ -1013,10 +815,10 @@ class GetAccountAssetsTransactionsTest : public QueryValidateExecuteTest { void SetUp() override { QueryValidateExecuteTest::SetUp(); role_permissions = {Role::kGetMyAccAstTxs}; - txs_observable = getDefaultTransactions(account_id, N); + txs = getDefaultTransactions(account_id, N); } - rxcpp::observable txs_observable; + std::vector txs; size_t N = 3; }; @@ -1036,10 +838,10 @@ TEST_F(GetAccountAssetsTransactionsTest, MyAccountValidCase) { EXPECT_CALL(*wsv_query, getRolePermissions(admin_role)) .WillOnce(Return(role_permissions)); - txs_observable = getDefaultTransactions(admin_id, N); + txs = getDefaultTransactions(admin_id, N); EXPECT_CALL(*block_query, getAccountAssetTransactions(admin_id, asset_id)) - .WillOnce(Return(txs_observable)); + .WillOnce(Return(txs)); auto response = validateAndExecute(query); ASSERT_NO_THROW({ @@ -1074,7 +876,7 @@ TEST_F(GetAccountAssetsTransactionsTest, AllAccountValidCase) { .WillOnce(Return(role_permissions)); EXPECT_CALL(*block_query, getAccountAssetTransactions(account_id, asset_id)) - .WillOnce(Return(txs_observable)); + .WillOnce(Return(txs)); auto response = validateAndExecute(query); ASSERT_NO_THROW({ @@ -1109,46 +911,7 @@ TEST_F(GetAccountAssetsTransactionsTest, DomainAccountValidCase) { .WillOnce(Return(role_permissions)); EXPECT_CALL(*block_query, getAccountAssetTransactions(account_id, asset_id)) - .WillOnce(Return(txs_observable)); - - auto response = validateAndExecute(query); - ASSERT_NO_THROW({ - const auto &cast_resp = boost::apply_visitor( - framework::SpecifiedVisitor< - shared_model::interface::TransactionsResponse>(), - response->get()); - - ASSERT_EQ(cast_resp.transactions().size(), N); - for (const auto &tx : cast_resp.transactions()) { - EXPECT_EQ(account_id, tx.creatorAccountId()); - } - }); -} - -/** - * @given initialized storage, granted permission - * @when get account information about other user - * @then Return error - */ -TEST_F(GetAccountAssetsTransactionsTest, GrantAccountValidCase) { - auto query = TestQueryBuilder() - .creatorAccountId(admin_id) - .getAccountAssetTransactions(account_id, asset_id) - .build(); - - role_permissions = {}; - - EXPECT_CALL(*wsv_query, getAccountRoles(admin_id)) - .WillOnce(Return(admin_roles)); - EXPECT_CALL(*wsv_query, getRolePermissions(admin_role)) - .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - admin_id, account_id, permissionOf(Role::kGetMyAccAstTxs))) - .WillOnce(Return(true)); - - EXPECT_CALL(*block_query, getAccountAssetTransactions(account_id, asset_id)) - .WillOnce(Return(txs_observable)); + .WillOnce(Return(txs)); auto response = validateAndExecute(query); ASSERT_NO_THROW({ @@ -1181,10 +944,6 @@ TEST_F(GetAccountAssetsTransactionsTest, DifferentDomainAccountInValidCase) { .WillOnce(Return(admin_roles)); EXPECT_CALL(*wsv_query, getRolePermissions(admin_role)) .WillOnce(Return(role_permissions)); - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - admin_id, "test@test2", permissionOf(Role::kGetMyAccAstTxs))) - .WillOnce(Return(false)); auto response = validateAndExecute(query); @@ -1213,7 +972,7 @@ TEST_F(GetAccountAssetsTransactionsTest, NoAccountExist) { .WillOnce(Return(role_permissions)); EXPECT_CALL(*block_query, getAccountAssetTransactions("none", asset_id)) - .WillOnce(Return(rxcpp::observable<>::empty())); + .WillOnce(Return(std::vector())); auto response = validateAndExecute(query); ASSERT_NO_THROW( @@ -1241,7 +1000,7 @@ TEST_F(GetAccountAssetsTransactionsTest, NoAssetExist) { .WillOnce(Return(role_permissions)); EXPECT_CALL(*block_query, getAccountAssetTransactions(account_id, "none")) - .WillOnce(Return(rxcpp::observable<>::empty())); + .WillOnce(Return(std::vector())); auto response = validateAndExecute(query); ASSERT_NO_THROW( diff --git a/test/module/irohad/model/converters/json_commands_test.cpp b/test/module/irohad/model/converters/json_commands_test.cpp index df227e5eca..6e086cda39 100644 --- a/test/module/irohad/model/converters/json_commands_test.cpp +++ b/test/module/irohad/model/converters/json_commands_test.cpp @@ -36,8 +36,8 @@ #include "model/commands/subtract_asset_quantity.hpp" #include "model/commands/transfer_asset.hpp" #include "model/converters/json_command_factory.hpp" -#include "validators/permissions.hpp" #include "model/sha3_hash.hpp" +#include "validators/permissions.hpp" using namespace rapidjson; using namespace iroha; @@ -104,10 +104,8 @@ TEST_F(JsonCommandTest, create_domain) { TEST_F(JsonCommandTest, add_asset_quantity) { auto orig_command = std::make_shared(); - orig_command->account_id = "23"; - iroha::Amount amount(150, 2); - orig_command->amount = amount; + orig_command->amount = "1.50"; orig_command->asset_id = "23"; auto json_command = factory.serializeAddAssetQuantity(orig_command); @@ -125,10 +123,8 @@ TEST_F(JsonCommandTest, add_asset_quantity) { */ TEST_F(JsonCommandTest, subtract_asset_quantity) { auto orig_command = std::make_shared(); - orig_command->account_id = "23"; - iroha::Amount amount(150, 2); - orig_command->amount = amount; + orig_command->amount = "1.50"; orig_command->asset_id = "23"; auto json_command = factory.serializeSubtractAssetQuantity(orig_command); diff --git a/test/module/irohad/model/converters/pb_commands_test.cpp b/test/module/irohad/model/converters/pb_commands_test.cpp index 3b8bf4c783..4ad27dbec4 100644 --- a/test/module/irohad/model/converters/pb_commands_test.cpp +++ b/test/module/irohad/model/converters/pb_commands_test.cpp @@ -48,10 +48,8 @@ void command_converter_test(iroha::model::Command &abstract_command) { TEST(CommandTest, add_asset_quantity) { auto orig_command = iroha::model::AddAssetQuantity(); - orig_command.account_id = "23"; - iroha::Amount amount(50, 1); - orig_command.amount = amount; + orig_command.amount = "5.0"; orig_command.asset_id = "23"; auto factory = iroha::model::converters::PbCommandFactory(); @@ -69,10 +67,8 @@ TEST(CommandTest, add_asset_quantity) { */ TEST(CommandTest, subtract_asset_quantity) { auto orig_command = iroha::model::SubtractAssetQuantity(); - orig_command.account_id = "23"; - iroha::Amount amount(50, 1); - orig_command.amount = amount; + orig_command.amount = "5.0"; orig_command.asset_id = "23"; auto factory = iroha::model::converters::PbCommandFactory(); diff --git a/test/module/irohad/model/converters/pb_query_responses_test.cpp b/test/module/irohad/model/converters/pb_query_responses_test.cpp index 010733f0aa..d29b2222c1 100644 --- a/test/module/irohad/model/converters/pb_query_responses_test.cpp +++ b/test/module/irohad/model/converters/pb_query_responses_test.cpp @@ -64,8 +64,7 @@ TEST(QueryResponseTest, AccountAsset) { model::AccountAsset account_asset; account_asset.account_id = "123"; account_asset.asset_id = "123"; - iroha::Amount amount(1); - account_asset.balance = amount; + account_asset.balance = "1"; auto pb_account_asset = pb_factory.serializeAccountAsset(account_asset); auto des_account_asset = pb_factory.deserializeAccountAsset(pb_account_asset); diff --git a/test/module/irohad/model/model_mocks.hpp b/test/module/irohad/model/model_mocks.hpp deleted file mode 100644 index 094fb6db20..0000000000 --- a/test/module/irohad/model/model_mocks.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef IROHA_MODEL_MOCKS_HPP -#define IROHA_MODEL_MOCKS_HPP - -#include -#include "ametsuchi/wsv_command.hpp" -#include "ametsuchi/wsv_query.hpp" -#include "execution/query_execution.hpp" -#include "model/command.hpp" - -namespace iroha { - namespace model { - - class MockCommand : public Command { - public: - MOCK_METHOD2(validate, bool(ametsuchi::WsvQuery &, const Account &)); - MOCK_METHOD2(execute, - bool(ametsuchi::WsvQuery &, ametsuchi::WsvCommand &)); - - MOCK_CONST_METHOD1(Equals, bool(const Command &)); - bool operator==(const Command &rhs) const override { - return Equals(rhs); - } - - MOCK_CONST_METHOD1(NotEquals, bool(const Command &)); - bool operator!=(const Command &rhs) const override { - return NotEquals(rhs); - } - }; - - class MockQueryProcessingFactory : public QueryProcessingFactory { - public: - MOCK_METHOD1(execute, std::shared_ptr(const Query &query)); - }; - } // namespace model -} // namespace iroha - -#endif // IROHA_MODEL_MOCKS_HPP diff --git a/test/module/irohad/model/operators/model_operators_test.cpp b/test/module/irohad/model/operators/model_operators_test.cpp index a00827eb82..744be3b072 100644 --- a/test/module/irohad/model/operators/model_operators_test.cpp +++ b/test/module/irohad/model/operators/model_operators_test.cpp @@ -58,9 +58,7 @@ TEST(ModelOperatorTest, AddPeerTest) { AddAssetQuantity createAddAssetQuantity() { AddAssetQuantity aaq; - aaq.account_id = "123"; - iroha::Amount amount(1010, 2); - aaq.amount = amount; + aaq.amount = "10.10"; aaq.asset_id = "123"; return aaq; } @@ -78,9 +76,7 @@ TEST(ModelOperatorTest, AddAssetQuantityTest) { SubtractAssetQuantity createSubtractAssetQuantity() { SubtractAssetQuantity saq; - saq.account_id = "acc"; - iroha::Amount amount(1010, 2); - saq.amount = amount; + saq.amount = "10.10"; saq.asset_id = "ast"; return saq; } @@ -214,8 +210,7 @@ TEST(ModelOperatorTest, SetQuorumTest) { TransferAsset createTransferAsset() { TransferAsset transferAsset; transferAsset.asset_id = "123"; - iroha::Amount amount(1010, 2); - transferAsset.amount = amount; + transferAsset.amount = "10.10"; transferAsset.src_account_id = "1"; transferAsset.dest_account_id = "2"; transferAsset.description = "test"; @@ -286,18 +281,6 @@ TEST(ModelOperatorTest, RevokePermissionTest) { ASSERT_NE(first, second); } -// -----|Amount|----- - -TEST(ModelOperatorTest, AmountTest) { - iroha::Amount amount1(1010, 2); - - iroha::Amount amount2(1010, 2); - - ASSERT_EQ(amount1, amount2); - iroha::Amount amount3(1011, 2); - ASSERT_NE(amount1, amount3); -} - // -----|Signature|----- Signature createSignature() { diff --git a/test/module/irohad/network/block_loader_test.cpp b/test/module/irohad/network/block_loader_test.cpp index 6f8e9bfed1..101fddc2bf 100644 --- a/test/module/irohad/network/block_loader_test.cpp +++ b/test/module/irohad/network/block_loader_test.cpp @@ -20,17 +20,14 @@ #include #include -#include "backend/protobuf/block.hpp" -#include "backend/protobuf/common_objects/peer.hpp" #include "builders/common_objects/peer_builder.hpp" -#include "builders/protobuf/block.hpp" -#include "builders/protobuf/builder_templates/block_template.hpp" #include "builders/protobuf/common_objects/proto_peer_builder.hpp" #include "cryptography/crypto_provider/crypto_defaults.hpp" #include "cryptography/hash.hpp" #include "datetime/time.hpp" #include "framework/test_subscriber.hpp" #include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" +#include "module/shared_model/builders/protobuf/test_block_builder.hpp" #include "network/impl/block_loader_impl.hpp" #include "network/impl/block_loader_service.hpp" #include "validators/default_validator.hpp" @@ -51,10 +48,7 @@ class BlockLoaderTest : public testing::Test { void SetUp() override { peer_query = std::make_shared(); storage = std::make_shared(); - loader = std::make_shared( - peer_query, - storage, - std::make_shared()); + loader = std::make_shared(peer_query, storage); service = std::make_shared(storage); grpc::ServerBuilder builder; @@ -82,21 +76,23 @@ class BlockLoaderTest : public testing::Test { } auto getBaseBlockBuilder() const { - constexpr auto kTotal = (1 << 4) - 1; return shared_model::proto::TemplateBlockBuilder< - kTotal, - shared_model::validation::DefaultBlockValidator, - shared_model::proto::Block>() + (1 << shared_model::proto::TemplateBlockBuilder<>::total) - 1, + shared_model::validation::AlwaysValidValidator, + shared_model::proto::UnsignedWrapper< + shared_model::proto::Block>>() .height(1) - .prevHash(Hash(std::string( - shared_model::crypto::DefaultCryptoAlgorithmType::kHashLength, - '0'))) + .prevHash(kPrevHash) .createdTime(iroha::time::now()); } + const Hash kPrevHash = + Hash(std::string(DefaultCryptoAlgorithmType::kHashLength, '0')); + std::shared_ptr peer; PublicKey peer_key = DefaultCryptoAlgorithmType::generateKeypair().publicKey(); + Keypair key = DefaultCryptoAlgorithmType::generateKeypair(); std::shared_ptr peer_query; std::shared_ptr storage; std::shared_ptr loader; @@ -111,14 +107,14 @@ class BlockLoaderTest : public testing::Test { */ TEST_F(BlockLoaderTest, ValidWhenSameTopBlock) { // Current block height 1 => Other block height 1 => no blocks received - auto block = getBaseBlockBuilder().build(); + auto block = getBaseBlockBuilder().build().signAndAddSignature(key).finish(); EXPECT_CALL(*peer_query, getLedgerPeers()) .WillOnce(Return(std::vector{peer})); EXPECT_CALL(*storage, getTopBlock()) .WillOnce(Return(iroha::expected::makeValue(wBlock(clone(block))))); EXPECT_CALL(*storage, getBlocksFrom(block.height() + 1)) - .WillOnce(Return(rxcpp::observable<>::empty())); + .WillOnce(Return(std::vector())); auto wrapper = make_test_subscriber( loader->retrieveBlocks(peer->pubkey()), 0); wrapper.subscribe(); @@ -133,17 +129,27 @@ TEST_F(BlockLoaderTest, ValidWhenSameTopBlock) { */ TEST_F(BlockLoaderTest, ValidWhenOneBlock) { // Current block height 1 => Other block height 2 => one block received - auto block = getBaseBlockBuilder().build(); - - auto top_block = getBaseBlockBuilder().height(block.height() + 1).build(); + // time validation should work based on the block field + // so it should pass stateless BlockLoader validation + auto block = getBaseBlockBuilder() + .createdTime(228) + .build() + .signAndAddSignature(key) + .finish(); + + auto top_block = getBaseBlockBuilder() + .createdTime(block.createdTime() + 1) + .height(block.height() + 1) + .build() + .signAndAddSignature(key) + .finish(); EXPECT_CALL(*peer_query, getLedgerPeers()) .WillOnce(Return(std::vector{peer})); EXPECT_CALL(*storage, getTopBlock()) .WillOnce(Return(iroha::expected::makeValue(wBlock(clone(block))))); EXPECT_CALL(*storage, getBlocksFrom(block.height() + 1)) - .WillOnce(Return(rxcpp::observable<>::just(top_block).map( - [](auto &&x) { return wBlock(clone(x)); }))); + .WillOnce(Return(std::vector{clone(top_block)})); auto wrapper = make_test_subscriber(loader->retrieveBlocks(peer_key), 1); wrapper.subscribe( @@ -159,14 +165,24 @@ TEST_F(BlockLoaderTest, ValidWhenOneBlock) { */ TEST_F(BlockLoaderTest, ValidWhenMultipleBlocks) { // Current block height 1 => Other block height n => n-1 blocks received - auto block = getBaseBlockBuilder().build(); + // time validation should work based on the block field + // so it should pass stateless BlockLoader validation + auto block = getBaseBlockBuilder() + .createdTime(1337) + .build() + .signAndAddSignature(key) + .finish(); auto num_blocks = 2; auto next_height = block.height() + 1; std::vector blocks; for (auto i = next_height; i < next_height + num_blocks; ++i) { - auto blk = getBaseBlockBuilder().height(i).build(); + auto blk = getBaseBlockBuilder() + .height(i) + .build() + .signAndAddSignature(key) + .finish(); blocks.emplace_back(clone(blk)); } @@ -175,7 +191,7 @@ TEST_F(BlockLoaderTest, ValidWhenMultipleBlocks) { EXPECT_CALL(*storage, getTopBlock()) .WillOnce(Return(iroha::expected::makeValue(wBlock(clone(block))))); EXPECT_CALL(*storage, getBlocksFrom(next_height)) - .WillOnce(Return(rxcpp::observable<>::iterate(blocks))); + .WillOnce(Return(blocks)); auto wrapper = make_test_subscriber( loader->retrieveBlocks(peer_key), num_blocks); auto height = next_height; @@ -192,13 +208,13 @@ TEST_F(BlockLoaderTest, ValidWhenMultipleBlocks) { */ TEST_F(BlockLoaderTest, ValidWhenBlockPresent) { // Request existing block => success - auto requested = getBaseBlockBuilder().build(); + auto requested = + getBaseBlockBuilder().build().signAndAddSignature(key).finish(); EXPECT_CALL(*peer_query, getLedgerPeers()) .WillOnce(Return(std::vector{peer})); EXPECT_CALL(*storage, getBlocksFrom(1)) - .WillOnce(Return(rxcpp::observable<>::just(requested).map( - [](auto &&x) { return wBlock(clone(x)); }))); + .WillOnce(Return(std::vector{clone(requested)})); auto block = loader->retrieveBlock(peer_key, requested.hash()); ASSERT_TRUE(block); @@ -212,14 +228,14 @@ TEST_F(BlockLoaderTest, ValidWhenBlockPresent) { */ TEST_F(BlockLoaderTest, ValidWhenBlockMissing) { // Request nonexisting block => failure - auto present = getBaseBlockBuilder().build(); + auto present = + getBaseBlockBuilder().build().signAndAddSignature(key).finish(); EXPECT_CALL(*peer_query, getLedgerPeers()) .WillOnce(Return(std::vector{peer})); EXPECT_CALL(*storage, getBlocksFrom(1)) - .WillOnce(Return(rxcpp::observable<>::just(present).map( - [](auto &&x) { return wBlock(clone(x)); }))); - auto block = loader->retrieveBlock(peer_key, Hash(std::string(32, '0'))); + .WillOnce(Return(std::vector{clone(present)})); + auto block = loader->retrieveBlock(peer_key, kPrevHash); ASSERT_FALSE(block); } diff --git a/test/module/irohad/network/network_mocks.hpp b/test/module/irohad/network/network_mocks.hpp index 9c7e0bcb01..7801639dd7 100644 --- a/test/module/irohad/network/network_mocks.hpp +++ b/test/module/irohad/network/network_mocks.hpp @@ -19,6 +19,7 @@ #define IROHA_NETWORK_MOCKS_HPP #include +#include "interfaces/iroha_internal/transaction_batch.hpp" #include "network/block_loader.hpp" #include "network/consensus_gate.hpp" #include "network/ordering_gate.hpp" @@ -34,16 +35,25 @@ namespace iroha { namespace network { class MockPeerCommunicationService : public PeerCommunicationService { public: - MOCK_METHOD1( + MOCK_CONST_METHOD1( propagate_transaction, void(std::shared_ptr)); + MOCK_CONST_METHOD1( + propagate_batch, + void(const shared_model::interface::TransactionBatch &)); + MOCK_CONST_METHOD0( on_proposal, rxcpp::observable< std::shared_ptr>()); MOCK_CONST_METHOD0(on_commit, rxcpp::observable()); + + MOCK_CONST_METHOD0( + on_verified_proposal, + rxcpp::observable< + std::shared_ptr>()); }; class MockBlockLoader : public BlockLoader { @@ -61,11 +71,15 @@ namespace iroha { class MockOrderingGate : public OrderingGate { public: - MOCK_METHOD1( + MOCK_CONST_METHOD1( propagateTransaction, void(std::shared_ptr transaction)); + MOCK_CONST_METHOD1( + propagateBatch, + void(const shared_model::interface::TransactionBatch &)); + MOCK_METHOD0(on_proposal, rxcpp::observable< std::shared_ptr>()); diff --git a/test/module/irohad/ordering/CMakeLists.txt b/test/module/irohad/ordering/CMakeLists.txt index 5dc742dba8..44b5b51bb1 100644 --- a/test/module/irohad/ordering/CMakeLists.txt +++ b/test/module/irohad/ordering/CMakeLists.txt @@ -16,7 +16,7 @@ addtest(ordering_service_test ordering_service_test.cpp) target_link_libraries(ordering_service_test ordering_service shared_model_stateless_validation - iroha_amount + shared_model_proto_backend ) addtest(ordering_gate_test ordering_gate_test.cpp) @@ -24,5 +24,4 @@ target_link_libraries(ordering_gate_test ordering_service shared_model_cryptography_model shared_model_stateless_validation - iroha_amount ) diff --git a/test/module/irohad/ordering/ordering_gate_test.cpp b/test/module/irohad/ordering/ordering_gate_test.cpp index 049100542b..b758fb4316 100644 --- a/test/module/irohad/ordering/ordering_gate_test.cpp +++ b/test/module/irohad/ordering/ordering_gate_test.cpp @@ -54,6 +54,8 @@ class MockOrderingGateTransport : public OrderingGateTransport { MOCK_METHOD1( propagateTransaction, void(std::shared_ptr)); + MOCK_METHOD1(propagateBatch, + void(const shared_model::interface::TransactionBatch &)); }; class OrderingGateTest : public ::testing::Test { @@ -138,7 +140,7 @@ TEST_F(OrderingGateTest, ProposalReceivedByGateWhenSent) { auto tx = shared_model::proto::TransactionBuilder() .createdTime(iroha::time::now()) .creatorAccountId("admin@ru") - .addAssetQuantity("admin@tu", "coin#coin", "1.0") + .addAssetQuantity("coin#coin", "1.0") .quorum(1) .build() .signAndAddSignature( @@ -212,7 +214,7 @@ TEST_F(QueueBehaviorTest, SendManyProposals) { auto tx = shared_model::proto::TransactionBuilder() .createdTime(iroha::time::now()) .creatorAccountId("admin@ru") - .addAssetQuantity("admin@tu", "coin#coin", "1.0") + .addAssetQuantity("coin#coin", "1.0") .quorum(1) .build() .signAndAddSignature( diff --git a/test/module/irohad/ordering/ordering_service_test.cpp b/test/module/irohad/ordering/ordering_service_test.cpp index 68c0be981f..ed9e5aa224 100644 --- a/test/module/irohad/ordering/ordering_service_test.cpp +++ b/test/module/irohad/ordering/ordering_service_test.cpp @@ -6,14 +6,15 @@ #include #include "backend/protobuf/common_objects/peer.hpp" +#include "backend/protobuf/proto_proposal_factory.hpp" #include "builders/protobuf/common_objects/proto_peer_builder.hpp" -#include "builders/protobuf/transaction.hpp" +#include "framework/batch_helper.hpp" +#include "interfaces/iroha_internal/transaction_batch.hpp" #include "logger/logger.hpp" #include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" #include "module/irohad/network/network_mocks.hpp" #include "module/irohad/ordering/mock_ordering_service_persistent_state.hpp" #include "module/shared_model/builders/protobuf/test_proposal_builder.hpp" -#include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" #include "ordering/impl/ordering_service_impl.hpp" #include "ordering/impl/ordering_service_transport_grpc.hpp" @@ -65,20 +66,8 @@ class OrderingServiceTest : public ::testing::Test { fake_transport = std::make_shared(); fake_persistent_state = std::make_shared(); - } - - auto getTx() { - return std::make_unique( - shared_model::proto::TransactionBuilder() - .createdTime(iroha::time::now()) - .creatorAccountId("admin@ru") - .addAssetQuantity("admin@tu", "coin#coin", "1.0") - .quorum(1) - .build() - .signAndAddSignature( - shared_model::crypto::DefaultCryptoAlgorithmType:: - generateKeypair()) - .finish()); + factory = std::make_unique>(); } auto initOs(size_t max_proposal) { @@ -88,6 +77,7 @@ class OrderingServiceTest : public ::testing::Test { proposal_timeout.get_observable(), fake_transport, fake_persistent_state, + std::move(factory), false); } @@ -102,6 +92,7 @@ class OrderingServiceTest : public ::testing::Test { std::string address{"0.0.0.0:50051"}; std::shared_ptr peer; std::shared_ptr wsv; + std::unique_ptr factory; rxcpp::subjects::subject proposal_timeout; }; @@ -157,7 +148,7 @@ TEST_F(OrderingServiceTest, ValidWhenProposalSizeStrategy) { fake_transport->subscribe(ordering_service); for (size_t i = 0; i < tx_num; ++i) { - ordering_service->onTransaction(getTx()); + ordering_service->onBatch(framework::batch::createValidBatch(1)); } } @@ -188,12 +179,13 @@ TEST_F(OrderingServiceTest, ValidWhenTimerStrategy) { fake_transport->subscribe(ordering_service); for (size_t i = 0; i < 8; ++i) { - ordering_service->onTransaction(getTx()); + ordering_service->onBatch(framework::batch::createValidBatch(1)); } makeProposalTimeout(); - ordering_service->onTransaction(getTx()); - ordering_service->onTransaction(getTx()); + ordering_service->onBatch(framework::batch::createValidBatch(1)); + ordering_service->onBatch(framework::batch::createValidBatch(1)); + makeProposalTimeout(); } @@ -214,7 +206,8 @@ TEST_F(OrderingServiceTest, BrokenPersistentState) { .WillRepeatedly(Return(false)); auto ordering_service = initOs(max_proposal); - ordering_service->onTransaction(getTx()); + ordering_service->onBatch(framework::batch::createValidBatch(1)); + makeProposalTimeout(); } @@ -235,7 +228,7 @@ TEST_F(OrderingServiceTest, ConcurrentGenerateProposal) { auto on_tx = [&]() { for (int i = 0; i < 1000; ++i) { - ordering_service->onTransaction(getTx()); + ordering_service->onBatch(framework::batch::createValidBatch(1)); } }; @@ -261,8 +254,8 @@ TEST_F(OrderingServiceTest, ConcurrentGenerateProposal) { * called after destructor call */ TEST_F(OrderingServiceTest, GenerateProposalDestructor) { - const auto max_proposal = 100000; - const auto commit_delay = 100ms; + const auto max_proposal = 600; + const auto commit_delay = 5s; EXPECT_CALL(*fake_persistent_state, loadProposalHeight()) .Times(1) .WillOnce(Return(boost::optional(1))); @@ -283,11 +276,14 @@ TEST_F(OrderingServiceTest, GenerateProposalDestructor) { rxcpp::observe_on_new_thread()), fake_transport, fake_persistent_state, + std::move(factory), true); auto on_tx = [&]() { - for (int i = 0; i < 1000; ++i) { - ordering_service.onTransaction(getTx()); + // create max_proposal+1 txs, so that publish proposal is invoked at least + // once (concurrency!) + for (int i = 0; i < max_proposal + 1; ++i) { + ordering_service.onBatch(framework::batch::createValidBatch(1)); } }; @@ -296,3 +292,38 @@ TEST_F(OrderingServiceTest, GenerateProposalDestructor) { } EXPECT_CALL(*fake_transport, publishProposalProxy(_, _)).Times(0); } + +/** + * Check that batches are processed by ordering service + * @given ordering service up and running + * @when feeding the ordering service two batches, such that number of + * transactions in both summed is greater than maximum number of transactions + * inside proposal + * @then proposal will still contain number of transactions, equal to sum of the + * batches + */ +TEST_F(OrderingServiceTest, BatchesProceed) { + const auto max_proposal = 12; + const auto first_batch_size = 10; + const auto second_batch_size = 5; + + auto batch_one = framework::batch::createValidBatch(first_batch_size); + auto batch_two = framework::batch::createValidBatch(second_batch_size); + + EXPECT_CALL(*fake_persistent_state, saveProposalHeight(_)) + .Times(1) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*fake_persistent_state, loadProposalHeight()) + .Times(1) + .WillOnce(Return( + boost::optional(first_batch_size + second_batch_size))); + EXPECT_CALL(*fake_transport, publishProposalProxy(_, _)).Times(1); + EXPECT_CALL(*wsv, getLedgerPeers()) + .WillRepeatedly(Return(std::vector{peer})); + + auto ordering_service = initOs(max_proposal); + fake_transport->subscribe(ordering_service); + + ordering_service->onBatch(std::move(batch_one)); + ordering_service->onBatch(std::move(batch_two)); +} diff --git a/test/module/irohad/simulator/simulator_test.cpp b/test/module/irohad/simulator/simulator_test.cpp index 45827be262..a6e5f4bf1c 100644 --- a/test/module/irohad/simulator/simulator_test.cpp +++ b/test/module/irohad/simulator/simulator_test.cpp @@ -15,7 +15,8 @@ * limitations under the License. */ -#include "simulator/impl/simulator.hpp" +#include + #include "backend/protobuf/transaction.hpp" #include "builders/protobuf/proposal.hpp" #include "builders/protobuf/transaction.hpp" @@ -27,6 +28,7 @@ #include "module/shared_model/builders/protobuf/test_block_builder.hpp" #include "module/shared_model/builders/protobuf/test_proposal_builder.hpp" #include "module/shared_model/cryptography/crypto_model_signer_mock.hpp" +#include "simulator/impl/simulator.hpp" using namespace iroha; using namespace iroha::validation; @@ -86,7 +88,7 @@ shared_model::proto::Proposal makeProposal(int height) { auto tx = shared_model::proto::TransactionBuilder() .createdTime(iroha::time::now()) .creatorAccountId("admin@ru") - .addAssetQuantity("admin@tu", "coin#coin", "1.0") + .addAssetQuantity("coin#coin", "1.0") .quorum(1) .build() .signAndAddSignature( @@ -116,7 +118,7 @@ TEST_F(SimulatorTest, ValidWhenPreviousBlock) { auto tx = shared_model::proto::TransactionBuilder() .createdTime(iroha::time::now()) .creatorAccountId("admin@ru") - .addAssetQuantity("admin@tu", "coin#coin", "1.0") + .addAssetQuantity("coin#coin", "1.0") .quorum(1) .build() .signAndAddSignature( @@ -138,7 +140,9 @@ TEST_F(SimulatorTest, ValidWhenPreviousBlock) { EXPECT_CALL(*query, getTopBlockHeight()).WillOnce(Return(1)); - EXPECT_CALL(*validator, validate(_, _)).WillOnce(Return(proposal)); + EXPECT_CALL(*validator, validate(_, _)) + .WillOnce(Return( + std::make_pair(proposal, iroha::validation::TransactionsErrors{}))); EXPECT_CALL(*ordering_gate, on_proposal()) .WillOnce(Return(rxcpp::observable<>::empty< @@ -153,8 +157,10 @@ TEST_F(SimulatorTest, ValidWhenPreviousBlock) { auto proposal_wrapper = make_test_subscriber(simulator->on_verified_proposal(), 1); proposal_wrapper.subscribe([&proposal](auto verified_proposal) { - ASSERT_EQ(verified_proposal->height(), proposal->height()); - ASSERT_EQ(verified_proposal->transactions(), proposal->transactions()); + ASSERT_EQ(verified_proposal->first->height(), proposal->height()); + ASSERT_EQ(verified_proposal->first->transactions(), + proposal->transactions()); + ASSERT_TRUE(verified_proposal->second.empty()); }); auto block_wrapper = @@ -246,3 +252,80 @@ TEST_F(SimulatorTest, FailWhenSameAsProposalHeight) { ASSERT_TRUE(proposal_wrapper.validate()); ASSERT_TRUE(block_wrapper.validate()); } + +/** + * Checks, that after failing a certain number of transactions in a proposal, + * returned verified proposal will have only valid transactions + * + * @given proposal consisting of several transactions + * @when failing some of the transactions in that proposal + * @then verified proposal consists of txs we did not fail + */ +TEST_F(SimulatorTest, RightNumberOfFailedTxs) { + // create a 3-height proposal, but validator returns only a 2-height verified + // proposal + auto tx = shared_model::proto::TransactionBuilder() + .createdTime(iroha::time::now()) + .creatorAccountId("admin@ru") + .addAssetQuantity("coin#coin", "1.0") + .quorum(1) + .build() + .signAndAddSignature( + shared_model::crypto::DefaultCryptoAlgorithmType:: + generateKeypair()) + .finish(); + + std::vector txs = {tx, tx, tx}; + auto proposal = std::make_shared( + shared_model::proto::ProposalBuilder() + .height(3) + .createdTime(iroha::time::now()) + .transactions(txs) + .build()); + auto verified_proposal = std::make_shared( + shared_model::proto::ProposalBuilder() + .height(2) + .createdTime(iroha::time::now()) + .transactions(std::vector{tx}) + .build()); + auto tx_errors = iroha::validation::TransactionsErrors{ + std::make_pair(validation::CommandError{"SomeCommand", "SomeError", true}, + shared_model::crypto::Hash(std::string(32, '0'))), + std::make_pair(validation::CommandError{"SomeCommand", "SomeError", true}, + shared_model::crypto::Hash(std::string(32, '0')))}; + shared_model::proto::Block block = makeBlock(proposal->height() - 1); + + EXPECT_CALL(*factory, createTemporaryWsv()).Times(1); + EXPECT_CALL(*query, getTopBlock()) + .WillOnce(Return(expected::makeValue(wBlock(clone(block))))); + + EXPECT_CALL(*query, getTopBlockHeight()).WillOnce(Return(2)); + + EXPECT_CALL(*validator, validate(_, _)) + .WillOnce(Return(std::make_pair(verified_proposal, tx_errors))); + + EXPECT_CALL(*ordering_gate, on_proposal()) + .WillOnce(Return(rxcpp::observable<>::empty< + std::shared_ptr>())); + + EXPECT_CALL(*shared_model::crypto::crypto_signer_expecter, + sign(A())) + .Times(1); + + init(); + + auto proposal_wrapper = + make_test_subscriber(simulator->on_verified_proposal(), 1); + proposal_wrapper.subscribe([&verified_proposal, + &tx_errors](auto verified_proposal_) { + // assure that txs in verified proposal do not include failed ones + ASSERT_EQ(verified_proposal_->first->height(), verified_proposal->height()); + ASSERT_EQ(verified_proposal_->first->transactions(), + verified_proposal->transactions()); + ASSERT_TRUE(verified_proposal_->second.size() == tx_errors.size()); + }); + + simulator->process_proposal(*proposal); + + ASSERT_TRUE(proposal_wrapper.validate()); +} diff --git a/test/module/irohad/synchronizer/CMakeLists.txt b/test/module/irohad/synchronizer/CMakeLists.txt index 2c2d7b48da..f91ea0ff3f 100644 --- a/test/module/irohad/synchronizer/CMakeLists.txt +++ b/test/module/irohad/synchronizer/CMakeLists.txt @@ -3,6 +3,5 @@ target_link_libraries(synchronizer_test synchronizer shared_model_cryptography shared_model_proto_backend + shared_model_stateless_validation ) - - diff --git a/test/module/irohad/torii/processor/query_processor_test.cpp b/test/module/irohad/torii/processor/query_processor_test.cpp index 6f06449611..0b531f02e5 100644 --- a/test/module/irohad/torii/processor/query_processor_test.cpp +++ b/test/module/irohad/torii/processor/query_processor_test.cpp @@ -1,18 +1,6 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include "backend/protobuf/block.hpp" @@ -23,7 +11,9 @@ #include "execution/query_execution.hpp" #include "framework/specified_visitor.hpp" #include "framework/test_subscriber.hpp" +#include "interfaces/query_responses/block_query_response.hpp" #include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" +#include "module/irohad/execution/execution_mocks.hpp" #include "module/irohad/validation/validation_mocks.hpp" #include "module/shared_model/builders/protobuf/test_block_builder.hpp" #include "module/shared_model/builders/protobuf/test_query_builder.hpp" @@ -47,176 +37,112 @@ using ::testing::Return; class QueryProcessorTest : public ::testing::Test { public: void SetUp() override { - created_time = iroha::time::now(); - account_id = "account@domain"; - counter = 1048576; + qry_exec = std::make_shared(); + storage = std::make_shared(); + qpi = std::make_shared(storage, qry_exec); + wsv_queries = std::make_shared(); + EXPECT_CALL(*storage, getWsvQuery()).WillRepeatedly(Return(wsv_queries)); + EXPECT_CALL(*storage, getBlockQuery()) + .WillRepeatedly(Return(block_queries)); } auto getBlocksQuery(const std::string &creator_account_id) { return TestUnsignedBlocksQueryBuilder() - .createdTime(created_time) + .createdTime(kCreatedTime) .creatorAccountId(creator_account_id) - .queryCounter(counter) + .queryCounter(kCounter) .build() .signAndAddSignature(keypair) .finish(); } - decltype(iroha::time::now()) created_time; - std::string account_id; - uint64_t counter; + const decltype(iroha::time::now()) kCreatedTime = iroha::time::now(); + const std::string kAccountId = "account@domain"; + const uint64_t kCounter = 1048576; shared_model::crypto::Keypair keypair = shared_model::crypto::DefaultCryptoAlgorithmType::generateKeypair(); std::vector signatories = { keypair.publicKey()}; - shared_model::interface::RolePermissionSet perms; - std::vector roles; + std::shared_ptr qry_exec; + std::shared_ptr wsv_queries; + std::shared_ptr block_queries; + std::shared_ptr storage; + std::shared_ptr qpi; }; /** - * @given account, ametsuchi queries and query processing factory - * @when stateless validation error - * @then Query Processor should return ErrorQueryResponse + * @given QueryProcessorImpl and GetAccountDetail query + * @when queryHandle called at normal flow + * @then the mocked value of validateAndExecute is returned */ TEST_F(QueryProcessorTest, QueryProcessorWhereInvokeInvalidQuery) { - auto wsv_queries = std::make_shared(); - auto block_queries = std::make_shared(); - auto storage = std::make_shared(); - auto qpf = - std::make_unique(wsv_queries, block_queries); - - iroha::torii::QueryProcessorImpl qpi(storage); - - auto query = TestUnsignedQueryBuilder() - .createdTime(created_time) - .creatorAccountId(account_id) - .getAccount(account_id) - .queryCounter(counter) - .build() - .signAndAddSignature(keypair) - .finish(); - - std::shared_ptr shared_account = clone( - shared_model::proto::AccountBuilder().accountId(account_id).build()); - - auto role = "admin"; - roles = {role}; - perms = {shared_model::interface::permissions::Role::kGetMyAccount}; - - EXPECT_CALL(*storage, getWsvQuery()).WillRepeatedly(Return(wsv_queries)); - EXPECT_CALL(*storage, getBlockQuery()).WillRepeatedly(Return(block_queries)); - EXPECT_CALL(*wsv_queries, getAccount(account_id)) - .WillOnce(Return(shared_account)); - EXPECT_CALL(*wsv_queries, getAccountRoles(account_id)) - .Times(2) - .WillRepeatedly(Return(roles)); - EXPECT_CALL(*wsv_queries, getRolePermissions(role)).WillOnce(Return(perms)); - EXPECT_CALL(*wsv_queries, getSignatories(account_id)) + auto qry = TestUnsignedQueryBuilder() + .creatorAccountId(kAccountId) + .getAccountDetail(kAccountId) + .build() + .signAndAddSignature(keypair) + .finish(); + auto qry_resp = + clone(TestQueryResponseBuilder().accountDetailResponse("").build()); + + EXPECT_CALL(*wsv_queries, getSignatories(kAccountId)) .WillRepeatedly(Return(signatories)); - - auto wrapper = make_test_subscriber(qpi.queryNotifier(), 1); - wrapper.subscribe([](auto response) { - ASSERT_NO_THROW(boost::apply_visitor( - framework::SpecifiedVisitor(), - response->get())); - }); - qpi.queryHandle( - std::make_shared(query.getTransport())); - ASSERT_TRUE(wrapper.validate()); + EXPECT_CALL(*qry_exec, validateAndExecute(_)) + .WillOnce(Invoke([&qry_resp](auto &query) { return clone(*qry_resp); })); + + auto response = qpi->queryHandle(qry); + ASSERT_TRUE(response); + ASSERT_NO_THROW(boost::apply_visitor( + framework::SpecifiedVisitor< + shared_model::interface::AccountDetailResponse>(), + response->get())); } /** - * @given account, ametsuchi queries and query processing factory - * @when signed with wrong key - * @then Query Processor should return StatefulFailed + * @given QueryProcessorImpl and GetAccountDetail query with wrong signature + * @when queryHandle called at normal flow + * @then Query Processor returns StatefulFailed response */ TEST_F(QueryProcessorTest, QueryProcessorWithWrongKey) { - auto wsv_queries = std::make_shared(); - auto block_queries = std::make_shared(); - auto storage = std::make_shared(); - auto qpf = - std::make_unique(wsv_queries, block_queries); - - iroha::torii::QueryProcessorImpl qpi(storage); - auto query = TestUnsignedQueryBuilder() - .createdTime(created_time) - .creatorAccountId(account_id) - .getAccount(account_id) - .queryCounter(counter) + .creatorAccountId(kAccountId) + .getAccountDetail(kAccountId) .build() .signAndAddSignature( shared_model::crypto::DefaultCryptoAlgorithmType:: generateKeypair()) .finish(); + auto qry_resp = + clone(TestQueryResponseBuilder().accountDetailResponse("").build()); - std::shared_ptr shared_account = clone( - shared_model::proto::AccountBuilder().accountId(account_id).build()); - auto role = "admin"; - roles = {role}; - perms = {shared_model::interface::permissions::Role::kGetMyAccount}; - - EXPECT_CALL(*storage, getWsvQuery()).WillRepeatedly(Return(wsv_queries)); - EXPECT_CALL(*storage, getBlockQuery()).WillRepeatedly(Return(block_queries)); - EXPECT_CALL(*wsv_queries, getSignatories(account_id)) + EXPECT_CALL(*wsv_queries, getSignatories(kAccountId)) .WillRepeatedly(Return(signatories)); - auto wrapper = make_test_subscriber(qpi.queryNotifier(), 1); - wrapper.subscribe([](auto response) { - ASSERT_TRUE(boost::apply_visitor( - shared_model::interface::QueryErrorResponseChecker< - shared_model::interface::StatefulFailedErrorResponse>(), - response->get())); - }); - qpi.queryHandle( - std::make_shared(query.getTransport())); - ASSERT_TRUE(wrapper.validate()); + auto response = qpi->queryHandle(query); + ASSERT_TRUE(response); + ASSERT_NO_THROW(boost::apply_visitor( + shared_model::interface::QueryErrorResponseChecker< + shared_model::interface::StatefulFailedErrorResponse>(), + response->get())); } /** - * @given account, ametsuchi queries and query processing factory + * @given account, ametsuchi queries * @when valid block query is send * @then Query Processor should start emitting BlockQueryRespones to the * observable */ TEST_F(QueryProcessorTest, GetBlocksQuery) { - auto wsv_queries = std::make_shared(); - auto block_queries = std::make_shared(); - auto storage = std::make_shared(); - auto blockNumber = 5; - auto qpf = - std::make_unique(wsv_queries, block_queries); - - iroha::torii::QueryProcessorImpl qpi(storage); + auto block_number = 5; + auto block_query = getBlocksQuery(kAccountId); - auto blockQuery = TestUnsignedBlocksQueryBuilder() - .createdTime(created_time) - .creatorAccountId(account_id) - .queryCounter(counter) - .build() - .signAndAddSignature(keypair) - .finish(); - - std::shared_ptr shared_account = clone( - shared_model::proto::AccountBuilder().accountId(account_id).build()); - - auto role = "admin"; - std::vector roles = {role}; - perms = {shared_model::interface::permissions::Role::kGetMyAccount, - shared_model::interface::permissions::Role::kGetBlocks}; - EXPECT_CALL(*storage, getWsvQuery()).WillRepeatedly(Return(wsv_queries)); - EXPECT_CALL(*storage, getBlockQuery()).WillRepeatedly(Return(block_queries)); - EXPECT_CALL(*wsv_queries, getAccountRoles(account_id)) - .WillOnce(Return(roles)); - EXPECT_CALL(*wsv_queries, getRolePermissions(role)).WillOnce(Return(perms)); - EXPECT_CALL(*wsv_queries, getSignatories(account_id)) - .WillRepeatedly(Return(signatories)); + EXPECT_CALL(*wsv_queries, getSignatories(kAccountId)) + .WillOnce(Return(signatories)); + EXPECT_CALL(*qry_exec, validate(_)).WillOnce(Return(true)); auto wrapper = make_test_subscriber( - qpi.blocksQueryHandle(std::make_shared( - blockQuery.getTransport())), - blockNumber); + qpi->blocksQueryHandle(block_query), block_number); wrapper.subscribe([](auto response) { ASSERT_NO_THROW({ boost::apply_visitor( @@ -224,57 +150,28 @@ TEST_F(QueryProcessorTest, GetBlocksQuery) { response->get()); }); }); - for (int i = 0; i < blockNumber; i++) { + for (int i = 0; i < block_number; i++) { storage->notifier.get_subscriber().on_next( - clone(TestBlockBuilder() - .height(1) - .prevHash(shared_model::crypto::Hash(std::string(32, '0'))) - .build())); + clone(TestBlockBuilder().build())); } ASSERT_TRUE(wrapper.validate()); } /** - * @given account, ametsuchi queries and query processing factory + * @given account, ametsuchi queries * @when valid block query is invalid (no can_get_blocks permission) * @then Query Processor should return an observable with blockError */ TEST_F(QueryProcessorTest, GetBlocksQueryNoPerms) { - auto wsv_queries = std::make_shared(); - auto block_queries = std::make_shared(); - auto storage = std::make_shared(); - auto blockNumber = 5; - auto qpf = - std::make_unique(wsv_queries, block_queries); - - iroha::torii::QueryProcessorImpl qpi(storage); - - auto blockQuery = TestUnsignedBlocksQueryBuilder() - .createdTime(created_time) - .creatorAccountId(account_id) - .queryCounter(counter) - .build() - .signAndAddSignature(keypair) - .finish(); - - std::shared_ptr shared_account = clone( - shared_model::proto::AccountBuilder().accountId(account_id).build()); + auto block_number = 5; + auto block_query = getBlocksQuery(kAccountId); - auto role = "admin"; - std::vector roles = {role}; - perms = {shared_model::interface::permissions::Role::kGetMyAccount}; - EXPECT_CALL(*storage, getWsvQuery()).WillRepeatedly(Return(wsv_queries)); - EXPECT_CALL(*storage, getBlockQuery()).WillRepeatedly(Return(block_queries)); - EXPECT_CALL(*wsv_queries, getAccountRoles(account_id)) - .WillOnce(Return(roles)); - EXPECT_CALL(*wsv_queries, getRolePermissions(role)).WillOnce(Return(perms)); - EXPECT_CALL(*wsv_queries, getSignatories(account_id)) + EXPECT_CALL(*wsv_queries, getSignatories(kAccountId)) .WillRepeatedly(Return(signatories)); + EXPECT_CALL(*qry_exec, validate(_)).WillOnce(Return(false)); - auto wrapper = make_test_subscriber( - qpi.blocksQueryHandle(std::make_shared( - blockQuery.getTransport())), - 1); + auto wrapper = + make_test_subscriber(qpi->blocksQueryHandle(block_query), 1); wrapper.subscribe([](auto response) { ASSERT_NO_THROW({ boost::apply_visitor(framework::SpecifiedVisitor< @@ -282,7 +179,7 @@ TEST_F(QueryProcessorTest, GetBlocksQueryNoPerms) { response->get()); }); }); - for (int i = 0; i < blockNumber; i++) { + for (int i = 0; i < block_number; i++) { storage->notifier.get_subscriber().on_next( clone(TestBlockBuilder() .height(1) @@ -291,110 +188,3 @@ TEST_F(QueryProcessorTest, GetBlocksQueryNoPerms) { } ASSERT_TRUE(wrapper.validate()); } - -/** - * @given admin with permisisons to get blocks - * @and user with no permissions to get blocks - * @when admin sends blocks query - * @and user sends blocks query - * @then admin will get only block response - * @and user will get only block error response with stateful invalid message - */ -TEST_F(QueryProcessorTest, NoOneSeesStatefulInvalidButCaller) { - auto wsv_queries = std::make_shared(); - auto block_queries = std::make_shared(); - auto storage = std::make_shared(); - - auto qpf = - std::make_unique(wsv_queries, block_queries); - - iroha::torii::QueryProcessorImpl qpi(storage); - - std::shared_ptr account_with_perms = clone( - shared_model::proto::AccountBuilder().accountId(account_id).build()); - - std::shared_ptr account_without_perms = - clone( - shared_model::proto::AccountBuilder().accountId(account_id).build()); - - auto admin_account_id = "admin@test"; - auto user_account_id = "user@test"; - - auto admin_role = "admin"; - auto user_role = "user"; - - shared_model::interface::RolePermissionSet admin_perms = { - shared_model::interface::permissions::Role::kGetMyAccount, - shared_model::interface::permissions::Role::kGetBlocks}; - shared_model::interface::RolePermissionSet user_perms = { - shared_model::interface::permissions::Role::kGetMyAccount}; - - EXPECT_CALL(*storage, getWsvQuery()).WillRepeatedly(Return(wsv_queries)); - EXPECT_CALL(*storage, getBlockQuery()).WillRepeatedly(Return(block_queries)); - - EXPECT_CALL(*wsv_queries, getSignatories(admin_account_id)) - .WillRepeatedly(Return(signatories)); - EXPECT_CALL(*wsv_queries, getSignatories(user_account_id)) - .WillRepeatedly(Return(signatories)); - - EXPECT_CALL(*wsv_queries, getAccountRoles(admin_account_id)) - .WillOnce(Return(std::vector{admin_role})); - EXPECT_CALL(*wsv_queries, getAccountRoles(user_account_id)) - .WillOnce(Return(std::vector{user_role})); - - EXPECT_CALL(*wsv_queries, getRolePermissions(admin_role)) - .WillOnce(Return(admin_perms)); - EXPECT_CALL(*wsv_queries, getRolePermissions(user_role)) - .WillOnce(Return(user_perms)); - - auto expected_block = - TestBlockBuilder() - .height(1) - .prevHash(shared_model::crypto::Hash(std::string(32, '0'))) - .build(); - - auto admin_block_query = getBlocksQuery(admin_account_id); - - // check that admin can get block and do not get anything but block responses - auto admin_wrapper = make_test_subscriber( - qpi.blocksQueryHandle(std::make_shared( - admin_block_query.getTransport())), - 1); - admin_wrapper.subscribe([&expected_block]( - const std::shared_ptr< - shared_model::interface::BlockQueryResponse> - &block) { - ASSERT_NO_THROW({ - auto &block_response = boost::apply_visitor( - framework::SpecifiedVisitor(), - block->get()); - ASSERT_EQ(block_response.block(), expected_block); - }); - }); - - auto user_block_query = getBlocksQuery(user_account_id); - - // check that user without can_get_blocks permission will not get anything but - // block error response - auto user_wrapper = make_test_subscriber( - qpi.blocksQueryHandle(std::make_shared( - user_block_query.getTransport())), - 1); - user_wrapper.subscribe( - [](const std::shared_ptr - &block) { - ASSERT_NO_THROW({ - auto &block_response = boost::apply_visitor( - framework::SpecifiedVisitor< - shared_model::interface::BlockErrorResponse>(), - block->get()); - ASSERT_EQ(block_response.message(), "Stateful invalid"); - }); - }); - - // apply expected block to the ledger - storage->notifier.get_subscriber().on_next(clone(expected_block)); - - ASSERT_TRUE(admin_wrapper.validate()); - ASSERT_TRUE(user_wrapper.validate()); -} diff --git a/test/module/irohad/torii/processor/transaction_processor_test.cpp b/test/module/irohad/torii/processor/transaction_processor_test.cpp index c231bf36d3..69a3d4c15b 100644 --- a/test/module/irohad/torii/processor/transaction_processor_test.cpp +++ b/test/module/irohad/torii/processor/transaction_processor_test.cpp @@ -7,13 +7,17 @@ #include "builders/protobuf/common_objects/proto_signature_builder.hpp" #include "builders/protobuf/proposal.hpp" #include "builders/protobuf/transaction.hpp" +#include "framework/batch_helper.hpp" #include "framework/specified_visitor.hpp" #include "framework/test_subscriber.hpp" +#include "interfaces/iroha_internal/transaction_sequence.hpp" #include "module/irohad/multi_sig_transactions/mst_mocks.hpp" #include "module/irohad/network/network_mocks.hpp" +#include "module/irohad/torii/torii_mocks.hpp" #include "module/shared_model/builders/protobuf/test_block_builder.hpp" #include "module/shared_model/builders/protobuf/test_proposal_builder.hpp" #include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" +#include "torii/impl/status_bus_impl.hpp" #include "torii/processor/transaction_processor_impl.hpp" using namespace iroha; @@ -35,13 +39,16 @@ class TransactionProcessorTest : public ::testing::Test { .WillRepeatedly(Return(prop_notifier.get_observable())); EXPECT_CALL(*pcs, on_commit()) .WillRepeatedly(Return(commit_notifier.get_observable())); + EXPECT_CALL(*pcs, on_verified_proposal()) + .WillRepeatedly(Return(verified_prop_notifier.get_observable())); EXPECT_CALL(*mp, onPreparedTransactionsImpl()) .WillRepeatedly(Return(mst_prepared_notifier.get_observable())); EXPECT_CALL(*mp, onExpiredTransactionsImpl()) .WillRepeatedly(Return(mst_expired_notifier.get_observable())); - tp = std::make_shared(pcs, mp); + status_bus = std::make_shared(); + tp = std::make_shared(pcs, mp, status_bus); } auto base_tx() { @@ -70,8 +77,7 @@ class TransactionProcessorTest : public ::testing::Test { auto tx_status = status_map.find(tx.hash()); ASSERT_NE(tx_status, status_map.end()); ASSERT_NO_THROW(boost::apply_visitor( - framework::SpecifiedVisitor(), - tx_status->second->get())); + framework::SpecifiedVisitor(), tx_status->second->get())); } } @@ -79,6 +85,7 @@ class TransactionProcessorTest : public ::testing::Test { rxcpp::subjects::subject mst_expired_notifier; std::shared_ptr pcs; + std::shared_ptr status_bus; std::shared_ptr tp; std::shared_ptr mp; @@ -90,6 +97,9 @@ class TransactionProcessorTest : public ::testing::Test { rxcpp::subjects::subject> prop_notifier; rxcpp::subjects::subject commit_notifier; + rxcpp::subjects::subject< + std::shared_ptr> + verified_prop_notifier; const size_t proposal_size = 5; const size_t block_size = 3; @@ -109,11 +119,11 @@ TEST_F(TransactionProcessorTest, TransactionProcessorOnProposalTest) { status_builder.notReceived().txHash(tx.hash()).build(); } - auto wrapper = - make_test_subscriber(tp->transactionNotifier(), proposal_size); - wrapper.subscribe([this](auto response) { - status_map[response->transactionHash()] = response; - }); + EXPECT_CALL(*status_bus, publish(_)) + .Times(proposal_size) + .WillRepeatedly(testing::Invoke([this](auto response) { + status_map[response->transactionHash()] = response; + })); EXPECT_CALL(*mp, propagateTransactionImpl(_)).Times(0); EXPECT_CALL(*pcs, propagate_transaction(_)).Times(txs.size()); @@ -130,12 +140,68 @@ TEST_F(TransactionProcessorTest, TransactionProcessorOnProposalTest) { prop_notifier.get_subscriber().on_next(proposal); prop_notifier.get_subscriber().on_completed(); - ASSERT_TRUE(wrapper.validate()); - SCOPED_TRACE("Stateless valid status verification"); validateStatuses(txs); } +/** + * @given transactions from the same batch + * @when transactions sequence is created and propagated @and all transactions + * were returned by pcs in proposal notifier + * @then all transactions have stateless valid status + */ +TEST_F(TransactionProcessorTest, TransactionProcessorOnProposalBatchTest) { + using namespace shared_model::validation; + using TxValidator = + TransactionValidator>; + + using TxsValidator = + UnsignedTransactionsCollectionValidator; + + auto transactions = + framework::batch::createValidBatch(proposal_size).transactions(); + + EXPECT_CALL(*status_bus, publish(_)) + .Times(proposal_size) + .WillRepeatedly(testing::Invoke([this](auto response) { + status_map[response->transactionHash()] = response; + })); + + auto transaction_sequence_result = + shared_model::interface::TransactionSequence::createTransactionSequence( + transactions, TxsValidator()); + auto transaction_sequence = + framework::expected::val(transaction_sequence_result).value().value; + + EXPECT_CALL(*mp, propagateTransactionImpl(_)).Times(0); + EXPECT_CALL(*pcs, propagate_batch(_)) + .Times(transaction_sequence.batches().size()); + + tp->transactionSequenceHandle(transaction_sequence); + + // create proposal from sequence transactions and notify about it + std::vector proto_transactions; + + std::transform( + transactions.begin(), + transactions.end(), + std::back_inserter(proto_transactions), + [](const auto tx) { + return *std::static_pointer_cast(tx); + }); + + auto proposal = std::make_shared( + TestProposalBuilder().transactions(proto_transactions).build()); + + prop_notifier.get_subscriber().on_next(proposal); + prop_notifier.get_subscriber().on_completed(); + + SCOPED_TRACE("Stateless valid status verification"); + validateStatuses( + proto_transactions); +} + /** * @given transaction processor * @when transactions compose proposal which is sent to peer @@ -151,13 +217,11 @@ TEST_F(TransactionProcessorTest, TransactionProcessorBlockCreatedTest) { status_builder.notReceived().txHash(tx.hash()).build(); } - auto wrapper = make_test_subscriber( - tp->transactionNotifier(), - txs.size() * 2); // every transaction is notified that it is stateless - // valid and then stateful valid - wrapper.subscribe([this](auto response) { - status_map[response->transactionHash()] = response; - }); + EXPECT_CALL(*status_bus, publish(_)) + .Times(txs.size() * 2) + .WillRepeatedly(testing::Invoke([this](auto response) { + status_map[response->transactionHash()] = response; + })); EXPECT_CALL(*mp, propagateTransactionImpl(_)).Times(0); EXPECT_CALL(*pcs, propagate_transaction(_)).Times(txs.size()); @@ -174,6 +238,11 @@ TEST_F(TransactionProcessorTest, TransactionProcessorBlockCreatedTest) { prop_notifier.get_subscriber().on_next(proposal); prop_notifier.get_subscriber().on_completed(); + // empty transactions errors - all txs are valid + verified_prop_notifier.get_subscriber().on_next( + std::make_shared( + std::make_pair(proposal, iroha::validation::TransactionsErrors{}))); + auto block = TestBlockBuilder().transactions(txs).build(); // 2. Create block and notify transaction processor about it @@ -187,8 +256,6 @@ TEST_F(TransactionProcessorTest, TransactionProcessorBlockCreatedTest) { // Note blocks_notifier hasn't invoked on_completed, so // transactions are not commited - ASSERT_TRUE(wrapper.validate()); - SCOPED_TRACE("Stateful valid status verification"); validateStatuses(txs); } @@ -209,14 +276,11 @@ TEST_F(TransactionProcessorTest, TransactionProcessorOnCommitTest) { status_builder.notReceived().txHash(tx.hash()).build(); } - auto wrapper = make_test_subscriber( - tp->transactionNotifier(), - txs.size() * 3); // evey transaction is notified that it is first - // stateless valid, then stateful valid and - // eventually committed - wrapper.subscribe([this](auto response) { - status_map[response->transactionHash()] = response; - }); + EXPECT_CALL(*status_bus, publish(_)) + .Times(txs.size() * 3) + .WillRepeatedly(testing::Invoke([this](auto response) { + status_map[response->transactionHash()] = response; + })); EXPECT_CALL(*mp, propagateTransactionImpl(_)).Times(0); EXPECT_CALL(*pcs, propagate_transaction(_)).Times(txs.size()); @@ -233,6 +297,11 @@ TEST_F(TransactionProcessorTest, TransactionProcessorOnCommitTest) { prop_notifier.get_subscriber().on_next(proposal); prop_notifier.get_subscriber().on_completed(); + // empty transactions errors - all txs are valid + verified_prop_notifier.get_subscriber().on_next( + std::make_shared( + std::make_pair(proposal, iroha::validation::TransactionsErrors{}))); + auto block = TestBlockBuilder().transactions(txs).build(); // 2. Create block and notify transaction processor about it @@ -240,8 +309,6 @@ TEST_F(TransactionProcessorTest, TransactionProcessorOnCommitTest) { std::shared_ptr(clone(block))); commit_notifier.get_subscriber().on_next(single_commit); - ASSERT_TRUE(wrapper.validate()); - SCOPED_TRACE("Committed status verification"); validateStatuses(txs); } @@ -250,9 +317,10 @@ TEST_F(TransactionProcessorTest, TransactionProcessorOnCommitTest) { * @given transaction processor * @when transactions compose proposal which is sent to peer * communication service @and some transactions became part of block, while some - * were not committed - * @then for every transaction from block COMMIT status is returned @and for - * every transaction not from block STATEFUL_INVALID_STATUS was returned + * were not committed, failing stateful validation + * @then for every transaction from block COMMIT status is returned @and + * for every transaction, which failed stateful validation, + * STATEFUL_INVALID_STATUS status is returned */ TEST_F(TransactionProcessorTest, TransactionProcessorInvalidTxsTest) { std::vector block_txs; @@ -263,9 +331,8 @@ TEST_F(TransactionProcessorTest, TransactionProcessorInvalidTxsTest) { status_builder.notReceived().txHash(tx.hash()).build(); } - std::vector - invalid_txs; // transactions will be stateful invalid if appeared - // in proposal but didn't appear in block + std::vector invalid_txs; + for (size_t i = block_size; i < proposal_size; i++) { auto &&tx = TestTransactionBuilder().createdTime(i).build(); invalid_txs.push_back(tx); @@ -273,20 +340,18 @@ TEST_F(TransactionProcessorTest, TransactionProcessorInvalidTxsTest) { status_builder.notReceived().txHash(tx.hash()).build(); } - auto wrapper = make_test_subscriber( - tp->transactionNotifier(), - proposal_size * 2 - + block_size); // For all transactions from proposal - // transaction notifier will notified - // twice (first that they are stateless - // valid and second that they either - // passed or not stateful validation) - // Plus all transactions from block will - // be committed and corresponding status will be sent - - wrapper.subscribe([this](auto response) { - status_map[response->transactionHash()] = response; - }); + // For all transactions from proposal + // transaction will be published twice + // (first that they are stateless + // valid and second that they either + // passed or not stateful validation) + // Plus all transactions from block will + // be committed and corresponding status will be sent + EXPECT_CALL(*status_bus, publish(_)) + .Times(proposal_size * 2 + block_size) + .WillRepeatedly(testing::Invoke([this](auto response) { + status_map[response->transactionHash()] = response; + })); auto proposal = std::make_shared( TestProposalBuilder() @@ -296,12 +361,25 @@ TEST_F(TransactionProcessorTest, TransactionProcessorInvalidTxsTest) { prop_notifier.get_subscriber().on_next(proposal); prop_notifier.get_subscriber().on_completed(); + // trigger the verified event with txs, which we want to fail, as errors + auto verified_proposal = std::make_shared( + TestProposalBuilder().transactions(block_txs).build()); + auto txs_errors = iroha::validation::TransactionsErrors{}; + for (size_t i = 0; i < invalid_txs.size(); ++i) { + txs_errors.push_back(std::make_pair( + iroha::validation::CommandError{ + "SomeCommandName", "SomeCommandError", true, i}, + invalid_txs[i].hash())); + } + verified_prop_notifier.get_subscriber().on_next( + std::make_shared( + std::make_pair(verified_proposal, txs_errors))); + auto block = TestBlockBuilder().transactions(block_txs).build(); Commit single_commit = rxcpp::observable<>::just( std::shared_ptr(clone(block))); commit_notifier.get_subscriber().on_next(single_commit); - ASSERT_TRUE(wrapper.validate()); { SCOPED_TRACE("Stateful invalid status verification"); @@ -372,16 +450,13 @@ TEST_F(TransactionProcessorTest, MultisigExpired) { shared_model::crypto::DefaultCryptoAlgorithmType:: generateKeypair()) .finish()); - - auto wrapper = make_test_subscriber(tp->transactionNotifier(), 1); - wrapper.subscribe([](auto response) { - ASSERT_NO_THROW( - boost::apply_visitor(framework::SpecifiedVisitor< - shared_model::interface::MstExpiredResponse>(), - response->get())); - }); + EXPECT_CALL(*status_bus, publish(_)) + .WillOnce(testing::Invoke([](auto response) { + ASSERT_NO_THROW(boost::apply_visitor( + framework::SpecifiedVisitor< + shared_model::interface::MstExpiredResponse>(), + response->get())); + })); tp->transactionHandle(tx); mst_expired_notifier.get_subscriber().on_next(tx); - - ASSERT_TRUE(wrapper.validate()); } diff --git a/test/module/irohad/torii/query_service_test.cpp b/test/module/irohad/torii/query_service_test.cpp index 863188becc..0a4ba15247 100644 --- a/test/module/irohad/torii/query_service_test.cpp +++ b/test/module/irohad/torii/query_service_test.cpp @@ -51,25 +51,25 @@ class QueryServiceTest : public ::testing::Test { shared_model::crypto::DefaultCryptoAlgorithmType:: generateKeypair()) .finish()); - - auto account = shared_model::proto::AccountBuilder() - .accountId("a") - .domainId("ru") - .quorum(2) - .build(); - - model_response = clone(TestQueryResponseBuilder() - .accountResponse(account, {"user"}) - .queryHash(query->hash()) - .build()); } void init() { query_service = std::make_shared(query_processor); } + std::unique_ptr getResponse() { + static auto account = shared_model::proto::AccountBuilder() + .accountId("a") + .domainId("ru") + .quorum(2) + .build(); + return clone(TestQueryResponseBuilder() + .accountResponse(account, {"user"}) + .queryHash(query->hash()) + .build()); + } + std::shared_ptr query; - std::shared_ptr model_response; std::shared_ptr query_service; std::shared_ptr query_processor; }; @@ -81,60 +81,19 @@ class QueryServiceTest : public ::testing::Test { */ TEST_F(QueryServiceTest, ValidWhenUniqueHash) { // unique query => query handled by query processor - rxcpp::subjects::subject< - std::shared_ptr> - notifier; - EXPECT_CALL(*query_processor, queryNotifier()) - .WillOnce(Return(notifier.get_observable())); - EXPECT_CALL( - *query_processor, - queryHandle( - // match by shared_ptr's content - Truly([this](std::shared_ptr rhs) { - return *rhs == *query; - }))) - .WillOnce(Invoke([this, ¬ifier](auto q) { - notifier.get_subscriber().on_next(model_response); - })); - init(); - - protocol::QueryResponse response; - query_service->Find(query->getTransport(), response); - auto resp = shared_model::proto::QueryResponse(response); - ASSERT_EQ(resp, *model_response); -} - -/** - * @given query and expected response - * @when query is sent to query service and query_processor does not process - * query - * @then NOT_SUPPORTED error response is returned - */ -TEST_F(QueryServiceTest, InvalidWhenUniqueHash) { - // unique query => query handled by query processor - rxcpp::subjects::subject< - std::shared_ptr> - notifier; - EXPECT_CALL(*query_processor, queryNotifier()) - .WillOnce(Return(notifier.get_observable())); - EXPECT_CALL( - *query_processor, - queryHandle( - // match by shared_ptr's content - Truly([this](std::shared_ptr rhs) { - return *rhs == *query; - }))) - .WillOnce(Return()); + EXPECT_CALL(*query_processor, + queryHandle( + // match by shared_ptr's content + Truly([this](const shared_model::interface::Query &rhs) { + return rhs == *query; + }))) + .WillOnce(Invoke([this](auto &) { return this->getResponse(); })); init(); protocol::QueryResponse response; query_service->Find(query->getTransport(), response); - ASSERT_TRUE(response.has_error_response()); auto resp = shared_model::proto::QueryResponse(response); - ASSERT_TRUE(boost::apply_visitor( - shared_model::interface::QueryErrorResponseChecker< - shared_model::interface::NotSupportedErrorResponse>(), - resp.get())); + ASSERT_EQ(resp, *getResponse()); } /** @@ -145,10 +104,9 @@ TEST_F(QueryServiceTest, InvalidWhenUniqueHash) { */ TEST_F(QueryServiceTest, InvalidWhenDuplicateHash) { // two same queries => only first query handled by query processor - EXPECT_CALL(*query_processor, queryNotifier()) - .WillOnce( - Return(rxcpp::observable<>::empty>())); - EXPECT_CALL(*query_processor, queryHandle(_)).WillOnce(Return()); + EXPECT_CALL(*query_processor, queryHandle(_)).WillOnce(Invoke([this](auto &) { + return this->getResponse(); + })); init(); diff --git a/test/module/irohad/torii/torii_mocks.hpp b/test/module/irohad/torii/torii_mocks.hpp index 974d56334f..cdc4a72123 100644 --- a/test/module/irohad/torii/torii_mocks.hpp +++ b/test/module/irohad/torii/torii_mocks.hpp @@ -1,43 +1,37 @@ /** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #ifndef IROHA_TORII_MOCKS_HPP #define IROHA_TORII_MOCKS_HPP -#include "torii/processor/query_processor.hpp" - #include +#include "interfaces/query_responses/block_query_response.hpp" +#include "interfaces/query_responses/query_response.hpp" +#include "torii/processor/query_processor.hpp" +#include "torii/status_bus.hpp" + namespace iroha { namespace torii { class MockQueryProcessor : public QueryProcessor { public: MOCK_METHOD1(queryHandle, - void(std::shared_ptr)); + std::unique_ptr( + const shared_model::interface::Query &)); MOCK_METHOD1( blocksQueryHandle, rxcpp::observable< std::shared_ptr>( - std::shared_ptr)); - MOCK_METHOD0( - queryNotifier, - rxcpp::observable< - std::shared_ptr>()); + const shared_model::interface::BlocksQuery &)); + }; + + class MockStatusBus : public StatusBus { + public: + MOCK_METHOD1(publish, void(StatusBus::Objects)); + MOCK_METHOD0(statuses, rxcpp::observable()); }; } // namespace torii } // namespace iroha diff --git a/test/module/irohad/torii/torii_queries_test.cpp b/test/module/irohad/torii/torii_queries_test.cpp index dfbe3a1f29..13ee2673e3 100644 --- a/test/module/irohad/torii/torii_queries_test.cpp +++ b/test/module/irohad/torii/torii_queries_test.cpp @@ -1,32 +1,22 @@ -/* -Copyright Soramitsu Co., Ltd. 2016 All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ #include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" #include "module/irohad/network/network_mocks.hpp" #include "module/irohad/torii/torii_mocks.hpp" #include "module/irohad/validation/validation_mocks.hpp" +#include "backend/protobuf/query_responses/proto_query_response.hpp" #include "builders/protobuf/common_objects/proto_account_asset_builder.hpp" #include "builders/protobuf/common_objects/proto_account_builder.hpp" -#include "builders/protobuf/common_objects/proto_amount_builder.hpp" #include "builders/protobuf/common_objects/proto_asset_builder.hpp" #include "builders/protobuf/queries.hpp" #include "module/shared_model/builders/protobuf/test_query_builder.hpp" #include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" +#include "execution/query_execution_impl.hpp" #include "framework/specified_visitor.hpp" #include "main/server_runner.hpp" #include "torii/processor/query_processor_impl.hpp" @@ -59,11 +49,12 @@ class ToriiQueriesTest : public testing::Test { //----------- Query Service ---------- - auto qpi = std::make_shared(storage); - EXPECT_CALL(*storage, getWsvQuery()).WillRepeatedly(Return(wsv_query)); EXPECT_CALL(*storage, getBlockQuery()).WillRepeatedly(Return(block_query)); + auto qpi = std::make_shared( + storage, std::make_shared(storage)); + //----------- Server run ---------------- runner->append(std::make_unique(qpi)) .run() @@ -154,12 +145,6 @@ TEST_F(ToriiQueriesTest, FindAccountWhenNoGrantPermissions) { shared_model::proto::AccountBuilder().accountId("b@domain").build(); auto creator = "a@domain"; - EXPECT_CALL( - *wsv_query, - hasAccountGrantablePermission( - creator, account.accountId(), permissionOf(Role::kGetMyAccount))) - .WillOnce(Return(false)); - EXPECT_CALL(*wsv_query, getSignatories(creator)) .WillRepeatedly(Return(signatories)); EXPECT_CALL(*wsv_query, getAccountRoles(creator)) @@ -199,11 +184,6 @@ TEST_F(ToriiQueriesTest, FindAccountWhenHasReadPermissions) { EXPECT_CALL(*wsv_query, getSignatories(creator)) .WillRepeatedly(Return(signatories)); - EXPECT_CALL( - *wsv_query, - hasAccountGrantablePermission( - creator, accountB->accountId(), permissionOf(Role::kGetMyAccount))) - .WillOnce(Return(true)); // Should be called once, after successful stateful validation EXPECT_CALL(*wsv_query, getAccount(accountB->accountId())) @@ -211,6 +191,9 @@ TEST_F(ToriiQueriesTest, FindAccountWhenHasReadPermissions) { std::vector roles = {"user"}; EXPECT_CALL(*wsv_query, getAccountRoles(_)).WillRepeatedly(Return(roles)); + EXPECT_CALL(*wsv_query, getRolePermissions(_)) + .WillOnce(Return(shared_model::interface::RolePermissionSet( + {shared_model::interface::permissions::Role::kGetAllAccounts}))); iroha::protocol::QueryResponse response; @@ -296,10 +279,6 @@ TEST_F(ToriiQueriesTest, FindAccountAssetWhenNoGrantPermissions) { EXPECT_CALL(*wsv_query, getSignatories(creator)) .WillRepeatedly(Return(signatories)); - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - creator, accountb_id, permissionOf(Role::kGetMyAccAst))) - .WillOnce(Return(false)); EXPECT_CALL(*wsv_query, getAccountRoles(creator)) .WillOnce(Return(boost::none)); @@ -335,14 +314,11 @@ TEST_F(ToriiQueriesTest, FindAccountAssetWhenHasRolePermissions) { auto account = shared_model::proto::AccountBuilder().accountId("accountA").build(); - auto amount = - shared_model::proto::AmountBuilder().intValue(100).precision(2).build(); - std::shared_ptr account_asset = clone(shared_model::proto::AccountAssetBuilder() .accountId("accountA") .assetId("usd") - .balance(amount) + .balance(shared_model::interface::Amount("1.00")) .build()); auto asset = shared_model::proto::AssetBuilder() @@ -415,10 +391,6 @@ TEST_F(ToriiQueriesTest, FindSignatoriesWhenNoGrantPermissions) { auto creator = "a@domain"; EXPECT_CALL(*wsv_query, getSignatories(creator)) .WillRepeatedly(Return(signatories)); - EXPECT_CALL(*wsv_query, - hasAccountGrantablePermission( - creator, "b@domain", permissionOf(Role::kGetMySignatories))) - .WillOnce(Return(false)); EXPECT_CALL(*wsv_query, getAccountRoles(creator)) .WillOnce(Return(boost::none)); @@ -504,17 +476,14 @@ TEST_F(ToriiQueriesTest, FindTransactionsWhenValid) { auto account = shared_model::proto::AccountBuilder().accountId("accountA").build(); auto creator = "a@domain"; - auto txs_observable = rxcpp::observable<>::iterate([&account] { - std::vector result; - for (size_t i = 0; i < 3; ++i) { - std::shared_ptr current = - clone(TestTransactionBuilder() - .creatorAccountId(account.accountId()) - .build()); - result.push_back(current); - } - return result; - }()); + std::vector txs; + for (size_t i = 0; i < 3; ++i) { + std::shared_ptr current = + clone(TestTransactionBuilder() + .creatorAccountId(account.accountId()) + .build()); + txs.push_back(current); + } EXPECT_CALL(*wsv_query, getSignatories(creator)) .WillRepeatedly(Return(signatories)); @@ -524,7 +493,7 @@ TEST_F(ToriiQueriesTest, FindTransactionsWhenValid) { perm.set(Role::kGetMyAccTxs); EXPECT_CALL(*wsv_query, getRolePermissions("test")).WillOnce(Return(perm)); EXPECT_CALL(*block_query, getAccountTransactions(creator)) - .WillOnce(Return(txs_observable)); + .WillOnce(Return(txs)); iroha::protocol::QueryResponse response; diff --git a/test/module/irohad/torii/torii_service_query_test.cpp b/test/module/irohad/torii/torii_service_query_test.cpp index a2ece6a01e..2c5d5b3ddb 100644 --- a/test/module/irohad/torii/torii_service_query_test.cpp +++ b/test/module/irohad/torii/torii_service_query_test.cpp @@ -33,10 +33,6 @@ class ToriiQueryServiceTest : public ::testing::Test { // ----------- Command Service -------------- query_processor = std::make_shared(); - EXPECT_CALL(*query_processor, queryNotifier()) - .WillOnce( - Return(rxcpp::observable<>::empty< - std::shared_ptr>())); //----------- Server run ---------------- runner->append(std::make_unique(query_processor)) @@ -91,12 +87,10 @@ TEST_F(ToriiQueryServiceTest, FetchBlocksWhenValidQuery) { .blockResponse(*proto_block) .build(); - EXPECT_CALL( - *query_processor, - blocksQueryHandle( - Truly([&blocks_query]( - const std::shared_ptr - query) { return *query == *blocks_query; }))) + EXPECT_CALL(*query_processor, + blocksQueryHandle(Truly([&blocks_query](auto &query) { + return query == *blocks_query; + }))) .WillOnce(Return(rxcpp::observable<>::just(block_response))); auto client = torii_utils::QuerySyncClient(ip, port); diff --git a/test/module/irohad/torii/torii_service_test.cpp b/test/module/irohad/torii/torii_service_test.cpp index d0a735406c..0ad77bb5f4 100644 --- a/test/module/irohad/torii/torii_service_test.cpp +++ b/test/module/irohad/torii/torii_service_test.cpp @@ -1,18 +1,7 @@ -/* -Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ #include "builders/protobuf/block.hpp" #include "builders/protobuf/proposal.hpp" @@ -28,6 +17,7 @@ limitations under the License. #include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" #include "torii/command_client.hpp" #include "torii/command_service.hpp" +#include "torii/impl/status_bus_impl.hpp" #include "torii/processor/transaction_processor_impl.hpp" constexpr size_t TimesToriiBlocking = 5; @@ -42,7 +32,18 @@ using namespace iroha::ametsuchi; using namespace iroha::torii; using namespace std::chrono_literals; -constexpr std::chrono::milliseconds proposal_delay = 10s; +constexpr std::chrono::milliseconds initial_timeout = 1s; +constexpr std::chrono::milliseconds nonfinal_timeout = 2 * 10s; + +/** +The do-while cycle imitates client resubscription to the stream. Stream +"expiration" is a valid designed case (see pr #1615 for the details). + +The number of attempts (3) is a magic constant here. The idea behind this number +is the following: only one resubscription is usually enough to pass the test; if +three resubscribes were not enough, then most likely there is another bug. + */ +constexpr uint32_t resubscribe_attempts = 3; using iroha::Commit; @@ -51,12 +52,20 @@ class CustomPeerCommunicationServiceMock : public PeerCommunicationService { CustomPeerCommunicationServiceMock( rxcpp::subjects::subject< std::shared_ptr> prop_notifier, - rxcpp::subjects::subject commit_notifier) - : prop_notifier_(prop_notifier), commit_notifier_(commit_notifier){}; + rxcpp::subjects::subject commit_notifier, + rxcpp::subjects::subject< + std::shared_ptr> + verified_prop_notifier) + : prop_notifier_(prop_notifier), + commit_notifier_(commit_notifier), + verified_prop_notifier_(verified_prop_notifier){}; void propagate_transaction( std::shared_ptr transaction) - override {} + const override {} + + void propagate_batch( + const shared_model::interface::TransactionBatch &batch) const override {} rxcpp::observable> on_proposal() const override { @@ -66,10 +75,19 @@ class CustomPeerCommunicationServiceMock : public PeerCommunicationService { return commit_notifier_.get_observable(); } + rxcpp::observable< + std::shared_ptr> + on_verified_proposal() const override { + return verified_prop_notifier_.get_observable(); + }; + private: rxcpp::subjects::subject> prop_notifier_; rxcpp::subjects::subject commit_notifier_; + rxcpp::subjects::subject< + std::shared_ptr> + verified_prop_notifier_; }; class ToriiServiceTest : public testing::Test { @@ -79,7 +97,7 @@ class ToriiServiceTest : public testing::Test { // ----------- Command Service -------------- pcsMock = std::make_shared( - prop_notifier_, commit_notifier_); + prop_notifier_, commit_notifier_, verified_prop_notifier_); mst = std::make_shared(); wsv_query = std::make_shared(); block_query = std::make_shared(); @@ -90,8 +108,10 @@ class ToriiServiceTest : public testing::Test { EXPECT_CALL(*mst, onExpiredTransactionsImpl()) .WillRepeatedly(Return(mst_expired_notifier.get_observable())); + auto status_bus = std::make_shared(); auto tx_processor = - std::make_shared(pcsMock, mst); + std::make_shared( + pcsMock, mst, status_bus); EXPECT_CALL(*block_query, getTxByHashSync(_)) .WillRepeatedly(Return(boost::none)); @@ -99,8 +119,11 @@ class ToriiServiceTest : public testing::Test { //----------- Server run ---------------- runner - ->append(std::make_unique( - tx_processor, storage, proposal_delay)) + ->append(std::make_unique(tx_processor, + storage, + status_bus, + initial_timeout, + nonfinal_timeout)) .run() .match( [this](iroha::expected::Value port) { @@ -122,6 +145,9 @@ class ToriiServiceTest : public testing::Test { rxcpp::subjects::subject> prop_notifier_; rxcpp::subjects::subject commit_notifier_; + rxcpp::subjects::subject< + std::shared_ptr> + verified_prop_notifier_; rxcpp::subjects::subject mst_prepared_notifier; rxcpp::subjects::subject mst_expired_notifier; @@ -254,6 +280,7 @@ TEST_F(ToriiServiceTest, StatusWhenBlocking) { } // create block from the all transactions but the last one + auto failed_tx_hash = txs.back().hash(); txs.pop_back(); auto block = clone(TestBlockBuilder() @@ -263,6 +290,27 @@ TEST_F(ToriiServiceTest, StatusWhenBlocking) { .prevHash(shared_model::crypto::Hash(std::string(32, '0'))) .build()); + // notify the verified proposal event about txs, which passed stateful + // validation + auto verified_proposal = std::make_shared( + TestProposalBuilder() + .height(1) + .createdTime(iroha::time::now()) + .transactions(txs) + .build()); + auto errors = iroha::validation::TransactionsErrors{std::make_pair( + iroha::validation::CommandError{ + "FailedCommand", "stateful validation failed", true, 2}, + failed_tx_hash)}; + auto stringified_error = "Stateful validation error in transaction " + + failed_tx_hash.hex() + ": " + "command 'FailedCommand' with index '2' " + "did not pass verification with error 'stateful " + "validation failed'"; + verified_prop_notifier_.get_subscriber().on_next( + std::make_shared( + std::make_pair(verified_proposal, errors))); + // create commit from block notifier's observable rxcpp::subjects::subject> block_notifier_; @@ -309,6 +357,7 @@ TEST_F(ToriiServiceTest, StatusWhenBlocking) { client5.Status(last_tx_request, stful_invalid_response); ASSERT_EQ(stful_invalid_response.tx_status(), iroha::protocol::TxStatus::STATEFUL_VALIDATION_FAILED); + ASSERT_EQ(stful_invalid_response.error_message(), stringified_error); } /** @@ -369,9 +418,14 @@ TEST_F(ToriiServiceTest, StreamingFullPipelineTest) { // (Committed in this case) will be received. We start request before // transaction sending so we need in a separate thread for it. std::thread t([&] { + auto resub_counter(resubscribe_attempts); iroha::protocol::TxStatusRequest tx_request; tx_request.set_tx_hash(txhash); - client.StatusStream(tx_request, torii_response); + do { + client.StatusStream(tx_request, torii_response); + } while (torii_response.back().tx_status() + != iroha::protocol::TxStatus::COMMITTED + and --resub_counter); }); client.Torii(iroha_tx.getTransport()); @@ -385,6 +439,9 @@ TEST_F(ToriiServiceTest, StreamingFullPipelineTest) { .height(1) .build()); prop_notifier_.get_subscriber().on_next(proposal); + verified_prop_notifier_.get_subscriber().on_next( + std::make_shared( + std::make_pair(proposal, iroha::validation::TransactionsErrors{}))); auto block = clone(proto::BlockBuilder() .height(1) @@ -432,3 +489,111 @@ TEST_F(ToriiServiceTest, StreamingNoTx) { ASSERT_EQ(torii_response.at(0).tx_status(), iroha::protocol::TxStatus::NOT_RECEIVED); } + +/** + * Checks that torii is able to handle lists (sequences) of transactions + * + * @given torii service and collection of transactions + * @when that collection is asked to be processed by Torii + * @then statuses of all transactions from that request are STATELESS_VALID + */ +TEST_F(ToriiServiceTest, ListOfTxs) { + const auto test_txs_number = 5; + + // initial preparations: creation of variables and txs + auto client = torii::CommandSyncClient(ip, port); + std::vector tx_hashes( + test_txs_number); + iroha::protocol::TxList tx_list; + + for (auto i = 0; i < test_txs_number; ++i) { + auto shm_tx = shared_model::proto::TransactionBuilder() + .creatorAccountId("doge@master" + std::to_string(i)) + .createdTime(iroha::time::now()) + .setAccountQuorum("doge@master", 2) + .quorum(1) + .build() + .signAndAddSignature( + shared_model::crypto::DefaultCryptoAlgorithmType:: + generateKeypair()) + .finish(); + tx_hashes[i] = shm_tx.hash(); + new (tx_list.add_transactions()) + iroha::protocol::Transaction(shm_tx.getTransport()); + } + + // send the txs + client.ListTorii(tx_list); + + // check their statuses + std::for_each( + std::begin(tx_hashes), std::end(tx_hashes), [&client](auto &hash) { + iroha::protocol::TxStatusRequest tx_request; + tx_request.set_tx_hash(shared_model::crypto::toBinaryString(hash)); + iroha::protocol::ToriiResponse toriiResponse; + client.Status(tx_request, toriiResponse); + + ASSERT_EQ(toriiResponse.tx_status(), + iroha::protocol::TxStatus::STATELESS_VALIDATION_SUCCESS); + }); +} + +/** + * Checks that in case of failed transactions list every transaction has a + * related status and error message + * + * @given torii service and a list of bad-formed transactions + * @when checking those transactions after sending them to Torii + * @then every transaction from this list will have STATELESS_FAILED status @and + * the same corresponding error message + */ +TEST_F(ToriiServiceTest, FailedListOfTxs) { + const auto test_txs_number = 5; + + // initial preparations: creation of variables and txs + auto client = torii::CommandSyncClient(ip, port); + std::vector tx_hashes( + test_txs_number); + iroha::protocol::TxList tx_list; + + for (auto i = 0; i < test_txs_number; ++i) { + auto shm_tx = TestTransactionBuilder() + .creatorAccountId("doge@master" + std::to_string(i)) + .createdTime(iroha::time::now(std::chrono::hours(24) + + std::chrono::minutes(1))) + .setAccountQuorum("doge@master", 2) + .quorum(1) + .build(); + tx_hashes[i] = shm_tx.hash(); + new (tx_list.add_transactions()) + iroha::protocol::Transaction(shm_tx.getTransport()); + } + + // send the txs + client.ListTorii(tx_list); + + // actual error message is too big and hardly predictable, so we want at least + // to make sure that edges of tx list are right + auto error_msg_beginning = + "Stateless invalid tx in transaction sequence, beginning " + "with tx : " + + tx_hashes.front().hex() + " and ending with tx " + + tx_hashes.back().hex(); + + // check their statuses + std::for_each( + std::begin(tx_hashes), + std::end(tx_hashes), + [&client, &error_msg_beginning](auto &hash) { + iroha::protocol::TxStatusRequest tx_request; + tx_request.set_tx_hash(shared_model::crypto::toBinaryString(hash)); + iroha::protocol::ToriiResponse toriiResponse; + client.Status(tx_request, toriiResponse); + auto error_beginning = toriiResponse.error_message().substr( + 0, toriiResponse.error_message().find_first_of('.')); + + ASSERT_EQ(toriiResponse.tx_status(), + iroha::protocol::TxStatus::STATELESS_VALIDATION_FAILED); + ASSERT_EQ(error_beginning, error_msg_beginning); + }); +} diff --git a/test/module/irohad/validation/CMakeLists.txt b/test/module/irohad/validation/CMakeLists.txt index 3f964e39d8..4c927941a9 100644 --- a/test/module/irohad/validation/CMakeLists.txt +++ b/test/module/irohad/validation/CMakeLists.txt @@ -1,35 +1,15 @@ -# -# Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. -# http://soramitsu.co.jp -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -addtest(query_execution_test query_execution.cpp) -target_link_libraries(query_execution_test - query_execution - shared_model_interfaces - shared_model_stateless_validation - ) +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 addtest(chain_validation_test chain_validation_test.cpp) target_link_libraries(chain_validation_test chain_validator - shared_model_stateless_validation - shared_model_proto_backend + shared_model_default_builders ) addtest(stateful_validator_test stateful_validator_test.cpp) target_link_libraries(stateful_validator_test - shared_model_default_builders - ) + stateful_validator + shared_model_default_builders + shared_model_proto_backend + ) diff --git a/test/module/irohad/validation/chain_validation_test.cpp b/test/module/irohad/validation/chain_validation_test.cpp index f14c4df853..42369cfeb0 100644 --- a/test/module/irohad/validation/chain_validation_test.cpp +++ b/test/module/irohad/validation/chain_validation_test.cpp @@ -1,40 +1,22 @@ /** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ -#include "builders/common_objects/peer_builder.hpp" -#include "builders/protobuf/common_objects/proto_peer_builder.hpp" #include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" #include "module/irohad/consensus/yac/yac_mocks.hpp" -#include "module/shared_model/builders/protobuf/test_block_builder.hpp" +#include "module/shared_model/interface_mocks.hpp" #include "validation/impl/chain_validator_impl.hpp" -#include "validators/field_validator.hpp" using namespace iroha; using namespace iroha::validation; using namespace iroha::ametsuchi; +using ::testing::_; using ::testing::A; using ::testing::ByRef; using ::testing::InvokeArgument; using ::testing::Return; -using ::testing::_; - -auto zero_string = std::string(32, '\0'); -auto fake_hash = shared_model::crypto::Hash(zero_string); class ChainValidationTest : public ::testing::Test { public: @@ -43,19 +25,17 @@ class ChainValidationTest : public ::testing::Test { storage = std::make_shared(); query = std::make_shared(); peers = std::vector>(); - } - /** - * Get block builder to build blocks for tests - * @return block builder - */ - auto getBlockBuilder() const { - return TestBlockBuilder() - .transactions(std::vector{}) - .height(1) - .prevHash(hash); + EXPECT_CALL(*block, height()).WillRepeatedly(Return(1)); + EXPECT_CALL(*block, prevHash()).WillRepeatedly(testing::ReturnRef(hash)); + EXPECT_CALL(*block, signatures()) + .WillRepeatedly( + testing::Return(std::initializer_list{})); + EXPECT_CALL(*block, payload()).WillRepeatedly(testing::ReturnRef(payload)); } + shared_model::crypto::Blob payload{"blob"}; + std::vector> peers; std::shared_ptr @@ -65,6 +45,7 @@ class ChainValidationTest : public ::testing::Test { std::shared_ptr validator; std::shared_ptr storage; std::shared_ptr query; + std::shared_ptr block = std::make_shared(); }; /** @@ -74,17 +55,15 @@ class ChainValidationTest : public ::testing::Test { */ TEST_F(ChainValidationTest, ValidCase) { // Valid previous hash, has supermajority, correct peers subset => valid - auto block = getBlockBuilder().build(); - - EXPECT_CALL(*supermajority_checker, hasSupermajority(block.signatures(), _)) + EXPECT_CALL(*supermajority_checker, hasSupermajority(block->signatures(), _)) .WillOnce(Return(true)); EXPECT_CALL(*query, getPeers()).WillOnce(Return(peers)); - EXPECT_CALL(*storage, apply(testing::Ref(block), _)) - .WillOnce(InvokeArgument<1>(ByRef(block), ByRef(*query), ByRef(hash))); + EXPECT_CALL(*storage, apply(testing::Ref(*block), _)) + .WillOnce(InvokeArgument<1>(ByRef(*block), ByRef(*query), ByRef(hash))); - ASSERT_TRUE(validator->validateBlock(block, *storage)); + ASSERT_TRUE(validator->validateBlock(*block, *storage)); } /** @@ -94,18 +73,16 @@ TEST_F(ChainValidationTest, ValidCase) { */ TEST_F(ChainValidationTest, FailWhenDifferentPrevHash) { // Invalid previous hash, has supermajority, correct peers subset => invalid - auto block = getBlockBuilder().build(); - shared_model::crypto::Hash another_hash = shared_model::crypto::Hash(std::string(32, '1')); EXPECT_CALL(*query, getPeers()).WillOnce(Return(peers)); - EXPECT_CALL(*storage, apply(testing::Ref(block), _)) + EXPECT_CALL(*storage, apply(testing::Ref(*block), _)) .WillOnce( - InvokeArgument<1>(ByRef(block), ByRef(*query), ByRef(another_hash))); + InvokeArgument<1>(ByRef(*block), ByRef(*query), ByRef(another_hash))); - ASSERT_FALSE(validator->validateBlock(block, *storage)); + ASSERT_FALSE(validator->validateBlock(*block, *storage)); } /** @@ -115,17 +92,15 @@ TEST_F(ChainValidationTest, FailWhenDifferentPrevHash) { */ TEST_F(ChainValidationTest, FailWhenNoSupermajority) { // Valid previous hash, no supermajority, correct peers subset => invalid - auto block = getBlockBuilder().build(); - - EXPECT_CALL(*supermajority_checker, hasSupermajority(block.signatures(), _)) + EXPECT_CALL(*supermajority_checker, hasSupermajority(block->signatures(), _)) .WillOnce(Return(false)); EXPECT_CALL(*query, getPeers()).WillOnce(Return(peers)); - EXPECT_CALL(*storage, apply(testing::Ref(block), _)) - .WillOnce(InvokeArgument<1>(ByRef(block), ByRef(*query), ByRef(hash))); + EXPECT_CALL(*storage, apply(testing::Ref(*block), _)) + .WillOnce(InvokeArgument<1>(ByRef(*block), ByRef(*query), ByRef(hash))); - ASSERT_FALSE(validator->validateBlock(block, *storage)); + ASSERT_FALSE(validator->validateBlock(*block, *storage)); } /** @@ -135,24 +110,16 @@ TEST_F(ChainValidationTest, FailWhenNoSupermajority) { */ TEST_F(ChainValidationTest, ValidWhenValidateChainFromOnePeer) { // Valid previous hash, has supermajority, correct peers subset => valid - auto block = getBlockBuilder().build(); - rxcpp::observable> - block_observable = rxcpp::observable<>::just(block).map([](auto &&x) { - return std::shared_ptr(clone(x)); - }); - EXPECT_CALL(*supermajority_checker, hasSupermajority(_, _)) .WillOnce(Return(true)); EXPECT_CALL(*query, getPeers()).WillOnce(Return(peers)); - EXPECT_CALL( - *storage, - apply(testing::Truly([&](const shared_model::interface::Block &rhs) { - return rhs == block; - }), - _)) - .WillOnce(InvokeArgument<1>(ByRef(block), ByRef(*query), ByRef(hash))); + EXPECT_CALL(*storage, apply(testing::Ref(*block), _)) + .WillOnce(InvokeArgument<1>(ByRef(*block), ByRef(*query), ByRef(hash))); - ASSERT_TRUE(validator->validateChain(block_observable, *storage)); + ASSERT_TRUE(validator->validateChain( + rxcpp::observable<>::just( + std::static_pointer_cast(block)), + *storage)); } diff --git a/test/module/irohad/validation/stateful_validator_test.cpp b/test/module/irohad/validation/stateful_validator_test.cpp index 7fb10780a4..63c9713c54 100644 --- a/test/module/irohad/validation/stateful_validator_test.cpp +++ b/test/module/irohad/validation/stateful_validator_test.cpp @@ -4,21 +4,36 @@ */ #include -#include "builders/protobuf/common_objects/proto_signature_builder.hpp" +#include +#include + +#include "common/result.hpp" #include "cryptography/crypto_provider/crypto_defaults.hpp" +#include "interfaces/iroha_internal/batch_meta.hpp" +#include "interfaces/transaction.hpp" +#include "module/irohad/ametsuchi/ametsuchi_mocks.hpp" +#include "module/shared_model/builders/protobuf/test_proposal_builder.hpp" +#include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" +#include "module/shared_model/interface_mocks.hpp" +#include "validation/impl/stateful_validator_impl.hpp" +#include "validation/stateful_validator.hpp" #include "validation/utils.hpp" +#include "backend/protobuf/proto_proposal_factory.hpp" using namespace iroha::validation; using namespace shared_model::crypto; +using ::testing::_; +using ::testing::A; +using ::testing::ByMove; +using ::testing::ByRef; +using ::testing::Eq; +using ::testing::Return; +using ::testing::ReturnArg; + class SignaturesSubset : public testing::Test { public: - auto makeSignature(PublicKey key, std::string sign) { - return shared_model::proto::SignatureBuilder() - .publicKey(key) - .signedData(Signed(sign)) - .build(); - } + std::vector keys{PublicKey("a"), PublicKey("b"), PublicKey("c")}; }; /** @@ -27,10 +42,10 @@ class SignaturesSubset : public testing::Test { * @then returned true */ TEST_F(SignaturesSubset, Equal) { - std::vector keys{PublicKey("a"), PublicKey("b"), PublicKey("c")}; - std::vector signatures; - for (const auto &k : keys) { - signatures.push_back(makeSignature(k, "")); + std::array signatures; + for (size_t i = 0; i < signatures.size(); ++i) { + EXPECT_CALL(signatures[i], publicKey()) + .WillRepeatedly(testing::ReturnRef(keys[i])); } ASSERT_TRUE(signaturesSubset(signatures, keys)); } @@ -42,13 +57,13 @@ TEST_F(SignaturesSubset, Equal) { * @then returned false */ TEST_F(SignaturesSubset, Lesser) { - std::vector keys{PublicKey("a"), PublicKey("b")}; - std::vector signatures; - for (const auto &k : keys) { - signatures.push_back(makeSignature(k, "")); + std::vector subkeys{keys.begin(), keys.end() - 1}; + std::array signatures; + for (size_t i = 0; i < signatures.size(); ++i) { + EXPECT_CALL(signatures[i], publicKey()) + .WillRepeatedly(testing::ReturnRef(keys[i])); } - signatures.push_back(makeSignature(PublicKey("c"), "")); - ASSERT_FALSE(signaturesSubset(signatures, keys)); + ASSERT_FALSE(signaturesSubset(signatures, subkeys)); } /** @@ -57,12 +72,11 @@ TEST_F(SignaturesSubset, Lesser) { * @then returned true */ TEST_F(SignaturesSubset, StrictSubset) { - std::vector keys{PublicKey("a"), PublicKey("b")}; - std::vector signatures; - for (const auto &k : keys) { - signatures.push_back(makeSignature(k, "")); + std::array signatures; + for (size_t i = 0; i < signatures.size(); ++i) { + EXPECT_CALL(signatures[i], publicKey()) + .WillRepeatedly(testing::ReturnRef(keys[i])); } - keys.push_back(PublicKey("c")); ASSERT_TRUE(signaturesSubset(signatures, keys)); } @@ -72,9 +86,190 @@ TEST_F(SignaturesSubset, StrictSubset) { * @then returned false */ TEST_F(SignaturesSubset, PublickeyUniqueness) { - std::vector keys{PublicKey("a"), PublicKey("a")}; - std::vector signatures; - signatures.push_back(makeSignature(PublicKey("a"), "")); - signatures.push_back(makeSignature(PublicKey("c"), "")); - ASSERT_FALSE(signaturesSubset(signatures, keys)); + std::vector repeated_keys{2, keys[0]}; + std::array signatures; + for (size_t i = 0; i < signatures.size(); ++i) { + EXPECT_CALL(signatures[i], publicKey()) + .WillRepeatedly(testing::ReturnRef(keys[i])); + } + ASSERT_FALSE(signaturesSubset(signatures, repeated_keys)); +} + +class Validator : public testing::Test { + public: + void SetUp() override { + factory = std::make_unique>(); + sfv = std::make_shared(std::move(factory)); + temp_wsv_mock = std::make_shared(); + } + + auto createBatch(std::vector creators, + shared_model::interface::types::BatchType batch_type) { + std::vector reduced_hashes; + std::vector txs; + auto current_time = iroha::time::now(); + + for (size_t i = 0; i < creators.size(); ++i) { + auto tx = TestTransactionBuilder() + .creatorAccountId(creators[i]) + .createdTime(current_time + i) + .quorum(1) + .createAsset("doge", "coin", 1) + .build(); + reduced_hashes.push_back(tx.reducedHash()); + } + + for (size_t i = 0; i < creators.size(); ++i) { + txs.push_back(TestTransactionBuilder() + .creatorAccountId(creators[i]) + .createdTime(current_time + i) + .quorum(1) + .createAsset("doge", "coin", 1) + .batchMeta(batch_type, reduced_hashes) + .build()); + } + return txs; + } + + std::shared_ptr sfv; + std::unique_ptr factory; + std::shared_ptr temp_wsv_mock; +}; + +/** + * @given several valid transactions + * @when statefully validating these transactions + * @then all of them will appear in verified proposal @and errors will be empty + */ +TEST_F(Validator, AllTxsValid) { + auto tx = TestTransactionBuilder() + .creatorAccountId("doge@master") + .createdTime(iroha::time::now()) + .quorum(1) + .createAsset("doge", "coin", 1) + .build(); + auto proposal = + TestProposalBuilder() + .createdTime(iroha::time::now()) + .height(3) + .transactions( + std::vector{tx, tx, tx}) + .build(); + + EXPECT_CALL(*temp_wsv_mock, apply(_, _)) + .WillRepeatedly(Return(iroha::expected::Value({}))); + + auto verified_proposal_and_errors = sfv->validate(proposal, *temp_wsv_mock); + ASSERT_EQ(verified_proposal_and_errors.first->transactions().size(), 3); + ASSERT_TRUE(verified_proposal_and_errors.second.empty()); +} + +/** + * @given several valid and a couple of invalid transactions + * @when statefully validating these transactions + * @then valid transactions will appear in verified proposal @and invalid ones + * will appear in errors + */ +TEST_F(Validator, SomeTxsFail) { + auto valid_tx = TestTransactionBuilder() + .creatorAccountId("doge@master") + .createdTime(iroha::time::now()) + .quorum(1) + .createAsset("doge", "coin", 1) + .build(); + auto invalid_tx = TestTransactionBuilder() + .creatorAccountId("doge@master") + .createdTime(iroha::time::now()) + .quorum(1) + .createAsset("cate", "coin", 1) + .build(); + auto proposal = + TestProposalBuilder() + .createdTime(iroha::time::now()) + .height(3) + .transactions(std::vector{ + valid_tx, invalid_tx, valid_tx}) + .build(); + + EXPECT_CALL(*temp_wsv_mock, apply(Eq(ByRef(invalid_tx)), _)) + .WillOnce(Return(iroha::expected::Error({}))); + EXPECT_CALL(*temp_wsv_mock, apply(Eq(ByRef(valid_tx)), _)) + .WillRepeatedly(Return(iroha::expected::Value({}))); + + auto verified_proposal_and_errors = sfv->validate(proposal, *temp_wsv_mock); + ASSERT_EQ(verified_proposal_and_errors.first->transactions().size(), 2); + ASSERT_EQ(verified_proposal_and_errors.second.size(), 1); +} + +/** + * @given two atomic batches @and one ordered @and several single transactions + * @when failing one of the atomic batched @and transaction from ordered batch + * @and transaction from single group + * @then verified proposal will contain transactions from non-failed atomic + * batch, non-failed part of ordered batch, non-failed transactions from single + * group @and errors will contain exactly one error (for failed atomic batch) + */ +TEST_F(Validator, Batches) { + auto single_tx = TestTransactionBuilder() + .creatorAccountId("doge@master") + .createdTime(iroha::time::now()) + .quorum(1) + .createAsset("doge", "coin", 1) + .build(); + auto success_atomic_batch = + createBatch(std::vector{"creator@d1", "creator@d2"}, + shared_model::interface::types::BatchType::ATOMIC); + auto failed_atomic_batch = + createBatch(std::vector{"creator@d3", "creator@d4"}, + shared_model::interface::types::BatchType::ATOMIC); + auto ordered_batch = + createBatch(std::vector{"creator@d5", "creator@d6"}, + shared_model::interface::types::BatchType::ORDERED); + + std::vector txs{single_tx, + ordered_batch[0], + ordered_batch[1], + failed_atomic_batch[0], + failed_atomic_batch[1], + success_atomic_batch[0], + success_atomic_batch[1]}; + + auto proposal = TestProposalBuilder() + .createdTime(iroha::time::now()) + .height(1) + .transactions(txs) + .build(); + + // calls to create savepoints, one per each atomic batch + EXPECT_CALL(*temp_wsv_mock, + createSavepoint("batch_" + failed_atomic_batch[0].hash().hex())) + .WillOnce(Return( + ByMove(std::make_unique< + iroha::ametsuchi::MockTemporaryWsvSavepointWrapper>()))); + EXPECT_CALL(*temp_wsv_mock, + createSavepoint("batch_" + success_atomic_batch[0].hash().hex())) + .WillOnce(Return( + ByMove(std::make_unique< + iroha::ametsuchi::MockTemporaryWsvSavepointWrapper>()))); + + // calls to validate transactions, one per each transaction except those, + // which are in failed atomic batch - there only calls before the failed + // transaction are needed + EXPECT_CALL(*temp_wsv_mock, apply(Eq(ByRef(txs[0])), _)) + .WillOnce(Return(iroha::expected::Value({}))); + EXPECT_CALL(*temp_wsv_mock, apply(Eq(ByRef(txs[1])), _)) + .WillOnce(Return(iroha::expected::Value({}))); + EXPECT_CALL(*temp_wsv_mock, apply(Eq(ByRef(txs[2])), _)) + .WillOnce(Return(iroha::expected::Value({}))); + EXPECT_CALL(*temp_wsv_mock, apply(Eq(ByRef(txs[3])), _)) + .WillOnce(Return(iroha::expected::Error({}))); + EXPECT_CALL(*temp_wsv_mock, apply(Eq(ByRef(txs[5])), _)) + .WillOnce(Return(iroha::expected::Value({}))); + EXPECT_CALL(*temp_wsv_mock, apply(Eq(ByRef(txs[6])), _)) + .WillOnce(Return(iroha::expected::Value({}))); + + auto verified_proposal_and_errors = sfv->validate(proposal, *temp_wsv_mock); + ASSERT_EQ(verified_proposal_and_errors.first->transactions().size(), 5); + ASSERT_EQ(verified_proposal_and_errors.second.size(), 1); } diff --git a/test/module/irohad/validation/validation_mocks.hpp b/test/module/irohad/validation/validation_mocks.hpp index 5bdddbfa80..646f3f3500 100644 --- a/test/module/irohad/validation/validation_mocks.hpp +++ b/test/module/irohad/validation/validation_mocks.hpp @@ -21,6 +21,7 @@ #include #include "interfaces/iroha_internal/proposal.hpp" +#include "interfaces/common_objects/types.hpp" #include "validation/chain_validator.hpp" #include "validation/stateful_validator.hpp" @@ -29,7 +30,7 @@ namespace iroha { class MockStatefulValidator : public validation::StatefulValidator { public: MOCK_METHOD2(validate, - std::shared_ptr( + VerifiedProposalAndErrors( const shared_model::interface::Proposal &, ametsuchi::TemporaryWsv &)); }; diff --git a/test/module/libs/CMakeLists.txt b/test/module/libs/CMakeLists.txt index d67148b9e0..cac6eb7650 100644 --- a/test/module/libs/CMakeLists.txt +++ b/test/module/libs/CMakeLists.txt @@ -3,7 +3,6 @@ set(CMAKE_BUILD_TYPE Debug) SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/test_bin) # Reusable tests -add_subdirectory(amount) add_subdirectory(cache) add_subdirectory(crypto) add_subdirectory(datetime) diff --git a/test/module/libs/amount/CMakeLists.txt b/test/module/libs/amount/CMakeLists.txt deleted file mode 100644 index 4491d1915e..0000000000 --- a/test/module/libs/amount/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. -# http://soramitsu.co.jp -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# amount Test - -AddTest(amount_test amount_test.cpp) - -target_link_libraries(amount_test - iroha_amount - ) diff --git a/test/module/libs/amount/amount_test.cpp b/test/module/libs/amount/amount_test.cpp deleted file mode 100644 index 5894383a83..0000000000 --- a/test/module/libs/amount/amount_test.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. 2017 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include - -class AmountTest : public testing::Test {}; - -TEST_F(AmountTest, TestBasic) { - iroha::Amount a(123, 2); - - // check taking percentage - auto b = a.percentage(50); - ASSERT_EQ(b.to_string(), "0.61"); - - // check summation - auto c = a + b; - ASSERT_TRUE(c); - ASSERT_EQ(c->to_string(), "1.84"); - - // check subtraction by subtracting b from c - auto aa = c - b; - ASSERT_TRUE(aa); - ASSERT_EQ(a, *aa); - - auto d = a.percentage(*c); // taking 1.84% of 1.23 - ASSERT_EQ(d.to_string(), "0.02"); - - auto e = - a.percentage(iroha::Amount(256)); // taking 256% of the amount == 3.1488 - // assuming that we round down - ASSERT_EQ(e.to_string(), "3.14"); - - // check move constructor - iroha::Amount f(std::move(a)); - ASSERT_EQ(f.to_string(), "1.23"); - - // check equals when different precisions - ASSERT_EQ(iroha::Amount(110, 2), iroha::Amount(11, 1)); - - // check comparisons - ASSERT_LT(iroha::Amount(110, 2), iroha::Amount(111, 2)); - ASSERT_LE(iroha::Amount(110, 2), iroha::Amount(111, 2)); - ASSERT_LE(iroha::Amount(111, 2), iroha::Amount(111, 2)); - - ASSERT_GT(iroha::Amount(111, 2), iroha::Amount(110, 2)); - ASSERT_GE(iroha::Amount(111, 2), iroha::Amount(110, 2)); - ASSERT_GE(iroha::Amount(111, 2), iroha::Amount(111, 2)); - - // check conversion to four uint64 - iroha::Amount g(boost::multiprecision::uint256_t( - "12391826391263918264198246192846192846192412398162398")); - auto vec = g.to_uint64s(); - iroha::Amount h(vec.at(0), vec.at(1), vec.at(2), vec.at(3)); - ASSERT_EQ(h.getPrecision(), 0); - ASSERT_EQ(g, h); -} - -// test with different precisions and values -TEST_F(AmountTest, TestStringConversion) { - for (uint8_t precision = 0; precision < 255; precision++) { - for (int val = 0; val < 10; val++) { - iroha::Amount amount(val, precision); - auto amount_str = amount.to_string(); - auto converted_amount = iroha::Amount::createFromString(amount_str); - ASSERT_TRUE(converted_amount); - ASSERT_EQ(converted_amount.value(), amount); - ASSERT_EQ(converted_amount->to_string(), amount.to_string()); - } - } - - auto a = iroha::Amount::createFromString(".20"); - ASSERT_EQ(a.value(), iroha::Amount(20, 2)); - - ASSERT_FALSE(iroha::Amount::createFromString("-0.20")); - ASSERT_FALSE(iroha::Amount::createFromString("-0..20")); - ASSERT_FALSE(iroha::Amount::createFromString("0..20")); - ASSERT_FALSE(iroha::Amount::createFromString("-0.20")); -} diff --git a/test/module/libs/cache/CMakeLists.txt b/test/module/libs/cache/CMakeLists.txt index b64871bb31..40879455bd 100644 --- a/test/module/libs/cache/CMakeLists.txt +++ b/test/module/libs/cache/CMakeLists.txt @@ -16,3 +16,5 @@ addtest(cache_test cache_test.cpp) target_link_libraries(cache_test torii_service ) + +addtest(single_pointer_cache_test single_pointer_cache_test.cpp) diff --git a/test/module/libs/cache/single_pointer_cache_test.cpp b/test/module/libs/cache/single_pointer_cache_test.cpp new file mode 100644 index 0000000000..c4d5f75f19 --- /dev/null +++ b/test/module/libs/cache/single_pointer_cache_test.cpp @@ -0,0 +1,107 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +#include "cache/single_pointer_cache.hpp" + +using namespace iroha::cache; + +class SinglePointerCacheTest : public ::testing::Test { + using SinglePointerIntCache = SinglePointerCache; + + protected: + void SetUp() override { + int_cache.release(); + } + + SinglePointerIntCache int_cache; + const int default_int_value = 5; +}; + +/** + * @given empty int cache + * @when trying to get the value inside + * @then cache will return nullptr + */ +TEST_F(SinglePointerCacheTest, GetWhenEmpty) { + ASSERT_FALSE(int_cache.get()); +} + +/** + * @given empty int cache + * @when inserting some value into it @and trying to get it + * @then cache will return the inserted value + */ +TEST_F(SinglePointerCacheTest, Insert) { + int_cache.insert(std::make_shared(default_int_value)); + ASSERT_EQ(*int_cache.get(), default_int_value); +} + +/** + * @given empty int cache + * @when inserting some value into it @and releasing the cache @and trying to + * get value inside + * @then cache will return nullptr + */ +TEST_F(SinglePointerCacheTest, Release) { + int_cache.insert(std::make_shared(default_int_value)); + ASSERT_TRUE(int_cache.get()); + + int_cache.release(); + ASSERT_FALSE(int_cache.get()); +} + +/** + * @given empty int cache + * @when several readers reading values from cache @and several writers writing + * values to the cache @and releaser emptyfying the cache are spawned + * @then the system must not crash + */ +TEST_F(SinglePointerCacheTest, MultithreadedCache) { + constexpr std::chrono::milliseconds sleep_interval{100}; + constexpr int run_times{10}; + + auto read = [this, &sleep_interval] { + // if cache is not empty, read the value; otherwise do nothing + for (auto i = 0; i < run_times; ++i) { + auto value_ptr = int_cache.get(); + if (value_ptr) { + ASSERT_NO_THROW(*value_ptr); + } + std::this_thread::sleep_for(sleep_interval); + } + }; + auto write_one = [this, &sleep_interval] { + // just write to cache + for (auto i = 0; i < run_times; i++) { + std::this_thread::sleep_for(sleep_interval); + int_cache.insert(std::make_shared(i)); + } + }; + auto write_two = [this, &sleep_interval] { + // just write to cache + for (auto i = run_times; i > 0; --i) { + std::this_thread::sleep_for(sleep_interval); + int_cache.insert(std::make_shared(i)); + } + }; + auto release = [this, &sleep_interval] { + // release the cache + for (auto i = 0; i < run_times; ++i) { + int_cache.release(); + std::this_thread::sleep_for(sleep_interval); + } + }; + + std::thread writer_one{write_one}, reader{read}, releaser{release}, writer_two{write_two}; + writer_one.join(); + reader.join(); + releaser.join(); + writer_two.join(); +} diff --git a/test/module/shared_model/CMakeLists.txt b/test/module/shared_model/CMakeLists.txt index aac86f3bde..09e2b5933a 100644 --- a/test/module/shared_model/CMakeLists.txt +++ b/test/module/shared_model/CMakeLists.txt @@ -41,3 +41,11 @@ target_link_libraries(amount_utils_test shared_model_default_builders shared_model_amount_utils ) + +AddTest(interface_test + interface_test.cpp + ) +target_link_libraries(interface_test + shared_model_default_builders + logger + ) diff --git a/test/module/shared_model/amount_utils_test.cpp b/test/module/shared_model/amount_utils_test.cpp index 114eabd626..e9e4074d8b 100644 --- a/test/module/shared_model/amount_utils_test.cpp +++ b/test/module/shared_model/amount_utils_test.cpp @@ -17,7 +17,6 @@ #include -#include "builders/default_builders.hpp" #include "utils/amount_utils.hpp" using namespace shared_model::detail; @@ -30,33 +29,14 @@ class AmountTest : public testing::Test {}; * @then correct amount is given */ TEST_F(AmountTest, PlusTest) { - iroha::expected::Result, - std::shared_ptr> - a = shared_model::builder::DefaultAmountBuilder::fromString("1234.567"); + shared_model::interface::Amount a("1234.567"), b("100"); - iroha::expected::Result, - std::shared_ptr> - b = shared_model::builder::DefaultAmountBuilder::fromString("100"); - - a.match( - [&b](const iroha::expected::Value< - std::shared_ptr> &a_value) { - b.match( - [&a_value](const iroha::expected::Value> &b_value) { - auto c = *a_value.value + *b_value.value; - c.match( - [](const iroha::expected::Value> &c_value) { - ASSERT_EQ(c_value.value->intValue(), 1334567); - ASSERT_EQ(c_value.value->precision(), 3); - }, - [](const iroha::expected::Error> - &e) { FAIL() << *e.error; }); - }, - [](const iroha::expected::Error> &e) { - FAIL() << *e.error; - }); + auto c = a + b; + c.match( + [](const iroha::expected::Value< + std::shared_ptr> &c_value) { + ASSERT_EQ(c_value.value->intValue(), 1334567); + ASSERT_EQ(c_value.value->precision(), 3); }, [](const iroha::expected::Error> &e) { FAIL() << *e.error; @@ -69,33 +49,13 @@ TEST_F(AmountTest, PlusTest) { * @then correct amount is given */ TEST_F(AmountTest, MinusTest) { - iroha::expected::Result, - std::shared_ptr> - a = shared_model::builder::DefaultAmountBuilder::fromString("1234.567"); - - iroha::expected::Result, - std::shared_ptr> - b = shared_model::builder::DefaultAmountBuilder::fromString("100"); - - a.match( - [&b](const iroha::expected::Value< - std::shared_ptr> &a_value) { - b.match( - [&a_value](const iroha::expected::Value> &b_value) { - auto c = *a_value.value - *b_value.value; - c.match( - [](const iroha::expected::Value> &c_value) { - ASSERT_EQ(c_value.value->intValue(), 1134567); - ASSERT_EQ(c_value.value->precision(), 3); - }, - [](const iroha::expected::Error> - &e) { FAIL() << *e.error; }); - }, - [](const iroha::expected::Error> &e) { - FAIL() << *e.error; - }); + shared_model::interface::Amount a("1234.567"), b("100"); + auto c = a - b; + c.match( + [](const iroha::expected::Value< + std::shared_ptr> &c_value) { + ASSERT_EQ(c_value.value->intValue(), 1134567); + ASSERT_EQ(c_value.value->precision(), 3); }, [](const iroha::expected::Error> &e) { FAIL() << *e.error; @@ -108,23 +68,13 @@ TEST_F(AmountTest, MinusTest) { * @then correct amount is given */ TEST_F(AmountTest, PrecisionTest) { - iroha::expected::Result, - std::shared_ptr> - a = shared_model::builder::DefaultAmountBuilder::fromString("1234.567"); - - a.match( + shared_model::interface::Amount a("1234.567"); + auto c = makeAmountWithPrecision(a, 4); + c.match( [](const iroha::expected::Value< - std::shared_ptr> &a_value) { - auto c = makeAmountWithPrecision(*a_value.value, 4); - c.match( - [](const iroha::expected::Value< - std::shared_ptr> &c_value) { - ASSERT_EQ(c_value.value->intValue(), 12345670); - ASSERT_EQ(c_value.value->precision(), 4); - }, - [](const iroha::expected::Error> &e) { - FAIL() << *e.error; - }); + std::shared_ptr> &c_value) { + ASSERT_EQ(c_value.value->intValue(), 12345670); + ASSERT_EQ(c_value.value->precision(), 4); }, [](const iroha::expected::Error> &e) { FAIL() << *e.error; @@ -140,33 +90,33 @@ TEST_F(AmountTest, PlusOverflowsTest) { const std::string &uint256_halfmax = "723700557733226221397318656304299424082937404160253525246609900049457060" "2495.0"; // 2**252 - 1 - iroha::expected::Result, - std::shared_ptr> - a = shared_model::builder::DefaultAmountBuilder::fromString( - uint256_halfmax); - iroha::expected::Result, - std::shared_ptr> - b = shared_model::builder::DefaultAmountBuilder::fromString("100.00"); + shared_model::interface::Amount a(uint256_halfmax), b("100.00"); + auto c = a + b; + c.match( + [](const iroha::expected::Value< + std::shared_ptr> &c_value) { + FAIL() << "Operation successful but shouldn't"; + }, + [](const iroha::expected::Error> &e) { + SUCCEED() << *e.error; + }); +} + +/** + * @given two amounts + * @when tries to sum it up + * @then correct amount is given + */ +TEST_F(AmountTest, LeadingZeroPlusTest) { + shared_model::interface::Amount a("12.3"), b("03.21"); - a.match( - [&b](const iroha::expected::Value< - std::shared_ptr> &a_value) { - b.match( - [&a_value](const iroha::expected::Value> &b_value) { - auto c = *a_value.value + *b_value.value; - c.match( - [](const iroha::expected::Value> &c_value) { - FAIL() << "Operation successful but shouldn't"; - }, - [](const iroha::expected::Error> - &e) { SUCCEED() << *e.error; }); - }, - [](const iroha::expected::Error> &e) { - FAIL() << *e.error; - }); + auto c = a + b; + c.match( + [](const iroha::expected::Value< + std::shared_ptr> &c_value) { + ASSERT_EQ(c_value.value->intValue(), 1551); + ASSERT_EQ(c_value.value->precision(), 2); }, [](const iroha::expected::Error> &e) { FAIL() << *e.error; diff --git a/test/module/shared_model/backend_proto/CMakeLists.txt b/test/module/shared_model/backend_proto/CMakeLists.txt index bc1675d33e..52467ec253 100644 --- a/test/module/shared_model/backend_proto/CMakeLists.txt +++ b/test/module/shared_model/backend_proto/CMakeLists.txt @@ -27,6 +27,7 @@ addtest(shared_proto_tx_response_test ) target_link_libraries(shared_proto_tx_response_test shared_model_proto_backend + shared_model_stateless_validation ) addtest(shared_proto_transaction_test @@ -47,14 +48,16 @@ target_link_libraries(shared_proto_queries_test shared_model_stateless_validation ) -addtest(shared_proto_query_responses_test - shared_proto_query_responses_test.cpp - ) -target_link_libraries(shared_proto_query_responses_test - shared_model_proto_backend - shared_model_stateless_validation - shared_model_cryptography - ) +if (IROHA_ROOT_PROJECT) + addtest(shared_proto_query_responses_test + shared_proto_query_responses_test.cpp + ) + target_link_libraries(shared_proto_query_responses_test + shared_model_proto_backend + shared_model_stateless_validation + shared_model_cryptography + ) +endif () addtest(shared_proto_util_test shared_proto_util_test.cpp @@ -63,18 +66,42 @@ target_link_libraries(shared_proto_util_test shared_model_proto_backend ) -addtest(shared_proto_amount_test - common_objects/shared_proto_amount_test.cpp +addtest(permissions_test + permissions_test.cpp + ) +target_link_libraries(permissions_test + shared_model_proto_backend ) -target_link_libraries(shared_proto_amount_test +addtest(proto_batch_test + proto_batch_test.cpp + ) +target_link_libraries(proto_batch_test shared_model_proto_backend + shared_model_stateless_validation ) -addtest(permissions_test - permissions_test.cpp +addtest(proto_transaction_sequence_test + proto_transaction_sequence_test.cpp ) -target_link_libraries(permissions_test +target_link_libraries(proto_transaction_sequence_test shared_model_proto_backend + shared_model_stateless_validation ) +addtest(proto_common_objects_factory_test + common_objects/proto_common_objects_factory_test.cpp + ) +target_link_libraries(proto_common_objects_factory_test + shared_model_cryptography + shared_model_stateless_validation + ) + +addtest(proto_proposal_factory_test + proto_proposal_factory_test.cpp + ) + +target_link_libraries(proto_proposal_factory_test + shared_model_stateless_validation + shared_model_proto_backend + ) diff --git a/test/module/shared_model/backend_proto/common_objects/proto_common_objects_factory_test.cpp b/test/module/shared_model/backend_proto/common_objects/proto_common_objects_factory_test.cpp new file mode 100644 index 0000000000..843d056c0f --- /dev/null +++ b/test/module/shared_model/backend_proto/common_objects/proto_common_objects_factory_test.cpp @@ -0,0 +1,266 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "backend/protobuf/common_objects/proto_common_objects_factory.hpp" +#include "cryptography/crypto_provider/crypto_defaults.hpp" +#include "framework/result_fixture.hpp" +#include "validators/field_validator.hpp" + +using namespace shared_model; +using namespace framework::expected; + +proto::ProtoCommonObjectsFactory factory; + +class PeerTest : public ::testing::Test { + public: + std::string valid_address = "127.0.0.1:8080"; + crypto::PublicKey valid_pubkey = + crypto::DefaultCryptoAlgorithmType::generateKeypair().publicKey(); + std::string invalid_address = "127.0.0.1"; +}; + +/** + * @given valid data for peer + * @when peer is created via factory + * @then peer is successfully initialized + */ +TEST_F(PeerTest, ValidPeerInitialization) { + auto peer = factory.createPeer(valid_address, valid_pubkey); + + peer.match( + [&](const ValueOf &v) { + ASSERT_EQ(v.value->address(), valid_address); + ASSERT_EQ(v.value->pubkey().hex(), valid_pubkey.hex()); + }, + [](const ErrorOf &e) { FAIL() << e.error; }); +} + +/** + * @given invalid data for peer + * @when peer is created via factory + * @then peer is not initialized correctly + */ +TEST_F(PeerTest, InvalidPeerInitialization) { + auto peer = factory.createPeer(invalid_address, valid_pubkey); + + peer.match( + [](const ValueOf &v) { FAIL() << "Expected error case"; }, + [](const ErrorOf &e) { SUCCEED(); }); +} + +class AccountTest : public ::testing::Test { + public: + interface::types::AccountIdType valid_account_id = "hello@world"; + interface::types::DomainIdType valid_domain_id = "bit.connect"; + interface::types::QuorumType valid_quorum = 1; + interface::types::JsonType valid_json = R"({"name": "json" })"; + + interface::types::AccountIdType invalid_account_id = "hello123"; +}; + +/** + * @given valid data for account + * @when account is created via factory + * @then peer is successfully initialized + */ +TEST_F(AccountTest, ValidAccountInitialization) { + auto account = factory.createAccount( + valid_account_id, valid_domain_id, valid_quorum, valid_json); + + account.match( + [&](const ValueOf &v) { + ASSERT_EQ(v.value->accountId(), valid_account_id); + ASSERT_EQ(v.value->domainId(), valid_domain_id); + ASSERT_EQ(v.value->quorum(), valid_quorum); + ASSERT_EQ(v.value->jsonData(), valid_json); + }, + [](const ErrorOf &e) { FAIL() << e.error; }); +} + +/** + * @given invalid data for account + * @when account is created via factory + * @then account is not initialized correctly + */ +TEST_F(AccountTest, InvalidAccountInitialization) { + auto account = factory.createAccount( + invalid_account_id, valid_domain_id, valid_quorum, valid_json); + + account.match( + [](const ValueOf &v) { + FAIL() << "Expected error case"; + }, + [](const ErrorOf &e) { SUCCEED(); }); +} + +class AccountAssetTest : public ::testing::Test { + public: + interface::types::AccountIdType valid_account_id = "hello@world"; + interface::types::AssetIdType valid_asset_id = "bit#connect"; + interface::Amount valid_amount = interface::Amount("10.00"); + + interface::types::AccountIdType invalid_account_id = "hello123"; +}; + +/** + * @given valid data for account asset + * @when account asset is created via factory + * @then account asset is successfully initialized + */ +TEST_F(AccountAssetTest, ValidAccountAssetInitialization) { + auto account_asset = factory.createAccountAsset( + valid_account_id, valid_asset_id, valid_amount); + + account_asset.match( + [&](const ValueOf &v) { + ASSERT_EQ(v.value->accountId(), valid_account_id); + ASSERT_EQ(v.value->assetId(), valid_asset_id); + ASSERT_EQ(v.value->balance(), valid_amount); + }, + [](const ErrorOf &e) { FAIL() << e.error; }); +} + +/** + * @given invalid data for account asset + * @when account asset is created via factory + * @then account asset is not initialized correctly + */ +TEST_F(AccountAssetTest, InvalidAccountAssetInitialization) { + auto account_asset = factory.createAccountAsset( + invalid_account_id, valid_asset_id, valid_amount); + + account_asset.match( + [](const ValueOf &v) { + FAIL() << "Expected error case"; + }, + [](const ErrorOf &e) { SUCCEED(); }); +} + +class AssetTest : public ::testing::Test { + public: + interface::types::AssetIdType valid_asset_id = "bit#connect"; + interface::types::DomainIdType valid_domain_id = "iroha.com"; + interface::types::PrecisionType valid_precision = 2; + + interface::types::AssetIdType invalid_asset_id = "bit"; +}; + +/** + * @given valid data for asset + * @when asset is created via factory + * @then asset is successfully initialized + */ +TEST_F(AssetTest, ValidAssetInitialization) { + auto asset = + factory.createAsset(valid_asset_id, valid_domain_id, valid_precision); + + asset.match( + [&](const ValueOf &v) { + ASSERT_EQ(v.value->assetId(), valid_asset_id); + ASSERT_EQ(v.value->domainId(), valid_domain_id); + ASSERT_EQ(v.value->precision(), valid_precision); + }, + [](const ErrorOf &e) { FAIL() << e.error; }); +} + +/** + * @given invalid data for asset + * @when asset is created via factory + * @then asset is not initialized correctly + */ +TEST_F(AssetTest, InvalidAssetInitialization) { + auto asset = + factory.createAsset(invalid_asset_id, valid_domain_id, valid_precision); + + asset.match( + [](const ValueOf &v) { + FAIL() << "Expected error case"; + }, + [](const ErrorOf &e) { SUCCEED(); }); +} + +class DomainTest : public ::testing::Test { + public: + interface::types::DomainIdType valid_domain_id = "iroha.com"; + interface::types::RoleIdType valid_role_id = "admin"; + + interface::types::DomainIdType invalid_domain_id = "123irohacom"; +}; + +/** + * @given valid data for domain + * @when domain is created via factory + * @then domain is successfully initialized + */ +TEST_F(DomainTest, ValidDomainInitialization) { + auto domain = + factory.createDomain(valid_domain_id, valid_role_id); + + domain.match( + [&](const ValueOf &v) { + ASSERT_EQ(v.value->domainId(), valid_domain_id); + ASSERT_EQ(v.value->defaultRole(), valid_role_id); + }, + [](const ErrorOf &e) { FAIL() << e.error; }); +} + +/** + * @given invalid data for domain + * @when domain is created via factory + * @then domain is not initialized correctly + */ +TEST_F(DomainTest, InvalidDomainInitialization) { + auto domain = + factory.createDomain(invalid_domain_id, valid_role_id); + + domain.match( + [](const ValueOf &v) { + FAIL() << "Expected error case"; + }, + [](const ErrorOf &e) { SUCCEED(); }); +} + +class SignatureTest : public ::testing::Test { + public: + crypto::PublicKey valid_pubkey = + crypto::DefaultCryptoAlgorithmType::generateKeypair().publicKey(); + crypto::Signed valid_data{"hello"}; + crypto::PublicKey invalid_pubkey{"1234"}; +}; + +/** + * @given valid data for signature + * @when signature is created via factory + * @then signature is successfully initialized + */ +TEST_F(SignatureTest, ValidSignatureInitialization) { + auto signature = + factory.createSignature(valid_pubkey, valid_data); + + signature.match( + [&](const ValueOf &v) { + ASSERT_EQ(v.value->publicKey().hex(), valid_pubkey.hex()); + ASSERT_EQ(v.value->signedData().hex(), valid_data.hex()); + }, + [](const ErrorOf &e) { FAIL() << e.error; }); +} + +/** + * @given invalid data for signature + * @when signature is created via factory + * @then signature is not initialized correctly + */ +TEST_F(SignatureTest, InvalidSignatureInitialization) { + auto signature = + factory.createSignature(invalid_pubkey, valid_data); + + signature.match( + [](const ValueOf &v) { + FAIL() << "Expected error case"; + }, + [](const ErrorOf &e) { SUCCEED(); }); +} diff --git a/test/module/shared_model/backend_proto/common_objects/shared_proto_amount_test.cpp b/test/module/shared_model/backend_proto/common_objects/shared_proto_amount_test.cpp deleted file mode 100644 index 9c41e92aea..0000000000 --- a/test/module/shared_model/backend_proto/common_objects/shared_proto_amount_test.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "backend/protobuf/common_objects/amount.hpp" - -#include - -using boost::multiprecision::uint256_t; - -const uint256_t kOne = 1; -/// each 64 bit part starts with 1 -const uint256_t kExpectedValue = - (kOne << (64 * 3)) | (kOne << (64 * 2)) | (kOne << 64) | kOne; - -/** - * @given protobuf amount object - * @when shared_model proto object is constructed - * @then int value of shared model object equals to initial protobuf value - */ -TEST(AmountTest, AmountIntValueInitialization) { - iroha::protocol::Amount amount; - - amount.mutable_value()->set_first(kOne.template convert_to()); - amount.mutable_value()->set_second(kOne.template convert_to()); - amount.mutable_value()->set_third(kOne.template convert_to()); - amount.mutable_value()->set_fourth(kOne.template convert_to()); - - shared_model::proto::Amount proto_amount(amount); - - ASSERT_EQ(proto_amount.intValue(), kExpectedValue); -} - -/** - * @given uint256_t value - * @when proto value of amount is filled - * @then int value of proto object equals uint256_t value - */ -TEST(AmountTest, ProtoValueFromIntConversion) { - iroha::protocol::Amount amount; - - shared_model::proto::convertToProtoAmount(*amount.mutable_value(), - kExpectedValue); - - ASSERT_EQ(amount.value().first(), kOne); - ASSERT_EQ(amount.value().second(), kOne); - ASSERT_EQ(amount.value().third(), kOne); - ASSERT_EQ(amount.value().fourth(), kOne); -} diff --git a/test/module/shared_model/backend_proto/proto_batch_test.cpp b/test/module/shared_model/backend_proto/proto_batch_test.cpp new file mode 100644 index 0000000000..23870846ae --- /dev/null +++ b/test/module/shared_model/backend_proto/proto_batch_test.cpp @@ -0,0 +1,271 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "builders/protobuf/transaction.hpp" +#include "framework/batch_helper.hpp" +#include "framework/result_fixture.hpp" +#include "interfaces/iroha_internal/transaction_batch.hpp" +#include "validators/field_validator.hpp" +#include "validators/transaction_validator.hpp" +#include "validators/transactions_collection/batch_order_validator.hpp" +#include "validators/transactions_collection/unsigned_transactions_collection_validator.hpp" + +using namespace shared_model; +using ::testing::_; +using ::testing::Return; +using ::testing::Test; +using ::testing::Truly; + +using TxValidator = validation::TransactionValidator< + validation::FieldValidator, + validation::CommandValidatorVisitor>; + +using TxsValidator = validation::UnsignedTransactionsCollectionValidator< + TxValidator, + validation::BatchOrderValidator>; + +/** + * Creates valid unsigned transaction + * @param created_time assigned to transactions + * @return std::shared_ptr containing valid unsigned + * transaction + */ +auto createValidUnsignedTransaction(size_t created_time = iroha::time::now()) { + return std::shared_ptr(clone( + framework::batch::prepareTransactionBuilder("valid@account", created_time) + .build())); +} + +/** + * Creates invalid unsigned transaction + * @param created_time assigned to transactions + * @return std::shared_ptr containing invalid unsigned + * transaction + */ +auto createInvalidUnsignedTransaction( + size_t created_time = iroha::time::now()) { + return std::shared_ptr( + clone(framework::batch::prepareTransactionBuilder("invalid#@account", + created_time) + .build())); +} + +/** + * @given valid transactions sequence from the single batch + * @when createTransactionBatch is invoked on that sequence + * @then transaction batch is created + */ +TEST(TransactionBatchTest, CreateTransactionBatchWhenValid) { + auto txs = framework::batch::createUnsignedBatchTransactions( + interface::types::BatchType::ATOMIC, + std::vector{"a@domain", "b@domain"}); + auto transaction_batch = + interface::TransactionBatch::createTransactionBatch(txs, TxsValidator()); + ASSERT_TRUE(framework::expected::val(transaction_batch)) + << framework::expected::err(transaction_batch).value().error; +} + +/** + * @given transactions sequence from the single batch containing valid + * transactions but with different batch types + * @when createTransactionBatch is invoked on that sequence + * @then transaction batch is not created + */ +TEST(TransactionBatchTest, CreateTransactionBatchWhenDifferentBatchType) { + auto tx1_fields = std::make_pair(interface::types::BatchType::ORDERED, + std::string("a@domain")); + auto tx2_fields = std::make_pair(interface::types::BatchType::ATOMIC, + std::string("b@domain")); + + auto txs = framework::batch::createUnsignedBatchTransactions( + std::vector{tx1_fields, tx2_fields}); + + auto transaction_batch = + interface::TransactionBatch::createTransactionBatch(txs, TxsValidator()); + ASSERT_TRUE(framework::expected::err(transaction_batch)); +} + +/** + * @given transactions sequence from the single batch containing one valid and + * one invalid transactions + * @when createTransactionBatch is invoked on that sequence + * @then transaction batch is not created + */ +TEST(TransactionBatchTest, CreateBatchWithValidAndInvalidTx) { + auto txs = framework::batch::createUnsignedBatchTransactions( + interface::types::BatchType::ATOMIC, + std::vector{"valid@name", "invalid#@name"}); + + auto transaction_batch = + interface::TransactionBatch::createTransactionBatch(txs, TxsValidator()); + ASSERT_TRUE(framework::expected::err(transaction_batch)); +} + +/** + * @given single valid transaction + * @when createTransactionBatch is invoked on that transaction + * @then transaction batch is created + */ +TEST(TransactionBatchTest, CreateSingleTxBatchWhenValid) { + TxValidator transaction_validator; + + auto tx1 = createValidUnsignedTransaction(); + + auto transaction_batch = interface::TransactionBatch::createTransactionBatch( + tx1, transaction_validator); + + ASSERT_TRUE(framework::expected::val(transaction_batch)) + << framework::expected::err(transaction_batch).value().error; +} + +/** + * @given single invalid transaction + * @when createTransactionBatch is invoked on that transaction + * @then transaction batch is not created + */ +TEST(TransactionBatchTest, CreateSingleTxBatchWhenInvalid) { + TxValidator transaction_validator; + + auto tx1 = createInvalidUnsignedTransaction(); + + auto transaction_batch = interface::TransactionBatch::createTransactionBatch( + tx1, transaction_validator); + + ASSERT_TRUE(framework::expected::err(transaction_batch)); +} + +/** + * Creates batch from transactions with provided quorum size and one signature + * @param quorum quorum size + * @return batch with transactions with one signature and quorum size + */ +auto createBatchWithTransactionsWithQuorum( + const interface::types::QuorumType &quorum) { + auto keypair = crypto::DefaultCryptoAlgorithmType::generateKeypair(); + + auto now = iroha::time::now(); + + auto batch_type = shared_model::interface::types::BatchType::ATOMIC; + std::string userone = "a@domain"; + std::string usertwo = "b@domain"; + + auto transactions = framework::batch::createBatchOneSignTransactions( + std::vector>{ + std::make_pair(batch_type, userone), + std::make_pair(batch_type, usertwo)}, + now, + quorum); + + return interface::TransactionBatch::createTransactionBatch(transactions, + TxsValidator()); +} + +/** + * @given transactions with transaction with quorum size 1 @and signatures size + * equals 1, @and all transactions are from the same batch + * @when transaction batch is created from that transactions + * @then created batch has all signatures + */ +TEST(TransactionBatchTest, BatchWithAllSignatures) { + auto quorum = 1; + auto transaction_batch = createBatchWithTransactionsWithQuorum(quorum); + auto transaction_batch_val = framework::expected::val(transaction_batch); + ASSERT_TRUE(transaction_batch_val) + << framework::expected::err(transaction_batch).value().error; + ASSERT_TRUE(transaction_batch_val->value.hasAllSignatures()); +} + +/** + * @given transactions with transaction with quorum size 2 @and signatures size + * equals 1, @and all transactions are from the same batch + * @when transaction batch is created from that transactions + * @then created batch does not have all signatures + */ +TEST(TransactionBatchTest, BatchWithMissingSignatures) { + auto quorum = 2; + auto transaction_batch = createBatchWithTransactionsWithQuorum(quorum); + auto transaction_batch_val = framework::expected::val(transaction_batch); + ASSERT_TRUE(transaction_batch_val) + << framework::expected::err(transaction_batch).value().error; + ASSERT_FALSE(transaction_batch_val->value.hasAllSignatures()); +} + +/** + * Create test transaction builder + * @param acc_quorum - quorum number for setAccountDetail + * @param created_time - time of creation + * @param quorum - tx quorum number + * @return test tx builder + */ +inline auto makeTxBuilder( + const shared_model::interface::types::QuorumType &acc_quorum = 1, + uint64_t created_time = iroha::time::now(), + uint8_t quorum = 3) { + return TestTransactionBuilder() + .createdTime(created_time) + .creatorAccountId("user@test") + .setAccountQuorum("user@test", acc_quorum) + .quorum(quorum); +} + +/** + * @given one tx-builder + * @when try to fetch hash + * @then got only one hash + */ +TEST(TransactionBatchTest, TemplateHasherOne) { + ASSERT_EQ( + 1, + framework::batch::internal::fetchReducedHashes(makeTxBuilder()).size()); +} + +/** + * @given 3 tx-builders + * @when try to fetch hashes + * @then got exactly 3 hashes + */ +TEST(TransactionBatchTest, TemplateHasherVariadic) { + ASSERT_EQ(3, + framework::batch::internal::fetchReducedHashes( + makeTxBuilder(), makeTxBuilder(), makeTxBuilder()) + .size()); +} + +/** + * @given one tx-builder + * @when try to create transaction + * @then got exactly one tx + */ +TEST(TransactionBatchTest, MakeTxBatchCollectionOne) { + ASSERT_EQ(1, + framework::batch::internal::makeTxBatchCollection(makeTxBuilder()) + .size()); +} + +/** + * @given three tx-builders + * @when try to create transaction collection + * @then got exactly 3 txes + */ +TEST(TransactionBatchTest, MakeTxBatchCollectionMany) { + ASSERT_EQ(3, + framework::batch::internal::makeTxBatchCollection( + makeTxBuilder(), makeTxBuilder(), makeTxBuilder()) + .size()); +} + +/** + * @given two tx-builders + * @when try to create batch + * @then batch contains two transactions + */ +TEST(TransactionBatchTest, CreateTestBatchTest) { + ASSERT_EQ(2, + framework::batch::makeTestBatch(makeTxBuilder(2), makeTxBuilder()) + ->transactions() + .size()); +} diff --git a/test/module/shared_model/backend_proto/proto_proposal_factory_test.cpp b/test/module/shared_model/backend_proto/proto_proposal_factory_test.cpp new file mode 100644 index 0000000000..bb00bfc828 --- /dev/null +++ b/test/module/shared_model/backend_proto/proto_proposal_factory_test.cpp @@ -0,0 +1,74 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "backend/protobuf/proto_proposal_factory.hpp" +#include "framework/result_fixture.hpp" +#include "module/shared_model/validators/validators.hpp" +#include "validators/default_validator.hpp" +#include "validators/field_validator.hpp" + +using namespace shared_model; +using namespace framework::expected; + +class ProposalFactoryTest : public ::testing::Test { + public: + shared_model::proto::ProtoProposalFactory< + validation::AlwaysValidValidator> + valid_factory; + shared_model::proto::ProtoProposalFactory< + validation::DefaultProposalValidator> + factory; + + interface::types::HeightType height{1}; + iroha::time::time_t time{iroha::time::now()}; + + std::vector txs; + + void SetUp() override { + txs.emplace_back(iroha::protocol::Transaction{}); + } + + void TearDown() override { + txs.clear(); + } +}; + + +/** + * @given proposal factory and valid data + * @when proposal is created using factory + * @then proposal is successfully created + */ +TEST_F(ProposalFactoryTest, ValidProposalTest) { + std::vector txs; + iroha::protocol::Transaction proto_tx; + txs.emplace_back(proto_tx); + auto proposal = valid_factory.createProposal(height, time, txs); + + proposal.match( + [&](const ValueOf &v) { + ASSERT_EQ(txs, v.value->transactions()); + ASSERT_EQ(height, v.value->height()); + ASSERT_EQ(time, v.value->createdTime()); + }, + [](const ErrorOf &e) { FAIL() << e.error; }); +} + +/** + * @given proposal factory and invalid data (empty transaction) + * @when proposal is created using factory + * @then proposal is not created successfully + */ +TEST_F(ProposalFactoryTest, InvalidProposalTest) { + auto proposal = factory.createProposal(height, time, txs); + + proposal.match( + [&](const ValueOf &) { + FAIL() << "unexpected value case"; + }, + [](const ErrorOf &) { SUCCEED(); }); +} diff --git a/test/module/shared_model/backend_proto/proto_transaction_sequence_test.cpp b/test/module/shared_model/backend_proto/proto_transaction_sequence_test.cpp new file mode 100644 index 0000000000..d5e029611d --- /dev/null +++ b/test/module/shared_model/backend_proto/proto_transaction_sequence_test.cpp @@ -0,0 +1,136 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "framework/batch_helper.hpp" +#include "framework/result_fixture.hpp" +#include "interfaces/iroha_internal/transaction_sequence.hpp" + +using namespace shared_model; +using ::testing::_; +using ::testing::Return; +using ::testing::Test; + +class MockTransactionCollectionValidator + : public validation::UnsignedTransactionsCollectionValidator< + validation::TransactionValidator>> { + public: + MOCK_CONST_METHOD1( + validatePointers, + validation::Answer(const interface::types::SharedTxsCollectionType &)); +}; + +shared_model::validation::Answer createAnswerWithErrors() { + shared_model::validation::Answer answer; + answer.addReason( + std::make_pair("transaction", std::vector{"some reason"})); + return answer; +} + +/** + * @given Transaction collection of several transactions + * @when create transaction sequence + * @and transactions validator returns empty answer + * @then TransactionSequence is created + */ +TEST(TransactionSequenceTest, CreateTransactionSequenceWhenValid) { + MockTransactionCollectionValidator transactions_validator; + + EXPECT_CALL(transactions_validator, validatePointers(_)) + .WillOnce(Return(validation::Answer())); + + std::shared_ptr tx(clone( + framework::batch::prepareTransactionBuilder("account@domain") + .batchMeta(shared_model::interface::types::BatchType::ATOMIC, + std::vector{}) + .build())); + + auto tx_sequence = interface::TransactionSequence::createTransactionSequence( + std::vector{tx, tx, tx}, transactions_validator); + + ASSERT_TRUE(framework::expected::val(tx_sequence)); +} + +/** + * @given Transaction collection of several transactions + * @when create transaction sequence + * @and transactions validator returns non empty answer + * @then TransactionSequence is not created + */ +TEST(TransactionSequenceTest, CreateTransactionSequenceWhenInvalid) { + MockTransactionCollectionValidator res; + + EXPECT_CALL(res, validatePointers(_)) + .WillOnce(Return(createAnswerWithErrors())); + + std::shared_ptr tx(clone( + framework::batch::prepareTransactionBuilder("account@domain") + .batchMeta(shared_model::interface::types::BatchType::ATOMIC, + std::vector{}) + .build())); + + auto tx_sequence = interface::TransactionSequence::createTransactionSequence( + std::vector{tx, tx, tx}, res); + + ASSERT_TRUE(framework::expected::err(tx_sequence)); +} + +/** + * @given Transaction collection of several transactions, including some of the + * united into the batches + * @when transactions validator returns empty answer + * @and create transaction sequence + * @then expected number of batches is created + */ +TEST(TransactionSequenceTest, CreateBatches) { + size_t batches_number = 3; + size_t txs_in_batch = 2; + size_t single_transactions = 1; + + MockTransactionCollectionValidator txs_validator; + + EXPECT_CALL(txs_validator, validatePointers(_)) + .Times(batches_number) + .WillRepeatedly(Return(validation::Answer())); + + interface::types::SharedTxsCollectionType tx_collection; + auto now = iroha::time::now(); + for (size_t i = 0; i < batches_number; i++) { + auto batch = framework::batch::createUnsignedBatchTransactions( + shared_model::interface::types::BatchType::ATOMIC, + txs_in_batch, + now + i); + tx_collection.insert(tx_collection.begin(), batch.begin(), batch.end()); + } + + for (size_t i = 0; i < single_transactions; i++) { + tx_collection.emplace_back( + clone(framework::batch::prepareTransactionBuilder( + "single_tx_account@domain" + std::to_string(i)) + .build())); + } + + auto tx_sequence_opt = + interface::TransactionSequence::createTransactionSequence(tx_collection, + txs_validator); + + auto tx_sequence = framework::expected::val(tx_sequence_opt); + ASSERT_TRUE(tx_sequence) + << framework::expected::err(tx_sequence_opt).value().error; + + ASSERT_EQ(boost::size(tx_sequence->value.batches()), + batches_number + single_transactions); + + size_t total_transactions = boost::accumulate( + tx_sequence->value.batches(), 0ul, [](auto sum, const auto &batch) { + return sum + boost::size(batch.transactions()); + }); + ASSERT_EQ(total_transactions, + batches_number * txs_in_batch + single_transactions); +} diff --git a/test/module/shared_model/backend_proto/shared_proto_transaction_test.cpp b/test/module/shared_model/backend_proto/shared_proto_transaction_test.cpp index e8ae026224..87c319161e 100644 --- a/test/module/shared_model/backend_proto/shared_proto_transaction_test.cpp +++ b/test/module/shared_model/backend_proto/shared_proto_transaction_test.cpp @@ -32,7 +32,7 @@ std::string creator_account_id = "admin@test"; */ iroha::protocol::Transaction generateEmptyTransaction() { iroha::protocol::Transaction proto_tx; - auto &payload = *proto_tx.mutable_payload(); + auto &payload = *proto_tx.mutable_payload()->mutable_reduced_payload(); payload.set_creator_account_id(creator_account_id); payload.set_created_time(created_time); payload.set_quorum(1); @@ -42,18 +42,15 @@ iroha::protocol::Transaction generateEmptyTransaction() { /** * Helper function to generate AddAssetQuantityCommand - * @param account_id account id to add asset quantity to * @param asset_id asset id to add value to * @return AddAssetQuantity protocol command */ iroha::protocol::AddAssetQuantity generateAddAssetQuantity( - std::string account_id, std::string asset_id) { + std::string asset_id) { iroha::protocol::AddAssetQuantity command; - command.set_account_id(account_id); command.set_asset_id(asset_id); - command.mutable_amount()->mutable_value()->set_fourth(1000); - command.mutable_amount()->set_precision(2); + command.set_amount("10.00"); return command; } @@ -68,10 +65,12 @@ TEST(ProtoTransaction, Builder) { std::string account_id = "admin@test", asset_id = "coin#test", amount = "10.00"; - auto command = - proto_tx.mutable_payload()->add_commands()->mutable_add_asset_quantity(); + auto command = proto_tx.mutable_payload() + ->mutable_reduced_payload() + ->add_commands() + ->mutable_add_asset_quantity(); - command->CopyFrom(generateAddAssetQuantity(account_id, asset_id)); + command->CopyFrom(generateAddAssetQuantity(asset_id)); auto keypair = shared_model::crypto::CryptoProviderEd25519Sha3::generateKeypair(); @@ -85,7 +84,7 @@ TEST(ProtoTransaction, Builder) { auto tx = shared_model::proto::TransactionBuilder() .creatorAccountId(creator_account_id) - .addAssetQuantity(account_id, asset_id, amount) + .addAssetQuantity(asset_id, amount) .createdTime(created_time) .quorum(1) .build(); @@ -107,12 +106,11 @@ TEST(ProtoTransaction, BuilderWithInvalidTx) { std::string invalid_asset_id = "cointest", // invalid asset_id without # amount = "10.00"; - ASSERT_THROW( - shared_model::proto::TransactionBuilder() - .creatorAccountId(invalid_account_id) - .addAssetQuantity(invalid_account_id, invalid_asset_id, amount) - .createdTime(created_time) - .quorum(1) - .build(), - std::invalid_argument); + ASSERT_THROW(shared_model::proto::TransactionBuilder() + .creatorAccountId(invalid_account_id) + .addAssetQuantity(invalid_asset_id, amount) + .createdTime(created_time) + .quorum(1) + .build(), + std::invalid_argument); } diff --git a/test/module/shared_model/backend_proto/shared_proto_util_test.cpp b/test/module/shared_model/backend_proto/shared_proto_util_test.cpp index 206530f6c1..00f8d60cd8 100644 --- a/test/module/shared_model/backend_proto/shared_proto_util_test.cpp +++ b/test/module/shared_model/backend_proto/shared_proto_util_test.cpp @@ -16,7 +16,7 @@ */ #include "backend/protobuf/util.hpp" -#include "block.pb.h" +#include "commands.pb.h" #include @@ -30,10 +30,10 @@ using shared_model::crypto::toBinaryString; * @then make sure that the deserialized from string is the same */ TEST(UtilTest, StringFromMakeBlob) { - protocol::Header base, deserialized; - base.set_created_time(100); + protocol::SetAccountQuorum base, deserialized; + base.set_quorum(100); auto blob = makeBlob(base); ASSERT_TRUE(deserialized.ParseFromString(toBinaryString(blob))); - ASSERT_EQ(deserialized.created_time(), base.created_time()); + ASSERT_EQ(deserialized.quorum(), base.quorum()); } diff --git a/test/module/shared_model/bindings/BuilderTest.java b/test/module/shared_model/bindings/BuilderTest.java index cb72c692a2..8d40d9dec4 100644 --- a/test/module/shared_model/bindings/BuilderTest.java +++ b/test/module/shared_model/bindings/BuilderTest.java @@ -10,7 +10,7 @@ import java.math.BigInteger; import iroha.protocol.Commands; -import iroha.protocol.BlockOuterClass; +import iroha.protocol.TransactionOuterClass; import com.google.protobuf.InvalidProtocolBufferException; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -150,7 +150,7 @@ private boolean checkProtoTx(Blob serialized) { } try { - BlockOuterClass.Transaction.parseFrom(bs); + TransactionOuterClass.Transaction.parseFrom(bs); } catch (InvalidProtocolBufferException e) { System.out.print("Exception: "); System.out.println(e.getMessage()); @@ -284,54 +284,31 @@ void addSignatoryInvalidKey() { @Test void addAssetQuantity() { - UnsignedTx tx = builder.addAssetQuantity("admin@test", "asset#domain", "12.345").build(); + UnsignedTx tx = builder.addAssetQuantity("asset#domain", "12.345").build(); assertTrue(checkProtoTx(proto(tx))); } @Test - void addAssetQuantityValidAccountsAndAssets() { + void addAssetQuantityValidAssets() { for (String domain: validDomains) { for (String name: validNameSymbols1) { - UnsignedTx tx = builder.addAssetQuantity(name + "@" + domain, name + "#" + domain, "100").build(); + UnsignedTx tx = builder.addAssetQuantity(name + "#" + domain, "100").build(); assertTrue(checkProtoTx(proto(tx))); } } } - @Test - void addAssetQuantityInvalidAccountDomain() { - for (String domain: invalidDomains) { - ModelTransactionBuilder builder = base().addAssetQuantity("admin@" + domain, "asset#test", "10"); - assertThrows(IllegalArgumentException.class, builder::build); - } - } - @Test void addAssetQuantityInvalidAssetDomain() { for (String domain: invalidDomains) { - ModelTransactionBuilder builder = base().addAssetQuantity("admin@test", "asset#" + domain, "10"); + ModelTransactionBuilder builder = base().addAssetQuantity("asset#" + domain, "10"); assertThrows(IllegalArgumentException.class, builder::build); } } @Test void addAssetZeroQuantity() { - ModelTransactionBuilder builder = base().addAssetQuantity("admin@test", "asset#domain", "0"); - assertThrows(IllegalArgumentException.class, builder::build); - } - - @Test - void addAssetQuantityInvalidAccountName() { - for (String accountName: invalidNameSymbols1) { - String accountId = accountName + "@test"; - ModelTransactionBuilder builder = base().addAssetQuantity(accountId, "asset#domain", "10"); - assertThrows(IllegalArgumentException.class, builder::build); - } - } - - @Test - void addAssetQuantityEmptyAccount() { - ModelTransactionBuilder builder = base().addAssetQuantity("", "asset#test", "10"); + ModelTransactionBuilder builder = base().addAssetQuantity("asset#domain", "0"); assertThrows(IllegalArgumentException.class, builder::build); } @@ -339,21 +316,21 @@ void addAssetQuantityEmptyAccount() { void addAssetQuantityInvalidAssetName() { for (String assetName: invalidNameSymbols1) { String assetId = assetName + "#test"; - ModelTransactionBuilder builder = base().addAssetQuantity("account@test", assetId, "10"); + ModelTransactionBuilder builder = base().addAssetQuantity(assetId, "10"); assertThrows(IllegalArgumentException.class, builder::build); } } @Test void addAssetQuantityEmptyAsset() { - ModelTransactionBuilder builder = base().addAssetQuantity("account@test", "", "10"); + ModelTransactionBuilder builder = base().addAssetQuantity("", "10"); assertThrows(IllegalArgumentException.class, builder::build); } @Test void addAssetQuantityInvalidAmount() { for (String amount: new String[]{"", "-12", "-13.45", "chars", "chars10"}) { - ModelTransactionBuilder builder = base().addAssetQuantity("account@test", "asset#test", amount); + ModelTransactionBuilder builder = base().addAssetQuantity("asset#test", amount); assertThrows(IllegalArgumentException.class, builder::build); } } @@ -819,11 +796,8 @@ void createRoleWithInvalidName() { @Test void createRoleEmptyPermissions() { - RolePermissionSet permissions = new RolePermissionSet(); - - ModelTransactionBuilder builder = new ModelTransactionBuilder(); - builder.createRole("new_role", permissions); - assertThrows(IllegalArgumentException.class, builder::build); + UnsignedTx tx = builder.createRole("new_role", new RolePermissionSet()).build(); + assertTrue(checkProtoTx(proto(tx))); } /* ====================== DetachRole Tests ====================== */ @@ -941,32 +915,16 @@ void revokePermissionEmptyAccount() { void subtractAssetQuantity() { for (String name: validNameSymbols1) { for (String domain: validDomains) { - UnsignedTx tx = builder.subtractAssetQuantity(name + "@" + domain, name + "#" + domain, "10.22").build(); + UnsignedTx tx = builder.subtractAssetQuantity(name + "#" + domain, "10.22").build(); assertTrue(checkProtoTx(proto(tx))); } } } - @Test - void subtractAssetQuantityInvalidAccount() { - for (String account: invalidNameSymbols1) { - ModelTransactionBuilder builder = base().subtractAssetQuantity(account + "@test", "coin#test", "10"); - assertThrows(IllegalArgumentException.class, builder::build); - } - } - - @Test - void subtractAssetQuantityInvalidAccountDomain() { - for (String domain: invalidDomains) { - ModelTransactionBuilder builder = base().subtractAssetQuantity("admin@" + domain, "coin#test", "10"); - assertThrows(IllegalArgumentException.class, builder::build); - } - } - @Test void subtractAssetQuantityInvalidAsset() { for (String asset: invalidNameSymbols1) { - ModelTransactionBuilder builder = base().subtractAssetQuantity("admin@test", asset + "#test", "10"); + ModelTransactionBuilder builder = base().subtractAssetQuantity(asset + "#test", "10"); assertThrows(IllegalArgumentException.class, builder::build); } } @@ -974,20 +932,14 @@ void subtractAssetQuantityInvalidAsset() { @Test void subtractAssetQuantityInvalidAssetDomain() { for (String domain: invalidDomains) { - ModelTransactionBuilder builder = base().subtractAssetQuantity("admin@test", "coin#" + domain, "10"); + ModelTransactionBuilder builder = base().subtractAssetQuantity("coin#" + domain, "10"); assertThrows(IllegalArgumentException.class, builder::build); } } - @Test - void subtractAssetQuantityEmptyAccount() { - ModelTransactionBuilder builder = base().subtractAssetQuantity("", "coin#test", "10"); - assertThrows(IllegalArgumentException.class, builder::build); - } - @Test void subtractAssetQuantityEmptyAsset() { - ModelTransactionBuilder builder = base().subtractAssetQuantity("admin@test", "", "10"); + ModelTransactionBuilder builder = base().subtractAssetQuantity("", "10"); assertThrows(IllegalArgumentException.class, builder::build); } @@ -1003,7 +955,7 @@ void subtractAssetQuantityInvalidAmount() { }; for (String amount: invalidAmounts) { - ModelTransactionBuilder builder = base().subtractAssetQuantity("admin@test", "coin#test", amount); + ModelTransactionBuilder builder = base().subtractAssetQuantity("coin#test", amount); assertThrows(IllegalArgumentException.class, builder::build); } } diff --git a/test/module/shared_model/bindings/CMakeLists.txt b/test/module/shared_model/bindings/CMakeLists.txt index c57d737f58..dbafdac111 100644 --- a/test/module/shared_model/bindings/CMakeLists.txt +++ b/test/module/shared_model/bindings/CMakeLists.txt @@ -65,15 +65,20 @@ if (SWIG_PYTHON) env "PYTHONPATH=${SWIG_LIB_DIR}${SEPARATOR}$ENV{PYTHONPATH}" ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/blocks-query-test.py WORKING_DIRECTORY ${SWIG_BUILD_DIR}) + add_test(NAME python_client_test + COMMAND ${CMAKE_COMMAND} -E + env "PYTHONPATH=${SWIG_LIB_DIR}${SEPARATOR}$ENV{PYTHONPATH}" + ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/client-test.py + WORKING_DIRECTORY ${SWIG_BUILD_DIR}) - foreach(item "block" "commands" "primitive" "queries") + foreach(item "block" "transaction" "commands" "primitive" "queries") compile_proto_to_python("${item}.proto") list(APPEND PROTO_SWIG_DEPS "${SWIG_BUILD_DIR}/${item}_pb2.py") endforeach(item) - add_custom_target(python_tests + add_custom_target(python_tests ALL DEPENDS "${PROTO_SWIG_DEPS}") - foreach(test "python_transaction_test" "python_query_test" "python_blocks_query_test") + foreach(test "python_transaction_test" "python_query_test" "python_blocks_query_test" "python_client_test") set_tests_properties(${test} PROPERTIES REQUIRED_FILES "${PROTO_SWIG_DEPS}" DEPENDS python_tests) @@ -99,7 +104,7 @@ if (SWIG_JAVA) add_test(NAME java_builders_test COMMAND gradle test "-PSWIG_BUILD_DIR=${SWIG_BUILD_DIR}" - "-PSCHEMA_DIR=${IROHA_SCHEMA_DIR}" + "-PSCHEMA_DIR=${PROJECT_SOURCE_DIR}/shared_model/schema" "-PPROTOBUF_PROTOC_EXECUTABLE=${PROTOC_EXEC}" "-PPATH_DIRS=$ENV{PATH}${SEPARATOR}${SWIG_LIB_DIR}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/test/module/shared_model/bindings/ClientTest.java b/test/module/shared_model/bindings/ClientTest.java new file mode 100644 index 0000000000..bc40380463 --- /dev/null +++ b/test/module/shared_model/bindings/ClientTest.java @@ -0,0 +1,141 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Disabled; + +import java.math.BigInteger; +import java.io.InputStream; +import java.io.IOException; + +import iroha.protocol.*; +import iroha.protocol.TransactionOuterClass.Transaction; +import iroha.protocol.Commands.Command; +import com.google.protobuf.ByteString; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ClientTest { + static { + try { + System.loadLibrary("irohajava"); + } catch (UnsatisfiedLinkError e) { + System.err.println("Native code library failed to load. \n" + e); + System.exit(1); + } + } + + private Keypair keys; + + @BeforeEach + void setUp() { + keys = new ModelCrypto().generateKeypair(); + } + + Command.Builder validAddPeer() { + return Command.newBuilder().setAddPeer(Commands.AddPeer.newBuilder().setPeer( + Primitive.Peer.newBuilder() + .setAddress("127.0.0.1:50500") + .setPeerKey(ByteString.copyFrom(String.format("%32.32s", " ").getBytes())))); + } + + Transaction.Payload.ReducedPayload.Builder defaultPayload() { + return Transaction.Payload.ReducedPayload.newBuilder() + .setCreatorAccountId("admin@test") + .setCreatedTime(System.currentTimeMillis()) + .setQuorum(1); + } + + ByteVector serialize(Transaction tx) { + ByteVector vec = new ByteVector(); + for (byte b : tx.toByteArray()) { + vec.add(b); + } + return vec; + } + + class ParseInputStream extends InputStream { + ParseInputStream(ByteVector vec) { + this.vec = vec; + index = 0; + } + + @Override + public int read() { + if (vec.size() > index) { + return vec.get(index++); + } + return -1; + } + + ByteVector vec; + int index; + } + + Transaction deserialize(ByteVector vec) { + System.out.println(vec.size()); + try { + return Transaction.parseFrom(new ParseInputStream(vec)); + } catch (IOException e) { + System.out.println(e.getMessage()); + System.exit(3); + } + return null; + } + + @Test + void hash() { + Transaction tx = Transaction.newBuilder() + .setPayload(Transaction.Payload.newBuilder().setReducedPayload( + defaultPayload().addCommands(validAddPeer()))) + .build(); + ByteVector h = iroha.hashTransaction(serialize(tx)); + assertEquals(h.size(), 32); + } + + @Test + void sign() { + Transaction tx = Transaction.newBuilder() + .setPayload(Transaction.Payload.newBuilder().setReducedPayload( + defaultPayload().addCommands(validAddPeer()))) + .build(); + assertEquals(tx.getSignaturesList().size(), 0); + Transaction signed_tx = deserialize(iroha.signTransaction(serialize(tx), keys)); + assertEquals(signed_tx.getSignaturesList().size(), 1); + } + + @Test + void validateWithoutCommand() { + Transaction tx = deserialize(iroha.signTransaction( + serialize( + Transaction.newBuilder() + .setPayload(Transaction.Payload.newBuilder().setReducedPayload(defaultPayload())) + .build()), + keys)); + assertThrows(IllegalArgumentException.class, () -> iroha.validateTransaction(serialize(tx))); + } + + @Test + void validateUnsigned() { + Transaction tx = Transaction.newBuilder() + .setPayload(Transaction.Payload.newBuilder().setReducedPayload( + defaultPayload().addCommands(validAddPeer()))) + .build(); + assertThrows(IllegalArgumentException.class, () -> iroha.validateTransaction(serialize(tx))); + } + + @Test + void validateCorrect() { + Transaction tx = deserialize( + iroha.signTransaction(serialize(Transaction.newBuilder() + .setPayload(Transaction.Payload.newBuilder().setReducedPayload( + defaultPayload().addCommands(validAddPeer()))) + .build()), + keys)); + iroha.validateTransaction(serialize(tx)); + } +} diff --git a/test/module/shared_model/bindings/QueryTest.java b/test/module/shared_model/bindings/QueryTest.java index 01ffee5346..f4b08e74b7 100644 --- a/test/module/shared_model/bindings/QueryTest.java +++ b/test/module/shared_model/bindings/QueryTest.java @@ -547,8 +547,8 @@ void getAccountDetailWithInvalidDomain() { } @Test - void getAccountDetailWithEmptyName() { - ModelQueryBuilder builder = base().getAccountDetail(""); - assertThrows(IllegalArgumentException.class, builder::build); + void getAccountDetailWithNoArgs() { + UnsignedQuery query = base().getAccountDetail().build(); + assertTrue(checkProtoQuery(proto(query))); } } diff --git a/test/module/shared_model/bindings/builder-test.py b/test/module/shared_model/bindings/builder-test.py index c82ca7175e..bad443bff3 100644 --- a/test/module/shared_model/bindings/builder-test.py +++ b/test/module/shared_model/bindings/builder-test.py @@ -9,7 +9,7 @@ import sys from google.protobuf.message import DecodeError -import block_pb2 as blk +import transaction_pb2 as trx # TODO luckychess 8.08.2018 add test for number of methods # in interface and proto implementation IR-1080 @@ -121,7 +121,7 @@ def check_proto_tx(self, blob): tmp = ''.join(map(chr, blob.blob())) else: tmp = bytes(blob.blob()) - blk.Transaction.FromString(tmp) + trx.Transaction.FromString(tmp) except DecodeError as e: print(e) return False @@ -208,47 +208,33 @@ def test_add_signatory_invalid_key(self): # ====================== AddAssetQuantity Tests ====================== def test_add_asset_quantity(self): - tx = self.builder.addAssetQuantity("admin@test", "asset#domain", "12.345").build() + tx = self.builder.addAssetQuantity("asset#domain", "12.345").build() self.assertTrue(self.check_proto_tx(self.proto(tx))) - def test_add_asset_quantity_valid_account_and_asset(self): + def test_add_asset_quantity_valid_asset(self): for name in VALID_NAMES_1: for domain in VALID_DOMAINS: - tx = self.builder.addAssetQuantity("{}@{}".format(name, domain), "{}#{}".format(name, domain), "100").build() + tx = self.builder.addAssetQuantity("{}#{}".format(name, domain), "100").build() self.assertTrue(self.check_proto_tx(self.proto(tx))) - def test_add_asset_quantity_invalid_account(self): - for name in INVALID_NAMES_1: - with self.assertRaises(ValueError): - self.base().addAssetQuantity("{}@test".format(name), "coin#test", "10").build() - - def test_add_asset_quantity_invalid_account_domain(self): - for domain in INVALID_DOMAINS: - with self.assertRaises(ValueError): - self.base().addAssetQuantity("admin@{}".format(domain), "coin#test", "10").build() - - def test_add_asset_quantity_empty_account(self): - with self.assertRaises(ValueError): - self.base().addAssetQuantity("", "coin#test", "10").build() - def test_add_asset_quantity_invalid_asset(self): for name in INVALID_NAMES_1: with self.assertRaises(ValueError): - self.base().addAssetQuantity("admin@test", "{}#test".format(name), "10").build() + self.base().addAssetQuantity("{}#test".format(name), "10").build() def test_add_asset_quantity_invalid_asset_domain(self): for domain in INVALID_DOMAINS: with self.assertRaises(ValueError): - self.base().addAssetQuantity("admin@test", "coin#{}".format(domain), "10").build() + self.base().addAssetQuantity("coin#{}".format(domain), "10").build() def test_add_asset_quantity_empty_asset(self): with self.assertRaises(ValueError): - self.base().addAssetQuantity("admin@test", "", "10").build() + self.base().addAssetQuantity("", "10").build() def test_add_asset_quantity_invalid_amount(self): for amount in ["", "-12", "-13.45", "chars", "chars10"]: with self.assertRaises(ValueError): - self.base().addAssetQuantity("admin@test", "coin#test", amount).build() + self.base().addAssetQuantity("coin#test", amount).build() # ====================== RemoveSignatory Tests ====================== @@ -532,8 +518,8 @@ def test_create_role_with_invalid_name(self): self.base().createRole(name, iroha.RolePermissionSet([iroha.Role_kReceive, iroha.Role_kGetRoles])).build() def test_create_role_with_empty_permissions(self): - with self.assertRaises(ValueError): - self.base().createRole("user", iroha.RolePermissionSet([])).build() + tx = self.builder.createRole("user", iroha.RolePermissionSet([])).build() + self.assertTrue(self.check_proto_tx(self.proto(tx))) # ====================== DetachRole Tests ====================== @@ -611,42 +597,28 @@ def test_revoke_permission_empty_account(self): def test_subtract_asset_quantity(self): for domain in VALID_DOMAINS: for name in VALID_NAMES_1: - tx = self.builder.subtractAssetQuantity("{}@{}".format(name, domain), "{}#{}".format(name, domain), "10").build() + tx = self.builder.subtractAssetQuantity("{}#{}".format(name, domain), "10").build() self.assertTrue(self.check_proto_tx(self.proto(tx))) - def test_subtract_asset_quantity_invalid_account(self): - for name in INVALID_NAMES_1: - with self.assertRaises(ValueError): - self.base().subtractAssetQuantity("{}@test".format(name), "coin#test", "10").build() - - def test_subtract_asset_quantity_invalid_account_domain(self): - for domain in INVALID_DOMAINS: - with self.assertRaises(ValueError): - self.base().subtractAssetQuantity("admin@{}".format(domain), "coin#test", "10").build() - - def test_subtract_asset_quantity_with_empty_account(self): - with self.assertRaises(ValueError): - self.base().subtractAssetQuantity("", "coin#test", "10").build() - def test_subtract_asset_quantity_invalid_asset_name(self): for name in INVALID_NAMES_1: with self.assertRaises(ValueError): - self.base().subtractAssetQuantity("admin@test", "{}#test".format(name), "10").build() + self.base().subtractAssetQuantity("{}#test".format(name), "10").build() def test_subtract_asset_quantity_invalid_asset_domain(self): for domain in INVALID_DOMAINS: with self.assertRaises(ValueError): - self.base().subtractAssetQuantity("admin@test", "coin#{}".format(domain), "10").build() + self.base().subtractAssetQuantity("coin#{}".format(domain), "10").build() def test_subtract_asset_quantity_empty_account(self): with self.assertRaises(ValueError): - self.base().subtractAssetQuantity("admin@test", "", "10").build() + self.base().subtractAssetQuantity("", "10").build() def test_subtract_asset_quantity_invalid_amount(self): amounts = ["", "0", "chars", "-10", "10chars", "10.10.10"] for amount in amounts: with self.assertRaises(ValueError): - self.base().subtractAssetQuantity("admin@test", "coin#test", amount).build() + self.base().subtractAssetQuantity("coin#test", amount).build() if __name__ == '__main__': unittest.main() diff --git a/test/module/shared_model/bindings/client-test.py b/test/module/shared_model/bindings/client-test.py new file mode 100644 index 0000000000..3e184ba63c --- /dev/null +++ b/test/module/shared_model/bindings/client-test.py @@ -0,0 +1,65 @@ +# +# Copyright Soramitsu Co., Ltd. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +from time import time +import unittest + +import transaction_pb2 as trx +import commands_pb2 as cmd +import iroha + +class ClientTest(unittest.TestCase): + def setUp(self): + self.keys = iroha.ModelCrypto().generateKeypair() + + def valid_add_peer_command(self): + command = cmd.Command() + command.add_peer.peer.address = "127.0.0.1:50500" + command.add_peer.peer.peer_key = b'A' * 32 + return command + + def unsigned_tx(self): + tx = trx.Transaction() + tx.payload.reduced_payload.creator_account_id = "admin@test" + tx.payload.reduced_payload.created_time = int(time() * 1000) + tx.payload.reduced_payload.quorum = 1 + return tx + + def test_hash(self): + tx = self.unsigned_tx() + tx.payload.reduced_payload.commands.extend([self.valid_add_peer_command()]) + h = iroha.hashTransaction(iroha.Blob(tx.SerializeToString()).blob()) + self.assertEqual(len(h), 32) + + def test_sign(self): + tx = self.unsigned_tx() + tx.payload.reduced_payload.commands.extend([self.valid_add_peer_command()]) + self.assertEqual(len(tx.signatures), 0) + tx_blob = iroha.signTransaction(iroha.Blob(tx.SerializeToString()).blob(), self.keys) + signed_tx = trx.Transaction() + signed_tx.ParseFromString(bytearray(tx_blob)) + self.assertEqual(len(signed_tx.signatures), 1) + + def test_validate_without_cmd(self): + tx = self.unsigned_tx() + tx_blob = iroha.signTransaction(iroha.Blob(tx.SerializeToString()).blob(), self.keys) + with self.assertRaises(ValueError): + iroha.validateTransaction(tx_blob) + + def test_validate_unsigned_tx(self): + tx = self.unsigned_tx() + tx.payload.reduced_payload.commands.extend([self.valid_add_peer_command()]) + self.assertEqual(len(tx.signatures), 0) + with self.assertRaises(ValueError): + iroha.validateTransaction(iroha.Blob(tx.SerializeToString()).blob()) + + def test_validate_correct_tx(self): + tx = self.unsigned_tx() + tx.payload.reduced_payload.commands.extend([self.valid_add_peer_command()]) + self.assertEqual(len(tx.signatures), 0) + iroha.signTransaction(iroha.Blob(tx.SerializeToString()).blob(), self.keys) + +if __name__ == '__main__': + unittest.main() diff --git a/test/module/shared_model/bindings/query-test.py b/test/module/shared_model/bindings/query-test.py index 68038b525c..265073f849 100644 --- a/test/module/shared_model/bindings/query-test.py +++ b/test/module/shared_model/bindings/query-test.py @@ -382,9 +382,9 @@ def test_get_account_detail_invalid_domain(self): with self.assertRaises(ValueError): self.base().getAccountDetail("admin@{}".format(domain)).build() - def test_get_account_detail_with_empty_name(self): - with self.assertRaises(ValueError): - self.base().getAccountDetail("").build() + def test_get_account_detail_with_no_args(self): + query = self.builder.getAccountDetail().build() + self.assertTrue(self.check_proto_query(self.proto(query))) if __name__ == '__main__': unittest.main() diff --git a/test/module/shared_model/builders/common_objects/CMakeLists.txt b/test/module/shared_model/builders/common_objects/CMakeLists.txt index dd24ae2349..3327ba3a9f 100644 --- a/test/module/shared_model/builders/common_objects/CMakeLists.txt +++ b/test/module/shared_model/builders/common_objects/CMakeLists.txt @@ -30,15 +30,6 @@ target_link_libraries(account_builder_test shared_model_stateless_validation ) -addtest(amount_builder_test - amount_builder_test.cpp - ) - -target_link_libraries(amount_builder_test - shared_model_proto_builders - shared_model_stateless_validation - ) - addtest(signature_builder_test signature_builder_test.cpp ) @@ -66,10 +57,11 @@ target_link_libraries(account_asset_builder_test shared_model_stateless_validation ) -addtest(query_response_builder_test - query_response_builder_test.cpp - ) - -target_link_libraries(query_response_builder_test - shared_model_proto_builders - ) +if (IROHA_ROOT_PROJECT) + addtest(query_response_builder_test + query_response_builder_test.cpp + ) + target_link_libraries(query_response_builder_test + shared_model_proto_builders + ) +endif () diff --git a/test/module/shared_model/builders/common_objects/account_asset_builder_test.cpp b/test/module/shared_model/builders/common_objects/account_asset_builder_test.cpp index e179c14567..def0d7bbfa 100644 --- a/test/module/shared_model/builders/common_objects/account_asset_builder_test.cpp +++ b/test/module/shared_model/builders/common_objects/account_asset_builder_test.cpp @@ -19,7 +19,6 @@ #include "builders/common_objects/account_asset_builder.hpp" #include "builders/protobuf/common_objects/proto_account_asset_builder.hpp" -#include "builders/protobuf/common_objects/proto_amount_builder.hpp" #include "builders_test_fixture.hpp" #include "validators/field_validator.hpp" @@ -39,8 +38,7 @@ TEST(AccountAssetBuilderTest, StatelessValidAllFields) { auto valid_account_id = "account@name"; auto valid_asset_id = "asset#coin"; - auto valid_balance = - shared_model::proto::AmountBuilder().intValue(100).precision(2).build(); + auto valid_balance = shared_model::interface::Amount("1.00"); auto account_asset = builder.accountId(valid_account_id) .assetId(valid_asset_id) @@ -73,8 +71,7 @@ TEST(AccountAssetBuilderTest, SeveralObjectsFromOneBuilder) { auto valid_account_id = "account@name"; auto valid_asset_id = "asset#coin"; - auto valid_balance = - shared_model::proto::AmountBuilder().intValue(100).precision(2).build(); + auto valid_balance = shared_model::interface::Amount("1.00"); auto state = builder.accountId(valid_account_id) .assetId(valid_asset_id) diff --git a/test/module/shared_model/builders/common_objects/amount_builder_test.cpp b/test/module/shared_model/builders/common_objects/amount_builder_test.cpp deleted file mode 100644 index 2dc5c2dadc..0000000000 --- a/test/module/shared_model/builders/common_objects/amount_builder_test.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include "builders/common_objects/amount_builder.hpp" -#include "builders/protobuf/common_objects/proto_amount_builder.hpp" -#include "builders_test_fixture.hpp" -#include "validators/field_validator.hpp" - -// TODO: 14.02.2018 nickaleks mock builder implementation IR-970 -// TODO: 14.02.2018 nickaleks mock field validator IR-971 - -/** - * @given field values which pass stateless validation - * @when AmountBuilder is invoked - * @then Amount object is successfully constructed and has valid fields - */ -TEST(AmountBuilderTest, StatelessValidAllFields) { - shared_model::builder::AmountBuilder - builder; - - boost::multiprecision::uint256_t valid_value = 100; - auto valid_precision = 2; - - auto amount = - builder.intValue(valid_value).precision(valid_precision).build(); - - amount.match( - [&](shared_model::builder::BuilderResult< - shared_model::interface::Amount>::ValueType &v) { - EXPECT_EQ(v.value->intValue(), valid_value); - EXPECT_EQ(v.value->precision(), valid_precision); - }, - [](shared_model::builder::BuilderResult< - shared_model::interface::Amount>::ErrorType &e) { - FAIL() << *e.error; - }); -} - -/** - * @given field values which pass stateless validation - * @when AmountBuilder is invoked twice - * @then Two identical (==) Amount objects are constructed - */ -TEST(AmountBuilderTest, SeveralObjectsFromOneBuilder) { - shared_model::builder::AmountBuilder - builder; - - boost::multiprecision::uint256_t valid_value = 100; - auto valid_precision = 2; - - auto state = builder.intValue(valid_value).precision(valid_precision); - - auto amount = state.build(); - auto amount2 = state.build(); - - testResultObjects(amount, amount2, [](auto &a, auto &b) { - // pointer points to different objects - ASSERT_TRUE(a != b); - - EXPECT_EQ(a->intValue(), b->intValue()); - EXPECT_EQ(a->precision(), b->precision()); - }); -} diff --git a/test/module/shared_model/builders/common_objects/query_response_builder_test.cpp b/test/module/shared_model/builders/common_objects/query_response_builder_test.cpp index 74af4e36c3..bdd9d998fe 100644 --- a/test/module/shared_model/builders/common_objects/query_response_builder_test.cpp +++ b/test/module/shared_model/builders/common_objects/query_response_builder_test.cpp @@ -22,7 +22,6 @@ #include "builders/default_builders.hpp" #include "builders/protobuf/builder_templates/query_response_template.hpp" #include "builders/protobuf/common_objects/proto_account_builder.hpp" -#include "builders/protobuf/common_objects/proto_amount_builder.hpp" #include "builders/protobuf/query_responses/proto_block_query_response_builder.hpp" #include "builders/query_responses/block_query_response_builder.hpp" #include "cryptography/keypair.hpp" @@ -40,26 +39,22 @@ const auto hash = std::string(32, '0'); uint64_t height = 1; uint8_t quorum = 2; -boost::multiprecision::uint256_t valid_value = 1000; auto valid_precision = 1; -const iroha::Amount amount(valid_value, valid_precision); -const auto proto_amount = shared_model::proto::AmountBuilder() - .intValue(valid_value) - .precision(valid_precision) - .build(); + const shared_model::interface::types::DetailType account_detail = "account-detail"; const auto query_hash = shared_model::interface::types::HashType("hashhash"); decltype(iroha::time::now()) created_time = iroha::time::now(); TEST(QueryResponseBuilderTest, AccountAssetResponse) { + const auto amount = shared_model::interface::Amount("10.00"); shared_model::proto::TemplateQueryResponseBuilder<> builder; shared_model::proto::QueryResponse query_response = builder.queryHash(query_hash) .accountAssetResponse({shared_model::proto::AccountAssetBuilder() .accountId(account_id) .assetId(asset_id) - .balance(proto_amount) + .balance(amount) .build()}) .build(); ASSERT_NO_THROW({ @@ -71,7 +66,7 @@ TEST(QueryResponseBuilderTest, AccountAssetResponse) { ASSERT_EQ(asset_response.assetId(), asset_id); ASSERT_EQ(asset_response.accountId(), account_id); - ASSERT_EQ(asset_response.balance(), proto_amount); + ASSERT_EQ(asset_response.balance(), amount); ASSERT_EQ(query_response.queryHash(), query_hash); }); } diff --git a/test/module/shared_model/builders/protobuf/CMakeLists.txt b/test/module/shared_model/builders/protobuf/CMakeLists.txt index cf25e2865a..41be205a07 100644 --- a/test/module/shared_model/builders/protobuf/CMakeLists.txt +++ b/test/module/shared_model/builders/protobuf/CMakeLists.txt @@ -12,13 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -addtest(transport_builder_test - transport_builder_test.cpp - ) -target_link_libraries(transport_builder_test - shared_model_proto_builders - shared_model_stateless_validation - ) +if (IROHA_ROOT_PROJECT) + addtest(transport_builder_test + transport_builder_test.cpp + ) + target_link_libraries(transport_builder_test + shared_model_proto_builders + shared_model_stateless_validation + ) +endif () addtest(proto_peer_builder_test common_objects/proto_peer_builder_test.cpp @@ -36,14 +38,6 @@ target_link_libraries(proto_account_builder_test shared_model_proto_builders ) -addtest(proto_amount_builder_test - common_objects/proto_amount_builder_test.cpp - ) - -target_link_libraries(proto_amount_builder_test - shared_model_proto_builders - ) - addtest(proto_signature_builder_test common_objects/proto_signature_builder_test.cpp ) diff --git a/test/module/shared_model/builders/protobuf/block_builder_test.cpp b/test/module/shared_model/builders/protobuf/block_builder_test.cpp index bc6ad0013e..3f0979f69a 100644 --- a/test/module/shared_model/builders/protobuf/block_builder_test.cpp +++ b/test/module/shared_model/builders/protobuf/block_builder_test.cpp @@ -21,7 +21,7 @@ TEST(BlockBuilderTest, BlockWithTransactions) { .createdTime(iroha::time::now()) .creatorAccountId("admin@test") .quorum(1) - .addAssetQuantity("admin@test", "coin#test", "1.0") + .addAssetQuantity("coin#test", "1.0") .build(); ASSERT_NO_THROW( diff --git a/test/module/shared_model/builders/protobuf/common_objects/proto_account_asset_builder_test.cpp b/test/module/shared_model/builders/protobuf/common_objects/proto_account_asset_builder_test.cpp index a1aefcf30b..f95341d5ab 100644 --- a/test/module/shared_model/builders/protobuf/common_objects/proto_account_asset_builder_test.cpp +++ b/test/module/shared_model/builders/protobuf/common_objects/proto_account_asset_builder_test.cpp @@ -18,7 +18,6 @@ #include #include "builders/protobuf/common_objects/proto_account_asset_builder.hpp" -#include "builders/protobuf/common_objects/proto_amount_builder.hpp" /** * @given fields for AccountAsset object @@ -31,8 +30,7 @@ TEST(ProtoAccountAssetBuilder, AllFieldsBuild) { auto expected_account_id = "account@name"; auto expected_asset_id = "asset#coin"; - auto expected_balance = - shared_model::proto::AmountBuilder().intValue(100).precision(2).build(); + auto expected_balance = shared_model::interface::Amount("1.00"); auto account_asset = builder.accountId(expected_account_id) .assetId(expected_asset_id) @@ -54,8 +52,7 @@ TEST(ProtoAccountAssetBuilderTest, SeveralObjectsFromOneBuilder) { auto expected_account_id = "account@name"; auto expected_asset_id = "asset#coin"; - auto expected_balance = - shared_model::proto::AmountBuilder().intValue(100).precision(2).build(); + auto expected_balance = shared_model::interface::Amount("1.00"); auto state = builder.accountId(expected_account_id) .assetId(expected_asset_id) diff --git a/test/module/shared_model/builders/protobuf/common_objects/proto_amount_builder_test.cpp b/test/module/shared_model/builders/protobuf/common_objects/proto_amount_builder_test.cpp deleted file mode 100644 index d916619564..0000000000 --- a/test/module/shared_model/builders/protobuf/common_objects/proto_amount_builder_test.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include - -#include "builders/protobuf/common_objects/proto_amount_builder.hpp" - -/** - * @given fields for Amount object - * @when AmountBuilder is invoked - * @then Amount object is successfully constructed and has the same fields as - * provided - */ -TEST(ProtoAmountBuilderTest, AllFieldsBuild) { - shared_model::proto::AmountBuilder builder; - - boost::multiprecision::uint256_t expected_int_value = 100; - auto expected_precision = 2; - - auto amount = builder.intValue(expected_int_value) - .precision(expected_precision) - .build(); - - EXPECT_EQ(amount.intValue(), expected_int_value); - EXPECT_EQ(amount.precision(), expected_precision); -} - -/** - * @given fields for Amount object - * @when AmountBuilder is invoked twice with the same configuration - * @then Two constructed Amount objects are identical - */ -TEST(ProtoAmountBuilderTest, SeveralObjectsFromOneBuilder) { - shared_model::proto::AmountBuilder builder; - - boost::multiprecision::uint256_t expected_int_value{"123456789012345678901234567890"}; - auto expected_precision = 2; - - auto state = - builder.intValue(expected_int_value).precision(expected_precision); - auto amount = state.build(); - auto amount2 = state.build(); - - EXPECT_EQ(amount.intValue(), amount2.intValue()); - EXPECT_EQ(amount.precision(), amount2.precision()); -} diff --git a/test/module/shared_model/builders/protobuf/test_account_asset_builder.hpp b/test/module/shared_model/builders/protobuf/test_account_asset_builder.hpp index d7afc38019..ca7eb86c5d 100644 --- a/test/module/shared_model/builders/protobuf/test_account_asset_builder.hpp +++ b/test/module/shared_model/builders/protobuf/test_account_asset_builder.hpp @@ -24,6 +24,6 @@ * Builder alias, to build shared model proto block object avoiding validation * and "required fields" check */ -using TestAccountAssetBuilder = shared_model::proto::AccountAsset; +using TestAccountAssetBuilder = shared_model::proto::AccountAssetBuilder; #endif //IROHA_TEST_ACCOUNT_BUILDER_HPP diff --git a/test/module/shared_model/builders/protobuf/test_asset_builder.hpp b/test/module/shared_model/builders/protobuf/test_asset_builder.hpp new file mode 100644 index 0000000000..3ef9a470f3 --- /dev/null +++ b/test/module/shared_model/builders/protobuf/test_asset_builder.hpp @@ -0,0 +1,29 @@ +/** + * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. + * http://soramitsu.co.jp + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IROHA_TEST_ASSET_BUILDER_HPP +#define IROHA_TEST_ASSET_BUILDER_HPP + +#include "builders/protobuf/common_objects/proto_asset_builder.hpp" + +/** + * Builder alias, to build shared model proto block object avoiding validation + * and "required fields" check + */ +using TestAccountAssetBuilder = shared_model::proto::AssetBuilder; + +#endif //IROHA_TEST_ASSET_BUILDER_HPP diff --git a/test/module/shared_model/builders/protobuf/test_transaction_builder.hpp b/test/module/shared_model/builders/protobuf/test_transaction_builder.hpp index 9b9f74da50..8edb5917da 100644 --- a/test/module/shared_model/builders/protobuf/test_transaction_builder.hpp +++ b/test/module/shared_model/builders/protobuf/test_transaction_builder.hpp @@ -18,9 +18,13 @@ #ifndef IROHA_TEST_TRANSACTION_BUILDER_HPP #define IROHA_TEST_TRANSACTION_BUILDER_HPP +#include + #include "builders/protobuf/builder_templates/transaction_template.hpp" #include "module/shared_model/validators/validators.hpp" +using ProtoTxType = shared_model::proto::Transaction; + /** * Builder alias, to build shared model proto transaction object avoiding * validation and "required fields" check @@ -28,12 +32,27 @@ using TestTransactionBuilder = shared_model::proto::TemplateTransactionBuilder< (1 << shared_model::proto::TemplateTransactionBuilder<>::total) - 1, shared_model::validation::AlwaysValidValidator, - shared_model::proto::Transaction>; + ProtoTxType>; +/** + * Builder for creating \class shared_model::proto::UnsignedWrapper of \class + * ProtoTxType + */ using TestUnsignedTransactionBuilder = shared_model::proto::TemplateTransactionBuilder< (1 << shared_model::proto::TemplateTransactionBuilder<>::total) - 1, shared_model::validation::AlwaysValidValidator, - shared_model::proto::UnsignedWrapper>; + shared_model::proto::UnsignedWrapper>; + +/** + * Wrapper for making shared_ptr on transaction from the passed builder + * @tparam Builder - universal reference type of the passed builder + * @param builder - instance of the passed builder + * @return shared_ptr to completed object + */ +template +inline auto makePolyTxFromBuilder(Builder &&builder) { + return std::make_shared(builder.build()); +} #endif // IROHA_TEST_TRANSACTION_BUILDER_HPP diff --git a/test/module/shared_model/builders/protobuf/transport_builder_test.cpp b/test/module/shared_model/builders/protobuf/transport_builder_test.cpp index 745ff7329a..f24e90bc56 100644 --- a/test/module/shared_model/builders/protobuf/transport_builder_test.cpp +++ b/test/module/shared_model/builders/protobuf/transport_builder_test.cpp @@ -1,49 +1,52 @@ /** - * Copyright Soramitsu Co., Ltd. 2018 All Rights Reserved. - * http://soramitsu.co.jp - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 */ #include -#include "block.pb.h" +#include #include "builders/protobuf/block.hpp" #include "builders/protobuf/block_variant_transport_builder.hpp" #include "builders/protobuf/empty_block.hpp" #include "builders/protobuf/proposal.hpp" #include "builders/protobuf/queries.hpp" #include "builders/protobuf/transaction.hpp" +#include "builders/protobuf/transaction_sequence_builder.hpp" #include "builders/protobuf/transport_builder.hpp" #include "common/types.hpp" +#include "endpoint.pb.h" +#include "framework/batch_helper.hpp" #include "framework/result_fixture.hpp" +#include "interfaces/common_objects/types.hpp" +#include "interfaces/iroha_internal/transaction_sequence.hpp" #include "module/shared_model/builders/protobuf/test_block_builder.hpp" #include "module/shared_model/builders/protobuf/test_empty_block_builder.hpp" #include "module/shared_model/builders/protobuf/test_proposal_builder.hpp" #include "module/shared_model/builders/protobuf/test_query_builder.hpp" #include "module/shared_model/builders/protobuf/test_transaction_builder.hpp" +#include "validators/transactions_collection/batch_order_validator.hpp" using namespace shared_model; using namespace shared_model::proto; using namespace iroha::expected; using iroha::operator|; +using TransactionSequenceBuilder = TransportBuilder< + interface::TransactionSequence, + validation::UnsignedTransactionsCollectionValidator< + validation::TransactionValidator< + validation::FieldValidator, + validation::CommandValidatorVisitor>, + validation::BatchOrderValidator>>; + class TransportBuilderTest : public ::testing::Test { protected: void SetUp() override { created_time = iroha::time::now(); invalid_created_time = 123; account_id = "account@domain"; + account_id2 = "acccount@domain"; quorum = 2; counter = 1048576; hash = std::string(32, '0'); @@ -59,7 +62,10 @@ class TransportBuilderTest : public ::testing::Test { .quorum(quorum) .setAccountQuorum(account_id, quorum); } - + auto createUnbuildTransaction() { + return getBaseTransactionBuilder() + .creatorAccountId(account_id); + } auto createTransaction() { return getBaseTransactionBuilder() .creatorAccountId(account_id) @@ -164,23 +170,26 @@ class TransportBuilderTest : public ::testing::Test { .transactions(std::vector()) .build(); } - /** * Receives model object, gets transport from it, converts transport into * model object and checks if original and obtained model objects are the same - * @tparam T model object type - * @tparam SV validator type + * @tparam ObjectOriginalModel - model object type + * @tparam Validator - validator type * @param orig_model * @param successCase function invoking if value exists * @param failCase function invoking when error returned */ - template - void testTransport(T orig_model, - std::function &)> successCase, - std::function &)> failCase) { + template + void testTransport(const ObjectOriginalModel &orig_model, + SuccessCase &&successCase, + FailCase &&failCase) { auto proto_model = orig_model.getTransport(); - auto built_model = TransportBuilder().build(proto_model); + auto built_model = + TransportBuilder().build(proto_model); built_model.match(successCase, failCase); } @@ -189,6 +198,7 @@ class TransportBuilderTest : public ::testing::Test { decltype(iroha::time::now()) created_time; decltype(created_time) invalid_created_time; std::string account_id; + std::string account_id2; uint8_t quorum; uint64_t counter; std::string hash; @@ -208,8 +218,7 @@ class TransportBuilderTest : public ::testing::Test { */ TEST_F(TransportBuilderTest, TransactionCreationTest) { auto orig_model = createTransaction(); - testTransport( + testTransport( orig_model, [&orig_model](const Value &model) { ASSERT_EQ(model.value.getTransport().SerializeAsString(), @@ -228,8 +237,7 @@ TEST_F(TransportBuilderTest, TransactionCreationTest) { */ TEST_F(TransportBuilderTest, InvalidTransactionCreationTest) { auto orig_model = createInvalidTransaction(); - testTransport( + testTransport( orig_model, [](const Value) { FAIL(); }, [](const Error &) { SUCCEED(); }); @@ -244,8 +252,7 @@ TEST_F(TransportBuilderTest, InvalidTransactionCreationTest) { */ TEST_F(TransportBuilderTest, QueryCreationTest) { auto orig_model = createQuery(); - testTransport( + testTransport( orig_model, [&orig_model](const Value &model) { ASSERT_EQ(model.value.getTransport().SerializeAsString(), @@ -261,8 +268,7 @@ TEST_F(TransportBuilderTest, QueryCreationTest) { */ TEST_F(TransportBuilderTest, InvalidQueryCreationTest) { auto orig_model = createInvalidQuery(); - testTransport( + testTransport( orig_model, [](const Value) { FAIL(); }, [](const Error &) { SUCCEED(); }); @@ -277,7 +283,7 @@ TEST_F(TransportBuilderTest, InvalidQueryCreationTest) { */ TEST_F(TransportBuilderTest, BlockCreationTest) { auto orig_model = createBlock(); - testTransport( + testTransport( orig_model, [&orig_model](const Value &model) { ASSERT_EQ(model.value.getTransport().SerializeAsString(), @@ -293,10 +299,10 @@ TEST_F(TransportBuilderTest, BlockCreationTest) { */ TEST_F(TransportBuilderTest, InvalidBlockCreationTest) { auto orig_model = createInvalidBlock(); - testTransport( + testTransport( orig_model, - [](const Value) { FAIL(); }, - [](const Error &) { SUCCEED(); }); + [](const Value> &) { FAIL(); }, + [](const Error &) { SUCCEED(); }); } //-------------------------------------PROPOSAL------------------------------------- @@ -308,7 +314,7 @@ TEST_F(TransportBuilderTest, InvalidBlockCreationTest) { */ TEST_F(TransportBuilderTest, ProposalCreationTest) { auto orig_model = createProposal(); - testTransport( + testTransport( orig_model, [&orig_model](const Value &model) { ASSERT_EQ(model.value.getTransport().SerializeAsString(), @@ -325,9 +331,9 @@ TEST_F(TransportBuilderTest, ProposalCreationTest) { */ TEST_F(TransportBuilderTest, DISABLED_EmptyProposalCreationTest) { auto orig_model = createEmptyProposal(); - testTransport( + testTransport( orig_model, - [](const Value) { FAIL(); }, + [](const Value &) { FAIL(); }, [](const Error &) { SUCCEED(); }); } @@ -340,7 +346,7 @@ TEST_F(TransportBuilderTest, DISABLED_EmptyProposalCreationTest) { */ TEST_F(TransportBuilderTest, EmptyBlockCreationTest) { auto orig_model = createEmptyBlock(); - testTransport( + testTransport( orig_model, [&orig_model](const Value &model) { ASSERT_EQ(model.value.getTransport().SerializeAsString(), @@ -434,3 +440,118 @@ TEST_F(TransportBuilderTest, BlockVariantWithInvalidBlock) { .build(block.getTransport())); ASSERT_TRUE(error); } + +//---------------------------Transaction Sequence------------------------------- + +/** + * @given empty range of transactions + * @when TransportBuilder tries to build TransactionSequence object + * @then built object contains TransactionSequence shared model object + * AND it containcs 0 transactions + */ +TEST_F(TransportBuilderTest, TransactionSequenceEmpty) { + iroha::protocol::TxList tx_list; + auto val = + framework::expected::val(TransactionSequenceBuilder().build(tx_list)); + ASSERT_TRUE(val); + val | [](auto &seq) { EXPECT_EQ(boost::size(seq.value.transactions()), 0); }; +} + +struct getProtocolTx { + iroha::protocol::Transaction operator()( + const std::shared_ptr tx) const { + return std::static_pointer_cast(tx)->getTransport(); + } +}; + +/** + * @given sequence of transaction with a right order + * @when TransportBuilder tries to build TransactionSequence object + * @then built object contains TransactionSequence shared model object + */ +TEST_F(TransportBuilderTest, TransactionSequenceCorrect) { + iroha::protocol::TxList tx_list; + auto now = iroha::time::now(); + auto batch1 = framework::batch::createUnsignedBatchTransactions( + interface::types::BatchType::ATOMIC, 10, now); + auto batch2 = framework::batch::createUnsignedBatchTransactions( + interface::types::BatchType::ATOMIC, 5, now + 1); + auto batch3 = framework::batch::createUnsignedBatchTransactions( + interface::types::BatchType::ATOMIC, 5, now + 2); + std::for_each(std::begin(batch1), std::end(batch1), [&tx_list](auto &tx) { + new (tx_list.add_transactions()) iroha::protocol::Transaction( + std::static_pointer_cast(tx)->getTransport()); + }); + + std::for_each(std::begin(batch2), std::end(batch2), [&tx_list](auto &tx) { + new (tx_list.add_transactions()) iroha::protocol::Transaction( + std::static_pointer_cast(tx)->getTransport()); + }); + + new (tx_list.add_transactions()) + iroha::protocol::Transaction(createTransaction().getTransport()); + new (tx_list.add_transactions()) + iroha::protocol::Transaction(createTransaction().getTransport()); + new (tx_list.add_transactions()) + iroha::protocol::Transaction(createTransaction().getTransport()); + std::for_each(std::begin(batch3), std::end(batch3), [&tx_list](auto &tx) { + new (tx_list.add_transactions()) iroha::protocol::Transaction( + std::static_pointer_cast(tx)->getTransport()); + }); + new (tx_list.add_transactions()) + iroha::protocol::Transaction(createTransaction().getTransport()); + + auto val = + framework::expected::val(TransactionSequenceBuilder().build(tx_list)); + + val | [](auto &seq) { EXPECT_EQ(boost::size(seq.value.transactions()), 24); }; +} +/** + * @given batch of transaction with transaction in the middle + * @when TransportBuilder tries to build TransactionSequence object + * @then built an error + * @note disabled because current algorithm of creating batches can process list + * of transaction where in the middle of the batch can appear single independent + * transaction + */ +TEST_F(TransportBuilderTest, DISABLED_TransactionInteraptedBatch) { + iroha::protocol::TxList tx_list; + auto batch = framework::batch::createUnsignedBatchTransactions( + interface::types::BatchType::ATOMIC, 10); + std::for_each(std::begin(batch), std::begin(batch) + 3, [&tx_list](auto &tx) { + new (tx_list.add_transactions()) iroha::protocol::Transaction( + std::static_pointer_cast(tx)->getTransport()); + }); + new (tx_list.add_transactions()) + iroha::protocol::Transaction(createTransaction().getTransport()); + std::for_each(std::begin(batch) + 3, std::end(batch), [&tx_list](auto &tx) { + new (tx_list.add_transactions()) iroha::protocol::Transaction( + std::static_pointer_cast(tx)->getTransport()); + }); + + auto error = + framework::expected::err(TransactionSequenceBuilder().build(tx_list)); + ASSERT_TRUE(error); +} + +/** + * @given batch of transaction with wrong order + * @when TransportBuilder tries to build TransactionSequence object + * @then built an error + */ +TEST_F(TransportBuilderTest, BatchWrongOrder) { + iroha::protocol::TxList tx_list; + auto batch = framework::batch::createUnsignedBatchTransactions( + interface::types::BatchType::ATOMIC, 10); + std::for_each(std::begin(batch) + 3, std::end(batch), [&tx_list](auto &tx) { + new (tx_list.add_transactions()) iroha::protocol::Transaction( + std::static_pointer_cast(tx)->getTransport()); + }); + std::for_each(std::begin(batch), std::begin(batch) + 3, [&tx_list](auto &tx) { + new (tx_list.add_transactions()) iroha::protocol::Transaction( + std::static_pointer_cast(tx)->getTransport()); + }); + auto error = + framework::expected::err(TransactionSequenceBuilder().build(tx_list)); + ASSERT_TRUE(error); +} diff --git a/test/module/shared_model/cryptography/CMakeLists.txt b/test/module/shared_model/cryptography/CMakeLists.txt index f704652d62..c2c2ea832a 100644 --- a/test/module/shared_model/cryptography/CMakeLists.txt +++ b/test/module/shared_model/cryptography/CMakeLists.txt @@ -8,7 +8,6 @@ target_link_libraries(crypto_usage_test shared_model_proto_builders shared_model_cryptography schema - iroha_amount ) addtest(security_signatures_test security_signatures_test.cpp) diff --git a/test/module/shared_model/interface_mocks.hpp b/test/module/shared_model/interface_mocks.hpp new file mode 100644 index 0000000000..fb8b3789a7 --- /dev/null +++ b/test/module/shared_model/interface_mocks.hpp @@ -0,0 +1,50 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "interfaces/iroha_internal/block.hpp" +#include "interfaces/transaction.hpp" + +namespace iface = shared_model::interface; + +struct BlockMock : public iface::Block { + MOCK_CONST_METHOD0(txsNumber, iface::types::TransactionsNumberType()); + MOCK_CONST_METHOD0(transactions, iface::types::TransactionsCollectionType()); + MOCK_CONST_METHOD0(height, iface::types::HeightType()); + MOCK_CONST_METHOD0(prevHash, const iface::types::HashType &()); + MOCK_CONST_METHOD0(signatures, iface::types::SignatureRangeType()); + MOCK_CONST_METHOD0(createdTime, iface::types::TimestampType()); + MOCK_CONST_METHOD0(payload, const iface::types::BlobType &()); + MOCK_CONST_METHOD0(blob, const iface::types::BlobType &()); + MOCK_METHOD2(addSignature, + bool(const shared_model::crypto::Signed &, + const shared_model::crypto::PublicKey &)); + MOCK_CONST_METHOD0(clone, BlockMock *()); +}; + +struct TransactionMock : public iface::Transaction { + MOCK_CONST_METHOD0(creatorAccountId, const iface::types::AccountIdType &()); + MOCK_CONST_METHOD0(quorum, iface::types::QuorumType()); + MOCK_CONST_METHOD0(commands, CommandsType()); + MOCK_CONST_METHOD0(reduced_payload, const iface::types::BlobType &()); + MOCK_CONST_METHOD0(batch_meta, + boost::optional>()); + MOCK_CONST_METHOD0(signatures, iface::types::SignatureRangeType()); + MOCK_CONST_METHOD0(createdTime, iface::types::TimestampType()); + MOCK_CONST_METHOD0(payload, const iface::types::BlobType &()); + MOCK_CONST_METHOD0(blob, const iface::types::BlobType &()); + MOCK_METHOD2(addSignature, + bool(const shared_model::crypto::Signed &, + const shared_model::crypto::PublicKey &)); + MOCK_CONST_METHOD0(clone, TransactionMock *()); +}; + +struct SignatureMock : public iface::Signature { + MOCK_CONST_METHOD0(publicKey, const PublicKeyType &()); + MOCK_CONST_METHOD0(signedData, const SignedType &()); + MOCK_CONST_METHOD0(clone, SignatureMock *()); +}; diff --git a/test/module/shared_model/interface_test.cpp b/test/module/shared_model/interface_test.cpp new file mode 100644 index 0000000000..99ec56e7c1 --- /dev/null +++ b/test/module/shared_model/interface_test.cpp @@ -0,0 +1,89 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "logger/logger.hpp" + +#include "builders/protobuf/transaction.hpp" + +class TransactionFixture : public ::testing::Test { + public: + TransactionFixture() + : keypair(shared_model::crypto::DefaultCryptoAlgorithmType:: + generateKeypair()), + time(iroha::time::now()) {} + + shared_model::crypto::Keypair keypair; + shared_model::interface::types::TimestampType time; + + logger::Logger log = logger::log("TransactionFixture"); + + auto makeTx() { + log->info("keypair = {}, timestemp = {}", keypair.toString(), time); + return std::make_shared( + shared_model::proto::TransactionBuilder() + .createdTime(time) + .creatorAccountId("user@test") + .setAccountQuorum("user@test", 1u) + .quorum(1) + .build() + .signAndAddSignature(keypair) + .finish()); + } +}; + +/** + * @given two same transactions + * @when nothing to do + * @then checks that transactions are the same + */ +TEST_F(TransactionFixture, checkEqualsOperatorObvious) { + auto tx1 = makeTx(); + auto tx2 = makeTx(); + ASSERT_EQ(*tx1, *tx2); +} + +/** + * @given two same transactions + * @when add same signatures to them + * @then checks that transactions are the same + */ +TEST_F(TransactionFixture, checkEqualsOperatorSameOrder) { + auto tx1 = makeTx(); + auto tx2 = makeTx(); + + tx1->addSignature(shared_model::crypto::Signed("signed_blob"), + shared_model::crypto::PublicKey("pub_key")); + tx2->addSignature(shared_model::crypto::Signed("signed_blob"), + shared_model::crypto::PublicKey("pub_key")); + + ASSERT_EQ(*tx1, *tx2); +} + +/** + * @given two same transactions + * @when add N signatures to first one and same but in reverse order to second + * @then checks that transactions are the same + */ +TEST_F(TransactionFixture, checkEqualsOperatorDifferentOrder) { + auto tx1 = makeTx(); + auto tx2 = makeTx(); + + auto N = 5; + + for (int i = 0; i < N; ++i) { + tx1->addSignature( + shared_model::crypto::Signed("signed_blob_" + std::to_string(i)), + shared_model::crypto::PublicKey("pub_key_" + std::to_string(i))); + } + + for (int i = N - 1; i >= 0; --i) { + tx2->addSignature( + shared_model::crypto::Signed("signed_blob_" + std::to_string(i)), + shared_model::crypto::PublicKey("pub_key_" + std::to_string(i))); + } + + ASSERT_EQ(*tx1, *tx2); +} diff --git a/test/module/shared_model/validators/field_validator_test.cpp b/test/module/shared_model/validators/field_validator_test.cpp index 12ba005b14..eb45249383 100644 --- a/test/module/shared_model/validators/field_validator_test.cpp +++ b/test/module/shared_model/validators/field_validator_test.cpp @@ -25,6 +25,7 @@ #include #include #include +#include "block.pb.h" #include "backend/protobuf/common_objects/peer.hpp" #include "backend/protobuf/permissions.hpp" @@ -267,12 +268,9 @@ class FieldValidatorTest : public ValidatorsTest { idTestCases("asset_id", &FieldValidatorTest::asset_id, '#'); std::vector amount_test_cases{ - {"valid_amount", - [&] { amount.mutable_value()->set_fourth(100); }, - true, - ""}, + {"valid_amount", [&] { amount = "100"; }, true, ""}, {"zero_amount", - [&] { amount.mutable_value()->set_fourth(0); }, + [&] { amount = "0"; }, false, "Amount must be greater than 0, passed value: 0"}}; @@ -562,6 +560,17 @@ class FieldValidatorTest : public ValidatorsTest { return all_cases; }(); + std::vector batch_meta_test_cases = [&]() { + iroha::protocol::Transaction::Payload::BatchMeta meta; + meta.set_type(iroha::protocol::Transaction::Payload::BatchMeta::BatchType:: + Transaction_Payload_BatchMeta_BatchType_ATOMIC); + meta.add_reduced_hashes("tst"); + std::vector all_cases; + all_cases.push_back(makeTestCase( + "batch meta test", &FieldValidatorTest::batch_meta, meta, true, "")); + return all_cases; + }(); + std::vector precision_test_cases{ makeValidCase(&FieldValidatorTest::precision, 0), makeValidCase(&FieldValidatorTest::precision, 1), @@ -632,11 +641,12 @@ class FieldValidatorTest : public ValidatorsTest { &FieldValidator::validateAssetId, &FieldValidatorTest::asset_id, asset_id_test_cases), - makeTransformValidator("amount", - &FieldValidator::validateAmount, - &FieldValidatorTest::amount, - [](auto &&x) { return proto::Amount(x); }, - amount_test_cases), + makeTransformValidator( + "amount", + &FieldValidator::validateAmount, + &FieldValidatorTest::amount, + [](auto &&x) { return shared_model::interface::Amount(x); }, + amount_test_cases), makeTransformValidator("peer", &FieldValidator::validatePeer, &FieldValidatorTest::peer, @@ -646,6 +656,10 @@ class FieldValidatorTest : public ValidatorsTest { &FieldValidator::validateAccountName, &FieldValidatorTest::account_name, account_name_test_cases), + makeValidator("writer", + &FieldValidator::validateAccountId, + &FieldValidatorTest::account_id, + account_id_test_cases), makeValidator("domain_id", &FieldValidator::validateDomainId, &FieldValidatorTest::domain_id, @@ -667,7 +681,13 @@ class FieldValidatorTest : public ValidatorsTest { makeValidator("description", &FieldValidator::validateDescription, &FieldValidatorTest::description, - description_test_cases)}; + description_test_cases), + makeTransformValidator( + "batch", + &FieldValidator::validateBatchMeta, + &FieldValidatorTest::batch_meta, + [](auto &&x) { return shared_model::proto::BatchMeta(x); }, + batch_meta_test_cases)}; }; /** @@ -685,7 +705,7 @@ TEST_F(FieldValidatorTest, CommandFieldsValidation) { [] { return iroha::protocol::Command::descriptor(); }, [&](auto field) { // Add new command to transaction - auto command = payload->add_commands(); + auto command = payload->mutable_reduced_payload()->add_commands(); // // Set concrete type for new command return command->GetReflection()->MutableMessage(command, field); }, @@ -700,18 +720,13 @@ TEST_F(FieldValidatorTest, CommandFieldsValidation) { * meaningful message */ TEST_F(FieldValidatorTest, TransactionFieldsValidation) { - iroha::protocol::Transaction proto_tx; - proto_tx.add_signatures(); // at least one signature in message - + auto proto_tx = std::make_shared(); + proto_tx->add_signatures(); // at least one signature in message + proto_tx->mutable_payload()->mutable_reduced_payload()->add_commands(); // iterate over all fields in transaction - iterateContainer( - [] { return iroha::protocol::Transaction::descriptor(); }, - [&](auto field) { - return field->is_repeated() - ? proto_tx.GetReflection()->MutableRepeatedMessage( - &proto_tx, field, 0) - : proto_tx.GetReflection()->MutableMessage(&proto_tx, field); - }, + iterateContainerRecursive( + proto_tx, + field_validators, [this](auto field, auto transaction_field) { this->runTestCases(field); }, [] {}); } diff --git a/test/module/shared_model/validators/query_validator_test.cpp b/test/module/shared_model/validators/query_validator_test.cpp index 772712921e..0f89fad225 100644 --- a/test/module/shared_model/validators/query_validator_test.cpp +++ b/test/module/shared_model/validators/query_validator_test.cpp @@ -21,7 +21,7 @@ class QueryValidatorTest : public ValidatorsTest { public: - shared_model::validation::DefaultQueryValidator query_validator; + shared_model::validation::DefaultUnsignedQueryValidator query_validator; }; using namespace shared_model; @@ -66,6 +66,20 @@ TEST_F(QueryValidatorTest, StatelessValidTest) { }); } +/** + * @given Protobuf query object with unset query + * @when validate is called + * @then there is a error returned + */ +TEST_F(QueryValidatorTest, UnsetQuery) { + iroha::protocol::Query qry; + qry.mutable_payload()->mutable_meta()->set_created_time(created_time); + qry.mutable_payload()->mutable_meta()->set_creator_account_id(account_id); + qry.mutable_payload()->mutable_meta()->set_query_counter(counter); + auto answer = query_validator.validate(proto::Query(qry)); + ASSERT_TRUE(answer.hasErrors()); +} + /** * @given Protobuf query object * @when Query has no fields set, and each query type has no fields set diff --git a/test/module/shared_model/validators/transaction_validator_test.cpp b/test/module/shared_model/validators/transaction_validator_test.cpp index cbf2d1b0c4..34b72767aa 100644 --- a/test/module/shared_model/validators/transaction_validator_test.cpp +++ b/test/module/shared_model/validators/transaction_validator_test.cpp @@ -33,12 +33,13 @@ class TransactionValidatorTest : public ValidatorsTest { TestTransactionBuilder builder; auto tx = builder.creatorAccountId(creator_account_id) - .createdTime(created_time) - .quorum(1) - .build() - .getTransport(); + .createdTime(created_time) + .quorum(1) + .build() + .getTransport(); return tx; } + shared_model::validation::DefaultTransactionValidator transaction_validator; }; /** @@ -48,11 +49,31 @@ class TransactionValidatorTest : public ValidatorsTest { */ TEST_F(TransactionValidatorTest, EmptyTransactionTest) { auto tx = generateEmptyTransaction(); - tx.mutable_payload()->set_created_time(created_time); + tx.mutable_payload()->mutable_reduced_payload()->set_created_time( + created_time); + auto result = proto::Transaction(iroha::protocol::Transaction(tx)); + auto answer = transaction_validator.validate(result); + ASSERT_EQ(answer.getReasonsMap().size(), 1); +} + +/** + * @given transaction without any commands + * @when commands validator is invoked + * @then answer has error about empty transaction + */ +TEST_F(TransactionValidatorTest, InvalidCreateRolePermission) { + auto tx = generateEmptyTransaction(); + tx.mutable_payload()->mutable_reduced_payload()->set_created_time( + created_time); + iroha::protocol::Command cmd; + cmd.mutable_create_role()->set_role_name("role"); + cmd.mutable_create_role()->add_permissions( + static_cast(-1)); + *tx.mutable_payload()->mutable_reduced_payload()->add_commands() = + std::move(cmd); shared_model::validation::DefaultTransactionValidator transaction_validator; auto result = proto::Transaction(iroha::protocol::Transaction(tx)); - auto answer = - transaction_validator.validate(result); + auto answer = transaction_validator.validate(result); ASSERT_EQ(answer.getReasonsMap().size(), 1); } @@ -63,34 +84,50 @@ TEST_F(TransactionValidatorTest, EmptyTransactionTest) { */ TEST_F(TransactionValidatorTest, StatelessValidTest) { iroha::protocol::Transaction tx = generateEmptyTransaction(); - tx.mutable_payload()->set_creator_account_id(account_id); - tx.mutable_payload()->set_created_time(created_time); + tx.mutable_payload()->mutable_reduced_payload()->set_creator_account_id( + account_id); + tx.mutable_payload()->mutable_reduced_payload()->set_created_time( + created_time); auto payload = tx.mutable_payload(); // Iterate through all command types, filling command fields with valid values - iterateContainer([] { return iroha::protocol::Command::descriptor(); }, - [&](auto field) { - // Add new command to transaction - auto command = payload->add_commands(); - // Set concrete type for new command - return command->GetReflection()->MutableMessage(command, - field); - }, - [this](auto field, auto command) { - // Will throw key exception in case new field is added - field_setters.at(field->name())( - command->GetReflection(), command, field); - }, - [] {}); + iterateContainer( + [] { return iroha::protocol::Command::descriptor(); }, + [&](auto field) { + // Add new command to transaction + auto command = payload->mutable_reduced_payload()->add_commands(); + // Set concrete type for new command + return command->GetReflection()->MutableMessage(command, field); + }, + [this](auto field, auto command) { + // Will throw key exception in case new field is added + field_setters.at(field->name())( + command->GetReflection(), command, field); + }, + [] {}); - shared_model::validation::DefaultTransactionValidator transaction_validator; auto result = proto::Transaction(iroha::protocol::Transaction(tx)); - auto answer = - transaction_validator.validate(result); + auto answer = transaction_validator.validate(result); ASSERT_FALSE(answer.hasErrors()) << answer.reason(); } +/** + * @given Protobuf transaction object with unset command + * @when validate is called + * @then there is a error returned + */ +TEST_F(TransactionValidatorTest, UnsetCommand) { + iroha::protocol::Transaction tx = generateEmptyTransaction(); + tx.mutable_payload()->mutable_reduced_payload()->set_creator_account_id( + account_id); + tx.mutable_payload()->mutable_reduced_payload()->set_created_time( + created_time); + auto answer = transaction_validator.validate(proto::Transaction(tx)); + tx.mutable_payload()->mutable_reduced_payload()->add_commands(); + ASSERT_TRUE(answer.hasErrors()); +} + /** * @given transaction made of commands with invalid fields * @when commands validation is invoked @@ -102,30 +139,54 @@ TEST_F(TransactionValidatorTest, StatelessInvalidTest) { auto payload = tx.mutable_payload(); iroha::ts64_t invalid_time = 10000000000ull; - payload->set_created_time(invalid_time); + payload->mutable_reduced_payload()->set_created_time(invalid_time); // create commands from default constructors, which will have empty, therefore // invalid values - iterateContainer([] { return iroha::protocol::Command::descriptor(); }, - [&](auto field) { - // Add new command to transaction - auto command = payload->add_commands(); - // Set concrete type for new command - return command->GetReflection()->MutableMessage(command, - field); - }, - [](auto, auto) { - // Note that no fields are set - }, - [] {}); + iterateContainer( + [] { return iroha::protocol::Command::descriptor(); }, + [&](auto field) { + // Add new command to transaction + auto command = payload->mutable_reduced_payload()->add_commands(); + // Set concrete type for new command + return command->GetReflection()->MutableMessage(command, field); + }, + [](auto, auto) { + // Note that no fields are set + }, + [] {}); - shared_model::validation::DefaultTransactionValidator transaction_validator; auto result = proto::Transaction(iroha::protocol::Transaction(tx)); - auto answer = - transaction_validator.validate(result); + auto answer = transaction_validator.validate(result); // in total there should be number_of_commands + 1 reasons of bad answer: // number_of_commands for each command + 1 for transaction metadata ASSERT_EQ(answer.getReasonsMap().size(), iroha::protocol::Command::descriptor()->field_count() + 1); } +/** + * @given transaction made of commands with valid fields + * @when commands validation is invoked + * @then answer has no errors + */ +TEST_F(TransactionValidatorTest, BatchValidTest) { + std::string creator_account_id = "admin@test"; + + TestTransactionBuilder builder; + auto tx = builder.creatorAccountId(creator_account_id) + .createdTime(created_time) + .quorum(1) + .batchMeta(interface::types::BatchType::ATOMIC, + std::vector()) + .createDomain("test", "test") + .build() + .getTransport(); + shared_model::validation::DefaultTransactionValidator transaction_validator; + auto result = proto::Transaction(iroha::protocol::Transaction(tx)); + auto answer = transaction_validator.validate(result); + + ASSERT_FALSE(answer.hasErrors()) << answer.reason(); + ASSERT_EQ(tx.payload().batch().type(), + static_cast(interface::types::BatchType::ATOMIC)); +} + diff --git a/test/module/shared_model/validators/validators.hpp b/test/module/shared_model/validators/validators.hpp index 3ceefbca90..3d83e35d0f 100644 --- a/test/module/shared_model/validators/validators.hpp +++ b/test/module/shared_model/validators/validators.hpp @@ -18,6 +18,8 @@ #ifndef IROHA_VALIDATOR_MOCKS_HPP #define IROHA_VALIDATOR_MOCKS_HPP +#include "validators/answer.hpp" + namespace shared_model { namespace validation { diff --git a/test/module/shared_model/validators/validators_fixture.hpp b/test/module/shared_model/validators/validators_fixture.hpp index cfe443b88c..b844998567 100644 --- a/test/module/shared_model/validators/validators_fixture.hpp +++ b/test/module/shared_model/validators/validators_fixture.hpp @@ -27,6 +27,7 @@ #include "interfaces/permissions.hpp" #include "primitive.pb.h" #include "queries.pb.h" +#include "transaction.pb.h" class ValidatorsTest : public ::testing::Test { public: @@ -66,14 +67,13 @@ class ValidatorsTest : public ::testing::Test { field_setters["permission"] = setEnum(role_permission); field_setters["grantable_permission"] = setEnum(grantable_permission); field_setters["key"] = setString(detail_key); + field_setters["writer"] = setString(writer); field_setters["detail"] = setString(detail_key); field_setters["value"] = setString(""); field_setters["tx_hashes"] = addString(hash); field_setters["quorum"] = setUInt32(quorum); field_setters["description"] = setString(""); - field_setters["amount"] = [&](auto refl, auto msg, auto field) { - refl->MutableMessage(msg, field)->CopyFrom(amount); - }; + field_setters["amount"] = setString(amount); field_setters["peer"] = [&](auto refl, auto msg, auto field) { refl->MutableMessage(msg, field)->CopyFrom(peer); }; @@ -118,14 +118,59 @@ class ValidatorsTest : public ::testing::Test { validator(); }); } + // TODO: IR-1490 02.07.2018 Rewrite interator for all containters + /** + * Iterate the container recursively (transaction or query), + * generating concrete subtypes + * and doing operation on concrete subtype fields. Call validator after each + * subtype + * @tparam FieldMap map of validators which are not nessery to traverse but + * validate as a whole + * @tparam FieldOp field operation type + * @tparam Validator validator type + * @param m protobuf message to iterate + * @param field_validators map of validators which are not nessery to traverse + * but validate as a whole + * @param field_op field operation callable object + * @param validator validator callable object + */ + template + void iterateContainerRecursive(std::shared_ptr m, + FieldMap &field_validators, + FieldOp &&field_op, + Validator &&validator) { + const google::protobuf::Descriptor *desc = m->GetDescriptor(); + const google::protobuf::Reflection *refl = m->GetReflection(); + // Get field descriptor for concrete type + const auto &range = boost::irange(0, desc->field_count()) + | boost::adaptors::transformed([&](auto i) { return desc->field(i); }); + // Iterate through all concrete types + boost::for_each(range, [&](auto field) { + if (field->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE + or field_validators.count(field->name())) { + field_op(field, m); + validator(); + } else { + const google::protobuf::Message *mfield = field->is_repeated() + ? refl->MutableRepeatedMessage(m.get(), field, 0) + : refl->MutableMessage(m.get(), field); + google::protobuf::Message *mcopy = mfield->New(); + mcopy->CopyFrom(*mfield); + void *ptr = new std::shared_ptr(mcopy); + std::shared_ptr *m = + static_cast *>(ptr); + this->iterateContainerRecursive( + *m, field_validators, field_op, validator); + } + }); + } protected: void SetUp() override { // Fill fields with valid values created_time = iroha::time::now(); precision = 2; - amount.set_precision(precision); - amount.mutable_value()->set_fourth(1000); + amount = "10.00"; public_key_size = 32; hash_size = 32; counter = 1048576; @@ -140,6 +185,7 @@ class ValidatorsTest : public ::testing::Test { account_name = "admin"; domain_id = "ru"; detail_key = "key"; + writer = "account@domain"; public_key = std::string(public_key_size, '0'); hash = std::string(public_key_size, '0'); role_permission = iroha::protocol::RolePermission::can_append_role; @@ -168,13 +214,15 @@ class ValidatorsTest : public ::testing::Test { std::string description; std::string public_key; std::string hash; + std::string writer; + iroha::protocol::Transaction::Payload::BatchMeta batch_meta; shared_model::interface::permissions::Role model_role_permission; shared_model::interface::permissions::Grantable model_grantable_permission; iroha::protocol::RolePermission role_permission; iroha::protocol::GrantablePermission grantable_permission; uint8_t quorum; uint8_t precision; - iroha::protocol::Amount amount; + std::string amount; iroha::protocol::Peer peer; decltype(iroha::time::now()) created_time; iroha::protocol::QueryPayloadMeta meta; diff --git a/test/regression/CMakeLists.txt b/test/regression/CMakeLists.txt index c3e22283c7..9705065eb1 100644 --- a/test/regression/CMakeLists.txt +++ b/test/regression/CMakeLists.txt @@ -4,3 +4,10 @@ target_link_libraries(regression_test application integration_framework ) + +addtest(query_regression_test query_test.cpp) + +target_link_libraries(query_regression_test + application + integration_framework + ) diff --git a/test/regression/query_test.cpp b/test/regression/query_test.cpp new file mode 100644 index 0000000000..19cab11a57 --- /dev/null +++ b/test/regression/query_test.cpp @@ -0,0 +1,69 @@ +/** + * Copyright Soramitsu Co., Ltd. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "builders/protobuf/queries.hpp" +#include "cryptography/crypto_provider/crypto_defaults.hpp" +#include "framework/integration_framework/integration_test_framework.hpp" +#include "framework/specified_visitor.hpp" +#include "module/shared_model/builders/protobuf/test_query_builder.hpp" + +template +auto makeQuery() { + return BaseType() + .createdTime(iroha::time::now()) + .creatorAccountId("admin@test") + .queryCounter(1) + .getAccount("admin@test") + .build(); +} + +template +auto createInvalidQuery(Query query, + const shared_model::crypto::Keypair &keypair) { + query.addSignature(shared_model::crypto::Signed(std::string(32, 'a')), + keypair.publicKey()); + return query; +} + +/** + * @given itf instance + * @when pass query with invalid signature + * @then assure that query with invalid signature is failed with stateless + * error + */ +TEST(QueryTest, FailedQueryTest) { + const auto key_pair = + shared_model::crypto::DefaultCryptoAlgorithmType::generateKeypair(); + + auto query_with_broken_signature = + createInvalidQuery(makeQuery(), key_pair); + auto stateless_invalid_query_response = [](auto &status) { + auto &resp = + boost::apply_visitor(framework::SpecifiedVisitor< + shared_model::interface::ErrorQueryResponse>(), + status.get()); + boost::apply_visitor( + framework::SpecifiedVisitor< + shared_model::interface::StatelessFailedErrorResponse>(), + resp.get()); + }; + + integration_framework::IntegrationTestFramework itf(1); + + itf.setInitialState(key_pair).sendQuery(query_with_broken_signature, + stateless_invalid_query_response); +} + +/** + * @given itf instance + * @when pass block query with invalid signature + * @then assure that query with invalid signature is failed with stateless + * error + */ +TEST(QueryTest, FailedBlockQueryTest) { + // TODO: 01/08/2018 @muratovv Implement test since IR-1569 will be completed +} diff --git a/test/regression/regression_test.cpp b/test/regression/regression_test.cpp index a67c321328..02b4d06460 100644 --- a/test/regression/regression_test.cpp +++ b/test/regression/regression_test.cpp @@ -18,6 +18,7 @@ #include #include "builders/protobuf/queries.hpp" #include "builders/protobuf/transaction.hpp" +#include "common/files.hpp" #include "cryptography/crypto_provider/crypto_defaults.hpp" #include "framework/integration_framework/integration_test_framework.hpp" #include "framework/specified_visitor.hpp" @@ -36,7 +37,7 @@ TEST(RegressionTest, SequentialInitialization) { auto tx = shared_model::proto::TransactionBuilder() .createdTime(iroha::time::now()) .creatorAccountId(kUser) - .addAssetQuantity(kUser, kAsset, "1.0") + .addAssetQuantity(kAsset, "1.0") .quorum(1) .build() .signAndAddSignature( @@ -87,7 +88,7 @@ TEST(RegressionTest, StateRecovery) { .createdTime(iroha::time::now()) .creatorAccountId("admin@test") .createAccount("user", "test", userKeypair.publicKey()) - .addAssetQuantity("admin@test", "coin#test", "133.0") + .addAssetQuantity("coin#test", "133.0") .transferAsset( "admin@test", "user@test", "coin#test", "descrs", "97.8") .quorum(1) @@ -120,6 +121,13 @@ TEST(RegressionTest, StateRecovery) { (boost::filesystem::temp_directory_path() / "iroha-state-recovery-test") .string(); const std::string dbname = "dbstatereq"; + + // Cleanup blockstore directory, because it may contain blocks from previous + // test launch if ITF was failed for some reason. If there are some blocks, + // then checkProposal will fail with "missed proposal" error, because of + // incorrect calculation of chain height. + iroha::remove_dir_contents(path); + { integration_framework::IntegrationTestFramework( 1, dbname, [](auto &) {}, false, path) diff --git a/test/system/CMakeLists.txt b/test/system/CMakeLists.txt index 6462cd0118..d36d5eaed0 100644 --- a/test/system/CMakeLists.txt +++ b/test/system/CMakeLists.txt @@ -1,10 +1,12 @@ addtest(irohad_test irohad_test.cpp) target_link_libraries(irohad_test boost - pqxx + SOCI::core + SOCI::postgresql rapidjson integration_framework_config_helper libs_common ) add_dependencies(irohad_test irohad) add_definitions(-DPATHIROHAD="${PROJECT_BINARY_DIR}/bin") +add_definitions(-DPATHEXAMPLE="${PROJECT_SOURCE_DIR}/example") diff --git a/test/system/irohad_test.cpp b/test/system/irohad_test.cpp index d71dc9f74d..027c881078 100644 --- a/test/system/irohad_test.cpp +++ b/test/system/irohad_test.cpp @@ -22,7 +22,8 @@ #include #include #include -#include +#include +#include #include "common/files.hpp" #include "common/types.hpp" @@ -89,7 +90,7 @@ class IrohadTest : public testing::Test { void setPaths() { path_irohad_ = boost::filesystem::path(PATHIROHAD); irohad_executable = path_irohad_ / "irohad"; - path_example_ = path_irohad_.parent_path().parent_path() / "example"; + path_example_ = boost::filesystem::path(PATHEXAMPLE); path_config_ = path_example_ / "config.sample"; path_genesis_ = path_example_ / "genesis.block"; path_keypair_ = path_example_ / "node0"; @@ -97,13 +98,6 @@ class IrohadTest : public testing::Test { } void dropPostgres() { - auto connection = std::make_shared(pgopts_); - try { - connection->activate(); - } catch (const pqxx::broken_connection &e) { - FAIL() << "Connection to PostgreSQL broken: " << e.what(); - } - const auto drop = R"( DROP TABLE IF EXISTS account_has_signatory; DROP TABLE IF EXISTS account_has_asset; @@ -122,10 +116,8 @@ DROP TABLE IF EXISTS index_by_creator_height; DROP TABLE IF EXISTS index_by_id_height_asset; )"; - pqxx::work txn(*connection); - txn.exec(drop); - txn.commit(); - connection->disconnect(); + soci::session sql(soci::postgresql, pgopts_); + sql << drop; } public: