Repository: hyperledger/indy-node (language: python)

Issue:
Ubuntu 20.04: create CI for Ubuntu 20.04
Create CI for Ubuntu 20.04 for a feature brunch `ubuntu-20.04-upgrade`

**Acceptance criteria:**
- All existing tests are launched via GitHub Actions, and they pass (if pass locally)
- All jobs(steps) pass
- Reports from launches are available.
Ubuntu 20.04: create CD for Ubuntu 20.04
Create CD for Ubuntu 20.04 for a feature brunch `ubuntu-20.04-upgrade`

**Acceptance criteria:**

- All existing tests are launched via GitHub Actions, and they pass (if pass locally)
- All jobs(steps) pass
- Reports from launches are available

Indy Node deb package are published to a public repository (focal like a channel name for Ubuntu 20.04 Focal Fossa)
added building and publishing of python artifacts
This PR adds the building and publishing of the Python artifacts based on the workflow of Indy-Plenum.
It also adjusts the building of the debian distribution of node accordingly.

Signed-off-by: udosson <r.klemens@yahoo.de>


Discussion / hints:

Fix patch:
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..92ce082e5
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Declare files that will always have LF line endings on checkout.
+**/publishPackages text eol=lf
\ No newline at end of file
diff --git a/.github/actions/publish-deb/action.yaml b/.github/actions/publish-deb/action.yaml
new file mode 100644
index 000000000..6b8c56d74
--- /dev/null
+++ b/.github/actions/publish-deb/action.yaml
@@ -0,0 +1,23 @@
+name: "Publish Debian Packages"
+description: >
+  Publish debian packages to artifactory using the jfrog cli.
+  This action wraps a script that performs organized publishing of debian packages.
+
+inputs:
+  sourceDirectory:
+    description: "The directory containing the packages to be published."
+    required: true
+  distribution:
+    description: "The distribution supported by the package. i.e xenial, focal, etc."
+    required: true
+  component:
+    description: "The component type.  i.e. stable, rc, dev, etc."
+    required: true
+
+runs:
+  using: "composite"
+  steps:
+    - name: Publish
+      shell: bash
+      run: |
+        ${{ github.action_path }}/publishPackages "${{ inputs.sourceDirectory }}" "${{ inputs.distribution }}" "${{ inputs.component }}"
diff --git a/.github/actions/publish-deb/publishPackages b/.github/actions/publish-deb/publishPackages
new file mode 100755
index 000000000..76d7fe868
--- /dev/null
+++ b/.github/actions/publish-deb/publishPackages
@@ -0,0 +1,33 @@
+#!/bin/bash
+SCRIPT_HOME="$( cd "$( dirname "$0" )" && pwd )"
+
+sourceDirectory=${1}
+distribution=${2}
+component=${3}
+uploadSpec=${UPLOAD_SPEC:-${4:-"${SCRIPT_HOME}/upload-spec.json"}}
+
+export CI=true
+
+fileList=$(find "${sourceDirectory}" -type f -name '*.deb' -mindepth 1 -maxdepth 1 -printf "%f\n" 2>/dev/null)
+for name in ${fileList}; do
+  shortName=$(echo "${name}" | sed -rn 's~^(python3-)?([^.]*)_.*.deb~\2~p')
+  architecture=$(echo "${name}" | sed -rn 's~^.*_(.*).deb~\1~p')
+  startingLetter=$(echo "${shortName}" | sed -rn 's~^(.).*~\1~p')
+
+  echo "====================================================================================================================="
+  echo "name: ${name}"
+  echo "shortName: ${shortName}"
+  echo "architecture: ${architecture}"
+  echo "startingLetter: ${startingLetter}"
+  echo "distribution: ${distribution}"
+  echo "component: ${component}"
+  echo "uploadSpec: ${uploadSpec}"
+  echo "---------------------------------------------------------------------------------------------------------------------"
+  jfrog rt u \
+    --deb ${distribution}/${component}/${architecture} \
+    --spec ${uploadSpec}\
+    --spec-vars "SOURCE_DIR=${sourceDirectory};PACKAGE_NAME=${name};COMPONENT=${component};PACKAGE_STARTING_LETTER=${startingLetter};PACKAGE_SHORT_NAME=${shortName};DISTRIBUTION=${distribution}" \
+    --detailed-summary
+  echo "====================================================================================================================="
+  echo
+done
\ No newline at end of file
diff --git a/.github/actions/publish-deb/upload-spec.json b/.github/actions/publish-deb/upload-spec.json
new file mode 100644
index 000000000..d98c56a72
--- /dev/null
+++ b/.github/actions/publish-deb/upload-spec.json
@@ -0,0 +1,8 @@
+{
+  "files": [
+    {
+      "pattern": "${SOURCE_DIR}/${PACKAGE_NAME}",
+      "target": "indy/pool/${DISTRIBUTION}/${COMPONENT}/${PACKAGE_STARTING_LETTER}/${PACKAGE_SHORT_NAME}/"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/.github/actions/set-version/action.yaml b/.github/actions/set-version/action.yaml
new file mode 100644
index 000000000..ceef38bdd
--- /dev/null
+++ b/.github/actions/set-version/action.yaml
@@ -0,0 +1,63 @@
+name: "Set Version"
+description: "Sets version parameters and makes them available as outputs for subsequent processes."
+
+inputs:
+  moduleName: 
+    description: "The name of the module containing the version management APIs for the project; i.e. load_version and set_version"
+    required: true
+    default: "indy_node"
+  isDev: 
+    description: "A flag indicating whether or not this is a dev build; set to either 'true' or 'false'."
+    required: true
+    default: false
+  isRC: 
+    description: "A flag indicating whether or not this is a release candidate build; set to either 'true' or 'false'."
+    required: true
+    default: false
+
+outputs:
+  targetReleaseVer:
+    description: "The target release version."
+    value: ${{ steps.versions.outputs.targetReleaseVer }}
+  upstreamVer:
+    description: "The version number to use with Python packages."
+    value: ${{ steps.versions.outputs.upstreamVer }}
+  pkgVer:
+    description: "The version number to use with Debian packages."
+    value: ${{ steps.versions.outputs.pkgVer }}
+
+runs:
+  using: "composite"
+  steps:
+    - name: Set Versions
+      id: versions
+      shell: bash
+      run: |
+        fullVersion=$(python3 -c "from ${{ inputs.moduleName }} import load_version; print(load_version().full)")
+        release=$(python3 -c "from ${{ inputs.moduleName }} import load_version; print(load_version().release)")
+        pre=$(python3 -c "from ${{ inputs.moduleName }} import load_version; pre = load_version().parts[-2]; print('' if pre is None else pre)")
+        revision=$(python3 -c "from ${{ inputs.moduleName }} import load_version; rev = load_version().parts[-1]; print('' if rev is None else rev)")
+
+        targetReleaseVer=${release}
+        upstreamVer=${targetReleaseVer}
+        pkgVer=${upstreamVer}
+
+        if [ ${{ inputs.isDev }} == "true" ] || [ ${{ inputs.isRC }} == "true" ]; then
+          if [ ${{ inputs.isDev }} == "true" ]; then
+            rev=${{ github.run_number }}
+          else
+            rev=${revision}
+          fi
+
+          upstreamVer+=".${pre}${rev}"
+          pkgVer+="~${pre}${rev}"
+        fi
+
+        # Set Outputs ...
+        echo "::set-output name=targetReleaseVer::${targetReleaseVer}"
+        echo "::set-output name=upstreamVer::${upstreamVer}"
+        echo "::set-output name=pkgVer::${pkgVer}"
+
+        echo "Target Release version: ${targetReleaseVer}"
+        echo "PyPI Release version: ${upstreamVer}"
+        echo "Debian Release version: ${pkgVer}"
\ No newline at end of file
diff --git a/.github/settings.yml b/.github/settings.yml
new file mode 100644
index 000000000..772375995
--- /dev/null
+++ b/.github/settings.yml
@@ -0,0 +1,18 @@
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+repository:
+  name: indy-node
+  description: The server portion of a distributed ledger purpose-built for decentralized identity.
+  homepage: https://wiki.hyperledger.org/display/indy
+  default_branch: master
+  has_downloads: false
+  has_issues: true
+  has_projects: false
+  has_wiki: false
+  archived: false
+  private: false
+  allow_squash_merge: true
+  allow_merge_commit: true
+  allow_rebase_merge: true
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
new file mode 100644
index 000000000..3dc4d3e85
--- /dev/null
+++ b/.github/workflows/README.md
@@ -0,0 +1,29 @@
+# GitHub Actions Workflow
+
+The workflow in the [build.yaml](build.yaml) file replaces the existing [Jenkins.ci](../../Jenkinsfile.ci) build process.
+
+The `lint` job replaces the `Static code validation` stage of the Jenkins pipeline, while the remainder of the jobs replace the `Build / Test` stage.
+
+The `Build result notification` stage was not moved to GHA, as build failures will be reports via GHA.
+
+Many of the other stages are replaced merely by the fact we're using Github Actions, we use prebuild Docker containers so we don't have to replicate the steps for building containers.
+
+The build process for `Jenkins.nightly` was not ported to GHA.
+
+Support for Windows continues as a `ToDo` item.
+
+
+## Configuring actions
+
+If you are cloning or forking this repo you will need to configure two secrets for Actions to run correctly.
+
+Secrets can be set via Settings -> Secrets -> New repository secret:
+
+`CR_USER`: is your GH username.  It must be lowercase.
+`CR_PAT`:  can be created by following the [Creating a personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) documentation.
+
+When you create your token, the only permission you need to select is `write:packages` **Upload packages to GitHub package registry**, all other necessary permissions will be selected by default.
+
+You may also need to enable [Improved container support](https://docs.github.com/en/packages/guides/enabling-improved-container-support) in order to allow the images to be written to your repository.  You'll see an error to this affect if this is the case.
+
+Once you have run the build once with those secrets, you have to make the images public.  Access the packages at https://ghcr.io/USER/indy-node/node-build and https://ghcr.io/USER/indy-node/node-lint and change the visibility in 'Package Settings' to 'Public' then re-run the build.  Alternatively, if you would prefer to keep the images private, you can manage access to the package and select only the user account associated with the token you setup above.
\ No newline at end of file
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 000000000..2f287f1e5
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,447 @@
+name: indy-node-build
+on:
+  push:
+    branches:
+      - ubuntu-20.04-upgrade
+
+  pull_request:
+    branches:
+      - ubuntu-20.04-upgrade
+      
+jobs:
+  workflow-setup:
+    name: Initialize Workflow
+    runs-on: ubuntu-latest
+    outputs:
+      CACHE_KEY_BUILD: ${{ steps.cache.outputs.CACHE_KEY_BUILD }}
+      CACHE_KEY_LINT: ${{ steps.cache.outputs.CACHE_KEY_LINT }}
+      UBUNTU_VERSION: ${{ steps.cache.outputs.UBUNTU_VERSION }}
+      # Expose the lowercase version of the GitHub repository name
+      # to all subsequent jobs that reference image repositories
+      # as the push and pull operations require the URL of the repository
+      # to be in lowercase.
+      GITHUB_REPOSITORY_NAME: ${{ steps.repository-name.outputs.lowercase }}
+      GITHUB_REF: ${{ steps.cache.outputs.GITHUB_REF }}
+      distribution: ${{ steps.cache.outputs.distribution }}
+      isDev: ${{ steps.build-flags.outputs.isDev }}
+      isRC: ${{ steps.build-flags.outputs.isRC }}
+      publish: ${{ steps.build-flags.outputs.publish }}
+    steps:
+      - name: Git checkout
+        uses: actions/checkout@v2
+
+      - name: Convert the GitHub repository name to lowercase
+        id: repository-name
+        uses: ASzc/change-string-case-action@v1
+        with:
+          string: ${{ github.repository }}
+
+      - name: Set outputs
+        id: cache
+        run: |
+          # Set variables according to version of ubuntu
+          if [[ "${{github.base_ref}}" == "master" || "${{github.ref}}" == "refs/heads/master" ]]; then
+            echo "::set-output name=CACHE_KEY_BUILD::${{ hashFiles('.github/workflows/build/Dockerfile.ubuntu-1604') }}"
+            echo "::set-output name=CACHE_KEY_LINT::${{ hashFiles('.github/workflows/lint/Dockerfile.ubuntu-1604') }}"
+            echo "::set-output name=UBUNTU_VERSION::ubuntu-1604"
+            echo "::set-output name=distribution::xenial"
+          fi
+          if [[ "${{github.base_ref}}" == "ubuntu-20.04-upgrade" || "${{github.ref}}" == "refs/heads/ubuntu-20.04-upgrade" ]]; then
+            echo "::set-output name=CACHE_KEY_BUILD::${{ hashFiles('.github/workflows/build/Dockerfile.ubuntu-2004') }}"
+            echo "::set-output name=CACHE_KEY_LINT::${{ hashFiles('.github/workflows/lint/Dockerfile.ubuntu-2004') }}"
+            echo "::set-output name=UBUNTU_VERSION::ubuntu-2004"
+            echo "::set-output name=distribution::focal"
+          fi
+     
+
+          if [[ "${{github.base_ref}}" == 'master' || "${{github.ref}}" == 'refs/heads/master' || "${{github.base_ref}}" == 'main' || "${{github.ref}}" == 'refs/heads/main' ]]; then
+            echo "::set-output name=GITHUB_REF::main"
+          elif [[ "${{github.base_ref}}" == 'release*' || "${{github.ref}}" == 'refs/heads/release*' ]]; then
+            echo "::set-output name=GITHUB_REF::rc"
+          elif [[ "${{github.base_ref}}" == 'stable' || "${{github.ref}}" == 'refs/heads/stable' ]]; then
+            echo "::set-output name=GITHUB_REF::stable"
+          else
+            echo "::set-output name=GITHUB_REF::dev"
+          fi
+
+      - name: Set build flags
+        id: build-flags
+        run: |
+
+          if [[ "${{steps.cache.outputs.GITHUB_REF}}" == 'dev' || "${{steps.cache.outputs.GITHUB_REF}}" == 'main' ]]; then
+            echo "::set-output name=isDev::true"
+          else
+            echo "::set-output name=isDev::false"
+          fi
+
+          if [[ "${{steps.cache.outputs.GITHUB_REF}}" == 'rc' ]]; then
+            echo "::set-output name=isRC::true"
+          else
+            echo "::set-output name=isRC::false"
+          fi
+
+          # Ensure publishing is only performed when the build is executed from the main (hyperledger/indy-node) repository.
+          if [[ ${{github.event.repository.full_name}} == 'hyperledger/indy-node' && ${{github.event_name}} == 'push' && ( ${{steps.cache.outputs.GITHUB_REF}} == 'main' || ${{steps.cache.outputs.GITHUB_REF}} == 'rc' || ${{steps.cache.outputs.GITHUB_REF}} == 'stable' ) ]]; then
+            echo "::set-output name=publish::true"
+          else
+            echo "::set-output name=publish::false"
+          fi
+
+  build-image:
+    name: Create Builder Image
+    # Reference to workflow-setup job is required to access its various outputs.
+    needs: workflow-setup
+    runs-on: ubuntu-latest
+    env:
+      CACHE_KEY_BUILD: ${{ needs.workflow-setup.outputs.CACHE_KEY_BUILD }}
+      GITHUB_REPOSITORY_NAME: ${{ needs.workflow-setup.outputs.GITHUB_REPOSITORY_NAME }}
+      UBUNTU_VERSION: ${{ needs.workflow-setup.outputs.UBUNTU_VERSION }}
+    steps:
+      - name: Git checkout
+        uses: actions/checkout@v2
+
+      - name: Try load from cache
+        id: cache-image
+        uses: actions/cache@v2
+        with:
+          path: ${GITHUB_WORKSPACE}/cache
+          key: ${{ env.CACHE_KEY_BUILD}}
+
+      - name: Prepare image labels and tags
+        if: steps.cache-image.outputs.cache-hit != 'true'
+        id: prep
+        shell: bash
+        run: |
+          DOCKER_IMAGE=ghcr.io/${{ env.GITHUB_REPOSITORY_NAME }}/node-build
+          TAGS="${DOCKER_IMAGE}:latest,${DOCKER_IMAGE}:${{ env.UBUNTU_VERSION }}"
+          echo ::set-output name=tags::${TAGS}
+          echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
+
+      - name: Log into the GitHub Container Registry
+        if: steps.cache-image.outputs.cache-hit != 'true'
+        uses: docker/login-action@v1
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Set up Docker Buildx
+        if: steps.cache-image.outputs.cache-hit != 'true'
+        uses: docker/setup-buildx-action@v1
+
+      - name: Build and push image
+        if: steps.cache-image.outputs.cache-hit != 'true'
+        uses: docker/build-push-action@v2
+        with:
+          context: .
+          file: .github/workflows/build/Dockerfile.${{ env.UBUNTU_VERSION }}
+          no-cache: true
+          push: ${{ github.event_name != 'pull_request' }}
+          tags: ${{ steps.prep.outputs.tags }}
+          labels: |
+            org.opencontainers.image.source=${{ github.event.repository.html_url }}
+            org.opencontainers.image.created=${{ steps.prep.outputs.created }}
+            org.opencontainers.image.revision=${{ github.sha }}
+
+      - name: Touch Cache
+        if: steps.cache-image.outputs.cache-hit != 'true'
+        run: |
+          mkdir -p ${GITHUB_WORKSPACE}/cache
+          touch ${GITHUB_WORKSPACE}/cache/${{ env.CACHE_KEY_BUILD }}
+
+  lint-image:
+    name: Create Linter Image
+    # Reference to workflow-setup job is required to access its various outputs.
+    needs: workflow-setup
+    runs-on: ubuntu-latest
+    env:
+      CACHE_KEY_LINT: ${{ needs.workflow-setup.outputs.CACHE_KEY_LINT }}
+      GITHUB_REPOSITORY_NAME: ${{ needs.workflow-setup.outputs.GITHUB_REPOSITORY_NAME }}
+      UBUNTU_VERSION: ${{ needs.workflow-setup.outputs.UBUNTU_VERSION }}
+    steps:
+      - name: Git checkout
+        uses: actions/checkout@v2
+
+      - name: Try load from cache
+        id: cache-image
+        uses: actions/cache@v2
+        with:
+          path: ${GITHUB_WORKSPACE}/cache
+          key: ${{ env.CACHE_KEY_LINT }}
+
+      - name: Prepare image labels and tags
+        if: steps.cache-image.outputs.cache-hit != 'true'
+        id: prep
+        shell: bash
+        run: |
+          DOCKER_IMAGE=ghcr.io/${{ env.GITHUB_REPOSITORY_NAME }}/node-lint
+          TAGS="${DOCKER_IMAGE}:latest,${DOCKER_IMAGE}:${{ env.UBUNTU_VERSION }}"
+          echo ::set-output name=tags::${TAGS}
+          echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
+
+      - name: Log into the GitHub Container Registry
+        if: steps.cache-image.outputs.cache-hit != 'true'
+        uses: docker/login-action@v1
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Set up Docker Buildx
+        if: steps.cache-image.outputs.cache-hit != 'true'
+        uses: docker/setup-buildx-action@v1
+
+      - name: Build and push image
+        if: steps.cache-image.outputs.cache-hit != 'true'
+        uses: docker/build-push-action@v2
+        with:
+          context: .
+          file: .github/workflows/lint/Dockerfile.${{ env.UBUNTU_VERSION }}
+          no-cache: true
+          push: ${{ github.event_name != 'pull_request' }}
+          tags: ${{ steps.prep.outputs.tags }}
+          labels: |
+            org.opencontainers.image.source=${{ github.event.repository.html_url }}
+            org.opencontainers.image.created=${{ steps.prep.outputs.created }}
+            org.opencontainers.image.revision=${{ github.sha }}
+
+      - name: Touch Cache
+        if: steps.cache-image.outputs.cache-hit != 'true'
+        run: |
+          mkdir -p ${GITHUB_WORKSPACE}/cache
+          touch ${GITHUB_WORKSPACE}/cache/${{ env.CACHE_KEY_LINT }}
+
+  indy_node_tests:
+    name: Sliced Module Tests
+    # Reference to workflow-setup job is required to access the GITHUB_REPOSITORY_NAME output.
+    needs: [workflow-setup, build-image]
+    runs-on: ubuntu-20.04
+    # Fix for scacap/action-surefire-report out of memory error:
+    # - https://github.com/ScaCap/action-surefire-report/issues/17
+    env:
+      NODE_OPTIONS: '--max_old_space_size=4096'
+    container:
+      image: ghcr.io/${{ needs.workflow-setup.outputs.GITHUB_REPOSITORY_NAME }}/node-build:${{ needs.workflow-setup.outputs.UBUNTU_VERSION }}
+    strategy:
+      matrix:
+        module: [indy_node, indy_common]
+        slice: [1, 2, 3, 4 ,5, 6, 7, 8, 9, 10, 11]
+      fail-fast: false
+    steps:
+      - name: Check out code
+        uses: actions/checkout@v2
+
+      # ===============================================
+      # Caching cannot be used.
+      #  - For some reason as soon as it is enabled
+      #    the test start complaining about zmq missing
+      #    for the plenum install.
+      # -----------------------------------------------
+      # - name: Cache pip
+      #   uses: actions/cache@v2
+      #   with:
+      #     # pip cache on the node-build image is not in the default location.
+      #     # path: ~/.cache/pip
+      #     path: /root/.cache/pip
+      #     key: ${{ runner.os }}-indy-node-pip-${{ hashFiles('**/requirements.txt', '**/setup.py') }}
+      #     restore-keys: |
+      #       ${{ runner.os }}-indy-node-pip-
+
+      - name: Install dependencies
+        run: |
+          # Explicitly use the existing pip cache location in the node-build image.
+          pip --cache-dir /root/.cache/pip install .[tests]
+
+      - name: Run Indy Node ${{ matrix.module }} test slice ${{ matrix.slice }}/${{ strategy.job-total }}
+        id: node-test
+        run: RUSTPYTHONASYNCIODEBUG=0 python3 runner.py --pytest "python3 -m pytest -l -vv" --dir "${{ matrix.module }}" --output "test-result-node-${{ matrix.slice }}.txt" --test-only-slice "${{ matrix.slice }}/${{ strategy.job-total }}"
+
+      - name: Publish Test Report
+        if: success() || failure()
+        uses: scacap/action-surefire-report@v1.0.7
+        continue-on-error: true
+        with:
+          check_name: Indy Node ${{ matrix.module }} Test Report for slice ${{ matrix.slice }}/${{ strategy.job-total }}
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          report_paths: "*-test-results.xml"
+
+      - name: Upload Detailed Test Failure Results
+        # The test runner only emits the detailed test results if the tests fail.
+        if: (steps.node-test.outcome == 'failure') && failure()
+        uses: actions/upload-artifact@v2
+        with:
+          name: detailed-test-result-slice-${{ matrix.slice }}
+          path: test-result-node-${{ matrix.slice }}.txt
+          retention-days: 5
+
+  lint:
+    name: Lint
+    # Reference to workflow-setup job is required to access the GITHUB_REPOSITORY_NAME output.
+    needs: [workflow-setup, lint-image]
+    runs-on: ubuntu-20.04
+    container:
+      image: ghcr.io/${{ needs.workflow-setup.outputs.GITHUB_REPOSITORY_NAME }}/node-lint:${{ needs.workflow-setup.outputs.UBUNTU_VERSION }}
+    steps:
+      - name: Check out code
+        uses: actions/checkout@v2
+
+      - name: flake8
+        run: python3 -m flake8
+
+  build_release:
+    name: Build Release
+    needs: [workflow-setup, indy_node_tests, lint]
+    runs-on: ubuntu-20.04
+    env:
+      UBUNTU_VERSION: ${{ needs.workflow-setup.outputs.UBUNTU_VERSION }}
+    container:
+      image: ghcr.io/${{ needs.workflow-setup.outputs.GITHUB_REPOSITORY_NAME }}/node-build:${{ needs.workflow-setup.outputs.UBUNTU_VERSION }}
+    steps:
+      - name: Check out code
+        uses: actions/checkout@v1
+
+      - name: Set Build Version
+        id: version
+        uses: ./.github/actions/set-version
+        with:
+          moduleName: indy_node
+          isDev: ${{ needs.workflow-setup.outputs.isDev }}
+          isRC: ${{ needs.workflow-setup.outputs.isRC }}
+
+      - name: Build Deployment Package
+        run: |
+          mkdir -p /tmp/node-build
+          ./build-scripts/${{ env.UBUNTU_VERSION }}/build-indy-node.sh "/__w/indy-node/indy-node" "${{ steps.version.outputs.upstreamVer }}" "/tmp/node-build" "${{ steps.version.outputs.pkgVer }}"
+
+      - uses: actions/upload-artifact@v2
+        with:
+          name: node-deb
+          path: /tmp/node-build
+          retention-days: 5
+
+  build_3rd_party_dependencies:
+    name: Build 3rd Party Dependencies
+    needs: [workflow-setup, indy_node_tests, lint]
+    runs-on: ubuntu-20.04
+    env:
+      UBUNTU_VERSION: ${{ needs.workflow-setup.outputs.UBUNTU_VERSION }}
+    container:
+      image: ghcr.io/${{ needs.workflow-setup.outputs.GITHUB_REPOSITORY_NAME }}/node-build:${{ needs.workflow-setup.outputs.UBUNTU_VERSION }}
+    steps:
+      - name: Check out code
+        uses: actions/checkout@v1
+
+      - name: Try load from cache.
+        id: third-party-dependencies
+        uses: actions/cache@v2
+        with:
+          path: /tmp/third-party-dependencies
+          key: ${{ format('third-party-dependencies-{0}', hashFiles(format('./build-scripts/{0}/build-3rd-parties.sh', needs.workflow-setup.outputs.UBUNTU_VERSION ))) }}           
+
+      - name: Build 3rd party deployment packages
+        if: steps.third-party-dependencies.outputs.cache-hit != 'true'
+        run: |
+          mkdir -p ./build-scripts/${{ env.UBUNTU_VERSION }}/cache/3rd-party-dependencies/
+          ./build-scripts/${{ env.UBUNTU_VERSION }}/build-3rd-parties.sh ./cache/3rd-party-dependencies
+          mv ./build-scripts/${{ env.UBUNTU_VERSION }}/cache/* /tmp/third-party-dependencies
+
+  build-python-packages:
+    name: Build Python Packages
+    runs-on: ubuntu-20.04
+    needs: [workflow-setup, indy_node_tests, lint]
+    steps:
+      - name: Check out code
+        uses: actions/checkout@v1
+
+      - name: Set up Python 3.8
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.8
+
+      - name: Install required packages via pip
+        run: |
+          python3 -m pip install pytest-runner wheel
+
+      - name: Set Build Version
+        id: version
+        uses: ./.github/actions/set-version
+        with:
+          moduleName: indy_node
+          isDev: ${{ needs.workflow-setup.outputs.isDev }}
+          isRC: ${{ needs.workflow-setup.outputs.isRC }}
+
+      - name: Prepare package and set version
+        run: |
+          ./build-scripts/${{ needs.workflow-setup.outputs.UBUNTU_VERSION }}/prepare-package.sh . indy_node "${{ steps.version.outputs.upstreamVer }}" python-packages
+
+      - name: Building python package
+        run: |
+          python3 setup.py sdist --dist-dir /tmp/dist bdist_wheel --dist-dir /tmp/dist
+
+      - uses: actions/upload-artifact@v2
+        with:
+          name: node-python
+          path: /tmp/dist
+          retention-days: 5
+
+  publish_artifacts:
+    name: Publish Artifacts
+    runs-on: ubuntu-20.04
+    needs: [workflow-setup, build_release, build_3rd_party_dependencies, build-python-packages]
+    if: needs.workflow-setup.outputs.publish == 'true'
+    env:
+      GITHUB_REF: ${{ needs.workflow-setup.outputs.GITHUB_REF }}
+    steps:
+      - name: Check out code
+        uses: actions/checkout@v1
+
+      - name: Setup JFrog CLI
+        uses: jfrog/setup-jfrog-cli@v1
+        env:
+          JF_ARTIFACTORY_1: ${{ secrets.INDY_ARTIFACTORY_REPO_CONFIG }}
+
+      - name: Ping Artifactory
+        run: |
+          # Test the connection to Ping the Hyperledger Artifactory server
+          # to ensure everything has been setup correctly.
+          jfrog rt ping
+
+      - name: Download Node Artifacts from Pipeline Artifacts
+        uses: actions/download-artifact@v2
+        with:
+          name: node-deb
+          path: to_publish
+
+      - name: Publish Node Artifacts
+        uses: ./.github/actions/publish-deb
+        with:
+          sourceDirectory: /home/runner/work/indy-node/indy-node/to_publish
+          distribution: ${{ env.distribution }}
+          component: ${{ env.GITHUB_REF }}
+
+      - name: Download 3rd Party Artifacts Dependencies from Cache
+        id: third-party-dependencies
+        uses: actions/cache@v2
+        with:
+          path: /tmp/third-party-dependencies
+          key: ${{ format('third-party-dependencies-{0}', hashFiles(format('./build-scripts/{0}/build-3rd-parties.sh', needs.workflow-setup.outputs.UBUNTU_VERSION ))) }}
+
+      - name: Publish 3rd Party Dependencies
+        uses: ./.github/actions/publish-deb
+        with:
+          sourceDirectory: /home/runner/tmp/third-party-dependencies
+          distribution: ${{ env.distribution }}
+          component: ${{ env.GITHUB_REF }}
+
+      - name: Download Python Packages from Pipeline Artifacts
+        uses: actions/download-artifact@v2
+        with:
+          name: node-python
+          path: dist
+
+      - name: Publish Python Package to PyPI
+        uses: pypa/gh-action-pypi-publish@release/v1
+        with:
+          user: __token__
+          password: ${{ secrets.PYPI_API_TOKEN }}
+          skip_existing: true
\ No newline at end of file
diff --git a/.github/workflows/build/Dockerfile.ubuntu-1604 b/.github/workflows/build/Dockerfile.ubuntu-1604
new file mode 100644
index 000000000..37cbd0e82
--- /dev/null
+++ b/.github/workflows/build/Dockerfile.ubuntu-1604
@@ -0,0 +1,29 @@
+FROM hyperledger/indy-core-baseci:0.0.3-master
+LABEL maintainer="Hyperledger <hyperledger-indy@lists.hyperledger.org>"
+
+RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CE7709D068DB5E88 \
+    && apt-get update -y \
+    && apt-get install -y \
+    python3-nacl \
+    libindy-crypto=0.4.5 \
+    libindy=1.15.0~1625-xenial \
+# rocksdb python wrapper
+    libbz2-dev \
+    zlib1g-dev \
+    liblz4-dev \
+    libsnappy-dev \
+    rocksdb=5.8.8 \
+    ursa=0.3.2-2 \
+# Build dependencies
+    ruby \
+    ruby-dev \
+    rubygems \
+    gcc \
+    make \
+# zstd is needed for caching in github actions pipeline
+    zstd
+
+# install fpm
+RUN gem install --no-ri --no-rdoc rake fpm
+
+RUN indy_image_clean
\ No newline at end of file
diff --git a/.github/workflows/build/Dockerfile.ubuntu-2004 b/.github/workflows/build/Dockerfile.ubuntu-2004
new file mode 100644
index 000000000..a7d5eb387
--- /dev/null
+++ b/.github/workflows/build/Dockerfile.ubuntu-2004
@@ -0,0 +1,75 @@
+FROM ubuntu:20.04
+
+ARG uid=1000
+ARG user=indy
+
+RUN apt-get update -y && apt-get install -y \
+    # common stuff
+    git \
+    wget \
+    gnupg \
+    apt-transport-https \
+    ca-certificates \
+    apt-utils
+
+# ========================================================================================================
+# Update repository signing keys
+# --------------------------------------------------------------------------------------------------------
+# Hyperledger
+RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9692C00E657DDE61 && \
+    # Sovrin
+    apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CE7709D068DB5E88
+# ========================================================================================================
+
+# Plenum
+#  - https://github.com/hyperledger/indy-plenum/issues/1546
+#  - Needed to pick up rocksdb=5.8.8
+RUN echo "deb  https://hyperledger.jfrog.io/artifactory/indy focal dev"  >> /etc/apt/sources.list && \
+    echo "deb http://security.ubuntu.com/ubuntu bionic-security main"  >> /etc/apt/sources.list && \
+    echo "deb https://repo.sovrin.org/deb bionic master" >> /etc/apt/sources.list && \
+    echo "deb https://repo.sovrin.org/sdk/deb bionic master" >> /etc/apt/sources.list
+
+# Sovrin
+RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CE7709D068DB5E88 
+
+RUN apt-get update -y && apt-get install -y \
+    # Python
+    python3-pip \
+    python3-nacl \
+    # rocksdb python wrapper
+    rocksdb=5.8.8 \
+    libgflags-dev \
+    libsnappy-dev \
+    zlib1g-dev \
+    libbz2-dev \
+    liblz4-dev \
+    libgflags-dev \
+    # zstd is needed for caching in github actions pipeline
+    zstd \
+    # fpm
+    ruby \
+    ruby-dev \
+    rubygems \
+    gcc \
+    make \
+    # Indy Node and Plenum
+    libssl1.0.0 \
+    ursa=0.3.2-1 \
+    # Indy SDK
+    libindy=1.15.0~1625-bionic \
+    # Need to move libursa.so to parent dir
+    && mv /usr/lib/ursa/* /usr/lib && rm -rf /usr/lib/ursa
+
+RUN pip3 install -U \
+    # Required by setup.py
+    setuptools==50.3.2 \
+    # Still pinned. Needs to be updated like in plenum
+    'pip<10.0.0' \
+    'pyzmq==18.1.0'
+
+
+# install fpm
+RUN gem install --no-document rake fpm
+
+RUN apt-get -y autoremove \
+    && rm -rf /var/lib/apt/lists/*
diff --git a/.github/workflows/build/README.md b/.github/workflows/build/README.md
new file mode 100644
index 000000000..984f201f2
--- /dev/null
+++ b/.github/workflows/build/README.md
@@ -0,0 +1,3 @@
+# Building the build image
+
+This `Dockerfile` is used as part of the workflow, any changes to it will force the docker image to be rebuilt and that new image will be used to run the downstream workflow.
\ No newline at end of file
diff --git a/.github/workflows/lint/Dockerfile.ubuntu-1604 b/.github/workflows/lint/Dockerfile.ubuntu-1604
new file mode 100644
index 000000000..4585a03eb
--- /dev/null
+++ b/.github/workflows/lint/Dockerfile.ubuntu-1604
@@ -0,0 +1,21 @@
+# Development
+FROM ubuntu:18.04
+LABEL maintainer="Hyperledger <hyperledger-indy@lists.hyperledger.org>"
+
+RUN apt-get update && apt-get dist-upgrade -y
+
+# Install environment
+RUN apt-get install -y \
+	git \
+	wget \
+	python3.5 \
+	python3-pip \
+	python-setuptools \
+	python3-nacl
+
+RUN pip3 install -U \
+	'pip<10.0.0' \
+	setuptools \
+	pep8==1.7.1 \
+	pep8-naming==0.6.1 \
+	flake8==3.5.0
diff --git a/.github/workflows/lint/Dockerfile.ubuntu-2004 b/.github/workflows/lint/Dockerfile.ubuntu-2004
new file mode 100644
index 000000000..935e84e51
--- /dev/null
+++ b/.github/workflows/lint/Dockerfile.ubuntu-2004
@@ -0,0 +1,19 @@
+# Development
+FROM ubuntu:20.04
+LABEL maintainer="Robin Klemens <r.klemens@yahoo.de>"
+
+RUN apt-get update && apt-get dist-upgrade -y
+
+# Install environment
+RUN apt-get install -y \
+	git \
+	wget \
+	python3-pip \
+	python3-nacl
+
+RUN pip3 install -U \
+	'pip<10.0.0' \
+	setuptools==50.3.2 \
+	pep8==1.7.1 \
+	pep8-naming==0.6.1 \
+	flake8==3.8.4
diff --git a/.github/workflows/lint/README.md b/.github/workflows/lint/README.md
new file mode 100644
index 000000000..6b2a83fd0
--- /dev/null
+++ b/.github/workflows/lint/README.md
@@ -0,0 +1,3 @@
+# Building the lint image
+
+This `Dockerfile` is used as part of the workflow, any changes to it will force the docker image to be rebuilt and that new image will be used to run the downstream workflow.
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 6147ec972..ca783e72d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,6 +24,9 @@ var/
 *.egg
 *.eggs
 
+# Needed for GitHub Actions
+!.github/workflows/build
+
 # Installer logs
 pip-log.txt
 pip-delete-this-directory.txt
@@ -72,8 +75,15 @@ include/
 # generated doc files
 docs/source/api_docs/
 
-# hidden files
-.*
+# IntelliJ specific config
+*.idea
+*.iml
+
+#vscode
+.vscode
 
 # Vagrant files
-.vagrant
\ No newline at end of file
+.vagrant
+
+# test output from working with GitHub actions
+test-result-node.xml
\ No newline at end of file
diff --git a/CODEOWNERS b/CODEOWNERS
index 7329bfc96..0f08cb393 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1,5 +1,7 @@
-# indy-admin
-*	@esplinr @dhh1128 @nage
+# SPDX-License-Identifier: Apache-2.0
+
+# Indy Admin
+*       @hyperledger/indy-admin
 
 # Indy Common
-*	@anikitinDSR @ashcherbakov @skhoroshavin @Toktar @donqui @ken-ebert
+*       @hyperledger/indy-common
diff --git a/Jenkinsfile.cd b/Jenkinsfile.cd
index 10db63458..49c5f0d83 100644
--- a/Jenkinsfile.cd
+++ b/Jenkinsfile.cd
@@ -93,7 +93,7 @@ def systemTests = { component, releaseVersion ->
             ['TestConsensusSuite.py'],
             ['test_state_proof.py']
         ]
-        testVersion = 'v0.8.41'
+        testVersion = 'v0.8.43'
         testVersionByTag = true
         delegate.gatherLogs = gatherLogs
     }
diff --git a/Jenkinsfile.ci b/Jenkinsfile.ci
index bdc2a1b5a..1fc543ddd 100644
--- a/Jenkinsfile.ci
+++ b/Jenkinsfile.ci
@@ -73,6 +73,7 @@ def withTestEnv(body) {
 
         buildDocker("hyperledger/indy-node-ci", "ci/ubuntu.dockerfile ci").inside {
             echo 'Test: Install dependencies'
+            sh "pip install pip==10.0.0"
             install()
             body.call('python')
         }
@@ -97,7 +98,7 @@ def test(options=[:]) {
 
     try {
         if (options.useRunner) {
-            sh "PYTHONASYNCIODEBUG='0' $options.python runner.py --pytest \"$options.python -m pytest -l -vv\" --dir $options.testDir --output \"$options.resFile\" --test-only-slice \"$options.testOnlySlice\""
+            sh "PYTHONASYNCIODEBUG='0' $options.python runner.py --pytest \"$options.python -m pytest -l -vv\" --dir $options.testDir --output \"$options.resFile\" --test-only-slice \"$options.testOnlySlice\" --nojunitxml"
         } else {
             sh "$options.python -m pytest -l -vv --junitxml=$options.resFile $options.testDir"
         }
@@ -152,7 +153,6 @@ def tests = [
     },
 ].collect {k, v -> [k, v]}
 
-
 def builds = [:]
 def _labels = labels.collect {k, v -> v}
 for (i = 0; i < _labels.size(); i++) {
diff --git a/Jenkinsfile.nightly b/Jenkinsfile.nightly
index 6eb3f576b..3b6448409 100644
--- a/Jenkinsfile.nightly
+++ b/Jenkinsfile.nightly
@@ -19,7 +19,7 @@ try {
     nodeWrapper('ubuntu') {
         stage('Resolve version to build') {
             docker.image('hyperledger/indy-core-baseci:0.0.3-master').inside('-u 0') {
-                sh "apt-get update && apt-get install -y $pkgName"
+                sh "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CE7709D068DB5E88 && apt-get update && apt-get install -y $pkgName"
                 releaseVersion = getReleaseVersion(mainModuleName, false)
                 buildPkgVersion = "${releaseVersion.release}~${releaseVersion.pre}${releaseVersion.revision}"
                 buildSrcVersion = sh(returnStdout: true, script: """
@@ -66,7 +66,7 @@ try {
             ['TestAdHocSuite.py'],
             ['TestProductionSuite.py']
         ]
-        testVersion = 'v0.8.41'
+        testVersion = 'v0.8.43'
         testVersionByTag = true
         delegate.gatherLogs = gatherLogs
     }
diff --git a/build-scripts/ubuntu-1604/build-3rd-parties.sh b/build-scripts/ubuntu-1604/build-3rd-parties.sh
index 994945d0e..4b7311b73 100755
--- a/build-scripts/ubuntu-1604/build-3rd-parties.sh
+++ b/build-scripts/ubuntu-1604/build-3rd-parties.sh
@@ -41,5 +41,11 @@ function build_from_pypi {
 # build 3rd parties:
 #   build_from_pypi <pypi-name> <version>
 # TODO duplicates list from Jenkinsfile.cd
+
+SCRIPT_PATH="${BASH_SOURCE[0]}"
+pushd `dirname ${SCRIPT_PATH}` >/dev/null
+
 build_from_pypi timeout-decorator 0.4.0
 build_from_pypi distro 1.3.0
+
+popd >/dev/null
\ No newline at end of file
diff --git a/build-scripts/ubuntu-1604/build-indy-node.sh b/build-scripts/ubuntu-1604/build-indy-node.sh
index fe4f0cf4c..5ac86fc2f 100755
--- a/build-scripts/ubuntu-1604/build-indy-node.sh
+++ b/build-scripts/ubuntu-1604/build-indy-node.sh
@@ -13,7 +13,7 @@ cp -r "${INPUT_PATH}/." "${TMP_DIR}"
 
 # prepare the sources
 cd "${TMP_DIR}/build-scripts/ubuntu-1604"
-./prepare-package.sh "${TMP_DIR}" "${VERSION}"
+./prepare-package.sh "${TMP_DIR}" indy_node "${VERSION}" debian-packages
 
 
 sed -i "s/{package_name}/${PACKAGE_NAME}/" "prerm"
diff --git a/build-scripts/ubuntu-1604/prepare-package.sh b/build-scripts/ubuntu-1604/prepare-package.sh
index 05cc43084..50d827506 100755
--- a/build-scripts/ubuntu-1604/prepare-package.sh
+++ b/build-scripts/ubuntu-1604/prepare-package.sh
@@ -1,29 +1,45 @@
 #!/bin/bash -xe
 
 if [ "$1" = "--help" ] ; then
-  echo "Usage: $0 <path-to-repo-folder> <release-version-dotted>"
+  echo "Usage: $0 <path-to-repo-folder> <main-module-name> <release-version-dotted> <distro-packages>"
+  echo "<distro-packages> - Set to 'debian-packages' when preparing deb packages, and 'python-packages' when preparing PyPi packages."
   exit 0
 fi
 
 repo="$1"
-version_dotted="$2"
+module_name="$2"
+version_dotted="$3"
+distro_packages="$4"
+
+BUMP_SH_SCRIPT="bump_version.sh"
+GENERATE_MANIFEST_SCRIPT="generate_manifest.sh"
 
 pushd $repo
 
 echo -e "\nSetting version to $version_dotted"
-bash -ex ./bump_version.sh $version_dotted
-cat indy_node/__version__.json
+bash -ex $BUMP_SH_SCRIPT $version_dotted
+cat $module_name/__version__.json
 
 echo -e "\nGenerating manifest"
-bash -ex ./generate_manifest.sh
-cat indy_node/__manifest__.json
-
-echo -e "\n\nPrepares indy-plenum debian package version"
-sed -i -r "s~indy-plenum==([0-9\.]+[0-9])(\.)?([a-z]+)~indy-plenum==\1\~\3~" setup.py
-
-echo -e "\nAdapt the dependencies for the Canonical archive"
-sed -i "s~timeout-decorator~python3-timeout-decorator~" setup.py
-sed -i "s~distro~python3-distro~" setup.py
+bash -ex $GENERATE_MANIFEST_SCRIPT
+cat $module_name/__manifest__.json
+
+echo -e "\n\nPrepares indy-node debian package version"
+sed -i -r "s~indy-node==([0-9\.]+[0-9])(\.)?([a-z]+)~indy-node==\1\~\3~" setup.py
+
+if [ "$distro_packages" = "debian-packages" ]; then
+  # Only used for the deb package builds, NOT for the PyPi package builds.
+  # Update the package names to match the versions that are pre-installed on the os.
+  echo -e "\nAdapt the dependencies for the Canonical archive"
+  #### ToDo adjust packages for the Cannonical archive for Ubuntu 20.04 (focal)
+  # sed -i "s~timeout-decorator~python3-timeout-decorator~" setup.py
+  # sed -i "s~distro~python3-distro~" setup.py
+elif [ "$distro_packages" = "python-packages" ]; then
+  echo -e "\nNo adaption of dependencies for python packages"
+else
+  echo -e "\nNo distribution specified. Please, specify distribution as 'debian-packages' or 'python-packages'."
+  exit 1
+fi
 
 echo "Preparing config files"
 GENERAL_CONFIG_DIR="\/etc\/indy"
diff --git a/build-scripts/ubuntu-2004/Dockerfile b/build-scripts/ubuntu-2004/Dockerfile
new file mode 100644
index 000000000..46c5f9d04
--- /dev/null
+++ b/build-scripts/ubuntu-2004/Dockerfile
@@ -0,0 +1,75 @@
+FROM ubuntu:20.04
+
+ARG uid=1000
+ARG user=indy
+
+RUN apt-get update -y && apt-get install -y \
+    # common stuff
+    git \
+    wget \
+    gnupg \
+    apt-transport-https \
+    ca-certificates \
+    apt-utils
+
+# ========================================================================================================
+# Update repository signing keys
+# --------------------------------------------------------------------------------------------------------
+# Hyperledger
+RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9692C00E657DDE61 && \
+    # Sovrin
+    apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CE7709D068DB5E88
+# ========================================================================================================
+
+# Plenum
+#  - https://github.com/hyperledger/indy-plenum/issues/1546
+#  - Needed to pick up rocksdb=5.8.8
+RUN echo "deb  https://hyperledger.jfrog.io/artifactory/indy focal dev"  >> /etc/apt/sources.list && \
+    echo "deb http://security.ubuntu.com/ubuntu bionic-security main"  >> /etc/apt/sources.list && \
+    echo "deb https://repo.sovrin.org/deb bionic master" >> /etc/apt/sources.list && \
+    echo "deb https://repo.sovrin.org/sdk/deb bionic master" >> /etc/apt/sources.list
+
+# Sovrin
+RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CE7709D068DB5E88 
+
+RUN apt-get update -y && apt-get install -y \
+    # Python
+    python3-pip \
+    python3-nacl \
+    # rocksdb python wrapper
+    rocksdb=5.8.8 \
+    libgflags-dev \
+    libsnappy-dev \
+    zlib1g-dev \
+    libbz2-dev \
+    liblz4-dev \
+    libgflags-dev \
+    # zstd is needed for caching in github actions pipeline
+    zstd \
+    # fpm
+    ruby \
+    ruby-dev \
+    rubygems \
+    gcc \
+    make \
+    # Indy Node and Plenum
+    libssl1.0.0 \
+    ursa=0.3.2-1 \
+    # Indy SDK
+    libindy=1.15.0~1625-bionic \
+    # Need to move libursa.so to parent dir
+    && mv /usr/lib/ursa/* /usr/lib && rm -rf /usr/lib/ursa
+
+RUN pip3 install -U \
+    # Required by setup.py
+    setuptools==50.3.2 \
+    # Still pinned. Needs to be update like in plenum
+    'pip<10.0.0' \
+    'pyzmq==18.1.0'
+
+
+# install fpm
+RUN gem install --no-document rake fpm
+
+RUN apt-get -y autoremove \
+    && rm -rf /var/lib/apt/lists/*
diff --git a/build-scripts/ubuntu-2004/README.md b/build-scripts/ubuntu-2004/README.md
new file mode 100644
index 000000000..d972042dc
--- /dev/null
+++ b/build-scripts/ubuntu-2004/README.md
@@ -0,0 +1,30 @@
+### Build indy-node using docker
+
+```
+./build-indy-node-docker.sh <path-to-sources> <version>
+```
+Built package is placed in a docker volume `indy-node-deb-u1604`. 
+
+### Build indy-node
+
+```
+./build-indy-node.sh <path to sources> <version> <output-path: default='.'>
+```
+
+Built package is placed in the `output-path` folder.
+
+### Build 3rd-party dependencies using docker
+
+```
+./build-3rd-parties-docker.sh
+```
+
+Built packages are placed in a docker volume `indy-node-deb-u1604`.
+
+### Build 3rd-party dependencies
+
+```
+./build-3rd-parties-docker.sh <output-path: default='.'>
+```
+
+Built packages are placed in the `output-path` folder.
diff --git a/build-scripts/ubuntu-2004/build-3rd-parties-docker.sh b/build-scripts/ubuntu-2004/build-3rd-parties-docker.sh
new file mode 100755
index 000000000..9ca5c7ae2
--- /dev/null
+++ b/build-scripts/ubuntu-2004/build-3rd-parties-docker.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+set -x
+set -e
+
+if [ -z "$2" ]; then
+    CMD="/root/build-3rd-parties.sh /output"
+else
+    CMD="$2"
+fi
+
+PKG_NAME=indy-node
+IMAGE_NAME="${PKG_NAME}-build-u2004"
+OUTPUT_VOLUME_NAME="${1:-"${PKG_NAME}-deb-u2004"}"
+
+docker build -t "${PKG_NAME}-build-u2004" -f Dockerfile .
+docker volume create --name "${OUTPUT_VOLUME_NAME}"
+
+docker run \
+    -i \
+    --rm \
+    -v "${OUTPUT_VOLUME_NAME}:/output" \
+    "${IMAGE_NAME}" \
+    $CMD
diff --git a/build-scripts/ubuntu-2004/build-3rd-parties.sh b/build-scripts/ubuntu-2004/build-3rd-parties.sh
new file mode 100755
index 000000000..4b7311b73
--- /dev/null
+++ b/build-scripts/ubuntu-2004/build-3rd-parties.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+set -e
+set -x
+
+OUTPUT_PATH="${1:-.}"
+
+function build_from_pypi {
+    PACKAGE_NAME="$1"
+
+    if [ -z "$2" ]; then
+        PACKAGE_VERSION=""
+    else
+        PACKAGE_VERSION="==$2"
+    fi
+    POSTINST_TMP="postinst-${PACKAGE_NAME}"
+    PREREM_TMP="prerm-${PACKAGE_NAME}"
+    cp postinst "${POSTINST_TMP}"
+    cp prerm "${PREREM_TMP}"
+    sed -i "s/{package_name}/python3-${PACKAGE_NAME}/" "${POSTINST_TMP}"
+    sed -i "s/{package_name}/python3-${PACKAGE_NAME}/" "${PREREM_TMP}"
+
+    fpm --input-type "python" \
+        --output-type "deb" \
+        --architecture "amd64" \
+        --verbose \
+        --python-package-name-prefix "python3"\
+        --python-bin "/usr/bin/python3" \
+        --exclude "*.pyc" \
+        --exclude "*.pyo" \
+        --maintainer "Hyperledger <hyperledger-indy@lists.hyperledger.org>" \
+        --after-install "${POSTINST_TMP}" \
+        --before-remove "${PREREM_TMP}" \
+        --package "${OUTPUT_PATH}" \
+        "${PACKAGE_NAME}${PACKAGE_VERSION}"
+
+    rm "${POSTINST_TMP}"
+    rm "${PREREM_TMP}"
+}
+
+# build 3rd parties:
+#   build_from_pypi <pypi-name> <version>
+# TODO duplicates list from Jenkinsfile.cd
+
+SCRIPT_PATH="${BASH_SOURCE[0]}"
+pushd `dirname ${SCRIPT_PATH}` >/dev/null
+
+build_from_pypi timeout-decorator 0.4.0
+build_from_pypi distro 1.3.0
+
+popd >/dev/null
\ No newline at end of file
diff --git a/build-scripts/ubuntu-2004/build-indy-node-docker.sh b/build-scripts/ubuntu-2004/build-indy-node-docker.sh
new file mode 100755
index 000000000..dd5e17796
--- /dev/null
+++ b/build-scripts/ubuntu-2004/build-indy-node-docker.sh
@@ -0,0 +1,31 @@
+#!/bin/bash -xe
+
+PKG_SOURCE_PATH="$1"
+VERSION="$2"
+PKG_NAME=indy-node
+IMAGE_NAME="${PKG_NAME}-build-u2004"
+OUTPUT_VOLUME_NAME="${3:-"${PKG_NAME}-deb-u2004"}"
+PACKAGE_VERSION="${4:-$VERSION}"
+
+if [[ (-z "${PKG_SOURCE_PATH}") || (-z "${VERSION}") ]]; then
+    echo "Usage: $0 <path-to-package-sources> <version> [<volume> [package-version]]"
+    exit 1;
+fi
+
+if [ -z "$5" ]; then
+    CMD="/root/build-${PKG_NAME}.sh /input ${VERSION} /output ${PACKAGE_VERSION}"
+else
+    CMD="$5"
+fi
+
+docker build -t "${IMAGE_NAME}" -f Dockerfile .
+docker volume create --name "${OUTPUT_VOLUME_NAME}"
+
+docker run \
+    -i \
+    --rm \
+    -v "${PKG_SOURCE_PATH}:/input" \
+    -v "${OUTPUT_VOLUME_NAME}:/output" \
+    -e PKG_NAME="${PKG_NAME}" \
+    "${IMAGE_NAME}" \
+    $CMD
diff --git a/build-scripts/ubuntu-2004/build-indy-node.sh b/build-scripts/ubuntu-2004/build-indy-node.sh
new file mode 100755
index 000000000..fda4e6816
--- /dev/null
+++ b/build-scripts/ubuntu-2004/build-indy-node.sh
@@ -0,0 +1,42 @@
+#!/bin/bash -xe
+
+INPUT_PATH="$1"
+VERSION="$2"
+OUTPUT_PATH="${3:-.}"
+PACKAGE_VERSION=${4:-$VERSION}
+
+PACKAGE_NAME=indy-node
+
+# copy the sources to a temporary folder
+TMP_DIR="$(mktemp -d)"
+cp -r "${INPUT_PATH}/." "${TMP_DIR}"
+
+# prepare the sources
+cd "${TMP_DIR}/build-scripts/ubuntu-2004"
+./prepare-package.sh "${TMP_DIR}" indy_node "${VERSION}" debian-packages
+
+
+sed -i "s/{package_name}/${PACKAGE_NAME}/" "prerm"
+
+fpm --input-type "python" \
+    --output-type "deb" \
+    --architecture "amd64" \
+    --verbose \
+    --python-package-name-prefix "python3" \
+    --python-bin "/usr/bin/python3" \
+    --exclude "*.pyc" \
+    --exclude "*.pyo" \
+    --depends at \
+    --depends iptables \
+    --depends libsodium18 \
+    --no-python-fix-dependencies \
+    --maintainer "Hyperledger <hyperledger-indy@lists.hyperledger.org>" \
+    --before-install "preinst_node" \
+    --after-install "postinst_node" \
+    --before-remove "prerm" \
+    --name "${PACKAGE_NAME}" \
+    --version ${PACKAGE_VERSION} \
+    --package "${OUTPUT_PATH}" \
+    "${TMP_DIR}"
+
+rm -rf "${TMP_DIR}"
diff --git a/build-scripts/ubuntu-2004/postinst b/build-scripts/ubuntu-2004/postinst
new file mode 100644
index 000000000..31b514505
--- /dev/null
+++ b/build-scripts/ubuntu-2004/postinst
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Automatically added from template:
+if which py3compile >/dev/null 2>&1; then
+	py3compile -O -p {package_name} /usr/local/lib/python3.8/dist-packages/
+fi
+
+# End automatically added section
diff --git a/build-scripts/ubuntu-2004/postinst_node b/build-scripts/ubuntu-2004/postinst_node
new file mode 100644
index 000000000..ae86a4b44
--- /dev/null
+++ b/build-scripts/ubuntu-2004/postinst_node
@@ -0,0 +1,198 @@
+#!/bin/bash
+
+# it should be fixed
+ENABLE_STDOUT_LOGGING="enableStdOutLogging"
+GENERAL_CONFIG_DIR="/etc/indy"
+GENERAL_DATA_DIR="/var/lib/indy"
+GENERAL_LOG_DIR="/var/log/indy"
+
+INSTALL_DIR='/usr/local/lib/python3.8/dist-packages'
+
+NOFILES_SOFT_LIMIT=65536
+NOFILES_HARD_LIMIT=131072
+
+CLIENT_CONNECTIONS_LIMIT=500
+
+
+# create general indy config folder if does not exist
+mkdir -p $GENERAL_CONFIG_DIR
+# create general indy data folder if does not exist
+mkdir -p $GENERAL_DATA_DIR
+# create general indy log folder if does not exist
+mkdir -p $GENERAL_LOG_DIR
+
+# create indy node config if does not exist
+if [ ! -f $GENERAL_CONFIG_DIR/indy_config.py ]; then
+    cp $INSTALL_DIR/indy_node/general_config/indy_config.py $GENERAL_CONFIG_DIR
+else
+    if [ -z "$(grep $ENABLE_STDOUT_LOGGING $GENERAL_CONFIG_DIR/indy_config.py)" ]; then
+        printf "\n%s\n%s\n" "# Disable stdout logging" "$ENABLE_STDOUT_LOGGING = False" >> $GENERAL_CONFIG_DIR/indy_config.py
+    fi
+fi
+
+chmod -R ug+rwx $GENERAL_CONFIG_DIR
+chmod -R ug+rwx $GENERAL_DATA_DIR
+chmod -R ug+rwx $GENERAL_LOG_DIR
+
+
+# init_indy_node script
+cat <<EOF > /usr/local/bin/init_indy_node
+#!/bin/bash
+
+if [ \$# -lt 5 ]; then
+    echo ""
+    echo "Usage: \$0 name ip port client_ip client_port [seed]";
+    echo "  name        - node name";
+    echo "  ip          - node IP";
+    echo "  port        - node port";
+    echo "  client_ip   - node client IP";
+    echo "  client_port - node client port";
+    echo "  seed        - node seed";
+    echo ""
+    exit 1;
+fi
+
+echo "NODE_NAME=\$1" > $GENERAL_CONFIG_DIR/indy.env
+echo "NODE_IP=\$2" >> $GENERAL_CONFIG_DIR/indy.env
+echo "NODE_PORT=\$3" >> $GENERAL_CONFIG_DIR/indy.env
+echo "NODE_CLIENT_IP=\$4" >> $GENERAL_CONFIG_DIR/indy.env
+echo "NODE_CLIENT_PORT=\$5" >> $GENERAL_CONFIG_DIR/indy.env
+echo "CLIENT_CONNECTIONS_LIMIT=$CLIENT_CONNECTIONS_LIMIT" >> $GENERAL_CONFIG_DIR/indy.env
+
+if [ -z \$6 ]; then
+    init_indy_keys --name \$1
+else
+    init_indy_keys --name \$1 --seed \$6
+fi
+EOF
+
+chmod +x /usr/local/bin/init_indy_node
+
+# add systemd script
+cat <<EOF > /etc/systemd/system/indy-node.service
+[Unit]
+Description=Indy Node
+Requires=indy-node-control.service
+
+[Service]
+EnvironmentFile=$GENERAL_CONFIG_DIR/indy.env
+ExecStart=/usr/bin/env python3 -O /usr/local/bin/start_indy_node \${NODE_NAME} \${NODE_IP} \${NODE_PORT} \${NODE_CLIENT_IP} \${NODE_CLIENT_PORT}
+User=indy
+Group=indy
+Restart=on-failure
+RestartSec=10
+StartLimitBurst=10
+StartLimitInterval=200
+TimeoutSec=300
+LimitNOFILE=$NOFILES_SOFT_LIMIT:$NOFILES_HARD_LIMIT
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+
+cat <<EOF > /etc/systemd/system/indy-node-control.service
+[Unit]
+Description=Service for upgrade of existing Indy Node and other operations
+#Requires=indy.service
+#After=indy.service
+After=network.target
+
+[Service]
+Type=simple
+EnvironmentFile=$GENERAL_CONFIG_DIR/node_control.conf
+ExecStart=/usr/bin/env python3 -O /usr/local/bin/start_node_control_tool \$TEST_MODE --hold-ext \${HOLD_EXT}
+Restart=on-failure
+RestartSec=10
+StartLimitBurst=10
+StartLimitInterval=200
+TimeoutSec=300
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+# add supervisord script
+cat <<EOF > /etc/supervisor/indy-node.conf
+[supervisord]
+nodaemon=true
+logfile=/var/log/indy/supervisord.log
+logfile_maxbytes=10MB
+loglevel=critical
+
+[supervisorctl]
+serverurl=http://127.0.0.1:9001
+
+[inet_http_server]
+port = 127.0.0.1:9001
+
+[rpcinterface:supervisor]
+supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
+
+[program:indy-node]
+command=sh -ac '. $GENERAL_CONFIG_DIR/indy.env;/usr/bin/env python3 -O /usr/local/bin/start_indy_node \${NODE_NAME} \${NODE_IP} \${NODE_PORT} \${NODE_CLIENT_IP} \${NODE_CLIENT_PORT}'
+user=indy
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
+autorestart=true
+startsecs=15
+startretries=6
+stopasgroup=true
+killasgroup=true
+
+[program:indy-node-control]
+command=sh -ac '. $GENERAL_CONFIG_DIR/node_control.conf;/usr/bin/env python3 -O /usr/local/bin/start_node_control_tool \${TEST_MODE} --hold-ext "\${HOLD_EXT}"'
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
+autorestart=true
+startsecs=15
+startretries=6
+stopasgroup=true
+killasgroup=true
+EOF
+
+HOLD_EXT_ADDED=$(grep HOLD_EXT /etc/indy/node_control.conf)
+if [ ! -f $GENERAL_CONFIG_DIR/node_control.conf ] || [ -z "${HOLD_EXT_ADDED}" ]; then
+    cat <<EOF > $GENERAL_CONFIG_DIR/node_control.conf
+# Uncomment this to run agent in test mode:
+#TEST_MODE=--test
+
+TEST_MODE=
+HOLD_EXT=
+EOF
+fi
+sed -ie '/HOLD_EXT/{ s/\\//g}' $GENERAL_CONFIG_DIR/node_control.conf
+
+mv /usr/local/bin/upgrade_indy_node_ubuntu1604.sh /usr/local/bin/upgrade_indy_node
+mv /usr/local/bin/upgrade_indy_node_ubuntu1604_test.sh /usr/local/bin/upgrade_indy_node_test
+mv /usr/local/bin/restart_indy_node_ubuntu1604.sh /usr/local/bin/restart_indy_node
+mv /usr/local/bin/restart_sovrin_node_ubuntu1604.sh /usr/local/bin/restart_sovrin_node
+mv /usr/local/bin/complete_rebranding_upgrade_ubuntu1604.sh /usr/local/bin/complete_rebranding_upgrade
+
+chmod +x /usr/local/bin/upgrade_indy_node
+chmod +x /usr/local/bin/upgrade_indy_node_test
+chmod +x /usr/local/bin/restart_indy_node
+chmod +x /usr/local/bin/restart_sovrin_node
+chmod +x /usr/local/bin/complete_rebranding_upgrade
+
+rm -f /usr/local/bin/delete_indy_node.bat /usr/local/bin/upgrade_indy_node_test.bat \
+    /usr/local/bin/restart_indy_node.bat /usr/local/bin/install_nssm.bat /usr/local/bin/upgrade_indy_node.bat \
+    /usr/local/bin/install_indy_node.bat /usr/local/bin/restart_upgrade_agent.bat
+
+chown -R indy:indy $GENERAL_CONFIG_DIR
+chown -R indy:indy $GENERAL_DATA_DIR
+chown -R indy:indy $GENERAL_LOG_DIR
+chmod -R ug+rw $GENERAL_CONFIG_DIR
+chmod -R ug+rw $GENERAL_DATA_DIR
+chmod -R ug+rw $GENERAL_LOG_DIR
+
+# Automatically added from template:
+if which py3compile >/dev/null 2>&1; then
+	py3compile -O -p indy-node $INSTALL_DIR
+fi
+
+# End automatically added section
diff --git a/build-scripts/ubuntu-2004/preinst_node b/build-scripts/ubuntu-2004/preinst_node
new file mode 100644
index 000000000..4e1addfb6
--- /dev/null
+++ b/build-scripts/ubuntu-2004/preinst_node
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+if ! id -u indy > /dev/null 2>&1; then
+    useradd -r -m -s /bin/bash -U indy
+fi
diff --git a/build-scripts/ubuntu-2004/prepare-package.sh b/build-scripts/ubuntu-2004/prepare-package.sh
new file mode 100755
index 000000000..e86f47209
--- /dev/null
+++ b/build-scripts/ubuntu-2004/prepare-package.sh
@@ -0,0 +1,59 @@
+#!/bin/bash -xe
+
+if [ "$1" = "--help" ] ; then
+  echo "Usage: $0 <path-to-repo-folder> <main-module-name> <release-version-dotted> <distro-packages>"
+  echo "<distro-packages> - Set to 'debian-packages' when preparing deb packages, and 'python-packages' when preparing PyPi packages."
+  exit 0
+fi
+
+repo="$1"
+module_name="$2"
+version_dotted="$3"
+distro_packages="$4"
+
+BUMP_SH_SCRIPT="bump_version.sh"
+GENERATE_MANIFEST_SCRIPT="generate_manifest.sh"
+
+pushd $repo
+
+echo -e "\nSetting version to $version_dotted"
+bash -ex $BUMP_SH_SCRIPT $version_dotted
+cat $module_name/__version__.json
+
+echo -e "\nGenerating manifest"
+bash -ex $GENERATE_MANIFEST_SCRIPT
+cat $module_name/__manifest__.json
+
+echo -e "\n\nPrepares indy-node debian package version"
+sed -i -r "s~indy-node==([0-9\.]+[0-9])(\.)?([a-z]+)~indy-node==\1\~\3~" setup.py
+
+if [ "$distro_packages" = "debian-packages" ]; then
+  # Only used for the deb package builds, NOT for the PyPi package builds.
+  # Update the package names to match the versions that are pre-installed on the os.
+  echo -e "\nAdapt the dependencies for the Canonical archive"
+  #### ToDo adjust packages for the Cannonical archive for Ubuntu 20.04 (focal)
+  # sed -i "s~timeout-decorator~python3-timeout-decorator~" setup.py
+  # sed -i "s~distro~python3-distro~" setup.py
+elif [ "$distro_packages" = "python-packages" ]; then
+  echo -e "\nNo adaption of dependencies for python packages"
+else
+  echo -e "\nNo distribution specified. Please, specify distribution as 'debian-packages' or 'python-packages'."
+  exit 1
+fi
+
+echo "Preparing config files"
+GENERAL_CONFIG_DIR="\/etc\/indy"
+REPO_GENERAL_CONFIG_DIR="indy_node/general_config"
+# Define user config directory
+sed -i "s/^\(GENERAL_CONFIG_DIR\s*=\s*\).*\$/\1\"$GENERAL_CONFIG_DIR\"/" indy_common/config.py
+# Create user config
+cp $REPO_GENERAL_CONFIG_DIR/general_config.py $REPO_GENERAL_CONFIG_DIR/indy_config.py
+cat $REPO_GENERAL_CONFIG_DIR/ubuntu_platform_config.py >> $REPO_GENERAL_CONFIG_DIR/indy_config.py
+rm -f $REPO_GENERAL_CONFIG_DIR/general_config.py
+rm -f $REPO_GENERAL_CONFIG_DIR/ubuntu_platform_config.py
+rm -f $REPO_GENERAL_CONFIG_DIR/windows_platform_config.py
+
+popd
+
+echo -e "\nFinished preparing $repo for publishing\n"
+
diff --git a/build-scripts/ubuntu-2004/prerm b/build-scripts/ubuntu-2004/prerm
new file mode 100644
index 000000000..40af9b22a
--- /dev/null
+++ b/build-scripts/ubuntu-2004/prerm
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+# Automatically added from template:
+
+dpkg -L {package_name} | perl -ne 's,/([^/]*)\.py$,/__pycache__/\1.*, or next; unlink $_ or die $! foreach glob($_)'
+
+# End automatically added section
\ No newline at end of file
diff --git a/ci/pipeline.groovy b/ci/pipeline.groovy
index 22fe63a74..d03a2b216 100644
--- a/ci/pipeline.groovy
+++ b/ci/pipeline.groovy
@@ -148,6 +148,7 @@ def systemTests(Closure body) {
             def uid = sh(returnStdout: true, script: 'id -u').trim()
             docker.build("hyperledger/indy-node-ci", "--build-arg uid=$uid -f ci/ubuntu.dockerfile ci").inside {
                 sh """
+                    pip install pip==10.0.0
                     pip install .[tests] >$pipLogName
                 """
 
@@ -160,7 +161,7 @@ def systemTests(Closure body) {
                 indySDKVersions.pypi = sh(returnStdout: true, script: """
                     grep "^Collecting python3-indy==" $pipLogName | awk '{print \$2}' | awk -F'==' '{print \$2}'
                 """).trim()
-                indySDKVersions.debian = indySDKVersions.pypi.replaceAll(/-(dev|rc)-(.*)/, "~\$2")
+                indySDKVersions.debian = indySDKVersions.pypi.replaceAll(/-(dev|rc)-(.*)/, "~\$2-xenial")
                 echo "indy-sdk version: ${indySDKVersions}"
             }
 
diff --git a/ci/ubuntu.dockerfile b/ci/ubuntu.dockerfile
index 46ea2eda4..c4799e8d9 100644
--- a/ci/ubuntu.dockerfile
+++ b/ci/ubuntu.dockerfile
@@ -5,10 +5,13 @@ ARG uid=1000
 ARG user=indy
 ARG venv=venv
 
+# Update Sovrin signing key
+RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CE7709D068DB5E88 
+
 RUN apt-get update -y && apt-get install -y \
     python3-nacl \
     ursa=0.3.2-2 \
-    libindy=1.13.0~1420 \
+    libindy=1.15.0~1618-xenial \
 # rocksdb python wrapper
     libbz2-dev \
     zlib1g-dev \
diff --git a/data/migrations/deb/1_2_233_to_1_2_234.py b/data/migrations/deb/1_2_233_to_1_2_234.py
index ed0c2d619..8087b5dab 100644
--- a/data/migrations/deb/1_2_233_to_1_2_234.py
+++ b/data/migrations/deb/1_2_233_to_1_2_234.py
@@ -50,9 +50,9 @@ def migrate_nodes_data():
     except FileNotFoundError:
         visit_dirs = []
     for node_name in visit_dirs:
-            move_path = os.path.join(old_nodes_data_dir, node_name)
-            to_path = os.path.join(new_node_data_dir, node_name)
-            ext_copytree(move_path, to_path)
+        move_path = os.path.join(old_nodes_data_dir, node_name)
+        to_path = os.path.join(new_node_data_dir, node_name)
+        ext_copytree(move_path, to_path)
     shutil.rmtree(old_nodes_data_dir)
     set_own_perm("indy", [new_node_data_dir])
 
diff --git a/docs/source/auth_rules.md b/docs/source/auth_rules.md
index 19444819d..3545b9700 100644
--- a/docs/source/auth_rules.md
+++ b/docs/source/auth_rules.md
@@ -537,6 +537,15 @@
     <td><sub>1 TRUSTEE OR 1 STEWARD OR 1 NETWORK_MONITOR</sub></td>
     <td><sub>Getting validator_info from pool</sub></td>
   </tr>
+    <tr>
+    <td><sub>LEDGERS_FREEZE</sub></td>
+    <td><sub>EDIT</sub></td>
+    <td><sub><code>*</code></sub></td>
+    <td><sub><code>*</code></sub></td>
+    <td><sub><code>*</code></sub></td>
+    <td><sub>3 TRUSTEE</sub></td>
+    <td><sub>Freeze specific ledgers</sub></td>
+  </tr>
 </table>
 
 
@@ -729,6 +738,11 @@
     <td><sub>N/A</sub></td>
   </tr>    
 
+  <tr>
+    <td><sub>LEDGERS_FREEZE</sub></td>
+    <td><sub>EDIT</sub></td>
+    <td><sub>N/A</sub></td>
+  </tr>   
         
 </table>
 
diff --git a/docs/source/requests-new.md b/docs/source/requests-new.md
index e69ff800f..cbb1f63e8 100644
--- a/docs/source/requests-new.md
+++ b/docs/source/requests-new.md
@@ -16,6 +16,7 @@
     * [NODE](#node)
     * [POOL_UPGRADE](#pool_upgrade)
     * [POOL_CONFIG](#pool_config)
+    * [LEDGERS_FREEZE](#ledgers_freeze)
 
 * [Read Requests](#read-requests)
 
@@ -24,6 +25,7 @@
     * [GET_SCHEMA](#get_schema)
     * [GET_CLAIM_DEF](#get_claim_def)
     * [GET_TXN](#get_txn)
+    * [GET_FROZEN_LEDGERS](#get_frozen_ledgers)
     
 This doc is about supported client"s Request (both write and read ones).
 If you are interested in transactions and their representation on the Ledger (that is internal one),
@@ -1386,6 +1388,66 @@ Command to change Pool's configuration
 }
 ```
 
+### LEDGERS_FREEZE
+
+Freeze deprecated ledgers (default ledgers such as the domain, config, pool, and audit ledgers cannot be frozen). If a ledger is frozen it can be used for reading but not for writing. Frozen ledgers will not be caught up by new nodes and they can't be unfrozen. Frozen ledgers can be removed without breaking consensus, but this would prevent third parties from auditing the ledger history. [More information is in the Indy Plenum documenation](https://hyperledger/indy-plenum/tree/master/docs/source/transaction_freeze_ledgers.md).
+
+The request has static and dynamic validations. Static validation checks to avoid freezing base ledgers (pool, audit, domain and config). Dynamic validation checks the existence of ledgers before freezing. Authorize checks the permissions for the freeze request (3 trustee signatures are needed by default).
+
+*Request Example*:
+```
+{
+    "operation": {
+        "type": "9",
+        "ledgers_ids": [1,2,3,4]
+    },
+    "identifier": "21BPzYYrFzbuECcBV3M1FH",
+    "reqId": 311348486,
+    "protocolVersion": 2,
+    "signature": "3YVzDtSxxnowVwAXZmxCG2fz1A38j1qLrwKmGEG653GZw7KJRBX57Stc1oxQZqqu9mCqFLa7aBzt4MKXk4MeunVj",
+}
+```
+
+*Reply Example*:
+```
+{
+   "op":"REPLY",
+   "result":{
+      "txn":{
+         "type":"9",
+         "data":{
+            "ledgers_ids":[1,2,3,4]
+         },
+         "protocolVersion":2,
+         "metadata":{
+            "reqId":311348486,
+            "payloadDigest":"09a3ccedf806e224beb56b547e967b442f3ee3181d5c87623f063742df7a692e",
+            "from":"M9BJDuS24bqbJNvBRsoGg3",
+            "digest":"dfdc6c5b77181953b4e32f975b0c5e64b25dc3e3061716aca1baae4cbe0ce494"
+         }
+      },
+      "ver":"1",
+      "auditPath":[
+         "DDwrSsKwpFkfGVqp7AxzRMusUuT9D5RmidCmnr8phTWD"
+      ],
+      "txnMetadata":{
+         "txnTime":1613735420,
+         "seqNo":3
+      },
+      "reqSignature":{
+         "type":"ED25519",
+         "values":[
+            {
+                "value":"bSMfwJraLXzBAmdZKQUC1XfoVb3YWygc6UCQAmrTNKDr9beXta7MFyNZnQtbmNoRurjicSHLo3sW7qv7ZTWcZJa",
+                "from":"M9BJDuS24bqbJNvBRsoGg3"
+            }
+         ]
+      },
+      "rootHash":"E27ssC3LK8azpgxxtBY4ETLXvDLGtfmeSjngc6jrV1Qt"
+   }
+}
+```
+
 ## Read Requests
 
 ### GET_NYM
@@ -1898,3 +1960,67 @@ A generic request to get a transaction from Ledger by its sequence number.
 }
 ```
 
+
+### GET_FROZEN_LEDGERS
+
+Get whole list of frozen ledgers. Reply has follow state format data:
+
+```
+<ledger_id>: {
+    ledger: <ledger_root_hash>,
+    state: <state_root_hash>,
+    seq_no: <last_seq_no>
+}
+```
+
+*Request Example*:
+```
+{
+   "operation":{
+      "type":"10"
+   },
+   "reqId":783857061,
+   "protocolVersion":2,
+   "identifier":"M9BJDuS24bqbJNvBRsoGg3"
+}
+```
+
+*Reply Example*:
+```
+{
+   "result":{
+      "seqNo":3,
+      "type":"10",
+      "state_proof":{
+         "root_hash":"HUv35b31eqncHZ1R8xMQW9pJnCBqAaUVrfCA8AeTtx6u",
+         "multi_signature":{
+            "value":{
+               "pool_state_root_hash":"4bCEk76QsB6p3yCiDntMedpeZmiQtdH9NRpcFyvaLHhc",
+               "state_root_hash":"HUv35b31eqncHZ1R8xMQW9pJnCBqAaUVrfCA8AeTtx6u",
+               "timestamp":1613736202,
+               "ledger_id":2,
+               "txn_root_hash":"BY6PV9SrV1dgQgxy2kpeTLESQfazTYoLdLZfjzVmcLeV"
+            },
+            "signature":"R8FRHVg51YiY5nS8Hh8iXNa1ZPKjrQMmurnrGek2A7QMKq79Pws4DLhgcVgf66PSJGEPjmyASYxFziEnubY1RFHQiE7ZToLZqW4oJt11hhL1XgXwrdswyqTQjuyxx5nzjyE4AzyTvs3BywD54s3w3mUhLG3QWwBp1uTX8agLEKZDkK",
+            "participants":[
+               "Gamma",
+               "Delta",
+               "Beta"
+            ]
+         },
+         "proof_nodes":"+L74vJEgNDpGUk9aRU5fTEVER0VSU7io+Ka4pHsibHNuIjozLCJsdXQiOjE2MTM3MzYyMDIsInZhbCI6eyI5MDkiOnsibGVkZ2VyIjoiR0tvdDVoQnNkODFrTXVwTkNYSGFxYmh2M2h1RWJ4QUZNTG5wY1gyaG5pd24iLCJzZXFfbm8iOjAsInN0YXRlIjoiRGZOTG1INERBSFRLdjYzWVBGSnp1UmRlRXRWd0Y1UnRWbnZLWUhkOGlMRUEifX19"
+      },
+      "txnTime":1613736202,
+      "reqId":666493618,
+      "data":{
+         "909":{
+            "seq_no":0,
+            "state":"DfNLmH4DAHTKv63YPFJzuRdeEtVwF5RtVnvKYHd8iLEA",
+            "ledger":"GKot5hBsd81kMupNCXHaqbhv3huEbxAFMLnpcX2hniwn"
+         }
+      },
+      "identifier":"M9BJDuS24bqbJNvBRsoGg3"
+   },
+   "op":"REPLY"
+}
+```
diff --git a/docs/source/requests.md b/docs/source/requests.md
index 5e0db7d78..f0a67932b 100644
--- a/docs/source/requests.md
+++ b/docs/source/requests.md
@@ -24,6 +24,7 @@
     * [TRANSACTION_AUTHOR_AGREEMENT](#transaction_author_agreement)
     * [TRANSACTION_AUTHOR_AGREEMENT_AML](#transaction_author_agreement_AML)    
     * [TRANSACTION_AUTHOR_AGREEMENT_DISABLE](#transaction_author_agreement_disable)
+    * [LEDGERS_FREEZE](#ledgers_freeze)
 
 
 * [Read Requests](#read-requests)
@@ -43,6 +44,7 @@
     * [GET_CONTEXT](#get_context)
     * [GET_RICH_SCHEMA](#get_rich_schema)
     * [GET_TXN](#get_txn)
+    * [GET_FROZEN_LEDGERS](#get_frozen_ledgers)
 
 * [Action Requests](#action-requests)
 
@@ -2953,7 +2955,64 @@ A new Agreement needs to be sent instead.
 }
 ```
 
+### LEDGERS_FREEZE
 
+Freeze deprecated ledgers (default ledgers such as the domain, config, pool, and audit ledgers cannot be frozen). If a ledger is frozen it can be used for reading but not for writing. Frozen ledgers will not be caught up by new nodes and they can't be unfrozen. Frozen ledgers can be removed without breaking consensus, but this would prevent third parties from auditing the ledger history. [More information is in the Indy Plenum documenation](https://hyperledger/indy-plenum/tree/master/docs/source/transaction_freeze_ledgers.md).
+
+The request has static and dynamic validations. Static validation checks to avoid freezing base ledgers (pool, audit, domain and config). Dynamic validation checks the existence of ledgers before freezing. Authorize checks the permissions for the freeze request (3 trustee signatures are needed by default).
+
+*Request Example*:
+```
+{
+    "operation": {
+        "type": "9",
+        "ledgers_ids": [1,2,3,4]
+    },
+    "identifier": "21BPzYYrFzbuECcBV3M1FH",
+    "reqId": 311348486,
+    "protocolVersion": 2,
+    "signature": "3YVzDtSxxnowVwAXZmxCG2fz1A38j1qLrwKmGEG653GZw7KJRBX57Stc1oxQZqqu9mCqFLa7aBzt4MKXk4MeunVj",
+}
+```
+
+*Reply Example*:
+```
+{
+   "op":"REPLY",
+   "result":{
+      "txn":{
+         "type":"9",
+         "data":{
+            "ledgers_ids":[ ]
+         },
+         "protocolVersion":2,
+         "metadata":{
+            "reqId":311348486,
+            "payloadDigest":"09a3ccedf806e224beb56b547e967b442f3ee3181d5c87623f063742df7a692e",
+            "from":"M9BJDuS24bqbJNvBRsoGg3",
+            "digest":"dfdc6c5b77181953b4e32f975b0c5e64b25dc3e3061716aca1baae4cbe0ce494"
+         }
+      },
+      "ver":"1",
+      "auditPath":[
+         "DDwrSsKwpFkfGVqp7AxzRMusUuT9D5RmidCmnr8phTWD"
+      ],
+      "txnMetadata":{
+         "txnTime":1613735420,
+         "seqNo":3
+      },
+      "reqSignature":{
+         "type":"ED25519",
+         "values":[
+            {               "value":"bSMfwJraLXzBAmdZKQUC1XfoVb3YWygc6UCQAmrTNKDr9beXta7MFyNZnQtbmNoRurjicSHLo3sW7qv7ZTWcZJa",
+               "from":"M9BJDuS24bqbJNvBRsoGg3"
+            }
+         ]
+      },
+      "rootHash":"E27ssC3LK8azpgxxtBY4ETLXvDLGtfmeSjngc6jrV1Qt"
+   }
+}
+```
 
 ## Read Requests
 
@@ -4184,6 +4243,70 @@ A generic request to get a transaction from Ledger by its sequence number.
 }
 ```
 
+### GET_FROZEN_LEDGERS
+
+Get whole list of frozen ledgers. Reply has follow state format data:
+
+```
+<ledger_id>: {
+    ledger: <ledger_root_hash>,
+    state: <state_root_hash>,
+    seq_no: <last_seq_no>
+}
+```
+
+*Request Example*:
+```
+{
+   "operation":{
+      "type":"10"
+   },
+   "reqId":783857061,
+   "protocolVersion":2,
+   "identifier":"M9BJDuS24bqbJNvBRsoGg3"
+}
+```
+
+*Reply Example*:
+```
+{
+   "result":{
+      "seqNo":3,
+      "type":"10",
+      "state_proof":{
+         "root_hash":"HUv35b31eqncHZ1R8xMQW9pJnCBqAaUVrfCA8AeTtx6u",
+         "multi_signature":{
+            "value":{
+               "pool_state_root_hash":"4bCEk76QsB6p3yCiDntMedpeZmiQtdH9NRpcFyvaLHhc",
+               "state_root_hash":"HUv35b31eqncHZ1R8xMQW9pJnCBqAaUVrfCA8AeTtx6u",
+               "timestamp":1613736202,
+               "ledger_id":2,
+               "txn_root_hash":"BY6PV9SrV1dgQgxy2kpeTLESQfazTYoLdLZfjzVmcLeV"
+            },
+            "signature":"R8FRHVg51YiY5nS8Hh8iXNa1ZPKjrQMmurnrGek2A7QMKq79Pws4DLhgcVgf66PSJGEPjmyASYxFziEnubY1RFHQiE7ZToLZqW4oJt11hhL1XgXwrdswyqTQjuyxx5nzjyE4AzyTvs3BywD54s3w3mUhLG3QWwBp1uTX8agLEKZDkK",
+            "participants":[
+               "Gamma",
+               "Delta",
+               "Beta"
+            ]
+         },
+         "proof_nodes":"+L74vJEgNDpGUk9aRU5fTEVER0VSU7io+Ka4pHsibHNuIjozLCJsdXQiOjE2MTM3MzYyMDIsInZhbCI6eyI5MDkiOnsibGVkZ2VyIjoiR0tvdDVoQnNkODFrTXVwTkNYSGFxYmh2M2h1RWJ4QUZNTG5wY1gyaG5pd24iLCJzZXFfbm8iOjAsInN0YXRlIjoiRGZOTG1INERBSFRLdjYzWVBGSnp1UmRlRXRWd0Y1UnRWbnZLWUhkOGlMRUEifX19"
+      },
+      "txnTime":1613736202,
+      "reqId":666493618,
+      "data":{
+         "909":{
+            "seq_no":0,
+            "state":"DfNLmH4DAHTKv63YPFJzuRdeEtVwF5RtVnvKYHd8iLEA",
+            "ledger":"GKot5hBsd81kMupNCXHaqbhv3huEbxAFMLnpcX2hniwn"
+         }
+      },
+      "identifier":"M9BJDuS24bqbJNvBRsoGg3"
+   },
+   "op":"REPLY"
+}
+```
+
 ## Action Requests
 
 ### POOL_RESTART
diff --git a/docs/source/transactions.md b/docs/source/transactions.md
index 9b205f338..b980446e0 100644
--- a/docs/source/transactions.md
+++ b/docs/source/transactions.md
@@ -30,6 +30,7 @@
     * [TRANSACTION_AUTHOR_AGREEMENT](#transaction_author_agreement)
     * [TRANSACTION_AUTHOR_AGREEMENT_AML](#transaction_author_agreement_AML)
     * [TRANSACTION_AUTHOR_AGREEMENT_DISABLE](#transaction_author_agreement_disable)
+    * [LEDGERS_FREEZE](#ledgers_freeze)
     
 
 ## General Information
@@ -2093,3 +2094,40 @@ A new Agreement needs to be sent instead.
 }
 ```
 
+#### LEDGERS_FREEZE
+
+Freeze deprecated ledgers (default ledgers such as the domain, config, pool, and audit ledgers cannot be frozen). If a ledger is frozen it can be used for reading but not for writing. Frozen ledgers will not be caught up by new nodes and they can't be unfrozen. Frozen ledgers can be removed without breaking consensus, but this would prevent third parties from auditing the ledger history. [More information is in the Indy Plenum documenation](https://hyperledger/indy-plenum/tree/master/docs/source/transaction_freeze_ledgers.md).
+
+**Example:**
+```
+{
+    "ver": 1,
+    "txn": {
+        "type":9,
+        "protocolVersion":2,
+
+        "data": {
+            "ver": 1,
+            "ledgers_ids": [1,2,3,4],
+        },
+
+        "metadata": {
+            "reqId":1513945121191691,
+            "from":"L5AD5g65TDQr1PPHHRoiGf",
+            "digest":"6cee82226c6e276c983f46d03e3b3d10436d90b67bf33dc67ce9901b44dbc97c",
+            "payloadDigest": "21f0f5c158ed6ad49ff855baf09a2ef9b4ed1a8015ac24bccc2e0106cd905685",
+        },
+    },
+    "txnMetadata": {
+        "txnTime":1577836799,
+        "seqNo": 10,
+    },
+    "reqSignature": {
+        "type": "ED25519",
+        "values": [{
+            "from": "L5AD5g65TDQr1PPHHRoiGf",
+            "value": "4X3skpoEK2DRgZxQ9PwuEvCJpL8JHdQ8X4HDDFyztgqE15DM2ZnkvrAh9bQY16egVinZTzwHqznmnkaFM4jjyDgd"
+        }]
+    }
+}
+```
diff --git a/indy_common/auth.py b/indy_common/auth.py
index 176f0eaed..dbaf9ab9a 100644
--- a/indy_common/auth.py
+++ b/indy_common/auth.py
@@ -1,5 +1,5 @@
 from indy_common.config_util import getConfig
-from plenum.common.constants import TRUSTEE, STEWARD, NODE
+from plenum.common.constants import TRUSTEE, STEWARD, NODE, LEDGERS_FREEZE
 from stp_core.common.log import getlogger
 
 from indy_common.constants import OWNER, POOL_UPGRADE, ENDORSER, NYM, \
@@ -62,6 +62,8 @@ def generate_auth_map(valid_roles):
             {TRUSTEE: []},
         '{}_<any>_<any>_<any>'.format(VALIDATOR_INFO):
             {TRUSTEE: [], STEWARD: []},
+        '{}_<any>_<any>_<any>'.format(LEDGERS_FREEZE):
+            {TRUSTEE: []},
     }
     return auth_map
 
diff --git a/indy_common/authorize/auth_constraints.py b/indy_common/authorize/auth_constraints.py
index 8c0e14e96..d136fe61e 100644
--- a/indy_common/authorize/auth_constraints.py
+++ b/indy_common/authorize/auth_constraints.py
@@ -136,7 +136,7 @@ def __str__(self):
         elif role == "ALL" and self.need_to_be_owner and self.sig_count == 1:
             error_msg = "1 signature of any role is required and needs to be owner"
         elif role == 'ALL' and not self.need_to_be_owner and self.sig_count == 1:
-            error_msg = "1 signature of any role is required".format(role)
+            error_msg = "1 signature of any role is required {}".format(role)
         elif role == 'ALL' and not self.need_to_be_owner and self.sig_count > 1:
             error_msg = "{} signatures of any role are required".format(self.sig_count)
         elif role == "ALL" and self.need_to_be_owner and self.sig_count > 1:
diff --git a/indy_common/authorize/auth_map.py b/indy_common/authorize/auth_map.py
index 0534acb08..9bd47c79f 100644
--- a/indy_common/authorize/auth_map.py
+++ b/indy_common/authorize/auth_map.py
@@ -9,7 +9,7 @@
     REVOC_REG_DEF, ATTRIB, AUTH_RULES, JSON_LD_CONTEXT, RICH_SCHEMA, RICH_SCHEMA_MAPPING, \
     RICH_SCHEMA_ENCODING, RICH_SCHEMA_CRED_DEF, RICH_SCHEMA_PRES_DEF
 from plenum.common.constants import TRUSTEE, STEWARD, VERKEY, TXN_AUTHOR_AGREEMENT, TXN_AUTHOR_AGREEMENT_AML, \
-    TXN_AUTHOR_AGREEMENT_DISABLE
+    TXN_AUTHOR_AGREEMENT_DISABLE, LEDGERS_FREEZE
 
 edit_role_actions = {}  # type: Dict[str, Dict[str, AuthActionEdit]]
 for role_from in accepted_roles:
@@ -236,6 +236,11 @@
                                       old_value='*',
                                       new_value='*')
 
+edit_frozen_ledgers = AuthActionEdit(txn_type=LEDGERS_FREEZE,
+                                     field='*',
+                                     old_value='*',
+                                     new_value='*')
+
 # Anyone constraint
 anyone_constraint = AuthConstraint(role='*',
                                    sig_count=1)
@@ -254,6 +259,9 @@
 # One Trustee constraint
 one_trustee_constraint = AuthConstraint(TRUSTEE, 1)
 
+# Three Trustee constraint
+three_trustee_constraint = AuthConstraint(TRUSTEE, 3)
+
 # Steward or Trustee constraint
 steward_or_trustee_constraint = AuthConstraintOr([AuthConstraint(STEWARD, 1),
                                                   AuthConstraint(TRUSTEE, 1)])
@@ -324,6 +332,7 @@
     (add_revoc_reg_entry.get_action_id(), endorser_or_steward_or_trustee_owner_constraint),
     (edit_revoc_reg_def.get_action_id(), owner_constraint),
     (edit_revoc_reg_entry.get_action_id(), owner_constraint),
+    (edit_frozen_ledgers.get_action_id(), three_trustee_constraint),
 ])
 
 # Edit Trustee:
diff --git a/indy_common/exceptions.py b/indy_common/exceptions.py
index 30e885db6..70c1b6735 100644
--- a/indy_common/exceptions.py
+++ b/indy_common/exceptions.py
@@ -13,7 +13,7 @@ class NotFound(RuntimeError):
 class ConnectionNotFound(NotFound):
     def __init__(self, name: str = None):
         if name:
-            self.reason = "Connection with name not found".format(name)
+            self.reason = "Connection with name not found {}".format(name)
 
 
 class VerkeyNotFound(NotFound):
diff --git a/indy_common/types.py b/indy_common/types.py
index f76e56b1a..b896fae67 100644
--- a/indy_common/types.py
+++ b/indy_common/types.py
@@ -496,7 +496,7 @@ class ClientOperationField(PClientOperationField):
         RICH_SCHEMA_CRED_DEF: ClientRichSchemaCredDefOperation(),
         RICH_SCHEMA_PRES_DEF: ClientRichSchemaPresDefOperation(),
         GET_RICH_SCHEMA_OBJECT_BY_ID: ClientGetRichSchemaObjectByIdOperation(),
-        GET_RICH_SCHEMA_OBJECT_BY_METADATA: ClientGetRichSchemaObjectByMetadataOperation(),
+        GET_RICH_SCHEMA_OBJECT_BY_METADATA: ClientGetRichSchemaObjectByMetadataOperation()
     }
 
     # TODO: it is a workaround because INDY-338, `operations` must be a class
diff --git a/indy_node/__metadata__.py b/indy_node/__metadata__.py
index f8fa05d90..1603cb026 100644
--- a/indy_node/__metadata__.py
+++ b/indy_node/__metadata__.py
@@ -47,7 +47,7 @@ def load_manifest(manifest_file: str = MANIFEST_FILE) -> Any:
 
 def set_manifest(manifest: Any, manifest_file: str = MANIFEST_FILE):
     with open(manifest_file, 'w') as _f:
-        json.dump(manifest, _f)
+        json.dump(manifest, _f, sort_keys=True)
         _f.write('\n')
 
 
diff --git a/indy_node/server/node_bootstrap.py b/indy_node/server/node_bootstrap.py
index bd5d2232c..89d7bca75 100644
--- a/indy_node/server/node_bootstrap.py
+++ b/indy_node/server/node_bootstrap.py
@@ -12,6 +12,7 @@
 from indy_node.server.request_handlers.config_req_handlers.auth_rule.auth_rule_handler import AuthRuleHandler
 from indy_node.server.request_handlers.config_req_handlers.auth_rule.auth_rule_handler_1_9_1 import AuthRuleHandler191
 from indy_node.server.request_handlers.config_req_handlers.auth_rule.auth_rules_handler import AuthRulesHandler
+from indy_node.server.request_handlers.config_req_handlers.ledgers_freeze_handler import LedgersFreezeHandler
 from indy_node.server.request_handlers.config_req_handlers.node_upgrade_handler import NodeUpgradeHandler
 from indy_node.server.request_handlers.config_req_handlers.pool_config_handler import PoolConfigHandler
 from indy_node.server.request_handlers.config_req_handlers.pool_upgrade_handler import PoolUpgradeHandler
@@ -45,6 +46,7 @@
 from indy_node.server.request_handlers.read_req_handlers.get_attribute_handler import GetAttributeHandler
 from indy_node.server.request_handlers.read_req_handlers.get_auth_rule_handler import GetAuthRuleHandler
 from indy_node.server.request_handlers.read_req_handlers.get_claim_def_handler import GetClaimDefHandler
+from plenum.server.request_handlers.ledgers_freeze.get_frozen_ledgers_handler import GetFrozenLedgersHandler
 from indy_node.server.request_handlers.read_req_handlers.get_nym_handler import GetNymHandler
 from indy_node.server.request_handlers.read_req_handlers.get_revoc_reg_def_handler import GetRevocRegDefHandler
 from indy_node.server.request_handlers.read_req_handlers.get_revoc_reg_delta_handler import GetRevocRegDeltaHandler
@@ -197,6 +199,9 @@ def _register_config_req_handlers(self):
         get_taa_aml_handler = GetTxnAuthorAgreementAmlHandler(database_manager=self.node.db_manager)
         get_taa_handler = GetTxnAuthorAgreementHandler(database_manager=self.node.db_manager)
         node_upgrade_handler = NodeUpgradeHandler(database_manager=self.node.db_manager)
+        ledgers_freeze_handler = LedgersFreezeHandler(database_manager=self.node.db_manager,
+                                                      write_req_validator=self.node.write_req_validator)
+        get_frozen_ledgers_handler = GetFrozenLedgersHandler(database_manager=self.node.db_manager)
         # Register write handlers
         self.node.write_manager.register_req_handler(auth_rule_handler)
         self.node.write_manager.register_req_handler(auth_rules_handler)
@@ -206,10 +211,12 @@ def _register_config_req_handlers(self):
         self.node.write_manager.register_req_handler(taa_handler)
         self.node.write_manager.register_req_handler(taa_disable_handler)
         self.node.write_manager.register_req_handler(node_upgrade_handler)
+        self.node.write_manager.register_req_handler(ledgers_freeze_handler)
         # Register read handlers
         self.node.read_manager.register_req_handler(get_auth_rule_handler)
         self.node.read_manager.register_req_handler(get_taa_aml_handler)
         self.node.read_manager.register_req_handler(get_taa_handler)
+        self.node.read_manager.register_req_handler(get_frozen_ledgers_handler)
         # Register write handlers for a version
         self.node.write_manager.register_req_handler_with_version(auth_rule_handler_1_9_1,
                                                                   version="1.9.1")
diff --git a/indy_node/server/request_handlers/config_req_handlers/auth_rule/auth_rule_handler.py b/indy_node/server/request_handlers/config_req_handlers/auth_rule/auth_rule_handler.py
index 376c7e7e8..0bda7a0c7 100644
--- a/indy_node/server/request_handlers/config_req_handlers/auth_rule/auth_rule_handler.py
+++ b/indy_node/server/request_handlers/config_req_handlers/auth_rule/auth_rule_handler.py
@@ -21,7 +21,7 @@ def static_validation(self, request: Request):
         self._validate_request_type(request)
         self._static_validation_for_rule(operation, identifier, req_id)
 
-    def dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
         self._validate_request_type(request)
         self.write_req_validator.validate(request,
                                           [AuthActionEdit(txn_type=AUTH_RULE,
diff --git a/indy_node/server/request_handlers/config_req_handlers/auth_rule/auth_rules_handler.py b/indy_node/server/request_handlers/config_req_handlers/auth_rule/auth_rules_handler.py
index d739c4736..807b035a0 100644
--- a/indy_node/server/request_handlers/config_req_handlers/auth_rule/auth_rules_handler.py
+++ b/indy_node/server/request_handlers/config_req_handlers/auth_rule/auth_rules_handler.py
@@ -22,7 +22,7 @@ def static_validation(self, request: Request):
         for rule in operation.get(RULES):
             self._static_validation_for_rule(rule, identifier, req_id)
 
-    def dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
         self._validate_request_type(request)
         self.write_req_validator.validate(request,
                                           [AuthActionEdit(txn_type=AUTH_RULES,
diff --git a/indy_node/server/request_handlers/config_req_handlers/ledgers_freeze_handler.py b/indy_node/server/request_handlers/config_req_handlers/ledgers_freeze_handler.py
new file mode 100644
index 000000000..b189bb637
--- /dev/null
+++ b/indy_node/server/request_handlers/config_req_handlers/ledgers_freeze_handler.py
@@ -0,0 +1,23 @@
+from typing import Optional
+
+from indy_common.authorize.auth_actions import AuthActionEdit
+from indy_common.authorize.auth_request_validator import WriteRequestValidator
+from plenum.common.constants import LEDGERS_FREEZE
+from plenum.common.request import Request
+from plenum.server.database_manager import DatabaseManager
+from plenum.server.request_handlers.ledgers_freeze.ledgers_freeze_handler import LedgersFreezeHandler as PLedgersFreezeHandler
+
+
+class LedgersFreezeHandler(PLedgersFreezeHandler):
+
+    def __init__(self, database_manager: DatabaseManager,
+                 write_req_validator: WriteRequestValidator):
+        super().__init__(database_manager)
+        self.write_req_validator = write_req_validator
+
+    def authorize(self, request):
+        self.write_req_validator.validate(request,
+                                          [AuthActionEdit(txn_type=LEDGERS_FREEZE,
+                                                          field='*',
+                                                          old_value='*',
+                                                          new_value='*')])
diff --git a/indy_node/server/request_handlers/config_req_handlers/node_upgrade_handler.py b/indy_node/server/request_handlers/config_req_handlers/node_upgrade_handler.py
index f0cb00241..28b96d8ea 100644
--- a/indy_node/server/request_handlers/config_req_handlers/node_upgrade_handler.py
+++ b/indy_node/server/request_handlers/config_req_handlers/node_upgrade_handler.py
@@ -16,7 +16,7 @@ def __init__(self, database_manager: DatabaseManager):
     def update_state(self, txn, prev_result, request, is_committed=False):
         pass
 
-    def dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
         pass
 
     def static_validation(self, request: Request):
diff --git a/indy_node/server/request_handlers/config_req_handlers/pool_config_handler.py b/indy_node/server/request_handlers/config_req_handlers/pool_config_handler.py
index 2e046fee0..d65adbb96 100644
--- a/indy_node/server/request_handlers/config_req_handlers/pool_config_handler.py
+++ b/indy_node/server/request_handlers/config_req_handlers/pool_config_handler.py
@@ -22,7 +22,7 @@ def __init__(self, database_manager: DatabaseManager,
     def static_validation(self, request: Request):
         self._validate_request_type(request)
 
-    def dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
         self._validate_request_type(request)
         action = '*'
         status = '*'
diff --git a/indy_node/server/request_handlers/config_req_handlers/pool_upgrade_handler.py b/indy_node/server/request_handlers/config_req_handlers/pool_upgrade_handler.py
index 5d31c74b4..f68bb2a69 100644
--- a/indy_node/server/request_handlers/config_req_handlers/pool_upgrade_handler.py
+++ b/indy_node/server/request_handlers/config_req_handlers/pool_upgrade_handler.py
@@ -48,7 +48,7 @@ def static_validation(self, request: Request):
                                            "{} not a valid schedule since {}".
                                            format(schedule, msg))
 
-    def dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
         self._validate_request_type(request)
         identifier, req_id, operation = get_request_data(request)
         status = '*'
diff --git a/indy_node/server/request_handlers/domain_req_handlers/attribute_handler.py b/indy_node/server/request_handlers/domain_req_handlers/attribute_handler.py
index 1afe389f8..f682a5e3e 100644
--- a/indy_node/server/request_handlers/domain_req_handlers/attribute_handler.py
+++ b/indy_node/server/request_handlers/domain_req_handlers/attribute_handler.py
@@ -48,14 +48,14 @@ def static_validation(self, request: Request):
                 get_key = attrib_raw_data_serializer.deserialize(operation[RAW])
                 if len(get_key) == 0:
                     raise InvalidClientRequest(identifier, request.reqId,
-                                               '"row" attribute field must contain non-empty dict'.
+                                               '"row" attribute field must contain non-empty dict {}'.
                                                format(TARGET_NYM))
             except JSONDecodeError:
                 raise InvalidClientRequest(identifier, request.reqId,
-                                           'Attribute field must be dict while adding it as a row field'.
+                                           'Attribute field must be dict while adding it as a row field {}'.
                                            format(TARGET_NYM))
 
-    def dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
         self._validate_request_type(request)
 
         identifier, req_id, operation = get_request_data(request)
diff --git a/indy_node/server/request_handlers/domain_req_handlers/claim_def_handler.py b/indy_node/server/request_handlers/domain_req_handlers/claim_def_handler.py
index 5e1b1855e..b8b8528e6 100644
--- a/indy_node/server/request_handlers/domain_req_handlers/claim_def_handler.py
+++ b/indy_node/server/request_handlers/domain_req_handlers/claim_def_handler.py
@@ -29,7 +29,7 @@ def __init__(self, database_manager: DatabaseManager,
     def static_validation(self, request: Request):
         pass
 
-    def dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
         # we can not add a Claim Def with existent ISSUER_DID
         # sine a Claim Def needs to be identified by seqNo
         self._validate_request_type(request)
diff --git a/indy_node/server/request_handlers/domain_req_handlers/idr_cache_nym_handler.py b/indy_node/server/request_handlers/domain_req_handlers/idr_cache_nym_handler.py
index b796b45ba..988f195ca 100644
--- a/indy_node/server/request_handlers/domain_req_handlers/idr_cache_nym_handler.py
+++ b/indy_node/server/request_handlers/domain_req_handlers/idr_cache_nym_handler.py
@@ -36,7 +36,7 @@ def update_state(self, txn, prev_result, request, is_committed=False):
     def static_validation(self, request):
         pass
 
-    def dynamic_validation(self, request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request, req_pp_time: Optional[int]):
         pass
 
     def gen_state_key(self, txn):
diff --git a/indy_node/server/request_handlers/domain_req_handlers/nym_handler.py b/indy_node/server/request_handlers/domain_req_handlers/nym_handler.py
index 3ea822cbd..5107ee588 100644
--- a/indy_node/server/request_handlers/domain_req_handlers/nym_handler.py
+++ b/indy_node/server/request_handlers/domain_req_handlers/nym_handler.py
@@ -42,7 +42,7 @@ def static_validation(self, request: Request):
                                        "{} not a valid role".
                                        format(role))
 
-    def dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
         self._validate_request_type(request)
         operation = request.operation
 
diff --git a/indy_node/server/request_handlers/domain_req_handlers/revoc_reg_def_handler.py b/indy_node/server/request_handlers/domain_req_handlers/revoc_reg_def_handler.py
index f387cfe5f..2a17f5de8 100644
--- a/indy_node/server/request_handlers/domain_req_handlers/revoc_reg_def_handler.py
+++ b/indy_node/server/request_handlers/domain_req_handlers/revoc_reg_def_handler.py
@@ -40,7 +40,7 @@ def static_validation(self, request: Request):
                                        "Expected: 'did:marker:signature_type:schema_ref' or "
                                        "'did:marker:signature_type:schema_ref:tag'".format(CRED_DEF_ID))
 
-    def dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
         self._validate_request_type(request)
         operation = request.operation
         cred_def_id = operation.get(CRED_DEF_ID)
diff --git a/indy_node/server/request_handlers/domain_req_handlers/revoc_reg_entry_handler.py b/indy_node/server/request_handlers/domain_req_handlers/revoc_reg_entry_handler.py
index 05ac52613..61b7b3951 100644
--- a/indy_node/server/request_handlers/domain_req_handlers/revoc_reg_entry_handler.py
+++ b/indy_node/server/request_handlers/domain_req_handlers/revoc_reg_entry_handler.py
@@ -28,7 +28,7 @@ def __init__(self, database_manager: DatabaseManager,
     def static_validation(self, request: Request):
         pass
 
-    def dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
         self._validate_request_type(request)
         rev_reg_tags = request.operation[REVOC_REG_DEF_ID]
         author_did, req_id, operation = get_request_data(request)
diff --git a/indy_node/server/request_handlers/domain_req_handlers/rich_schema/abstract_rich_schema_object_handler.py b/indy_node/server/request_handlers/domain_req_handlers/rich_schema/abstract_rich_schema_object_handler.py
index 0c3530fb4..b6b171ebc 100644
--- a/indy_node/server/request_handlers/domain_req_handlers/rich_schema/abstract_rich_schema_object_handler.py
+++ b/indy_node/server/request_handlers/domain_req_handlers/rich_schema/abstract_rich_schema_object_handler.py
@@ -72,7 +72,7 @@ def do_static_validation_json_ld(self, content_as_dict, request):
             raise InvalidClientRequest(request.identifier, request.reqId,
                                        "content's @id must be equal to id={}".format(request.operation[RS_ID]))
 
-    def dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
         self._validate_request_type(request)
 
         rs_id = request.operation[RS_ID]
diff --git a/indy_node/server/request_handlers/domain_req_handlers/schema_handler.py b/indy_node/server/request_handlers/domain_req_handlers/schema_handler.py
index fe34d2039..4702bc2dd 100644
--- a/indy_node/server/request_handlers/domain_req_handlers/schema_handler.py
+++ b/indy_node/server/request_handlers/domain_req_handlers/schema_handler.py
@@ -27,7 +27,7 @@ def __init__(self, database_manager: DatabaseManager,
     def static_validation(self, request: Request):
         pass
 
-    def dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
+    def additional_dynamic_validation(self, request: Request, req_pp_time: Optional[int]):
         # we can not add a Schema with already existent NAME and VERSION
         # sine a Schema needs to be identified by seqNo
         self._validate_request_type(request)
diff --git a/indy_node/utils/node_control_tool.py b/indy_node/utils/node_control_tool.py
index 5899e1f6e..8138bc582 100644
--- a/indy_node/utils/node_control_tool.py
+++ b/indy_node/utils/node_control_tool.py
@@ -216,7 +216,7 @@ def _do_upgrade(
                 .format(curr_pkg_ver, new_pkg_ver, ex)
             )
             if rollback:
-                logger.error("Trying to rollback to the previous version {}"
+                logger.error("Trying to rollback to the previous version {} {}"
                              .format(ex, curr_pkg_ver))
                 self._do_upgrade(
                     pkg_name, new_pkg_ver, curr_pkg_ver, rollback=False)
diff --git a/indy_node/utils/node_control_utils.py b/indy_node/utils/node_control_utils.py
index 318e1fa9e..0a4b8cd2a 100644
--- a/indy_node/utils/node_control_utils.py
+++ b/indy_node/utils/node_control_utils.py
@@ -298,7 +298,7 @@ def get_latest_pkg_version(
                 ['apt-cache', 'show', pkg_name, '|', 'grep', '-E', "'^Version: '"]
             )
             output = cls.run_shell_script_extended(cmd).strip()
-        except ShellError as exc:
+        except ShellError:
             # will fail if either package not found or grep returns nothing
             # the latter is unexpected and treated as no-data as well
             logger.info(
diff --git a/pool_automation/scripts/namespace-config.py b/pool_automation/scripts/namespace-config.py
index 6a0400c24..2b3ac1e1f 100644
--- a/pool_automation/scripts/namespace-config.py
+++ b/pool_automation/scripts/namespace-config.py
@@ -133,8 +133,7 @@ def _dump(self, stream):
 
             if role_ref.defaults:
                 _s = yaml.safe_dump(role_ref.defaults, default_flow_style=False)
-                stream.write(''.join(["\n# defaults\n\n"] +
-                             ["#{}".format(_l) for _l in _s.splitlines(True)]))
+                stream.write(''.join(["\n# defaults\n\n"] + ["#{}".format(_l) for _l in _s.splitlines(True)]))
 
 
 def _load_roles():
diff --git a/runner.py b/runner.py
index c69dfce9d..3f68ef246 100644
--- a/runner.py
+++ b/runner.py
@@ -14,13 +14,13 @@ def get_first_level_path(path):
     return "/".join(parts[:3])  # <package-name>/test/<dir>
 
 
-def run(pytest, output_file, repeatUntilFailure, testDir, test_slice):
+def run(pytest, output_file, repeatUntilFailure, testDir, test_slice, test_results):
     if repeatUntilFailure:
         log("'repeatUntilFailure' is set")
         log("Is going to repeat the test suite until failure")
-    log("Preparing test suite with {}".format(pytest))
+    log("Preparing test suite with python3 -m pytest -q")
     testListFile = "test_list.txt"
-    collect_status = os.system('{} --collect-only {} > {}'.format(pytest, testDir, testListFile))
+    collect_status = os.system('python3 -m pytest -q --collect-only {} > {}'.format(testDir, testListFile))
 
     if collect_status != 0:
         log("Test suit preparation error {}".format(collect_status))
@@ -37,7 +37,7 @@ def run(pytest, output_file, repeatUntilFailure, testDir, test_slice):
         log(collectedData)
         return -1
 
-    testList = re.findall("<Module '(.+)'>", collectedData)
+    testList = re.findall('({}.*?)::'.format(testDir), collectedData)
     # testList = list(set(os.path.dirname(t) for t in testList))
     first_level_tests = set()
     for path in testList:
@@ -45,7 +45,8 @@ def run(pytest, output_file, repeatUntilFailure, testDir, test_slice):
         first_level_tests.add(p)
     first_level_tests = sorted(list(first_level_tests))
     test_list_sliced = first_level_tests[test_slice[0] - 1::test_slice[1]]
-    log("Found {} tests in {} modules, sliced {} test modules".format(len(testList), len(first_level_tests), len(test_list_sliced)))
+    log("Found {} tests in {} modules, sliced {} test modules".
+        format(len(testList), len(first_level_tests), len(test_list_sliced)))
     if not testList:
         m = re.search("errors during collection", collectedData)
         if m:
@@ -69,9 +70,12 @@ def run(pytest, output_file, repeatUntilFailure, testDir, test_slice):
     errorTestPat = re.compile('____ (ERROR.+) ____')
     while True:
         for i, tests in enumerate(test_list_sliced):
-            # testRep = '{}.rep'.format(test.split("/")[-1])
+            if test_results:
+                testResults = '--junitxml={}-test-results.xml'.format(tests.split("/")[-1])
+                pytest_cmd = '{} {} {} > {}'.format(pytest, testResults, tests, testRep)
+            else:
+                pytest_cmd = '{} {} > {}'.format(pytest, tests, testRep)
             testStartTime = time.time()
-            pytest_cmd = '{} {} > {}'.format(pytest, tests, testRep)
             log(pytest_cmd)
             r = os.system(pytest_cmd)
             testExecutionTime = time.time() - testStartTime
@@ -207,12 +211,16 @@ def parse_test_slice(x):
                         default=(1, 1),
                         help='example: `1/3` -- run only first third part of the tests, \
                         it is needed only for parallel testing')
+    parser.add_argument('--nojunitxml',
+                        help='do not record junitxml test results', default=False, action="store_true")
+
     args = parser.parse_args()
     r = run(
         pytest=args.pytest,
         output_file=args.output if not args.nooutput else None,
         repeatUntilFailure=args.repeatUntilFailure,
         testDir=args.dir,
-        test_slice=args.test_slice
+        test_slice=args.test_slice,
+        test_results=not args.nojunitxml
     )
     sys.exit(0 if r == 0 else 1)
diff --git a/scripts/performance/perf_load/perf_processes.py b/scripts/performance/perf_load/perf_processes.py
index a77bdde54..13a61faa7 100755
--- a/scripts/performance/perf_load/perf_processes.py
+++ b/scripts/performance/perf_load/perf_processes.py
@@ -371,7 +371,7 @@ def screen_stat(self):
         self._loop.call_later(self._refresh_rate, self.screen_stat)
 
     def schedule_stop(self):
-        self._logger.debug("schedule_stop _stop_sec".format(self._stop_sec))
+        self._logger.debug("schedule_stop _stop_sec {}".format(self._stop_sec))
         if self._stop_sec > 0:
             self._loop.call_later(self._stop_sec, self.sig_handler, signal.SIGINT)
 
diff --git a/scripts/remove_ledger.py b/scripts/remove_ledger.py
new file mode 100644
index 000000000..c8d36e4cf
--- /dev/null
+++ b/scripts/remove_ledger.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+
+from pathlib import Path
+import shutil
+from sys import argv
+from indy_common.config_util import getConfig
+from indy_common.config_helper import ConfigHelper
+
+
+def warn(ledger_name, directories_path):
+    print('The following directories will be deleted:')
+
+    for path in directories_path:
+        print(str(path))
+
+    print('Deleting a ledger is an irrevocable operation.\nProceed only if you know the consequences.')
+    answer = input('Do you want to delete ledger ' + ledger_name + '?\n Press [y/N]')
+
+    if answer.lower() == 'yes' or answer.lower() == 'y':
+        return True
+
+    return False
+
+
+def remove(ledger_name):
+    exceptions = ["domain", "config", "pool", "audit"]
+    if ledger_name not in exceptions:
+        directories_path = []
+
+        for path in Path(config_helper.ledger_data_dir).rglob(ledger_name + "_*"):
+            directories_path.append(path)
+
+        if not len(directories_path):
+            print('Ledger doesn`t exist: ' + ledger_name)
+
+        elif warn(ledger_name, directories_path):
+            for path in directories_path:
+                shutil.rmtree(str(path))
+            print('Ledger removed successfully!')
+
+    else:
+        print('Can`t delete built in ledger: ' + ledger_name)
+
+
+if __name__ == '__main__':
+    config = getConfig()
+    config_helper = ConfigHelper(config)
+    script, ledger_name = argv
+    remove(ledger_name)
diff --git a/setup.py b/setup.py
index 2ca73c29f..6fec65897 100644
--- a/setup.py
+++ b/setup.py
@@ -6,8 +6,8 @@
 from setuptools import setup, find_packages
 
 v = sys.version_info
-if sys.version_info < (3, 5):
-    msg = "FAIL: Requires Python 3.5 or later, " \
+if sys.version_info < (3, 8):
+    msg = "FAIL: Requires Python 3.8 or later, " \
           "but setup.py was run using {}.{}.{}"
     v = sys.version_info
     print(msg.format(v.major, v.minor, v.micro))
@@ -28,7 +28,7 @@
 BASE_DIR = os.path.join(os.path.expanduser("~"), ".indy")
 
 tests_require = ['attrs>=20.3.0', 'pytest>=6.2.2', 'pytest-xdist>=2.2.1', 'pytest-forked>=1.3.0',
-                 'python3-indy==1.13.0-dev-1420', 'pytest-asyncio>=0.14.0']
+                 'python3-indy==1.15.0-dev-1625', 'pytest-asyncio>=0.14.0']
 
 setup(
     name=metadata['__title__'],
@@ -54,7 +54,9 @@
     data_files=[(
         (BASE_DIR, ['data/nssm_original.exe'])
     )],
-    install_requires=['indy-plenum==1.13.0.dev1032', 'timeout-decorator>=0.5.0', 'distro>=1.5.0'],
+    install_requires=['indy-plenum==1.13.0.dev135',
+                      'timeout-decorator>=0.5.0',
+                      'distro>=1.5.0'],
     setup_requires=['pytest-runner'],
     extras_require={
         'tests': tests_require


Test patch:
diff --git a/indy_common/test/auth/test_auth_constraint.py b/indy_common/test/auth/test_auth_constraint.py
index fd6828d9b..91075cfdd 100644
--- a/indy_common/test/auth/test_auth_constraint.py
+++ b/indy_common/test/auth/test_auth_constraint.py
@@ -62,7 +62,7 @@ def test_str_any_1_sig_not_owner():
     constraint = AuthConstraint(role='*',
                                 sig_count=1,
                                 need_to_be_owner=False)
-    assert str(constraint) == '1 signature of any role is required'
+    assert str(constraint) == '1 signature of any role is required ALL'
 
 
 def test_str_any_several_sig_not_owner():
diff --git a/indy_common/test/auth/test_auth_nym_with_new_auth_map.py b/indy_common/test/auth/test_auth_nym_with_new_auth_map.py
index 3e10c7319..d3cd4788a 100644
--- a/indy_common/test/auth/test_auth_nym_with_new_auth_map.py
+++ b/indy_common/test/auth/test_auth_nym_with_new_auth_map.py
@@ -306,6 +306,6 @@ def test_change_verkey(write_request_validation, req, is_owner):
     assert authorized == write_request_validation(req,
                                                   [AuthActionEdit(txn_type=NYM,
                                                                   field=VERKEY,
-                                                                  old_value="_verkey".format(req.identifier),
+                                                                  old_value="_verkey {}".format(req.identifier),
                                                                   new_value='new_value',
                                                                   is_owner=is_owner)])
diff --git a/indy_node/test/auth_rule/auth_framework/edit_frozen_ledgers.py b/indy_node/test/auth_rule/auth_framework/edit_frozen_ledgers.py
new file mode 100644
index 000000000..8dd0d3801
--- /dev/null
+++ b/indy_node/test/auth_rule/auth_framework/edit_frozen_ledgers.py
@@ -0,0 +1,79 @@
+import pytest
+from plenum.common.constants import STEWARD, TRUSTEE_STRING, LEDGERS_FREEZE
+
+from indy_node.server.request_handlers.action_req_handlers.pool_restart_handler import PoolRestartHandler
+
+from indy_common.authorize.auth_actions import EDIT_PREFIX
+from indy_common.authorize.auth_constraints import AuthConstraint
+from indy_node.test.auth_rule.auth_framework.basic import AuthTest
+from plenum.common.exceptions import RequestRejectedException
+from plenum.test.freeze_ledgers.helper import sdk_send_freeze_ledgers
+from plenum.test.pool_transactions.helper import sdk_add_new_nym
+
+from indy_node.test.helper import build_auth_rule_request_json, sdk_send_and_check_req_json
+
+
+class EditFrozenLedgersTest(AuthTest):
+    def __init__(self, env, action_id):
+        super().__init__(env, action_id)
+        self.trustee_wallets = [self.trustee_wallet]
+
+    def prepare(self):
+        for i in range(3):
+            wallet = sdk_add_new_nym(self.looper,
+                                     self.sdk_pool_handle,
+                                     self.trustee_wallet,
+                                     alias='trustee{}'.format(i),
+                                     role=TRUSTEE_STRING)
+            self.trustee_wallets.append(wallet)
+        self.default_auth_rule = self.get_default_auth_rule()
+        self.changed_auth_rule = self.get_changed_auth_rule()
+        for n in self.env.txnPoolNodeSet:
+            for h in n.action_manager.request_handlers.values():
+                if isinstance(h, PoolRestartHandler):
+                    h.restarter.handleRestartRequest = lambda *args, **kwargs: True
+
+    def run(self):
+        frozen_ledgers_ids = []
+
+        # Step 1. Check default auth rule
+        sdk_send_freeze_ledgers(self.looper, self.sdk_pool_handle, self.trustee_wallets, frozen_ledgers_ids)
+        with pytest.raises(RequestRejectedException):
+            sdk_send_freeze_ledgers(self.looper, self.sdk_pool_handle, [self.new_default_wallet], frozen_ledgers_ids)
+
+        # Step 2. Change auth rule
+        self.send_and_check(self.changed_auth_rule, wallet=self.trustee_wallet)
+
+        # Step 3. Check, that we cannot send txn the old way
+        sdk_send_freeze_ledgers(self.looper, self.sdk_pool_handle, [self.new_default_wallet], frozen_ledgers_ids)
+        with pytest.raises(RequestRejectedException):
+            sdk_send_freeze_ledgers(self.looper, self.sdk_pool_handle, self.trustee_wallets, frozen_ledgers_ids)
+
+        # Step 4. Check, that we can send restart action in changed way
+        sdk_send_freeze_ledgers(self.looper, self.sdk_pool_handle, [self.new_default_wallet], frozen_ledgers_ids)
+
+        # Step 5. Return default auth rule
+        self.send_and_check(self.default_auth_rule, self.trustee_wallet)
+
+        # Step 6. Check, that default auth rule works
+        sdk_send_freeze_ledgers(self.looper, self.sdk_pool_handle, self.trustee_wallets, frozen_ledgers_ids)
+        with pytest.raises(RequestRejectedException):
+            sdk_send_freeze_ledgers(self.looper, self.sdk_pool_handle, [self.new_default_wallet], frozen_ledgers_ids)
+
+    def result(self):
+        pass
+
+    def get_changed_auth_rule(self):
+        self.new_default_wallet = sdk_add_new_nym(self.looper, self.sdk_pool_handle, self.trustee_wallet, role=STEWARD)
+        constraint = AuthConstraint(role=STEWARD,
+                                    sig_count=1,
+                                    need_to_be_owner=False)
+        return build_auth_rule_request_json(
+            self.looper, self.trustee_wallet[1],
+            auth_action=EDIT_PREFIX,
+            auth_type=LEDGERS_FREEZE,
+            field='*',
+            old_value='*',
+            new_value='*',
+            constraint=constraint.as_dict
+        )
diff --git a/indy_node/test/auth_rule/auth_framework/test_auth_rule_using.py b/indy_node/test/auth_rule/auth_framework/test_auth_rule_using.py
index 2f7341461..8744fca1b 100644
--- a/indy_node/test/auth_rule/auth_framework/test_auth_rule_using.py
+++ b/indy_node/test/auth_rule/auth_framework/test_auth_rule_using.py
@@ -4,6 +4,7 @@
 from collections import OrderedDict
 
 from indy_node.test.auth_rule.auth_framework.disable_taa import TAADisableTest
+from indy_node.test.auth_rule.auth_framework.edit_frozen_ledgers import EditFrozenLedgersTest
 from plenum.common.constants import STEWARD, TRUSTEE, IDENTITY_OWNER
 
 from indy_common.constants import (
@@ -123,6 +124,7 @@ class TestAuthRuleUsing():
         auth_map.change_client_port.get_action_id(): EditNodeClientPortTest,
         auth_map.change_bls_key.get_action_id(): EditNodeBlsTest,
         auth_map.disable_txn_author_agreement.get_action_id(): TAADisableTest,
+        auth_map.edit_frozen_ledgers.get_action_id(): EditFrozenLedgersTest,
     })
 
     # TODO a workaround until sdk aceepts empty TAA to make possible its deactivation
diff --git a/indy_node/test/auth_rule/test_auth_txn_with_deprecated_key.py b/indy_node/test/auth_rule/test_auth_txn_with_deprecated_key.py
new file mode 100644
index 000000000..d2c270b29
--- /dev/null
+++ b/indy_node/test/auth_rule/test_auth_txn_with_deprecated_key.py
@@ -0,0 +1,150 @@
+import shutil
+from contextlib import contextmanager
+
+import pytest
+
+from indy_common.config_helper import NodeConfigHelper
+from indy_node.test.helper import TestNode
+from plenum.test.node_catchup.helper import ensure_all_nodes_have_same_data
+from plenum.test.test_node import ensureElectionsDone, ensure_node_disconnected, checkNodesConnected
+from indy_node.test.auth_rule.helper import sdk_send_and_check_auth_rule_request, sdk_send_and_check_get_auth_rule_request
+from indy_common.authorize.auth_actions import ADD_PREFIX, AuthActionAdd
+from indy_common.authorize.auth_constraints import AuthConstraint, ROLE
+from indy_common.constants import CONSTRAINT, AUTH_TYPE, CONFIG_LEDGER_ID, NYM
+from indy_common.authorize.auth_map import one_trustee_constraint
+from plenum.common.constants import STEWARD, DATA
+from plenum.common.exceptions import RequestNackedException
+
+
+@contextmanager
+def extend_auth_map(nodes, key, constraint):
+    """
+    Context manager to add a new auth rule to the auth map and remove it on exit.
+
+    :param nodes: nodes list which auth maps should be changed
+    :param key: str gotten from AuthActionAdd(...).get_action_id()
+    :param constraint: AuthConstraint
+    """
+    for node in nodes:
+        node.write_req_validator.auth_map[key] = constraint
+    yield
+    for node in nodes:
+        node.write_req_validator.auth_map.pop(key, None)
+
+
+def test_auth_txn_with_deprecated_key(tconf, tdir, allPluginsPath,
+                                      txnPoolNodeSet,
+                   looper,
+                   sdk_wallet_trustee,
+                   sdk_pool_handle):
+    """
+    Add to the auth_map a fake rule
+    Send AUTH_RULE txn to change this fake rule (and set the fake key to the config state)
+    Send GET_AUTH_RULE txn and check that the fake rule was changed
+    Remove the fake auth rule from the map
+    Check that we can't get the fake auth rule
+    Restart the last node with its state regeneration
+    Check that nodes data is equal after changing the existing auth rule (restarted node regenerate config state)
+    """
+
+    fake_txn_type = "100002"
+    fake_key = AuthActionAdd(txn_type=fake_txn_type,
+                                field="*",
+                                value="*").get_action_id()
+    fake_constraint = one_trustee_constraint
+    new_auth_constraint = AuthConstraint(role=STEWARD, sig_count=1, need_to_be_owner=False).as_dict
+
+    # Add to the auth_map a fake rule
+    with extend_auth_map(txnPoolNodeSet,
+                         fake_key,
+                         fake_constraint):
+        # Send AUTH_RULE txn to change this fake rule (and set the fake key to the config state)
+        sdk_send_and_check_auth_rule_request(looper,
+                                             sdk_pool_handle,
+                                             sdk_wallet_trustee,
+                                             auth_action=ADD_PREFIX,
+                                             auth_type=fake_txn_type,
+                                             field='*',
+                                             new_value='*',
+                                             constraint=new_auth_constraint)
+        # Send GET_AUTH_RULE txn and check that the fake rule was changed
+        result = sdk_send_and_check_get_auth_rule_request(
+            looper,
+            sdk_pool_handle,
+            sdk_wallet_trustee,
+            auth_type=fake_txn_type,
+            auth_action=ADD_PREFIX,
+            field="*",
+            new_value="*"
+        )[0][1]["result"][DATA][0]
+        assert result[AUTH_TYPE] == fake_txn_type
+        assert result[CONSTRAINT] == new_auth_constraint
+
+    # Remove the fake auth rule from the map
+    # Check that we can't get the fake auth rule
+    with pytest.raises(RequestNackedException, match="not found in authorization map"):
+        sdk_send_and_check_auth_rule_request(looper,
+                                         sdk_pool_handle,
+                                         sdk_wallet_trustee,
+                                         auth_action=ADD_PREFIX,
+                                         auth_type=fake_txn_type,
+                                         field='*',
+                                         new_value='*',
+                                         constraint=AuthConstraint(role=STEWARD, sig_count=2,
+                                                                   need_to_be_owner=False).as_dict)
+
+    resp = sdk_send_and_check_get_auth_rule_request(looper,
+                                                    sdk_pool_handle,
+                                                    sdk_wallet_trustee)
+
+    assert all(rule[AUTH_TYPE] != fake_txn_type for rule in resp[0][1]["result"][DATA])
+
+    with pytest.raises(RequestNackedException, match="not found in authorization map"):
+        sdk_send_and_check_get_auth_rule_request(
+            looper,
+            sdk_pool_handle,
+            sdk_wallet_trustee,
+            auth_type=fake_txn_type,
+            auth_action=ADD_PREFIX,
+            field="*",
+            new_value="*"
+        )
+    # Restart the last node with its state regeneration
+    ensure_all_nodes_have_same_data(looper, txnPoolNodeSet)
+
+    node_to_stop = txnPoolNodeSet[-1]
+    node_state = node_to_stop.states[CONFIG_LEDGER_ID]
+    assert not node_state.isEmpty
+    state_db_path = node_state._kv.db_path
+    node_to_stop.cleanupOnStopping = False
+    node_to_stop.stop()
+    looper.removeProdable(node_to_stop)
+    ensure_node_disconnected(looper, node_to_stop, txnPoolNodeSet[:-1])
+
+    shutil.rmtree(state_db_path)
+
+    config_helper = NodeConfigHelper(node_to_stop.name, tconf, chroot=tdir)
+    restarted_node = TestNode(
+        node_to_stop.name,
+        config_helper=config_helper,
+        config=tconf,
+        pluginPaths=allPluginsPath,
+        ha=node_to_stop.nodestack.ha,
+        cliha=node_to_stop.clientstack.ha)
+    looper.add(restarted_node)
+    txnPoolNodeSet[-1] = restarted_node
+
+    # Check that nodes data is equal (restarted node regenerate config state)
+    looper.run(checkNodesConnected(txnPoolNodeSet))
+    ensureElectionsDone(looper, txnPoolNodeSet, customTimeout=30)
+    sdk_send_and_check_auth_rule_request(looper,
+                                         sdk_pool_handle,
+                                         sdk_wallet_trustee,
+                                         auth_action=ADD_PREFIX,
+                                         auth_type=NYM,
+                                         field=ROLE,
+                                         new_value=STEWARD,
+                                         constraint=AuthConstraint(role=STEWARD, sig_count=2,
+                                                                   need_to_be_owner=False).as_dict)
+    ensure_all_nodes_have_same_data(looper, txnPoolNodeSet, custom_timeout=20)
+
diff --git a/indy_node/test/auth_rule/test_catching_up_auth_rule_txn.py b/indy_node/test/auth_rule/test_catching_up_auth_rule_txn.py
index 0b2e09f75..45da913c9 100644
--- a/indy_node/test/auth_rule/test_catching_up_auth_rule_txn.py
+++ b/indy_node/test/auth_rule/test_catching_up_auth_rule_txn.py
@@ -43,12 +43,16 @@ def test_catching_up_auth_rule_txn(looper,
                                              auth_type=action.txn_type, field=action.field,
                                              new_value=action.value, old_value=None,
                                              constraint=changed_constraint.as_dict)
+        sdk_add_new_nym(looper,
+                        sdk_pool_handle,
+                        sdk_wallet_trustee,
+                        'newSteward2')
         delayed_node.start_catchup()
         looper.run(eventually(lambda: assertExp(delayed_node.mode == Mode.participating)))
     sdk_add_new_nym(looper,
                     sdk_pool_handle,
                     sdk_wallet_steward,
-                    'newSteward2',
+                    'newSteward3',
                     STEWARD_STRING,
                     dest=new_steward_did, verkey=new_steward_verkey)
     ensure_all_nodes_have_same_data(looper, txnPoolNodeSet)
diff --git a/indy_node/test/freeze_ledgers/__init__.py b/indy_node/test/freeze_ledgers/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/indy_node/test/freeze_ledgers/test_send_ledgers_freeze.py b/indy_node/test/freeze_ledgers/test_send_ledgers_freeze.py
new file mode 100644
index 000000000..2f692fef7
--- /dev/null
+++ b/indy_node/test/freeze_ledgers/test_send_ledgers_freeze.py
@@ -0,0 +1,39 @@
+import pytest
+from plenum.common.constants import DATA
+from plenum.common.exceptions import RequestRejectedException
+from plenum.test.freeze_ledgers.helper import sdk_get_frozen_ledgers, sdk_send_freeze_ledgers
+from plenum.test.helper import freshness
+
+FRESHNESS_TIMEOUT = 5
+
+
+@pytest.fixture(scope="module")
+def tconf(tconf):
+    with freshness(tconf, enabled=True, timeout=FRESHNESS_TIMEOUT):
+        yield tconf
+
+
+def test_send_freeze_ledgers(looper, txnPoolNodeSet, sdk_pool_handle, sdk_wallet_trustee_list):
+    with pytest.raises(RequestRejectedException, match="Not enough TRUSTEE signatures"):
+        sdk_send_freeze_ledgers(
+            looper, sdk_pool_handle,
+            sdk_wallet_trustee_list[:-1],
+            []
+        )
+
+    # check that the config state doesn't contain frozen ledgers records
+    result = sdk_get_frozen_ledgers(looper, sdk_pool_handle,
+                                    sdk_wallet_trustee_list[0])[1]["result"][DATA]
+    assert result is None
+
+    # add to the config state a frozen ledgers record with an empty list
+    sdk_send_freeze_ledgers(
+        looper, sdk_pool_handle,
+        sdk_wallet_trustee_list,
+        []
+    )
+
+    # check that the config state contains a frozen ledgers record with an empty list
+    result = sdk_get_frozen_ledgers(looper, sdk_pool_handle,
+                                    sdk_wallet_trustee_list[0])[1]["result"][DATA]
+    assert len(result) == 0
diff --git a/pool_automation/roles/aws_manage/library/test_stateful_set.py b/pool_automation/roles/aws_manage/library/test_stateful_set.py
index 49094f64e..446b7ccf5 100644
--- a/pool_automation/roles/aws_manage/library/test_stateful_set.py
+++ b/pool_automation/roles/aws_manage/library/test_stateful_set.py
@@ -189,7 +189,7 @@ def on_demand_prices(request, pricing_client, ec2_prices,
 
 @pytest.fixture
 def ec2ctxs(regions, ec2, ec2cl, on_demand_prices, ec2_prices):
-    assert len(set([len(l) for l in (regions, ec2, ec2cl)])) == 1
+    assert len(set([len(j) for j in (regions, ec2, ec2cl)])) == 1
     return [EC2TestCtx(r, rc, cl, ec2_prices[r]) for r, rc, cl
             in zip(regions, ec2, ec2cl)]
 
diff --git a/pool_automation/roles/perf_scripts/molecule/resources/tests/base/test_installed.py b/pool_automation/roles/perf_scripts/molecule/resources/tests/base/test_installed.py
index cf029d6fa..00963f6e1 100644
--- a/pool_automation/roles/perf_scripts/molecule/resources/tests/base/test_installed.py
+++ b/pool_automation/roles/perf_scripts/molecule/resources/tests/base/test_installed.py
@@ -27,8 +27,7 @@ def test_correct_python_packages_are_installed_inside_venv(host, ansible_vars, v
 
     # TODO python3-indy's package metadata doesn't match package version
     # for non-stable packages, thus the check will fail for them
-    if (ansible_vars['perf_scripts_python3_indy_ver'] is not None and
-            'dev' not in ansible_vars['perf_scripts_python3_indy_ver']):
+    if (ansible_vars['perf_scripts_python3_indy_ver'] is not None and 'dev' not in ansible_vars['perf_scripts_python3_indy_ver']):
         assert pip_packages['python3-indy']['version'] == ansible_vars['perf_scripts_python3_indy_ver']